Browse Source

feat: enhance model assessment functionality with versioning and report selection

pull/60/head
CJL6015 1 month ago
parent
commit
17d2cbc95e
  1. 2
      .vscode/settings.json
  2. 18
      src/api/alert/model/assessReport.ts
  3. 17
      src/api/alert/model/model/assessReportModel.ts
  4. 14
      src/api/alert/model/models.ts
  5. 216
      src/views/model/AssessReport.vue
  6. 8
      src/views/model/list/ModelCard.vue
  7. 1
      src/views/model/list/data.tsx
  8. 405
      src/views/model/train/index.vue

2
.vscode/settings.json

@ -120,7 +120,7 @@
"source.organizeImports": "never",
"source.fixAll.stylelint": "explicit"
},
"editor.defaultFormatter": "octref.vetur"
"editor.defaultFormatter": "stylelint.vscode-stylelint"
},
"i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested",

18
src/api/alert/model/assessReport.ts

@ -5,6 +5,8 @@ import type {
AssessInitQuery,
AssessInitResponse,
AssessReportDetail,
AssessReportName,
AssessReportSimple,
AssessReportSavePayload,
} from './model/assessReportModel'
import { defHttp } from '@/utils/http/axios'
@ -14,6 +16,9 @@ enum Api {
REPORT = '/alert/assess-report/report',
EVALUATE = '/alert/assess-report/evaluate',
SAVE = '/alert/assess-report',
LEGACY_SAVE = '/alert/assess-report/legacy',
LIST = '/alert/assess-report/list',
NAME = '/alert/assess-report/name',
CLEAN_SUMMARY = '/alert/assess-report/clean/summary',
CLEAN_DEAD = '/alert/assess-report/clean/dead',
CLEAN_CONDITION_RATE = '/alert/assess-report/clean/condition-rate',
@ -37,6 +42,19 @@ export function saveAssessReport(data: AssessReportSavePayload) {
return defHttp.post<boolean>({ url: Api.SAVE, data })
}
// 兼容旧表 Assess_Report_CFG 的插入
export function saveAssessReportLegacy(data: AssessReportSavePayload & { rawReport?: string }) {
return defHttp.post<boolean>({ url: Api.LEGACY_SAVE, data })
}
export function fetchAssessReportList(params: { modelId: number | string; version?: string; valid?: boolean }) {
return defHttp.get<AssessReportSimple[]>({ url: Api.LIST, params })
}
export function fetchAssessReportNames(params: { modelId: number | string; version?: string }) {
return defHttp.get<AssessReportName[]>({ url: Api.NAME, params })
}
export function fetchCleanSummary(params: { modelId: number | string; time: string; version: string }) {
return defHttp.get<AssessCleanSummary>({ url: Api.CLEAN_SUMMARY, params })
}

17
src/api/alert/model/model/assessReportModel.ts

@ -121,3 +121,20 @@ export interface AssessCleanSummary {
envelopeClauses?: string[]
timeRange?: string[]
}
export interface AssessReportName {
id: number
name: string
}
export interface AssessReportSimple {
id: number
score?: number
coverRange?: number
validFlag?: boolean
identifier?: string
verifier?: string
assessTime?: string
version?: string
conditionName?: string
}

14
src/api/alert/model/models.ts

@ -11,12 +11,14 @@ enum Api {
TRAIN_MODEL = '/alert/model/train',
TEST_MODEL = '/alert/model/test',
BOTTOM_MODEL = '/alert/model/bottom/',
VERSION_LIST = '/alert/model/version/',
VERSION_NEW = '/alert/model/version/new/',
}
export function modelCardListApi(params?: ModelQueryParams) {
return defHttp.get<ModelCardItem[]>({ url: Api.MODEL_CARD_LIST, params })
}
export const modelInfoApi = (id: any) => defHttp.get<any>({ url: `${Api.MODEL_INFO}/${id}` })
export const modelInfoApi = (idOrPath: any) => defHttp.get<any>({ url: `${Api.MODEL_INFO}/${idOrPath}` })
export function updateModelInfo(params: any) {
return defHttp.patch<boolean>({ url: Api.MODEL_INFO, params })
@ -39,4 +41,12 @@ export const trainModelApi = (params: any) => defHttp.post<any>({ url: Api.TRAIN
export const testModelApi = (params: any) => defHttp.post<any>({ url: Api.TEST_MODEL, data: params })
export const bottomModelApi = (id: any) => defHttp.post<any>({ url: Api.BOTTOM_MODEL + id })
export const bottomModelApi = (id: any, reportId?: number | string) =>
defHttp.post<any>({
url: Api.BOTTOM_MODEL + id,
data: reportId ? { reportId } : {},
})
export const versionListApi = (id: any) => defHttp.get<any[]>({ url: Api.VERSION_LIST + id })
export const createDraftVersionApi = (id: any) => defHttp.post<any>({ url: Api.VERSION_NEW + id })

216
src/views/model/AssessReport.vue

@ -25,7 +25,7 @@ import {
fetchAssessCondition,
fetchCleanSummary,
fetchReportDetail,
saveAssessReport,
saveAssessReportLegacy,
} from '@/api/alert/model/assessReport'
import type {
AssessCleanSummary,
@ -37,7 +37,8 @@ import type {
import { PageWrapper } from '@/components/Page'
import { useMessage } from '@/hooks/web/useMessage'
import { useUserStore } from '@/store/modules/user'
import { modelInfoApi } from '@/api/alert/model/models'
import { modelInfoApi, versionListApi } from '@/api/alert/model/models'
import Icon from '@/components/Icon'
type RangeValue = [Dayjs, Dayjs]
@ -98,13 +99,14 @@ export default defineComponent({
const assessResult = ref<AssessResultRow[]>(defaultAssessResult())
const cleanSummary = ref<AssessCleanSummary>()
const cleanModalVisible = ref(false)
const selectedVersion = ref<string>(((route.query.version as string) || 'v-test').replace('%20', ' '))
const formModel = reactive<{
version: string
timeRange?: RangeValue
description?: string
}>({
version: ((route.query.version as string) || 'v-test').replace('%20', ' '),
version: selectedVersion.value,
timeRange: undefined,
description: '',
})
@ -115,6 +117,123 @@ export default defineComponent({
percent: 15,
})
const basicColumns = computed<ColumnProps<any>[]>(() => [
{
title: ' ',
dataIndex: 'group',
width: 120,
align: 'center',
customRender: ({ record }) => {
if (record.group === '模型参数') {
return {
children: record.group,
props: { rowSpan: record.groupRowSpan },
}
}
return {
children: record.name ?? '',
props: { rowSpan: 1 },
}
},
},
{
title: '序号',
dataIndex: 'displayIndex',
width: 80,
align: 'center',
customRender: ({ record }) => {
if (record.group === '模型信息')
return { children: '', props: { colSpan: 0 } }
return record.displayIndex ?? record.index ?? '--'
},
},
{
title: '参数名称',
dataIndex: 'name',
width: 220,
align: 'center',
customRender: ({ record, text }) => {
if (record.group === '模型信息')
return { children: '', props: { colSpan: 0 } }
return record.description ?? record.name ?? text ?? '--'
},
},
{
title: '参数KKS码 / 内容',
dataIndex: 'contentOrPoint',
width: 200,
align: 'center',
customRender: ({ record }) => {
if (record.group === '模型参数')
return record.pointId ?? record.PointId ?? '--'
return {
children: record.content ?? '--',
props: { colSpan: 5 },
}
},
},
{
title: '是否锁定',
dataIndex: 'lock',
width: 120,
align: 'center',
customRender: ({ record, text }) => {
if (record.group === '模型信息')
return { children: '', props: { colSpan: 0 } }
return text ? '是' : '否'
},
},
{
title: '是否参与预警',
dataIndex: 'alarm',
width: 160,
align: 'center',
customRender: ({ record }) => {
if (record.group === '模型信息')
return { children: '', props: { colSpan: 0 } }
return h(Switch, {
checked: record.alarm,
disabled: record.lock,
onChange: (checked: boolean) => toggleAlarm(record, checked),
})
},
},
])
const basicRows = computed(() => {
const paramRows = (pointRows.value || []).map((item, index) => ({
...item,
key: `param-${item.pointId || index}`,
group: '模型参数',
displayIndex: (item.index ?? index) + 1,
groupRowSpan: index === 0 ? (pointRows.value?.length || 0) : 0,
}))
const versionRow = {
name: '版本',
content: selectedVersion.value || 'v-test',
key: 'info-version',
group: '模型信息',
displayIndex: '--',
}
const infoRows = [versionRow].concat((modeRows.value || []).map((item, index) => ({
...item,
key: `info-${index}`,
group: '模型信息',
displayIndex: '--',
})))
return [...paramRows, ...infoRows]
})
const sumTrainMode = (trainTime?: any[]) => {
if (!Array.isArray(trainTime))
return undefined
return trainTime.reduce((sum, item) => {
const modeVal = Number(item?.mode)
return sum + (Number.isFinite(modeVal) ? modeVal : 0)
}, 0)
}
const normalizePointRow = (item: any, index: number): AssessPointRow => {
const tMax = item.TMax ?? item.tMax
const tMin = item.TMin ?? item.tMin
@ -193,16 +312,18 @@ export default defineComponent({
timestamp.value = detail.report.time || ''
}
else {
const modelInfo = await modelInfoApi(modelId.value as string)
const modelInfo = await modelInfoApi(`${modelId.value}?version=${encodeURIComponent(formModel.version)}`)
baseInfo.value = modelInfo || {}
modelName.value = modelInfo?.name || modelInfo?.Model_Name || ''
selectedVersion.value = modelInfo?.version || modelInfo?.Cur_Version || formModel.version
pointRows.value = (modelInfo?.pointInfo || []).map((item: any, index: number) => normalizePointRow(item, index))
const sampleCount = sumTrainMode(modelInfo?.trainTime)
modeRows.value = [
{ name: '主元个数', content: modelInfo?.principal ?? '' },
{ name: '运行模式', content: modelInfo?.alarmmodelset?.alarmname || '全工况运行' },
{ name: '模式编码', content: modelInfo?.alarmmodelset?.alarmcondition || '1=1' },
{ name: '样本类型', content: '训练样本' },
{ name: '样本数量', content: modelInfo?.trainTime?.length || '--' },
{ name: '样本数量', content: sampleCount ?? '--' },
]
assessRows.value
= pointRows.value
@ -212,12 +333,8 @@ export default defineComponent({
bottomScore.value = modelInfo?.model_score
coverage.value = modelInfo?.load_coverage
timestamp.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
if (!identifier.value) {
identifier.value = userStore.getUserInfo?.username
|| userStore.getUserInfo?.nickName
|| userStore.getUserInfo?.userName
|| ''
}
if (!identifier.value)
identifier.value = userStore.getUserInfo?.user?.nickname
}
if (algorithm.value) {
const condition = await fetchAssessCondition(algorithm.value)
@ -233,6 +350,7 @@ export default defineComponent({
}
}
const toggleAlarm = (row: AssessPointRow, checked: boolean) => {
row.alarm = checked
assessRows.value = assessRows.value.filter(item => item.pointId !== row.pointId)
@ -328,7 +446,7 @@ export default defineComponent({
const limit: number[] = []
const uplow: string[] = []
points.forEach((p) => {
dead.push(p.dead ? 0 : 1)
dead.push(p.dead ? 1 : 0)
limit.push(p.limit ? 1 : 0)
uplow.push(`${p.Lower ?? p.lower ?? ''},${p.Upper ?? p.upper ?? ''}`)
})
@ -420,10 +538,10 @@ export default defineComponent({
const match = item.pointId === result.pointId || item.index === result.index
if (!match)
return item
const fdrForward = (result.fdrForward * 100).toFixed(2)
const fdrReverse = (result.fdrReverse * 100).toFixed(2)
const farForward = (result.farForward * 100).toFixed(2)
const farReverse = (result.farReverse * 100).toFixed(2)
const fdrForward = (Number(result.fdrForward) * 100).toFixed(2)
const fdrReverse = (Number(result.fdrReverse) * 100).toFixed(2)
const farForward = (Number(result.farForward) * 100).toFixed(2)
const farReverse = (Number(result.farReverse) * 100).toFixed(2)
const reconForward = result.reconForward !== undefined ? result.reconForward.toFixed(2) : '--'
const reconReverse = result.reconReverse !== undefined ? result.reconReverse.toFixed(2) : '--'
return {
@ -435,7 +553,7 @@ export default defineComponent({
})
})
}
updateAssessResult(res?.durationSeconds, res?.coverage)
updateAssessResult(res?.durationSeconds ?? 0, res?.coverage ?? 0)
if (!timestamp.value)
timestamp.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
@ -480,7 +598,11 @@ export default defineComponent({
timestamp: timestamp.value || dayjs().format('YYYY-MM-DD HH:mm:ss'),
conditionName: modeRows.value[1]?.content as string,
}
await saveAssessReport(payload)
// legacy Assess_Report_CFG
await saveAssessReportLegacy({
...payload,
rawReport: JSON.stringify(payload.report),
})
createMessage.success('评估报告已提交')
router.back()
}
@ -511,44 +633,6 @@ export default defineComponent({
}
}
const pointColumns = computed<ColumnProps<AssessPointRow>[]>(() => [
{
title: ' ',
dataIndex: 'group',
width: 120,
align: 'center',
customRender: () => '模型参数',
},
{
title: '序号',
dataIndex: 'index',
width: 80,
align: 'center',
customRender: ({ record, index }) => (record.index ?? index) + 1,
},
{ title: '参数名称', dataIndex: 'description', width: 220, align: 'center' },
{ title: '参数KKS码', dataIndex: 'pointId', width: 200, align: 'center' },
{
title: '是否锁定',
dataIndex: 'lock',
width: 120,
align: 'center',
customRender: ({ text }) => (text ? '是' : '否'),
},
{
title: '是否参与预警',
dataIndex: 'alarm',
width: 160,
align: 'center',
customRender: ({ record }) =>
h(Switch, {
checked: record.alarm,
disabled: record.lock,
onChange: (checked: boolean) => toggleAlarm(record, checked),
}),
},
])
const assessColumns = computed<ColumnProps<AssessRow>[]>(() => [
{ title: '参与预警的参数', dataIndex: 'description', width: 200, align: 'center' },
{ title: '单位', dataIndex: 'unit', width: 80, align: 'center' },
@ -616,7 +700,9 @@ export default defineComponent({
loading,
modeRows,
modelName,
pointColumns,
selectedVersion,
basicColumns,
basicRows,
pointRows,
toggleAlarm,
updateAssessResult,
@ -635,20 +721,12 @@ export default defineComponent({
1. 模型基本信息
</h3>
<a-table
:columns="pointColumns" :data-source="pointRows" row-key="pointId" size="small" :pagination="false"
bordered align="center"
/>
<a-table
class="mode-table"
:columns="[
{ title: '', dataIndex: 'name', width: 200, align: 'center' },
{ title: '', dataIndex: 'content', align: 'center' },
]"
:data-source="modeRows"
:pagination="false"
:columns="basicColumns"
:data-source="basicRows"
:row-key="record => record.key || record.pointId || record.name"
size="small"
:pagination="false"
bordered
row-key="name"
align="center"
/>
</a-card>

8
src/views/model/list/ModelCard.vue

@ -26,8 +26,9 @@ const [registerDraw, { openDrawer }] = useDrawer()
//
const go = useGo()
function changeModel(id) {
go(`/model/train/${id}`)
function changeModel(id, version?) {
const versionParam = version ? `?version=${encodeURIComponent(version)}` : ''
go(`/model/train/${id}${versionParam}`)
}
const modelCardList = ref<Array<ModelItem>>([])
@ -55,6 +56,7 @@ watch(
const card: ModelItem = {
id: modelCard.id,
title: modelCard.name,
version: modelCard.version,
icon: icons[modelCard.status],
value: 1,
total: 1,
@ -90,7 +92,7 @@ watch(
:hoverable="true"
:body-style="item.bodyStyle"
:head-style="item.headStyle"
@click="changeModel(item.id)"
@click="changeModel(item.id, item.version)"
>
<template #extra>
<Icon :icon="item.icon" :size="30" color="white" />

1
src/views/model/list/data.tsx

@ -10,6 +10,7 @@ export interface ModelItem {
status: string
creator: string
createTime: string
version?: string
description: string
headStyle: CSSProperties
bodyStyle: CSSProperties

405
src/views/model/train/index.vue

@ -19,8 +19,11 @@ import {
Modal,
RangePicker,
Row,
Select,
Space,
Spin,
Steps,
Table,
Tabs,
Transfer,
} from 'ant-design-vue'
@ -30,16 +33,21 @@ import { BasicTable, useTable } from '@/components/Table'
import { PageWrapper } from '@/components/Page'
import {
bottomModelApi,
createDraftVersionApi,
modelInfoApi,
testModelApi,
trainModelApi,
updateModelInfo,
versionListApi,
} from '@/api/alert/model/models'
import { fetchAssessReportList, fetchAssessReportNames } from '@/api/alert/model/assessReport'
import type { AssessReportSimple } from '@/api/alert/model/model/assessReportModel'
import { getExaHistorys } from '@/api/alert/exa/index'
import { useECharts } from '@/hooks/web/useECharts'
import { useMessage } from '@/hooks/web/useMessage'
import { pointListApi } from '@/api/alert/model/select'
import { useGo } from '@/hooks/web/usePage'
import Icon from '@/components/Icon'
export default defineComponent({
components: {
@ -53,6 +61,7 @@ export default defineComponent({
[Steps.Step.name]: Steps.Step,
ATabs: Tabs,
ATabPane: Tabs.TabPane,
ATable: Table,
ARangePicker: RangePicker,
AForm: Form,
AFormItem: FormItem,
@ -66,19 +75,26 @@ export default defineComponent({
ATransfer: Transfer,
ARow: Row,
ACol: Col,
ASelect: Select,
ASpace: Space,
Icon,
},
setup() {
const { createMessage } = useMessage()
const route = useRoute()
const go = useGo()
const id = route.params.id
const routeVersion = route.query.version as string | undefined
const model = ref(null)
const brushActivated = ref<Set<number>>(new Set())
const spinning = ref(false)
let trainTimeCopy = ''
const fetchModelInfo = async () => {
const modelInfo = await modelInfoApi(id)
const fetchModelInfo = async (version?: string) => {
const path = version ? `${id}?version=${encodeURIComponent(version)}` : (routeVersion ? `${id}?version=${encodeURIComponent(routeVersion)}` : id)
const modelInfo = await modelInfoApi(path)
model.value = normalizeTrainTime(modelInfo)
selectedVersion.value = modelInfo?.version || modelInfo?.Cur_Version || ''
await loadReportName(selectedVersion.value)
trainTimeCopy = JSON.stringify(model.value.trainTime)
getHistory()
}
@ -117,6 +133,22 @@ export default defineComponent({
scroll: { y: 300 },
})
const updatePrincipalFromTrainTime = (trainTimeList: any[]) => {
if (!Array.isArray(trainTimeList) || !model.value)
return
// principal is the sum of mode for each trainTime entry
model.value.principal = trainTimeList.reduce((sum, item) => {
const modeVal = Number(item?.mode)
return sum + (Number.isFinite(modeVal) ? modeVal : 0)
}, 0)
}
watch(
() => model.value?.trainTime,
newTrainTime => updatePrincipalFromTrainTime(newTrainTime as any[]),
{ deep: true, immediate: true },
)
const activeKey = ref('1')
type RangeValue = [Dayjs, Dayjs]
const currentDate: Dayjs = dayjs('2023-10-29 00:00:00')
@ -134,7 +166,7 @@ export default defineComponent({
return
spinning.value = true
if (model.value.para) {
getTestData()
await getTestData()
}
else {
const params = {
@ -160,12 +192,15 @@ export default defineComponent({
spinning.value = false
}
async function getTestData() {
async function getTestData(range?: RangeValue) {
const timeRange = range || historyTime.value
if (!timeRange)
return
const params = {
Model_id: id,
version: model.value?.Cur_Version ? model.value?.Cur_Version : 'v-test',
Test_Data: {
time: historyTime.value
time: timeRange
.map(t => dayjs(t).format('YYYY-MM-DD HH:mm:ss'))
.join(','),
points: model.value.pointInfo.map(t => t.pointId).join(','),
@ -173,20 +208,25 @@ export default defineComponent({
},
}
const result = await testModelApi(params)
const sampleData = result.sampleData
const reconData = result.reconData
const sampleData = result?.sampleData ?? result?.SampleData ?? []
const reconData = result?.reconData ?? result?.ReconData ?? []
if (!Array.isArray(sampleData) || !Array.isArray(reconData) || sampleData.length === 0 || reconData.length === 0) {
historyList.value = []
createMessage.warning('未获取到测试数据')
return
}
const xData = generateTimeList(
historyTime.value,
timeRange,
model.value.sampling * 1000,
)
historyList.value = sampleData.map((item, index) => {
const point = model.value?.pointInfo[index]
return {
data: [
item.map((t, i) => {
(Array.isArray(item) ? item : []).map((t, i) => {
return [xData[i], t]
}),
reconData[index].map((t, i) => {
(Array.isArray(reconData[index]) ? reconData[index] : []).map((t, i) => {
return [xData[i], t]
}),
],
@ -313,8 +353,8 @@ export default defineComponent({
type: 'brush',
areas,
})
brushActivated.value.add(index)
isInitBrush.value = true
brushActivated.value.add(index)
}
onMounted(async () => {
@ -349,8 +389,8 @@ export default defineComponent({
console.log('Selected train time:', trainTime)
if (trainTimeCopy === JSON.stringify(trainTime))
return
model.value = normalizeTrainTime({ ...model.value, trainTime })
trainTimeCopy = JSON.stringify(model.value.trainTime)
model.value.trainTime = trainTime
trainTimeCopy = JSON.stringify(trainTime)
echartsRefs.value.forEach((chart, index) => {
if (chart) {
chart.dispatchAction({
@ -447,7 +487,7 @@ export default defineComponent({
return
}
const params = {
conditon: modelInfo.alarmmodelset?.alarmcondition || '1==1',
conditon: modelInfo.alarmmodelset?.alarmcondition || '1=1',
Hyper_para: {
percent: modelInfo.rate,
},
@ -492,8 +532,8 @@ export default defineComponent({
model.value.pointInfo = (model.value.pointInfo || []).map((p: any, idx: number) => {
const q99 = qcul99Line[idx]
const upperB = parseFloat(p.upperBound)
const lowerB = parseFloat(p.lowerBound)
const upperB = Number.parseFloat(p.upperBound)
const lowerB = Number.parseFloat(p.lowerBound)
return {
...p,
TMax: pxMax[idx] !== undefined ? Number(pxMax[idx]).toFixed(2) : p.TMax,
@ -531,7 +571,27 @@ export default defineComponent({
}
updateModelInfoDebounced()
getTestData()
await updateModelInfoDebounced.flush()
// 使
try {
if (model.value.trainTime && model.value.trainTime.length > 0) {
const sorted = [...model.value.trainTime].sort(
(a, b) => dayjs(a.st).valueOf() - dayjs(b.st).valueOf(),
)
const trainRange: RangeValue = [
dayjs(sorted[0].st),
dayjs(sorted[sorted.length - 1].et),
]
historyTime.value = trainRange
await getTestData(trainRange)
}
else {
await getTestData()
}
}
catch (e) {
console.error('自动测试失败:', e)
}
createMessage.success('模型训练成功')
}
catch (error) {
@ -594,7 +654,7 @@ export default defineComponent({
}
const mode = ref({
alarmcondition: '1==1',
alarmcondition: '1=1',
alarmname: '全工况运行',
})
const openEditModeModal = ref<boolean>(false)
@ -602,7 +662,7 @@ export default defineComponent({
function openEditMode() {
openEditModeModal.value = true
mode.value = {
alarmcondition: model.value?.alarmmodelset?.alarmcondition || '1==1',
alarmcondition: model.value?.alarmmodelset?.alarmcondition || '1=1',
alarmname: model.value?.alarmmodelset?.alarmname || '全工况运行',
}
}
@ -696,31 +756,214 @@ export default defineComponent({
openEditModelModal.value = false
}
async function bottomModel() {
if (!model.value.para) {
const reportModalVisible = ref(false)
const reportLoading = ref(false)
const reportList = ref<AssessReportSimple[]>([])
const selectedReportId = ref<number | null>(null)
const reportName = ref<string>('暂无报告')
const reportNameOptions = ref<{ label: string; value: number }[]>([])
const reportNameSelectVisible = ref(false)
const selectedReportNameId = ref<number | null>(null)
const isVTestVersion = computed(
() => (model.value?.Cur_Version || model.value?.version) === 'v-test',
)
const isBottomed = computed(() => model.value?.btmState === '已下装')
const reportColumns = [
{ title: '评估时间', dataIndex: 'assessTime' },
{ title: '得分', dataIndex: 'score' },
{ title: '覆盖面', dataIndex: 'coverRange' },
{ title: '验证人', dataIndex: 'identifier' },
{ title: '版本', dataIndex: 'version' },
{ title: '模式', dataIndex: 'conditionName' },
]
function onSelectReport(selectedRowKeys: (string | number)[]) {
selectedReportId.value = (selectedRowKeys && selectedRowKeys.length > 0)
? Number(selectedRowKeys[0])
: null
}
function openReportSelector() {
loadReportName()
reportNameSelectVisible.value = true
}
function confirmReportSelect() {
if (selectedReportNameId.value)
goAssessReport(selectedReportNameId.value)
else
goAssessReport()
reportNameSelectVisible.value = false
}
async function openBottomModal() {
if (!model.value?.para) {
createMessage.error('模型未训练,无法下装')
return
}
if (!model.value?.trainTime || model.value.trainTime.length === 0) {
createMessage.error('模型尚未建立,无法下装')
return
}
if (model.value?.btmState === '已下装' && (model.value?.Cur_Version || model.value?.version) !== 'v-test') {
createMessage.error('模型已下装,无法重复操作')
return
}
reportLoading.value = true
try {
const version = model.value?.Cur_Version || model.value?.version || 'v-test'
// todo validtruefalse
const list = await fetchAssessReportList({ modelId: id as string, version, valid: false })
reportList.value = list || []
if (!reportList.value.length) {
createMessage.error('请先完成合格并审核通过的评估报告')
return
}
selectedReportId.value = reportList.value[0]?.id ?? null
reportModalVisible.value = true
}
catch (error) {
console.error('获取评估报告失败:', error)
createMessage.error('获取评估报告失败')
}
finally {
reportLoading.value = false
}
}
async function loadReportName(version?: string) {
try {
reportLoading.value = true
const names = await fetchAssessReportNames({ modelId: id as string, version: version || selectedVersion.value })
if (names && names.length > 0) {
reportName.value = names[0].name
selectedReportId.value = names[0].id
selectedReportNameId.value = names[0].id
reportNameOptions.value = names.map(n => ({ label: n.name, value: n.id }))
}
else {
reportName.value = '暂无报告'
selectedReportId.value = null
selectedReportNameId.value = null
reportNameOptions.value = []
}
}
catch (error) {
reportName.value = '暂无报告'
selectedReportNameId.value = null
reportNameOptions.value = []
}
finally {
reportLoading.value = false
}
}
async function confirmBottomModel() {
if (!selectedReportId.value) {
createMessage.warning('请选择评估报告')
return
}
spinning.value = true
try {
const response = await bottomModelApi(model.value.id)
model.value = response
const response = await bottomModelApi(model.value.id, selectedReportId.value)
model.value.version = response.version
createMessage.success('模型下装成功')
reportModalVisible.value = false
}
catch (error) {
console.error('模型下装失败:', error)
createMessage.error('模型下装失败')
}
finally {
spinning.value = false
}
}
const versionLoading = ref(false)
const versionList = ref<any[]>([])
const selectedVersion = ref<string>('')
const versionOptions = computed(() => {
const options = versionList.value
.filter(v => v?.version)
.map(v => ({ label: v.version, value: v.version }))
if (selectedVersion.value && !options.find(o => o.value === selectedVersion.value))
options.unshift({ label: selectedVersion.value, value: selectedVersion.value })
return options
})
const versionDropdownVisible = ref(false)
async function loadVersionList() {
versionLoading.value = true
try {
const list = await versionListApi(id)
versionList.value = list || []
}
catch (error) {
createMessage.error('获取版本列表失败')
}
finally {
versionLoading.value = false
}
}
async function handleVersionChange(value: string) {
if (!value)
return
await fetchModelInfo(value)
}
async function createDraftVersion() {
const confirmed = await new Promise<boolean>((resolve) => {
Modal.confirm({
title: '确认创建草稿版本?',
content: '创建草稿版本后将显示训练、清除、修改和下装按钮。',
onOk: () => resolve(true),
onCancel: () => resolve(false),
})
})
if (!confirmed)
return
try {
await createDraftVersionApi(id)
selectedVersion.value = 'v-test'
if (model.value) {
model.value = {
...model.value,
Cur_Version: 'v-test',
version: 'v-test',
}
}
createMessage.success('已创建草稿版本 v-test,请重新训练')
versionList.value = []
await fetchModelInfo('v-test')
}
catch (error) {
console.error('底层模型获取失败:', error)
createMessage.error('底层模型获取失败')
createMessage.error('创建草稿版本失败')
}
}
const showTrainActions = computed(
() => !isBottomed.value || isVTestVersion.value || selectedVersion.value === 'v-test',
)
function goAssessReport() {
const version = model.value?.Cur_Version || 'v-test'
function goAssessReport(reportId?: number | null | Event) {
// PointerEvent
const rid = (reportId && typeof reportId === 'object') ? null : reportId
const version = selectedVersion.value || model.value?.Cur_Version || 'v-test'
const algorithm = model.value?.algorithm || 'PCA'
go(`/model/assess-report/${id}?version=${encodeURIComponent(version)}&algorithm=${algorithm}`)
const extra = rid ? `&reportId=${rid}` : ''
go(`/model/assess-report/${id}?version=${encodeURIComponent(version)}&algorithm=${algorithm}${extra}`)
}
return {
pointTable,
model,
selectedVersion,
versionOptions,
versionDropdownVisible,
reportName,
activeKey,
historyTime,
historyList,
@ -752,8 +995,28 @@ export default defineComponent({
openEditModel,
handleEditModelOk,
handleEditModelCancel,
bottomModel,
openBottomModal,
confirmBottomModel,
reportModalVisible,
reportList,
reportColumns,
reportLoading,
selectedReportId,
onSelectReport,
reportName,
reportNameOptions,
reportNameSelectVisible,
selectedReportNameId,
openReportSelector,
confirmReportSelect,
loadReportName,
versionList,
versionLoading,
loadVersionList,
handleVersionChange,
createDraftVersion,
goAssessReport,
showTrainActions,
}
},
})
@ -771,18 +1034,47 @@ export default defineComponent({
{{ model?.description || "暂无描述" }}
</a-descriptions-item>
<a-descriptions-item label="版本">
{{ model?.Cur_Version || "v-test" }}
<template v-if="!versionDropdownVisible">
<span
style=" text-decoration: underline;cursor: pointer"
@click="() => { versionDropdownVisible = true; loadVersionList() }"
>
{{ selectedVersion || 'v-test' }}
</span>
<a-button type="link" size="small" @click="createDraftVersion">
<Icon icon="ant-design:plus-circle-outlined" />
</a-button>
</template>
<template v-else>
<a-select
v-model:value="selectedVersion"
size="small"
style="min-width: 160px"
:loading="versionLoading"
:options="versionOptions"
placeholder="选择版本"
:allow-clear="false"
@change="(v) => { handleVersionChange(v); versionDropdownVisible = false }"
@blur="versionDropdownVisible = false"
/>
</template>
</a-descriptions-item>
<a-descriptions-item label="评估报告">
<span
style=" text-decoration: underline;cursor: pointer"
@click="openReportSelector"
>
{{ reportName }}
</span>
<a-button type="link" size="small" @click="goAssessReport">
新增/查看
<Icon icon="ant-design:plus-circle-outlined" />
</a-button>
</a-descriptions-item>
<a-descriptions-item label="创建人">
{{ model?.founder }}
</a-descriptions-item>
<a-descriptions-item label="创建时间">
{{ model?.creatTime }}
{{ model?.creatTime || model?.createTime }}
</a-descriptions-item>
<a-descriptions-item label="最近修改人">
{{ model?.Modifier || "暂无" }}
@ -832,7 +1124,7 @@ export default defineComponent({
style="margin-top: 16px; margin-bottom: -20px"
>
<a-button size="large" @click="openEditMode">
{{ model?.alarmmodelset.alarmname }}
{{ model?.alarmmodelset?.alarmname || '全工况运行' }}
</a-button>
</a-card>
@ -875,6 +1167,7 @@ export default defineComponent({
</a-form-item>
</a-form>
<a-button
v-if="showTrainActions"
type="primary"
style="margin-left: auto"
@click="trainModel"
@ -882,6 +1175,7 @@ export default defineComponent({
模型训练
</a-button>
<a-button
v-if="showTrainActions"
type="primary"
style="margin-left: 10px"
@click="clearModel"
@ -889,19 +1183,18 @@ export default defineComponent({
清除训练结果
</a-button>
<a-button
v-if="showTrainActions"
type="primary"
style="margin-left: 10px"
@click="goAssessReport"
style="margin-left: 6px"
@click="openEditModel"
>
评估报告
</a-button>
<a-button type="primary" style="margin-left: 6px" @click="openEditModel">
修改模型
</a-button>
<a-button
v-if="showTrainActions"
danger
style="margin-left: 10px"
@click="bottomModel"
@click="openBottomModal"
>
下装
</a-button>
@ -931,6 +1224,40 @@ export default defineComponent({
</a-spin>
</a-card>
</div>
<a-modal
v-model:open="reportModalVisible"
title="选择评估报告"
:confirm-loading="spinning"
@ok="confirmBottomModel"
>
<a-table
:columns="reportColumns"
:data-source="reportList"
:loading="reportLoading"
row-key="id"
:pagination="false"
size="small"
:row-selection="{
type: 'radio',
selectedRowKeys: selectedReportId ? [selectedReportId] : [],
onChange: onSelectReport,
}"
/>
</a-modal>
<a-modal
v-model:open="reportNameSelectVisible"
title="评估报告版本"
@ok="confirmReportSelect"
@cancel="() => { reportNameSelectVisible = false }"
>
<a-select
v-model:value="selectedReportNameId"
:options="reportNameOptions"
style="width: 100%"
placeholder="请选择评估报告"
:loading="reportLoading"
/>
</a-modal>
<a-modal
v-model:open="openEditPointModal"
title="更改上下限"

Loading…
Cancel
Save