feat(lab): add lazy E40 evidence review

This commit is contained in:
DCCONSTRUCTIONS
2026-07-30 21:53:04 +03:00
parent e9bbfb9a41
commit fdcaf57ae7
24 changed files with 3570 additions and 165 deletions
@@ -0,0 +1,243 @@
import {
AdvancedLaboratoryContractError,
fetchOne,
parseE31,
parseE32,
parseE33,
parseE37,
parseE38,
parseE39,
type AdvancedLaboratoryResults,
type LaboratoryFetch,
} from "./advancedResults";
import { fetchE34TemporalLayerResult } from "./e34TemporalLayer";
import { fetchE35DegradationRecoveryResult } from "./e35DegradationRecovery";
import { fetchE40ProductGateResult } from "./e40ProductGate";
export type AdvancedLaboratoryWorkId =
| "e31-source-binding"
| "e32-track-geometry"
| "e33-worker-shadow"
| "e34-temporal-layer"
| "e35-degradation-recovery"
| "e37-ravnoves-acceptance"
| "e38-perception-baseline"
| "e39-perception-refinement"
| "e40-perception-product-gate";
export interface AdvancedLaboratoryIndexItem {
workId: AdvancedLaboratoryWorkId;
resultId: string;
createdAtUtc: string;
}
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
"e31-source-binding",
"e32-track-geometry",
"e33-worker-shadow",
"e34-temporal-layer",
"e35-degradation-recovery",
"e37-ravnoves-acceptance",
"e38-perception-baseline",
"e39-perception-refinement",
"e40-perception-product-gate",
];
const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
"e31-source-binding": "e31-source-qualification",
"e32-track-geometry": "e32-track-geometry",
"e33-worker-shadow": "e33-worker-shadow",
"e34-temporal-layer": "e34-temporal-occupied",
"e35-degradation-recovery": "e35-degradation-recovery",
"e37-ravnoves-acceptance": "e37-ravnoves-acceptance",
"e38-perception-baseline": "e38-perception-baseline",
"e39-perception-refinement": "e39-perception-refinement",
"e40-perception-product-gate": "e40-perception-product-gate",
};
export function isAdvancedLaboratoryWorkId(
value: string,
): value is AdvancedLaboratoryWorkId {
return WORK_IDS.includes(value as AdvancedLaboratoryWorkId);
}
export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
return {
e31: null,
e32: null,
e33: null,
e34: null,
e35: null,
e37: null,
e38: null,
e39: null,
e40: null,
};
}
function objectValue(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new AdvancedLaboratoryContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function exactString(value: unknown, expected: string, label: string): string {
if (value !== expected) {
throw new AdvancedLaboratoryContractError(`${label}: нарушен контракт.`);
}
return expected;
}
function parseIndexItem(value: unknown): AdvancedLaboratoryIndexItem {
const item = objectValue(value, "Индекс LAB.item");
if (
typeof item.work_id !== "string"
|| !isAdvancedLaboratoryWorkId(item.work_id)
) {
throw new AdvancedLaboratoryContractError(
"Индекс LAB.work_id: неизвестная лабораторная работа.",
);
}
const prefix = RESULT_PREFIX[item.work_id];
if (
typeof item.result_id !== "string"
|| !new RegExp(`^${prefix}-[a-f0-9]{64}$`).test(item.result_id)
) {
throw new AdvancedLaboratoryContractError(
"Индекс LAB.result_id: нарушена идентичность результата.",
);
}
if (typeof item.created_at_utc !== "string" || !item.created_at_utc.trim()) {
throw new AdvancedLaboratoryContractError(
"Индекс LAB.created_at_utc: ожидалась строка.",
);
}
exactString(item.access, "read-only", "Индекс LAB.item.access");
return {
workId: item.work_id,
resultId: item.result_id,
createdAtUtc: item.created_at_utc,
};
}
export async function fetchAdvancedLaboratoryIndex({
fetcher = fetch,
signal,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {}): Promise<readonly AdvancedLaboratoryIndexItem[]> {
const response = await fetcher("/api/v1/laboratory/advanced-index", {
method: "GET",
headers: { Accept: "application/json" },
signal,
});
if (!response.ok) {
throw new AdvancedLaboratoryContractError(
`Индекс LAB недоступен: HTTP ${response.status}.`,
);
}
const payload = objectValue(await response.json(), "Индекс LAB");
exactString(
payload.schema_version,
"missioncore.laboratory-advanced-index/v1",
"Индекс LAB.schema_version",
);
exactString(payload.access, "read-only", "Индекс LAB.access");
if (!Array.isArray(payload.items) || payload.items.length > WORK_IDS.length) {
throw new AdvancedLaboratoryContractError(
"Индекс LAB.items: нарушен размер каталога.",
);
}
const items = payload.items.map(parseIndexItem);
if (new Set(items.map((item) => item.workId)).size !== items.length) {
throw new AdvancedLaboratoryContractError(
"Индекс LAB.items: лабораторная работа продублирована.",
);
}
return items;
}
export function advancedLaboratoryResultAvailable(
workId: AdvancedLaboratoryWorkId,
results: AdvancedLaboratoryResults,
): boolean {
return workId === "e31-source-binding" ? results.e31 !== null
: workId === "e32-track-geometry" ? results.e32 !== null
: workId === "e33-worker-shadow" ? results.e33 !== null
: workId === "e34-temporal-layer" ? results.e34 !== null
: workId === "e35-degradation-recovery" ? results.e35 !== null
: workId === "e37-ravnoves-acceptance" ? results.e37 !== null
: workId === "e38-perception-baseline" ? results.e38 !== null
: workId === "e39-perception-refinement" ? results.e39 !== null
: results.e40 !== null;
}
export async function fetchAdvancedLaboratoryResult(
workId: AdvancedLaboratoryWorkId,
{
fetcher = fetch,
signal,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {},
): Promise<AdvancedLaboratoryResults> {
const results = emptyAdvancedLaboratoryResults();
if (workId === "e31-source-binding") {
results.e31 = await fetchOne(
"/api/v1/laboratory/e31/results?limit=1",
parseE31,
fetcher,
signal,
);
} else if (workId === "e32-track-geometry") {
results.e32 = await fetchOne(
"/api/v1/laboratory/e32/results?limit=1",
parseE32,
fetcher,
signal,
);
} else if (workId === "e33-worker-shadow") {
results.e33 = await fetchOne(
"/api/v1/laboratory/e33/results?limit=1",
parseE33,
fetcher,
signal,
);
} else if (workId === "e34-temporal-layer") {
results.e34 = await fetchE34TemporalLayerResult({ fetcher, signal });
} else if (workId === "e35-degradation-recovery") {
results.e35 = await fetchE35DegradationRecoveryResult({ fetcher, signal });
} else if (workId === "e37-ravnoves-acceptance") {
results.e37 = await fetchOne(
"/api/v1/laboratory/e37/results?limit=1",
parseE37,
fetcher,
signal,
);
} else if (workId === "e38-perception-baseline") {
results.e38 = await fetchOne(
"/api/v1/laboratory/e38/results?limit=1",
parseE38,
fetcher,
signal,
);
} else if (workId === "e39-perception-refinement") {
results.e39 = await fetchOne(
"/api/v1/laboratory/e39/results?limit=1",
parseE39,
fetcher,
signal,
);
} else {
results.e40 = await fetchE40ProductGateResult({ fetcher, signal });
}
if (!advancedLaboratoryResultAvailable(workId, results)) {
throw new AdvancedLaboratoryContractError(
"Выбранная лабораторная работа не прошла серверную проверку.",
);
}
return results;
}
@@ -256,7 +256,7 @@ export class AdvancedLaboratoryContractError extends Error {
}
}
type LaboratoryFetch = (
export type LaboratoryFetch = (
input: RequestInfo | URL,
init?: RequestInit,
) => Promise<Response>;
@@ -402,7 +402,7 @@ function parseCatalog<T>(
return value.items.length ? parseItem(value.items[0]) : null;
}
function parseE31(value: unknown): E31LaboratoryResult {
export function parseE31(value: unknown): E31LaboratoryResult {
const item = record(value, "E31");
const metrics = record(item.metrics, "E31.metrics");
diagnosticAuthority(item.authority, "E31.authority");
@@ -437,7 +437,7 @@ function parseE31(value: unknown): E31LaboratoryResult {
};
}
function parseE32(value: unknown): E32LaboratoryResult {
export function parseE32(value: unknown): E32LaboratoryResult {
const item = record(value, "E32");
const metrics = record(item.metrics, "E32.metrics");
diagnosticAuthority(item.authority, "E32.authority");
@@ -470,7 +470,7 @@ function parseE32(value: unknown): E32LaboratoryResult {
};
}
function parseE33(value: unknown): E33LaboratoryResult {
export function parseE33(value: unknown): E33LaboratoryResult {
const item = record(value, "E33");
const worker = record(item.worker, "E33.worker");
const metrics = record(item.metrics, "E33.metrics");
@@ -521,7 +521,7 @@ function parseE33(value: unknown): E33LaboratoryResult {
};
}
function parseE37(value: unknown): E37AcceptanceContractResult {
export function parseE37(value: unknown): E37AcceptanceContractResult {
const item = record(value, "E37");
const metrics = record(item.metrics, "E37.metrics");
const distributions = record(
@@ -718,7 +718,7 @@ function parseE38Dimension(
};
}
function parseE38(value: unknown): E38PerceptionBaselineResult {
export function parseE38(value: unknown): E38PerceptionBaselineResult {
const item = record(value, "E38");
const metrics = record(item.metrics, "E38.metrics");
const dimensions = record(metrics.dimensions, "E38.metrics.dimensions");
@@ -810,7 +810,7 @@ function parseDevelopmentDimension(
};
}
function parseE39(value: unknown): E39PerceptionRefinementResult {
export 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");
@@ -944,7 +944,7 @@ function parseE39(value: unknown): E39PerceptionRefinementResult {
};
}
async function fetchOne<T>(
export async function fetchOne<T>(
path: string,
parser: (value: unknown) => T,
fetcher: LaboratoryFetch,
@@ -59,6 +59,8 @@ export interface E30ReviewItem {
materialization: {
framePointCount: number;
projectedPointCount: number;
projectionWidth: number;
projectionHeight: number;
candidatePointCount: number;
selectedPointCount: number;
rejectedCandidatePointCount: number;
@@ -428,6 +430,14 @@ function parseItem(value: unknown): E30ReviewItem {
materialized.projected_point_count,
"E30 materialization.projected_point_count",
),
projectionWidth: integerValue(
materialized.projection_width,
"E30 materialization.projection_width",
),
projectionHeight: integerValue(
materialized.projection_height,
"E30 materialization.projection_height",
),
candidatePointCount: integerValue(
materialized.candidate_point_count,
"E30 materialization.candidate_point_count",
@@ -0,0 +1,538 @@
export const E40_CASE_DIMENSIONS = [
"presence",
"geometry_association",
"freshness",
] as const;
export type E40CaseDimension = (typeof E40_CASE_DIMENSIONS)[number];
export type E40CaseSeverity = "high" | "medium" | "standard";
export type E40CaseStratum =
| "agree"
| "camera-only"
| "conflict"
| "geometry-only"
| "unknown";
export type E40PredictionBasis =
| "camera-only-softmax"
| "fixed-stratum-policy";
export interface E40CaseState {
presence: "background-or-noise" | "object-present" | "occupied-environment";
geometryAssociation:
| "independent-occupied"
| "insufficient-support"
| "object-associated"
| "rejected-nonobject"
| "unknown";
freshness: "current" | "stale" | "unavailable";
}
export interface E40ErrorCase {
itemId: string;
sequence: number;
sourceFrameIndex: number;
sourceStratum: E40CaseStratum;
severity: E40CaseSeverity;
presenceConfidence: number;
predictionBasis: E40PredictionBasis;
reference: E40CaseState;
prediction: E40CaseState;
mismatchedDimensions: readonly E40CaseDimension[];
access: "read-only";
}
export interface E40CaseCatalog {
resultId: string;
materializationId: string;
items: readonly E40ErrorCase[];
total: number;
truncated: boolean;
access: "read-only";
}
export type E40OperatorVerdict = "confirmed-error" | "rejected-error";
export interface E40OperatorDecision {
itemId: string;
verdict: E40OperatorVerdict;
revision: number;
idempotencyKey: string;
decidedAtUtc: string;
eventId: string;
}
export interface E40OperatorReview {
reviewId: string;
resultId: string;
materializationId: string;
caseCatalogSha256: string;
itemSetSha256: string;
itemCount: number;
reviewerId: string;
revision: number;
decisions: readonly E40OperatorDecision[];
reviewedItemCount: number;
remainingItemCount: number;
updatedAtUtc: string | null;
access: "review-write";
}
export class E40CaseReviewContractError extends Error {
constructor(message: string) {
super(message);
this.name = "E40CaseReviewContractError";
}
}
type LaboratoryFetch = (
input: RequestInfo | URL,
init?: RequestInit,
) => Promise<Response>;
function record(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new E40CaseReviewContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function stringValue(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new E40CaseReviewContractError(`${label}: ожидалась строка.`);
}
return value;
}
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 E40CaseReviewContractError(`${label}: неверный content id.`);
}
return parsed;
}
function sha256Value(value: unknown, label: string): string {
const parsed = stringValue(value, label);
if (!/^[a-f0-9]{64}$/.test(parsed)) {
throw new E40CaseReviewContractError(`${label}: неверный SHA-256.`);
}
return parsed;
}
function integerValue(value: unknown, label: string): number {
if (
typeof value !== "number"
|| !Number.isSafeInteger(value)
|| value < 0
) {
throw new E40CaseReviewContractError(`${label}: ожидалось целое число.`);
}
return value;
}
function nullableString(value: unknown, label: string): string | null {
if (value === null) return null;
return stringValue(value, label);
}
function probability(value: unknown, label: string): number {
if (
typeof value !== "number"
|| !Number.isFinite(value)
|| value < 0
|| value > 1
) {
throw new E40CaseReviewContractError(`${label}: ожидалась вероятность.`);
}
return value;
}
function exactString<T extends string>(
value: unknown,
expected: T,
label: string,
): T {
if (value !== expected) {
throw new E40CaseReviewContractError(`${label}: неверное значение.`);
}
return expected;
}
function enumValue<T extends string>(
value: unknown,
allowed: readonly T[],
label: string,
): T {
if (typeof value !== "string" || !allowed.includes(value as T)) {
throw new E40CaseReviewContractError(`${label}: значение не поддерживается.`);
}
return value as T;
}
function parseState(value: unknown, label: string): E40CaseState {
const source = record(value, label);
return {
presence: enumValue(
source.presence,
[
"background-or-noise",
"object-present",
"occupied-environment",
],
`${label}.presence`,
),
geometryAssociation: enumValue(
source.geometry_association,
[
"independent-occupied",
"insufficient-support",
"object-associated",
"rejected-nonobject",
"unknown",
],
`${label}.geometry_association`,
),
freshness: enumValue(
source.freshness,
["current", "stale", "unavailable"],
`${label}.freshness`,
),
};
}
function parseCase(value: unknown, index: number): E40ErrorCase {
const label = `E40 case[${index}]`;
const source = record(value, label);
if (!Array.isArray(source.mismatched_dimensions)) {
throw new E40CaseReviewContractError(
`${label}.mismatched_dimensions: ожидался массив.`,
);
}
const mismatchedDimensions = source.mismatched_dimensions.map(
(dimension, dimensionIndex) => enumValue(
dimension,
E40_CASE_DIMENSIONS,
`${label}.mismatched_dimensions[${dimensionIndex}]`,
),
);
if (!mismatchedDimensions.length) {
throw new E40CaseReviewContractError(`${label}: кейс не содержит ошибки.`);
}
return {
itemId: contentId(source.item_id, "e30-review-item", `${label}.item_id`),
sequence: integerValue(source.sequence, `${label}.sequence`),
sourceFrameIndex: integerValue(
source.source_frame_index,
`${label}.source_frame_index`,
),
sourceStratum: enumValue(
source.source_stratum,
["agree", "camera-only", "conflict", "geometry-only", "unknown"],
`${label}.source_stratum`,
),
severity: enumValue(
source.severity,
["high", "medium", "standard"],
`${label}.severity`,
),
presenceConfidence: probability(
source.presence_confidence,
`${label}.presence_confidence`,
),
predictionBasis: enumValue(
source.prediction_basis,
["camera-only-softmax", "fixed-stratum-policy"],
`${label}.prediction_basis`,
),
reference: parseState(source.reference, `${label}.reference`),
prediction: parseState(source.prediction, `${label}.prediction`),
mismatchedDimensions,
access: exactString(source.access, "read-only", `${label}.access`),
};
}
export function parseE40CaseCatalog(payload: unknown): E40CaseCatalog {
const source = record(payload, "E40 case catalog");
exactString(
source.schema_version,
"missioncore.laboratory-e40-case-catalog/v1",
"E40 case catalog.schema_version",
);
if (!Array.isArray(source.items) || source.items.length > 64) {
throw new E40CaseReviewContractError(
"E40 case catalog.items: нарушена граница.",
);
}
return {
resultId: contentId(
source.result_id,
"e40-perception-product-gate",
"E40 case catalog.result_id",
),
materializationId: contentId(
source.materialization_id,
"e30-materialization",
"E40 case catalog.materialization_id",
),
items: source.items.map(parseCase),
total: integerValue(source.total, "E40 case catalog.total"),
truncated: typeof source.truncated === "boolean"
? source.truncated
: (() => {
throw new E40CaseReviewContractError(
"E40 case catalog.truncated: ожидался boolean.",
);
})(),
access: exactString(
source.access,
"read-only",
"E40 case catalog.access",
),
};
}
function parseOperatorDecision(
value: unknown,
index: number,
): E40OperatorDecision {
const label = `E40 operator decision[${index}]`;
const source = record(value, label);
return {
itemId: contentId(source.item_id, "e30-review-item", `${label}.item_id`),
verdict: enumValue(
source.verdict,
["confirmed-error", "rejected-error"],
`${label}.verdict`,
),
revision: integerValue(source.revision, `${label}.revision`),
idempotencyKey: stringValue(
source.idempotency_key,
`${label}.idempotency_key`,
),
decidedAtUtc: stringValue(
source.decided_at_utc,
`${label}.decided_at_utc`,
),
eventId: contentId(
source.event_id,
"e40-operator-verdict",
`${label}.event_id`,
),
};
}
export function parseE40OperatorReview(
payload: unknown,
): E40OperatorReview {
const source = record(payload, "E40 operator review");
exactString(
source.schema_version,
"missioncore.e40-operator-review/v1",
"E40 operator review.schema_version",
);
exactString(
source.protocol,
"sealed-error-adjudication/v1",
"E40 operator review.protocol",
);
const binding = record(source.source, "E40 operator review.source");
if (!Array.isArray(source.decisions) || source.decisions.length > 64) {
throw new E40CaseReviewContractError(
"E40 operator review.decisions: нарушена граница.",
);
}
const decisions = source.decisions.map(parseOperatorDecision);
const reviewedItemCount = integerValue(
source.reviewed_item_count,
"E40 operator review.reviewed_item_count",
);
const remainingItemCount = integerValue(
source.remaining_item_count,
"E40 operator review.remaining_item_count",
);
const itemCount = integerValue(
binding.item_count,
"E40 operator review.source.item_count",
);
if (
reviewedItemCount !== decisions.length
|| reviewedItemCount + remainingItemCount !== itemCount
|| new Set(decisions.map((decision) => decision.itemId)).size
!== decisions.length
) {
throw new E40CaseReviewContractError(
"E40 operator review: счётчики решений несовместимы.",
);
}
return {
reviewId: contentId(
source.review_id,
"e40-operator-review",
"E40 operator review.review_id",
),
resultId: contentId(
binding.result_id,
"e40-perception-product-gate",
"E40 operator review.source.result_id",
),
materializationId: contentId(
binding.materialization_id,
"e30-materialization",
"E40 operator review.source.materialization_id",
),
caseCatalogSha256: sha256Value(
binding.case_catalog_sha256,
"E40 operator review.source.case_catalog_sha256",
),
itemSetSha256: sha256Value(
binding.item_set_sha256,
"E40 operator review.source.item_set_sha256",
),
itemCount,
reviewerId: stringValue(
source.reviewer_id,
"E40 operator review.reviewer_id",
),
revision: integerValue(
source.revision,
"E40 operator review.revision",
),
decisions,
reviewedItemCount,
remainingItemCount,
updatedAtUtc: nullableString(
source.updated_at_utc,
"E40 operator review.updated_at_utc",
),
access: exactString(
source.access,
"review-write",
"E40 operator review.access",
),
};
}
export async function fetchE40CaseCatalog(
resultId: string,
{
fetcher = fetch,
signal,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {},
): Promise<E40CaseCatalog> {
const response = await fetcher(
`/api/v1/laboratory/e40/results/${encodeURIComponent(resultId)}/cases?limit=48`,
{
method: "GET",
headers: { Accept: "application/json" },
signal,
},
);
if (!response.ok) {
throw new E40CaseReviewContractError(
`Case-review E40 недоступен: HTTP ${response.status}.`,
);
}
const catalog = parseE40CaseCatalog(await response.json());
if (catalog.resultId !== resultId) {
throw new E40CaseReviewContractError(
"Case-review E40 вернул другой результат.",
);
}
return catalog;
}
export async function fetchE40OperatorReview(
resultId: string,
{
reviewerId = "DC",
fetcher = fetch,
signal,
}: {
reviewerId?: string;
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {},
): Promise<E40OperatorReview> {
const response = await fetcher(
`/api/v1/laboratory/e40/results/${encodeURIComponent(resultId)}`
+ `/operator-review?reviewer_id=${encodeURIComponent(reviewerId)}`,
{
method: "GET",
headers: { Accept: "application/json" },
signal,
},
);
if (!response.ok) {
throw new E40CaseReviewContractError(
`Операторская проверка E40 недоступна: HTTP ${response.status}.`,
);
}
const review = parseE40OperatorReview(await response.json());
if (review.resultId !== resultId || review.reviewerId !== reviewerId) {
throw new E40CaseReviewContractError(
"Операторская проверка E40 вернула другой источник.",
);
}
return review;
}
export async function saveE40OperatorVerdict(
resultId: string,
itemId: string,
{
reviewerId = "DC",
expectedRevision,
idempotencyKey,
verdict,
fetcher = fetch,
signal,
}: {
reviewerId?: string;
expectedRevision: number;
idempotencyKey: string;
verdict: E40OperatorVerdict;
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
},
): Promise<E40OperatorReview> {
const response = await fetcher(
`/api/v1/laboratory/e40/results/${encodeURIComponent(resultId)}`
+ `/operator-review/decisions/${encodeURIComponent(itemId)}`,
{
method: "PUT",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
reviewer_id: reviewerId,
expected_revision: expectedRevision,
idempotency_key: idempotencyKey,
verdict,
}),
signal,
},
);
if (!response.ok) {
throw new E40CaseReviewContractError(
`Решение E40 не сохранено: HTTP ${response.status}.`,
);
}
const review = parseE40OperatorReview(await response.json());
if (
review.resultId !== resultId
|| review.reviewerId !== reviewerId
|| !review.decisions.some(
(decision) => decision.itemId === itemId
&& decision.verdict === verdict,
)
) {
throw new E40CaseReviewContractError(
"Решение E40 не подтверждено сервером.",
);
}
return review;
}
+1
View File
@@ -3,6 +3,7 @@
@import "./styles/shell.css";
@import "./styles/workspaces.css";
@import "./styles/laboratory.css";
@import "./styles/e40-case-review.css";
@import "./styles/laboratory-reporting.css";
@import "./styles/e34-temporal-layer.css";
@import "./styles/e35-degradation-recovery.css";
@@ -0,0 +1,297 @@
.e40-case-review {
display: grid;
height: clamp(38rem, 66vh, 52rem);
min-height: 38rem;
grid-template-rows: auto minmax(0, 1fr);
gap: 0.65rem;
border-radius: var(--nodedc-radius-option);
background: var(--nodedc-glass-panel-bg-soft);
padding: 0.75rem;
}
.e40-case-review__header {
display: flex;
min-width: 0;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
}
.e40-case-review__header > div {
display: grid;
min-width: 0;
gap: 0.18rem;
}
.e40-case-review__header strong {
color: var(--nodedc-text-primary);
font-size: 0.78rem;
}
.e40-case-review__header small {
color: var(--nodedc-text-muted);
font-size: 0.56rem;
line-height: 1.45;
}
.e40-case-review__viewer {
display: grid;
min-width: 0;
min-height: 0;
}
.e40-case-review__viewer > .laboratory-evidence-viewer {
min-height: 0;
}
.e40-case-review__state {
display: flex;
min-height: 16rem;
align-items: center;
justify-content: center;
gap: 0.55rem;
border-radius: var(--nodedc-radius-option);
background: var(--nodedc-canvas);
color: var(--nodedc-text-muted);
font-size: 0.62rem;
}
.e40-case-review__controls {
display: flex;
flex: 1;
min-width: 0;
align-items: center;
gap: 0.45rem;
}
.e40-case-review__viewer
.laboratory-evidence-viewer__controls:has(.e40-case-review__controls) {
right: 0.6rem;
left: 0.6rem;
}
.e40-case-review__pagination {
display: flex;
flex: none;
gap: 0.35rem;
}
.e40-case-review__glass-button {
background: var(--nodedc-floating-surface);
backdrop-filter: blur(var(--nodedc-blur-control));
}
.e40-case-review__controls .nodedc-select-anchor,
.e40-case-review__controls .nodedc-select {
width: clamp(15rem, 30vw, 26rem);
}
.e40-case-review__select .nodedc-select__value,
.e40-case-review__select .nodedc-select__toggle,
.e40-case-review__select-menu {
background: var(--nodedc-floating-surface);
backdrop-filter: blur(var(--nodedc-blur-control));
}
.e40-case-review__layer-button {
margin-left: auto;
--nodedc-button-bg: var(--nodedc-floating-surface);
--nodedc-button-color: var(--nodedc-text-secondary);
backdrop-filter: blur(var(--nodedc-blur-control));
}
.e40-case-review__layer-button[data-active="true"] {
--nodedc-button-bg: var(--nodedc-glass-control-active);
--nodedc-button-color: var(--nodedc-glass-control-active-text);
}
.e40-case-review__telemetry {
position: absolute;
z-index: 3;
left: 0.6rem;
bottom: 0.6rem;
display: grid;
width: min(46rem, calc(100% - 1.2rem));
grid-template-columns: minmax(0, 1fr) minmax(14rem, 18rem);
align-items: end;
gap: 0.75rem;
border-radius: var(--nodedc-radius-control-compact);
background: var(--nodedc-floating-surface);
padding: 0.55rem 0.65rem;
color: var(--nodedc-text-secondary);
backdrop-filter: blur(var(--nodedc-blur-control));
pointer-events: none;
}
.e40-case-review__telemetry-content {
display: grid;
min-width: 0;
gap: 0.45rem;
}
.e40-case-review__question {
display: grid;
gap: 0.15rem;
border-radius: var(--nodedc-radius-control-compact);
background: var(--nodedc-glass-control-bg);
padding: 0.45rem 0.55rem;
}
.e40-case-review__question > span {
color: var(--nodedc-text-muted);
font-size: 0.46rem;
font-weight: 700;
letter-spacing: 0.12em;
}
.e40-case-review__question > strong {
color: var(--nodedc-text-primary);
font-size: 0.62rem;
line-height: 1.35;
}
.e40-case-review__question > small {
color: var(--nodedc-text-muted);
font-size: 0.49rem;
line-height: 1.35;
}
.e40-case-review__telemetry-content > span,
.e40-case-review__telemetry dt {
color: var(--nodedc-text-muted);
font-size: 0.49rem;
}
.e40-case-review__telemetry dl {
display: grid;
gap: 0.35rem;
margin: 0;
}
.e40-case-review__telemetry dl > div {
display: grid;
grid-template-columns: 6.2rem minmax(0, 1fr);
align-items: start;
gap: 0.45rem;
}
.e40-case-review__telemetry dt,
.e40-case-review__telemetry dd {
margin: 0;
}
.e40-case-review__telemetry dd {
display: grid;
min-width: 0;
gap: 0.12rem;
font-size: 0.55rem;
}
.e40-case-review__telemetry dd span,
.e40-case-review__telemetry dd strong {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.e40-case-review__telemetry dd strong {
color: var(--nodedc-text-primary);
}
.e40-case-review__verdict {
display: grid;
justify-items: end;
gap: 0.28rem;
pointer-events: auto;
}
.e40-case-review__verdict > span {
color: var(--nodedc-text-muted);
font-size: 0.49rem;
white-space: nowrap;
}
.e40-case-review__verdict-error {
max-width: 12rem;
color: rgb(var(--nodedc-danger-rgb));
font-size: 0.48rem;
line-height: 1.35;
text-align: right;
}
.e40-case-review__verdict-buttons {
display: flex;
gap: 0.2rem;
border-radius: 999px;
background: var(--nodedc-glass-control-bg);
padding: 0.18rem;
backdrop-filter: blur(var(--nodedc-blur-control));
}
.e40-case-review__verdict-guide {
display: grid;
justify-self: stretch;
gap: 0.26rem;
}
.e40-case-review__verdict-guide > span {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: start;
gap: 0.28rem;
color: var(--nodedc-text-muted);
font-size: 0.49rem;
line-height: 1.35;
}
.e40-case-review__verdict-guide > span[data-verdict="confirmed"] > svg {
color: rgb(var(--nodedc-success-rgb));
}
.e40-case-review__verdict-guide > span[data-verdict="rejected"] > svg {
color: rgb(var(--nodedc-danger-rgb));
}
.e40-case-review__commit-note {
max-width: 18rem;
color: var(--nodedc-text-muted);
font-size: 0.46rem;
line-height: 1.3;
text-align: right;
}
.e40-case-review__verdict-button {
width: 2rem;
height: 2rem;
min-width: 2rem;
background: transparent;
}
.e40-case-review__verdict-button[data-active="true"][data-verdict="confirmed"] {
background: rgb(var(--nodedc-success-rgb) / 0.12);
color: rgb(var(--nodedc-success-rgb));
}
.e40-case-review__verdict-button[data-active="true"][data-verdict="rejected"] {
background: rgb(var(--nodedc-danger-rgb) / 0.12);
color: rgb(var(--nodedc-danger-rgb));
}
@media (max-width: 900px) {
.e40-case-review {
height: 42rem;
}
.e40-case-review__controls .nodedc-select-anchor,
.e40-case-review__controls .nodedc-select {
width: clamp(10rem, 42vw, 18rem);
}
.e40-case-review__telemetry {
grid-template-columns: minmax(0, 1fr);
}
.e40-case-review__verdict {
justify-items: start;
}
}
@@ -460,7 +460,7 @@ export function E30EvidencePointCloud({ detail }: E30EvidencePointCloudProps) {
</span>
<span data-point="rejected">Отклонено · {rejectedCount}</span>
<span data-point="selected">
Выбрано E29 · {detail.selected.pointsMapXyzM.length}
Проверяемый кластер E29 · {detail.selected.pointsMapXyzM.length}
</span>
</div>
</div>
@@ -206,7 +206,7 @@ export function E30EvidenceProjection({
</span>
<span data-point="rejected">Кандидаты · {rejectedCount}</span>
<span data-point="selected">
Выбрано E29 · {detail.selected.pointsMapXyzM.length}
Проверяемый кластер E29 · {detail.selected.pointsMapXyzM.length}
</span>
</div>
) : null}
@@ -1,6 +1,11 @@
import type { ComponentType } from "react";
import type { LaboratoryOption } from "../../components/laboratory/LaboratoryPresentation";
import {
isAdvancedLaboratoryWorkId,
type AdvancedLaboratoryIndexItem,
type AdvancedLaboratoryWorkId,
} from "../../core/laboratory/advancedIndex";
import type { AdvancedLaboratoryResults } from "../../core/laboratory/advancedResults";
import type { ObservationSessionSummary } from "../../core/observation/sessionArchive";
import type { WorkspaceRendererProps } from "../contracts";
@@ -15,97 +20,29 @@ import { E39Result } from "./E39Result";
import { E40Result } from "./E40Result";
import { RecordedReplayEvidence } from "./RecordedReplayEvidence";
export type AdvancedLaboratoryWorkId =
| "e31-source-binding"
| "e32-track-geometry"
| "e33-worker-shadow"
| "e34-temporal-layer"
| "e35-degradation-recovery"
| "e37-ravnoves-acceptance"
| "e38-perception-baseline"
| "e39-perception-refinement"
| "e40-perception-product-gate";
export { isAdvancedLaboratoryWorkId };
export type { AdvancedLaboratoryWorkId };
type LaboratoryWorkspaceProps = WorkspaceRendererProps & {
SpatialView: ComponentType<WorkspaceRendererProps>;
};
export function isAdvancedLaboratoryWorkId(
value: string,
): value is AdvancedLaboratoryWorkId {
return (
value === "e31-source-binding"
|| value === "e32-track-geometry"
|| value === "e33-worker-shadow"
|| value === "e34-temporal-layer"
|| value === "e35-degradation-recovery"
|| value === "e37-ravnoves-acceptance"
|| value === "e38-perception-baseline"
|| value === "e39-perception-refinement"
|| value === "e40-perception-product-gate"
);
}
export function advancedLaboratoryWorkOptions(
results: AdvancedLaboratoryResults,
sourceSessions: ReadonlyMap<string, ObservationSessionSummary>,
index: readonly AdvancedLaboratoryIndexItem[],
): readonly LaboratoryOption<AdvancedLaboratoryWorkId>[] {
const options: LaboratoryOption<AdvancedLaboratoryWorkId>[] = [];
if (results.e31 && sourceSessions.has(results.e31.sourceSessionId)) {
options.push({
id: "e31-source-binding",
label: "LAB E31 · source binding",
});
}
if (results.e32 && sourceSessions.has(results.e32.sourceSessionId)) {
options.push({
id: "e32-track-geometry",
label: "LAB E32 · TrackGeometry v1",
});
}
if (results.e33 && sourceSessions.has(results.e33.sourceSessionId)) {
options.push({
id: "e33-worker-shadow",
label: "LAB E33 · worker shadow 1×",
});
}
if (results.e34) {
options.push({
id: "e34-temporal-layer",
label: "LAB E34 · temporal occupied/unknown",
});
}
if (results.e35) {
options.push({
id: "e35-degradation-recovery",
label: "LAB E35 · degradation recovery",
});
}
if (results.e37) {
options.push({
id: "e37-ravnoves-acceptance",
label: "LAB E37 · RAVNOVES00 acceptance R0",
});
}
if (results.e38) {
options.push({
id: "e38-perception-baseline",
label: "LAB E38 · perception quality R1",
});
}
if (results.e39) {
options.push({
id: "e39-perception-refinement",
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;
const available = new Set(index.map(({ workId }) => workId));
const options: readonly LaboratoryOption<AdvancedLaboratoryWorkId>[] = [
{ id: "e31-source-binding", label: "LAB E31 · source binding" },
{ id: "e32-track-geometry", label: "LAB E32 · TrackGeometry v1" },
{ id: "e33-worker-shadow", label: "LAB E33 · worker shadow 1×" },
{ id: "e34-temporal-layer", label: "LAB E34 · temporal occupied/unknown" },
{ id: "e35-degradation-recovery", label: "LAB E35 · degradation recovery" },
{ id: "e37-ravnoves-acceptance", label: "LAB E37 · RAVNOVES00 acceptance R0" },
{ id: "e38-perception-baseline", label: "LAB E38 · perception quality R1" },
{ id: "e39-perception-refinement", label: "LAB E39 · perception refinement R1" },
{ id: "e40-perception-product-gate", label: "LAB E40 · leakage-resistant product gate" },
];
return options.filter(({ id }) => available.has(id));
}
export function advancedLaboratorySourceSession(
@@ -0,0 +1,550 @@
import { useEffect, useState } from "react";
import {
Button,
Icon,
IconButton,
Select,
StatusBadge,
} from "@nodedc/ui-react";
import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer";
import {
fetchE30ReviewItemDetail,
type E30ReviewItemDetail,
} from "../../core/laboratory/e30Review";
import {
fetchE40CaseCatalog,
fetchE40OperatorReview,
saveE40OperatorVerdict,
type E40CaseCatalog,
type E40CaseDimension,
type E40CaseSeverity,
type E40CaseState,
type E40CaseStratum,
type E40ErrorCase,
type E40OperatorReview,
type E40OperatorVerdict,
} from "../../core/laboratory/e40CaseReview";
import { E30EvidencePointCloud } from "../E30EvidencePointCloud";
import { E30EvidenceProjection } from "../E30EvidenceProjection";
type EvidenceMode = "camera" | "3d";
const SEVERITY_LABELS: Record<E40CaseSeverity, string> = {
high: "Высокий приоритет",
medium: "Средний приоритет",
standard: "Стандартный приоритет",
};
const STRATUM_LABELS: Record<E40CaseStratum, string> = {
agree: "согласованные источники",
"camera-only": "только камера",
conflict: "конфликт источников",
"geometry-only": "только геометрия",
unknown: "неопределённый источник",
};
const DIMENSION_LABELS: Record<E40CaseDimension, string> = {
presence: "Присутствие",
geometry_association: "Связь с геометрией",
freshness: "Актуальность",
};
const VALUE_LABELS: Readonly<Record<string, string>> = {
"background-or-noise": "Фон или шум",
"object-present": "Объект присутствует",
"occupied-environment": "Занятая среда",
"independent-occupied": "Независимая геометрия",
"insufficient-support": "Недостаточно опоры",
"object-associated": "Связано с объектом",
"rejected-nonobject": "Не объект",
unknown: "Не определено",
current: "Актуально",
stale: "Устарело",
unavailable: "Недоступно",
};
function stateValue(
state: E40CaseState,
dimension: E40CaseDimension,
): string {
if (dimension === "presence") return state.presence;
if (dimension === "geometry_association") {
return state.geometryAssociation;
}
return state.freshness;
}
function caseLabel(item: E40ErrorCase): string {
const dimensions = item.mismatchedDimensions
.map((dimension) => DIMENSION_LABELS[dimension])
.join(" + ");
return `${SEVERITY_LABELS[item.severity]} · кадр ${item.sourceFrameIndex} · ${dimensions}`;
}
function reviewPrompt(
item: E40ErrorCase,
detail: E30ReviewItemDetail,
): {
question: string;
evidence: string;
confirm: string;
reject: string;
} {
const hasSelectedPoints = detail.selected.pointsMapXyzM.length > 0;
const subject = hasSelectedPoints
? "Белый LiDAR-кластер"
: "Выделенная область";
const evidence = hasSelectedPoints
? detail.locatorKind === "geometry-only-cluster"
? (
"Белые точки — самостоятельный кластер занятой геометрии E29 без "
+ "класса. Это проверяемая LiDAR-опора, а не отметки ошибки."
)
: (
"Белые точки — LiDAR-опора, которую E29 связал с наблюдением. "
+ "Это проверяемые исходные точки, а не отметки ошибки."
)
: (
"Белого LiDAR-кластера в этом кейсе нет: решение принимается по "
+ "кадру и рамке наблюдения."
);
if (item.reference.presence === "object-present") {
return {
question: `${subject} относится к отдельному видимому объекту?`,
evidence,
confirm: (
"Да — объект виден; эталон верен, расхождение E40 подтверждается."
),
reject: (
"Нет — это фон или часть среды; предполагаемая ошибка отклоняется."
),
};
}
if (item.reference.presence === "background-or-noise") {
return {
question: `${subject} является фоном или шумом, а не объектом?`,
evidence,
confirm: (
"Да — это фон или шум; эталон верен, расхождение E40 подтверждается."
),
reject: (
"Нет — отдельный объект есть; предполагаемая ошибка отклоняется."
),
};
}
return {
question: "Проверяемый эталон точнее описывает кадр, чем решение E40?",
evidence,
confirm: "Да — эталон верен, расхождение E40 подтверждается.",
reject: "Нет — эталон спорен, предполагаемая ошибка отклоняется.",
};
}
export function E40CaseReview({ resultId }: { resultId: string }) {
const [catalog, setCatalog] = useState<E40CaseCatalog | null>(null);
const [selectedItemId, setSelectedItemId] = useState<string | null>(null);
const [detail, setDetail] = useState<E30ReviewItemDetail | null>(null);
const [review, setReview] = useState<E40OperatorReview | null>(null);
const [catalogLoading, setCatalogLoading] = useState(true);
const [detailLoading, setDetailLoading] = useState(false);
const [verdictPending, setVerdictPending] = useState(false);
const [error, setError] = useState<string | null>(null);
const [reviewError, setReviewError] = useState<string | null>(null);
const [mode, setMode] = useState<EvidenceMode>("camera");
const [pointLayerVisible, setPointLayerVisible] = useState(true);
const [expanded, setExpanded] = useState(false);
useEffect(() => {
const controller = new AbortController();
setCatalogLoading(true);
setCatalog(null);
setDetail(null);
setReview(null);
setSelectedItemId(null);
setError(null);
setReviewError(null);
void Promise.all([
fetchE40CaseCatalog(resultId, { signal: controller.signal }),
fetchE40OperatorReview(resultId, { signal: controller.signal }),
]).then(([nextCatalog, nextReview]) => {
if (
nextReview.materializationId !== nextCatalog.materializationId
|| nextReview.reviewedItemCount > nextCatalog.total
) {
throw new Error(
"Операторская проверка E40 не совпадает с каталогом ошибок.",
);
}
setCatalog(nextCatalog);
setReview(nextReview);
setSelectedItemId(nextCatalog.items[0]?.itemId ?? null);
}).catch((caught: unknown) => {
if (controller.signal.aborted) return;
setError(
caught instanceof Error
? caught.message
: "Case-review E40 недоступен.",
);
}).finally(() => {
if (!controller.signal.aborted) setCatalogLoading(false);
});
return () => controller.abort();
}, [resultId]);
useEffect(() => {
if (!catalog || !selectedItemId) {
setDetail(null);
return;
}
const controller = new AbortController();
setDetailLoading(true);
setDetail(null);
setError(null);
void fetchE30ReviewItemDetail(
catalog.materializationId,
selectedItemId,
{ signal: controller.signal },
).then((next) => {
setDetail(next);
setMode(next.cameraFrame ? "camera" : "3d");
}).catch((caught: unknown) => {
if (controller.signal.aborted) return;
setError(
caught instanceof Error
? caught.message
: "Визуальное доказательство кейса недоступно.",
);
}).finally(() => {
if (!controller.signal.aborted) setDetailLoading(false);
});
return () => controller.abort();
}, [catalog, selectedItemId]);
const selectedCase = catalog?.items.find(
(item) => item.itemId === selectedItemId,
) ?? null;
const selectedIndex = selectedCase && catalog
? catalog.items.findIndex((item) => item.itemId === selectedCase.itemId)
: -1;
const selectedDecision = selectedCase
? review?.decisions.find(
(decision) => decision.itemId === selectedCase.itemId,
) ?? null
: null;
const prompt = selectedCase && detail
? reviewPrompt(selectedCase, detail)
: null;
const navigate = (offset: -1 | 1) => {
if (!catalog?.items.length || selectedIndex < 0) return;
const index = (
selectedIndex + offset + catalog.items.length
) % catalog.items.length;
setSelectedItemId(catalog.items[index].itemId);
};
const saveVerdict = async (verdict: E40OperatorVerdict) => {
if (!review || !selectedCase || verdictPending) return;
setVerdictPending(true);
setReviewError(null);
try {
setReview(await saveE40OperatorVerdict(
resultId,
selectedCase.itemId,
{
expectedRevision: review.revision,
idempotencyKey: `ui-${crypto.randomUUID()}`,
verdict,
},
));
} catch (caught) {
setReviewError(
caught instanceof Error
? caught.message
: "Решение оператора не сохранено.",
);
} finally {
setVerdictPending(false);
}
};
if (catalogLoading) {
return (
<div className="e40-case-review__state" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Проверяем индекс исторической видимой оценки E40</span>
</div>
);
}
if (error && !selectedCase) {
return (
<div className="e40-case-review__state" role="status">
<Icon name="alert" size={18} />
<span>{error}</span>
</div>
);
}
if (!catalog?.items.length) {
return (
<div className="e40-case-review__state" role="status">
<Icon name="check" size={18} />
<span>Расхождений исторической оценки E40 не опубликовано.</span>
</div>
);
}
return (
<div className="e40-case-review">
<header className="e40-case-review__header">
<div>
<span className="section-eyebrow">
CASE REVIEW · HISTORICAL VISIBLE EVALUATION
</span>
<strong>
Визуальная проверка {catalog.items.length} из {catalog.total}{" "}
расхождений
</strong>
<small>
Загружается только выбранный camera/LiDAR-кейс. Проверено
оператором: {review?.reviewedItemCount ?? 0} из {catalog.total}.
</small>
</div>
{selectedCase ? (
<StatusBadge
tone={selectedCase.severity === "high"
? "danger"
: selectedCase.severity === "medium"
? "warning"
: "neutral"}
>
{SEVERITY_LABELS[selectedCase.severity]}
</StatusBadge>
) : null}
</header>
<div className="e40-case-review__viewer">
{detailLoading ? (
<div className="e40-case-review__state" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Проверяем точный кадр и LiDAR-геометрию</span>
</div>
) : error || !detail || !selectedCase ? (
<div className="e40-case-review__state" role="status">
<Icon name="alert" size={18} />
<span>{error ?? "Выберите ошибку для проверки."}</span>
</div>
) : (
<LaboratoryEvidenceViewer
label="Визуальная проверка расхождения E40"
mode={mode}
modes={[
{ value: "camera", label: "Камера" },
{ value: "3d", label: "3D" },
]}
expanded={expanded}
onModeChange={setMode}
onExpandedChange={setExpanded}
actions={(
<div className="e40-case-review__controls">
<div
className="e40-case-review__pagination"
aria-label="Последовательная проверка ошибок"
>
<IconButton
className="e40-case-review__glass-button"
label="Предыдущее расхождение"
disabled={catalog.items.length < 2}
onClick={() => navigate(-1)}
>
<Icon name="chevron-left" size={16} />
</IconButton>
<IconButton
className="e40-case-review__glass-button"
label="Следующее расхождение"
disabled={catalog.items.length < 2}
onClick={() => navigate(1)}
>
<Icon name="chevron-right" size={16} />
</IconButton>
</div>
<Select
className="e40-case-review__select"
menuClassName="e40-case-review__select-menu"
label="Расхождение исторической оценки E40"
value={selectedCase.itemId}
options={catalog.items.map((item) => ({
value: item.itemId,
label: caseLabel(item),
}))}
variant="split"
menuWidth="anchor"
onChange={setSelectedItemId}
/>
{mode === "camera" ? (
<Button
className="e40-case-review__layer-button"
size="compact"
variant="secondary"
shape="pill"
icon={<Icon name="sliders" size={16} />}
data-active={pointLayerVisible ? "true" : undefined}
aria-pressed={pointLayerVisible}
onClick={() => setPointLayerVisible((visible) => !visible)}
>
LiDAR
</Button>
) : null}
</div>
)}
overlay={(
<aside
className="e40-case-review__telemetry"
aria-label="Эталон и результат E40"
>
<div className="e40-case-review__telemetry-content">
<span>
Кадр {detail.sourceFrameIndex}
{" · "}
{STRATUM_LABELS[selectedCase.sourceStratum]}
{selectedCase.predictionBasis === "camera-only-softmax"
? (
<>
{" · "}
вероятность camera-only softmax{" "}
{(selectedCase.presenceConfidence * 100).toLocaleString(
"ru-RU",
{ maximumFractionDigits: 1 },
)}%
</>
)
: " · фиксированная stratum-policy, не вероятность"}
</span>
{prompt ? (
<div className="e40-case-review__question">
<span>ЧТО ПРОВЕРЯЕМ</span>
<strong>{prompt.question}</strong>
<small>{prompt.evidence}</small>
</div>
) : null}
<dl>
{selectedCase.mismatchedDimensions.map((dimension) => (
<div key={dimension}>
<dt>{DIMENSION_LABELS[dimension]}</dt>
<dd>
<span>
Эталон ·{" "}
{VALUE_LABELS[
stateValue(selectedCase.reference, dimension)
]}
</span>
<strong>
{selectedCase.predictionBasis
=== "camera-only-softmax"
? "Модель E40"
: "Правило E40"}{" "}
·{" "}
{VALUE_LABELS[
stateValue(selectedCase.prediction, dimension)
]}
</strong>
</dd>
</div>
))}
</dl>
</div>
<div className="e40-case-review__verdict">
<span>
{verdictPending
? "Сохраняем…"
: selectedDecision?.verdict === "confirmed-error"
? "Ошибка подтверждена"
: selectedDecision?.verdict === "rejected-error"
? "Расхождение отклонено"
: "Решение оператора"}
</span>
<div
className="e40-case-review__verdict-buttons"
aria-label="Решение оператора"
>
<IconButton
className="e40-case-review__verdict-button"
data-verdict="confirmed"
data-active={
selectedDecision?.verdict === "confirmed-error"
? "true"
: undefined
}
label={prompt?.confirm ?? "Подтвердить ошибку модели"}
aria-pressed={
selectedDecision?.verdict === "confirmed-error"
}
disabled={verdictPending}
onClick={() => void saveVerdict("confirmed-error")}
>
<Icon name="check" size={16} />
</IconButton>
<IconButton
className="e40-case-review__verdict-button"
data-verdict="rejected"
data-active={
selectedDecision?.verdict === "rejected-error"
? "true"
: undefined
}
label={prompt?.reject ?? "Отклонить ошибку модели"}
aria-pressed={
selectedDecision?.verdict === "rejected-error"
}
disabled={verdictPending}
onClick={() => void saveVerdict("rejected-error")}
>
<Icon name="close" size={16} />
</IconButton>
</div>
{prompt ? (
<div className="e40-case-review__verdict-guide">
<span data-verdict="confirmed">
<Icon name="check" size={12} />
{prompt.confirm}
</span>
<span data-verdict="rejected">
<Icon name="close" size={12} />
{prompt.reject}
</span>
</div>
) : null}
<small className="e40-case-review__commit-note">
Выбор сохраняется сразу; исходные E30/E40 не изменяются.
</small>
{reviewError ? (
<small
className="e40-case-review__verdict-error"
role="alert"
>
{reviewError}
</small>
) : null}
</div>
</aside>
)}
>
{mode === "camera" ? (
<E30EvidenceProjection
detail={detail}
projectionWidth={detail.materialization.projectionWidth}
projectionHeight={detail.materialization.projectionHeight}
pointLayerVisible={pointLayerVisible}
/>
) : (
<E30EvidencePointCloud detail={detail} />
)}
</LaboratoryEvidenceViewer>
)}
</div>
</div>
);
}
@@ -1,6 +1,5 @@
import {
LaboratoryEvidence,
LaboratoryMetricGrid,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
@@ -9,6 +8,7 @@ import type {
E40PerceptionProductGateResult,
} from "../../core/laboratory/e40ProductGate";
import { formatNumber } from "../../presentation";
import { E40CaseReview } from "./E40CaseReview";
function percent(value: number): string {
return `${(value * 100).toLocaleString("ru-RU", {
@@ -32,14 +32,14 @@ export function E40Result({
result.developmentCrossValidation.protocols.wholeTrackOrSceneWindow.dimensions
);
const gateLabel = result.qualityGatePassed
? "RAVNOVES00 product gate пройден"
: "RAVNOVES00 product gate не пройден";
? "Историческая оценка E40 пройдена"
: "Историческая оценка E40 не пройдена";
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="LAB E40 · leakage-resistant product gate"
description="После E39 проверена route-coordinate-free модель: смежные кадры, целые track и scene-window изолированы при development-проверке, а sealed validation исполнен один раз на Worker 006."
title="LAB E40 · historical visible engineering evaluation"
description="Историческая source-scoped оценка E40: camera-only softmax и фиксированная stratum-policy измерены на уже видимом инженерном контракте RAVNOVES00. Это не operational camera→LiDAR pipeline и не blind validation."
status={gateLabel}
statusTone={result.qualityGatePassed ? "success" : "warning"}
facts={[
@@ -53,7 +53,7 @@ export function E40Result({
},
{
label: "Validation",
value: `${formatNumber(metrics.validationItems, 0)} · sealed`,
value: `${formatNumber(metrics.validationItems, 0)} · historical visible`,
},
{
label: "Исполнение",
@@ -61,10 +61,10 @@ export function E40Result({
},
]}
brief={{
question: "Удерживает ли perception выбранного сенсорного рига не менее 90% по presence, geometry association и freshness после удаления route-coordinate leakage и группировки зависимых кадров?",
approach: "Для 340 development-кейсов зафиксированы 125 признаков без frame index, session time, review ordinal, track ID и абсолютных map-координат. Консервативная stratum-policy и camera-only softmax прошли два групповых пятифолдовых протокола; validation labels при выборе и обучении не использовались.",
principalResult: `Development: contiguous presence ${percent(contiguous.presence.accuracy)}, grouped presence ${percent(grouped.presence.accuracy)}. Sealed validation: presence ${percent(dimensions.presence.accuracy)}, geometry ${percent(dimensions.geometryAssociation.accuracy)}, freshness ${percent(dimensions.freshness.accuracy)}.`,
limitation: "Это source-scoped доказательство только для RAVNOVES00 на инженерно проверенной разметке. Оно не доказывает перенос на другой маршрут, растительность, камеру, риг или живой ровер и не выдаёт навигационных либо safety-полномочий.",
question: "Как camera-only softmax и фиксированная политика страт согласуются с уже видимым инженерным контрактом RAVNOVES00?",
approach: "Для 340 development-кейсов зафиксированы 125 признаков без frame index, session time, review ordinal, track ID и абсолютных map-координат. Только camera-only использует softmax; agree, conflict, geometry-only и unknown получают детерминированные policy-state, а не вероятность operational detector.",
principalResult: `Development: contiguous presence ${percent(contiguous.presence.accuracy)}, grouped presence ${percent(grouped.presence.accuracy)}. Historical visible evaluation: presence ${percent(dimensions.presence.accuracy)}, geometry ${percent(dimensions.geometryAssociation.accuracy)}, freshness ${percent(dimensions.freshness.accuracy)}.`,
limitation: "E40 — историческая source-scoped инженерная оценка поверх E29/E30, а не сама camera-first perception. Она не является blind validation и не доказывает независимую точность, перенос, навигацию или safety.",
}}
method={{
completeness: "complete",
@@ -74,7 +74,7 @@ export function E40Result({
{
kind: "source",
name: "E37 frozen acceptance contract",
version: `${formatNumber(metrics.developmentItems, 0)} development + ${formatNumber(metrics.validationItems, 0)} sealed validation`,
version: `${formatNumber(metrics.developmentItems, 0)} development + ${formatNumber(metrics.validationItems, 0)} historical visible evaluation`,
role: "неизменяемый denominator и reference-метрики трёх измерений",
identitySha256: null,
},
@@ -96,7 +96,7 @@ export function E40Result({
kind: "runtime",
name: "Worker 006",
version: result.workerNode,
role: "одно sealed evaluation в package-bound контейнере без командных полномочий",
role: "одно historical visible evaluation в package-bound контейнере без командных полномочий",
identitySha256: null,
},
],
@@ -105,34 +105,11 @@ export function E40Result({
)}
evidence={(
<LaboratoryEvidence
eyebrow="GROUPED DEVELOPMENT → SEALED VALIDATION"
eyebrow="GROUPED DEVELOPMENT → HISTORICAL VISIBLE EVALUATION"
title="Устойчивость без route-coordinate leakage"
kind="diagnostic-model"
>
<LaboratoryMetricGrid
metrics={[
{
label: "Contiguous time CV",
value: percent(contiguous.presence.accuracy),
hint: `presence · geometry ${percent(contiguous.geometryAssociation.accuracy)}`,
},
{
label: "Whole track / scene CV",
value: percent(grouped.presence.accuracy),
hint: `presence · geometry ${percent(grouped.geometryAssociation.accuracy)}`,
},
{
label: "Sealed presence",
value: percent(dimensions.presence.accuracy),
hint: `${formatNumber(dimensions.presence.correct, 0)} / ${formatNumber(dimensions.presence.total, 0)} · цель 90%`,
},
{
label: "Sealed freshness",
value: percent(dimensions.freshness.accuracy),
hint: `${formatNumber(dimensions.freshness.correct, 0)} / ${formatNumber(dimensions.freshness.total, 0)} · цель 90%`,
},
]}
/>
<E40CaseReview resultId={result.resultId} />
</LaboratoryEvidence>
)}
result={(
@@ -167,7 +144,7 @@ export function E40Result({
conclusion={{
proved: result.qualityGatePassed
? "На неизменяемом RAVNOVES00 все три task-level dimension достигли 90%, полный denominator учтён, ложное свободное пространство не опубликовано и high-severity ошибок нет."
: `Development-профиль устойчив к двум зависимым разбиениям; sealed evaluation завершён с полным учётом ${formatNumber(metrics.validationItems, 0)} кейсов и без ложного свободного пространства.`,
: `Development-профиль устойчив к двум зависимым разбиениям; historical visible evaluation завершён с полным учётом ${formatNumber(metrics.validationItems, 0)} кейсов и без ложного свободного пространства.`,
notProved: "Не доказаны независимая физическая ground truth, второй маршрут, другой риг, растительная среда, живой rover runtime, навигация, команды или safety.",
decision: result.qualityGatePassed
? "Зафиксировать RAVNOVES00 perception gate как закрытый source-scoped этап. Следующий шаг — temporal product state и live-rover replay без расширения полномочий."
@@ -29,7 +29,6 @@ import {
type E30ReviewResult,
} from "../../core/laboratory/e30Review";
import {
fetchAdvancedLaboratoryResults,
type AdvancedLaboratoryResults,
} from "../../core/laboratory/advancedResults";
import {
@@ -51,6 +50,7 @@ import {
e28LaboratoryBrief, e29LaboratoryBrief,
e30LaboratoryBrief, PUBLISHED_LABORATORY_BRIEF,
} from "./laboratoryArchiveBriefs";
import { useAdvancedLaboratoryCatalog } from "./useAdvancedLaboratoryCatalog";
type LaboratoryWorkspaceProps = WorkspaceRendererProps & {
SpatialView: ComponentType<WorkspaceRendererProps>;
};
@@ -63,18 +63,6 @@ type LaboratoryWorkId =
| AdvancedLaboratoryWorkId
| `session:${string}`;
const EMPTY_ADVANCED_RESULTS: AdvancedLaboratoryResults = {
e31: null,
e32: null,
e33: null,
e34: null,
e35: null,
e37: null,
e38: null,
e39: null,
e40: null,
};
function laboratoryWorkOrdinal(value: string): number {
const match = value.match(/\bE(\d+)\b/i);
return match ? Number(match[1]) : -1;
@@ -572,13 +560,11 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
const [e28Model, setE28Model] = useState<LidarLocalSurfaceModel | null>(null);
const [e29Result, setE29Result] = useState<E29EvidenceResult | null>(null);
const [e30Result, setE30Result] = useState<E30ReviewResult | null>(null);
const [advancedResults, setAdvancedResults] = useState(
EMPTY_ADVANCED_RESULTS,
);
const [evidenceLoading, setEvidenceLoading] = useState(true);
const [evidenceError, setEvidenceError] = useState<string | null>(null);
const sessions = useObservationSessions({
limit: 100,
pollingEnabled: false,
replayEnabled: props.sessionArchive.blockedReason === null,
onReplayBegin: props.sessionArchive.onReplayBegin,
onReplayAccepted: props.sessionArchive.onReplayAccepted,
@@ -603,6 +589,18 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
() => new Map(sessions.items.map((session) => [session.id, session])),
[sessions.items],
);
const advanced = useAdvancedLaboratoryCatalog({
selectedWorkId: workId,
onResultLoaded: (nextWorkId, result) => {
const sourceSession = advancedLaboratorySourceSession(
nextWorkId,
result,
sourceSessions,
);
if (sourceSession) void sessions.replay(sourceSession.id);
},
});
const advancedResults: AdvancedLaboratoryResults = advanced.results;
useEffect(() => {
const controller = new AbortController();
@@ -612,22 +610,18 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
fetchLidarLocalSurfaces({ signal: controller.signal }),
fetchE29EvidenceCatalog({ signal: controller.signal }),
fetchE30ReviewCatalog({ signal: controller.signal }),
fetchAdvancedLaboratoryResults({ signal: controller.signal }),
]).then(([e28, e29, e30, advanced]) => {
]).then(([e28, e29, e30]) => {
if (controller.signal.aborted) return;
const nextE28 = e28.status === "fulfilled" ? e28.value.items[0] ?? null : null;
const nextE29 = e29.status === "fulfilled" ? e29.value.items[0] ?? null : null;
const nextE30 = e30.status === "fulfilled" ? e30.value.items[0] ?? null : null;
const nextAdvanced = advanced.status === "fulfilled" ? advanced.value : null;
setE28Model(nextE28);
setE29Result(nextE29);
setE30Result(nextE30);
setAdvancedResults(nextAdvanced ?? EMPTY_ADVANCED_RESULTS);
const failures = [
e28.status === "rejected" ? "E28" : null,
e29.status === "rejected" ? "E29" : null,
e30.status === "rejected" ? "E30" : null,
advanced.status === "rejected" ? "E31E40" : null,
].filter(Boolean);
setEvidenceError(
failures.length
@@ -673,19 +667,15 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
label: "LAB E30 · evidence review A2",
});
}
items.push(...advancedLaboratoryWorkOptions(
advancedResults,
sourceSessions,
));
items.push(...advancedLaboratoryWorkOptions(advanced.index));
return items.sort(
(left, right) => laboratoryWorkOrdinal(right.label) - laboratoryWorkOrdinal(left.label),
);
}, [
advancedResults,
advanced.index,
e28Model,
e29Result,
e30Result,
sourceSessions,
]);
const profiles = useMemo(() => {
const items: LaboratoryOption<LaboratoryProfileId>[] = [];
@@ -726,6 +716,7 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
useEffect(() => {
if (
evidenceLoading
|| advanced.indexLoading
|| sessions.state === "idle"
|| sessions.state === "loading"
) return;
@@ -761,6 +752,7 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
}
}, [
evidenceLoading,
advanced.indexLoading,
profileId,
profiles,
publishedWorks,
@@ -813,6 +805,7 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
if (
evidenceLoading
|| advanced.indexLoading
|| sessions.state === "idle"
|| sessions.state === "loading"
) {
@@ -831,7 +824,10 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
<Icon name="database" size={20} />
<strong>Подтверждённых лабораторных работ нет</strong>
<p>
{evidenceError ?? sessions.error ?? "Непроверенные и отсутствующие результаты скрыты."}
{evidenceError
?? advanced.indexError
?? sessions.error
?? "Непроверенные и отсутствующие результаты скрыты."}
</p>
</div>
);
@@ -953,6 +949,18 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
result={e30Result}
sourceSession={e30SourceSession}
/>
) : advanced.loadingWorkId === workId ? (
<div className="laboratory-result-pending" role="status">
<span className="busy-indicator" aria-hidden="true" />
<strong>Подготавливаем выбранную лабораторную работу</strong>
<p>Сервер проверяет только её доказательства и связанные артефакты.</p>
</div>
) : advanced.failedWorkId === workId ? (
<div className="laboratory-result-pending">
<Icon name="database" size={20} />
<strong>Работа не прошла ревизию</strong>
<p>{advanced.resultError}</p>
</div>
) : isAdvancedLaboratoryWorkId(workId) ? (
<AdvancedLaboratoryResult
props={props}
@@ -0,0 +1,118 @@
import { useEffect, useRef, useState } from "react";
import {
advancedLaboratoryResultAvailable,
emptyAdvancedLaboratoryResults,
fetchAdvancedLaboratoryIndex,
fetchAdvancedLaboratoryResult,
isAdvancedLaboratoryWorkId,
type AdvancedLaboratoryIndexItem,
type AdvancedLaboratoryWorkId,
} from "../../core/laboratory/advancedIndex";
import type {
AdvancedLaboratoryResults,
} from "../../core/laboratory/advancedResults";
function mergeResults(
current: AdvancedLaboratoryResults,
next: AdvancedLaboratoryResults,
): AdvancedLaboratoryResults {
return {
e31: next.e31 ?? current.e31,
e32: next.e32 ?? current.e32,
e33: next.e33 ?? current.e33,
e34: next.e34 ?? current.e34,
e35: next.e35 ?? current.e35,
e37: next.e37 ?? current.e37,
e38: next.e38 ?? current.e38,
e39: next.e39 ?? current.e39,
e40: next.e40 ?? current.e40,
};
}
function errorMessage(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
: "Выбранная лабораторная работа не прошла серверную проверку.";
}
export function useAdvancedLaboratoryCatalog({
selectedWorkId,
onResultLoaded,
}: {
selectedWorkId: string;
onResultLoaded?: (
workId: AdvancedLaboratoryWorkId,
result: AdvancedLaboratoryResults,
) => void | Promise<void>;
}) {
const [index, setIndex] = useState<readonly AdvancedLaboratoryIndexItem[]>([]);
const [indexLoading, setIndexLoading] = useState(true);
const [indexError, setIndexError] = useState<string | null>(null);
const [results, setResults] = useState<AdvancedLaboratoryResults>(
emptyAdvancedLaboratoryResults,
);
const [loadingWorkId, setLoadingWorkId] =
useState<AdvancedLaboratoryWorkId | null>(null);
const [failedWorkId, setFailedWorkId] =
useState<AdvancedLaboratoryWorkId | null>(null);
const [resultError, setResultError] = useState<string | null>(null);
const onResultLoadedRef = useRef(onResultLoaded);
onResultLoadedRef.current = onResultLoaded;
useEffect(() => {
const controller = new AbortController();
setIndexLoading(true);
setIndexError(null);
void fetchAdvancedLaboratoryIndex({ signal: controller.signal })
.then((items) => {
if (!controller.signal.aborted) setIndex(items);
})
.catch((error: unknown) => {
if (!controller.signal.aborted) {
setIndex([]);
setIndexError(errorMessage(error));
}
})
.finally(() => {
if (!controller.signal.aborted) setIndexLoading(false);
});
return () => controller.abort();
}, []);
useEffect(() => {
if (
!isAdvancedLaboratoryWorkId(selectedWorkId)
|| advancedLaboratoryResultAvailable(selectedWorkId, results)
) return;
const controller = new AbortController();
setLoadingWorkId(selectedWorkId);
setFailedWorkId(null);
setResultError(null);
void fetchAdvancedLaboratoryResult(selectedWorkId, {
signal: controller.signal,
}).then(async (next) => {
if (controller.signal.aborted) return;
setResults((current) => mergeResults(current, next));
await onResultLoadedRef.current?.(selectedWorkId, next);
}).catch((error: unknown) => {
if (!controller.signal.aborted) {
setFailedWorkId(selectedWorkId);
setResultError(errorMessage(error));
}
}).finally(() => {
if (!controller.signal.aborted) setLoadingWorkId(null);
});
return () => controller.abort();
}, [results, selectedWorkId]);
return {
index,
indexLoading,
indexError,
results,
loadingWorkId,
failedWorkId,
resultError,
} as const;
}
@@ -5,6 +5,8 @@ import { createServer } from "vite";
let server;
let fetchAdvancedLaboratoryResults;
let fetchAdvancedLaboratoryIndex;
let fetchAdvancedLaboratoryResult;
let AdvancedLaboratoryContractError;
const authority = {
@@ -637,6 +639,10 @@ before(async () => {
fetchAdvancedLaboratoryResults,
AdvancedLaboratoryContractError,
} = await server.ssrLoadModule("/src/core/laboratory/advancedResults.ts"));
({
fetchAdvancedLaboratoryIndex,
fetchAdvancedLaboratoryResult,
} = await server.ssrLoadModule("/src/core/laboratory/advancedIndex.ts"));
});
after(async () => {
@@ -703,6 +709,53 @@ test("decodes E31E40 from separate read-only catalogs", async () => {
]);
});
test("LAB bootstrap reads a lightweight advanced index", async () => {
const requests = [];
const decoded = await fetchAdvancedLaboratoryIndex({
fetcher: async (input, init) => {
requests.push({ input: String(input), method: init?.method });
return new Response(JSON.stringify({
schema_version: "missioncore.laboratory-advanced-index/v1",
items: [
{
work_id: "e40-perception-product-gate",
result_id: `e40-perception-product-gate-${"a".repeat(64)}`,
created_at_utc: "2026-07-28T11:00:00Z",
access: "read-only",
},
],
access: "read-only",
}), { status: 200 });
},
});
assert.deepEqual(decoded.map((item) => item.workId), [
"e40-perception-product-gate",
]);
assert.deepEqual(requests, [
{ input: "/api/v1/laboratory/advanced-index", method: "GET" },
]);
});
test("selected advanced LAB fetches only its own strict catalog", async () => {
const requests = [];
const decoded = await fetchAdvancedLaboratoryResult(
"e40-perception-product-gate",
{
fetcher: async (input, init) => {
requests.push({ input: String(input), method: init?.method });
return new Response(JSON.stringify(catalog(e40())), { status: 200 });
},
},
);
assert.equal(decoded.e40.qualityGatePassed, true);
assert.equal(decoded.e31, null);
assert.deepEqual(requests, [
{ 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) => {
@@ -73,6 +73,8 @@ function item(overrides = {}) {
materialization: {
frame_point_count: 1813,
projected_point_count: 3,
projection_width: 800,
projection_height: 600,
candidate_point_count: 2,
selected_point_count: 1,
rejected_candidate_point_count: 1,
@@ -0,0 +1,174 @@
import assert from "node:assert/strict";
import { after, before, test } from "node:test";
import { createServer } from "vite";
let server;
let parseE40CaseCatalog;
let parseE40OperatorReview;
let fetchE40CaseCatalog;
let saveE40OperatorVerdict;
let E40CaseReviewContractError;
const resultId = `e40-perception-product-gate-${"a".repeat(64)}`;
const materializationId = `e30-materialization-${"b".repeat(64)}`;
const itemId = `e30-review-item-${"c".repeat(64)}`;
function payload(overrides = {}) {
return {
schema_version: "missioncore.laboratory-e40-case-catalog/v1",
result_id: resultId,
materialization_id: materializationId,
items: [{
item_id: itemId,
sequence: 3,
source_frame_index: 1080,
source_stratum: "geometry-only",
severity: "high",
presence_confidence: 1,
prediction_basis: "fixed-stratum-policy",
reference: {
presence: "object-present",
geometry_association: "object-associated",
freshness: "current",
},
prediction: {
presence: "occupied-environment",
geometry_association: "independent-occupied",
freshness: "current",
},
mismatched_dimensions: ["presence", "geometry_association"],
access: "read-only",
}],
total: 23,
truncated: false,
access: "read-only",
...overrides,
};
}
function reviewPayload(overrides = {}) {
return {
schema_version: "missioncore.e40-operator-review/v1",
review_id: `e40-operator-review-${"f".repeat(64)}`,
protocol: "sealed-error-adjudication/v1",
source: {
result_id: resultId,
materialization_id: materializationId,
case_catalog_sha256: "1".repeat(64),
item_count: 1,
item_set_sha256: "2".repeat(64),
},
reviewer_id: "DC",
revision: 1,
decisions: [{
item_id: itemId,
verdict: "confirmed-error",
revision: 1,
idempotency_key: "ui-test",
decided_at_utc: "2026-07-30T17:00:00.000Z",
event_id: `e40-operator-verdict-${"3".repeat(64)}`,
}],
reviewed_item_count: 1,
remaining_item_count: 0,
updated_at_utc: "2026-07-30T17:00:00.000Z",
access: "review-write",
...overrides,
};
}
before(async () => {
server = await createServer({
configFile: false,
appType: "custom",
optimizeDeps: { noDiscovery: true },
server: { middlewareMode: true, hmr: false },
});
({
parseE40CaseCatalog,
parseE40OperatorReview,
fetchE40CaseCatalog,
saveE40OperatorVerdict,
E40CaseReviewContractError,
} = await server.ssrLoadModule("/src/core/laboratory/e40CaseReview.ts"));
});
test("E40 operator review preserves the sealed source and verdict", () => {
const review = parseE40OperatorReview(reviewPayload());
assert.equal(review.resultId, resultId);
assert.equal(review.materializationId, materializationId);
assert.equal(review.caseCatalogSha256, "1".repeat(64));
assert.equal(review.reviewedItemCount, 1);
assert.equal(review.decisions[0].verdict, "confirmed-error");
});
after(async () => {
await server.close();
});
test("E40 case catalog preserves the sealed error binding", () => {
const catalog = parseE40CaseCatalog(payload());
assert.equal(catalog.resultId, resultId);
assert.equal(catalog.materializationId, materializationId);
assert.equal(catalog.items[0].sourceFrameIndex, 1080);
assert.equal(catalog.items[0].predictionBasis, "fixed-stratum-policy");
assert.deepEqual(
catalog.items[0].mismatchedDimensions,
["presence", "geometry_association"],
);
assert.equal(
catalog.items[0].prediction.geometryAssociation,
"independent-occupied",
);
});
test("E40 case fetch is bounded and rejects a foreign result", async () => {
let requestUrl = "";
const fetcher = async (input) => {
requestUrl = String(input);
return new Response(JSON.stringify(payload({
result_id: `e40-perception-product-gate-${"d".repeat(64)}`,
})), {
status: 200,
headers: { "Content-Type": "application/json" },
});
};
await assert.rejects(
fetchE40CaseCatalog(resultId, { fetcher }),
E40CaseReviewContractError,
);
assert.match(requestUrl, /cases\?limit=48$/);
});
test("E40 operator verdict is committed with revision and idempotency", async () => {
let requestUrl = "";
let requestInit;
const fetcher = async (input, init) => {
requestUrl = String(input);
requestInit = init;
return new Response(JSON.stringify(reviewPayload()), {
status: 200,
headers: { "Content-Type": "application/json" },
});
};
const review = await saveE40OperatorVerdict(resultId, itemId, {
expectedRevision: 0,
idempotencyKey: "ui-test",
verdict: "confirmed-error",
fetcher,
});
assert.match(requestUrl, /operator-review\/decisions\/e30-review-item-/);
assert.equal(requestInit.method, "PUT");
assert.deepEqual(JSON.parse(requestInit.body), {
reviewer_id: "DC",
expected_revision: 0,
idempotency_key: "ui-test",
verdict: "confirmed-error",
});
assert.equal(review.revision, 1);
});
@@ -42,6 +42,10 @@ const e40ResultUrl = new URL(
"../src/workspaces/laboratory/E40Result.tsx",
import.meta.url,
);
const e40CaseReviewUrl = new URL(
"../src/workspaces/laboratory/E40CaseReview.tsx",
import.meta.url,
);
const e35StylesUrl = new URL(
"../src/styles/e35-degradation-recovery.css",
import.meta.url,
@@ -265,9 +269,10 @@ test("E39 reports refinement and the CV-to-validation gap through the canonical
assert.match(advancedSource, /<E39Result/);
});
test("E40 reports dual grouped qualification and the sealed product gate through the canonical LAB anatomy", async () => {
const [e40Source, advancedSource] = await Promise.all([
test("E40 reports historical visible evaluation with bounded camera-LiDAR case review", async () => {
const [e40Source, caseReviewSource, advancedSource] = await Promise.all([
readFile(e40ResultUrl, "utf8"),
readFile(e40CaseReviewUrl, "utf8"),
readFile(advancedLaboratoryResultUrl, "utf8"),
]);
@@ -275,10 +280,22 @@ test("E40 reports dual grouped qualification and the sealed product gate through
assert.match(e40Source, /<LaboratoryEvidence/);
assert.match(e40Source, /<LaboratoryResultSummary/);
assert.match(e40Source, /frame index, session time, review ordinal, track ID/);
assert.match(e40Source, /validation labels при выборе и обучении не использовались/);
assert.match(e40Source, /Whole track \/ scene CV/);
assert.match(e40Source, /[Ss]ource-scoped perception gate/);
assert.match(e40Source, /Только camera-only использует softmax/);
assert.match(e40Source, /<E40CaseReview resultId=\{result\.resultId\}/);
assert.match(e40Source, /historical visible engineering evaluation/);
assert.match(e40Source, /не подбирая профиль по validation/);
assert.match(caseReviewSource, /<LaboratoryEvidenceViewer/);
assert.match(caseReviewSource, /<E30EvidenceProjection/);
assert.match(caseReviewSource, /<E30EvidencePointCloud/);
assert.match(caseReviewSource, /fetchE30ReviewItemDetail/);
assert.match(caseReviewSource, /fetchE40OperatorReview/);
assert.match(caseReviewSource, /saveE40OperatorVerdict/);
assert.match(caseReviewSource, /Расхождение исторической оценки E40/);
assert.match(caseReviewSource, /label="Предыдущее расхождение"/);
assert.match(caseReviewSource, /label="Следующее расхождение"/);
assert.match(caseReviewSource, /расхождение E40 подтверждается/);
assert.match(caseReviewSource, /предполагаемая ошибка отклоняется/);
assert.match(caseReviewSource, /фиксированная stratum-policy, не вероятность/);
assert.doesNotMatch(
e40Source,
/className="laboratory-(?:summary|result-summary|result-metrics|result-conclusion)"/,