Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
thermal-control-system
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
luwei
thermal-control-system
Commits
3faede64
Commit
3faede64
authored
Apr 27, 2026
by
luwei
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
功能修改
parent
478c4876
Changes
19
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
686 additions
and
124 deletions
+686
-124
data_management.py
backend/app/api/data_management.py
+14
-0
package_management.py
backend/app/api/package_management.py
+17
-1
__init__.py
backend/app/models/__init__.py
+2
-2
data_management.py
backend/app/models/data_management.py
+24
-1
data_management_service.py
backend/app/services/data_management_service.py
+184
-1
eval_service.py
backend/app/services/eval_service.py
+39
-0
package_management_service.py
backend/app/services/package_management_service.py
+43
-8
DataManagement-D7Mq9-H4.css
frontend/dist/assets/DataManagement-D7Mq9-H4.css
+0
-1
ModelEvaluation-CVr4AavX.js
frontend/dist/assets/ModelEvaluation-CVr4AavX.js
+0
-4
ModelList-DLDTMRtX.js
frontend/dist/assets/ModelList-DLDTMRtX.js
+0
-1
echarts-B4btcaVd.js
frontend/dist/assets/echarts-B4btcaVd.js
+0
-39
es-DCOtnflc.js
frontend/dist/assets/es-DCOtnflc.js
+0
-50
request-D8DwihSV.js
frontend/dist/assets/request-D8DwihSV.js
+0
-9
trainManagement-TFX-G3Jl.js
frontend/dist/assets/trainManagement-TFX-G3Jl.js
+0
-1
index.html
frontend/dist/index.html
+2
-2
dataManagement.js
frontend/src/api/dataManagement.js
+8
-0
index.vue
frontend/src/views/DataManagement/index.vue
+190
-2
AddPackage.vue
...end/src/views/PackageManagement/components/AddPackage.vue
+139
-2
init_tables.sql
sql/init_tables.sql
+24
-0
No files found.
backend/app/api/data_management.py
View file @
3faede64
...
...
@@ -100,6 +100,20 @@ def delete_file(file_id: str):
raise
HTTPException
(
status_code
=
400
,
detail
=
str
(
error
))
from
error
@
router
.
get
(
'/quality-config'
)
def
get_quality_config
():
return
success_response
(
data
=
service
.
get_quality_config
())
@
router
.
get
(
'/files/{file_id}/quality'
)
def
get_file_quality
(
file_id
:
str
):
try
:
result
=
service
.
assess_file_quality
(
file_id
=
file_id
)
return
success_response
(
data
=
result
)
except
ValueError
as
error
:
raise
HTTPException
(
status_code
=
400
,
detail
=
str
(
error
))
from
error
@
router
.
get
(
'/files/{file_id}/records'
)
def
get_file_records
(
file_id
:
str
,
limit
:
int
=
Query
(
default
=
500
,
ge
=
1
,
le
=
5000
)):
try
:
...
...
backend/app/api/package_management.py
View file @
3faede64
...
...
@@ -73,18 +73,30 @@ class PackageCreateRequest(BaseModel):
remark
:
str
|
None
=
Field
(
default
=
None
)
file_ids
:
list
[
int
]
=
Field
(
default_factory
=
list
)
clean_rules
:
CleanRules
|
None
=
Field
(
default
=
None
)
row_start
:
int
|
None
=
Field
(
default
=
None
,
ge
=
1
)
row_end
:
int
|
None
=
Field
(
default
=
None
,
ge
=
1
)
class
PreviewRequest
(
BaseModel
):
file_ids
:
list
[
int
]
=
Field
(
default_factory
=
list
)
clean_rules
:
CleanRules
|
None
=
Field
(
default
=
None
)
row_start
:
int
|
None
=
Field
(
default
=
None
,
ge
=
1
)
row_end
:
int
|
None
=
Field
(
default
=
None
,
ge
=
1
)
@
router
.
post
(
'/preview'
)
def
preview_package
(
request
:
PreviewRequest
,
limit
:
int
=
Query
(
default
=
300
,
ge
=
1
,
le
=
2000
)):
try
:
if
request
.
row_start
and
request
.
row_end
and
request
.
row_start
>
request
.
row_end
:
raise
ValueError
(
'起始行不能大于结束行'
)
clean_rules
=
request
.
clean_rules
.
model_dump
()
if
request
.
clean_rules
else
None
result
=
service
.
preview_records
(
file_ids
=
request
.
file_ids
,
limit
=
limit
,
clean_rules
=
clean_rules
)
result
=
service
.
preview_records
(
file_ids
=
request
.
file_ids
,
limit
=
limit
,
clean_rules
=
clean_rules
,
row_start
=
request
.
row_start
,
row_end
=
request
.
row_end
,
)
return
success_response
(
data
=
result
)
except
ValueError
as
error
:
raise
HTTPException
(
status_code
=
400
,
detail
=
str
(
error
))
from
error
...
...
@@ -101,6 +113,8 @@ def list_packages(
@
router
.
post
(
''
)
def
create_package
(
request
:
PackageCreateRequest
):
try
:
if
request
.
row_start
and
request
.
row_end
and
request
.
row_start
>
request
.
row_end
:
raise
ValueError
(
'起始行不能大于结束行'
)
category_id
=
None
if
request
.
category_id
in
(
None
,
''
,
'all'
)
else
str
(
request
.
category_id
)
clean_rules
=
request
.
clean_rules
.
model_dump
()
if
request
.
clean_rules
else
None
pkg
=
service
.
create_package
(
...
...
@@ -109,6 +123,8 @@ def create_package(request: PackageCreateRequest):
remark
=
request
.
remark
,
file_ids
=
request
.
file_ids
,
clean_rules
=
clean_rules
,
row_start
=
request
.
row_start
,
row_end
=
request
.
row_end
,
)
return
success_response
(
data
=
pkg
,
message
=
'数据包创建成功'
)
except
ValueError
as
error
:
...
...
backend/app/models/__init__.py
View file @
3faede64
from
app.models.data_management
import
Category
,
DataFile
,
DataPackage
,
DataPackageFile
from
app.models.data_management
import
Category
,
DataFile
,
DataPackage
,
DataPackageFile
,
DataQualityConfig
from
app.models.eval_management
import
EvalRecord
from
app.models.train_management
import
SavedModel
,
TrainTask
__all__
=
[
'Category'
,
'DataFile'
,
'DataPackage'
,
'DataPackageFile'
,
'TrainTask'
,
'SavedModel'
,
'EvalRecord'
]
__all__
=
[
'Category'
,
'DataFile'
,
'DataPackage'
,
'DataPackageFile'
,
'
DataQualityConfig'
,
'
TrainTask'
,
'SavedModel'
,
'EvalRecord'
]
backend/app/models/data_management.py
View file @
3faede64
from
sqlalchemy
import
BIGINT
,
TIMESTAMP
,
Enum
,
Index
,
Integer
,
JSON
,
String
,
Text
,
text
from
sqlalchemy
import
BIGINT
,
TIMESTAMP
,
Double
,
Enum
,
Index
,
Integer
,
JSON
,
String
,
Text
,
text
from
sqlalchemy.orm
import
Mapped
,
mapped_column
from
app.database
import
Base
...
...
@@ -59,6 +59,8 @@ class DataPackage(Base):
remark
:
Mapped
[
str
|
None
]
=
mapped_column
(
Text
,
nullable
=
True
,
comment
=
'备注'
)
data_count
:
Mapped
[
int
]
=
mapped_column
(
Integer
,
nullable
=
False
,
server_default
=
text
(
'0'
),
comment
=
'数据条数'
)
clean_rules
:
Mapped
[
dict
|
None
]
=
mapped_column
(
JSON
,
nullable
=
True
,
comment
=
'野值清洗规则'
)
row_start
:
Mapped
[
int
|
None
]
=
mapped_column
(
Integer
,
nullable
=
True
,
comment
=
'数据行起始行号(1-based, 含)'
)
row_end
:
Mapped
[
int
|
None
]
=
mapped_column
(
Integer
,
nullable
=
True
,
comment
=
'数据行结束行号(1-based, 含)'
)
created_at
:
Mapped
[
str
]
=
mapped_column
(
TIMESTAMP
,
nullable
=
False
,
server_default
=
text
(
'CURRENT_TIMESTAMP'
))
updated_at
:
Mapped
[
str
]
=
mapped_column
(
TIMESTAMP
,
...
...
@@ -79,3 +81,24 @@ class DataPackageFile(Base):
package_id
:
Mapped
[
int
]
=
mapped_column
(
BIGINT
,
nullable
=
False
,
comment
=
'数据包ID'
)
file_id
:
Mapped
[
int
]
=
mapped_column
(
BIGINT
,
nullable
=
False
,
comment
=
'数据文件ID'
)
sort_order
:
Mapped
[
int
]
=
mapped_column
(
Integer
,
nullable
=
False
,
server_default
=
text
(
'0'
),
comment
=
'排序'
)
class
DataQualityConfig
(
Base
):
__tablename__
=
'data_quality_config'
__table_args__
=
(
{
'mysql_comment'
:
'数据质量准确性配置表'
},
)
id
:
Mapped
[
int
]
=
mapped_column
(
BIGINT
,
primary_key
=
True
,
autoincrement
=
True
)
field_name
:
Mapped
[
str
]
=
mapped_column
(
String
(
50
),
nullable
=
False
,
unique
=
True
,
comment
=
'字段名'
)
label
:
Mapped
[
str
]
=
mapped_column
(
String
(
100
),
nullable
=
False
,
comment
=
'显示名称'
)
unit
:
Mapped
[
str
]
=
mapped_column
(
String
(
20
),
nullable
=
False
,
server_default
=
text
(
"''"
),
comment
=
'单位'
)
min_value
:
Mapped
[
float
|
None
]
=
mapped_column
(
Double
,
nullable
=
True
,
comment
=
'合规最小值'
)
max_value
:
Mapped
[
float
|
None
]
=
mapped_column
(
Double
,
nullable
=
True
,
comment
=
'合规最大值'
)
created_at
:
Mapped
[
str
]
=
mapped_column
(
TIMESTAMP
,
nullable
=
False
,
server_default
=
text
(
'CURRENT_TIMESTAMP'
))
updated_at
:
Mapped
[
str
]
=
mapped_column
(
TIMESTAMP
,
nullable
=
False
,
server_default
=
text
(
'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
),
)
backend/app/services/data_management_service.py
View file @
3faede64
...
...
@@ -9,7 +9,7 @@ from openpyxl import Workbook, load_workbook
from
sqlalchemy
import
func
from
app.database
import
db_session
from
app.models
import
Category
,
DataFile
from
app.models
import
Category
,
DataFile
,
DataQualityConfig
class
DataManagementService
:
...
...
@@ -436,3 +436,186 @@ class DataManagementService:
workbook
.
save
(
output
)
workbook
.
close
()
return
output
.
getvalue
()
# ── quality ───────────────────────────────────────────────────────────────
def
get_quality_config
(
self
)
->
list
[
dict
[
str
,
Any
]]:
with
db_session
()
as
session
:
rows
=
session
.
query
(
DataQualityConfig
)
.
order_by
(
DataQualityConfig
.
id
.
asc
())
.
all
()
return
[
{
'id'
:
item
.
id
,
'field_name'
:
item
.
field_name
,
'label'
:
item
.
label
,
'unit'
:
item
.
unit
,
'min_value'
:
item
.
min_value
,
'max_value'
:
item
.
max_value
,
}
for
item
in
rows
]
def
assess_file_quality
(
self
,
file_id
:
str
)
->
dict
[
str
,
Any
]:
file_db_id
=
self
.
_parse_int_id
(
file_id
,
'文件ID'
)
with
db_session
()
as
session
:
file_meta
=
session
.
query
(
DataFile
)
.
filter
(
DataFile
.
id
==
file_db_id
)
.
first
()
if
not
file_meta
:
raise
ValueError
(
'文件不存在'
)
target_path
=
self
.
_resolve_local_file_path
(
file_meta
.
file_path
,
file_meta
.
stored_name
)
if
not
target_path
.
exists
():
raise
ValueError
(
'文件已丢失,请重新上传'
)
quality_config
=
self
.
_get_quality_config_map
()
all_rows
=
self
.
_read_all_excel_rows
(
target_path
)
if
not
all_rows
:
return
self
.
_empty_quality_result
()
first_row
=
[
item
.
strip
()
for
item
in
all_rows
[
0
]]
has_header
=
any
(
self
.
_normalize_header
(
item
)
in
{
'time'
,
'current'
,
'voltage'
,
'set_temperature'
,
'actual_temperature'
}
for
item
in
first_row
)
if
has_header
:
header
=
first_row
data_rows
=
all_rows
[
1
:]
else
:
header
=
[
'time'
,
'current'
,
'voltage'
,
'set_temperature'
,
'actual_temperature'
]
data_rows
=
all_rows
index_map
=
self
.
_build_index_map
(
header
)
total
=
len
(
data_rows
)
if
total
==
0
:
return
self
.
_empty_quality_result
()
measurement_fields
=
[
'current'
,
'voltage'
,
'set_temperature'
,
'actual_temperature'
]
complete_count
=
0
continuous_count
=
0
accurate_count
=
0
has_measurement_count
=
0
for
row
in
data_rows
:
field_values
:
dict
[
str
,
float
|
None
]
=
{}
for
field
in
measurement_fields
:
val_str
=
self
.
_read_value
(
row
,
index_map
[
field
])
field_values
[
field
]
=
self
.
_to_float
(
val_str
)
any_present
=
any
(
v
is
not
None
for
v
in
field_values
.
values
())
all_present
=
all
(
v
is
not
None
for
v
in
field_values
.
values
())
if
any_present
:
continuous_count
+=
1
has_measurement_count
+=
1
in_range
=
True
for
field
,
val
in
field_values
.
items
():
if
val
is
None
:
continue
cfg
=
quality_config
.
get
(
field
,
{})
min_val
=
cfg
.
get
(
'min_value'
)
max_val
=
cfg
.
get
(
'max_value'
)
if
min_val
is
not
None
and
val
<
min_val
:
in_range
=
False
break
if
max_val
is
not
None
and
val
>
max_val
:
in_range
=
False
break
if
in_range
:
accurate_count
+=
1
if
all_present
:
complete_count
+=
1
completeness
=
round
(
complete_count
/
total
*
100
,
2
)
if
total
>
0
else
0.0
continuity
=
round
(
continuous_count
/
total
*
100
,
2
)
if
total
>
0
else
0.0
accuracy
=
round
(
accurate_count
/
has_measurement_count
*
100
,
2
)
if
has_measurement_count
>
0
else
0.0
return
{
'total'
:
total
,
'complete_count'
:
complete_count
,
'continuous_count'
:
continuous_count
,
'accurate_count'
:
accurate_count
,
'has_measurement_count'
:
has_measurement_count
,
'completeness'
:
completeness
,
'continuity'
:
continuity
,
'accuracy'
:
accuracy
,
'quality_config'
:
list
(
quality_config
.
values
()),
}
def
_get_quality_config_map
(
self
)
->
dict
[
str
,
dict
[
str
,
Any
]]:
with
db_session
()
as
session
:
rows
=
session
.
query
(
DataQualityConfig
)
.
all
()
return
{
item
.
field_name
:
{
'field_name'
:
item
.
field_name
,
'label'
:
item
.
label
,
'unit'
:
item
.
unit
,
'min_value'
:
item
.
min_value
,
'max_value'
:
item
.
max_value
,
}
for
item
in
rows
}
@
staticmethod
def
_empty_quality_result
()
->
dict
[
str
,
Any
]:
return
{
'total'
:
0
,
'complete_count'
:
0
,
'continuous_count'
:
0
,
'accurate_count'
:
0
,
'has_measurement_count'
:
0
,
'completeness'
:
0.0
,
'continuity'
:
0.0
,
'accuracy'
:
0.0
,
'quality_config'
:
[],
}
def
_read_all_excel_rows
(
self
,
file_path
:
Path
)
->
list
[
list
[
str
]]:
"""Read all rows including fully-empty rows (for continuity check)."""
suffix
=
file_path
.
suffix
.
lower
()
if
suffix
==
'.xlsx'
:
return
self
.
_read_xlsx_rows_all
(
file_path
)
if
suffix
==
'.xls'
:
return
self
.
_read_xls_rows_all
(
file_path
)
if
suffix
==
'.csv'
:
return
self
.
_read_csv_rows_all
(
file_path
)
return
[]
def
_read_xlsx_rows_all
(
self
,
file_path
:
Path
)
->
list
[
list
[
str
]]:
workbook
=
load_workbook
(
file_path
,
data_only
=
True
,
read_only
=
True
)
try
:
sheet
=
workbook
.
active
rows
:
list
[
list
[
str
]]
=
[]
for
row
in
sheet
.
iter_rows
(
values_only
=
True
):
cells
=
[
self
.
_cell_to_text
(
cell
)
for
cell
in
row
]
rows
.
append
(
cells
)
return
rows
finally
:
workbook
.
close
()
def
_read_xls_rows_all
(
self
,
file_path
:
Path
)
->
list
[
list
[
str
]]:
workbook
=
xlrd
.
open_workbook
(
file_path
)
sheet
=
workbook
.
sheet_by_index
(
0
)
rows
:
list
[
list
[
str
]]
=
[]
for
row_index
in
range
(
sheet
.
nrows
):
cells
=
[
self
.
_cell_to_text
(
sheet
.
cell_value
(
row_index
,
col_index
))
for
col_index
in
range
(
sheet
.
ncols
)]
rows
.
append
(
cells
)
return
rows
def
_read_csv_rows_all
(
self
,
file_path
:
Path
)
->
list
[
list
[
str
]]:
encodings
=
[
'utf-8-sig'
,
'utf-8'
,
'gbk'
,
'gb2312'
]
for
encoding
in
encodings
:
try
:
with
file_path
.
open
(
'r'
,
encoding
=
encoding
,
newline
=
''
)
as
file
:
reader
=
csv
.
reader
(
file
)
rows
:
list
[
list
[
str
]]
=
[]
for
row
in
reader
:
cells
=
[
self
.
_cell_to_text
(
cell
)
for
cell
in
row
]
rows
.
append
(
cells
)
return
rows
except
UnicodeDecodeError
:
continue
raise
ValueError
(
'CSV 文件编码无法识别,请保存为 UTF-8 或 GBK 后重试'
)
backend/app/services/eval_service.py
View file @
3faede64
...
...
@@ -105,6 +105,8 @@ class EvalService:
def
_load_package_records
(
self
,
package_id
:
int
)
->
list
[
dict
[
str
,
Any
]]:
with
db_session
()
as
session
:
pkg
=
session
.
query
(
DataPackage
)
.
filter
(
DataPackage
.
id
==
package_id
)
.
first
()
clean_rules
=
pkg
.
clean_rules
if
pkg
else
None
pf_rows
=
(
session
.
query
(
DataPackageFile
)
.
filter
(
DataPackageFile
.
package_id
==
package_id
)
...
...
@@ -126,8 +128,45 @@ class EvalService:
recs
,
_
=
self
.
_dm
.
_read_records
(
path
,
limit
=
None
)
all_records
.
extend
(
recs
)
if
clean_rules
and
clean_rules
.
get
(
'enabled'
):
all_records
=
self
.
_apply_clean_rules
(
all_records
,
clean_rules
)
return
all_records
@
staticmethod
def
_apply_clean_rules
(
records
:
list
[
dict
[
str
,
Any
]],
clean_rules
:
dict
)
->
list
[
dict
[
str
,
Any
]]:
c_min
=
clean_rules
.
get
(
'current_min'
)
c_max
=
clean_rules
.
get
(
'current_max'
)
v_min
=
clean_rules
.
get
(
'voltage_min'
)
v_max
=
clean_rules
.
get
(
'voltage_max'
)
t_min
=
clean_rules
.
get
(
'temperature_min'
)
t_max
=
clean_rules
.
get
(
'temperature_max'
)
result
:
list
[
dict
[
str
,
Any
]]
=
[]
for
r
in
records
:
current
=
r
.
get
(
'current'
)
voltage
=
r
.
get
(
'voltage'
)
temp
=
r
.
get
(
'actual_temperature'
)
if
current
is
not
None
:
if
c_min
is
not
None
and
current
<
c_min
:
continue
if
c_max
is
not
None
and
current
>
c_max
:
continue
if
voltage
is
not
None
:
if
v_min
is
not
None
and
voltage
<
v_min
:
continue
if
v_max
is
not
None
and
voltage
>
v_max
:
continue
if
temp
is
not
None
:
if
t_min
is
not
None
and
temp
<
t_min
:
continue
if
t_max
is
not
None
and
temp
>
t_max
:
continue
result
.
append
(
r
)
return
result
@
staticmethod
def
_record_to_dict
(
row
:
EvalRecord
,
include_chart
:
bool
=
True
)
->
dict
[
str
,
Any
]:
d
:
dict
[
str
,
Any
]
=
{
...
...
backend/app/services/package_management_service.py
View file @
3faede64
...
...
@@ -154,11 +154,15 @@ class PackageManagementService:
remark
:
str
|
None
,
file_ids
:
list
[
int
],
clean_rules
:
dict
|
None
=
None
,
row_start
:
int
|
None
=
None
,
row_end
:
int
|
None
=
None
,
)
->
dict
[
str
,
Any
]:
if
not
name
:
raise
ValueError
(
'数据包名称不能为空'
)
if
not
file_ids
:
raise
ValueError
(
'请至少选择一个数据文件'
)
if
row_start
is
not
None
and
row_end
is
not
None
and
row_start
>
row_end
:
raise
ValueError
(
'起始行不能大于结束行'
)
cat_db_id
:
int
|
None
=
None
if
category_id
and
str
(
category_id
)
.
strip
()
.
lower
()
not
in
{
''
,
'all'
,
'none'
,
'null'
}:
...
...
@@ -180,8 +184,13 @@ class PackageManagementService:
file_map
=
{
f
.
id
:
f
for
f
in
files
}
if
clean_rules
and
clean_rules
.
get
(
'enabled'
):
result
=
self
.
_merge_records
(
file_ids
,
file_map
,
limit
=
None
,
clean_rules
=
clean_rules
)
needs_full_merge
=
bool
((
clean_rules
and
clean_rules
.
get
(
'enabled'
))
or
row_start
is
not
None
or
row_end
is
not
None
)
if
needs_full_merge
:
result
=
self
.
_merge_records
(
file_ids
,
file_map
,
limit
=
None
,
clean_rules
=
clean_rules
,
row_start
=
row_start
,
row_end
=
row_end
,
)
total_count
=
result
[
'count'
]
stored_rules
:
dict
|
None
=
clean_rules
else
:
...
...
@@ -194,6 +203,8 @@ class PackageManagementService:
remark
=
remark
,
data_count
=
total_count
,
clean_rules
=
stored_rules
,
row_start
=
row_start
,
row_end
=
row_end
,
)
session
.
add
(
pkg
)
session
.
flush
()
...
...
@@ -225,6 +236,8 @@ class PackageManagementService:
raise
ValueError
(
'数据包不存在'
)
stored_clean_rules
=
pkg
.
clean_rules
stored_row_start
=
pkg
.
row_start
stored_row_end
=
pkg
.
row_end
pf_rows
=
(
session
.
query
(
DataPackageFile
)
...
...
@@ -236,13 +249,19 @@ class PackageManagementService:
files
=
session
.
query
(
DataFile
)
.
filter
(
DataFile
.
id
.
in_
(
file_ids
))
.
all
()
file_map
=
{
f
.
id
:
f
for
f
in
files
}
return
self
.
_merge_records
(
file_ids
,
file_map
,
limit
,
clean_rules
=
stored_clean_rules
)
return
self
.
_merge_records
(
file_ids
,
file_map
,
limit
,
clean_rules
=
stored_clean_rules
,
row_start
=
stored_row_start
,
row_end
=
stored_row_end
,
)
def
preview_records
(
self
,
file_ids
:
list
[
int
],
limit
:
int
=
300
,
clean_rules
:
dict
|
None
=
None
,
row_start
:
int
|
None
=
None
,
row_end
:
int
|
None
=
None
,
)
->
dict
[
str
,
Any
]:
if
not
file_ids
:
return
{
'records'
:
[],
'count'
:
0
}
...
...
@@ -251,7 +270,11 @@ class PackageManagementService:
files
=
session
.
query
(
DataFile
)
.
filter
(
DataFile
.
id
.
in_
(
file_ids
))
.
all
()
file_map
=
{
f
.
id
:
f
for
f
in
files
}
return
self
.
_merge_records
(
file_ids
,
file_map
,
limit
,
clean_rules
=
clean_rules
)
return
self
.
_merge_records
(
file_ids
,
file_map
,
limit
,
clean_rules
=
clean_rules
,
row_start
=
row_start
,
row_end
=
row_end
,
)
# ── helpers ──────────────────────────────────────────────────────────────
...
...
@@ -261,8 +284,12 @@ class PackageManagementService:
file_map
:
dict
[
int
,
Any
],
limit
:
int
|
None
,
clean_rules
:
dict
|
None
=
None
,
row_start
:
int
|
None
=
None
,
row_end
:
int
|
None
=
None
,
)
->
dict
[
str
,
Any
]:
use_filter
=
bool
(
clean_rules
and
clean_rules
.
get
(
'enabled'
))
use_range
=
row_start
is
not
None
or
row_end
is
not
None
need_full_read
=
use_filter
or
use_range
all_records
:
list
[
dict
[
str
,
Any
]]
=
[]
total_count
=
0
remaining
=
limit
...
...
@@ -272,22 +299,28 @@ class PackageManagementService:
continue
file_meta
=
file_map
[
fid
]
path
=
self
.
_dm
.
_resolve_local_file_path
(
file_meta
.
file_path
,
file_meta
.
stored_name
)
if
use_filter
:
# Read all rows so filter can be applied correctly
if
need_full_read
:
records
,
count
=
self
.
_dm
.
_read_records
(
path
,
limit
=
None
)
else
:
read_limit
=
(
remaining
if
remaining
is
not
None
and
remaining
>
0
else
0
)
if
remaining
is
not
None
else
None
records
,
count
=
self
.
_dm
.
_read_records
(
path
,
limit
=
read_limit
)
total_count
+=
count
all_records
.
extend
(
records
)
if
not
use_filter
and
remaining
is
not
None
:
if
not
need_full_read
and
remaining
is
not
None
:
remaining
-=
len
(
records
)
if
remaining
<=
0
:
break
if
use_filter
:
all_records
=
self
.
_apply_clean_rules
(
all_records
,
clean_rules
)
total_count
=
len
(
all_records
)
if
use_range
:
# row_start/row_end are 1-based and inclusive
start_idx
=
(
row_start
-
1
)
if
row_start
and
row_start
>=
1
else
0
end_idx
=
row_end
if
row_end
else
len
(
all_records
)
all_records
=
all_records
[
start_idx
:
end_idx
]
total_count
=
len
(
all_records
)
if
limit
is
not
None
and
limit
>
0
:
return
{
'records'
:
all_records
[:
limit
],
'count'
:
total_count
}
...
...
@@ -334,6 +367,8 @@ class PackageManagementService:
'category_id'
:
pkg
.
category_id
,
'remark'
:
pkg
.
remark
,
'data_count'
:
pkg
.
data_count
,
'row_start'
:
pkg
.
row_start
,
'row_end'
:
pkg
.
row_end
,
'created_at'
:
pkg
.
created_at
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
pkg
.
created_at
else
''
,
'updated_at'
:
pkg
.
updated_at
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
pkg
.
updated_at
else
''
,
}
...
...
frontend/dist/assets/DataManagement-D7Mq9-H4.css
deleted
100644 → 0
View file @
478c4876
.data-page
[
data-v-7089f2ac
]
{
background
:
var
(
--bg-page
);
height
:
100%
}
.data-page.dragging
[
data-v-7089f2ac
]
{
-webkit-user-select
:
none
;
user-select
:
none
;
cursor
:
col-resize
}
.data-layout
[
data-v-7089f2ac
]
{
background
:
var
(
--bg-white
);
border
:
1px
solid
var
(
--border-color
);
height
:
100%
;
box-shadow
:
var
(
--shadow-card
);
align-items
:
stretch
;
gap
:
0
;
display
:
flex
}
.pane-divider
[
data-v-7089f2ac
]
{
background
:
var
(
--border-color
);
cursor
:
col-resize
;
flex-shrink
:
0
;
width
:
4px
;
transition
:
background
.15s
;
position
:
relative
}
.pane-divider
[
data-v-7089f2ac
]
:after
{
content
:
""
;
position
:
absolute
;
inset
:
0
-2px
}
.pane-divider
[
data-v-7089f2ac
]
:hover
,
.dragging
.pane-divider
[
data-v-7089f2ac
]
{
background
:
var
(
--primary-border
)}
.pane-card
[
data-v-7089f2ac
]
{
border
:
none
;
border-right
:
1px
solid
var
(
--border-color
);
background
:
var
(
--bg-white
);
border-radius
:
0
;
min-width
:
0
;
height
:
100%
;
box-shadow
:
none
!important
}
.pane-card
[
data-v-7089f2ac
]
.el-card__header
{
border-bottom
:
1px
solid
var
(
--border-color
);
background
:
#f7f8fa
;
align-items
:
center
;
height
:
44px
;
padding
:
0
16px
;
display
:
flex
}
.pane-card
[
data-v-7089f2ac
]
.el-card__body
{
flex-direction
:
column
;
gap
:
10px
;
height
:
calc
(
100%
-
44px
);
padding
:
12px
16px
;
display
:
flex
}
.pane-card--tree
[
data-v-7089f2ac
]
.el-card__body
{
padding
:
8px
0
;
overflow-y
:
auto
}
.pane-header
[
data-v-7089f2ac
]
{
width
:
100%
;
color
:
var
(
--text-primary
);
justify-content
:
space-between
;
align-items
:
center
;
font-size
:
14px
;
font-weight
:
600
;
display
:
flex
}
.file-title
[
data-v-7089f2ac
]
{
color
:
var
(
--text-secondary
);
margin-left
:
4px
;
font-size
:
13px
;
font-weight
:
400
}
.tree-node
[
data-v-7089f2ac
]
{
justify-content
:
space-between
;
align-items
:
center
;
gap
:
8px
;
width
:
100%
;
padding-right
:
4px
;
display
:
flex
}
.tree-main
[
data-v-7089f2ac
]
{
align-items
:
center
;
gap
:
6px
;
min-width
:
0
;
display
:
inline-flex
}
.tree-icon
[
data-v-7089f2ac
]
{
color
:
var
(
--primary
);
flex
:
none
;
font-size
:
14px
}
.tree-name
[
data-v-7089f2ac
]
{
color
:
var
(
--text-primary
);
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
font-size
:
13px
;
overflow
:
hidden
}
.tree-actions
[
data-v-7089f2ac
]
{
flex-shrink
:
0
;
display
:
none
}
.tree-actions
.el-button
[
data-v-7089f2ac
]
{
color
:
var
(
--text-tertiary
);
padding
:
0
3px
;
font-size
:
13px
}
.tree-actions
.el-button
[
data-v-7089f2ac
]
:hover
{
color
:
var
(
--primary
)}
.tree-node
:hover
.tree-actions
[
data-v-7089f2ac
]
{
display
:
inline-flex
}
.upload-panel
[
data-v-7089f2ac
]
{
width
:
100%
}
.upload-panel__icon
[
data-v-7089f2ac
]
{
color
:
var
(
--primary
);
margin-bottom
:
8px
;
font-size
:
28px
}
.search-form
[
data-v-7089f2ac
]
{
flex-shrink
:
0
;
margin-bottom
:
4px
}
.content-wrap
[
data-v-7089f2ac
]
{
flex
:
1
;
min-height
:
0
}
@media
(
width
<=
980px
){
.data-layout
[
data-v-7089f2ac
]
{
display
:
block
;
overflow-y
:
auto
}
.pane-divider
[
data-v-7089f2ac
]
{
display
:
none
}
.pane-card
[
data-v-7089f2ac
]
{
border-right
:
none
;
border-bottom
:
1px
solid
var
(
--border-color
);
min-height
:
420px
;
margin-bottom
:
12px
;
width
:
100%
!important
}}
frontend/dist/assets/ModelEvaluation-CVr4AavX.js
deleted
100644 → 0
View file @
478c4876
import
{
n
as
e
,
r
as
t
,
u
as
n
}
from
"./es-DCOtnflc.js"
;
import
{
$
as
r
,
A
as
i
,
B
as
a
,
D
as
o
,
Ht
as
s
,
I
as
c
,
N
as
l
,
Q
as
u
,
R
as
d
,
V
as
f
,
Vt
as
p
,
X
as
m
,
_
as
h
,
a
as
g
,
at
as
_
,
d
as
v
,
f
as
y
,
ht
as
b
,
m
as
x
,
p
as
S
,
st
as
C
,
t
as
w
,
u
as
T
,
v
as
E
}
from
"./_plugin-vue_export-helper-D1RKUtCV.js"
;
import
{
t
as
D
}
from
"./request-D8DwihSV.js"
;
import
{
t
as
O
}
from
"./echarts-B4btcaVd.js"
;
function
k
(){
return
D
.
get
(
`/eval/packages`
)}
function
A
(){
return
D
.
get
(
`/eval/models`
)}
function
j
(
e
){
return
D
.
post
(
`/eval/run`
,
e
)}
function
M
(){
return
D
.
get
(
`/eval/records`
)}
function
N
(
e
){
return
D
.
get
(
`/eval/records/
${
e
}
`
)}
function
P
(
e
){
return
D
.
delete
(
`/eval/records/
${
e
}
`
)}
var
F
=
{
__name
:
`EvalChart`
,
props
:{
chartData
:{
type
:
Array
,
default
:()
=>
[]},
height
:{
type
:
String
,
default
:
`360px`
}},
setup
(
e
){
let
t
=
e
,
n
=
C
(
null
),
r
=
null
,
a
=
T
(()
=>
Array
.
isArray
(
t
.
chartData
)?
t
.
chartData
.
filter
(
e
=>
e
!=
null
):[]),
s
=
T
(()
=>
a
.
value
.
map
(
e
=>
e
.
time
||
String
(
e
.
index
??
``
))),
u
=
T
(()
=>
a
.
value
.
map
(
e
=>
e
.
actual
??
null
)),
d
=
T
(()
=>
a
.
value
.
map
(
e
=>
e
.
predicted
??
null
)),
f
=
()
=>
{
if
(
!
n
.
value
)
return
;
r
||=
O
(
n
.
value
);
let
e
=
a
.
value
.
length
>
0
;
r
.
setOption
({
animation
:
!
1
,
color
:[
`#409EFF`
,
`#F56C6C`
],
tooltip
:{
trigger
:
`axis`
,
backgroundColor
:
`rgba(255,255,255,0.96)`
,
borderColor
:
`#e2e8f0`
,
borderWidth
:
1
,
textStyle
:{
color
:
`#334155`
},
extraCssText
:
`box-shadow: 0 8px 24px rgba(15,23,42,0.14); border-radius: 10px;`
,
formatter
(
e
){
if
(
!
e
?.
length
)
return
``
;
let
t
=
[
`<div style="margin-bottom:6px;font-weight:600;font-size:12px;">
${
e
[
0
].
axisValue
}
</div>`
];
return
e
.
forEach
(
e
=>
{
let
n
=
e
.
data
==
null
?
`--`
:
Number
(
e
.
data
).
toFixed
(
4
);
t
.
push
(
`<div style="display:flex;align-items:center;gap:8px;min-width:180px;justify-content:space-between;">
<span>
${
e
.
marker
}${
e
.
seriesName
}
</span>
<strong>
${
n
}
℃</strong>
</div>`
)}),
t
.
join
(
``
)}},
legend
:{
bottom
:
4
,
itemWidth
:
20
,
itemHeight
:
10
,
textStyle
:{
color
:
`#475569`
,
fontSize
:
12
},
data
:[
`实际温度 (℃)`
,
`预测温度 (℃)`
]},
grid
:{
top
:
16
,
left
:
16
,
right
:
20
,
bottom
:
52
,
containLabel
:
!
0
},
xAxis
:{
type
:
`category`
,
boundaryGap
:
!
1
,
data
:
s
.
value
,
axisLabel
:{
color
:
`#64748b`
,
fontSize
:
11
,
rotate
:
35
,
interval
:
Math
.
max
(
0
,
Math
.
floor
(
s
.
value
.
length
/
12
)
-
1
),
hideOverlap
:
!
0
},
axisLine
:{
lineStyle
:{
color
:
`#cbd5e1`
}}},
yAxis
:{
type
:
`value`
,
name
:
`温度 (℃)`
,
nameTextStyle
:{
color
:
`#64748b`
,
fontSize
:
11
},
axisLabel
:{
color
:
`#64748b`
,
fontSize
:
11
},
splitLine
:{
lineStyle
:{
type
:
`dashed`
,
color
:
`rgba(148,163,184,0.4)`
}}},
series
:[{
name
:
`实际温度 (℃)`
,
type
:
`line`
,
smooth
:
!
1
,
symbol
:
`none`
,
lineStyle
:{
width
:
2
,
type
:
`solid`
},
data
:
u
.
value
},{
name
:
`预测温度 (℃)`
,
type
:
`line`
,
smooth
:
!
1
,
symbol
:
`none`
,
lineStyle
:{
width
:
2
,
type
:
`dashed`
},
data
:
d
.
value
}],
graphic
:
e
?[]:[{
type
:
`text`
,
left
:
`center`
,
top
:
`middle`
,
style
:{
text
:
`暂无评估数据`
,
fill
:
`#94a3b8`
,
fontSize
:
14
}}],
dataZoom
:
e
?[{
type
:
`inside`
,
start
:
0
,
end
:
100
},{
type
:
`slider`
,
start
:
0
,
end
:
100
,
height
:
20
,
bottom
:
28
}]:[]},
!
0
)},
h
=
()
=>
r
?.
resize
();
return
m
(()
=>
t
.
chartData
,
async
()
=>
{
await
o
(),
f
()},{
deep
:
!
0
}),
l
(
async
()
=>
{
await
o
(),
f
(),
window
.
addEventListener
(
`resize`
,
h
)}),
i
(()
=>
{
window
.
removeEventListener
(
`resize`
,
h
),
r
?.
dispose
(),
r
=
null
}),(
e
,
r
)
=>
(
c
(),
x
(
`div`
,{
ref_key
:
`chartRef`
,
ref
:
n
,
style
:
p
({
width
:
`100%`
,
height
:
t
.
height
})},
null
,
4
))}},
ee
=
{
class
:
`eval-page`
},
te
=
{
key
:
0
,
class
:
`eval-loading`
},
ne
=
{
class
:
`metrics-row`
},
re
=
{
class
:
`metric-chip`
},
ie
=
{
class
:
`metric-value`
},
ae
=
{
class
:
`metric-chip`
},
oe
=
{
class
:
`metric-value`
},
I
=
{
class
:
`metric-chip`
},
L
=
{
class
:
`metric-value`
},
R
=
{
class
:
`metric-chip`
},
z
=
{
class
:
`metric-value`
},
B
=
{
class
:
`metric-chip`
},
V
=
{
class
:
`metric-value`
},
H
=
{
class
:
`card-header-row`
},
U
=
{
class
:
`metric-cell`
},
W
=
{
class
:
`metric-cell`
},
G
=
{
style
:{
"min-height"
:
`120px`
}},
K
=
{
class
:
`metrics-row`
,
style
:{
"margin-bottom"
:
`12px`
}},
se
=
{
class
:
`metric-chip`
},
ce
=
{
class
:
`metric-value`
},
le
=
{
class
:
`metric-chip`
},
ue
=
{
class
:
`metric-value`
},
de
=
{
class
:
`metric-chip`
},
fe
=
{
class
:
`metric-value`
},
q
=
{
class
:
`metric-chip`
},
pe
=
{
class
:
`metric-value`
},
J
=
w
({
__name
:
`index`
,
setup
(
i
){
let
o
=
C
([]),
p
=
C
([]),
m
=
_
({
model_id
:
``
,
package_id
:
``
}),
w
=
C
(
!
1
),
T
=
C
(
null
),
D
=
C
([]),
O
=
C
(
!
1
),
J
=
C
(
!
1
),
Y
=
C
(
null
),
X
=
C
(
!
1
),
Z
=
async
()
=>
{
let
[
e
,
t
]
=
await
Promise
.
all
([
k
(),
A
()]);
o
.
value
=
e
,
p
.
value
=
t
},
Q
=
async
()
=>
{
O
.
value
=!
0
;
try
{
D
.
value
=
await
M
()}
finally
{
O
.
value
=!
1
}},
me
=
async
()
=>
{
if
(
!
m
.
model_id
){
t
.
warning
(
`请选择模型`
);
return
}
if
(
!
m
.
package_id
){
t
.
warning
(
`请选择数据包`
);
return
}
w
.
value
=!
0
,
T
.
value
=
null
;
try
{
T
.
value
=
await
j
({
model_id
:
m
.
model_id
,
package_id
:
m
.
package_id
}),
t
.
success
(
`评估完成`
),
await
Q
()}
catch
(
e
){
t
.
error
(
e
?.
message
||
`评估失败`
)}
finally
{
w
.
value
=!
1
}},
he
=
async
e
=>
{
X
.
value
=!
0
,
J
.
value
=!
0
,
Y
.
value
=
null
;
try
{
Y
.
value
=
await
N
(
e
.
id
)}
finally
{
X
.
value
=!
1
}},
ge
=
async
n
=>
{
try
{
await
e
.
confirm
(
`确定删除模型"
${
n
.
model_name
}
"在"
${
n
.
package_name
}
"上的评估记录吗?`
,
`提示`
,{
type
:
`warning`
}),
await
P
(
n
.
id
),
t
.
success
(
`已删除`
),
T
.
value
?.
id
===
n
.
id
&&
(
T
.
value
=
null
),
await
Q
()}
catch
{}},
$
=
e
=>
e
==
null
?
`-`
:
Number
(
e
).
toFixed
(
5
);
return
l
(
async
()
=>
{
await
Promise
.
all
([
Z
(),
Q
()])}),(
e
,
t
)
=>
{
let
i
=
a
(
`el-option`
),
l
=
a
(
`el-select`
),
_
=
a
(
`el-form-item`
),
C
=
a
(
`el-button`
),
k
=
a
(
`el-form`
),
A
=
a
(
`el-icon`
),
j
=
a
(
`el-card`
),
M
=
a
(
`el-table-column`
),
N
=
a
(
`el-table`
),
P
=
a
(
`el-dialog`
),
Z
=
f
(
`loading`
);
return
c
(),
x
(
`div`
,
ee
,[
E
(
j
,{
class
:
`config-card`
,
shadow
:
`hover`
},{
header
:
u
(()
=>
[...
t
[
4
]
||=
[
v
(
`span`
,{
class
:
`card-title`
},
`评估配置`
,
-
1
)]]),
default
:
u
(()
=>
[
E
(
k
,{
model
:
m
,
inline
:
``
,
class
:
`eval-form`
},{
default
:
u
(()
=>
[
E
(
_
,{
label
:
`选择模型`
,
required
:
``
},{
default
:
u
(()
=>
[
E
(
l
,{
modelValue
:
m
.
model_id
,
"onUpdate:modelValue"
:
t
[
0
]
||=
e
=>
m
.
model_id
=
e
,
placeholder
:
`请选择已保存模型`
,
filterable
:
``
,
style
:{
width
:
`260px`
}},{
default
:
u
(()
=>
[(
c
(
!
0
),
x
(
g
,
null
,
d
(
p
.
value
,
e
=>
(
c
(),
y
(
i
,{
key
:
e
.
id
,
label
:
`
${
e
.
model_name
}
(训练包:
${
e
.
package_name
}
)`
,
value
:
e
.
id
},
null
,
8
,[
`label`
,
`value`
]))),
128
))]),
_
:
1
},
8
,[
`modelValue`
])]),
_
:
1
}),
E
(
_
,{
label
:
`选择数据包`
,
required
:
``
},{
default
:
u
(()
=>
[
E
(
l
,{
modelValue
:
m
.
package_id
,
"onUpdate:modelValue"
:
t
[
1
]
||=
e
=>
m
.
package_id
=
e
,
placeholder
:
`请选择评估数据包`
,
filterable
:
``
,
style
:{
width
:
`240px`
}},{
default
:
u
(()
=>
[(
c
(
!
0
),
x
(
g
,
null
,
d
(
o
.
value
,
e
=>
(
c
(),
y
(
i
,{
key
:
e
.
id
,
label
:
`
${
e
.
name
}
(
${
e
.
data_count
}
条)`
,
value
:
e
.
id
},
null
,
8
,[
`label`
,
`value`
]))),
128
))]),
_
:
1
},
8
,[
`modelValue`
])]),
_
:
1
}),
E
(
_
,
null
,{
default
:
u
(()
=>
[
E
(
C
,{
type
:
`primary`
,
loading
:
w
.
value
,
onClick
:
me
},{
default
:
u
(()
=>
[...
t
[
5
]
||=
[
h
(
` 开始评估 `
,
-
1
)]]),
_
:
1
},
8
,[
`loading`
])]),
_
:
1
})]),
_
:
1
},
8
,[
`model`
]),
w
.
value
?(
c
(),
x
(
`div`
,
te
,[
E
(
A
,{
class
:
`is-loading`
,
style
:{
"font-size"
:
`24px`
,
color
:
`#409EFF`
}},{
default
:
u
(()
=>
[
E
(
b
(
n
))]),
_
:
1
}),
t
[
6
]
||=
v
(
`span`
,
null
,
`正在推理,请稍候…`
,
-
1
)])):
T
.
value
?(
c
(),
x
(
g
,{
key
:
1
},[
v
(
`div`
,
ne
,[
v
(
`div`
,
re
,[
t
[
7
]
||=
v
(
`span`
,{
class
:
`metric-label`
},
`评估样本`
,
-
1
),
v
(
`span`
,
ie
,
s
(
T
.
value
.
total_count
)
+
` 条`
,
1
)]),
v
(
`div`
,
ae
,[
t
[
8
]
||=
v
(
`span`
,{
class
:
`metric-label`
},
`MAE`
,
-
1
),
v
(
`span`
,
oe
,
s
(
$
(
T
.
value
.
mae
))
+
` ℃`
,
1
)]),
v
(
`div`
,
I
,[
t
[
9
]
||=
v
(
`span`
,{
class
:
`metric-label`
},
`RMSE`
,
-
1
),
v
(
`span`
,
L
,
s
(
$
(
T
.
value
.
rmse
))
+
` ℃`
,
1
)]),
v
(
`div`
,
R
,[
t
[
10
]
||=
v
(
`span`
,{
class
:
`metric-label`
},
`模型`
,
-
1
),
v
(
`span`
,
z
,
s
(
T
.
value
.
model_name
),
1
)]),
v
(
`div`
,
B
,[
t
[
11
]
||=
v
(
`span`
,{
class
:
`metric-label`
},
`数据包`
,
-
1
),
v
(
`span`
,
V
,
s
(
T
.
value
.
package_name
),
1
)])]),
E
(
F
,{
"chart-data"
:
T
.
value
.
chart_data
,
height
:
`340px`
},
null
,
8
,[
`chart-data`
])],
64
)):
S
(
``
,
!
0
)]),
_
:
1
}),
E
(
j
,{
class
:
`records-card`
,
shadow
:
`hover`
},{
header
:
u
(()
=>
[
v
(
`div`
,
H
,[
t
[
13
]
||=
v
(
`span`
,{
class
:
`card-title`
},
`评估记录`
,
-
1
),
E
(
C
,{
icon
:
b
(
n
),
size
:
`small`
,
plain
:
``
,
loading
:
O
.
value
,
onClick
:
Q
},{
default
:
u
(()
=>
[...
t
[
12
]
||=
[
h
(
` 刷新 `
,
-
1
)]]),
_
:
1
},
8
,[
`icon`
,
`loading`
])])]),
default
:
u
(()
=>
[
r
((
c
(),
y
(
N
,{
data
:
D
.
value
,
border
:
``
,
stripe
:
``
,
height
:
`calc(100vh - 580px)`
},{
default
:
u
(()
=>
[
E
(
M
,{
prop
:
`model_name`
,
label
:
`模型名称`
,
"min-width"
:
`140`
,
"show-overflow-tooltip"
:
``
}),
E
(
M
,{
prop
:
`package_name`
,
label
:
`评估数据包`
,
"min-width"
:
`130`
,
"show-overflow-tooltip"
:
``
}),
E
(
M
,{
label
:
`样本数`
,
width
:
`90`
,
align
:
`center`
},{
default
:
u
(({
row
:
e
})
=>
[
h
(
s
(
e
.
total_count
),
1
)]),
_
:
1
}),
E
(
M
,{
label
:
`MAE (℃)`
,
width
:
`110`
,
align
:
`center`
},{
default
:
u
(({
row
:
e
})
=>
[
v
(
`span`
,
U
,
s
(
$
(
e
.
mae
)),
1
)]),
_
:
1
}),
E
(
M
,{
label
:
`RMSE (℃)`
,
width
:
`110`
,
align
:
`center`
},{
default
:
u
(({
row
:
e
})
=>
[
v
(
`span`
,
W
,
s
(
$
(
e
.
rmse
)),
1
)]),
_
:
1
}),
E
(
M
,{
prop
:
`created_at`
,
label
:
`评估时间`
,
width
:
`165`
}),
E
(
M
,{
label
:
`操作`
,
width
:
`120`
,
fixed
:
`right`
,
align
:
`center`
},{
default
:
u
(({
row
:
e
})
=>
[
E
(
C
,{
link
:
``
,
type
:
`primary`
,
onClick
:
t
=>
he
(
e
)},{
default
:
u
(()
=>
[...
t
[
14
]
||=
[
h
(
`查看`
,
-
1
)]]),
_
:
1
},
8
,[
`onClick`
]),
E
(
C
,{
link
:
``
,
type
:
`danger`
,
onClick
:
t
=>
ge
(
e
)},{
default
:
u
(()
=>
[...
t
[
15
]
||=
[
h
(
`删除`
,
-
1
)]]),
_
:
1
},
8
,[
`onClick`
])]),
_
:
1
})]),
_
:
1
},
8
,[
`data`
])),[[
Z
,
O
.
value
]])]),
_
:
1
}),
E
(
P
,{
modelValue
:
J
.
value
,
"onUpdate:modelValue"
:
t
[
3
]
||=
e
=>
J
.
value
=
e
,
title
:
Y
.
value
?
`
${
Y
.
value
.
model_name
}
—
${
Y
.
value
.
package_name
}
`
:
`评估详情`
,
width
:
`860px`
,
"destroy-on-close"
:
``
},{
footer
:
u
(()
=>
[
E
(
C
,{
onClick
:
t
[
2
]
||=
e
=>
J
.
value
=!
1
},{
default
:
u
(()
=>
[...
t
[
20
]
||=
[
h
(
`关闭`
,
-
1
)]]),
_
:
1
})]),
default
:
u
(()
=>
[
r
((
c
(),
x
(
`div`
,
G
,[
Y
.
value
?(
c
(),
x
(
g
,{
key
:
0
},[
v
(
`div`
,
K
,[
v
(
`div`
,
se
,[
t
[
16
]
||=
v
(
`span`
,{
class
:
`metric-label`
},
`样本数`
,
-
1
),
v
(
`span`
,
ce
,
s
(
Y
.
value
.
total_count
),
1
)]),
v
(
`div`
,
le
,[
t
[
17
]
||=
v
(
`span`
,{
class
:
`metric-label`
},
`MAE`
,
-
1
),
v
(
`span`
,
ue
,
s
(
$
(
Y
.
value
.
mae
))
+
` ℃`
,
1
)]),
v
(
`div`
,
de
,[
t
[
18
]
||=
v
(
`span`
,{
class
:
`metric-label`
},
`RMSE`
,
-
1
),
v
(
`span`
,
fe
,
s
(
$
(
Y
.
value
.
rmse
))
+
` ℃`
,
1
)]),
v
(
`div`
,
q
,[
t
[
19
]
||=
v
(
`span`
,{
class
:
`metric-label`
},
`评估时间`
,
-
1
),
v
(
`span`
,
pe
,
s
(
Y
.
value
.
created_at
),
1
)])]),
E
(
F
,{
"chart-data"
:
Y
.
value
.
chart_data
||
[],
height
:
`380px`
},
null
,
8
,[
`chart-data`
])],
64
)):
S
(
``
,
!
0
)])),[[
Z
,
X
.
value
]])]),
_
:
1
},
8
,[
`modelValue`
,
`title`
])])}}},[[
`__scopeId`
,
`data-v-ec2828d6`
]]);
export
{
J
as
default
};
\ No newline at end of file
frontend/dist/assets/ModelList-DLDTMRtX.js
deleted
100644 → 0
View file @
478c4876
import
{
n
as
e
,
r
as
t
,
u
as
n
}
from
"./es-DCOtnflc.js"
;
import
{
$
as
r
,
B
as
i
,
Ht
as
a
,
I
as
o
,
N
as
s
,
Q
as
c
,
V
as
l
,
_
as
u
,
d
,
f
,
ht
as
p
,
m
,
p
as
h
,
st
as
g
,
t
as
_
,
v
}
from
"./_plugin-vue_export-helper-D1RKUtCV.js"
;
import
{
a
as
y
,
r
as
b
}
from
"./trainManagement-TFX-G3Jl.js"
;
var
x
=
{
class
:
`model-list-page`
},
S
=
{
class
:
`card-header-row`
},
C
=
{
class
:
`params-text`
},
w
=
{
class
:
`loss-text`
},
T
=
_
({
__name
:
`index`
,
setup
(
_
){
let
T
=
g
([]),
E
=
g
(
!
1
),
D
=
async
()
=>
{
E
.
value
=!
0
;
try
{
T
.
value
=
await
y
()}
finally
{
E
.
value
=!
1
}},
O
=
async
n
=>
{
try
{
await
e
.
confirm
(
`确定删除模型"
${
n
.
model_name
}
"吗?删除后无法恢复。`
,
`提示`
,{
type
:
`warning`
}),
await
b
(
n
.
id
),
t
.
success
(
`模型已删除`
),
await
D
()}
catch
{}},
k
=
e
=>
e
?[
`seq=
${
e
.
seq_len
}
`
,
`hidden=
${
e
.
hidden_size
}
`
,
`layers=
${
e
.
num_layers
}
`
,
`epochs=
${
e
.
epochs
}
`
,
`lr=
${
e
.
learning_rate
}
`
].
join
(
` / `
):
`-`
,
A
=
e
=>
e
.
train_loss
==
null
?
`-`
:
`训练
${
Number
(
e
.
train_loss
).
toFixed
(
5
)}
`
+
(
e
.
val_loss
==
null
?
``
:
` / 验证
${
Number
(
e
.
val_loss
).
toFixed
(
5
)}
`
);
return
s
(
D
),(
e
,
t
)
=>
{
let
s
=
i
(
`el-button`
),
g
=
i
(
`el-table-column`
),
_
=
i
(
`el-tooltip`
),
y
=
i
(
`el-table`
),
b
=
i
(
`el-empty`
),
j
=
i
(
`el-card`
),
M
=
l
(
`loading`
);
return
o
(),
m
(
`div`
,
x
,[
v
(
j
,{
shadow
:
`hover`
,
class
:
`list-card`
},{
header
:
c
(()
=>
[
d
(
`div`
,
S
,[
t
[
1
]
||=
d
(
`span`
,{
class
:
`card-title`
},
`已保存模型`
,
-
1
),
v
(
s
,{
icon
:
p
(
n
),
size
:
`small`
,
plain
:
``
,
loading
:
E
.
value
,
onClick
:
D
},{
default
:
c
(()
=>
[...
t
[
0
]
||=
[
u
(
` 刷新 `
,
-
1
)]]),
_
:
1
},
8
,[
`icon`
,
`loading`
])])]),
default
:
c
(()
=>
[
r
((
o
(),
f
(
y
,{
data
:
T
.
value
,
border
:
``
,
stripe
:
``
,
height
:
`calc(100vh - 160px)`
},{
default
:
c
(()
=>
[
v
(
g
,{
type
:
`index`
,
width
:
`55`
,
label
:
`#`
,
align
:
`center`
}),
v
(
g
,{
prop
:
`model_name`
,
label
:
`模型名称`
,
"min-width"
:
`150`
,
"show-overflow-tooltip"
:
``
}),
v
(
g
,{
prop
:
`package_name`
,
label
:
`训练数据包`
,
"min-width"
:
`140`
,
"show-overflow-tooltip"
:
``
}),
v
(
g
,{
label
:
`LSTM 参数`
,
"min-width"
:
`280`
,
"show-overflow-tooltip"
:
``
},{
default
:
c
(({
row
:
e
})
=>
[
v
(
_
,{
content
:
k
(
e
.
params
),
placement
:
`top`
},{
default
:
c
(()
=>
[
d
(
`span`
,
C
,
a
(
k
(
e
.
params
)),
1
)]),
_
:
2
},
1032
,[
`content`
])]),
_
:
1
}),
v
(
g
,{
label
:
`训练损失`
,
"min-width"
:
`190`
,
"show-overflow-tooltip"
:
``
},{
default
:
c
(({
row
:
e
})
=>
[
d
(
`span`
,
w
,
a
(
A
(
e
)),
1
)]),
_
:
1
}),
v
(
g
,{
prop
:
`created_at`
,
label
:
`保存时间`
,
width
:
`170`
}),
v
(
g
,{
label
:
`操作`
,
width
:
`90`
,
fixed
:
`right`
,
align
:
`center`
},{
default
:
c
(({
row
:
e
})
=>
[
v
(
s
,{
link
:
``
,
type
:
`danger`
,
onClick
:
t
=>
O
(
e
)},{
default
:
c
(()
=>
[...
t
[
2
]
||=
[
u
(
`删除`
,
-
1
)]]),
_
:
1
},
8
,[
`onClick`
])]),
_
:
1
})]),
_
:
1
},
8
,[
`data`
])),[[
M
,
E
.
value
]]),
!
E
.
value
&&
T
.
value
.
length
===
0
?(
o
(),
f
(
b
,{
key
:
0
,
description
:
`暂无已保存的模型,请先在模型训练页面完成训练并保存`
,
style
:{
padding
:
`40px 0`
}})):
h
(
``
,
!
0
)]),
_
:
1
})])}}},[[
`__scopeId`
,
`data-v-c204d828`
]]);
export
{
T
as
default
};
\ No newline at end of file
frontend/dist/assets/echarts-B4btcaVd.js
deleted
100644 → 0
View file @
478c4876
This diff is collapsed.
Click to expand it.
frontend/dist/assets/es-DCOtnflc.js
deleted
100644 → 0
View file @
478c4876
This diff is collapsed.
Click to expand it.
frontend/dist/assets/request-D8DwihSV.js
deleted
100644 → 0
View file @
478c4876
This diff is collapsed.
Click to expand it.
frontend/dist/assets/trainManagement-TFX-G3Jl.js
deleted
100644 → 0
View file @
478c4876
import
{
t
as
e
}
from
"./request-D8DwihSV.js"
;
function
t
(){
return
e
.
get
(
`/train/packages`
)}
function
n
(){
return
e
.
get
(
`/train/tasks`
)}
function
r
(
t
){
return
e
.
post
(
`/train/tasks`
,
t
)}
function
i
(
t
){
return
e
.
post
(
`/train/tasks/
${
t
}
/cancel`
)}
function
a
(
t
){
return
e
.
post
(
`/train/tasks/
${
t
}
/restart`
)}
function
o
(
t
){
return
e
.
delete
(
`/train/tasks/
${
t
}
`
)}
function
s
(
t
){
return
e
.
post
(
`/train/tasks/
${
t
}
/save`
)}
function
c
(){
return
e
.
get
(
`/train/models`
)}
function
l
(
t
){
return
e
.
delete
(
`/train/models/
${
t
}
`
)}
export
{
c
as
a
,
a
as
c
,
o
as
i
,
s
as
l
,
r
as
n
,
t
as
o
,
l
as
r
,
n
as
s
,
i
as
t
};
\ No newline at end of file
frontend/dist/index.html
View file @
3faede64
...
...
@@ -5,9 +5,9 @@
<link
rel=
"icon"
href=
"/favicon.ico"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<title>
热实验温度控制系统
</title>
<script
type=
"module"
crossorigin
src=
"/assets/index-
CvdqK_rU
.js"
></script>
<script
type=
"module"
crossorigin
src=
"/assets/index-
8e_Vyf7m
.js"
></script>
<link
rel=
"modulepreload"
crossorigin
href=
"/assets/_plugin-vue_export-helper-D1RKUtCV.js"
>
<link
rel=
"modulepreload"
crossorigin
href=
"/assets/es-
DCOtnflc
.js"
>
<link
rel=
"modulepreload"
crossorigin
href=
"/assets/es-
XoFJL7_H
.js"
>
<link
rel=
"stylesheet"
crossorigin
href=
"/assets/index-pfQxyObg.css"
>
</head>
<body>
...
...
frontend/src/api/dataManagement.js
View file @
3faede64
...
...
@@ -41,3 +41,11 @@ export function downloadDataTemplate() {
responseType
:
'blob'
,
})
}
export
function
getFileQuality
(
fileId
)
{
return
request
.
get
(
`/data/files/
${
fileId
}
/quality`
)
}
export
function
getQualityConfig
()
{
return
request
.
get
(
'/data/quality-config'
)
}
frontend/src/views/DataManagement/index.vue
View file @
3faede64
<
script
setup
>
import
{
Delete
,
Edit
,
Folder
,
FolderOpened
,
Upload
}
from
'@element-plus/icons-vue'
import
{
Delete
,
Document
,
Edit
,
Folder
,
FolderOpened
,
Upload
}
from
'@element-plus/icons-vue'
import
{
computed
,
onBeforeUnmount
,
onMounted
,
reactive
,
ref
}
from
'vue'
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
import
{
...
...
@@ -9,6 +9,7 @@ import {
downloadDataTemplate
,
getCategoryTree
,
getDataFiles
,
getFileQuality
,
getFileRecords
,
updateCategory
,
uploadDataFile
,
...
...
@@ -42,6 +43,32 @@ const uploadForm = reactive({
categoryId
:
''
,
})
const
qualityDialogVisible
=
ref
(
false
)
const
qualityLoading
=
ref
(
false
)
const
qualityFile
=
ref
(
null
)
const
qualityResult
=
ref
(
null
)
const
qualityColorClass
=
(
percent
)
=>
{
if
(
percent
>=
90
)
return
'quality-good'
if
(
percent
>=
70
)
return
'quality-warn'
return
'quality-bad'
}
const
handleQualityCheck
=
async
(
row
)
=>
{
qualityFile
.
value
=
row
qualityResult
.
value
=
null
qualityDialogVisible
.
value
=
true
qualityLoading
.
value
=
true
try
{
qualityResult
.
value
=
await
getFileQuality
(
row
.
id
)
}
catch
{
ElMessage
.
error
(
'质量评估失败'
)
qualityDialogVisible
.
value
=
false
}
finally
{
qualityLoading
.
value
=
false
}
}
const
layoutRef
=
ref
(
null
)
const
leftPaneWidth
=
ref
(
16
)
const
middlePaneWidth
=
ref
(
38
)
...
...
@@ -483,9 +510,10 @@ onBeforeUnmount(() => {
<el-table-column
prop=
"filename"
label=
"文件名"
min-width=
"160"
/>
<el-table-column
prop=
"uploaded_at"
label=
"上传时间"
min-width=
"170"
/>
<el-table-column
prop=
"data_count"
label=
"数据量"
width=
"90"
/>
<el-table-column
label=
"操作"
width=
"
15
0"
fixed=
"right"
>
<el-table-column
label=
"操作"
width=
"
20
0"
fixed=
"right"
>
<
template
#
default=
"{ row }"
>
<el-button
link
type=
"primary"
@
click=
"handleViewFile(row)"
>
查看
</el-button>
<el-button
link
type=
"success"
@
click=
"handleQualityCheck(row)"
>
质量判定
</el-button>
<el-button
link
type=
"danger"
@
click=
"handleDeleteFile(row)"
>
删除
</el-button>
</
template
>
</el-table-column>
...
...
@@ -578,6 +606,99 @@ onBeforeUnmount(() => {
<el-button
type=
"primary"
@
click=
"submitCategory"
>
保存
</el-button>
</
template
>
</el-dialog>
<!-- 质量判定对话框 -->
<el-dialog
v-model=
"qualityDialogVisible"
title=
"数据质量判定"
width=
"500px"
destroy-on-close
>
<div
v-if=
"qualityFile"
class=
"quality-filename"
>
<el-icon><Document
/></el-icon>
{{ qualityFile.filename }}
</div>
<div
v-loading=
"qualityLoading"
class=
"quality-body"
>
<
template
v-if=
"qualityResult && !qualityLoading"
>
<div
class=
"quality-total"
>
共
{{
qualityResult
.
total
}}
条数据行
</div>
<!-- 完整性 -->
<div
class=
"quality-item"
>
<div
class=
"quality-item__header"
>
<span
class=
"quality-item__label"
>
完整性
</span>
<span
class=
"quality-item__count"
>
{{
qualityResult
.
complete_count
}}
/
{{
qualityResult
.
total
}}
条字段完整
</span>
<span
class=
"quality-item__pct"
:class=
"qualityColorClass(qualityResult.completeness)"
>
{{
qualityResult
.
completeness
}}
%
</span>
</div>
<el-progress
:percentage=
"qualityResult.completeness"
:color=
"qualityResult.completeness >= 90 ? '#67c23a' : qualityResult.completeness >= 70 ? '#e6a23c' : '#f56c6c'"
:stroke-width=
"10"
:show-text=
"false"
/>
<div
class=
"quality-item__desc"
>
每条数据行的电流、电压、设定温度、实际温度均有值
</div>
</div>
<!-- 连续性 -->
<div
class=
"quality-item"
>
<div
class=
"quality-item__header"
>
<span
class=
"quality-item__label"
>
连续性
</span>
<span
class=
"quality-item__count"
>
{{
qualityResult
.
continuous_count
}}
/
{{
qualityResult
.
total
}}
条有效行
</span>
<span
class=
"quality-item__pct"
:class=
"qualityColorClass(qualityResult.continuity)"
>
{{
qualityResult
.
continuity
}}
%
</span>
</div>
<el-progress
:percentage=
"qualityResult.continuity"
:color=
"qualityResult.continuity >= 90 ? '#67c23a' : qualityResult.continuity >= 70 ? '#e6a23c' : '#f56c6c'"
:stroke-width=
"10"
:show-text=
"false"
/>
<div
class=
"quality-item__desc"
>
非断点行(至少有一个测量值的行)占比,断点为所有测量字段均为空的行
</div>
</div>
<!-- 准确性 -->
<div
class=
"quality-item"
>
<div
class=
"quality-item__header"
>
<span
class=
"quality-item__label"
>
准确性
</span>
<span
class=
"quality-item__count"
>
{{
qualityResult
.
accurate_count
}}
/
{{
qualityResult
.
has_measurement_count
}}
条在范围内
</span>
<span
class=
"quality-item__pct"
:class=
"qualityColorClass(qualityResult.accuracy)"
>
{{
qualityResult
.
accuracy
}}
%
</span>
</div>
<el-progress
:percentage=
"qualityResult.accuracy"
:color=
"qualityResult.accuracy >= 90 ? '#67c23a' : qualityResult.accuracy >= 70 ? '#e6a23c' : '#f56c6c'"
:stroke-width=
"10"
:show-text=
"false"
/>
<div
class=
"quality-item__desc"
>
所有非空测量值均在配置范围内的行占比
<span
v-if=
"qualityResult.quality_config && qualityResult.quality_config.length"
>
(当前配置:
<span
v-for=
"(cfg, idx) in qualityResult.quality_config"
:key=
"cfg.field_name"
>
<template
v-if=
"cfg.min_value !== null || cfg.max_value !== null"
>
{{
cfg
.
label
}}
<template
v-if=
"cfg.min_value !== null"
>
≥
{{
cfg
.
min_value
}}{{
cfg
.
unit
}}
</
template
>
<
template
v-if=
"cfg.max_value !== null"
>
≤
{{
cfg
.
max_value
}}{{
cfg
.
unit
}}
</
template
>
<
template
v-if=
"idx < qualityResult.quality_config.length - 1"
>
、
</
template
>
</template>
</span>
)
</span>
<span
v-else
class=
"quality-no-config"
>
(未配置准确性范围,请在 data_quality_config 表中设置)
</span>
</div>
</div>
</template>
<el-empty
v-else-if=
"!qualityLoading"
description=
"暂无质量数据"
:image-size=
"60"
/>
</div>
<
template
#
footer
>
<el-button
@
click=
"qualityDialogVisible = false"
>
关闭
</el-button>
</
template
>
</el-dialog>
</section>
</template>
...
...
@@ -760,4 +881,71 @@ onBeforeUnmount(() => {
border-bottom
:
1px
solid
var
(
--
border-color
);
}
}
// quality dialog
.quality-filename
{
display
:
flex
;
align-items
:
center
;
gap
:
6px
;
font-size
:
13px
;
color
:
#475569
;
margin-bottom
:
16px
;
word-break
:
break-all
;
}
.quality-total
{
font-size
:
13px
;
color
:
#64748b
;
margin-bottom
:
12px
;
}
.quality-body
{
min-height
:
120px
;
}
.quality-item
{
margin-bottom
:
20px
;
&
__header
{
display
:
flex
;
align-items
:
baseline
;
gap
:
8px
;
margin-bottom
:
6px
;
}
&
__label
{
font-size
:
14px
;
font-weight
:
600
;
color
:
#0f172a
;
min-width
:
48px
;
}
&
__count
{
font-size
:
12px
;
color
:
#64748b
;
flex
:
1
;
}
&
__pct
{
font-size
:
18px
;
font-weight
:
700
;
min-width
:
56px
;
text-align
:
right
;
&
.quality-good
{
color
:
#67c23a
;
}
&
.quality-warn
{
color
:
#e6a23c
;
}
&
.quality-bad
{
color
:
#f56c6c
;
}
}
&
__desc
{
font-size
:
12px
;
color
:
#94a3b8
;
margin-top
:
4px
;
line-height
:
1
.5
;
}
}
.quality-no-config
{
color
:
#e6a23c
;
}
</
style
>
frontend/src/views/PackageManagement/components/AddPackage.vue
View file @
3faede64
...
...
@@ -3,6 +3,7 @@ import { ArrowLeft } from '@element-plus/icons-vue'
import
{
ElMessage
}
from
'element-plus'
import
{
computed
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
{
createPackage
,
getAllDataFiles
,
getPkgCategoryTree
,
previewPackage
}
from
'@/api/packageManagement'
import
{
getQualityConfig
}
from
'@/api/dataManagement'
import
DataCurve
from
'@/views/DataManagement/components/DataCurve.vue'
const
emit
=
defineEmits
([
'cancel'
,
'saved'
])
...
...
@@ -56,6 +57,56 @@ const cleanRules = reactive({
temperature_max
:
null
,
})
const
applyQualityConfigToCleanRules
=
(
configs
)
=>
{
const
map
=
{}
for
(
const
cfg
of
configs
)
{
map
[
cfg
.
field_name
]
=
cfg
}
if
(
map
.
current
)
{
if
(
cleanRules
.
current_min
===
null
)
cleanRules
.
current_min
=
map
.
current
.
min_value
??
null
if
(
cleanRules
.
current_max
===
null
)
cleanRules
.
current_max
=
map
.
current
.
max_value
??
null
}
if
(
map
.
voltage
)
{
if
(
cleanRules
.
voltage_min
===
null
)
cleanRules
.
voltage_min
=
map
.
voltage
.
min_value
??
null
if
(
cleanRules
.
voltage_max
===
null
)
cleanRules
.
voltage_max
=
map
.
voltage
.
max_value
??
null
}
// temperature: take broader range of set_temperature and actual_temperature
const
setTMin
=
map
.
set_temperature
?.
min_value
??
null
const
setTMax
=
map
.
set_temperature
?.
max_value
??
null
const
actTMin
=
map
.
actual_temperature
?.
min_value
??
null
const
actTMax
=
map
.
actual_temperature
?.
max_value
??
null
if
(
cleanRules
.
temperature_min
===
null
)
{
const
candidates
=
[
setTMin
,
actTMin
].
filter
((
v
)
=>
v
!==
null
)
cleanRules
.
temperature_min
=
candidates
.
length
?
Math
.
min
(...
candidates
)
:
null
}
if
(
cleanRules
.
temperature_max
===
null
)
{
const
candidates
=
[
setTMax
,
actTMax
].
filter
((
v
)
=>
v
!==
null
)
cleanRules
.
temperature_max
=
candidates
.
length
?
Math
.
max
(...
candidates
)
:
null
}
}
watch
(
()
=>
cleanRules
.
enabled
,
async
(
enabled
)
=>
{
if
(
!
enabled
)
return
// Only auto-fill if all fields are still null (user hasn't typed yet)
const
allNull
=
cleanRules
.
current_min
===
null
&&
cleanRules
.
current_max
===
null
&&
cleanRules
.
voltage_min
===
null
&&
cleanRules
.
voltage_max
===
null
&&
cleanRules
.
temperature_min
===
null
&&
cleanRules
.
temperature_max
===
null
if
(
!
allNull
)
return
try
{
const
configs
=
await
getQualityConfig
()
applyQualityConfigToCleanRules
(
configs
)
}
catch
{
// ignore: quality config may not be configured yet
}
},
)
const
cleanRulesPayload
=
computed
(()
=>
{
if
(
!
cleanRules
.
enabled
)
return
null
return
{
...
...
@@ -69,7 +120,31 @@ const cleanRulesPayload = computed(() => {
}
})
// ── preview ───────────────────────────────────────────────────────────────────
// ── row range ─────────────────────────────────────────────────────────────────
const
rowRange
=
reactive
({
enabled
:
false
,
start
:
null
,
end
:
null
,
})
const
rowRangeError
=
computed
(()
=>
{
if
(
!
rowRange
.
enabled
)
return
''
const
s
=
rowRange
.
start
const
e
=
rowRange
.
end
if
(
s
!==
null
&&
s
<
1
)
return
'起始行不能小于 1'
if
(
e
!==
null
&&
e
<
1
)
return
'结束行不能小于 1'
if
(
s
!==
null
&&
e
!==
null
&&
s
>
e
)
return
'起始行不能大于结束行'
return
''
})
const
rowRangePayload
=
computed
(()
=>
{
if
(
!
rowRange
.
enabled
)
return
{
row_start
:
null
,
row_end
:
null
}
return
{
row_start
:
rowRange
.
start
??
null
,
row_end
:
rowRange
.
end
??
null
,
}
})
const
previewLoading
=
ref
(
false
)
const
previewRecords
=
ref
([])
const
previewTotal
=
ref
(
0
)
...
...
@@ -87,7 +162,11 @@ const triggerPreview = () => {
previewLoading
.
value
=
true
try
{
const
result
=
await
previewPackage
(
{
file_ids
:
selectedFileIds
.
value
,
clean_rules
:
cleanRulesPayload
.
value
},
{
file_ids
:
selectedFileIds
.
value
,
clean_rules
:
cleanRulesPayload
.
value
,
...
rowRangePayload
.
value
,
},
{
limit
:
300
},
)
previewRecords
.
value
=
result
.
records
...
...
@@ -100,6 +179,7 @@ const triggerPreview = () => {
watch
(
selectedFileIds
,
triggerPreview
,
{
deep
:
true
})
watch
(
cleanRules
,
triggerPreview
,
{
deep
:
true
})
watch
(
rowRange
,
triggerPreview
,
{
deep
:
true
})
// ── save ──────────────────────────────────────────────────────────────────────
const
saving
=
ref
(
false
)
...
...
@@ -113,6 +193,10 @@ const handleGenerate = async () => {
ElMessage
.
warning
(
'请至少选择一个数据文件'
)
return
}
if
(
rowRangeError
.
value
)
{
ElMessage
.
warning
(
rowRangeError
.
value
)
return
}
saving
.
value
=
true
try
{
...
...
@@ -122,6 +206,7 @@ const handleGenerate = async () => {
remark
:
form
.
remark
.
trim
()
||
null
,
file_ids
:
selectedFileIds
.
value
,
clean_rules
:
cleanRulesPayload
.
value
,
...
rowRangePayload
.
value
,
})
ElMessage
.
success
(
'数据包创建成功'
)
emit
(
'saved'
)
...
...
@@ -255,6 +340,34 @@ onMounted(async () => {
</div>
</div>
</el-form-item>
<el-form-item
label=
"数据行范围"
>
<div
class=
"row-range-wrap"
>
<el-checkbox
v-model=
"rowRange.enabled"
>
启用行范围截取
</el-checkbox>
<div
v-if=
"rowRange.enabled"
class=
"row-range-inputs"
>
<el-input-number
v-model=
"rowRange.start"
:controls=
"false"
:value-on-clear=
"null"
:min=
"1"
:precision=
"0"
placeholder=
"起始行"
class=
"range-input"
/>
<span
class=
"range-sep"
>
~
</span>
<el-input-number
v-model=
"rowRange.end"
:controls=
"false"
:value-on-clear=
"null"
:min=
"1"
:precision=
"0"
placeholder=
"结束行"
class=
"range-input"
/>
<span
class=
"range-unit"
>
行(含两端)
</span>
<div
v-if=
"rowRangeError"
class=
"row-range-error"
>
{{
rowRangeError
}}
</div>
</div>
</div>
</el-form-item>
<el-form-item
label=
"备注"
>
<el-input
v-model=
"form.remark"
...
...
@@ -450,4 +563,28 @@ onMounted(async () => {
font-size
:
13px
;
color
:
#94a3b8
;
}
.row-range-wrap
{
width
:
100%
;
}
.row-range-inputs
{
display
:
flex
;
align-items
:
center
;
flex-wrap
:
wrap
;
gap
:
8px
;
margin-top
:
8px
;
}
.range-unit
{
font-size
:
12px
;
color
:
#94a3b8
;
}
.row-range-error
{
width
:
100%
;
font-size
:
12px
;
color
:
#f56c6c
;
margin-top
:
2px
;
}
</
style
>
\ No newline at end of file
sql/init_tables.sql
View file @
3faede64
...
...
@@ -45,6 +45,12 @@ CREATE TABLE IF NOT EXISTS data_packages (
ALTER
TABLE
data_packages
ADD
COLUMN
IF
NOT
EXISTS
clean_rules
JSON
NULL
COMMENT
'野值清洗规则'
;
ALTER
TABLE
data_packages
ADD
COLUMN
IF
NOT
EXISTS
row_start
INT
NULL
COMMENT
'数据行起始行号(1-based, 含)'
;
ALTER
TABLE
data_packages
ADD
COLUMN
IF
NOT
EXISTS
row_end
INT
NULL
COMMENT
'数据行结束行号(1-based, 含)'
;
CREATE
TABLE
IF
NOT
EXISTS
data_package_files
(
id
BIGINT
PRIMARY
KEY
AUTO_INCREMENT
,
package_id
BIGINT
NOT
NULL
COMMENT
'数据包ID'
,
...
...
@@ -59,3 +65,21 @@ ALTER TABLE train_tasks
ALTER
TABLE
saved_models
ADD
COLUMN
IF
NOT
EXISTS
test_loss
FLOAT
NULL
COMMENT
'测试集损失'
;
CREATE
TABLE
IF
NOT
EXISTS
data_quality_config
(
id
BIGINT
PRIMARY
KEY
AUTO_INCREMENT
,
field_name
VARCHAR
(
50
)
NOT
NULL
COMMENT
'字段名: current/voltage/set_temperature/actual_temperature'
,
label
VARCHAR
(
100
)
NOT
NULL
COMMENT
'显示名称'
,
unit
VARCHAR
(
20
)
NOT
NULL
DEFAULT
''
COMMENT
'单位'
,
min_value
DOUBLE
DEFAULT
NULL
COMMENT
'合规最小值'
,
max_value
DOUBLE
DEFAULT
NULL
COMMENT
'合规最大值'
,
created_at
TIMESTAMP
NOT
NULL
DEFAULT
CURRENT_TIMESTAMP
,
updated_at
TIMESTAMP
NOT
NULL
DEFAULT
CURRENT_TIMESTAMP
ON
UPDATE
CURRENT_TIMESTAMP
,
UNIQUE
KEY
uk_field_name
(
field_name
)
)
COMMENT
=
'数据质量准确性配置表'
;
INSERT
IGNORE
INTO
data_quality_config
(
field_name
,
label
,
unit
)
VALUES
(
'current'
,
'电流'
,
'A'
),
(
'voltage'
,
'电压'
,
'V'
),
(
'set_temperature'
,
'设定温度'
,
'℃'
),
(
'actual_temperature'
,
'实际温度'
,
'℃'
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment