Files
ozone-tech_owl_prime/src/domain/inspectionViewModel.ts
Даня Архипов 985f7c327d feat: maximize sorter demo realism and add safe agent MVP
Wire live classifyItem into continuous playback, add jam/E-stop cases,
presenter controls (seek/speed/hotkeys), quality modes, demo scripts,
audit docs, and a verify-only autonomous agent CLI with hard safety limits.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-15 16:53:35 +00:00

133 lines
3.8 KiB
TypeScript

/**
* Inspection View Model — adapts playback state to CV inspection overlay data.
*/
import type { ContinuousPlaybackState, CasePhase } from './continuousPlayback';
import type { Category } from './types';
import { ITEMS } from '../data/items';
export type DetectedShape = 'box' | 'cylinder' | 'round' | 'irregular' | 'unknown';
export interface InspectionData {
visible: boolean;
phase: CasePhase;
phaseLabel: string;
itemTitle: string;
shape: DetectedShape;
dimensions: {
width: number;
depth: number;
height: number;
};
roundness: number;
confidence: number;
isLowConfidence: boolean;
category: Category | null;
command: string;
warning: string | null;
dimensionsFail: boolean;
roundnessFail: boolean;
cPriority: boolean;
}
const PHASE_LABELS: Record<CasePhase, string> = {
spawn: 'Waiting...',
move_to_detection: 'Approaching camera',
detection: 'Detecting object',
measurement: 'Measuring dimensions',
classification: 'Classifying',
command_sent: 'Command sent',
routing: 'Routing to zone',
exit: 'Exiting',
clear_gap: 'Clear',
fault_hold: 'FAULT / Jam',
emergency_hold: 'EMERGENCY STOP',
recover: 'Recovering',
};
const VISIBLE_PHASES: CasePhase[] = [
'detection',
'measurement',
'classification',
'command_sent',
'routing',
'fault_hold',
'emergency_hold',
];
function normalizeShape(shape: string, roundness: number): DetectedShape {
const lower = shape.toLowerCase();
if (lower.includes('cylinder') || lower.includes('bottle')) return 'cylinder';
if (lower.includes('round') || lower.includes('plate') || roundness >= 0.7) return 'round';
if (lower.includes('box') || lower.includes('rectangular')) return 'box';
if (lower.includes('irregular') || lower.includes('soft')) return 'irregular';
return 'unknown';
}
function checkDimensionsFail(dims: { width: number; depth: number; height: number }): boolean {
const MIN = { width: 10, depth: 10, height: 10 };
const MAX = { width: 450, depth: 320, height: 320 };
return (
dims.width < MIN.width || dims.depth < MIN.depth || dims.height < MIN.height ||
dims.width > MAX.width || dims.depth > MAX.depth || dims.height > MAX.height
);
}
export function getInspectionData(playback: ContinuousPlaybackState): InspectionData {
const currentCase = playback.currentCase;
const phase = playback.currentPhase;
// Get item data
const itemId = currentCase.itemId.replace('-LC', '');
const itemData = ITEMS.find(i => i.id === itemId) ?? ITEMS[0];
const isLowConfidence = currentCase.id === 'low_confidence';
const confidence = isLowConfidence ? 0.58 : itemData.confidence;
const dimensions = itemData.dimensionsMm;
const roundness = itemData.roundness;
const shape = normalizeShape(itemData.shape, roundness);
const dimensionsFail = checkDimensionsFail(dimensions);
const roundnessFail = roundness >= 0.7;
const cPriority = currentCase.id === 'c_priority';
const category = playback.targetCategory;
const command = playback.command;
return {
visible: VISIBLE_PHASES.includes(phase),
phase,
phaseLabel: PHASE_LABELS[phase],
itemTitle: currentCase.title,
shape,
dimensions,
roundness,
confidence,
isLowConfidence,
category,
command,
warning: currentCase.warning ?? null,
dimensionsFail,
roundnessFail,
cPriority,
};
}
export function shouldShowBoundingBox(phase: CasePhase): boolean {
return phase === 'measurement' || phase === 'classification';
}
export function shouldShowScanEffect(phase: CasePhase): boolean {
return phase === 'detection';
}
export function shouldShowShapeOutline(phase: CasePhase): boolean {
return phase === 'classification' || phase === 'command_sent';
}
export function shouldHighlightCamera(phase: CasePhase): boolean {
return phase === 'detection' || phase === 'measurement';
}