feat: add presentation demo mode
This commit is contained in:
22
src/data/criteria.test.ts
Normal file
22
src/data/criteria.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { OZON_CRITERIA } from './criteria';
|
||||
import { SCENARIOS } from './scenarios';
|
||||
|
||||
const scenarioIds = new Set(SCENARIOS.map((scenario) => scenario.id));
|
||||
|
||||
describe('OZON criteria', () => {
|
||||
it('contains evidence and valid scenario references', () => {
|
||||
expect(OZON_CRITERIA.length).toBeGreaterThan(0);
|
||||
|
||||
for (const criterion of OZON_CRITERIA) {
|
||||
expect(criterion.title).toBeTruthy();
|
||||
expect(criterion.status).toMatch(/covered|partially covered|demo step available/);
|
||||
expect(criterion.evidence).toBeTruthy();
|
||||
expect(criterion.relatedDoc).toBeTruthy();
|
||||
expect(criterion.relatedScenarioIds.length).toBeGreaterThan(0);
|
||||
for (const scenarioId of criterion.relatedScenarioIds) {
|
||||
expect(scenarioIds.has(scenarioId)).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
108
src/data/criteria.ts
Normal file
108
src/data/criteria.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import type { OzonCriterion } from '../domain/types';
|
||||
|
||||
export const OZON_CRITERIA: OzonCriterion[] = [
|
||||
{
|
||||
id: 'category-correctness',
|
||||
title: 'Category correctness',
|
||||
status: 'covered',
|
||||
evidence: 'Classifier tests and decision tree prove B/C/D outcomes for mock SKUs.',
|
||||
relatedScenarioIds: ['normal_flow', 'oversized_item', 'round_object'],
|
||||
relatedDoc: 'docs/SCENARIOS.md',
|
||||
},
|
||||
{
|
||||
id: 'classification-rules',
|
||||
title: 'Classification rules: dimensions, roundness, priority',
|
||||
status: 'covered',
|
||||
evidence: 'ClassificationPanel shows thresholds, actual values, PASS/FAIL and C priority.',
|
||||
relatedScenarioIds: ['oversized_item', 'round_object', 'boundary_dimensions'],
|
||||
relatedDoc: 'docs/ARCHITECTURE.md',
|
||||
},
|
||||
{
|
||||
id: 'boundary-cases',
|
||||
title: 'Boundary cases',
|
||||
status: 'covered',
|
||||
evidence: 'Boundary dimensions scenario and tests cover max boundary and min-width violation.',
|
||||
relatedScenarioIds: ['boundary_dimensions'],
|
||||
relatedDoc: 'docs/SCENARIOS.md',
|
||||
},
|
||||
{
|
||||
id: 'physical-routing',
|
||||
title: 'Physical routing B/C/D',
|
||||
status: 'covered',
|
||||
evidence: 'SorterScene displays B straight route, C lower route and D upper route with commands.',
|
||||
relatedScenarioIds: ['normal_flow', 'oversized_item', 'round_object'],
|
||||
relatedDoc: 'docs/DEMO_SCRIPT.md',
|
||||
},
|
||||
{
|
||||
id: 'manipulation-logic',
|
||||
title: 'Manipulation logic',
|
||||
status: 'covered',
|
||||
evidence: 'Stop-gate and pusher states are tied to state machine routes.',
|
||||
relatedScenarioIds: ['oversized_item', 'round_object'],
|
||||
relatedDoc: 'docs/ARCHITECTURE.md',
|
||||
},
|
||||
{
|
||||
id: 'geometry-variety',
|
||||
title: 'Geometry variety',
|
||||
status: 'covered',
|
||||
evidence: 'Mock items include boxes, pouf, plate, bottle, cylinder, pen and boundary box.',
|
||||
relatedScenarioIds: ['normal_flow', 'round_object', 'boundary_dimensions'],
|
||||
relatedDoc: 'docs/SCENARIOS.md',
|
||||
},
|
||||
{
|
||||
id: 'safety-emergency',
|
||||
title: 'Safety / emergency stop',
|
||||
status: 'demo step available',
|
||||
evidence: 'Emergency stop scenario enters EMERGENCY_STOP and requires Reset.',
|
||||
relatedScenarioIds: ['emergency_stop'],
|
||||
relatedDoc: 'docs/DEMO_SCRIPT.md',
|
||||
},
|
||||
{
|
||||
id: 'cycle-time-throughput',
|
||||
title: 'Cycle time and throughput',
|
||||
status: 'covered',
|
||||
evidence: 'Metrics cards show processed count, avg cycle time and throughput.',
|
||||
relatedScenarioIds: ['normal_flow', 'close_items'],
|
||||
relatedDoc: 'docs/ARCHITECTURE.md',
|
||||
},
|
||||
{
|
||||
id: 'timing-synchronization',
|
||||
title: 'Timing synchronization',
|
||||
status: 'covered',
|
||||
evidence: 'CycleTimeline shows state timestamps, durations and active/done status.',
|
||||
relatedScenarioIds: ['normal_flow', 'close_items'],
|
||||
relatedDoc: 'docs/ARCHITECTURE.md',
|
||||
},
|
||||
{
|
||||
id: 'fault-handling',
|
||||
title: 'Fault handling',
|
||||
status: 'demo step available',
|
||||
evidence: 'Jam scenario enters FAULT, logs fault and stops conveyor.',
|
||||
relatedScenarioIds: ['jam', 'low_confidence'],
|
||||
relatedDoc: 'docs/JURY_QA.md',
|
||||
},
|
||||
{
|
||||
id: 'cv-actuator-integration',
|
||||
title: 'CV-to-actuator integration',
|
||||
status: 'covered',
|
||||
evidence: 'Pseudo-CV packet feeds classification, which emits ROUTE_TO_* actuator commands.',
|
||||
relatedScenarioIds: ['normal_flow', 'low_confidence'],
|
||||
relatedDoc: 'docs/ARCHITECTURE.md',
|
||||
},
|
||||
{
|
||||
id: 'engineering-realism',
|
||||
title: 'Engineering realism',
|
||||
status: 'partially covered',
|
||||
evidence: 'Scaled 2D layout, sensor latency, PID imitation and event trace are present; real hardware is out of MVP scope.',
|
||||
relatedScenarioIds: ['normal_flow', 'jam', 'emergency_stop'],
|
||||
relatedDoc: 'docs/JURY_QA.md',
|
||||
},
|
||||
{
|
||||
id: 'reproducibility-docs',
|
||||
title: 'Reproducibility / docs',
|
||||
status: 'covered',
|
||||
evidence: 'README and docs describe architecture, demo script, scenarios and jury Q&A.',
|
||||
relatedScenarioIds: ['normal_flow', 'boundary_dimensions'],
|
||||
relatedDoc: 'docs/DEMO_SCRIPT.md',
|
||||
},
|
||||
];
|
||||
17
src/data/demoSteps.test.ts
Normal file
17
src/data/demoSteps.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { DEMO_STEPS } from './demoSteps';
|
||||
import { SCENARIOS } from './scenarios';
|
||||
|
||||
const scenarioIds = new Set(SCENARIOS.map((scenario) => scenario.id));
|
||||
|
||||
describe('demo steps', () => {
|
||||
it('have required narrative fields and valid scenarios', () => {
|
||||
for (const step of DEMO_STEPS) {
|
||||
expect(step.id).toBeTruthy();
|
||||
expect(step.title).toBeTruthy();
|
||||
expect(scenarioIds.has(step.scenarioId)).toBe(true);
|
||||
expect(step.relatedCriteria.length).toBeGreaterThan(0);
|
||||
expect(step.presenterPhrase).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
124
src/data/demoSteps.ts
Normal file
124
src/data/demoSteps.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import type { DemoStep } from '../domain/types';
|
||||
|
||||
export const DEMO_STEPS: DemoStep[] = [
|
||||
{
|
||||
id: 'system-overview',
|
||||
title: 'System overview',
|
||||
scenarioId: 'normal_flow',
|
||||
preferredAction: 'reset',
|
||||
focusArea: 'scene',
|
||||
explanation: 'The line is presented as detection -> classification -> actuator command -> physical route.',
|
||||
whatToWatch: 'Scaled SVG scene, sensor positions, stop-gate, pushers and B/C/D routing arrows.',
|
||||
juryValue: 'Shows that the project is an engineering simulation of a sorting cell, not a decorative animation.',
|
||||
relatedCriteria: ['engineering-realism', 'cv-actuator-integration', 'physical-routing'],
|
||||
presenterPhrase: 'We model the whole loop: the item is detected, classified by rules, converted to an actuator command and routed physically.',
|
||||
},
|
||||
{
|
||||
id: 'normal-item-to-b',
|
||||
title: 'Normal item to B',
|
||||
scenarioId: 'normal_flow',
|
||||
preferredAction: 'step',
|
||||
focusArea: 'classification',
|
||||
explanation: 'A regular box passes dimensions and roundness checks, so it stays on the main sorter path.',
|
||||
whatToWatch: 'Decision tree PASS/PASS and ROUTE_TO_B command in the scene and event log.',
|
||||
juryValue: 'Proves the happy path and shows category correctness for standard goods.',
|
||||
relatedCriteria: ['category-correctness', 'classification-rules', 'physical-routing'],
|
||||
presenterPhrase: 'For a normal SKU, dimensions pass and circular section is not detected, so the gate opens to B.',
|
||||
},
|
||||
{
|
||||
id: 'oversized-to-c',
|
||||
title: 'Oversized item to C',
|
||||
scenarioId: 'oversized_item',
|
||||
preferredAction: 'step',
|
||||
focusArea: 'classification',
|
||||
explanation: 'Oversized goods fail the first rule and are routed to roll-cage C before shape is considered.',
|
||||
whatToWatch: 'Dimensions FAIL, category C, pusher C and route arrow to the lower roll-cage.',
|
||||
juryValue: 'Shows rule priority and safe diversion for non-compliant dimensions.',
|
||||
relatedCriteria: ['classification-rules', 'boundary-cases', 'manipulation-logic'],
|
||||
presenterPhrase: 'C has priority because geometry safety is checked before shape: this item cannot continue on the main sorter.',
|
||||
},
|
||||
{
|
||||
id: 'round-object-to-d',
|
||||
title: 'Round object to D',
|
||||
scenarioId: 'round_object',
|
||||
preferredAction: 'step',
|
||||
focusArea: 'scene',
|
||||
explanation: 'Roundness >= 0.8 after valid dimensions sends the item to D for repack/shape issue handling.',
|
||||
whatToWatch: 'Roundness FAIL for B, category D and the upper route/pusher D command.',
|
||||
juryValue: 'Shows how the circle-section rule becomes a physical route.',
|
||||
relatedCriteria: ['classification-rules', 'geometry-variety', 'physical-routing'],
|
||||
presenterPhrase: 'The dimensions fit, but roundness is high, so the system sends it to D for do-upakovka.',
|
||||
},
|
||||
{
|
||||
id: 'boundary-dimensions',
|
||||
title: 'Boundary dimensions',
|
||||
scenarioId: 'boundary_dimensions',
|
||||
preferredAction: 'step',
|
||||
focusArea: 'classification',
|
||||
explanation: 'Items at the edge of the allowed envelope prove the rule implementation is stable.',
|
||||
whatToWatch: '450 x 320 x 320 passes, while width 9 mm fails min width and goes to C.',
|
||||
juryValue: 'Shows deterministic boundary behavior rather than visual guessing.',
|
||||
relatedCriteria: ['boundary-cases', 'category-correctness', 'reproducibility-docs'],
|
||||
presenterPhrase: 'The exact boundary is accepted, but one millimeter below minimum width is rejected.',
|
||||
},
|
||||
{
|
||||
id: 'low-confidence-fallback',
|
||||
title: 'Low confidence fallback',
|
||||
scenarioId: 'low_confidence',
|
||||
preferredAction: 'step',
|
||||
focusArea: 'eventLog',
|
||||
explanation: 'Low pseudo-CV confidence raises a warning but does not block rule-based classification.',
|
||||
whatToWatch: 'Warning message, fallback note and final category in classification/event log.',
|
||||
juryValue: 'Shows graceful degradation when CV is uncertain.',
|
||||
relatedCriteria: ['cv-actuator-integration', 'fault-handling', 'classification-rules'],
|
||||
presenterPhrase: 'CV confidence is low, so we flag it, but dimensions and roundness still produce a deterministic decision.',
|
||||
},
|
||||
{
|
||||
id: 'close-items-queue',
|
||||
title: 'Close items queue',
|
||||
scenarioId: 'close_items',
|
||||
preferredAction: 'start',
|
||||
focusArea: 'timeline',
|
||||
explanation: 'Close spacing creates a queue warning and items are still processed one cycle at a time.',
|
||||
whatToWatch: 'Queue length, queue delay, spacing warning and sequential timeline.',
|
||||
juryValue: 'Shows robustness for a common operational issue without overcomplicated physics.',
|
||||
relatedCriteria: ['timing-synchronization', 'cycle-time-throughput', 'fault-handling'],
|
||||
presenterPhrase: 'The line detects close spacing, holds the queue and keeps the state machine deterministic.',
|
||||
},
|
||||
{
|
||||
id: 'jam-fault-handling',
|
||||
title: 'Jam / fault handling',
|
||||
scenarioId: 'jam',
|
||||
preferredAction: 'step',
|
||||
focusArea: 'safety',
|
||||
explanation: 'A jam at the stop-gate moves the system to FAULT and stops the conveyor.',
|
||||
whatToWatch: 'FAULT state, red scene overlay, PID target 0 and Reset required.',
|
||||
juryValue: 'Shows fail-safe behavior and operator recovery path.',
|
||||
relatedCriteria: ['fault-handling', 'safety-emergency', 'pid-synchronization'],
|
||||
presenterPhrase: 'When the gate jams, the safest command is to stop motion and require reset.',
|
||||
},
|
||||
{
|
||||
id: 'emergency-stop',
|
||||
title: 'Emergency stop',
|
||||
scenarioId: 'emergency_stop',
|
||||
preferredAction: 'step',
|
||||
focusArea: 'safety',
|
||||
explanation: 'Emergency stop interrupts the line and brings motion toward zero.',
|
||||
whatToWatch: 'EMERGENCY_STOP state, stopped conveyor and PID speed falling to zero.',
|
||||
juryValue: 'Shows explicit safety handling for abnormal operation.',
|
||||
relatedCriteria: ['safety-emergency', 'fault-handling', 'engineering-realism'],
|
||||
presenterPhrase: 'Emergency stop is not another route; it is a safety state that freezes the line until reset.',
|
||||
},
|
||||
{
|
||||
id: 'performance-sync',
|
||||
title: 'Performance and synchronization',
|
||||
scenarioId: 'normal_flow',
|
||||
preferredAction: 'start',
|
||||
focusArea: 'pid',
|
||||
explanation: 'Timeline, latency cards and PID graph show how the simulated control loop stays synchronized.',
|
||||
whatToWatch: 'CycleTimeline, CV latency, actuator latency, throughput and PID speed curve.',
|
||||
juryValue: 'Shows that the demo explains timing and throughput, not only classification.',
|
||||
relatedCriteria: ['cycle-time-throughput', 'timing-synchronization', 'engineering-realism'],
|
||||
presenterPhrase: 'Here we close the loop: decisions are timestamped, actuators have latency and conveyor control is visible.',
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user