From 783aa444acf23fdb15a92e531cbd3ce94d56f88b Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Wed, 29 Jul 2026 01:52:56 +0300 Subject: [PATCH] feat(lab): publish E40 perception product gate --- .../laboratory/LaboratoryPresentation.tsx | 28 +- .../src/core/laboratory/advancedResults.ts | 57 ++- .../src/core/laboratory/catalogTransport.ts | 17 + .../src/core/laboratory/e40ProductGate.ts | 458 ++++++++++++++++++ .../laboratory/AdvancedLaboratoryResult.tsx | 14 +- .../src/workspaces/laboratory/E40Result.tsx | 180 +++++++ .../laboratory/LaboratoryArchiveWorkspace.tsx | 3 +- .../test/advancedLaboratoryResults.test.mjs | 146 +++++- .../test/applicationArchitecture.test.mjs | 2 + .../test/laboratoryProductUi.test.mjs | 26 + 10 files changed, 898 insertions(+), 33 deletions(-) create mode 100644 apps/control-station/src/core/laboratory/catalogTransport.ts create mode 100644 apps/control-station/src/core/laboratory/e40ProductGate.ts create mode 100644 apps/control-station/src/workspaces/laboratory/E40Result.tsx diff --git a/apps/control-station/src/components/laboratory/LaboratoryPresentation.tsx b/apps/control-station/src/components/laboratory/LaboratoryPresentation.tsx index 7a138e8..c785188 100644 --- a/apps/control-station/src/components/laboratory/LaboratoryPresentation.tsx +++ b/apps/control-station/src/components/laboratory/LaboratoryPresentation.tsx @@ -263,6 +263,24 @@ export function LaboratoryConclusion({ ); } +export function LaboratoryMetricGrid({ + metrics, +}: { + metrics: readonly LaboratoryResultMetric[]; +}) { + return ( +
+ {metrics.map((metric) => ( +
+ {metric.label} + {metric.value} + {metric.hint} +
+ ))} +
+ ); +} + export function LaboratoryResultSummary({ title, status, @@ -285,15 +303,7 @@ export function LaboratoryResultSummary({ {status} -
- {metrics.map((metric) => ( -
- {metric.label} - {metric.value} - {metric.hint} -
- ))} -
+ ); diff --git a/apps/control-station/src/core/laboratory/advancedResults.ts b/apps/control-station/src/core/laboratory/advancedResults.ts index 1384c1a..23c06bc 100644 --- a/apps/control-station/src/core/laboratory/advancedResults.ts +++ b/apps/control-station/src/core/laboratory/advancedResults.ts @@ -1,3 +1,17 @@ +import { + fetchE34TemporalLayerResult, + type E34TemporalLayerResult, +} from "./e34TemporalLayer"; +import { + fetchE35DegradationRecoveryResult, + type E35DegradationRecoveryResult, +} from "./e35DegradationRecovery"; +import { + fetchE40ProductGateResult, + type E40PerceptionProductGateResult, +} from "./e40ProductGate"; +import { settledCatalogValue } from "./catalogTransport"; + export interface E31LaboratoryResult { resultId: string; createdAtUtc: string | null; @@ -179,7 +193,7 @@ export interface E38PerceptionBaselineResult { access: "read-only"; } -export interface E39DevelopmentDimensionMetric { +export interface DevelopmentDimensionMetric { correct: number; incorrect: number; total: number; @@ -188,6 +202,8 @@ export interface E39DevelopmentDimensionMetric { passed: boolean; } +export type E39DevelopmentDimensionMetric = DevelopmentDimensionMetric; + export interface E39PerceptionRefinementResult { resultId: string; createdAtUtc: string | null; @@ -205,9 +221,9 @@ export interface E39PerceptionRefinementResult { validationLabelsUsed: false; passed: boolean; dimensions: { - presence: E39DevelopmentDimensionMetric; - geometryAssociation: E39DevelopmentDimensionMetric; - freshness: E39DevelopmentDimensionMetric; + presence: DevelopmentDimensionMetric; + geometryAssociation: DevelopmentDimensionMetric; + freshness: DevelopmentDimensionMetric; }; }; metrics: E38PerceptionBaselineResult["metrics"]; @@ -230,6 +246,7 @@ export interface AdvancedLaboratoryResults { e37: E37AcceptanceContractResult | null; e38: E38PerceptionBaselineResult | null; e39: E39PerceptionRefinementResult | null; + e40: E40PerceptionProductGateResult | null; } export class AdvancedLaboratoryContractError extends Error { @@ -778,10 +795,10 @@ function parseE38(value: unknown): E38PerceptionBaselineResult { }; } -function parseE39DevelopmentDimension( +function parseDevelopmentDimension( value: unknown, label: string, -): E39DevelopmentDimensionMetric { +): DevelopmentDimensionMetric { const source = record(value, label); return { correct: integerValue(source.correct, `${label}.correct`), @@ -859,15 +876,15 @@ function parseE39(value: unknown): E39PerceptionRefinementResult { "E39.development_cross_validation.passed", ), dimensions: { - presence: parseE39DevelopmentDimension( + presence: parseDevelopmentDimension( developmentDimensions.presence, "E39.development_cross_validation.dimensions.presence", ), - geometryAssociation: parseE39DevelopmentDimension( + geometryAssociation: parseDevelopmentDimension( developmentDimensions.geometry_association, "E39.development_cross_validation.dimensions.geometry_association", ), - freshness: parseE39DevelopmentDimension( + freshness: parseDevelopmentDimension( developmentDimensions.freshness, "E39.development_cross_validation.dimensions.freshness", ), @@ -951,7 +968,7 @@ export async function fetchAdvancedLaboratoryResults({ fetcher?: LaboratoryFetch; signal?: AbortSignal; } = {}): Promise { - const [e31, e32, e33, e34, e35, e37, e38, e39] = await Promise.all([ + const settled = await Promise.allSettled([ fetchOne("/api/v1/laboratory/e31/results?limit=1", parseE31, fetcher, signal), fetchOne("/api/v1/laboratory/e32/results?limit=1", parseE32, fetcher, signal), fetchOne("/api/v1/laboratory/e33/results?limit=1", parseE33, fetcher, signal), @@ -960,14 +977,16 @@ export async function fetchAdvancedLaboratoryResults({ fetchOne("/api/v1/laboratory/e37/results?limit=1", parseE37, fetcher, signal), fetchOne("/api/v1/laboratory/e38/results?limit=1", parseE38, fetcher, signal), fetchOne("/api/v1/laboratory/e39/results?limit=1", parseE39, fetcher, signal), + fetchE40ProductGateResult({ fetcher, signal }), ]); - return { e31, e32, e33, e34, e35, e37, e38, e39 }; + const e31 = settledCatalogValue(settled[0]); + const e32 = settledCatalogValue(settled[1]); + const e33 = settledCatalogValue(settled[2]); + const e34 = settledCatalogValue(settled[3]); + const e35 = settledCatalogValue(settled[4]); + const e37 = settledCatalogValue(settled[5]); + const e38 = settledCatalogValue(settled[6]); + const e39 = settledCatalogValue(settled[7]); + const e40 = settledCatalogValue(settled[8]); + return { e31, e32, e33, e34, e35, e37, e38, e39, e40 }; } -import { - fetchE34TemporalLayerResult, - type E34TemporalLayerResult, -} from "./e34TemporalLayer"; -import { - fetchE35DegradationRecoveryResult, - type E35DegradationRecoveryResult, -} from "./e35DegradationRecovery"; diff --git a/apps/control-station/src/core/laboratory/catalogTransport.ts b/apps/control-station/src/core/laboratory/catalogTransport.ts new file mode 100644 index 0000000..332e198 --- /dev/null +++ b/apps/control-station/src/core/laboratory/catalogTransport.ts @@ -0,0 +1,17 @@ +export function settledCatalogValue( + result: PromiseSettledResult, +): T | null { + if (result.status === "fulfilled") { + return result.value; + } + const reason = result.reason; + const isAbort = reason instanceof Error && reason.name === "AbortError"; + const isHttpTransport = ( + reason instanceof Error + && /Каталог LAB.*недоступен: HTTP \d+\./.test(reason.message) + ); + if (!isAbort && (reason instanceof TypeError || isHttpTransport)) { + return null; + } + throw reason; +} diff --git a/apps/control-station/src/core/laboratory/e40ProductGate.ts b/apps/control-station/src/core/laboratory/e40ProductGate.ts new file mode 100644 index 0000000..b3b4208 --- /dev/null +++ b/apps/control-station/src/core/laboratory/e40ProductGate.ts @@ -0,0 +1,458 @@ +export interface E40DevelopmentDimensionMetric { + correct: number; + incorrect: number; + total: number; + accuracy: number; + target: number; + passed: boolean; +} + +export interface E40DimensionMetric extends E40DevelopmentDimensionMetric { + byStratum: Readonly>; + confusion: readonly { + reference: string; + prediction: string; + count: number; + }[]; +} + +export interface E40DevelopmentProtocol { + items: number; + foldSizes: Readonly>; + passed: boolean; + dimensions: { + presence: E40DevelopmentDimensionMetric; + geometryAssociation: E40DevelopmentDimensionMetric; + freshness: E40DevelopmentDimensionMetric; + }; +} + +export interface E40PerceptionProductGateResult { + resultId: string; + createdAtUtc: string | null; + sourceSessionId: string; + sourceDisplayName: string; + status: "measured-leakage-resistant-product-gate"; + profileId: string; + workerNode: string; + qualityGatePassed: boolean; + developmentCrossValidation: { + strategy: string; + seed: string; + folds: number; + items: number; + validationLabelsUsed: false; + passed: boolean; + protocols: { + contiguousSourceTime: E40DevelopmentProtocol; + wholeTrackOrSceneWindow: E40DevelopmentProtocol; + }; + }; + metrics: { + developmentItems: number; + validationItems: number; + terminalOutcomes: number; + accountingFraction: number; + falseFreeClaims: number; + highSeverityFailures: number; + dimensions: { + presence: E40DimensionMetric; + geometryAssociation: E40DimensionMetric; + freshness: E40DimensionMetric; + }; + }; + blockingChecks: readonly string[]; + method: { + summary: string; + selection: string; + dimensionProjection: string; + }; + limitations: readonly string[]; + access: "read-only"; +} + +export class E40ProductGateContractError extends Error { + constructor(message: string) { + super(message); + this.name = "E40ProductGateContractError"; + } +} + +type LaboratoryFetch = ( + input: RequestInfo | URL, + init?: RequestInit, +) => Promise; + +function record(value: unknown, label: string): Record { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new E40ProductGateContractError(`${label}: ожидался объект.`); + } + return value as Record; +} + +function stringValue(value: unknown, label: string): string { + if (typeof value !== "string" || !value.trim()) { + throw new E40ProductGateContractError(`${label}: ожидалась строка.`); + } + return value; +} + +function optionalString(value: unknown, label: string): string | null { + return value === null ? null : stringValue(value, label); +} + +function numberValue(value: unknown, label: string): number { + if (typeof value !== "number" || !Number.isFinite(value) || value < 0) { + throw new E40ProductGateContractError(`${label}: ожидалось число.`); + } + return value; +} + +function integerValue(value: unknown, label: string): number { + const parsed = numberValue(value, label); + if (!Number.isSafeInteger(parsed)) { + throw new E40ProductGateContractError(`${label}: ожидалось целое число.`); + } + return parsed; +} + +function booleanValue(value: unknown, label: string): boolean { + if (typeof value !== "boolean") { + throw new E40ProductGateContractError(`${label}: ожидался boolean.`); + } + return value; +} + +function falseValue(value: unknown, label: string): false { + if (value !== false) { + throw new E40ProductGateContractError(`${label}: ожидалось false.`); + } + return false; +} + +function exactString( + value: unknown, + expected: T, + label: string, +): T { + if (value !== expected) { + throw new E40ProductGateContractError(`${label}: неверное значение.`); + } + return expected; +} + +function strings(value: unknown, label: string): readonly string[] { + if (!Array.isArray(value)) { + throw new E40ProductGateContractError(`${label}: ожидался массив.`); + } + return value.map((item, index) => stringValue(item, `${label}[${index}]`)); +} + +function numberRecord( + value: unknown, + label: string, +): Readonly> { + const source = record(value, label); + return Object.fromEntries( + Object.entries(source).map(([key, item]) => [ + key, + integerValue(item, `${label}.${key}`), + ]), + ); +} + +function contentId(value: unknown, prefix: string, label: string): string { + const parsed = stringValue(value, label); + if (!new RegExp(`^${prefix}-[a-f0-9]{64}$`).test(parsed)) { + throw new E40ProductGateContractError(`${label}: неверный content id.`); + } + return parsed; +} + +function diagnosticAuthority(value: unknown, label: string): void { + const authority = record(value, label); + if ( + authority.commands_enabled !== false + || authority.navigation_or_safety_accepted !== false + ) { + throw new E40ProductGateContractError(`${label}: запрещённые полномочия.`); + } +} + +function parseDevelopmentDimension( + value: unknown, + label: string, +): E40DevelopmentDimensionMetric { + const dimension = record(value, label); + return { + correct: integerValue(dimension.correct, `${label}.correct`), + incorrect: integerValue(dimension.incorrect, `${label}.incorrect`), + total: integerValue(dimension.total, `${label}.total`), + accuracy: numberValue(dimension.accuracy, `${label}.accuracy`), + target: numberValue(dimension.target, `${label}.target`), + passed: booleanValue(dimension.passed, `${label}.passed`), + }; +} + +function parseDimension(value: unknown, label: string): E40DimensionMetric { + const dimension = record(value, label); + const byStratum = record(dimension.by_stratum, `${label}.by_stratum`); + const confusion = dimension.confusion; + if (!Array.isArray(confusion)) { + throw new E40ProductGateContractError( + `${label}.confusion: ожидался массив.`, + ); + } + return { + ...parseDevelopmentDimension(value, label), + byStratum: Object.fromEntries( + Object.entries(byStratum).map(([stratum, raw]) => { + const item = record(raw, `${label}.by_stratum.${stratum}`); + return [stratum, { + correct: integerValue( + item.correct, + `${label}.by_stratum.${stratum}.correct`, + ), + incorrect: integerValue( + item.incorrect, + `${label}.by_stratum.${stratum}.incorrect`, + ), + total: integerValue( + item.total, + `${label}.by_stratum.${stratum}.total`, + ), + accuracy: numberValue( + item.accuracy, + `${label}.by_stratum.${stratum}.accuracy`, + ), + }]; + }), + ), + confusion: confusion.map((raw, index) => { + const item = record(raw, `${label}.confusion[${index}]`); + return { + reference: stringValue( + item.reference, + `${label}.confusion[${index}].reference`, + ), + prediction: stringValue( + item.prediction, + `${label}.confusion[${index}].prediction`, + ), + count: integerValue( + item.count, + `${label}.confusion[${index}].count`, + ), + }; + }), + }; +} + +function parseDevelopmentProtocol( + value: unknown, + label: string, +): E40DevelopmentProtocol { + const protocol = record(value, label); + const dimensions = record(protocol.dimensions, `${label}.dimensions`); + return { + items: integerValue(protocol.items, `${label}.items`), + foldSizes: numberRecord(protocol.fold_sizes, `${label}.fold_sizes`), + passed: booleanValue(protocol.passed, `${label}.passed`), + dimensions: { + presence: parseDevelopmentDimension( + dimensions.presence, + `${label}.dimensions.presence`, + ), + geometryAssociation: parseDevelopmentDimension( + dimensions.geometry_association, + `${label}.dimensions.geometry_association`, + ), + freshness: parseDevelopmentDimension( + dimensions.freshness, + `${label}.dimensions.freshness`, + ), + }, + }; +} + +function parseResult(value: unknown): E40PerceptionProductGateResult { + const item = record(value, "E40"); + const metrics = record(item.metrics, "E40.metrics"); + const dimensions = record(metrics.dimensions, "E40.metrics.dimensions"); + const development = record( + item.development_cross_validation, + "E40.development_cross_validation", + ); + const protocols = record( + development.protocols, + "E40.development_cross_validation.protocols", + ); + const method = record(item.method, "E40.method"); + diagnosticAuthority(item.authority, "E40.authority"); + return { + resultId: contentId( + item.result_id, + "e40-perception-product-gate", + "E40.result_id", + ), + createdAtUtc: optionalString(item.created_at_utc, "E40.created_at_utc"), + sourceSessionId: stringValue( + item.source_session_id, + "E40.source_session_id", + ), + sourceDisplayName: stringValue( + item.source_display_name, + "E40.source_display_name", + ), + status: exactString( + item.status, + "measured-leakage-resistant-product-gate", + "E40.status", + ), + profileId: stringValue(item.profile_id, "E40.profile_id"), + workerNode: stringValue(item.worker_node, "E40.worker_node"), + qualityGatePassed: booleanValue( + item.quality_gate_passed, + "E40.quality_gate_passed", + ), + developmentCrossValidation: { + strategy: stringValue( + development.strategy, + "E40.development_cross_validation.strategy", + ), + seed: stringValue( + development.seed, + "E40.development_cross_validation.seed", + ), + folds: integerValue( + development.folds, + "E40.development_cross_validation.folds", + ), + items: integerValue( + development.items, + "E40.development_cross_validation.items", + ), + validationLabelsUsed: falseValue( + development.validation_labels_used, + "E40.development_cross_validation.validation_labels_used", + ), + passed: booleanValue( + development.passed, + "E40.development_cross_validation.passed", + ), + protocols: { + contiguousSourceTime: parseDevelopmentProtocol( + protocols["contiguous-source-time-five-fold"], + "E40.development_cross_validation.protocols.contiguous-source-time-five-fold", + ), + wholeTrackOrSceneWindow: parseDevelopmentProtocol( + protocols["whole-track-or-scene-window-five-fold"], + "E40.development_cross_validation.protocols.whole-track-or-scene-window-five-fold", + ), + }, + }, + metrics: { + developmentItems: integerValue( + metrics.development_items, + "E40.metrics.development_items", + ), + validationItems: integerValue( + metrics.validation_items, + "E40.metrics.validation_items", + ), + terminalOutcomes: integerValue( + metrics.terminal_outcomes, + "E40.metrics.terminal_outcomes", + ), + accountingFraction: numberValue( + metrics.accounting_fraction, + "E40.metrics.accounting_fraction", + ), + falseFreeClaims: integerValue( + metrics.false_free_claims, + "E40.metrics.false_free_claims", + ), + highSeverityFailures: integerValue( + metrics.high_severity_failures, + "E40.metrics.high_severity_failures", + ), + dimensions: { + presence: parseDimension( + dimensions.presence, + "E40.metrics.dimensions.presence", + ), + geometryAssociation: parseDimension( + dimensions.geometry_association, + "E40.metrics.dimensions.geometry_association", + ), + freshness: parseDimension( + dimensions.freshness, + "E40.metrics.dimensions.freshness", + ), + }, + }, + blockingChecks: strings(item.blocking_checks, "E40.blocking_checks"), + method: { + summary: stringValue(method.summary, "E40.method.summary"), + selection: stringValue(method.selection, "E40.method.selection"), + dimensionProjection: stringValue( + method.dimension_projection, + "E40.method.dimension_projection", + ), + }, + limitations: strings(item.limitations, "E40.limitations"), + access: exactString(item.access, "read-only", "E40.access"), + }; +} + +function parseCatalog(payload: unknown): E40PerceptionProductGateResult | null { + const catalog = record(payload, "Каталог LAB E40"); + exactString( + catalog.schema_version, + "missioncore.laboratory-advanced-catalog/v1", + "Каталог LAB E40.schema_version", + ); + booleanValue(catalog.configured, "Каталог LAB E40.configured"); + integerValue(catalog.candidate_total, "Каталог LAB E40.candidate_total"); + integerValue(catalog.invalid_total, "Каталог LAB E40.invalid_total"); + exactString(catalog.access, "read-only", "Каталог LAB E40.access"); + if (!Array.isArray(catalog.items)) { + throw new E40ProductGateContractError( + "Каталог LAB E40.items: ожидался массив.", + ); + } + if (catalog.items.length > 1) { + throw new E40ProductGateContractError( + "Каталог LAB E40.items: нарушен limit=1.", + ); + } + return catalog.items.length ? parseResult(catalog.items[0]) : null; +} + +export async function fetchE40ProductGateResult({ + fetcher = fetch, + signal, +}: { + fetcher?: LaboratoryFetch; + signal?: AbortSignal; +} = {}): Promise { + const response = await fetcher( + "/api/v1/laboratory/e40/results?limit=1", + { + method: "GET", + headers: { Accept: "application/json" }, + signal, + }, + ); + if (!response.ok) { + throw new E40ProductGateContractError( + `Каталог LAB E40 недоступен: HTTP ${response.status}.`, + ); + } + return parseCatalog(await response.json()); +} diff --git a/apps/control-station/src/workspaces/laboratory/AdvancedLaboratoryResult.tsx b/apps/control-station/src/workspaces/laboratory/AdvancedLaboratoryResult.tsx index 3f40029..a7eeedd 100644 --- a/apps/control-station/src/workspaces/laboratory/AdvancedLaboratoryResult.tsx +++ b/apps/control-station/src/workspaces/laboratory/AdvancedLaboratoryResult.tsx @@ -12,6 +12,7 @@ import { E35Result } from "./E35Result"; import { E37Result } from "./E37Result"; import { E38Result } from "./E38Result"; import { E39Result } from "./E39Result"; +import { E40Result } from "./E40Result"; import { RecordedReplayEvidence } from "./RecordedReplayEvidence"; export type AdvancedLaboratoryWorkId = @@ -22,7 +23,8 @@ export type AdvancedLaboratoryWorkId = | "e35-degradation-recovery" | "e37-ravnoves-acceptance" | "e38-perception-baseline" - | "e39-perception-refinement"; + | "e39-perception-refinement" + | "e40-perception-product-gate"; type LaboratoryWorkspaceProps = WorkspaceRendererProps & { SpatialView: ComponentType; @@ -40,6 +42,7 @@ export function isAdvancedLaboratoryWorkId( || value === "e37-ravnoves-acceptance" || value === "e38-perception-baseline" || value === "e39-perception-refinement" + || value === "e40-perception-product-gate" ); } @@ -96,6 +99,12 @@ export function advancedLaboratoryWorkOptions( label: "LAB E39 · perception refinement R1", }); } + if (results.e40) { + options.push({ + id: "e40-perception-product-gate", + label: "LAB E40 · leakage-resistant product gate", + }); + } return options; } @@ -133,6 +142,9 @@ export function AdvancedLaboratoryResult({ failedSessionId: string | null; replayError: string | null; }) { + if (workId === "e40-perception-product-gate" && results.e40) { + return ; + } if (workId === "e39-perception-refinement" && results.e39) { return ; } diff --git a/apps/control-station/src/workspaces/laboratory/E40Result.tsx b/apps/control-station/src/workspaces/laboratory/E40Result.tsx new file mode 100644 index 0000000..c1a15cc --- /dev/null +++ b/apps/control-station/src/workspaces/laboratory/E40Result.tsx @@ -0,0 +1,180 @@ +import { + LaboratoryEvidence, + LaboratoryMetricGrid, + LaboratoryResultSummary, + LaboratorySummary, + LaboratoryWorkTemplate, +} from "../../components/laboratory/LaboratoryPresentation"; +import type { + E40PerceptionProductGateResult, +} from "../../core/laboratory/e40ProductGate"; +import { formatNumber } from "../../presentation"; + +function percent(value: number): string { + return `${(value * 100).toLocaleString("ru-RU", { + maximumFractionDigits: 1, + })}%`; +} + +export function E40Result({ + rigLabel, + result, +}: { + rigLabel: string; + result: E40PerceptionProductGateResult; +}) { + const metrics = result.metrics; + const dimensions = metrics.dimensions; + const contiguous = ( + result.developmentCrossValidation.protocols.contiguousSourceTime.dimensions + ); + const grouped = ( + result.developmentCrossValidation.protocols.wholeTrackOrSceneWindow.dimensions + ); + const gateLabel = result.qualityGatePassed + ? "RAVNOVES00 product gate пройден" + : "RAVNOVES00 product gate не пройден"; + return ( + + )} + evidence={( + + + + )} + result={( + + )} + /> + ); +} diff --git a/apps/control-station/src/workspaces/laboratory/LaboratoryArchiveWorkspace.tsx b/apps/control-station/src/workspaces/laboratory/LaboratoryArchiveWorkspace.tsx index 80fc160..f27d9c3 100644 --- a/apps/control-station/src/workspaces/laboratory/LaboratoryArchiveWorkspace.tsx +++ b/apps/control-station/src/workspaces/laboratory/LaboratoryArchiveWorkspace.tsx @@ -72,6 +72,7 @@ const EMPTY_ADVANCED_RESULTS: AdvancedLaboratoryResults = { e37: null, e38: null, e39: null, + e40: null, }; function laboratoryWorkOrdinal(value: string): number { @@ -625,7 +626,7 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) { e28.status === "rejected" ? "E28" : null, e29.status === "rejected" ? "E29" : null, e30.status === "rejected" ? "E30" : null, - advanced.status === "rejected" ? "E31–E35" : null, + advanced.status === "rejected" ? "E31–E40" : null, ].filter(Boolean); setEvidenceError( failures.length diff --git a/apps/control-station/test/advancedLaboratoryResults.test.mjs b/apps/control-station/test/advancedLaboratoryResults.test.mjs index 32b05ec..a2dce1b 100644 --- a/apps/control-station/test/advancedLaboratoryResults.test.mjs +++ b/apps/control-station/test/advancedLaboratoryResults.test.mjs @@ -526,6 +526,107 @@ function e39() { }; } +function e40DevelopmentProtocol({ + accuracy, + correct, + incorrect, +}) { + return { + items: 340, + fold_sizes: { + 0: 68, + 1: 68, + 2: 68, + 3: 68, + 4: 68, + }, + passed: true, + dimensions: { + presence: e39DevelopmentDimension({ accuracy, correct, incorrect }), + geometry_association: e39DevelopmentDimension({ + accuracy, + correct, + incorrect, + }), + freshness: e39DevelopmentDimension({ + accuracy: 0.967647, + correct: 329, + incorrect: 11, + }), + }, + }; +} + +function e40() { + return { + result_id: `e40-perception-product-gate-${"a".repeat(64)}`, + created_at_utc: "2026-07-28T08:30:00Z", + source_session_id: "20260720T065719Z_viewer_live", + source_display_name: "RAVNOVES00", + status: "measured-leakage-resistant-product-gate", + profile_id: "e40-ravnoves00-leakage-resistant-product-gate/v1", + worker_node: "DESKTOP-OPJ8J04", + quality_gate_passed: true, + development_cross_validation: { + strategy: "dual-leakage-resistant-development-five-fold", + seed: "e40-development-cv-v1", + folds: 5, + items: 340, + validation_labels_used: false, + passed: true, + protocols: { + "contiguous-source-time-five-fold": e40DevelopmentProtocol({ + accuracy: 0.908824, + correct: 309, + incorrect: 31, + }), + "whole-track-or-scene-window-five-fold": e40DevelopmentProtocol({ + accuracy: 0.920588, + correct: 313, + incorrect: 27, + }), + }, + }, + metrics: { + development_items: 340, + validation_items: 146, + terminal_outcomes: 146, + accounting_fraction: 1, + false_free_claims: 0, + high_severity_failures: 0, + dimensions: { + presence: e38Dimension({ + accuracy: 0.90411, + correct: 132, + incorrect: 14, + passed: true, + }), + geometry_association: e38Dimension({ + accuracy: 0.90411, + correct: 132, + incorrect: 14, + passed: true, + }), + freshness: e38Dimension({ + accuracy: 0.958904, + correct: 140, + incorrect: 6, + passed: true, + }), + }, + }, + blocking_checks: [], + method: { + summary: "conservative policy plus camera-only softmax", + selection: "dual grouped development cross-validation", + dimension_projection: "presence plus immutable stratum", + }, + limitations: ["RAVNOVES00 source scoped"], + authority, + access: "read-only", + }; +} + before(async () => { server = await createServer({ appType: "custom", @@ -542,9 +643,11 @@ after(async () => { await server?.close(); }); -test("decodes E31–E39 from separate read-only catalogs", async () => { +test("decodes E31–E40 from separate read-only catalogs", async () => { const requests = []; - const items = [e31(), e32(), e33(), e34(), e35(), e37(), e38(), e39()]; + const items = [ + e31(), e32(), e33(), e34(), e35(), e37(), e38(), e39(), e40(), + ]; const decoded = await fetchAdvancedLaboratoryResults({ fetcher: async (input, init) => { requests.push({ input: String(input), method: init?.method }); @@ -578,6 +681,15 @@ test("decodes E31–E39 from separate read-only catalogs", async () => { assert.equal(decoded.e39.metrics.dimensions.presence.accuracy, 0.849315); assert.equal(decoded.e39.metrics.highSeverityFailures, 8); assert.equal(decoded.e39.qualityGatePassed, false); + assert.equal( + decoded.e40.developmentCrossValidation.protocols + .wholeTrackOrSceneWindow.dimensions.presence.accuracy, + 0.920588, + ); + assert.equal(decoded.e40.developmentCrossValidation.validationLabelsUsed, false); + assert.equal(decoded.e40.metrics.dimensions.presence.accuracy, 0.90411); + assert.equal(decoded.e40.metrics.highSeverityFailures, 0); + assert.equal(decoded.e40.qualityGatePassed, true); assert.deepEqual(requests, [ { input: "/api/v1/laboratory/e31/results?limit=1", method: "GET" }, { input: "/api/v1/laboratory/e32/results?limit=1", method: "GET" }, @@ -587,9 +699,36 @@ test("decodes E31–E39 from separate read-only catalogs", async () => { { input: "/api/v1/laboratory/e37/results?limit=1", method: "GET" }, { input: "/api/v1/laboratory/e38/results?limit=1", method: "GET" }, { input: "/api/v1/laboratory/e39/results?limit=1", method: "GET" }, + { input: "/api/v1/laboratory/e40/results?limit=1", method: "GET" }, ]); }); +test("keeps valid LAB catalogs available when one transport endpoint fails", async () => { + const decoded = await fetchAdvancedLaboratoryResults({ + fetcher: async (input) => { + const path = String(input); + if (path.includes("/e31/")) { + return new Response("", { status: 503 }); + } + const item = path.includes("/e32/") ? e32() + : path.includes("/e33/") ? e33() + : path.includes("/e34/") ? e34() + : path.includes("/e35/") ? e35() + : path.includes("/e37/") ? e37() + : path.includes("/e38/") ? e38() + : path.includes("/e39/") ? e39() : e40(); + return new Response(JSON.stringify(catalog(item)), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }, + }); + + assert.equal(decoded.e31, null); + assert.equal(decoded.e32.metrics.qualifiedPointsPublished, 2119302); + assert.equal(decoded.e40.qualityGatePassed, true); +}); + test("rejects authority escalation in an accepted-looking result", async () => { const forged = { ...e31(), @@ -604,7 +743,8 @@ test("rejects authority escalation in an accepted-looking result", async () => { : String(input).includes("/e34/") ? e34() : String(input).includes("/e35/") ? e35() : String(input).includes("/e37/") ? e37() - : String(input).includes("/e38/") ? e38() : e39(), + : String(input).includes("/e38/") ? e38() + : String(input).includes("/e39/") ? e39() : e40(), )), { status: 200 }), }), AdvancedLaboratoryContractError, diff --git a/apps/control-station/test/applicationArchitecture.test.mjs b/apps/control-station/test/applicationArchitecture.test.mjs index 867ba19..ba24b0c 100644 --- a/apps/control-station/test/applicationArchitecture.test.mjs +++ b/apps/control-station/test/applicationArchitecture.test.mjs @@ -110,6 +110,8 @@ test("central composition files cannot silently become monoliths again", async ( ["App.tsx", 1_250], ["workspaces/Workspaces.tsx", 1_200], ["workspaces/laboratory/LaboratoryArchiveWorkspace.tsx", 1_000], + ["core/laboratory/advancedResults.ts", 1_000], + ["core/laboratory/e40ProductGate.ts", 500], ["styles/workspaces.css", 4_350], ["styles/laboratory.css", 900], ["styles/laboratory-reporting.css", 100], diff --git a/apps/control-station/test/laboratoryProductUi.test.mjs b/apps/control-station/test/laboratoryProductUi.test.mjs index 5183ac3..ae2c144 100644 --- a/apps/control-station/test/laboratoryProductUi.test.mjs +++ b/apps/control-station/test/laboratoryProductUi.test.mjs @@ -38,6 +38,10 @@ const e39ResultUrl = new URL( "../src/workspaces/laboratory/E39Result.tsx", import.meta.url, ); +const e40ResultUrl = new URL( + "../src/workspaces/laboratory/E40Result.tsx", + import.meta.url, +); const e35StylesUrl = new URL( "../src/styles/e35-degradation-recovery.css", import.meta.url, @@ -261,6 +265,28 @@ test("E39 reports refinement and the CV-to-validation gap through the canonical assert.match(advancedSource, / { + const [e40Source, advancedSource] = await Promise.all([ + readFile(e40ResultUrl, "utf8"), + readFile(advancedLaboratoryResultUrl, "utf8"), + ]); + + assert.match(e40Source, / { const workspacesSource = await readFile(workspacesUrl, "utf8");