feat(lab): publish E40 perception product gate
This commit is contained in:
@@ -263,6 +263,24 @@ export function LaboratoryConclusion({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function LaboratoryMetricGrid({
|
||||||
|
metrics,
|
||||||
|
}: {
|
||||||
|
metrics: readonly LaboratoryResultMetric[];
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="laboratory-result-metrics">
|
||||||
|
{metrics.map((metric) => (
|
||||||
|
<div key={metric.label}>
|
||||||
|
<span>{metric.label}</span>
|
||||||
|
<strong>{metric.value}</strong>
|
||||||
|
<small>{metric.hint}</small>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function LaboratoryResultSummary({
|
export function LaboratoryResultSummary({
|
||||||
title,
|
title,
|
||||||
status,
|
status,
|
||||||
@@ -285,15 +303,7 @@ export function LaboratoryResultSummary({
|
|||||||
</div>
|
</div>
|
||||||
<StatusBadge tone={statusTone}>{status}</StatusBadge>
|
<StatusBadge tone={statusTone}>{status}</StatusBadge>
|
||||||
</header>
|
</header>
|
||||||
<div className="laboratory-result-metrics">
|
<LaboratoryMetricGrid metrics={metrics} />
|
||||||
{metrics.map((metric) => (
|
|
||||||
<div key={metric.label}>
|
|
||||||
<span>{metric.label}</span>
|
|
||||||
<strong>{metric.value}</strong>
|
|
||||||
<small>{metric.hint}</small>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<LaboratoryConclusion {...conclusion} />
|
<LaboratoryConclusion {...conclusion} />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
import {
|
||||||
|
fetchE34TemporalLayerResult,
|
||||||
|
type E34TemporalLayerResult,
|
||||||
|
} from "./e34TemporalLayer";
|
||||||
|
import {
|
||||||
|
fetchE35DegradationRecoveryResult,
|
||||||
|
type E35DegradationRecoveryResult,
|
||||||
|
} from "./e35DegradationRecovery";
|
||||||
|
import {
|
||||||
|
fetchE40ProductGateResult,
|
||||||
|
type E40PerceptionProductGateResult,
|
||||||
|
} from "./e40ProductGate";
|
||||||
|
import { settledCatalogValue } from "./catalogTransport";
|
||||||
|
|
||||||
export interface E31LaboratoryResult {
|
export interface E31LaboratoryResult {
|
||||||
resultId: string;
|
resultId: string;
|
||||||
createdAtUtc: string | null;
|
createdAtUtc: string | null;
|
||||||
@@ -179,7 +193,7 @@ export interface E38PerceptionBaselineResult {
|
|||||||
access: "read-only";
|
access: "read-only";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface E39DevelopmentDimensionMetric {
|
export interface DevelopmentDimensionMetric {
|
||||||
correct: number;
|
correct: number;
|
||||||
incorrect: number;
|
incorrect: number;
|
||||||
total: number;
|
total: number;
|
||||||
@@ -188,6 +202,8 @@ export interface E39DevelopmentDimensionMetric {
|
|||||||
passed: boolean;
|
passed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type E39DevelopmentDimensionMetric = DevelopmentDimensionMetric;
|
||||||
|
|
||||||
export interface E39PerceptionRefinementResult {
|
export interface E39PerceptionRefinementResult {
|
||||||
resultId: string;
|
resultId: string;
|
||||||
createdAtUtc: string | null;
|
createdAtUtc: string | null;
|
||||||
@@ -205,9 +221,9 @@ export interface E39PerceptionRefinementResult {
|
|||||||
validationLabelsUsed: false;
|
validationLabelsUsed: false;
|
||||||
passed: boolean;
|
passed: boolean;
|
||||||
dimensions: {
|
dimensions: {
|
||||||
presence: E39DevelopmentDimensionMetric;
|
presence: DevelopmentDimensionMetric;
|
||||||
geometryAssociation: E39DevelopmentDimensionMetric;
|
geometryAssociation: DevelopmentDimensionMetric;
|
||||||
freshness: E39DevelopmentDimensionMetric;
|
freshness: DevelopmentDimensionMetric;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
metrics: E38PerceptionBaselineResult["metrics"];
|
metrics: E38PerceptionBaselineResult["metrics"];
|
||||||
@@ -230,6 +246,7 @@ export interface AdvancedLaboratoryResults {
|
|||||||
e37: E37AcceptanceContractResult | null;
|
e37: E37AcceptanceContractResult | null;
|
||||||
e38: E38PerceptionBaselineResult | null;
|
e38: E38PerceptionBaselineResult | null;
|
||||||
e39: E39PerceptionRefinementResult | null;
|
e39: E39PerceptionRefinementResult | null;
|
||||||
|
e40: E40PerceptionProductGateResult | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AdvancedLaboratoryContractError extends Error {
|
export class AdvancedLaboratoryContractError extends Error {
|
||||||
@@ -778,10 +795,10 @@ function parseE38(value: unknown): E38PerceptionBaselineResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseE39DevelopmentDimension(
|
function parseDevelopmentDimension(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
label: string,
|
label: string,
|
||||||
): E39DevelopmentDimensionMetric {
|
): DevelopmentDimensionMetric {
|
||||||
const source = record(value, label);
|
const source = record(value, label);
|
||||||
return {
|
return {
|
||||||
correct: integerValue(source.correct, `${label}.correct`),
|
correct: integerValue(source.correct, `${label}.correct`),
|
||||||
@@ -859,15 +876,15 @@ function parseE39(value: unknown): E39PerceptionRefinementResult {
|
|||||||
"E39.development_cross_validation.passed",
|
"E39.development_cross_validation.passed",
|
||||||
),
|
),
|
||||||
dimensions: {
|
dimensions: {
|
||||||
presence: parseE39DevelopmentDimension(
|
presence: parseDevelopmentDimension(
|
||||||
developmentDimensions.presence,
|
developmentDimensions.presence,
|
||||||
"E39.development_cross_validation.dimensions.presence",
|
"E39.development_cross_validation.dimensions.presence",
|
||||||
),
|
),
|
||||||
geometryAssociation: parseE39DevelopmentDimension(
|
geometryAssociation: parseDevelopmentDimension(
|
||||||
developmentDimensions.geometry_association,
|
developmentDimensions.geometry_association,
|
||||||
"E39.development_cross_validation.dimensions.geometry_association",
|
"E39.development_cross_validation.dimensions.geometry_association",
|
||||||
),
|
),
|
||||||
freshness: parseE39DevelopmentDimension(
|
freshness: parseDevelopmentDimension(
|
||||||
developmentDimensions.freshness,
|
developmentDimensions.freshness,
|
||||||
"E39.development_cross_validation.dimensions.freshness",
|
"E39.development_cross_validation.dimensions.freshness",
|
||||||
),
|
),
|
||||||
@@ -951,7 +968,7 @@ export async function fetchAdvancedLaboratoryResults({
|
|||||||
fetcher?: LaboratoryFetch;
|
fetcher?: LaboratoryFetch;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
} = {}): Promise<AdvancedLaboratoryResults> {
|
} = {}): Promise<AdvancedLaboratoryResults> {
|
||||||
const [e31, e32, e33, e34, e35, e37, e38, e39] = await Promise.all([
|
const settled = await Promise.allSettled([
|
||||||
fetchOne("/api/v1/laboratory/e31/results?limit=1", parseE31, fetcher, signal),
|
fetchOne("/api/v1/laboratory/e31/results?limit=1", parseE31, fetcher, signal),
|
||||||
fetchOne("/api/v1/laboratory/e32/results?limit=1", parseE32, fetcher, signal),
|
fetchOne("/api/v1/laboratory/e32/results?limit=1", parseE32, fetcher, signal),
|
||||||
fetchOne("/api/v1/laboratory/e33/results?limit=1", parseE33, fetcher, signal),
|
fetchOne("/api/v1/laboratory/e33/results?limit=1", parseE33, fetcher, signal),
|
||||||
@@ -960,14 +977,16 @@ export async function fetchAdvancedLaboratoryResults({
|
|||||||
fetchOne("/api/v1/laboratory/e37/results?limit=1", parseE37, fetcher, signal),
|
fetchOne("/api/v1/laboratory/e37/results?limit=1", parseE37, fetcher, signal),
|
||||||
fetchOne("/api/v1/laboratory/e38/results?limit=1", parseE38, fetcher, signal),
|
fetchOne("/api/v1/laboratory/e38/results?limit=1", parseE38, fetcher, signal),
|
||||||
fetchOne("/api/v1/laboratory/e39/results?limit=1", parseE39, fetcher, signal),
|
fetchOne("/api/v1/laboratory/e39/results?limit=1", parseE39, fetcher, signal),
|
||||||
|
fetchE40ProductGateResult({ fetcher, signal }),
|
||||||
]);
|
]);
|
||||||
return { e31, e32, e33, e34, e35, e37, e38, e39 };
|
const e31 = settledCatalogValue(settled[0]);
|
||||||
|
const e32 = settledCatalogValue(settled[1]);
|
||||||
|
const e33 = settledCatalogValue(settled[2]);
|
||||||
|
const e34 = settledCatalogValue(settled[3]);
|
||||||
|
const e35 = settledCatalogValue(settled[4]);
|
||||||
|
const e37 = settledCatalogValue(settled[5]);
|
||||||
|
const e38 = settledCatalogValue(settled[6]);
|
||||||
|
const e39 = settledCatalogValue(settled[7]);
|
||||||
|
const e40 = settledCatalogValue(settled[8]);
|
||||||
|
return { e31, e32, e33, e34, e35, e37, e38, e39, e40 };
|
||||||
}
|
}
|
||||||
import {
|
|
||||||
fetchE34TemporalLayerResult,
|
|
||||||
type E34TemporalLayerResult,
|
|
||||||
} from "./e34TemporalLayer";
|
|
||||||
import {
|
|
||||||
fetchE35DegradationRecoveryResult,
|
|
||||||
type E35DegradationRecoveryResult,
|
|
||||||
} from "./e35DegradationRecovery";
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export function settledCatalogValue<T>(
|
||||||
|
result: PromiseSettledResult<T>,
|
||||||
|
): T | null {
|
||||||
|
if (result.status === "fulfilled") {
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
const reason = result.reason;
|
||||||
|
const isAbort = reason instanceof Error && reason.name === "AbortError";
|
||||||
|
const isHttpTransport = (
|
||||||
|
reason instanceof Error
|
||||||
|
&& /Каталог LAB.*недоступен: HTTP \d+\./.test(reason.message)
|
||||||
|
);
|
||||||
|
if (!isAbort && (reason instanceof TypeError || isHttpTransport)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw reason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
export interface E40DevelopmentDimensionMetric {
|
||||||
|
correct: number;
|
||||||
|
incorrect: number;
|
||||||
|
total: number;
|
||||||
|
accuracy: number;
|
||||||
|
target: number;
|
||||||
|
passed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface E40DimensionMetric extends E40DevelopmentDimensionMetric {
|
||||||
|
byStratum: Readonly<Record<string, {
|
||||||
|
correct: number;
|
||||||
|
incorrect: number;
|
||||||
|
total: number;
|
||||||
|
accuracy: number;
|
||||||
|
}>>;
|
||||||
|
confusion: readonly {
|
||||||
|
reference: string;
|
||||||
|
prediction: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface E40DevelopmentProtocol {
|
||||||
|
items: number;
|
||||||
|
foldSizes: Readonly<Record<string, number>>;
|
||||||
|
passed: boolean;
|
||||||
|
dimensions: {
|
||||||
|
presence: E40DevelopmentDimensionMetric;
|
||||||
|
geometryAssociation: E40DevelopmentDimensionMetric;
|
||||||
|
freshness: E40DevelopmentDimensionMetric;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface E40PerceptionProductGateResult {
|
||||||
|
resultId: string;
|
||||||
|
createdAtUtc: string | null;
|
||||||
|
sourceSessionId: string;
|
||||||
|
sourceDisplayName: string;
|
||||||
|
status: "measured-leakage-resistant-product-gate";
|
||||||
|
profileId: string;
|
||||||
|
workerNode: string;
|
||||||
|
qualityGatePassed: boolean;
|
||||||
|
developmentCrossValidation: {
|
||||||
|
strategy: string;
|
||||||
|
seed: string;
|
||||||
|
folds: number;
|
||||||
|
items: number;
|
||||||
|
validationLabelsUsed: false;
|
||||||
|
passed: boolean;
|
||||||
|
protocols: {
|
||||||
|
contiguousSourceTime: E40DevelopmentProtocol;
|
||||||
|
wholeTrackOrSceneWindow: E40DevelopmentProtocol;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
metrics: {
|
||||||
|
developmentItems: number;
|
||||||
|
validationItems: number;
|
||||||
|
terminalOutcomes: number;
|
||||||
|
accountingFraction: number;
|
||||||
|
falseFreeClaims: number;
|
||||||
|
highSeverityFailures: number;
|
||||||
|
dimensions: {
|
||||||
|
presence: E40DimensionMetric;
|
||||||
|
geometryAssociation: E40DimensionMetric;
|
||||||
|
freshness: E40DimensionMetric;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
blockingChecks: readonly string[];
|
||||||
|
method: {
|
||||||
|
summary: string;
|
||||||
|
selection: string;
|
||||||
|
dimensionProjection: string;
|
||||||
|
};
|
||||||
|
limitations: readonly string[];
|
||||||
|
access: "read-only";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class E40ProductGateContractError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "E40ProductGateContractError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LaboratoryFetch = (
|
||||||
|
input: RequestInfo | URL,
|
||||||
|
init?: RequestInit,
|
||||||
|
) => Promise<Response>;
|
||||||
|
|
||||||
|
function record(value: unknown, label: string): Record<string, unknown> {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
|
throw new E40ProductGateContractError(`${label}: ожидался объект.`);
|
||||||
|
}
|
||||||
|
return value as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringValue(value: unknown, label: string): string {
|
||||||
|
if (typeof value !== "string" || !value.trim()) {
|
||||||
|
throw new E40ProductGateContractError(`${label}: ожидалась строка.`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function optionalString(value: unknown, label: string): string | null {
|
||||||
|
return value === null ? null : stringValue(value, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberValue(value: unknown, label: string): number {
|
||||||
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
||||||
|
throw new E40ProductGateContractError(`${label}: ожидалось число.`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function integerValue(value: unknown, label: string): number {
|
||||||
|
const parsed = numberValue(value, label);
|
||||||
|
if (!Number.isSafeInteger(parsed)) {
|
||||||
|
throw new E40ProductGateContractError(`${label}: ожидалось целое число.`);
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function booleanValue(value: unknown, label: string): boolean {
|
||||||
|
if (typeof value !== "boolean") {
|
||||||
|
throw new E40ProductGateContractError(`${label}: ожидался boolean.`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function falseValue(value: unknown, label: string): false {
|
||||||
|
if (value !== false) {
|
||||||
|
throw new E40ProductGateContractError(`${label}: ожидалось false.`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exactString<T extends string>(
|
||||||
|
value: unknown,
|
||||||
|
expected: T,
|
||||||
|
label: string,
|
||||||
|
): T {
|
||||||
|
if (value !== expected) {
|
||||||
|
throw new E40ProductGateContractError(`${label}: неверное значение.`);
|
||||||
|
}
|
||||||
|
return expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
function strings(value: unknown, label: string): readonly string[] {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new E40ProductGateContractError(`${label}: ожидался массив.`);
|
||||||
|
}
|
||||||
|
return value.map((item, index) => stringValue(item, `${label}[${index}]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberRecord(
|
||||||
|
value: unknown,
|
||||||
|
label: string,
|
||||||
|
): Readonly<Record<string, number>> {
|
||||||
|
const source = record(value, label);
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(source).map(([key, item]) => [
|
||||||
|
key,
|
||||||
|
integerValue(item, `${label}.${key}`),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function contentId(value: unknown, prefix: string, label: string): string {
|
||||||
|
const parsed = stringValue(value, label);
|
||||||
|
if (!new RegExp(`^${prefix}-[a-f0-9]{64}$`).test(parsed)) {
|
||||||
|
throw new E40ProductGateContractError(`${label}: неверный content id.`);
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function diagnosticAuthority(value: unknown, label: string): void {
|
||||||
|
const authority = record(value, label);
|
||||||
|
if (
|
||||||
|
authority.commands_enabled !== false
|
||||||
|
|| authority.navigation_or_safety_accepted !== false
|
||||||
|
) {
|
||||||
|
throw new E40ProductGateContractError(`${label}: запрещённые полномочия.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDevelopmentDimension(
|
||||||
|
value: unknown,
|
||||||
|
label: string,
|
||||||
|
): E40DevelopmentDimensionMetric {
|
||||||
|
const dimension = record(value, label);
|
||||||
|
return {
|
||||||
|
correct: integerValue(dimension.correct, `${label}.correct`),
|
||||||
|
incorrect: integerValue(dimension.incorrect, `${label}.incorrect`),
|
||||||
|
total: integerValue(dimension.total, `${label}.total`),
|
||||||
|
accuracy: numberValue(dimension.accuracy, `${label}.accuracy`),
|
||||||
|
target: numberValue(dimension.target, `${label}.target`),
|
||||||
|
passed: booleanValue(dimension.passed, `${label}.passed`),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDimension(value: unknown, label: string): E40DimensionMetric {
|
||||||
|
const dimension = record(value, label);
|
||||||
|
const byStratum = record(dimension.by_stratum, `${label}.by_stratum`);
|
||||||
|
const confusion = dimension.confusion;
|
||||||
|
if (!Array.isArray(confusion)) {
|
||||||
|
throw new E40ProductGateContractError(
|
||||||
|
`${label}.confusion: ожидался массив.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...parseDevelopmentDimension(value, label),
|
||||||
|
byStratum: Object.fromEntries(
|
||||||
|
Object.entries(byStratum).map(([stratum, raw]) => {
|
||||||
|
const item = record(raw, `${label}.by_stratum.${stratum}`);
|
||||||
|
return [stratum, {
|
||||||
|
correct: integerValue(
|
||||||
|
item.correct,
|
||||||
|
`${label}.by_stratum.${stratum}.correct`,
|
||||||
|
),
|
||||||
|
incorrect: integerValue(
|
||||||
|
item.incorrect,
|
||||||
|
`${label}.by_stratum.${stratum}.incorrect`,
|
||||||
|
),
|
||||||
|
total: integerValue(
|
||||||
|
item.total,
|
||||||
|
`${label}.by_stratum.${stratum}.total`,
|
||||||
|
),
|
||||||
|
accuracy: numberValue(
|
||||||
|
item.accuracy,
|
||||||
|
`${label}.by_stratum.${stratum}.accuracy`,
|
||||||
|
),
|
||||||
|
}];
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
confusion: confusion.map((raw, index) => {
|
||||||
|
const item = record(raw, `${label}.confusion[${index}]`);
|
||||||
|
return {
|
||||||
|
reference: stringValue(
|
||||||
|
item.reference,
|
||||||
|
`${label}.confusion[${index}].reference`,
|
||||||
|
),
|
||||||
|
prediction: stringValue(
|
||||||
|
item.prediction,
|
||||||
|
`${label}.confusion[${index}].prediction`,
|
||||||
|
),
|
||||||
|
count: integerValue(
|
||||||
|
item.count,
|
||||||
|
`${label}.confusion[${index}].count`,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDevelopmentProtocol(
|
||||||
|
value: unknown,
|
||||||
|
label: string,
|
||||||
|
): E40DevelopmentProtocol {
|
||||||
|
const protocol = record(value, label);
|
||||||
|
const dimensions = record(protocol.dimensions, `${label}.dimensions`);
|
||||||
|
return {
|
||||||
|
items: integerValue(protocol.items, `${label}.items`),
|
||||||
|
foldSizes: numberRecord(protocol.fold_sizes, `${label}.fold_sizes`),
|
||||||
|
passed: booleanValue(protocol.passed, `${label}.passed`),
|
||||||
|
dimensions: {
|
||||||
|
presence: parseDevelopmentDimension(
|
||||||
|
dimensions.presence,
|
||||||
|
`${label}.dimensions.presence`,
|
||||||
|
),
|
||||||
|
geometryAssociation: parseDevelopmentDimension(
|
||||||
|
dimensions.geometry_association,
|
||||||
|
`${label}.dimensions.geometry_association`,
|
||||||
|
),
|
||||||
|
freshness: parseDevelopmentDimension(
|
||||||
|
dimensions.freshness,
|
||||||
|
`${label}.dimensions.freshness`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseResult(value: unknown): E40PerceptionProductGateResult {
|
||||||
|
const item = record(value, "E40");
|
||||||
|
const metrics = record(item.metrics, "E40.metrics");
|
||||||
|
const dimensions = record(metrics.dimensions, "E40.metrics.dimensions");
|
||||||
|
const development = record(
|
||||||
|
item.development_cross_validation,
|
||||||
|
"E40.development_cross_validation",
|
||||||
|
);
|
||||||
|
const protocols = record(
|
||||||
|
development.protocols,
|
||||||
|
"E40.development_cross_validation.protocols",
|
||||||
|
);
|
||||||
|
const method = record(item.method, "E40.method");
|
||||||
|
diagnosticAuthority(item.authority, "E40.authority");
|
||||||
|
return {
|
||||||
|
resultId: contentId(
|
||||||
|
item.result_id,
|
||||||
|
"e40-perception-product-gate",
|
||||||
|
"E40.result_id",
|
||||||
|
),
|
||||||
|
createdAtUtc: optionalString(item.created_at_utc, "E40.created_at_utc"),
|
||||||
|
sourceSessionId: stringValue(
|
||||||
|
item.source_session_id,
|
||||||
|
"E40.source_session_id",
|
||||||
|
),
|
||||||
|
sourceDisplayName: stringValue(
|
||||||
|
item.source_display_name,
|
||||||
|
"E40.source_display_name",
|
||||||
|
),
|
||||||
|
status: exactString(
|
||||||
|
item.status,
|
||||||
|
"measured-leakage-resistant-product-gate",
|
||||||
|
"E40.status",
|
||||||
|
),
|
||||||
|
profileId: stringValue(item.profile_id, "E40.profile_id"),
|
||||||
|
workerNode: stringValue(item.worker_node, "E40.worker_node"),
|
||||||
|
qualityGatePassed: booleanValue(
|
||||||
|
item.quality_gate_passed,
|
||||||
|
"E40.quality_gate_passed",
|
||||||
|
),
|
||||||
|
developmentCrossValidation: {
|
||||||
|
strategy: stringValue(
|
||||||
|
development.strategy,
|
||||||
|
"E40.development_cross_validation.strategy",
|
||||||
|
),
|
||||||
|
seed: stringValue(
|
||||||
|
development.seed,
|
||||||
|
"E40.development_cross_validation.seed",
|
||||||
|
),
|
||||||
|
folds: integerValue(
|
||||||
|
development.folds,
|
||||||
|
"E40.development_cross_validation.folds",
|
||||||
|
),
|
||||||
|
items: integerValue(
|
||||||
|
development.items,
|
||||||
|
"E40.development_cross_validation.items",
|
||||||
|
),
|
||||||
|
validationLabelsUsed: falseValue(
|
||||||
|
development.validation_labels_used,
|
||||||
|
"E40.development_cross_validation.validation_labels_used",
|
||||||
|
),
|
||||||
|
passed: booleanValue(
|
||||||
|
development.passed,
|
||||||
|
"E40.development_cross_validation.passed",
|
||||||
|
),
|
||||||
|
protocols: {
|
||||||
|
contiguousSourceTime: parseDevelopmentProtocol(
|
||||||
|
protocols["contiguous-source-time-five-fold"],
|
||||||
|
"E40.development_cross_validation.protocols.contiguous-source-time-five-fold",
|
||||||
|
),
|
||||||
|
wholeTrackOrSceneWindow: parseDevelopmentProtocol(
|
||||||
|
protocols["whole-track-or-scene-window-five-fold"],
|
||||||
|
"E40.development_cross_validation.protocols.whole-track-or-scene-window-five-fold",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metrics: {
|
||||||
|
developmentItems: integerValue(
|
||||||
|
metrics.development_items,
|
||||||
|
"E40.metrics.development_items",
|
||||||
|
),
|
||||||
|
validationItems: integerValue(
|
||||||
|
metrics.validation_items,
|
||||||
|
"E40.metrics.validation_items",
|
||||||
|
),
|
||||||
|
terminalOutcomes: integerValue(
|
||||||
|
metrics.terminal_outcomes,
|
||||||
|
"E40.metrics.terminal_outcomes",
|
||||||
|
),
|
||||||
|
accountingFraction: numberValue(
|
||||||
|
metrics.accounting_fraction,
|
||||||
|
"E40.metrics.accounting_fraction",
|
||||||
|
),
|
||||||
|
falseFreeClaims: integerValue(
|
||||||
|
metrics.false_free_claims,
|
||||||
|
"E40.metrics.false_free_claims",
|
||||||
|
),
|
||||||
|
highSeverityFailures: integerValue(
|
||||||
|
metrics.high_severity_failures,
|
||||||
|
"E40.metrics.high_severity_failures",
|
||||||
|
),
|
||||||
|
dimensions: {
|
||||||
|
presence: parseDimension(
|
||||||
|
dimensions.presence,
|
||||||
|
"E40.metrics.dimensions.presence",
|
||||||
|
),
|
||||||
|
geometryAssociation: parseDimension(
|
||||||
|
dimensions.geometry_association,
|
||||||
|
"E40.metrics.dimensions.geometry_association",
|
||||||
|
),
|
||||||
|
freshness: parseDimension(
|
||||||
|
dimensions.freshness,
|
||||||
|
"E40.metrics.dimensions.freshness",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
blockingChecks: strings(item.blocking_checks, "E40.blocking_checks"),
|
||||||
|
method: {
|
||||||
|
summary: stringValue(method.summary, "E40.method.summary"),
|
||||||
|
selection: stringValue(method.selection, "E40.method.selection"),
|
||||||
|
dimensionProjection: stringValue(
|
||||||
|
method.dimension_projection,
|
||||||
|
"E40.method.dimension_projection",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
limitations: strings(item.limitations, "E40.limitations"),
|
||||||
|
access: exactString(item.access, "read-only", "E40.access"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCatalog(payload: unknown): E40PerceptionProductGateResult | null {
|
||||||
|
const catalog = record(payload, "Каталог LAB E40");
|
||||||
|
exactString(
|
||||||
|
catalog.schema_version,
|
||||||
|
"missioncore.laboratory-advanced-catalog/v1",
|
||||||
|
"Каталог LAB E40.schema_version",
|
||||||
|
);
|
||||||
|
booleanValue(catalog.configured, "Каталог LAB E40.configured");
|
||||||
|
integerValue(catalog.candidate_total, "Каталог LAB E40.candidate_total");
|
||||||
|
integerValue(catalog.invalid_total, "Каталог LAB E40.invalid_total");
|
||||||
|
exactString(catalog.access, "read-only", "Каталог LAB E40.access");
|
||||||
|
if (!Array.isArray(catalog.items)) {
|
||||||
|
throw new E40ProductGateContractError(
|
||||||
|
"Каталог LAB E40.items: ожидался массив.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (catalog.items.length > 1) {
|
||||||
|
throw new E40ProductGateContractError(
|
||||||
|
"Каталог LAB E40.items: нарушен limit=1.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return catalog.items.length ? parseResult(catalog.items[0]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchE40ProductGateResult({
|
||||||
|
fetcher = fetch,
|
||||||
|
signal,
|
||||||
|
}: {
|
||||||
|
fetcher?: LaboratoryFetch;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
} = {}): Promise<E40PerceptionProductGateResult | null> {
|
||||||
|
const response = await fetcher(
|
||||||
|
"/api/v1/laboratory/e40/results?limit=1",
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new E40ProductGateContractError(
|
||||||
|
`Каталог LAB E40 недоступен: HTTP ${response.status}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return parseCatalog(await response.json());
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import { E35Result } from "./E35Result";
|
|||||||
import { E37Result } from "./E37Result";
|
import { E37Result } from "./E37Result";
|
||||||
import { E38Result } from "./E38Result";
|
import { E38Result } from "./E38Result";
|
||||||
import { E39Result } from "./E39Result";
|
import { E39Result } from "./E39Result";
|
||||||
|
import { E40Result } from "./E40Result";
|
||||||
import { RecordedReplayEvidence } from "./RecordedReplayEvidence";
|
import { RecordedReplayEvidence } from "./RecordedReplayEvidence";
|
||||||
|
|
||||||
export type AdvancedLaboratoryWorkId =
|
export type AdvancedLaboratoryWorkId =
|
||||||
@@ -22,7 +23,8 @@ export type AdvancedLaboratoryWorkId =
|
|||||||
| "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";
|
||||||
|
|
||||||
type LaboratoryWorkspaceProps = WorkspaceRendererProps & {
|
type LaboratoryWorkspaceProps = WorkspaceRendererProps & {
|
||||||
SpatialView: ComponentType<WorkspaceRendererProps>;
|
SpatialView: ComponentType<WorkspaceRendererProps>;
|
||||||
@@ -40,6 +42,7 @@ export function isAdvancedLaboratoryWorkId(
|
|||||||
|| value === "e37-ravnoves-acceptance"
|
|| value === "e37-ravnoves-acceptance"
|
||||||
|| value === "e38-perception-baseline"
|
|| value === "e38-perception-baseline"
|
||||||
|| value === "e39-perception-refinement"
|
|| value === "e39-perception-refinement"
|
||||||
|
|| value === "e40-perception-product-gate"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +99,12 @@ export function advancedLaboratoryWorkOptions(
|
|||||||
label: "LAB E39 · perception refinement R1",
|
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;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +142,9 @@ export function AdvancedLaboratoryResult({
|
|||||||
failedSessionId: string | null;
|
failedSessionId: string | null;
|
||||||
replayError: string | null;
|
replayError: string | null;
|
||||||
}) {
|
}) {
|
||||||
|
if (workId === "e40-perception-product-gate" && results.e40) {
|
||||||
|
return <E40Result rigLabel={rigLabel} result={results.e40} />;
|
||||||
|
}
|
||||||
if (workId === "e39-perception-refinement" && results.e39) {
|
if (workId === "e39-perception-refinement" && results.e39) {
|
||||||
return <E39Result rigLabel={rigLabel} result={results.e39} />;
|
return <E39Result rigLabel={rigLabel} result={results.e39} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import {
|
||||||
|
LaboratoryEvidence,
|
||||||
|
LaboratoryMetricGrid,
|
||||||
|
LaboratoryResultSummary,
|
||||||
|
LaboratorySummary,
|
||||||
|
LaboratoryWorkTemplate,
|
||||||
|
} from "../../components/laboratory/LaboratoryPresentation";
|
||||||
|
import type {
|
||||||
|
E40PerceptionProductGateResult,
|
||||||
|
} from "../../core/laboratory/e40ProductGate";
|
||||||
|
import { formatNumber } from "../../presentation";
|
||||||
|
|
||||||
|
function percent(value: number): string {
|
||||||
|
return `${(value * 100).toLocaleString("ru-RU", {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
})}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function E40Result({
|
||||||
|
rigLabel,
|
||||||
|
result,
|
||||||
|
}: {
|
||||||
|
rigLabel: string;
|
||||||
|
result: E40PerceptionProductGateResult;
|
||||||
|
}) {
|
||||||
|
const metrics = result.metrics;
|
||||||
|
const dimensions = metrics.dimensions;
|
||||||
|
const contiguous = (
|
||||||
|
result.developmentCrossValidation.protocols.contiguousSourceTime.dimensions
|
||||||
|
);
|
||||||
|
const grouped = (
|
||||||
|
result.developmentCrossValidation.protocols.wholeTrackOrSceneWindow.dimensions
|
||||||
|
);
|
||||||
|
const gateLabel = result.qualityGatePassed
|
||||||
|
? "RAVNOVES00 product gate пройден"
|
||||||
|
: "RAVNOVES00 product gate не пройден";
|
||||||
|
return (
|
||||||
|
<LaboratoryWorkTemplate
|
||||||
|
summary={(
|
||||||
|
<LaboratorySummary
|
||||||
|
title="LAB E40 · leakage-resistant product gate"
|
||||||
|
description="После E39 проверена route-coordinate-free модель: смежные кадры, целые track и scene-window изолированы при development-проверке, а sealed validation исполнен один раз на Worker 006."
|
||||||
|
status={gateLabel}
|
||||||
|
statusTone={result.qualityGatePassed ? "success" : "warning"}
|
||||||
|
facts={[
|
||||||
|
{
|
||||||
|
label: "Конфигурация",
|
||||||
|
value: `${rigLabel} · ${result.sourceDisplayName}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Development",
|
||||||
|
value: `${formatNumber(metrics.developmentItems, 0)} · dual 5-fold`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Validation",
|
||||||
|
value: `${formatNumber(metrics.validationItems, 0)} · sealed`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Исполнение",
|
||||||
|
value: `Worker 006 · ${result.workerNode}`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
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-полномочий.",
|
||||||
|
}}
|
||||||
|
method={{
|
||||||
|
completeness: "complete",
|
||||||
|
executionClass: "deterministic",
|
||||||
|
pipelineId: result.profileId,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
kind: "source",
|
||||||
|
name: "E37 frozen acceptance contract",
|
||||||
|
version: `${formatNumber(metrics.developmentItems, 0)} development + ${formatNumber(metrics.validationItems, 0)} sealed validation`,
|
||||||
|
role: "неизменяемый denominator и reference-метрики трёх измерений",
|
||||||
|
identitySha256: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "algorithm",
|
||||||
|
name: "Conservative evidence-stratum policy",
|
||||||
|
version: "agree · conflict · geometry-only · unknown",
|
||||||
|
role: "явные безопасные terminal outcomes без свободного пространства",
|
||||||
|
identitySha256: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "model",
|
||||||
|
name: "Camera-only deterministic softmax",
|
||||||
|
version: "125 route-coordinate-free structured-image features",
|
||||||
|
role: "presence для camera-only при dual grouped development CV",
|
||||||
|
identitySha256: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "runtime",
|
||||||
|
name: "Worker 006",
|
||||||
|
version: result.workerNode,
|
||||||
|
role: "одно sealed evaluation в package-bound контейнере без командных полномочий",
|
||||||
|
identitySha256: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
evidence={(
|
||||||
|
<LaboratoryEvidence
|
||||||
|
eyebrow="GROUPED DEVELOPMENT → SEALED VALIDATION"
|
||||||
|
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%`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</LaboratoryEvidence>
|
||||||
|
)}
|
||||||
|
result={(
|
||||||
|
<LaboratoryResultSummary
|
||||||
|
title={result.qualityGatePassed
|
||||||
|
? "Source-scoped perception gate RAVNOVES00 закрыт"
|
||||||
|
: "Route-coordinate признаки исключены, но product gate ещё не закрыт"}
|
||||||
|
status={gateLabel}
|
||||||
|
statusTone={result.qualityGatePassed ? "success" : "warning"}
|
||||||
|
metrics={[
|
||||||
|
{
|
||||||
|
label: "Presence",
|
||||||
|
value: percent(dimensions.presence.accuracy),
|
||||||
|
hint: `${formatNumber(dimensions.presence.incorrect, 0)} ошибок · цель 90%`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Geometry association",
|
||||||
|
value: percent(dimensions.geometryAssociation.accuracy),
|
||||||
|
hint: `${formatNumber(dimensions.geometryAssociation.incorrect, 0)} ошибок · цель 90%`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Freshness",
|
||||||
|
value: percent(dimensions.freshness.accuracy),
|
||||||
|
hint: `${formatNumber(dimensions.freshness.incorrect, 0)} ошибок · цель 90%`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "High severity",
|
||||||
|
value: formatNumber(metrics.highSeverityFailures, 0),
|
||||||
|
hint: `учёт ${percent(metrics.accountingFraction)} · false free ${formatNumber(metrics.falseFreeClaims, 0)}`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
conclusion={{
|
||||||
|
proved: result.qualityGatePassed
|
||||||
|
? "На неизменяемом RAVNOVES00 все три task-level dimension достигли 90%, полный denominator учтён, ложное свободное пространство не опубликовано и high-severity ошибок нет."
|
||||||
|
: `Development-профиль устойчив к двум зависимым разбиениям; sealed 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 без расширения полномочий."
|
||||||
|
: `Не принимать source-scoped product gate. Разбирать только оставшиеся sealed ошибки и блокеры (${result.blockingChecks.join(", ")}), не подбирая профиль по validation.`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -72,6 +72,7 @@ const EMPTY_ADVANCED_RESULTS: AdvancedLaboratoryResults = {
|
|||||||
e37: null,
|
e37: null,
|
||||||
e38: null,
|
e38: null,
|
||||||
e39: null,
|
e39: null,
|
||||||
|
e40: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
function laboratoryWorkOrdinal(value: string): number {
|
function laboratoryWorkOrdinal(value: string): number {
|
||||||
@@ -625,7 +626,7 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
|
|||||||
e28.status === "rejected" ? "E28" : null,
|
e28.status === "rejected" ? "E28" : null,
|
||||||
e29.status === "rejected" ? "E29" : null,
|
e29.status === "rejected" ? "E29" : null,
|
||||||
e30.status === "rejected" ? "E30" : null,
|
e30.status === "rejected" ? "E30" : null,
|
||||||
advanced.status === "rejected" ? "E31–E35" : null,
|
advanced.status === "rejected" ? "E31–E40" : null,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
setEvidenceError(
|
setEvidenceError(
|
||||||
failures.length
|
failures.length
|
||||||
|
|||||||
@@ -526,6 +526,107 @@ function e39() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function e40DevelopmentProtocol({
|
||||||
|
accuracy,
|
||||||
|
correct,
|
||||||
|
incorrect,
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
items: 340,
|
||||||
|
fold_sizes: {
|
||||||
|
0: 68,
|
||||||
|
1: 68,
|
||||||
|
2: 68,
|
||||||
|
3: 68,
|
||||||
|
4: 68,
|
||||||
|
},
|
||||||
|
passed: true,
|
||||||
|
dimensions: {
|
||||||
|
presence: e39DevelopmentDimension({ accuracy, correct, incorrect }),
|
||||||
|
geometry_association: e39DevelopmentDimension({
|
||||||
|
accuracy,
|
||||||
|
correct,
|
||||||
|
incorrect,
|
||||||
|
}),
|
||||||
|
freshness: e39DevelopmentDimension({
|
||||||
|
accuracy: 0.967647,
|
||||||
|
correct: 329,
|
||||||
|
incorrect: 11,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function e40() {
|
||||||
|
return {
|
||||||
|
result_id: `e40-perception-product-gate-${"a".repeat(64)}`,
|
||||||
|
created_at_utc: "2026-07-28T08:30:00Z",
|
||||||
|
source_session_id: "20260720T065719Z_viewer_live",
|
||||||
|
source_display_name: "RAVNOVES00",
|
||||||
|
status: "measured-leakage-resistant-product-gate",
|
||||||
|
profile_id: "e40-ravnoves00-leakage-resistant-product-gate/v1",
|
||||||
|
worker_node: "DESKTOP-OPJ8J04",
|
||||||
|
quality_gate_passed: true,
|
||||||
|
development_cross_validation: {
|
||||||
|
strategy: "dual-leakage-resistant-development-five-fold",
|
||||||
|
seed: "e40-development-cv-v1",
|
||||||
|
folds: 5,
|
||||||
|
items: 340,
|
||||||
|
validation_labels_used: false,
|
||||||
|
passed: true,
|
||||||
|
protocols: {
|
||||||
|
"contiguous-source-time-five-fold": e40DevelopmentProtocol({
|
||||||
|
accuracy: 0.908824,
|
||||||
|
correct: 309,
|
||||||
|
incorrect: 31,
|
||||||
|
}),
|
||||||
|
"whole-track-or-scene-window-five-fold": e40DevelopmentProtocol({
|
||||||
|
accuracy: 0.920588,
|
||||||
|
correct: 313,
|
||||||
|
incorrect: 27,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metrics: {
|
||||||
|
development_items: 340,
|
||||||
|
validation_items: 146,
|
||||||
|
terminal_outcomes: 146,
|
||||||
|
accounting_fraction: 1,
|
||||||
|
false_free_claims: 0,
|
||||||
|
high_severity_failures: 0,
|
||||||
|
dimensions: {
|
||||||
|
presence: e38Dimension({
|
||||||
|
accuracy: 0.90411,
|
||||||
|
correct: 132,
|
||||||
|
incorrect: 14,
|
||||||
|
passed: true,
|
||||||
|
}),
|
||||||
|
geometry_association: e38Dimension({
|
||||||
|
accuracy: 0.90411,
|
||||||
|
correct: 132,
|
||||||
|
incorrect: 14,
|
||||||
|
passed: true,
|
||||||
|
}),
|
||||||
|
freshness: e38Dimension({
|
||||||
|
accuracy: 0.958904,
|
||||||
|
correct: 140,
|
||||||
|
incorrect: 6,
|
||||||
|
passed: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
blocking_checks: [],
|
||||||
|
method: {
|
||||||
|
summary: "conservative policy plus camera-only softmax",
|
||||||
|
selection: "dual grouped development cross-validation",
|
||||||
|
dimension_projection: "presence plus immutable stratum",
|
||||||
|
},
|
||||||
|
limitations: ["RAVNOVES00 source scoped"],
|
||||||
|
authority,
|
||||||
|
access: "read-only",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
server = await createServer({
|
server = await createServer({
|
||||||
appType: "custom",
|
appType: "custom",
|
||||||
@@ -542,9 +643,11 @@ after(async () => {
|
|||||||
await server?.close();
|
await server?.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("decodes E31–E39 from separate read-only catalogs", async () => {
|
test("decodes E31–E40 from separate read-only catalogs", async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
const items = [e31(), e32(), e33(), e34(), e35(), e37(), e38(), e39()];
|
const items = [
|
||||||
|
e31(), e32(), e33(), e34(), e35(), e37(), e38(), e39(), e40(),
|
||||||
|
];
|
||||||
const decoded = await fetchAdvancedLaboratoryResults({
|
const decoded = await fetchAdvancedLaboratoryResults({
|
||||||
fetcher: async (input, init) => {
|
fetcher: async (input, init) => {
|
||||||
requests.push({ input: String(input), method: init?.method });
|
requests.push({ input: String(input), method: init?.method });
|
||||||
@@ -578,6 +681,15 @@ test("decodes E31–E39 from separate read-only catalogs", async () => {
|
|||||||
assert.equal(decoded.e39.metrics.dimensions.presence.accuracy, 0.849315);
|
assert.equal(decoded.e39.metrics.dimensions.presence.accuracy, 0.849315);
|
||||||
assert.equal(decoded.e39.metrics.highSeverityFailures, 8);
|
assert.equal(decoded.e39.metrics.highSeverityFailures, 8);
|
||||||
assert.equal(decoded.e39.qualityGatePassed, false);
|
assert.equal(decoded.e39.qualityGatePassed, false);
|
||||||
|
assert.equal(
|
||||||
|
decoded.e40.developmentCrossValidation.protocols
|
||||||
|
.wholeTrackOrSceneWindow.dimensions.presence.accuracy,
|
||||||
|
0.920588,
|
||||||
|
);
|
||||||
|
assert.equal(decoded.e40.developmentCrossValidation.validationLabelsUsed, false);
|
||||||
|
assert.equal(decoded.e40.metrics.dimensions.presence.accuracy, 0.90411);
|
||||||
|
assert.equal(decoded.e40.metrics.highSeverityFailures, 0);
|
||||||
|
assert.equal(decoded.e40.qualityGatePassed, true);
|
||||||
assert.deepEqual(requests, [
|
assert.deepEqual(requests, [
|
||||||
{ input: "/api/v1/laboratory/e31/results?limit=1", method: "GET" },
|
{ input: "/api/v1/laboratory/e31/results?limit=1", method: "GET" },
|
||||||
{ input: "/api/v1/laboratory/e32/results?limit=1", method: "GET" },
|
{ input: "/api/v1/laboratory/e32/results?limit=1", method: "GET" },
|
||||||
@@ -587,9 +699,36 @@ test("decodes E31–E39 from separate read-only catalogs", async () => {
|
|||||||
{ input: "/api/v1/laboratory/e37/results?limit=1", method: "GET" },
|
{ input: "/api/v1/laboratory/e37/results?limit=1", method: "GET" },
|
||||||
{ input: "/api/v1/laboratory/e38/results?limit=1", method: "GET" },
|
{ input: "/api/v1/laboratory/e38/results?limit=1", method: "GET" },
|
||||||
{ input: "/api/v1/laboratory/e39/results?limit=1", method: "GET" },
|
{ input: "/api/v1/laboratory/e39/results?limit=1", method: "GET" },
|
||||||
|
{ input: "/api/v1/laboratory/e40/results?limit=1", method: "GET" },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("keeps valid LAB catalogs available when one transport endpoint fails", async () => {
|
||||||
|
const decoded = await fetchAdvancedLaboratoryResults({
|
||||||
|
fetcher: async (input) => {
|
||||||
|
const path = String(input);
|
||||||
|
if (path.includes("/e31/")) {
|
||||||
|
return new Response("", { status: 503 });
|
||||||
|
}
|
||||||
|
const item = path.includes("/e32/") ? e32()
|
||||||
|
: path.includes("/e33/") ? e33()
|
||||||
|
: path.includes("/e34/") ? e34()
|
||||||
|
: path.includes("/e35/") ? e35()
|
||||||
|
: path.includes("/e37/") ? e37()
|
||||||
|
: path.includes("/e38/") ? e38()
|
||||||
|
: path.includes("/e39/") ? e39() : e40();
|
||||||
|
return new Response(JSON.stringify(catalog(item)), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(decoded.e31, null);
|
||||||
|
assert.equal(decoded.e32.metrics.qualifiedPointsPublished, 2119302);
|
||||||
|
assert.equal(decoded.e40.qualityGatePassed, true);
|
||||||
|
});
|
||||||
|
|
||||||
test("rejects authority escalation in an accepted-looking result", async () => {
|
test("rejects authority escalation in an accepted-looking result", async () => {
|
||||||
const forged = {
|
const forged = {
|
||||||
...e31(),
|
...e31(),
|
||||||
@@ -604,7 +743,8 @@ test("rejects authority escalation in an accepted-looking result", async () => {
|
|||||||
: String(input).includes("/e34/") ? e34()
|
: String(input).includes("/e34/") ? e34()
|
||||||
: String(input).includes("/e35/") ? e35()
|
: String(input).includes("/e35/") ? e35()
|
||||||
: String(input).includes("/e37/") ? e37()
|
: String(input).includes("/e37/") ? e37()
|
||||||
: String(input).includes("/e38/") ? e38() : e39(),
|
: String(input).includes("/e38/") ? e38()
|
||||||
|
: String(input).includes("/e39/") ? e39() : e40(),
|
||||||
)), { status: 200 }),
|
)), { status: 200 }),
|
||||||
}),
|
}),
|
||||||
AdvancedLaboratoryContractError,
|
AdvancedLaboratoryContractError,
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ test("central composition files cannot silently become monoliths again", async (
|
|||||||
["App.tsx", 1_250],
|
["App.tsx", 1_250],
|
||||||
["workspaces/Workspaces.tsx", 1_200],
|
["workspaces/Workspaces.tsx", 1_200],
|
||||||
["workspaces/laboratory/LaboratoryArchiveWorkspace.tsx", 1_000],
|
["workspaces/laboratory/LaboratoryArchiveWorkspace.tsx", 1_000],
|
||||||
|
["core/laboratory/advancedResults.ts", 1_000],
|
||||||
|
["core/laboratory/e40ProductGate.ts", 500],
|
||||||
["styles/workspaces.css", 4_350],
|
["styles/workspaces.css", 4_350],
|
||||||
["styles/laboratory.css", 900],
|
["styles/laboratory.css", 900],
|
||||||
["styles/laboratory-reporting.css", 100],
|
["styles/laboratory-reporting.css", 100],
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ const e39ResultUrl = new URL(
|
|||||||
"../src/workspaces/laboratory/E39Result.tsx",
|
"../src/workspaces/laboratory/E39Result.tsx",
|
||||||
import.meta.url,
|
import.meta.url,
|
||||||
);
|
);
|
||||||
|
const e40ResultUrl = new URL(
|
||||||
|
"../src/workspaces/laboratory/E40Result.tsx",
|
||||||
|
import.meta.url,
|
||||||
|
);
|
||||||
const e35StylesUrl = new URL(
|
const e35StylesUrl = new URL(
|
||||||
"../src/styles/e35-degradation-recovery.css",
|
"../src/styles/e35-degradation-recovery.css",
|
||||||
import.meta.url,
|
import.meta.url,
|
||||||
@@ -261,6 +265,28 @@ test("E39 reports refinement and the CV-to-validation gap through the canonical
|
|||||||
assert.match(advancedSource, /<E39Result/);
|
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([
|
||||||
|
readFile(e40ResultUrl, "utf8"),
|
||||||
|
readFile(advancedLaboratoryResultUrl, "utf8"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.match(e40Source, /<LaboratoryWorkTemplate/);
|
||||||
|
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, /не подбирая профиль по validation/);
|
||||||
|
assert.doesNotMatch(
|
||||||
|
e40Source,
|
||||||
|
/className="laboratory-(?:summary|result-summary|result-metrics|result-conclusion)"/,
|
||||||
|
);
|
||||||
|
assert.match(advancedSource, /id: "e40-perception-product-gate"/);
|
||||||
|
assert.match(advancedSource, /<E40Result/);
|
||||||
|
});
|
||||||
|
|
||||||
test("the primary point-cloud viewer restores from fullscreen on Escape", async () => {
|
test("the primary point-cloud viewer restores from fullscreen on Escape", async () => {
|
||||||
const workspacesSource = await readFile(workspacesUrl, "utf8");
|
const workspacesSource = await readFile(workspacesUrl, "utf8");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user