From 1f20e0d7d95f540cb0eed8b5fa2f0adb77ad235d Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Tue, 28 Jul 2026 10:06:57 +0300 Subject: [PATCH] feat(lab): publish E39 refinement result --- .../src/core/laboratory/advancedResults.ts | 204 +++++++++++++++++- .../laboratory/AdvancedLaboratoryResult.tsx | 14 +- .../src/workspaces/laboratory/E39Result.tsx | 176 +++++++++++++++ .../laboratory/LaboratoryArchiveWorkspace.tsx | 1 + .../test/advancedLaboratoryResults.test.mjs | 110 +++++++++- .../test/laboratoryProductUi.test.mjs | 22 ++ src/k1link/web/advanced_laboratory_api.py | 89 ++++++++ src/k1link/web/app.py | 7 + tests/test_advanced_laboratory_api.py | 156 +++++++++++++- 9 files changed, 770 insertions(+), 9 deletions(-) create mode 100644 apps/control-station/src/workspaces/laboratory/E39Result.tsx diff --git a/apps/control-station/src/core/laboratory/advancedResults.ts b/apps/control-station/src/core/laboratory/advancedResults.ts index 9fe4efb..1384c1a 100644 --- a/apps/control-station/src/core/laboratory/advancedResults.ts +++ b/apps/control-station/src/core/laboratory/advancedResults.ts @@ -179,6 +179,48 @@ export interface E38PerceptionBaselineResult { access: "read-only"; } +export interface E39DevelopmentDimensionMetric { + correct: number; + incorrect: number; + total: number; + accuracy: number; + target: number; + passed: boolean; +} + +export interface E39PerceptionRefinementResult { + resultId: string; + createdAtUtc: string | null; + sourceSessionId: string; + sourceDisplayName: string; + status: "measured-r1-source-scoped-refinement"; + profileId: string; + workerNode: string; + qualityGatePassed: boolean; + developmentCrossValidation: { + strategy: string; + seed: string; + folds: number; + items: number; + validationLabelsUsed: false; + passed: boolean; + dimensions: { + presence: E39DevelopmentDimensionMetric; + geometryAssociation: E39DevelopmentDimensionMetric; + freshness: E39DevelopmentDimensionMetric; + }; + }; + metrics: E38PerceptionBaselineResult["metrics"]; + blockingChecks: readonly string[]; + method: { + summary: string; + selection: string; + dimensionProjection: string; + }; + limitations: readonly string[]; + access: "read-only"; +} + export interface AdvancedLaboratoryResults { e31: E31LaboratoryResult | null; e32: E32LaboratoryResult | null; @@ -187,6 +229,7 @@ export interface AdvancedLaboratoryResults { e35: E35DegradationRecoveryResult | null; e37: E37AcceptanceContractResult | null; e38: E38PerceptionBaselineResult | null; + e39: E39PerceptionRefinementResult | null; } export class AdvancedLaboratoryContractError extends Error { @@ -248,6 +291,13 @@ function trueValue(value: unknown, label: string): true { return true; } +function falseValue(value: unknown, label: string): false { + if (value !== false) { + throw new AdvancedLaboratoryContractError(`${label}: ожидалось false.`); + } + return false; +} + function finiteNumber(value: unknown, label: string): number { if (typeof value !== "number" || !Number.isFinite(value)) { throw new AdvancedLaboratoryContractError(`${label}: ожидалось число.`); @@ -728,6 +778,155 @@ function parseE38(value: unknown): E38PerceptionBaselineResult { }; } +function parseE39DevelopmentDimension( + value: unknown, + label: string, +): E39DevelopmentDimensionMetric { + const source = record(value, label); + return { + correct: integerValue(source.correct, `${label}.correct`), + incorrect: integerValue(source.incorrect, `${label}.incorrect`), + total: integerValue(source.total, `${label}.total`), + accuracy: numberValue(source.accuracy, `${label}.accuracy`), + target: numberValue(source.target, `${label}.target`), + passed: booleanValue(source.passed, `${label}.passed`), + }; +} + +function parseE39(value: unknown): E39PerceptionRefinementResult { + const item = record(value, "E39"); + const metrics = record(item.metrics, "E39.metrics"); + const dimensions = record(metrics.dimensions, "E39.metrics.dimensions"); + const developmentCrossValidation = record( + item.development_cross_validation, + "E39.development_cross_validation", + ); + const developmentDimensions = record( + developmentCrossValidation.dimensions, + "E39.development_cross_validation.dimensions", + ); + const method = record(item.method, "E39.method"); + diagnosticAuthority(item.authority, "E39.authority"); + return { + resultId: contentId( + item.result_id, + "e39-perception-refinement", + "E39.result_id", + ), + createdAtUtc: optionalString(item.created_at_utc, "E39.created_at_utc"), + sourceSessionId: stringValue( + item.source_session_id, + "E39.source_session_id", + ), + sourceDisplayName: stringValue( + item.source_display_name, + "E39.source_display_name", + ), + status: exactString( + item.status, + "measured-r1-source-scoped-refinement", + "E39.status", + ), + profileId: stringValue(item.profile_id, "E39.profile_id"), + workerNode: stringValue(item.worker_node, "E39.worker_node"), + qualityGatePassed: booleanValue( + item.quality_gate_passed, + "E39.quality_gate_passed", + ), + developmentCrossValidation: { + strategy: stringValue( + developmentCrossValidation.strategy, + "E39.development_cross_validation.strategy", + ), + seed: stringValue( + developmentCrossValidation.seed, + "E39.development_cross_validation.seed", + ), + folds: integerValue( + developmentCrossValidation.folds, + "E39.development_cross_validation.folds", + ), + items: integerValue( + developmentCrossValidation.items, + "E39.development_cross_validation.items", + ), + validationLabelsUsed: falseValue( + developmentCrossValidation.validation_labels_used, + "E39.development_cross_validation.validation_labels_used", + ), + passed: booleanValue( + developmentCrossValidation.passed, + "E39.development_cross_validation.passed", + ), + dimensions: { + presence: parseE39DevelopmentDimension( + developmentDimensions.presence, + "E39.development_cross_validation.dimensions.presence", + ), + geometryAssociation: parseE39DevelopmentDimension( + developmentDimensions.geometry_association, + "E39.development_cross_validation.dimensions.geometry_association", + ), + freshness: parseE39DevelopmentDimension( + developmentDimensions.freshness, + "E39.development_cross_validation.dimensions.freshness", + ), + }, + }, + metrics: { + developmentItems: integerValue( + metrics.development_items, + "E39.metrics.development_items", + ), + validationItems: integerValue( + metrics.validation_items, + "E39.metrics.validation_items", + ), + terminalOutcomes: integerValue( + metrics.terminal_outcomes, + "E39.metrics.terminal_outcomes", + ), + accountingFraction: numberValue( + metrics.accounting_fraction, + "E39.metrics.accounting_fraction", + ), + falseFreeClaims: integerValue( + metrics.false_free_claims, + "E39.metrics.false_free_claims", + ), + highSeverityFailures: integerValue( + metrics.high_severity_failures, + "E39.metrics.high_severity_failures", + ), + dimensions: { + presence: parseE38Dimension( + dimensions.presence, + "E39.metrics.dimensions.presence", + ), + geometryAssociation: parseE38Dimension( + dimensions.geometry_association, + "E39.metrics.dimensions.geometry_association", + ), + freshness: parseE38Dimension( + dimensions.freshness, + "E39.metrics.dimensions.freshness", + ), + }, + }, + blockingChecks: strings(item.blocking_checks, "E39.blocking_checks"), + method: { + summary: stringValue(method.summary, "E39.method.summary"), + selection: stringValue(method.selection, "E39.method.selection"), + dimensionProjection: stringValue( + method.dimension_projection, + "E39.method.dimension_projection", + ), + }, + limitations: strings(item.limitations, "E39.limitations"), + access: exactString(item.access, "read-only", "E39.access"), + }; +} + async function fetchOne( path: string, parser: (value: unknown) => T, @@ -752,7 +951,7 @@ export async function fetchAdvancedLaboratoryResults({ fetcher?: LaboratoryFetch; signal?: AbortSignal; } = {}): Promise { - const [e31, e32, e33, e34, e35, e37, e38] = await Promise.all([ + const [e31, e32, e33, e34, e35, e37, e38, e39] = await Promise.all([ 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), @@ -760,8 +959,9 @@ export async function fetchAdvancedLaboratoryResults({ fetchE35DegradationRecoveryResult({ fetcher, signal }), 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), ]); - return { e31, e32, e33, e34, e35, e37, e38 }; + return { e31, e32, e33, e34, e35, e37, e38, e39 }; } import { fetchE34TemporalLayerResult, diff --git a/apps/control-station/src/workspaces/laboratory/AdvancedLaboratoryResult.tsx b/apps/control-station/src/workspaces/laboratory/AdvancedLaboratoryResult.tsx index c46f68d..3f40029 100644 --- a/apps/control-station/src/workspaces/laboratory/AdvancedLaboratoryResult.tsx +++ b/apps/control-station/src/workspaces/laboratory/AdvancedLaboratoryResult.tsx @@ -11,6 +11,7 @@ import { E34Result } from "./E34Result"; import { E35Result } from "./E35Result"; import { E37Result } from "./E37Result"; import { E38Result } from "./E38Result"; +import { E39Result } from "./E39Result"; import { RecordedReplayEvidence } from "./RecordedReplayEvidence"; export type AdvancedLaboratoryWorkId = @@ -20,7 +21,8 @@ export type AdvancedLaboratoryWorkId = | "e34-temporal-layer" | "e35-degradation-recovery" | "e37-ravnoves-acceptance" - | "e38-perception-baseline"; + | "e38-perception-baseline" + | "e39-perception-refinement"; type LaboratoryWorkspaceProps = WorkspaceRendererProps & { SpatialView: ComponentType; @@ -37,6 +39,7 @@ export function isAdvancedLaboratoryWorkId( || value === "e35-degradation-recovery" || value === "e37-ravnoves-acceptance" || value === "e38-perception-baseline" + || value === "e39-perception-refinement" ); } @@ -87,6 +90,12 @@ export function advancedLaboratoryWorkOptions( label: "LAB E38 · perception quality R1", }); } + if (results.e39) { + options.push({ + id: "e39-perception-refinement", + label: "LAB E39 · perception refinement R1", + }); + } return options; } @@ -124,6 +133,9 @@ export function AdvancedLaboratoryResult({ failedSessionId: string | null; replayError: string | null; }) { + if (workId === "e39-perception-refinement" && results.e39) { + return ; + } if (workId === "e38-perception-baseline" && results.e38) { return ; } diff --git a/apps/control-station/src/workspaces/laboratory/E39Result.tsx b/apps/control-station/src/workspaces/laboratory/E39Result.tsx new file mode 100644 index 0000000..2f476c1 --- /dev/null +++ b/apps/control-station/src/workspaces/laboratory/E39Result.tsx @@ -0,0 +1,176 @@ +import { + LaboratoryEvidence, + LaboratoryResultSummary, + LaboratorySummary, + LaboratoryWorkTemplate, +} from "../../components/laboratory/LaboratoryPresentation"; +import type { + E39PerceptionRefinementResult, +} from "../../core/laboratory/advancedResults"; +import { formatNumber } from "../../presentation"; + +function percent(value: number): string { + return `${(value * 100).toLocaleString("ru-RU", { + maximumFractionDigits: 1, + })}%`; +} + +export function E39Result({ + rigLabel, + result, +}: { + rigLabel: string; + result: E39PerceptionRefinementResult; +}) { + const metrics = result.metrics; + const dimensions = metrics.dimensions; + const development = result.developmentCrossValidation.dimensions; + const gateLabel = result.qualityGatePassed + ? "R1 gate пройден" + : "R1 gate не пройден"; + return ( + + )} + evidence={( + +
+
+ Development presence + {percent(development.presence.accuracy)} + + {`${formatNumber(development.presence.correct, 0)} / ${formatNumber(development.presence.total, 0)} · 5-fold CV`} + +
+
+ Validation presence + {percent(dimensions.presence.accuracy)} + + {`${formatNumber(dimensions.presence.correct, 0)} / ${formatNumber(dimensions.presence.total, 0)} · цель 90%`} + +
+
+ Validation geometry + {percent(dimensions.geometryAssociation.accuracy)} + + {`${formatNumber(dimensions.geometryAssociation.correct, 0)} / ${formatNumber(dimensions.geometryAssociation.total, 0)} · цель 90%`} + +
+
+ Validation freshness + {percent(dimensions.freshness.accuracy)} + + {`${formatNumber(dimensions.freshness.correct, 0)} / ${formatNumber(dimensions.freshness.total, 0)} · цель 90%`} + +
+
+
+ )} + result={( + + )} + /> + ); +} diff --git a/apps/control-station/src/workspaces/laboratory/LaboratoryArchiveWorkspace.tsx b/apps/control-station/src/workspaces/laboratory/LaboratoryArchiveWorkspace.tsx index 57cb1be..80fc160 100644 --- a/apps/control-station/src/workspaces/laboratory/LaboratoryArchiveWorkspace.tsx +++ b/apps/control-station/src/workspaces/laboratory/LaboratoryArchiveWorkspace.tsx @@ -71,6 +71,7 @@ const EMPTY_ADVANCED_RESULTS: AdvancedLaboratoryResults = { e35: null, e37: null, e38: null, + e39: null, }; function laboratoryWorkOrdinal(value: string): number { diff --git a/apps/control-station/test/advancedLaboratoryResults.test.mjs b/apps/control-station/test/advancedLaboratoryResults.test.mjs index 94cbc46..32b05ec 100644 --- a/apps/control-station/test/advancedLaboratoryResults.test.mjs +++ b/apps/control-station/test/advancedLaboratoryResults.test.mjs @@ -432,6 +432,100 @@ function e38() { }; } +function e39DevelopmentDimension({ + accuracy, + correct, + incorrect, +}) { + return { + correct, + incorrect, + total: 340, + accuracy, + target: 0.9, + passed: true, + }; +} + +function e39() { + return { + result_id: `e39-perception-refinement-${"9".repeat(64)}`, + created_at_utc: "2026-07-28T06:29:42.847Z", + source_session_id: "20260720T065719Z_viewer_live", + source_display_name: "RAVNOVES00", + status: "measured-r1-source-scoped-refinement", + profile_id: "e39-ravnoves00-r1-development-knn/v1", + worker_node: "DESKTOP-OPJ8J04", + quality_gate_passed: false, + development_cross_validation: { + strategy: "deterministic-item-hash-five-fold", + seed: "e39-dev-cv", + folds: 5, + items: 340, + validation_labels_used: false, + passed: true, + dimensions: { + presence: e39DevelopmentDimension({ + accuracy: 0.902941, + correct: 307, + incorrect: 33, + }), + geometry_association: e39DevelopmentDimension({ + accuracy: 0.905882, + correct: 308, + incorrect: 32, + }), + freshness: e39DevelopmentDimension({ + accuracy: 0.964706, + correct: 328, + incorrect: 12, + }), + }, + }, + metrics: { + development_items: 340, + validation_items: 146, + terminal_outcomes: 146, + accounting_fraction: 1, + false_free_claims: 0, + high_severity_failures: 8, + dimensions: { + presence: e38Dimension({ + accuracy: 0.849315, + correct: 124, + incorrect: 22, + passed: false, + }), + geometry_association: e38Dimension({ + accuracy: 0.849315, + correct: 124, + incorrect: 22, + passed: false, + }), + freshness: e38Dimension({ + accuracy: 0.931507, + correct: 136, + incorrect: 10, + passed: true, + }), + }, + }, + blocking_checks: [ + "presence_target_reached", + "geometry_association_target_reached", + "high_severity_failures_zero", + ], + method: { + summary: "robust-scaled 3-neighbour presence classification", + selection: "development-only deterministic five-fold cross-validation", + dimension_projection: "derived from predicted presence and immutable stratum", + }, + limitations: ["source-scoped"], + authority, + access: "read-only", + }; +} + before(async () => { server = await createServer({ appType: "custom", @@ -448,9 +542,9 @@ after(async () => { await server?.close(); }); -test("decodes E31–E38 from separate read-only catalogs", async () => { +test("decodes E31–E39 from separate read-only catalogs", async () => { const requests = []; - const items = [e31(), e32(), e33(), e34(), e35(), e37(), e38()]; + const items = [e31(), e32(), e33(), e34(), e35(), e37(), e38(), e39()]; const decoded = await fetchAdvancedLaboratoryResults({ fetcher: async (input, init) => { requests.push({ input: String(input), method: init?.method }); @@ -476,6 +570,14 @@ test("decodes E31–E38 from separate read-only catalogs", async () => { assert.equal(decoded.e38.metrics.dimensions.presence.accuracy, 0.821918); assert.equal(decoded.e38.metrics.dimensions.freshness.passed, true); assert.equal(decoded.e38.qualityGatePassed, false); + assert.equal( + decoded.e39.developmentCrossValidation.dimensions.presence.accuracy, + 0.902941, + ); + assert.equal(decoded.e39.developmentCrossValidation.validationLabelsUsed, false); + assert.equal(decoded.e39.metrics.dimensions.presence.accuracy, 0.849315); + assert.equal(decoded.e39.metrics.highSeverityFailures, 8); + assert.equal(decoded.e39.qualityGatePassed, false); assert.deepEqual(requests, [ { input: "/api/v1/laboratory/e31/results?limit=1", method: "GET" }, { input: "/api/v1/laboratory/e32/results?limit=1", method: "GET" }, @@ -484,6 +586,7 @@ test("decodes E31–E38 from separate read-only catalogs", async () => { { input: "/api/v1/laboratory/e35/results?limit=1", method: "GET" }, { 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" }, ]); }); @@ -500,7 +603,8 @@ test("rejects authority escalation in an accepted-looking result", async () => { : String(input).includes("/e33/") ? e33() : String(input).includes("/e34/") ? e34() : String(input).includes("/e35/") ? e35() - : String(input).includes("/e37/") ? e37() : e38(), + : String(input).includes("/e37/") ? e37() + : String(input).includes("/e38/") ? e38() : e39(), )), { status: 200 }), }), AdvancedLaboratoryContractError, diff --git a/apps/control-station/test/laboratoryProductUi.test.mjs b/apps/control-station/test/laboratoryProductUi.test.mjs index 872928c..5183ac3 100644 --- a/apps/control-station/test/laboratoryProductUi.test.mjs +++ b/apps/control-station/test/laboratoryProductUi.test.mjs @@ -34,6 +34,10 @@ const e38ResultUrl = new URL( "../src/workspaces/laboratory/E38Result.tsx", import.meta.url, ); +const e39ResultUrl = new URL( + "../src/workspaces/laboratory/E39Result.tsx", + import.meta.url, +); const e35StylesUrl = new URL( "../src/styles/e35-degradation-recovery.css", import.meta.url, @@ -239,6 +243,24 @@ test("E38 reports the frozen R1 baseline through the canonical LAB anatomy", asy assert.match(advancedSource, / { + const [e39Source, advancedSource] = await Promise.all([ + readFile(e39ResultUrl, "utf8"), + readFile(advancedLaboratoryResultUrl, "utf8"), + ]); + + assert.match(e39Source, / { const workspacesSource = await readFile(workspacesUrl, "utf8"); diff --git a/src/k1link/web/advanced_laboratory_api.py b/src/k1link/web/advanced_laboratory_api.py index d67e43e..ad6097f 100644 --- a/src/k1link/web/advanced_laboratory_api.py +++ b/src/k1link/web/advanced_laboratory_api.py @@ -44,6 +44,11 @@ from k1link.compute.e38_perception_baseline import ( E38PerceptionBaselineError, read_e38_perception_baseline, ) +from k1link.compute.e39_perception_refinement import ( + E39PerceptionRefinement, + E39PerceptionRefinementError, + read_e39_perception_refinement, +) LABORATORY_ADVANCED_CATALOG_SCHEMA: Final = ( "missioncore.laboratory-advanced-catalog/v1" @@ -56,6 +61,7 @@ _E34_RESULT_ID = re.compile(r"^e34-temporal-occupied-[a-f0-9]{64}$") _E35_RESULT_ID = re.compile(r"^e35-degradation-recovery-[a-f0-9]{64}$") _E37_RESULT_ID = re.compile(r"^e37-ravnoves-acceptance-[a-f0-9]{64}$") _E38_RESULT_ID = re.compile(r"^e38-perception-baseline-[a-f0-9]{64}$") +_E39_RESULT_ID = re.compile(r"^e39-perception-refinement-[a-f0-9]{64}$") RootProvider = Callable[[], Path | None] @@ -133,6 +139,15 @@ def _read_e38_cached( return read_e38_perception_baseline(Path(root_text)) +@lru_cache(maxsize=16) +def _read_e39_cached( + root_text: str, + signature: tuple[int, ...], +) -> E39PerceptionRefinement: + del signature + return read_e39_perception_refinement(Path(root_text)) + + def _configured_root(provider: RootProvider) -> Path | None: value = provider() if value is None: @@ -604,6 +619,46 @@ def _project_e38(result: E38PerceptionBaseline) -> dict[str, object]: } +def _project_e39(result: E39PerceptionRefinement) -> dict[str, object]: + identity = _object(result.manifest.get("identity"), "E39 identity") + source = _object(identity.get("source"), "E39 source") + execution = _object(identity.get("execution"), "E39 execution") + profile = _object(identity.get("profile"), "E39 profile") + metrics = _object(result.report.get("metrics"), "E39 metrics") + dimensions = _object(metrics.get("dimensions"), "E39 dimensions") + quality_gate = _object(result.report.get("quality_gate"), "E39 gate") + development_cv = _object( + result.report.get("development_cross_validation"), + "E39 development CV", + ) + return { + "result_id": result.result_id, + "created_at_utc": result.manifest.get("created_at_utc"), + "source_session_id": source.get("session_id"), + "source_display_name": source.get("display_name"), + "status": result.report.get("status"), + "profile_id": profile.get("profile_id"), + "worker_node": execution.get("worker_node"), + "quality_gate_passed": quality_gate.get("passed"), + "development_cross_validation": copy.deepcopy(development_cv), + "metrics": { + "development_items": metrics.get("development_items"), + "validation_items": metrics.get("validation_items"), + "terminal_outcomes": metrics.get("terminal_outcomes"), + "accounting_fraction": metrics.get("accounting_fraction"), + "false_free_claims": metrics.get("false_free_claims"), + "high_severity_failures": metrics.get("high_severity_failures"), + "dimensions": copy.deepcopy(dimensions), + }, + "blocking_checks": copy.deepcopy(quality_gate.get("blocking_checks")), + "method": copy.deepcopy(result.report.get("method")), + "decision": copy.deepcopy(result.report.get("decision")), + "limitations": copy.deepcopy(result.report.get("limitations")), + "authority": copy.deepcopy(result.report.get("authority")), + "access": "read-only", + } + + def _empty_catalog(configured: bool) -> dict[str, object]: return { "schema_version": LABORATORY_ADVANCED_CATALOG_SCHEMA, @@ -624,6 +679,7 @@ def build_advanced_laboratory_router( e35_root_provider: RootProvider = lambda: None, e37_root_provider: RootProvider = lambda: None, e38_root_provider: RootProvider = lambda: None, + e39_root_provider: RootProvider = lambda: None, ) -> APIRouter: router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"]) @@ -886,4 +942,37 @@ def build_advanced_laboratory_router( "invalid_total": invalid_total, } + @router.get("/e39/results") + def list_e39_results( + limit: int = Query(default=1, ge=1, le=10), + ) -> dict[str, object]: + root = _configured_root(e39_root_provider) + if root is None: + return _empty_catalog(False) + candidates = _candidates(root, _E39_RESULT_ID) + items: list[dict[str, object]] = [] + invalid_total = 0 + for candidate in candidates: + try: + result = _read_e39_cached( + str(candidate.resolve()), + _result_signature(candidate), + ) + if len(items) < limit: + items.append(_project_e39(result)) + except ( + E39PerceptionRefinementError, + KeyError, + OSError, + TypeError, + ValueError, + ): + invalid_total += 1 + return { + **_empty_catalog(True), + "items": items, + "candidate_total": len(candidates), + "invalid_total": invalid_total, + } + return router diff --git a/src/k1link/web/app.py b/src/k1link/web/app.py index 6afd421..8b2341e 100644 --- a/src/k1link/web/app.py +++ b/src/k1link/web/app.py @@ -550,6 +550,13 @@ app.include_router( / "e38" / "results" ), + e39_root_provider=lambda: ( + REPOSITORY_ROOT + / ".runtime" + / "compute-experiments" + / "e39" + / "results" + ), ) ) app.include_router( diff --git a/tests/test_advanced_laboratory_api.py b/tests/test_advanced_laboratory_api.py index 576dc44..fb25c2d 100644 --- a/tests/test_advanced_laboratory_api.py +++ b/tests/test_advanced_laboratory_api.py @@ -26,7 +26,7 @@ def _endpoint(router: APIRouter, path: str) -> object: def test_advanced_catalogs_are_empty_when_not_configured() -> None: router = build_advanced_laboratory_router() - for name in ("e31", "e32", "e33", "e34", "e35", "e37", "e38"): + for name in ("e31", "e32", "e33", "e34", "e35", "e37", "e38", "e39"): route = _endpoint(router, f"/api/v1/laboratory/{name}/results") catalog = route(limit=1) # type: ignore[operator] assert catalog == { @@ -49,7 +49,8 @@ def test_advanced_catalogs_fail_closed_on_incomplete_results( e35 = tmp_path / "e35" e37 = tmp_path / "e37" e38 = tmp_path / "e38" - for root in (e31, e32, e33, e34, e35, e37, e38): + e39 = tmp_path / "e39" + for root in (e31, e32, e33, e34, e35, e37, e38, e39): root.mkdir() (e31 / f"e31-source-qualification-{'1' * 64}").mkdir() (e32 / f"e32-track-geometry-{'2' * 64}").mkdir() @@ -58,6 +59,7 @@ def test_advanced_catalogs_fail_closed_on_incomplete_results( (e35 / f"e35-degradation-recovery-{'5' * 64}").mkdir() (e37 / f"e37-ravnoves-acceptance-{'7' * 64}").mkdir() (e38 / f"e38-perception-baseline-{'8' * 64}").mkdir() + (e39 / f"e39-perception-refinement-{'9' * 64}").mkdir() router = build_advanced_laboratory_router( e31_root_provider=lambda: e31, e32_root_provider=lambda: e32, @@ -66,9 +68,10 @@ def test_advanced_catalogs_fail_closed_on_incomplete_results( e35_root_provider=lambda: e35, e37_root_provider=lambda: e37, e38_root_provider=lambda: e38, + e39_root_provider=lambda: e39, ) - for name in ("e31", "e32", "e33", "e34", "e35", "e37", "e38"): + for name in ("e31", "e32", "e33", "e34", "e35", "e37", "e38", "e39"): route = _endpoint(router, f"/api/v1/laboratory/{name}/results") catalog = route(limit=1) # type: ignore[operator] assert catalog["configured"] is True @@ -220,6 +223,153 @@ def test_e34_catalog_projects_only_accepted_read_only_evidence( assert item["access"] == "read-only" +def test_e39_catalog_projects_development_cv_and_sealed_validation( + tmp_path: Path, + monkeypatch: MonkeyPatch, +) -> None: + result_id = f"e39-perception-refinement-{'9' * 64}" + root = tmp_path / "e39" + candidate = root / result_id + candidate.mkdir(parents=True) + (candidate / "manifest.json").write_text("{}", encoding="utf-8") + authority = { + "commands_enabled": False, + "navigation_or_safety_accepted": False, + } + dimension = { + "correct": 124, + "incorrect": 22, + "total": 146, + "accuracy": 0.849315, + "target": 0.9, + "passed": False, + "confusion": [], + "by_stratum": {}, + } + result = SimpleNamespace( + result_id=result_id, + manifest={ + "created_at_utc": "2026-07-28T06:29:42.847Z", + "identity": { + "source": { + "session_id": "20260720T065719Z_viewer_live", + "display_name": "RAVNOVES00", + }, + "profile": { + "profile_id": "e39-ravnoves00-r1-development-knn/v1", + }, + "execution": { + "worker_node": "DESKTOP-OPJ8J04", + }, + }, + }, + report={ + "status": "measured-r1-source-scoped-refinement", + "development_cross_validation": { + "strategy": "deterministic-item-hash-five-fold", + "seed": "e39-dev-cv", + "folds": 5, + "items": 340, + "validation_labels_used": False, + "passed": True, + "dimensions": { + "presence": { + "correct": 307, + "incorrect": 33, + "total": 340, + "accuracy": 0.902941, + "target": 0.9, + "passed": True, + }, + "geometry_association": { + "correct": 308, + "incorrect": 32, + "total": 340, + "accuracy": 0.905882, + "target": 0.9, + "passed": True, + }, + "freshness": { + "correct": 328, + "incorrect": 12, + "total": 340, + "accuracy": 0.964706, + "target": 0.9, + "passed": True, + }, + }, + }, + "metrics": { + "development_items": 340, + "validation_items": 146, + "terminal_outcomes": 146, + "accounting_fraction": 1.0, + "false_free_claims": 0, + "high_severity_failures": 8, + "dimensions": { + "presence": dimension, + "geometry_association": dimension, + "freshness": { + **dimension, + "correct": 136, + "incorrect": 10, + "accuracy": 0.931507, + "passed": True, + }, + }, + }, + "quality_gate": { + "passed": False, + "blocking_checks": [ + "presence_target_reached", + "geometry_association_target_reached", + "high_severity_failures_zero", + ], + }, + "method": { + "summary": "robust-scaled 3-neighbour classification", + "selection": "development-only five-fold cross-validation", + "dimension_projection": "presence plus immutable stratum", + }, + "decision": { + "r1_refinement_measured": True, + "accepted_for_release": False, + }, + "limitations": ["source-scoped"], + "authority": authority, + }, + ) + + def fake_read( + root_text: str, + signature: tuple[int, ...], + ) -> SimpleNamespace: + assert root_text == str(candidate.resolve()) + assert signature + return result + + monkeypatch.setattr(advanced_api, "_read_e39_cached", fake_read) + router = build_advanced_laboratory_router( + e39_root_provider=lambda: root, + ) + route = _endpoint(router, "/api/v1/laboratory/e39/results") + catalog = route(limit=1) # type: ignore[operator] + + assert catalog["candidate_total"] == 1 + assert catalog["invalid_total"] == 0 + item = catalog["items"][0] + assert item["development_cross_validation"]["passed"] is True + assert ( + item["development_cross_validation"]["validation_labels_used"] + is False + ) + assert item["metrics"]["dimensions"]["presence"]["accuracy"] == 0.849315 + assert item["metrics"]["high_severity_failures"] == 8 + assert item["quality_gate_passed"] is False + assert item["authority"] == authority + assert item["access"] == "read-only" + + def test_e35_catalog_projects_recovery_and_review( tmp_path: Path, monkeypatch: MonkeyPatch,