feat: refine demo flow measurement results

This commit is contained in:
2026-08-13 16:51:20 +08:00
parent 66c7dd1b65
commit 3db84a7fb2
3 changed files with 70 additions and 5 deletions

View File

@@ -2,8 +2,8 @@ import { useState, useEffect, useMemo, useRef } from 'react';
import { trpc } from '@/lib/trpc'; import { trpc } from '@/lib/trpc';
import { useAuth } from '@/_core/hooks/useAuth'; import { useAuth } from '@/_core/hooks/useAuth';
import { BarChart2, Ruler, Zap, Play, Trash2, ChevronDown, ChevronRight, RefreshCw, Flame, Box, Layers, Crosshair, Cpu, Timer, Square, Maximize2, FileSpreadsheet, Image as ImageIcon } from 'lucide-react'; import { BarChart2, Ruler, Zap, Play, Trash2, ChevronDown, ChevronRight, RefreshCw, Flame, Box, Layers, Crosshair, Cpu, Timer, Square, Maximize2, FileSpreadsheet, Image as ImageIcon } from 'lucide-react';
import GenericResultView, { filterVisibleMeasurements, formatValue, normalizeOverlay3D, numberArrayFromUnknown, statusClass, statusLabel, type Overlay3D } from './GenericResultView'; import GenericResultView, { filterVisibleMeasurements, formatValue, normalizeOverlay3D, numberArrayFromUnknown, statusClass, type Overlay3D } from './GenericResultView';
import { aggregateDemoFlowResults, type DemoFlowArtifactGroup, type DemoFlowStatusNotice } from './demoFlowAggregation'; import { aggregateDemoFlowResults, demoFlowStatusLabel, type DemoFlowArtifactGroup, type DemoFlowStatusNotice } from './demoFlowAggregation';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog';
@@ -937,7 +937,7 @@ export default function AnalysisPanel({ taskId, onReplay, onViewSmoothHeatmap, o
</td>} </td>}
<td className="px-2 text-right whitespace-nowrap"> <td className="px-2 text-right whitespace-nowrap">
<span className={`px-1 py-0.5 rounded text-[9px] ${statusClass(row.measurement.status)}`}> <span className={`px-1 py-0.5 rounded text-[9px] ${statusClass(row.measurement.status)}`}>
{row.measurement.status === 'info' ? '参考' : statusLabel(row.measurement)} {demoFlowStatusLabel(row.measurement)}
</span> </span>
</td> </td>
</tr> </tr>

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { aggregateDemoFlowResults, type DemoFlowSourceResult } from './demoFlowAggregation'; import { aggregateDemoFlowResults, demoFlowStatusLabel, type DemoFlowSourceResult } from './demoFlowAggregation';
function result(id: number, index: number, status: string, resultData: Record<string, unknown>): DemoFlowSourceResult { function result(id: number, index: number, status: string, resultData: Record<string, unknown>): DemoFlowSourceResult {
return { id, type: 'plugin', pluginId: `plugin-${id}`, status, resultData: { demoFlowIndex: index, ...resultData } }; return { id, type: 'plugin', pluginId: `plugin-${id}`, status, resultData: { demoFlowIndex: index, ...resultData } };
@@ -65,6 +65,47 @@ describe('aggregateDemoFlowResults', () => {
expect(model.tableRows).toHaveLength(4); expect(model.tableRows).toHaveLength(4);
}); });
it('过滤全流程指定的中间指标,同时保持其他有效测量项的原顺序', () => {
const model = aggregateDemoFlowResults([
result(10, 1, 'completed', {
measurements: [
{ name: '车体长度', value: 100, status: 'pass' },
{ name: '前后端平移差', value: 0.1, status: 'pass' },
{ name: '车体宽度', value: 20, status: 'warning' },
{ name: 'ICP fitness', value: 0.98, status: 'pass' },
{ name: '配准 RMSE', value: 0.02, status: 'pass' },
{ name: '车体高度', value: 30, status: 'fail' },
],
}),
], nameOf);
expect(model.measurementRows.map((row) => row.measurement.name)).toEqual([
'车体长度', '车体宽度', '车体高度',
]);
});
it('全流程结果使用统一结论文案,不采用算法自定义文案', () => {
expect(demoFlowStatusLabel({ status: 'pass' })).toBe('合格');
expect(demoFlowStatusLabel({ status: 'warning' })).toBe('警告');
expect(demoFlowStatusLabel({ status: 'fail' })).toBe('超差');
expect(demoFlowStatusLabel({ status: 'info' })).toBe('参考');
expect(demoFlowStatusLabel({})).toBe('参考');
const model = aggregateDemoFlowResults([
result(10, 1, 'completed', {
measurements: [
{ name: '通过项', value: 1, status: 'pass', passLabel: '通过' },
{ name: '警告项', value: 2, status: 'warning', warningLabel: '部分成功' },
{ name: '失败项', value: 3, status: 'fail', failLabel: '失败' },
],
}),
], nameOf);
expect(model.measurementRows.map((row) => demoFlowStatusLabel(row.measurement))).toEqual([
'合格', '警告', '超差',
]);
});
it('全部为参考项时进入空测量结果状态,且不生成算法分组行', () => { it('全部为参考项时进入空测量结果状态,且不生成算法分组行', () => {
const model = aggregateDemoFlowResults([ const model = aggregateDemoFlowResults([
result(10, 1, 'completed', { result(10, 1, 'completed', {

View File

@@ -1,5 +1,11 @@
import { filterVisibleMeasurements, type Measurement, type PluginResultLike } from './GenericResultView'; import { filterVisibleMeasurements, type Measurement, type PluginResultLike } from './GenericResultView';
const DEMO_FLOW_HIDDEN_MEASUREMENT_NAMES = new Set([
'前后端平移差',
'ICP fitness',
'配准 RMSE',
]);
export type DemoFlowSourceResult = { export type DemoFlowSourceResult = {
id: number; id: number;
type: string; type: string;
@@ -96,6 +102,24 @@ function hasOverlay(value: PluginResultLike['overlay3d']) {
return Boolean(value && value.kind !== 'none'); return Boolean(value && value.kind !== 'none');
} }
export function filterDemoFlowMeasurements(measurements: readonly Measurement[]): Measurement[] {
return filterVisibleMeasurements(measurements)
.filter((measurement) => !DEMO_FLOW_HIDDEN_MEASUREMENT_NAMES.has(measurement.name));
}
export function demoFlowStatusLabel(measurement: Pick<Measurement, 'status'>): string {
switch (measurement.status) {
case 'pass':
return '合格';
case 'warning':
return '警告';
case 'fail':
return '超差';
default:
return '参考';
}
}
export function aggregateDemoFlowResults( export function aggregateDemoFlowResults(
results: DemoFlowSourceResult[], results: DemoFlowSourceResult[],
getAlgorithmName: (result: DemoFlowSourceResult) => string, getAlgorithmName: (result: DemoFlowSourceResult) => string,
@@ -113,7 +137,7 @@ export function aggregateDemoFlowResults(
const data = (asRecord(result.resultData) ?? {}) as PluginResultLike & Record<string, unknown>; const data = (asRecord(result.resultData) ?? {}) as PluginResultLike & Record<string, unknown>;
const sourceAlgorithmId = result.pluginId ?? (typeof data.pluginId === 'string' ? data.pluginId : result.type); const sourceAlgorithmId = result.pluginId ?? (typeof data.pluginId === 'string' ? data.pluginId : result.type);
const algorithmName = getAlgorithmName(result); const algorithmName = getAlgorithmName(result);
const measurements = filterVisibleMeasurements(Array.isArray(data.measurements) ? data.measurements : []); const measurements = filterDemoFlowMeasurements(Array.isArray(data.measurements) ? data.measurements : []);
if (measurements.length > 0 && options.includeAlgorithmGroups) { if (measurements.length > 0 && options.includeAlgorithmGroups) {
tableRows.push({ kind: 'algorithm', sourceAlgorithmId, algorithmName, resultId: result.id, flowIndex: index }); tableRows.push({ kind: 'algorithm', sourceAlgorithmId, algorithmName, resultId: result.id, flowIndex: index });