From 932c5216dca0316021d53c0672d5b769272a5489 Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Wed, 26 Aug 2026 10:15:56 +0300 Subject: [PATCH] feat(control-station): review native raw fisheye risk cases --- .../src/core/laboratory/advancedIndex.ts | 2 +- .../src/core/laboratory/m48tRiskQuality.ts | 300 +++++++++++++++++- .../laboratory/M48TRiskQualityResult.tsx | 85 ++++- .../laboratory/M48TRiskQualityVisual.tsx | 115 ++++++- .../laboratory/laboratoryArchiveProfiles.ts | 6 +- .../test/m48tRiskQuality.test.mjs | 172 ++++++++++ config/laboratory-value-review.json | 2 +- ...E_4_OBJECT_CENTRIC_RECORDED_REALTIME_CV.md | 30 ++ 8 files changed, 694 insertions(+), 18 deletions(-) create mode 100644 apps/control-station/test/m48tRiskQuality.test.mjs diff --git a/apps/control-station/src/core/laboratory/advancedIndex.ts b/apps/control-station/src/core/laboratory/advancedIndex.ts index cb814ab..1571268 100644 --- a/apps/control-station/src/core/laboratory/advancedIndex.ts +++ b/apps/control-station/src/core/laboratory/advancedIndex.ts @@ -136,7 +136,7 @@ const RESULT_PREFIX: Readonly> = { "m48-object-centric-quality": "m48-object-quality-(?:pack|result)", "m48-small-static-passage-regression": "m48-small-static-passage-regression", "m48s-fixed-class-detector": "m48s-fixed-class-detector-lab", - "m48t-risk-quality-temporal": "m48t-risk-quality-temporal-lab", + "m48t-risk-quality-temporal": "(?:m48t-risk-quality-temporal-lab|m48q-native-risk-quality-lab)", "m47-reference-graph-shadow": "m47-reference-graph-lab", "m4-replay-threat": "m4-threat-replay", "l3-pointpillars-visual-audit": "l3-pointpillars-visual-audit", diff --git a/apps/control-station/src/core/laboratory/m48tRiskQuality.ts b/apps/control-station/src/core/laboratory/m48tRiskQuality.ts index 75cfe5b..d7fa5ef 100644 --- a/apps/control-station/src/core/laboratory/m48tRiskQuality.ts +++ b/apps/control-station/src/core/laboratory/m48tRiskQuality.ts @@ -19,7 +19,8 @@ export interface M48TReviewCase { sha256: string; } -export interface M48TRiskQualityResult { +export interface M48TLegacyRiskQualityResult { + variant: "legacy-coco-quality"; resultId: string; createdAtUtc: string; source: { @@ -93,6 +94,90 @@ export interface M48TRiskQualityResult { limitations: readonly string[]; } +export type M48QRiskFamily = "person" | "animal" | "light-road-user" | "vehicle"; + +export interface M48QNativeProposal { + proposalId: string; + className: string; + riskFamily: M48QRiskFamily; + score: number; + boxXyxy: readonly [number, number, number, number]; +} + +export interface M48QNativeReviewCase { + caseId: string; + sequence: number; + frameId: string; + evidenceTimeNs: number; + imageUrl: string; + width: 800; + height: 600; + byteLength: number; + sha256: string; + selectionBuckets: readonly string[]; + nativeDetectionCount: number; + legacyDetectionCount: number; + matchedDetectionCount: number; + proposals: readonly M48QNativeProposal[]; +} + +export interface M48QNativeRiskQualityResult { + variant: "native-risk-review"; + resultId: string; + createdAtUtc: string; + source: { + sourceId: "RAVNOVES00"; + frameCount: 4489; + width: 800; + height: 600; + geometricResampling: false; + }; + candidate: { + providerId: string; + modelId: "rf_detr_large_native_kb4:1"; + preprocessId: "raw-kb4-uint8-fused-mask-rgb-pad8-imagenet-trt/v0"; + minimumScore: 0.25; + }; + execution: { + effectiveWorldStateFps: number; + worldStateCompletionP95Ms: number; + detectorTotalP95Ms: number; + gpuUtilizationP95Percent: number; + gpuMemoryMaximumMib: number; + additionalInferencePasses: 0; + }; + selection: { + caseCount: 24; + minimumSequenceSeparation: number; + bucketCoverage: Readonly>; + classCounts: Readonly>; + }; + runtime: { + deliveryRatio: number; + integratedGatePassed: true; + operatingTargetGatePassed: true; + }; + parity: { + precision: number; + recall: number; + meanIou: number; + }; + acceptance: { + reviewReady: true; + independentQualityEvaluated: false; + semanticCandidateAccepted: false; + }; + review: { + cases: readonly M48QNativeReviewCase[]; + }; + method: M48TLaboratoryMethod; + limitations: readonly string[]; +} + +export type M48TRiskQualityResult = + | M48TLegacyRiskQualityResult + | M48QNativeRiskQualityResult; + type LaboratoryFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise; export class M48TContractError extends Error {} @@ -173,9 +258,10 @@ function method(value: unknown): M48TLaboratoryMethod { }; } -function parseResult(value: unknown, expectedResultId: string): M48TRiskQualityResult { +function parseLegacyResult(value: unknown, expectedResultId: string): M48TLegacyRiskQualityResult { const raw = object(value, "M4.8T result"); exact(raw.schema_version, "missioncore.m48t-risk-quality-temporal-view/v1", "M4.8T schema"); + exact(raw.variant, "legacy-coco-quality", "M4.8T variant"); exact(raw.result_id, expectedResultId, "M4.8T identity"); exact(raw.status, "complete-quality-gate-failed-temporal-invariant-passed", "M4.8T status"); exact(raw.access, "read-only", "M4.8T access"); @@ -234,6 +320,7 @@ function parseResult(value: unknown, expectedResultId: string): M48TRiskQualityR for (const [key, count] of Object.entries(failures)) parsedFailures[key] = integer(count, `M4.8T failure ${key}`); const temporalConfiguration = object(configuration.temporal, "M4.8T temporal configuration"); return { + variant: "legacy-coco-quality", resultId: expectedResultId, createdAtUtc: text(raw.created_at_utc, "M4.8T created at"), source: { @@ -306,6 +393,207 @@ function parseResult(value: unknown, expectedResultId: string): M48TRiskQualityR }; } +function numberRecord(value: unknown, label: string): Readonly> { + const raw = object(value, label); + const parsed: Record = {}; + for (const [key, item] of Object.entries(raw)) parsed[key] = integer(item, `${label}.${key}`); + return parsed; +} + +function nativeRiskFamily(value: unknown): M48QRiskFamily { + const parsed = text(value, "M4.8Q risk family"); + if (!["person", "animal", "light-road-user", "vehicle"].includes(parsed)) { + throw new M48TContractError("M4.8Q risk family: неизвестное значение."); + } + return parsed as M48QRiskFamily; +} + +function nativeProposal(value: unknown): M48QNativeProposal { + const raw = object(value, "M4.8Q proposal"); + const coordinates = array(raw.box_xyxy, "M4.8Q proposal box").map((item) => + number(item, "M4.8Q proposal coordinate") + ); + if ( + coordinates.length !== 4 + || coordinates[0] >= coordinates[2] + || coordinates[1] >= coordinates[3] + || coordinates[2] > 800 + || coordinates[3] > 600 + ) { + throw new M48TContractError("M4.8Q proposal box: нарушена raw-raster геометрия."); + } + const score = number(raw.score, "M4.8Q proposal score"); + if (score < 0.25 || score > 1) throw new M48TContractError("M4.8Q proposal score: нарушен threshold."); + return { + proposalId: text(raw.proposal_id, "M4.8Q proposal id"), + className: text(raw.class_name, "M4.8Q proposal class"), + riskFamily: nativeRiskFamily(raw.risk_family), + score, + boxXyxy: coordinates as [number, number, number, number], + }; +} + +function parseNativeResult(value: unknown, expectedResultId: string): M48QNativeRiskQualityResult { + const raw = object(value, "M4.8Q result"); + exact(raw.schema_version, "missioncore.m48q-native-risk-quality-view/v1", "M4.8Q schema"); + exact(raw.variant, "native-risk-review", "M4.8Q variant"); + exact(raw.result_id, expectedResultId, "M4.8Q identity"); + exact(raw.status, "complete-review-ready-quality-not-adjudicated", "M4.8Q status"); + exact(raw.access, "read-only", "M4.8Q access"); + exact(raw.ground_truth, false, "M4.8Q ground truth"); + + const source = object(raw.source, "M4.8Q source"); + exact(source.source_id, "RAVNOVES00", "M4.8Q source id"); + exact(source.frame_count, 4489, "M4.8Q source frames"); + exact(source.raster_width, 800, "M4.8Q source width"); + exact(source.raster_height, 600, "M4.8Q source height"); + exact(source.geometric_resampling, false, "M4.8Q source resampling"); + exact(source.rectification, false, "M4.8Q source rectification"); + exact(source.warp, false, "M4.8Q source warp"); + + const configuration = object(raw.configuration, "M4.8Q configuration"); + const candidate = object(configuration.candidate, "M4.8Q candidate"); + const execution = object(raw.execution, "M4.8Q execution"); + const metrics = object(raw.metrics, "M4.8Q metrics"); + const selection = object(metrics.selection, "M4.8Q selection"); + const runtime = object(metrics.runtime, "M4.8Q runtime"); + const parity = object(metrics.native_tensor_parity, "M4.8Q parity"); + const acceptance = object(raw.acceptance, "M4.8Q acceptance"); + exact(acceptance.review_ready, true, "M4.8Q review readiness"); + exact(acceptance.integrated_runtime_gate_passed, true, "M4.8Q runtime gate"); + exact(acceptance.independent_quality_evaluated, false, "M4.8Q quality state"); + exact(acceptance.semantic_candidate_accepted, false, "M4.8Q candidate state"); + exact(selection.case_count, 24, "M4.8Q selected cases"); + + const review = object(raw.review, "M4.8Q review"); + const raster = object(review.source_raster, "M4.8Q review raster"); + exact(raster.width, 800, "M4.8Q review width"); + exact(raster.height, 600, "M4.8Q review height"); + const overlay = object(review.overlay, "M4.8Q overlay"); + exact(overlay.client_rendered, true, "M4.8Q client overlay"); + exact(overlay.toggleable, true, "M4.8Q overlay toggle"); + const cases = array(review.cases, "M4.8Q review cases").map((value) => { + const item = object(value, "M4.8Q review case"); + const caseId = text(item.case_id, "M4.8Q case id"); + if (!/^[0-9]{6}$/.test(caseId)) throw new M48TContractError("M4.8Q case identity нарушена."); + const sequence = integer(item.sequence, "M4.8Q case sequence"); + exact(caseId, sequence.toString().padStart(6, "0"), "M4.8Q case/sequence identity"); + exact(item.frame_id, `frame-${caseId}`, "M4.8Q frame identity"); + exact(item.media_type, "image/jpeg", "M4.8Q case media"); + exact(item.width, 800, "M4.8Q case width"); + exact(item.height, 600, "M4.8Q case height"); + exact(item.geometric_resampling, false, "M4.8Q case resampling"); + const comparison = object(item.comparison, "M4.8Q comparison"); + const proposals = array(item.proposals, "M4.8Q proposals").map(nativeProposal); + if (!proposals.length) throw new M48TContractError("M4.8Q proposals: пустой review case."); + return { + caseId, + sequence, + frameId: `frame-${caseId}`, + evidenceTimeNs: integer(item.evidence_time_ns, "M4.8Q evidence time"), + imageUrl: text(item.image_url, "M4.8Q image URL"), + width: 800 as const, + height: 600 as const, + byteLength: integer(item.byte_length, "M4.8Q image bytes"), + sha256: sha(item.sha256, "M4.8Q image SHA"), + selectionBuckets: array(item.selection_buckets, "M4.8Q selection buckets").map((bucket) => + text(bucket, "M4.8Q selection bucket") + ), + nativeDetectionCount: integer(comparison.native_detection_count, "M4.8Q native count"), + legacyDetectionCount: integer(comparison.legacy_704_detection_count, "M4.8Q legacy count"), + matchedDetectionCount: integer( + comparison.matched_detection_count_iou_at_least_0_5, + "M4.8Q matched count", + ), + proposals, + }; + }); + if (cases.length !== 24 || new Set(cases.map(({ caseId }) => caseId)).size !== 24) { + throw new M48TContractError("M4.8Q review catalog: нарушен размер."); + } + + return { + variant: "native-risk-review", + resultId: expectedResultId, + createdAtUtc: text(raw.created_at_utc, "M4.8Q created at"), + source: { + sourceId: "RAVNOVES00", + frameCount: 4489, + width: 800, + height: 600, + geometricResampling: false, + }, + candidate: { + providerId: text(candidate.provider_id, "M4.8Q provider"), + modelId: exact(candidate.model_id, "rf_detr_large_native_kb4:1", "M4.8Q model"), + preprocessId: exact( + candidate.preprocess_id, + "raw-kb4-uint8-fused-mask-rgb-pad8-imagenet-trt/v0", + "M4.8Q preprocess", + ), + minimumScore: exact(candidate.minimum_score, 0.25, "M4.8Q threshold"), + }, + execution: { + effectiveWorldStateFps: number(execution.effective_world_state_fps, "M4.8Q FPS"), + worldStateCompletionP95Ms: number( + execution.world_state_completion_p95_ms, + "M4.8Q world-state p95", + ), + detectorTotalP95Ms: number(execution.detector_total_p95_ms, "M4.8Q detector p95"), + gpuUtilizationP95Percent: number( + execution.gpu_utilization_p95_percent, + "M4.8Q GPU p95", + ), + gpuMemoryMaximumMib: number( + execution.gpu_memory_used_maximum_mib, + "M4.8Q VRAM maximum", + ), + additionalInferencePasses: exact( + execution.additional_inference_passes, + 0, + "M4.8Q inference passes", + ), + }, + selection: { + caseCount: 24, + minimumSequenceSeparation: integer( + selection.minimum_sequence_separation, + "M4.8Q case separation", + ), + bucketCoverage: numberRecord(selection.selected_bucket_coverage, "M4.8Q bucket coverage"), + classCounts: numberRecord(selection.selected_class_counts, "M4.8Q class counts"), + }, + runtime: { + deliveryRatio: number(runtime.delivery_ratio, "M4.8Q delivery ratio"), + integratedGatePassed: exact( + runtime.integrated_runtime_gate_passed, + true, + "M4.8Q integrated gate", + ), + operatingTargetGatePassed: exact( + runtime.operating_target_gate_passed, + true, + "M4.8Q target gate", + ), + }, + parity: { + precision: number(parity.risk_detection_precision, "M4.8Q parity precision"), + recall: number(parity.risk_detection_recall, "M4.8Q parity recall"), + meanIou: number(parity.matched_mean_iou, "M4.8Q parity IoU"), + }, + acceptance: { + reviewReady: true, + independentQualityEvaluated: false, + semanticCandidateAccepted: false, + }, + review: { cases }, + method: method(raw.method), + limitations: array(raw.limitations, "M4.8Q limitations").map((item) => + text(item, "M4.8Q limitation") + ), + }; +} + export async function fetchM48TRiskQualityResult( resultId: string, { @@ -313,7 +601,7 @@ export async function fetchM48TRiskQualityResult( signal, }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {}, ): Promise { - if (!/^m48t-risk-quality-temporal-lab-[a-f0-9]{64}$/.test(resultId)) { + if (!/^(?:m48t-risk-quality-temporal-lab|m48q-native-risk-quality-lab)-[a-f0-9]{64}$/.test(resultId)) { throw new M48TContractError("M4.8T result identity недопустима."); } const response = await fetcher(`/api/v1/laboratory/m48t/risk-quality/results/${resultId}`, { @@ -322,5 +610,9 @@ export async function fetchM48TRiskQualityResult( signal, }); if (!response.ok) throw new M48TContractError(`M4.8T недоступен: HTTP ${response.status}.`); - return parseResult(await response.json(), resultId); + const payload: unknown = await response.json(); + const raw = object(payload, "M4.8T/M4.8Q result"); + return raw.schema_version === "missioncore.m48q-native-risk-quality-view/v1" + ? parseNativeResult(payload, resultId) + : parseLegacyResult(payload, resultId); } diff --git a/apps/control-station/src/workspaces/laboratory/M48TRiskQualityResult.tsx b/apps/control-station/src/workspaces/laboratory/M48TRiskQualityResult.tsx index 60b2c28..2a52844 100644 --- a/apps/control-station/src/workspaces/laboratory/M48TRiskQualityResult.tsx +++ b/apps/control-station/src/workspaces/laboratory/M48TRiskQualityResult.tsx @@ -4,7 +4,11 @@ import { LaboratorySummary, LaboratoryWorkTemplate, } from "../../components/laboratory/LaboratoryPresentation"; -import type { M48TRiskQualityResult } from "../../core/laboratory/m48tRiskQuality"; +import type { + M48QNativeRiskQualityResult, + M48TLegacyRiskQualityResult, + M48TRiskQualityResult, +} from "../../core/laboratory/m48tRiskQuality"; import { M48TRiskQualityVisual } from "./M48TRiskQualityVisual"; function percent(value: number, digits = 1): string { @@ -21,6 +25,19 @@ export function M48TRiskQualityResultView({ }: { rigLabel: string; result: M48TRiskQualityResult; +}) { + if (result.variant === "native-risk-review") { + return ; + } + return ; +} + +function M48TLegacyRiskQualityResultView({ + rigLabel, + result, +}: { + rigLabel: string; + result: M48TLegacyRiskQualityResult; }) { return ( ); } + +function M48QNativeRiskQualityResultView({ + rigLabel, + result, +}: { + rigLabel: string; + result: M48QNativeRiskQualityResult; +}) { + const classes = Object.entries(result.selection.classCounts) + .map(([name, count]) => `${name} ${count}`) + .join(" · "); + return ( + + )} + evidence={( + + + + )} + result={( + + )} + /> + ); +} diff --git a/apps/control-station/src/workspaces/laboratory/M48TRiskQualityVisual.tsx b/apps/control-station/src/workspaces/laboratory/M48TRiskQualityVisual.tsx index 35bb8b2..c1eff0b 100644 --- a/apps/control-station/src/workspaces/laboratory/M48TRiskQualityVisual.tsx +++ b/apps/control-station/src/workspaces/laboratory/M48TRiskQualityVisual.tsx @@ -1,18 +1,35 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { Icon, IconButton } from "@nodedc/ui-react"; import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer"; -import type { M48TRiskQualityResult } from "../../core/laboratory/m48tRiskQuality"; +import { RecordedEvidenceImageScene } from "../../components/laboratory/RecordedEvidenceImageScene"; +import type { + RecordedEvidenceBox, + RecordedEvidenceBoxTone, +} from "../../components/laboratory/RecordedEvidenceBoxOverlay"; +import type { + M48QNativeRiskQualityResult, + M48QRiskFamily, + M48TLegacyRiskQualityResult, + M48TRiskQualityResult, +} from "../../core/laboratory/m48tRiskQuality"; -export function M48TRiskQualityVisual({ result }: { result: M48TRiskQualityResult }) { +function navigateIndex(current: number, offset: -1 | 1, length: number): number { + return (current + offset + length) % length; +} + +function familyTone(family: M48QRiskFamily): RecordedEvidenceBoxTone { + if (family === "person" || family === "animal") return "danger"; + if (family === "light-road-user") return "warning"; + return "accent"; +} + +function LegacyVisual({ result }: { result: M48TLegacyRiskQualityResult }) { const [index, setIndex] = useState(0); const [expanded, setExpanded] = useState(false); const item = result.review.cases[index] ?? null; - const navigate = (offset: -1 | 1) => { - setIndex((current) => ( - current + offset + result.review.cases.length - ) % result.review.cases.length); - }; + const navigate = (offset: -1 | 1) => + setIndex((current) => navigateIndex(current, offset, result.review.cases.length)); return (
@@ -61,3 +78,85 @@ export function M48TRiskQualityVisual({ result }: { result: M48TRiskQualityResul
); } + +function NativeVisual({ result }: { result: M48QNativeRiskQualityResult }) { + const [index, setIndex] = useState(0); + const [expanded, setExpanded] = useState(false); + const [boxesVisible, setBoxesVisible] = useState(true); + const item = result.review.cases[index] ?? null; + const boxes = useMemo(() => { + if (!item || !boxesVisible) return []; + return item.proposals.map((proposal) => ({ + boxXyxy: proposal.boxXyxy, + label: `${proposal.className} · ${proposal.score.toFixed(2)}`, + tone: familyTone(proposal.riskFamily), + })); + }, [boxesVisible, item]); + const navigate = (offset: -1 | 1) => + setIndex((current) => navigateIndex(current, offset, result.review.cases.length)); + + return ( +
+ undefined} + onExpandedChange={setExpanded} + actions={( +
+ navigate(-1)}> + + + navigate(1)}> + + +
+ )} + trailingActions={( + setBoxesVisible((visible) => !visible)} + > + + + )} + overlay={item ? ( +
+
+ RAVNOVES00 · raw KB4 800×600 · resampling NO + + case {index + 1}/{result.review.cases.length} · frame {item.sequence} · {item.proposals.length} native boxes + + + {item.selectionBuckets.join(" · ")} · native/legacy {item.nativeDetectionCount}/{item.legacyDetectionCount} (diagnostic only) + +
+
+ ) : null} + > + {item ? ( + + ) : ( +
+ + M4.8Q native visual evidence недоступно. +
+ )} +
+
+ ); +} + +export function M48TRiskQualityVisual({ result }: { result: M48TRiskQualityResult }) { + return result.variant === "native-risk-review" + ? + : ; +} diff --git a/apps/control-station/src/workspaces/laboratory/laboratoryArchiveProfiles.ts b/apps/control-station/src/workspaces/laboratory/laboratoryArchiveProfiles.ts index 67803c3..8e32baf 100644 --- a/apps/control-station/src/workspaces/laboratory/laboratoryArchiveProfiles.ts +++ b/apps/control-station/src/workspaces/laboratory/laboratoryArchiveProfiles.ts @@ -86,10 +86,10 @@ const KNOWN_WORKS: Readonly `${rig(rigLabel)} RIGHT · COCO quality + bounded temporal identity`, + profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Native raw-fisheye perception gate`, experimentId: "m48t-risk-quality-temporal", - experimentName: "RF-DETR independent semantic quality and temporal identity", - variantName: "M4.8T · COCO val2017 truth + RAVNOVES00 temporal shadow", + experimentName: "RF-DETR native risk review and temporal identity", + variantName: "M4.8Q · native raw KB4 review · quality not adjudicated", }, "m47-reference-graph-shadow": { profileId: "rig-dual-evidence-virtual-corridor-v1", diff --git a/apps/control-station/test/m48tRiskQuality.test.mjs b/apps/control-station/test/m48tRiskQuality.test.mjs new file mode 100644 index 0000000..44ca484 --- /dev/null +++ b/apps/control-station/test/m48tRiskQuality.test.mjs @@ -0,0 +1,172 @@ +import assert from "node:assert/strict"; +import { after, before, test } from "node:test"; +import { readFile } from "node:fs/promises"; +import { createServer } from "vite"; + +let server; +let fetchM48TRiskQualityResult; + +const resultId = `m48q-native-risk-quality-lab-${"a".repeat(64)}`; + +before(async () => { + server = await createServer({ + appType: "custom", + logLevel: "silent", + server: { middlewareMode: true }, + }); + ({ fetchM48TRiskQualityResult } = await server.ssrLoadModule( + "/src/core/laboratory/m48tRiskQuality.ts", + )); +}); + +after(async () => server?.close()); + +function response(value) { + return { ok: true, status: 200, json: async () => value }; +} + +function payload() { + return { + schema_version: "missioncore.m48q-native-risk-quality-view/v1", + variant: "native-risk-review", + result_id: resultId, + created_at_utc: "2026-08-26T10:00:00Z", + status: "complete-review-ready-quality-not-adjudicated", + access: "read-only", + ground_truth: false, + source: { + source_id: "RAVNOVES00", + frame_count: 4489, + raster_width: 800, + raster_height: 600, + geometric_resampling: false, + rectification: false, + warp: false, + }, + configuration: { + candidate: { + provider_id: "triton-rf-detr-large-coco-native-kb4-risk-fp16-shadow/v0", + model_id: "rf_detr_large_native_kb4:1", + preprocess_id: "raw-kb4-uint8-fused-mask-rgb-pad8-imagenet-trt/v0", + minimum_score: 0.25, + }, + }, + method: { + schema_version: "missioncore.laboratory-method/v1", + completeness: "complete", + execution_class: "hybrid", + pipeline_id: "m48q-native-raw-fisheye-risk-case-review/v1", + components: [{ + kind: "model", + name: "RF-DETR-L native KB4 TensorRT", + version: "rf_detr_large_native_kb4:1", + role: "risk proposals", + identity_sha256: "b".repeat(64), + }], + }, + execution: { + effective_world_state_fps: 11.84338, + world_state_completion_p95_ms: 47.940779, + detector_total_p95_ms: 21.19895, + gpu_utilization_p95_percent: 53, + gpu_memory_used_maximum_mib: 9718, + additional_inference_passes: 0, + }, + metrics: { + selection: { + case_count: 24, + minimum_sequence_separation: 12, + selected_bucket_coverage: { person: 22, animal: 3 }, + selected_class_counts: { person: 44, dog: 3 }, + }, + runtime: { + delivery_ratio: 1, + integrated_runtime_gate_passed: true, + operating_target_gate_passed: true, + }, + native_tensor_parity: { + risk_detection_precision: 0.9887, + risk_detection_recall: 0.9831, + matched_mean_iou: 0.9882, + }, + }, + acceptance: { + review_ready: true, + integrated_runtime_gate_passed: true, + independent_quality_evaluated: false, + semantic_candidate_accepted: false, + }, + review: { + source_raster: { width: 800, height: 600 }, + overlay: { client_rendered: true, toggleable: true }, + cases: Array.from({ length: 24 }, (_, index) => { + const caseId = String(index * 20 + 12).padStart(6, "0"); + return { + case_id: caseId, + sequence: Number(caseId), + frame_id: `frame-${caseId}`, + evidence_time_ns: 35_000_000_000 + index * 1_000_000, + image_url: `/review/${caseId}.jpg`, + media_type: "image/jpeg", + width: 800, + height: 600, + byte_length: 1000 + index, + sha256: String(index.toString(16)).padStart(64, "0"), + geometric_resampling: false, + selection_buckets: [index % 2 ? "person" : "animal"], + comparison: { + native_detection_count: 1, + legacy_704_detection_count: 1, + matched_detection_count_iou_at_least_0_5: 1, + }, + proposals: [{ + proposal_id: `proposal-${index}-0`, + class_name: index % 2 ? "person" : "dog", + risk_family: index % 2 ? "person" : "animal", + score: 0.75, + box_xyxy: [10, 20, 100, 200], + }], + }; + }), + }, + limitations: ["No independent route truth."], + }; +} + +test("M4.8Q parser accepts only the native 800x600 no-resampling review contract", async () => { + const result = await fetchM48TRiskQualityResult(resultId, { + fetcher: async () => response(payload()), + }); + + assert.equal(result.variant, "native-risk-review"); + assert.equal(result.review.cases.length, 24); + assert.equal(result.review.cases[0].width, 800); + assert.equal(result.review.cases[0].proposals[0].className, "dog"); + assert.equal(result.execution.additionalInferencePasses, 0); + assert.equal(result.acceptance.independentQualityEvaluated, false); +}); + +test("M4.8Q parser fails closed if raw-fisheye geometry is resampled", async () => { + const invalid = payload(); + invalid.source.geometric_resampling = true; + + await assert.rejects( + fetchM48TRiskQualityResult(resultId, { + fetcher: async () => response(invalid), + }), + /source resampling/, + ); +}); + +test("M4.8Q reuses the canonical image scene and viewer overlay toggle", async () => { + const source = await readFile( + new URL("../src/workspaces/laboratory/M48TRiskQualityVisual.tsx", import.meta.url), + "utf8", + ); + + assert.match(source, /