feat(lab): publish E31 through E33 results
This commit is contained in:
@@ -0,0 +1,366 @@
|
|||||||
|
export interface E31LaboratoryResult {
|
||||||
|
resultId: string;
|
||||||
|
createdAtUtc: string | null;
|
||||||
|
sourceSessionId: string;
|
||||||
|
status: "accepted-diagnostic-source-profile";
|
||||||
|
eligibleForE32: true;
|
||||||
|
profileId: string;
|
||||||
|
producerSha256: string;
|
||||||
|
metrics: {
|
||||||
|
frameCount: number;
|
||||||
|
availableBindingCount: number;
|
||||||
|
availableFraction: number;
|
||||||
|
lidarCameraP95Ms: number;
|
||||||
|
posePointP95Ms: number;
|
||||||
|
selectedOffsetMs: number;
|
||||||
|
correspondenceCount: number;
|
||||||
|
supportedFraction: number;
|
||||||
|
semanticSelfSampleCount: number;
|
||||||
|
semanticSelfCollateralCount: number;
|
||||||
|
exactGeometryCorrectionCount: number;
|
||||||
|
geometryPointMaskStatus: string;
|
||||||
|
};
|
||||||
|
limitations: readonly string[];
|
||||||
|
access: "read-only";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface E32LaboratoryResult {
|
||||||
|
resultId: string;
|
||||||
|
createdAtUtc: string | null;
|
||||||
|
sourceSessionId: string;
|
||||||
|
status: "accepted-diagnostic-track-geometry-replay";
|
||||||
|
profileId: string;
|
||||||
|
producerSha256: string;
|
||||||
|
e31ResultId: string;
|
||||||
|
metrics: {
|
||||||
|
framesTotal: number;
|
||||||
|
framesSourceAvailable: number;
|
||||||
|
semanticPublished: number;
|
||||||
|
semanticMasked: number;
|
||||||
|
geometryPublished: number;
|
||||||
|
observationsArbitrated: number;
|
||||||
|
overlappingClaimsRemoved: number;
|
||||||
|
qualifiedPointsPublished: number;
|
||||||
|
qualifiedPointsWithheld: number;
|
||||||
|
frameProcessingP95Ms: number;
|
||||||
|
buildElapsedMs: number;
|
||||||
|
};
|
||||||
|
access: "read-only";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface E33LaboratoryResult {
|
||||||
|
resultId: string;
|
||||||
|
createdAtUtc: string | null;
|
||||||
|
sourceSessionId: string;
|
||||||
|
status: "accepted-recorded-source-paced-shadow";
|
||||||
|
e32ResultId: string;
|
||||||
|
pipelineId: string;
|
||||||
|
mode: string;
|
||||||
|
worker: {
|
||||||
|
node: string;
|
||||||
|
containerImage: string;
|
||||||
|
python: string;
|
||||||
|
numpy: string;
|
||||||
|
};
|
||||||
|
metrics: {
|
||||||
|
sourceFrames: number;
|
||||||
|
deliveredFrames: number;
|
||||||
|
inputSuperseded: number;
|
||||||
|
resultSuperseded: number;
|
||||||
|
effectiveDeliveryFps: number;
|
||||||
|
deadlineMissFraction: number;
|
||||||
|
processingP95Ms: number;
|
||||||
|
releaseLagP95Ms: number;
|
||||||
|
resultAgeP95Ms: number;
|
||||||
|
processRssP95Mib: number;
|
||||||
|
gpuUtilizationP95Percent: number;
|
||||||
|
gpuVisible: boolean;
|
||||||
|
workQueueCapacity: number;
|
||||||
|
resultQueueCapacity: number;
|
||||||
|
wallToIdealRatio: number;
|
||||||
|
};
|
||||||
|
access: "read-only";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdvancedLaboratoryResults {
|
||||||
|
e31: E31LaboratoryResult | null;
|
||||||
|
e32: E32LaboratoryResult | null;
|
||||||
|
e33: E33LaboratoryResult | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdvancedLaboratoryContractError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "AdvancedLaboratoryContractError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 AdvancedLaboratoryContractError(`${label}: ожидался объект.`);
|
||||||
|
}
|
||||||
|
return value as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringValue(value: unknown, label: string): string {
|
||||||
|
if (typeof value !== "string" || !value.trim()) {
|
||||||
|
throw new AdvancedLaboratoryContractError(`${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 AdvancedLaboratoryContractError(`${label}: ожидалось число.`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function integerValue(value: unknown, label: string): number {
|
||||||
|
const parsed = numberValue(value, label);
|
||||||
|
if (!Number.isSafeInteger(parsed)) {
|
||||||
|
throw new AdvancedLaboratoryContractError(`${label}: ожидалось целое число.`);
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function booleanValue(value: unknown, label: string): boolean {
|
||||||
|
if (typeof value !== "boolean") {
|
||||||
|
throw new AdvancedLaboratoryContractError(`${label}: ожидался boolean.`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function trueValue(value: unknown, label: string): true {
|
||||||
|
if (value !== true) {
|
||||||
|
throw new AdvancedLaboratoryContractError(`${label}: ожидалось true.`);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function finiteNumber(value: unknown, label: string): number {
|
||||||
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||||
|
throw new AdvancedLaboratoryContractError(`${label}: ожидалось число.`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exactString<T extends string>(
|
||||||
|
value: unknown,
|
||||||
|
expected: T,
|
||||||
|
label: string,
|
||||||
|
): T {
|
||||||
|
if (value !== expected) {
|
||||||
|
throw new AdvancedLaboratoryContractError(`${label}: неверное значение.`);
|
||||||
|
}
|
||||||
|
return expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
function strings(value: unknown, label: string): readonly string[] {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new AdvancedLaboratoryContractError(`${label}: ожидался массив.`);
|
||||||
|
}
|
||||||
|
return value.map((item, index) => stringValue(item, `${label}[${index}]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 AdvancedLaboratoryContractError(`${label}: неверный content id.`);
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sha256(value: unknown, label: string): string {
|
||||||
|
const parsed = stringValue(value, label);
|
||||||
|
if (!/^[a-f0-9]{64}$/.test(parsed)) {
|
||||||
|
throw new AdvancedLaboratoryContractError(`${label}: неверный SHA-256.`);
|
||||||
|
}
|
||||||
|
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 AdvancedLaboratoryContractError(`${label}: запрещённые полномочия.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCatalog<T>(
|
||||||
|
payload: unknown,
|
||||||
|
parseItem: (value: unknown) => T,
|
||||||
|
): T | null {
|
||||||
|
const value = record(payload, "Каталог LAB");
|
||||||
|
exactString(
|
||||||
|
value.schema_version,
|
||||||
|
"missioncore.laboratory-advanced-catalog/v1",
|
||||||
|
"Каталог LAB.schema_version",
|
||||||
|
);
|
||||||
|
booleanValue(value.configured, "Каталог LAB.configured");
|
||||||
|
integerValue(value.candidate_total, "Каталог LAB.candidate_total");
|
||||||
|
integerValue(value.invalid_total, "Каталог LAB.invalid_total");
|
||||||
|
exactString(value.access, "read-only", "Каталог LAB.access");
|
||||||
|
if (!Array.isArray(value.items)) {
|
||||||
|
throw new AdvancedLaboratoryContractError("Каталог LAB.items: ожидался массив.");
|
||||||
|
}
|
||||||
|
if (value.items.length > 1) {
|
||||||
|
throw new AdvancedLaboratoryContractError("Каталог LAB.items: нарушен limit=1.");
|
||||||
|
}
|
||||||
|
return value.items.length ? parseItem(value.items[0]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseE31(value: unknown): E31LaboratoryResult {
|
||||||
|
const item = record(value, "E31");
|
||||||
|
const metrics = record(item.metrics, "E31.metrics");
|
||||||
|
diagnosticAuthority(item.authority, "E31.authority");
|
||||||
|
return {
|
||||||
|
resultId: contentId(item.result_id, "e31-source-qualification", "E31.result_id"),
|
||||||
|
createdAtUtc: optionalString(item.created_at_utc, "E31.created_at_utc"),
|
||||||
|
sourceSessionId: stringValue(item.source_session_id, "E31.source_session_id"),
|
||||||
|
status: exactString(
|
||||||
|
item.status,
|
||||||
|
"accepted-diagnostic-source-profile",
|
||||||
|
"E31.status",
|
||||||
|
),
|
||||||
|
eligibleForE32: trueValue(item.eligible_for_e32, "E31.eligible_for_e32"),
|
||||||
|
profileId: stringValue(item.profile_id, "E31.profile_id"),
|
||||||
|
producerSha256: sha256(item.producer_sha256, "E31.producer_sha256"),
|
||||||
|
metrics: {
|
||||||
|
frameCount: integerValue(metrics.frame_count, "E31.metrics.frame_count"),
|
||||||
|
availableBindingCount: integerValue(metrics.available_binding_count, "E31.metrics.available_binding_count"),
|
||||||
|
availableFraction: numberValue(metrics.available_fraction, "E31.metrics.available_fraction"),
|
||||||
|
lidarCameraP95Ms: numberValue(metrics.lidar_camera_p95_ms, "E31.metrics.lidar_camera_p95_ms"),
|
||||||
|
posePointP95Ms: numberValue(metrics.pose_point_p95_ms, "E31.metrics.pose_point_p95_ms"),
|
||||||
|
selectedOffsetMs: finiteNumber(metrics.selected_offset_ms, "E31.metrics.selected_offset_ms"),
|
||||||
|
correspondenceCount: integerValue(metrics.correspondence_count, "E31.metrics.correspondence_count"),
|
||||||
|
supportedFraction: numberValue(metrics.supported_fraction, "E31.metrics.supported_fraction"),
|
||||||
|
semanticSelfSampleCount: integerValue(metrics.semantic_self_sample_count, "E31.metrics.semantic_self_sample_count"),
|
||||||
|
semanticSelfCollateralCount: integerValue(metrics.semantic_self_collateral_count, "E31.metrics.semantic_self_collateral_count"),
|
||||||
|
exactGeometryCorrectionCount: integerValue(metrics.exact_geometry_correction_count, "E31.metrics.exact_geometry_correction_count"),
|
||||||
|
geometryPointMaskStatus: stringValue(metrics.geometry_point_mask_status, "E31.metrics.geometry_point_mask_status"),
|
||||||
|
},
|
||||||
|
limitations: strings(item.limitations, "E31.limitations"),
|
||||||
|
access: exactString(item.access, "read-only", "E31.access"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseE32(value: unknown): E32LaboratoryResult {
|
||||||
|
const item = record(value, "E32");
|
||||||
|
const metrics = record(item.metrics, "E32.metrics");
|
||||||
|
diagnosticAuthority(item.authority, "E32.authority");
|
||||||
|
return {
|
||||||
|
resultId: contentId(item.result_id, "e32-track-geometry", "E32.result_id"),
|
||||||
|
createdAtUtc: optionalString(item.created_at_utc, "E32.created_at_utc"),
|
||||||
|
sourceSessionId: stringValue(item.source_session_id, "E32.source_session_id"),
|
||||||
|
status: exactString(
|
||||||
|
item.status,
|
||||||
|
"accepted-diagnostic-track-geometry-replay",
|
||||||
|
"E32.status",
|
||||||
|
),
|
||||||
|
profileId: stringValue(item.profile_id, "E32.profile_id"),
|
||||||
|
producerSha256: sha256(item.producer_sha256, "E32.producer_sha256"),
|
||||||
|
e31ResultId: contentId(item.e31_result_id, "e31-source-qualification", "E32.e31_result_id"),
|
||||||
|
metrics: {
|
||||||
|
framesTotal: integerValue(metrics.frames_total, "E32.metrics.frames_total"),
|
||||||
|
framesSourceAvailable: integerValue(metrics.frames_source_available, "E32.metrics.frames_source_available"),
|
||||||
|
semanticPublished: integerValue(metrics.semantic_published, "E32.metrics.semantic_published"),
|
||||||
|
semanticMasked: integerValue(metrics.semantic_masked, "E32.metrics.semantic_masked"),
|
||||||
|
geometryPublished: integerValue(metrics.geometry_published, "E32.metrics.geometry_published"),
|
||||||
|
observationsArbitrated: integerValue(metrics.observations_arbitrated, "E32.metrics.observations_arbitrated"),
|
||||||
|
overlappingClaimsRemoved: integerValue(metrics.overlapping_claims_removed, "E32.metrics.overlapping_claims_removed"),
|
||||||
|
qualifiedPointsPublished: integerValue(metrics.qualified_points_published, "E32.metrics.qualified_points_published"),
|
||||||
|
qualifiedPointsWithheld: integerValue(metrics.qualified_points_withheld, "E32.metrics.qualified_points_withheld"),
|
||||||
|
frameProcessingP95Ms: numberValue(metrics.frame_processing_p95_ms, "E32.metrics.frame_processing_p95_ms"),
|
||||||
|
buildElapsedMs: numberValue(metrics.build_elapsed_ms, "E32.metrics.build_elapsed_ms"),
|
||||||
|
},
|
||||||
|
access: exactString(item.access, "read-only", "E32.access"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseE33(value: unknown): E33LaboratoryResult {
|
||||||
|
const item = record(value, "E33");
|
||||||
|
const worker = record(item.worker, "E33.worker");
|
||||||
|
const metrics = record(item.metrics, "E33.metrics");
|
||||||
|
diagnosticAuthority(item.authority, "E33.authority");
|
||||||
|
return {
|
||||||
|
resultId: contentId(item.result_id, "e33-worker-shadow", "E33.result_id"),
|
||||||
|
createdAtUtc: optionalString(item.created_at_utc, "E33.created_at_utc"),
|
||||||
|
sourceSessionId: stringValue(item.source_session_id, "E33.source_session_id"),
|
||||||
|
status: exactString(
|
||||||
|
item.status,
|
||||||
|
"accepted-recorded-source-paced-shadow",
|
||||||
|
"E33.status",
|
||||||
|
),
|
||||||
|
e32ResultId: contentId(item.e32_result_id, "e32-track-geometry", "E33.e32_result_id"),
|
||||||
|
pipelineId: stringValue(item.pipeline_id, "E33.pipeline_id"),
|
||||||
|
mode: stringValue(item.mode, "E33.mode"),
|
||||||
|
worker: {
|
||||||
|
node: stringValue(worker.node, "E33.worker.node"),
|
||||||
|
containerImage: stringValue(worker.container_image, "E33.worker.container_image"),
|
||||||
|
python: stringValue(worker.python, "E33.worker.python"),
|
||||||
|
numpy: stringValue(worker.numpy, "E33.worker.numpy"),
|
||||||
|
},
|
||||||
|
metrics: {
|
||||||
|
sourceFrames: integerValue(metrics.source_frames, "E33.metrics.source_frames"),
|
||||||
|
deliveredFrames: integerValue(metrics.delivered_frames, "E33.metrics.delivered_frames"),
|
||||||
|
inputSuperseded: integerValue(metrics.input_superseded, "E33.metrics.input_superseded"),
|
||||||
|
resultSuperseded: integerValue(metrics.result_superseded, "E33.metrics.result_superseded"),
|
||||||
|
effectiveDeliveryFps: numberValue(metrics.effective_delivery_fps, "E33.metrics.effective_delivery_fps"),
|
||||||
|
deadlineMissFraction: numberValue(metrics.deadline_miss_fraction, "E33.metrics.deadline_miss_fraction"),
|
||||||
|
processingP95Ms: numberValue(metrics.processing_p95_ms, "E33.metrics.processing_p95_ms"),
|
||||||
|
releaseLagP95Ms: numberValue(metrics.release_lag_p95_ms, "E33.metrics.release_lag_p95_ms"),
|
||||||
|
resultAgeP95Ms: numberValue(metrics.result_age_p95_ms, "E33.metrics.result_age_p95_ms"),
|
||||||
|
processRssP95Mib: numberValue(metrics.process_rss_p95_mib, "E33.metrics.process_rss_p95_mib"),
|
||||||
|
gpuUtilizationP95Percent: numberValue(metrics.gpu_utilization_p95_percent, "E33.metrics.gpu_utilization_p95_percent"),
|
||||||
|
gpuVisible: booleanValue(metrics.gpu_visible, "E33.metrics.gpu_visible"),
|
||||||
|
workQueueCapacity: integerValue(metrics.work_queue_capacity, "E33.metrics.work_queue_capacity"),
|
||||||
|
resultQueueCapacity: integerValue(metrics.result_queue_capacity, "E33.metrics.result_queue_capacity"),
|
||||||
|
wallToIdealRatio: numberValue(metrics.wall_to_ideal_ratio, "E33.metrics.wall_to_ideal_ratio"),
|
||||||
|
},
|
||||||
|
access: exactString(item.access, "read-only", "E33.access"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchOne<T>(
|
||||||
|
path: string,
|
||||||
|
parser: (value: unknown) => T,
|
||||||
|
fetcher: LaboratoryFetch,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<T | null> {
|
||||||
|
const response = await fetcher(path, {
|
||||||
|
method: "GET",
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new AdvancedLaboratoryContractError(`Каталог LAB недоступен: HTTP ${response.status}.`);
|
||||||
|
}
|
||||||
|
return parseCatalog(await response.json(), parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchAdvancedLaboratoryResults({
|
||||||
|
fetcher = fetch,
|
||||||
|
signal,
|
||||||
|
}: {
|
||||||
|
fetcher?: LaboratoryFetch;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
} = {}): Promise<AdvancedLaboratoryResults> {
|
||||||
|
const [e31, e32, e33] = await Promise.all([
|
||||||
|
fetchOne("/api/v1/laboratory/e31/results?limit=1", parseE31, fetcher, signal),
|
||||||
|
fetchOne("/api/v1/laboratory/e32/results?limit=1", parseE32, fetcher, signal),
|
||||||
|
fetchOne("/api/v1/laboratory/e33/results?limit=1", parseE33, fetcher, signal),
|
||||||
|
]);
|
||||||
|
return { e31, e32, e33 };
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { StatusBadge } from "@nodedc/ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
LaboratoryEvidence,
|
||||||
|
LaboratorySummary,
|
||||||
|
LaboratoryWorkTemplate,
|
||||||
|
} from "../../components/laboratory/LaboratoryPresentation";
|
||||||
|
import type { E31LaboratoryResult } from "../../core/laboratory/advancedResults";
|
||||||
|
import { formatNumber } from "../../presentation";
|
||||||
|
|
||||||
|
export function E31Result({
|
||||||
|
rigLabel,
|
||||||
|
result,
|
||||||
|
evidence,
|
||||||
|
}: {
|
||||||
|
rigLabel: string;
|
||||||
|
result: E31LaboratoryResult;
|
||||||
|
evidence: ReactNode;
|
||||||
|
}) {
|
||||||
|
const metrics = result.metrics;
|
||||||
|
return (
|
||||||
|
<LaboratoryWorkTemplate
|
||||||
|
summary={(
|
||||||
|
<LaboratorySummary
|
||||||
|
title="LAB E31 · квалификация source binding"
|
||||||
|
description="Проверена привязка camera, LiDAR и pose к неизменяемой записи RAVNOVES00. Профиль принят только для этого источника: время основано на host-arrival, а не на аппаратном firing time."
|
||||||
|
status="Допущено к E32"
|
||||||
|
statusTone="success"
|
||||||
|
facts={[
|
||||||
|
{ label: "Конфигурация", value: `${rigLabel} · camera + LiDAR + pose` },
|
||||||
|
{ label: "Источник", value: result.sourceSessionId },
|
||||||
|
{
|
||||||
|
label: "Привязано",
|
||||||
|
value: `${formatNumber(metrics.availableBindingCount, 0)} / ${formatNumber(metrics.frameCount, 0)} кадров`,
|
||||||
|
},
|
||||||
|
{ label: "Контур", value: "Source-scoped · read-only" },
|
||||||
|
]}
|
||||||
|
method={{
|
||||||
|
completeness: "complete",
|
||||||
|
executionClass: "deterministic",
|
||||||
|
pipelineId: result.profileId,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
kind: "source",
|
||||||
|
name: result.sourceSessionId,
|
||||||
|
version: "immutable RAVNOVES00 replay",
|
||||||
|
role: "camera, LiDAR and pose evidence",
|
||||||
|
identitySha256: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "algorithm",
|
||||||
|
name: "Source-time and calibration qualification",
|
||||||
|
version: result.profileId,
|
||||||
|
role: "timing, offset sensitivity and self-mask checks",
|
||||||
|
identitySha256: result.producerSha256,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
evidence={(
|
||||||
|
<LaboratoryEvidence
|
||||||
|
eyebrow="ИСХОДНОЕ ДОКАЗАТЕЛЬСТВО"
|
||||||
|
title="Запись, на которой квалифицирована привязка"
|
||||||
|
kind="recorded-replay"
|
||||||
|
resizable
|
||||||
|
>
|
||||||
|
{evidence}
|
||||||
|
</LaboratoryEvidence>
|
||||||
|
)}
|
||||||
|
result={(
|
||||||
|
<section className="laboratory-result-summary">
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<span className="section-eyebrow">РЕЗУЛЬТАТ И ВЫВОД</span>
|
||||||
|
<h2>Привязка воспроизводима в границах этого источника</h2>
|
||||||
|
</div>
|
||||||
|
<StatusBadge tone="success">Source profile принят</StatusBadge>
|
||||||
|
</header>
|
||||||
|
<div className="laboratory-result-metrics">
|
||||||
|
<div>
|
||||||
|
<span>Camera ↔ LiDAR p95</span>
|
||||||
|
<strong>{metrics.lidarCameraP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 2 })} мс</strong>
|
||||||
|
<small>host-arrival binding</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Pose ↔ point p95</span>
|
||||||
|
<strong>{metrics.posePointP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 2 })} мс</strong>
|
||||||
|
<small>по доступным кадрам</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Поддержка соответствий</span>
|
||||||
|
<strong>{(metrics.supportedFraction * 100).toLocaleString("ru-RU", { maximumFractionDigits: 1 })}%</strong>
|
||||||
|
<small>{formatNumber(metrics.correspondenceCount, 0)} соответствий</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Self-mask</span>
|
||||||
|
<strong>{formatNumber(metrics.semanticSelfSampleCount, 0)} образцов</strong>
|
||||||
|
<small>{formatNumber(metrics.semanticSelfCollateralCount, 0)} collateral</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>Результат диагностический: он не переносится на другой монтаж сенсоров и не выдаёт командных полномочий.</p>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { StatusBadge } from "@nodedc/ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
LaboratoryEvidence,
|
||||||
|
LaboratorySummary,
|
||||||
|
LaboratoryWorkTemplate,
|
||||||
|
} from "../../components/laboratory/LaboratoryPresentation";
|
||||||
|
import type { E32LaboratoryResult } from "../../core/laboratory/advancedResults";
|
||||||
|
import { formatNumber } from "../../presentation";
|
||||||
|
|
||||||
|
export function E32Result({
|
||||||
|
rigLabel,
|
||||||
|
result,
|
||||||
|
evidence,
|
||||||
|
}: {
|
||||||
|
rigLabel: string;
|
||||||
|
result: E32LaboratoryResult;
|
||||||
|
evidence: ReactNode;
|
||||||
|
}) {
|
||||||
|
const metrics = result.metrics;
|
||||||
|
return (
|
||||||
|
<LaboratoryWorkTemplate
|
||||||
|
summary={(
|
||||||
|
<LaboratorySummary
|
||||||
|
title="LAB E32 · TrackGeometry v1 replay"
|
||||||
|
description="Полная запись E29 пересчитана в детерминированный покадровый TrackGeometry. Перекрывающиеся LiDAR-точки получили единственного владельца, а диапазоны без квалифицированного источника не опубликованы."
|
||||||
|
status="Replay принят"
|
||||||
|
statusTone="success"
|
||||||
|
facts={[
|
||||||
|
{ label: "Конфигурация", value: `${rigLabel} · TrackGeometry v1` },
|
||||||
|
{ label: "Источник", value: result.sourceSessionId },
|
||||||
|
{ label: "Кадры", value: formatNumber(metrics.framesTotal, 0) },
|
||||||
|
{ label: "Контур", value: "Diagnostic replay · read-only" },
|
||||||
|
]}
|
||||||
|
method={{
|
||||||
|
completeness: "complete",
|
||||||
|
executionClass: "deterministic",
|
||||||
|
pipelineId: result.profileId,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
kind: "source",
|
||||||
|
name: result.e31ResultId,
|
||||||
|
version: "accepted E31 source profile",
|
||||||
|
role: "qualified timing and calibration binding",
|
||||||
|
identitySha256: result.e31ResultId.split("-").at(-1) ?? null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "algorithm",
|
||||||
|
name: "Exact E29 → TrackGeometry",
|
||||||
|
version: result.profileId,
|
||||||
|
role: "point ownership, masks and deterministic storage",
|
||||||
|
identitySha256: result.producerSha256,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
evidence={(
|
||||||
|
<LaboratoryEvidence
|
||||||
|
eyebrow="ИСХОДНОЕ ДОКАЗАТЕЛЬСТВО"
|
||||||
|
title="Полная запись, пересчитанная в TrackGeometry"
|
||||||
|
kind="recorded-replay"
|
||||||
|
resizable
|
||||||
|
>
|
||||||
|
{evidence}
|
||||||
|
</LaboratoryEvidence>
|
||||||
|
)}
|
||||||
|
result={(
|
||||||
|
<section className="laboratory-result-summary">
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<span className="section-eyebrow">РЕЗУЛЬТАТ И ВЫВОД</span>
|
||||||
|
<h2>TrackGeometry построен без повторной настройки порогов</h2>
|
||||||
|
</div>
|
||||||
|
<StatusBadge tone="success">Детерминированный replay</StatusBadge>
|
||||||
|
</header>
|
||||||
|
<div className="laboratory-result-metrics">
|
||||||
|
<div>
|
||||||
|
<span>Опубликовано точек</span>
|
||||||
|
<strong>{formatNumber(metrics.qualifiedPointsPublished, 0)}</strong>
|
||||||
|
<small>{formatNumber(metrics.qualifiedPointsWithheld, 0)} withheld</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Арбитраж наблюдений</span>
|
||||||
|
<strong>{formatNumber(metrics.observationsArbitrated, 0)}</strong>
|
||||||
|
<small>{formatNumber(metrics.overlappingClaimsRemoved, 0)} overlap снято</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Semantic</span>
|
||||||
|
<strong>{formatNumber(metrics.semanticPublished, 0)}</strong>
|
||||||
|
<small>{formatNumber(metrics.semanticMasked, 0)} self-mask</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Frame processing p95</span>
|
||||||
|
<strong>{metrics.frameProcessingP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс</strong>
|
||||||
|
<small>{(metrics.buildElapsedMs / 1000).toLocaleString("ru-RU", { maximumFractionDigits: 2 })} с build</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>Отсутствие LiDAR-точек по-прежнему не трактуется как свободное пространство; результат не является навигационным ground truth.</p>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { StatusBadge } from "@nodedc/ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
LaboratoryEvidence,
|
||||||
|
LaboratorySummary,
|
||||||
|
LaboratoryWorkTemplate,
|
||||||
|
} from "../../components/laboratory/LaboratoryPresentation";
|
||||||
|
import type { E33LaboratoryResult } from "../../core/laboratory/advancedResults";
|
||||||
|
import { formatNumber } from "../../presentation";
|
||||||
|
|
||||||
|
export function E33Result({
|
||||||
|
rigLabel,
|
||||||
|
result,
|
||||||
|
evidence,
|
||||||
|
}: {
|
||||||
|
rigLabel: string;
|
||||||
|
result: E33LaboratoryResult;
|
||||||
|
evidence: ReactNode;
|
||||||
|
}) {
|
||||||
|
const metrics = result.metrics;
|
||||||
|
return (
|
||||||
|
<LaboratoryWorkTemplate
|
||||||
|
summary={(
|
||||||
|
<LaboratorySummary
|
||||||
|
title="LAB E33 · worker shadow 1×"
|
||||||
|
description="Полная запись TrackGeometry выполнена отдельным bounded worker в темпе исходника 1×. Проверены очереди, доставка каждого кадра, задержка результата и ресурсы без подключения командного канала."
|
||||||
|
status="Worker gate пройден"
|
||||||
|
statusTone="success"
|
||||||
|
facts={[
|
||||||
|
{ label: "Конфигурация", value: `${rigLabel} · worker shadow` },
|
||||||
|
{ label: "Worker", value: result.worker.node },
|
||||||
|
{ label: "Доставка", value: `${formatNumber(metrics.deliveredFrames, 0)} / ${formatNumber(metrics.sourceFrames, 0)} кадров` },
|
||||||
|
{ label: "Контур", value: "Recorded-source-paced · 1×" },
|
||||||
|
]}
|
||||||
|
method={{
|
||||||
|
completeness: "complete",
|
||||||
|
executionClass: "deterministic",
|
||||||
|
pipelineId: result.pipelineId,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
kind: "source",
|
||||||
|
name: result.e32ResultId,
|
||||||
|
version: "accepted TrackGeometry replay",
|
||||||
|
role: "immutable frame stream",
|
||||||
|
identitySha256: result.e32ResultId.split("-").at(-1) ?? null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "runtime",
|
||||||
|
name: result.worker.node,
|
||||||
|
version: `${result.worker.python} · NumPy ${result.worker.numpy}`,
|
||||||
|
role: "bounded worker, work/result queues 2/2",
|
||||||
|
identitySha256: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
evidence={(
|
||||||
|
<LaboratoryEvidence
|
||||||
|
eyebrow="ИСХОДНОЕ ДОКАЗАТЕЛЬСТВО"
|
||||||
|
title="Запись, доставленная worker без потерь"
|
||||||
|
kind="recorded-replay"
|
||||||
|
resizable
|
||||||
|
>
|
||||||
|
{evidence}
|
||||||
|
</LaboratoryEvidence>
|
||||||
|
)}
|
||||||
|
result={(
|
||||||
|
<section className="laboratory-result-summary">
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<span className="section-eyebrow">РЕЗУЛЬТАТ И ВЫВОД</span>
|
||||||
|
<h2>Полный replay выдержан в темпе источника</h2>
|
||||||
|
</div>
|
||||||
|
<StatusBadge tone="success">
|
||||||
|
{formatNumber(metrics.deliveredFrames, 0)} / {formatNumber(metrics.sourceFrames, 0)}
|
||||||
|
</StatusBadge>
|
||||||
|
</header>
|
||||||
|
<div className="laboratory-result-metrics">
|
||||||
|
<div>
|
||||||
|
<span>Эффективная частота</span>
|
||||||
|
<strong>{metrics.effectiveDeliveryFps.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} FPS</strong>
|
||||||
|
<small>скорость источника 1×</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Result age p95</span>
|
||||||
|
<strong>{metrics.resultAgeP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс</strong>
|
||||||
|
<small>{(metrics.deadlineMissFraction * 100).toLocaleString("ru-RU", { maximumFractionDigits: 2 })}% deadline miss</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Processing p95</span>
|
||||||
|
<strong>{metrics.processingP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс</strong>
|
||||||
|
<small>{metrics.releaseLagP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс release lag</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Ресурсы p95</span>
|
||||||
|
<strong>{metrics.processRssP95Mib.toLocaleString("ru-RU", { maximumFractionDigits: 1 })} MiB</strong>
|
||||||
|
<small>GPU {metrics.gpuUtilizationP95Percent.toLocaleString("ru-RU", { maximumFractionDigits: 1 })}%</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>Очереди остались bounded, пропусков нет. Результат подтверждает только worker shadow и не даёт навигационных или safety-полномочий.</p>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -27,6 +27,12 @@ import {
|
|||||||
fetchE30ReviewCatalog,
|
fetchE30ReviewCatalog,
|
||||||
type E30ReviewResult,
|
type E30ReviewResult,
|
||||||
} from "../../core/laboratory/e30Review";
|
} from "../../core/laboratory/e30Review";
|
||||||
|
import {
|
||||||
|
fetchAdvancedLaboratoryResults,
|
||||||
|
type E31LaboratoryResult,
|
||||||
|
type E32LaboratoryResult,
|
||||||
|
type E33LaboratoryResult,
|
||||||
|
} from "../../core/laboratory/advancedResults";
|
||||||
import {
|
import {
|
||||||
fetchLidarLocalSurfaces,
|
fetchLidarLocalSurfaces,
|
||||||
type LidarLocalSurfaceModel,
|
type LidarLocalSurfaceModel,
|
||||||
@@ -35,6 +41,10 @@ import { formatNumber } from "../../presentation";
|
|||||||
import { E30ReviewWorkspace } from "../E30ReviewWorkspace";
|
import { E30ReviewWorkspace } from "../E30ReviewWorkspace";
|
||||||
import { LidarQualityWorkspace } from "../LidarQualityWorkspace";
|
import { LidarQualityWorkspace } from "../LidarQualityWorkspace";
|
||||||
import type { WorkspaceRendererProps } from "../contracts";
|
import type { WorkspaceRendererProps } from "../contracts";
|
||||||
|
import { E31Result } from "./E31Result";
|
||||||
|
import { E32Result } from "./E32Result";
|
||||||
|
import { E33Result } from "./E33Result";
|
||||||
|
import { RecordedReplayEvidence } from "./RecordedReplayEvidence";
|
||||||
|
|
||||||
type LaboratoryWorkspaceProps = WorkspaceRendererProps & {
|
type LaboratoryWorkspaceProps = WorkspaceRendererProps & {
|
||||||
SpatialView: ComponentType<WorkspaceRendererProps>;
|
SpatialView: ComponentType<WorkspaceRendererProps>;
|
||||||
@@ -45,6 +55,9 @@ type LaboratoryWorkId =
|
|||||||
| "e28-local-surface"
|
| "e28-local-surface"
|
||||||
| "e29-camera-geometry"
|
| "e29-camera-geometry"
|
||||||
| "e30-evidence-review"
|
| "e30-evidence-review"
|
||||||
|
| "e31-source-binding"
|
||||||
|
| "e32-track-geometry"
|
||||||
|
| "e33-worker-shadow"
|
||||||
| `session:${string}`;
|
| `session:${string}`;
|
||||||
|
|
||||||
function digestFromContentId(value: string | null | undefined): string | null {
|
function digestFromContentId(value: string | null | undefined): string | null {
|
||||||
@@ -535,6 +548,9 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
|
|||||||
const [e28Model, setE28Model] = useState<LidarLocalSurfaceModel | null>(null);
|
const [e28Model, setE28Model] = useState<LidarLocalSurfaceModel | null>(null);
|
||||||
const [e29Result, setE29Result] = useState<E29EvidenceResult | null>(null);
|
const [e29Result, setE29Result] = useState<E29EvidenceResult | null>(null);
|
||||||
const [e30Result, setE30Result] = useState<E30ReviewResult | null>(null);
|
const [e30Result, setE30Result] = useState<E30ReviewResult | null>(null);
|
||||||
|
const [e31Result, setE31Result] = useState<E31LaboratoryResult | null>(null);
|
||||||
|
const [e32Result, setE32Result] = useState<E32LaboratoryResult | null>(null);
|
||||||
|
const [e33Result, setE33Result] = useState<E33LaboratoryResult | null>(null);
|
||||||
const [evidenceLoading, setEvidenceLoading] = useState(true);
|
const [evidenceLoading, setEvidenceLoading] = useState(true);
|
||||||
const [evidenceError, setEvidenceError] = useState<string | null>(null);
|
const [evidenceError, setEvidenceError] = useState<string | null>(null);
|
||||||
const sessions = useObservationSessions({
|
const sessions = useObservationSessions({
|
||||||
@@ -566,18 +582,24 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
|
|||||||
fetchLidarLocalSurfaces({ signal: controller.signal }),
|
fetchLidarLocalSurfaces({ signal: controller.signal }),
|
||||||
fetchE29EvidenceCatalog({ signal: controller.signal }),
|
fetchE29EvidenceCatalog({ signal: controller.signal }),
|
||||||
fetchE30ReviewCatalog({ signal: controller.signal }),
|
fetchE30ReviewCatalog({ signal: controller.signal }),
|
||||||
]).then(([e28, e29, e30]) => {
|
fetchAdvancedLaboratoryResults({ signal: controller.signal }),
|
||||||
|
]).then(([e28, e29, e30, advanced]) => {
|
||||||
if (controller.signal.aborted) return;
|
if (controller.signal.aborted) return;
|
||||||
const nextE28 = e28.status === "fulfilled" ? e28.value.items[0] ?? null : null;
|
const nextE28 = e28.status === "fulfilled" ? e28.value.items[0] ?? null : null;
|
||||||
const nextE29 = e29.status === "fulfilled" ? e29.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 nextE30 = e30.status === "fulfilled" ? e30.value.items[0] ?? null : null;
|
||||||
|
const nextAdvanced = advanced.status === "fulfilled" ? advanced.value : null;
|
||||||
setE28Model(nextE28);
|
setE28Model(nextE28);
|
||||||
setE29Result(nextE29);
|
setE29Result(nextE29);
|
||||||
setE30Result(nextE30);
|
setE30Result(nextE30);
|
||||||
|
setE31Result(nextAdvanced?.e31 ?? null);
|
||||||
|
setE32Result(nextAdvanced?.e32 ?? null);
|
||||||
|
setE33Result(nextAdvanced?.e33 ?? null);
|
||||||
const failures = [
|
const failures = [
|
||||||
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–E33" : null,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
setEvidenceError(
|
setEvidenceError(
|
||||||
failures.length
|
failures.length
|
||||||
@@ -623,8 +645,34 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
|
|||||||
label: "LAB E30 · evidence review A2",
|
label: "LAB E30 · evidence review A2",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (e31Result && sourceSessions.has(e31Result.sourceSessionId)) {
|
||||||
|
items.push({
|
||||||
|
id: "e31-source-binding",
|
||||||
|
label: "LAB E31 · source binding",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (e32Result && sourceSessions.has(e32Result.sourceSessionId)) {
|
||||||
|
items.push({
|
||||||
|
id: "e32-track-geometry",
|
||||||
|
label: "LAB E32 · TrackGeometry v1",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (e33Result && sourceSessions.has(e33Result.sourceSessionId)) {
|
||||||
|
items.push({
|
||||||
|
id: "e33-worker-shadow",
|
||||||
|
label: "LAB E33 · worker shadow 1×",
|
||||||
|
});
|
||||||
|
}
|
||||||
return items;
|
return items;
|
||||||
}, [e28Model, e29Result, e30Result, sourceSessions]);
|
}, [
|
||||||
|
e28Model,
|
||||||
|
e29Result,
|
||||||
|
e30Result,
|
||||||
|
e31Result,
|
||||||
|
e32Result,
|
||||||
|
e33Result,
|
||||||
|
sourceSessions,
|
||||||
|
]);
|
||||||
const profiles = useMemo(() => {
|
const profiles = useMemo(() => {
|
||||||
const items: LaboratoryOption<LaboratoryProfileId>[] = [];
|
const items: LaboratoryOption<LaboratoryProfileId>[] = [];
|
||||||
if (sensorWorks.length) {
|
if (sensorWorks.length) {
|
||||||
@@ -660,6 +708,15 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
|
|||||||
const e30SourceSession = e30Result
|
const e30SourceSession = e30Result
|
||||||
? sourceSessions.get(e30Result.sourceSessionId) ?? null
|
? sourceSessions.get(e30Result.sourceSessionId) ?? null
|
||||||
: null;
|
: null;
|
||||||
|
const e31SourceSession = e31Result
|
||||||
|
? sourceSessions.get(e31Result.sourceSessionId) ?? null
|
||||||
|
: null;
|
||||||
|
const e32SourceSession = e32Result
|
||||||
|
? sourceSessions.get(e32Result.sourceSessionId) ?? null
|
||||||
|
: null;
|
||||||
|
const e33SourceSession = e33Result
|
||||||
|
? sourceSessions.get(e33Result.sourceSessionId) ?? null
|
||||||
|
: null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -721,6 +778,17 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
|
|||||||
}
|
}
|
||||||
if (next === "e30-evidence-review" && e30SourceSession) {
|
if (next === "e30-evidence-review" && e30SourceSession) {
|
||||||
void sessions.replay(e30SourceSession.id);
|
void sessions.replay(e30SourceSession.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const advancedSession = next === "e31-source-binding"
|
||||||
|
? e31SourceSession
|
||||||
|
: next === "e32-track-geometry"
|
||||||
|
? e32SourceSession
|
||||||
|
: next === "e33-worker-shadow"
|
||||||
|
? e33SourceSession
|
||||||
|
: null;
|
||||||
|
if (advancedSession) {
|
||||||
|
void sessions.replay(advancedSession.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -862,6 +930,45 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
|
|||||||
result={e30Result}
|
result={e30Result}
|
||||||
sourceSession={e30SourceSession}
|
sourceSession={e30SourceSession}
|
||||||
/>
|
/>
|
||||||
|
) : workId === "e31-source-binding" && e31Result && e31SourceSession ? (
|
||||||
|
<E31Result
|
||||||
|
rigLabel={rigLabel}
|
||||||
|
result={e31Result}
|
||||||
|
evidence={(
|
||||||
|
<RecordedReplayEvidence
|
||||||
|
props={props}
|
||||||
|
sourceSession={e31SourceSession}
|
||||||
|
loading={sessions.replayingSessionId === e31SourceSession.id}
|
||||||
|
error={sessions.failedSessionId === e31SourceSession.id ? sessions.error : null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : workId === "e32-track-geometry" && e32Result && e32SourceSession ? (
|
||||||
|
<E32Result
|
||||||
|
rigLabel={rigLabel}
|
||||||
|
result={e32Result}
|
||||||
|
evidence={(
|
||||||
|
<RecordedReplayEvidence
|
||||||
|
props={props}
|
||||||
|
sourceSession={e32SourceSession}
|
||||||
|
loading={sessions.replayingSessionId === e32SourceSession.id}
|
||||||
|
error={sessions.failedSessionId === e32SourceSession.id ? sessions.error : null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : workId === "e33-worker-shadow" && e33Result && e33SourceSession ? (
|
||||||
|
<E33Result
|
||||||
|
rigLabel={rigLabel}
|
||||||
|
result={e33Result}
|
||||||
|
evidence={(
|
||||||
|
<RecordedReplayEvidence
|
||||||
|
props={props}
|
||||||
|
sourceSession={e33SourceSession}
|
||||||
|
loading={sessions.replayingSessionId === e33SourceSession.id}
|
||||||
|
error={sessions.failedSessionId === e33SourceSession.id ? sessions.error : null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
) : selectedSession ? (
|
) : selectedSession ? (
|
||||||
<PublishedLaboratoryResult
|
<PublishedLaboratoryResult
|
||||||
props={props}
|
props={props}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type { ComponentType } from "react";
|
||||||
|
import { Icon } from "@nodedc/ui-react";
|
||||||
|
|
||||||
|
import type { ObservationSessionSummary } from "../../core/observation/sessionArchive";
|
||||||
|
import type { WorkspaceRendererProps } from "../contracts";
|
||||||
|
|
||||||
|
type ReplayEvidenceProps = WorkspaceRendererProps & {
|
||||||
|
SpatialView: ComponentType<WorkspaceRendererProps>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function RecordedReplayEvidence({
|
||||||
|
props,
|
||||||
|
sourceSession,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
}: {
|
||||||
|
props: ReplayEvidenceProps;
|
||||||
|
sourceSession: ObservationSessionSummary;
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}) {
|
||||||
|
if (props.recordedReplay?.sessionId === sourceSession.id) {
|
||||||
|
return <props.SpatialView {...props} />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="laboratory-result-pending" role="status">
|
||||||
|
{loading ? <span className="busy-indicator" aria-hidden="true" /> : <Icon name="database" size={20} />}
|
||||||
|
<strong>{loading ? "Проверяем и открываем запись" : "Исходная запись не открыта"}</strong>
|
||||||
|
<p>
|
||||||
|
{error ?? (loading
|
||||||
|
? "Viewer появится после серверной проверки неизменяемого RRD."
|
||||||
|
: "Выберите работу повторно, чтобы открыть связанный источник.")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { after, before, test } from "node:test";
|
||||||
|
|
||||||
|
import { createServer } from "vite";
|
||||||
|
|
||||||
|
let server;
|
||||||
|
let fetchAdvancedLaboratoryResults;
|
||||||
|
let AdvancedLaboratoryContractError;
|
||||||
|
|
||||||
|
const authority = {
|
||||||
|
commands_enabled: false,
|
||||||
|
navigation_or_safety_accepted: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
function catalog(item) {
|
||||||
|
return {
|
||||||
|
schema_version: "missioncore.laboratory-advanced-catalog/v1",
|
||||||
|
configured: true,
|
||||||
|
items: [item],
|
||||||
|
candidate_total: 1,
|
||||||
|
invalid_total: 0,
|
||||||
|
access: "read-only",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function e31() {
|
||||||
|
return {
|
||||||
|
result_id: `e31-source-qualification-${"1".repeat(64)}`,
|
||||||
|
created_at_utc: "2026-07-27T10:00:00Z",
|
||||||
|
source_session_id: "20260720T065719Z_viewer_live",
|
||||||
|
status: "accepted-diagnostic-source-profile",
|
||||||
|
eligible_for_e32: true,
|
||||||
|
profile_id: "e31-ravnoves00-source-qualification/v1",
|
||||||
|
producer_sha256: "a".repeat(64),
|
||||||
|
metrics: {
|
||||||
|
frame_count: 4489,
|
||||||
|
available_binding_count: 3928,
|
||||||
|
available_fraction: 0.875,
|
||||||
|
lidar_camera_p95_ms: 70.78,
|
||||||
|
pose_point_p95_ms: 21.85,
|
||||||
|
selected_offset_ms: 0,
|
||||||
|
correspondence_count: 87,
|
||||||
|
supported_fraction: 1,
|
||||||
|
semantic_self_sample_count: 8,
|
||||||
|
semantic_self_collateral_count: 0,
|
||||||
|
exact_geometry_correction_count: 2,
|
||||||
|
geometry_point_mask_status: "rejected",
|
||||||
|
},
|
||||||
|
limitations: ["source scoped"],
|
||||||
|
authority,
|
||||||
|
access: "read-only",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function e32() {
|
||||||
|
return {
|
||||||
|
result_id: `e32-track-geometry-${"2".repeat(64)}`,
|
||||||
|
created_at_utc: "2026-07-27T10:10:00Z",
|
||||||
|
source_session_id: "20260720T065719Z_viewer_live",
|
||||||
|
status: "accepted-diagnostic-track-geometry-replay",
|
||||||
|
profile_id: "e32-exact-e29-to-track-geometry/v1",
|
||||||
|
producer_sha256: "b".repeat(64),
|
||||||
|
e31_result_id: e31().result_id,
|
||||||
|
metrics: {
|
||||||
|
frames_total: 4489,
|
||||||
|
frames_source_available: 3928,
|
||||||
|
semantic_published: 20119,
|
||||||
|
semantic_masked: 385,
|
||||||
|
geometry_published: 21318,
|
||||||
|
observations_arbitrated: 709,
|
||||||
|
overlapping_claims_removed: 4461,
|
||||||
|
qualified_points_published: 2119302,
|
||||||
|
qualified_points_withheld: 1558,
|
||||||
|
frame_processing_p95_ms: 3.081,
|
||||||
|
build_elapsed_ms: 10194.25,
|
||||||
|
},
|
||||||
|
authority,
|
||||||
|
access: "read-only",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function e33() {
|
||||||
|
return {
|
||||||
|
result_id: `e33-worker-shadow-${"3".repeat(64)}`,
|
||||||
|
created_at_utc: "2026-07-27T10:36:13Z",
|
||||||
|
source_session_id: "20260720T065719Z_viewer_live",
|
||||||
|
status: "accepted-recorded-source-paced-shadow",
|
||||||
|
e32_result_id: e32().result_id,
|
||||||
|
pipeline_id: "e32-track-geometry-recorded-source-paced-worker-shadow/v1",
|
||||||
|
mode: "full-session-qualification",
|
||||||
|
worker: {
|
||||||
|
node: "DESKTOP-OPJ8J04",
|
||||||
|
container_image: "qualified@sha256:abc",
|
||||||
|
python: "3.12.3",
|
||||||
|
numpy: "1.26.4",
|
||||||
|
},
|
||||||
|
metrics: {
|
||||||
|
source_frames: 4489,
|
||||||
|
delivered_frames: 4489,
|
||||||
|
input_superseded: 0,
|
||||||
|
result_superseded: 0,
|
||||||
|
effective_delivery_fps: 10.006,
|
||||||
|
deadline_miss_fraction: 0,
|
||||||
|
processing_p95_ms: 0.749,
|
||||||
|
release_lag_p95_ms: 0.461,
|
||||||
|
result_age_p95_ms: 2.668,
|
||||||
|
process_rss_p95_mib: 90.32,
|
||||||
|
gpu_utilization_p95_percent: 52,
|
||||||
|
gpu_visible: true,
|
||||||
|
work_queue_capacity: 2,
|
||||||
|
result_queue_capacity: 2,
|
||||||
|
wall_to_ideal_ratio: 1.0001,
|
||||||
|
},
|
||||||
|
authority,
|
||||||
|
access: "read-only",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
server = await createServer({
|
||||||
|
appType: "custom",
|
||||||
|
logLevel: "silent",
|
||||||
|
server: { middlewareMode: true },
|
||||||
|
});
|
||||||
|
({
|
||||||
|
fetchAdvancedLaboratoryResults,
|
||||||
|
AdvancedLaboratoryContractError,
|
||||||
|
} = await server.ssrLoadModule("/src/core/laboratory/advancedResults.ts"));
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await server?.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("decodes E31, E32 and E33 from separate read-only catalogs", async () => {
|
||||||
|
const requests = [];
|
||||||
|
const items = [e31(), e32(), e33()];
|
||||||
|
const decoded = await fetchAdvancedLaboratoryResults({
|
||||||
|
fetcher: async (input, init) => {
|
||||||
|
requests.push({ input: String(input), method: init?.method });
|
||||||
|
return new Response(JSON.stringify(catalog(items[requests.length - 1])), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.equal(decoded.e31.metrics.availableBindingCount, 3928);
|
||||||
|
assert.equal(decoded.e32.metrics.qualifiedPointsPublished, 2119302);
|
||||||
|
assert.equal(decoded.e33.metrics.deliveredFrames, 4489);
|
||||||
|
assert.deepEqual(requests, [
|
||||||
|
{ input: "/api/v1/laboratory/e31/results?limit=1", method: "GET" },
|
||||||
|
{ input: "/api/v1/laboratory/e32/results?limit=1", method: "GET" },
|
||||||
|
{ input: "/api/v1/laboratory/e33/results?limit=1", method: "GET" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rejects authority escalation in an accepted-looking result", async () => {
|
||||||
|
const forged = {
|
||||||
|
...e31(),
|
||||||
|
authority: { ...authority, commands_enabled: true },
|
||||||
|
};
|
||||||
|
await assert.rejects(
|
||||||
|
() => fetchAdvancedLaboratoryResults({
|
||||||
|
fetcher: async (input) => new Response(JSON.stringify(catalog(
|
||||||
|
String(input).includes("/e31/") ? forged
|
||||||
|
: String(input).includes("/e32/") ? e32() : e33(),
|
||||||
|
)), { status: 200 }),
|
||||||
|
}),
|
||||||
|
AdvancedLaboratoryContractError,
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -142,6 +142,25 @@ class _E30Chain:
|
|||||||
human_decisions: tuple[dict[str, Any], ...]
|
human_decisions: tuple[dict[str, Any], ...]
|
||||||
|
|
||||||
|
|
||||||
|
def read_e31_source_qualification(root: Path) -> E31SourceQualification:
|
||||||
|
"""Open and fully validate one immutable E31 result."""
|
||||||
|
|
||||||
|
resolved = _safe_result_root(root, _RESULT_ID)
|
||||||
|
manifest = _read_json(resolved / E31_MANIFEST_NAME)
|
||||||
|
identity = _object(manifest.get("identity"), "E31 identity")
|
||||||
|
result = _read_existing(resolved, identity)
|
||||||
|
if (
|
||||||
|
result.report.get("schema_version") != E31_SOURCE_QUALIFICATION_REPORT_SCHEMA
|
||||||
|
or result.report.get("result_id") != resolved.name
|
||||||
|
or result.report.get("authority") != _diagnostic_authority()
|
||||||
|
or result.manifest.get("status") != result.report.get("status")
|
||||||
|
or result.manifest.get("eligible_for_e32")
|
||||||
|
!= result.report.get("eligible_for_e32")
|
||||||
|
):
|
||||||
|
raise E31SourceQualificationError("E31 result report is inconsistent")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def build_e31_source_qualification(
|
def build_e31_source_qualification(
|
||||||
*,
|
*,
|
||||||
source_pack_root: Path,
|
source_pack_root: Path,
|
||||||
|
|||||||
@@ -0,0 +1,418 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import re
|
||||||
|
from collections.abc import Callable
|
||||||
|
from functools import lru_cache
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Final
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Query
|
||||||
|
|
||||||
|
from k1link.compute.e31_source_qualification import (
|
||||||
|
E31SourceQualification,
|
||||||
|
E31SourceQualificationError,
|
||||||
|
read_e31_source_qualification,
|
||||||
|
)
|
||||||
|
from k1link.compute.e32_track_geometry_replay import (
|
||||||
|
E32TrackGeometryReplay,
|
||||||
|
E32TrackGeometryReplayError,
|
||||||
|
read_e32_track_geometry_replay,
|
||||||
|
)
|
||||||
|
from k1link.compute.e33_worker_shadow import (
|
||||||
|
E33WorkerShadowError,
|
||||||
|
E33WorkerShadowResult,
|
||||||
|
read_e33_worker_shadow_result,
|
||||||
|
)
|
||||||
|
|
||||||
|
LABORATORY_ADVANCED_CATALOG_SCHEMA: Final = (
|
||||||
|
"missioncore.laboratory-advanced-catalog/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
_E31_RESULT_ID = re.compile(r"^e31-source-qualification-[a-f0-9]{64}$")
|
||||||
|
_E32_RESULT_ID = re.compile(r"^e32-track-geometry-[a-f0-9]{64}$")
|
||||||
|
_E33_RESULT_ID = re.compile(r"^e33-worker-shadow-[a-f0-9]{64}$")
|
||||||
|
|
||||||
|
RootProvider = Callable[[], Path | None]
|
||||||
|
|
||||||
|
|
||||||
|
def _result_signature(root: Path) -> tuple[int, ...]:
|
||||||
|
signature: list[int] = []
|
||||||
|
for path in sorted(root.iterdir(), key=lambda item: item.name):
|
||||||
|
if not path.is_file() or path.is_symlink():
|
||||||
|
continue
|
||||||
|
stat = path.stat()
|
||||||
|
signature.extend((stat.st_size, stat.st_mtime_ns))
|
||||||
|
return tuple(signature)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=16)
|
||||||
|
def _read_e31_cached(
|
||||||
|
root_text: str,
|
||||||
|
signature: tuple[int, ...],
|
||||||
|
) -> E31SourceQualification:
|
||||||
|
del signature
|
||||||
|
return read_e31_source_qualification(Path(root_text))
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=16)
|
||||||
|
def _read_e32_cached(
|
||||||
|
root_text: str,
|
||||||
|
signature: tuple[int, ...],
|
||||||
|
) -> E32TrackGeometryReplay:
|
||||||
|
del signature
|
||||||
|
return read_e32_track_geometry_replay(Path(root_text))
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=16)
|
||||||
|
def _read_e33_cached(
|
||||||
|
root_text: str,
|
||||||
|
signature: tuple[int, ...],
|
||||||
|
) -> E33WorkerShadowResult:
|
||||||
|
del signature
|
||||||
|
return read_e33_worker_shadow_result(Path(root_text))
|
||||||
|
|
||||||
|
|
||||||
|
def _configured_root(provider: RootProvider) -> Path | None:
|
||||||
|
value = provider()
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
candidate = value.expanduser().absolute()
|
||||||
|
if candidate.is_symlink():
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
root = candidate.resolve(strict=True)
|
||||||
|
except OSError:
|
||||||
|
return None
|
||||||
|
if not root.is_dir():
|
||||||
|
return None
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
def _candidates(root: Path, pattern: re.Pattern[str]) -> list[Path]:
|
||||||
|
return sorted(
|
||||||
|
(
|
||||||
|
candidate
|
||||||
|
for candidate in root.iterdir()
|
||||||
|
if candidate.is_dir()
|
||||||
|
and not candidate.is_symlink()
|
||||||
|
and pattern.fullmatch(candidate.name) is not None
|
||||||
|
),
|
||||||
|
key=lambda candidate: candidate.stat().st_mtime_ns,
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _object(value: object, label: str) -> dict[str, Any]:
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
raise ValueError(f"{label} is invalid")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _project_e31(result: E31SourceQualification) -> dict[str, object]:
|
||||||
|
identity = _object(result.manifest.get("identity"), "E31 identity")
|
||||||
|
source = _object(identity.get("source"), "E31 source")
|
||||||
|
method = _object(identity.get("method"), "E31 method")
|
||||||
|
source_time = _object(result.report.get("source_time"), "E31 source time")
|
||||||
|
lidar_camera = _object(
|
||||||
|
source_time.get("lidar_camera_abs_delta_ms"),
|
||||||
|
"E31 LiDAR/camera timing",
|
||||||
|
)
|
||||||
|
pose_point = _object(
|
||||||
|
source_time.get("pose_point_abs_delta_ms"),
|
||||||
|
"E31 pose/point timing",
|
||||||
|
)
|
||||||
|
offset = _object(result.report.get("offset_sensitivity"), "E31 offset")
|
||||||
|
self_mask = _object(result.report.get("self_mask"), "E31 self-mask")
|
||||||
|
semantic_mask = _object(self_mask.get("semantic_mask"), "E31 semantic mask")
|
||||||
|
geometry_mask = _object(
|
||||||
|
self_mask.get("geometry_point_mask"),
|
||||||
|
"E31 geometry mask",
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"result_id": result.result_id,
|
||||||
|
"created_at_utc": result.manifest.get("created_at_utc"),
|
||||||
|
"source_session_id": source.get("session_id"),
|
||||||
|
"status": result.report.get("status"),
|
||||||
|
"eligible_for_e32": result.report.get("eligible_for_e32"),
|
||||||
|
"profile_id": method.get("profile_id"),
|
||||||
|
"producer_sha256": identity.get("producer_sha256"),
|
||||||
|
"metrics": {
|
||||||
|
"frame_count": source_time.get("frame_count"),
|
||||||
|
"available_binding_count": source_time.get("available_binding_count"),
|
||||||
|
"available_fraction": source_time.get("available_fraction"),
|
||||||
|
"lidar_camera_p95_ms": lidar_camera.get("p95"),
|
||||||
|
"pose_point_p95_ms": pose_point.get("p95"),
|
||||||
|
"selected_offset_ms": offset.get("selected_offset_ms"),
|
||||||
|
"correspondence_count": offset.get("correspondence_count"),
|
||||||
|
"supported_fraction": offset.get("baseline_supported_fraction"),
|
||||||
|
"semantic_self_sample_count": semantic_mask.get("source_sample_count"),
|
||||||
|
"semantic_self_collateral_count": semantic_mask.get(
|
||||||
|
"collateral_item_count"
|
||||||
|
),
|
||||||
|
"exact_geometry_correction_count": len(
|
||||||
|
self_mask.get("exact_correction_item_ids", [])
|
||||||
|
),
|
||||||
|
"geometry_point_mask_status": geometry_mask.get("status"),
|
||||||
|
},
|
||||||
|
"limitations": copy.deepcopy(result.report.get("limitations")),
|
||||||
|
"authority": copy.deepcopy(result.report.get("authority")),
|
||||||
|
"access": "read-only",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _project_e32(result: E32TrackGeometryReplay) -> dict[str, object]:
|
||||||
|
identity = _object(result.manifest.get("identity"), "E32 identity")
|
||||||
|
source = _object(identity.get("source"), "E32 source")
|
||||||
|
method = _object(identity.get("method"), "E32 method")
|
||||||
|
binding = _object(
|
||||||
|
identity.get("track_geometry_binding"),
|
||||||
|
"E32 track geometry binding",
|
||||||
|
)
|
||||||
|
metrics = _object(result.report.get("metrics"), "E32 metrics")
|
||||||
|
frames = _object(metrics.get("frames"), "E32 frames")
|
||||||
|
objects = _object(metrics.get("objects"), "E32 objects")
|
||||||
|
ownership = _object(metrics.get("point_ownership"), "E32 point ownership")
|
||||||
|
point_rows = _object(metrics.get("qualified_point_rows"), "E32 point rows")
|
||||||
|
runtime = _object(metrics.get("runtime"), "E32 runtime")
|
||||||
|
frame_processing = _object(
|
||||||
|
runtime.get("frame_processing_ms"),
|
||||||
|
"E32 frame processing",
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"result_id": result.result_id,
|
||||||
|
"created_at_utc": result.manifest.get("created_at_utc"),
|
||||||
|
"source_session_id": binding.get("source_session_id"),
|
||||||
|
"status": result.report.get("status"),
|
||||||
|
"profile_id": method.get("profile_id"),
|
||||||
|
"producer_sha256": identity.get("producer_sha256"),
|
||||||
|
"e31_result_id": source.get("e31_result_id"),
|
||||||
|
"metrics": {
|
||||||
|
"frames_total": frames.get("total"),
|
||||||
|
"frames_source_available": frames.get("source_available"),
|
||||||
|
"semantic_published": objects.get("semantic_published"),
|
||||||
|
"semantic_masked": objects.get("semantic_masked"),
|
||||||
|
"geometry_published": objects.get("geometry_published"),
|
||||||
|
"observations_arbitrated": ownership.get("observations_arbitrated"),
|
||||||
|
"overlapping_claims_removed": ownership.get(
|
||||||
|
"overlapping_claims_removed"
|
||||||
|
),
|
||||||
|
"qualified_points_published": point_rows.get("e32_published"),
|
||||||
|
"qualified_points_withheld": point_rows.get(
|
||||||
|
"unqualified_ranges_withheld"
|
||||||
|
),
|
||||||
|
"frame_processing_p95_ms": frame_processing.get("p95"),
|
||||||
|
"build_elapsed_ms": runtime.get("build_elapsed_ms"),
|
||||||
|
},
|
||||||
|
"decision": copy.deepcopy(result.report.get("decision")),
|
||||||
|
"authority": copy.deepcopy(result.report.get("authority")),
|
||||||
|
"access": "read-only",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _project_e33(
|
||||||
|
result: E33WorkerShadowResult,
|
||||||
|
linked_e32: E32TrackGeometryReplay,
|
||||||
|
) -> dict[str, object]:
|
||||||
|
identity = _object(result.result.get("identity"), "E33 identity")
|
||||||
|
linked_identity = _object(
|
||||||
|
linked_e32.manifest.get("identity"),
|
||||||
|
"linked E32 identity",
|
||||||
|
)
|
||||||
|
binding = _object(
|
||||||
|
linked_identity.get("track_geometry_binding"),
|
||||||
|
"linked E32 binding",
|
||||||
|
)
|
||||||
|
profile = _object(identity.get("profile"), "E33 profile")
|
||||||
|
worker = _object(identity.get("worker"), "E33 worker")
|
||||||
|
metrics = _object(result.report.get("metrics"), "E33 metrics")
|
||||||
|
accounting = _object(metrics.get("accounting"), "E33 accounting")
|
||||||
|
processing = _object(metrics.get("processing_ms"), "E33 processing")
|
||||||
|
release_lag = _object(metrics.get("release_lag_ms"), "E33 release lag")
|
||||||
|
result_age = _object(metrics.get("result_age_ms"), "E33 result age")
|
||||||
|
resources = _object(metrics.get("resources"), "E33 resources")
|
||||||
|
process_rss = _object(resources.get("process_rss_mib"), "E33 RSS")
|
||||||
|
gpu_utilization = _object(
|
||||||
|
resources.get("gpu_utilization_percent"),
|
||||||
|
"E33 GPU utilization",
|
||||||
|
)
|
||||||
|
work_queue = _object(metrics.get("work_queue"), "E33 work queue")
|
||||||
|
result_queue = _object(metrics.get("result_queue"), "E33 result queue")
|
||||||
|
return {
|
||||||
|
"result_id": result.result_id,
|
||||||
|
"created_at_utc": result.result.get("created_at_utc"),
|
||||||
|
"source_session_id": binding.get("source_session_id"),
|
||||||
|
"status": result.result.get("acceptance_state"),
|
||||||
|
"e32_result_id": identity.get("e32_result_id"),
|
||||||
|
"pipeline_id": identity.get("pipeline"),
|
||||||
|
"mode": profile.get("mode"),
|
||||||
|
"worker": {
|
||||||
|
"node": worker.get("worker_node"),
|
||||||
|
"container_image": worker.get("container_image"),
|
||||||
|
"python": worker.get("python"),
|
||||||
|
"numpy": worker.get("numpy"),
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"source_frames": accounting.get("source_frames"),
|
||||||
|
"delivered_frames": accounting.get("delivered"),
|
||||||
|
"input_superseded": accounting.get("input_superseded"),
|
||||||
|
"result_superseded": accounting.get("result_superseded"),
|
||||||
|
"effective_delivery_fps": metrics.get("effective_delivery_fps"),
|
||||||
|
"deadline_miss_fraction": metrics.get("deadline_miss_fraction"),
|
||||||
|
"processing_p95_ms": processing.get("p95"),
|
||||||
|
"release_lag_p95_ms": release_lag.get("p95"),
|
||||||
|
"result_age_p95_ms": result_age.get("p95"),
|
||||||
|
"process_rss_p95_mib": process_rss.get("p95"),
|
||||||
|
"gpu_utilization_p95_percent": gpu_utilization.get("p95"),
|
||||||
|
"gpu_visible": resources.get("gpu_visible"),
|
||||||
|
"work_queue_capacity": work_queue.get("capacity"),
|
||||||
|
"result_queue_capacity": result_queue.get("capacity"),
|
||||||
|
"wall_to_ideal_ratio": metrics.get("wall_to_ideal_ratio"),
|
||||||
|
},
|
||||||
|
"acceptance": copy.deepcopy(result.report.get("acceptance")),
|
||||||
|
"authority": copy.deepcopy(result.report.get("authority")),
|
||||||
|
"access": "read-only",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _empty_catalog(configured: bool) -> dict[str, object]:
|
||||||
|
return {
|
||||||
|
"schema_version": LABORATORY_ADVANCED_CATALOG_SCHEMA,
|
||||||
|
"configured": configured,
|
||||||
|
"items": [],
|
||||||
|
"candidate_total": 0,
|
||||||
|
"invalid_total": 0,
|
||||||
|
"access": "read-only",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_advanced_laboratory_router(
|
||||||
|
*,
|
||||||
|
e31_root_provider: RootProvider = lambda: None,
|
||||||
|
e32_root_provider: RootProvider = lambda: None,
|
||||||
|
e33_root_provider: RootProvider = lambda: None,
|
||||||
|
) -> APIRouter:
|
||||||
|
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
|
||||||
|
|
||||||
|
@router.get("/e31/results")
|
||||||
|
def list_e31_results(
|
||||||
|
limit: int = Query(default=1, ge=1, le=10),
|
||||||
|
) -> dict[str, object]:
|
||||||
|
root = _configured_root(e31_root_provider)
|
||||||
|
if root is None:
|
||||||
|
return _empty_catalog(False)
|
||||||
|
candidates = _candidates(root, _E31_RESULT_ID)
|
||||||
|
items: list[dict[str, object]] = []
|
||||||
|
invalid_total = 0
|
||||||
|
for candidate in candidates:
|
||||||
|
try:
|
||||||
|
result = _read_e31_cached(
|
||||||
|
str(candidate.resolve()),
|
||||||
|
_result_signature(candidate),
|
||||||
|
)
|
||||||
|
if result.report.get("eligible_for_e32") is not True:
|
||||||
|
raise ValueError("E31 result is not accepted")
|
||||||
|
if len(items) < limit:
|
||||||
|
items.append(_project_e31(result))
|
||||||
|
except (
|
||||||
|
E31SourceQualificationError,
|
||||||
|
KeyError,
|
||||||
|
OSError,
|
||||||
|
TypeError,
|
||||||
|
ValueError,
|
||||||
|
):
|
||||||
|
invalid_total += 1
|
||||||
|
return {
|
||||||
|
**_empty_catalog(True),
|
||||||
|
"items": items,
|
||||||
|
"candidate_total": len(candidates),
|
||||||
|
"invalid_total": invalid_total,
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.get("/e32/results")
|
||||||
|
def list_e32_results(
|
||||||
|
limit: int = Query(default=1, ge=1, le=10),
|
||||||
|
) -> dict[str, object]:
|
||||||
|
root = _configured_root(e32_root_provider)
|
||||||
|
if root is None:
|
||||||
|
return _empty_catalog(False)
|
||||||
|
candidates = _candidates(root, _E32_RESULT_ID)
|
||||||
|
items: list[dict[str, object]] = []
|
||||||
|
invalid_total = 0
|
||||||
|
for candidate in candidates:
|
||||||
|
try:
|
||||||
|
result = _read_e32_cached(
|
||||||
|
str(candidate.resolve()),
|
||||||
|
_result_signature(candidate),
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
result.report.get("status")
|
||||||
|
!= "accepted-diagnostic-track-geometry-replay"
|
||||||
|
):
|
||||||
|
raise ValueError("E32 result is not accepted")
|
||||||
|
if len(items) < limit:
|
||||||
|
items.append(_project_e32(result))
|
||||||
|
except (
|
||||||
|
E32TrackGeometryReplayError,
|
||||||
|
KeyError,
|
||||||
|
OSError,
|
||||||
|
TypeError,
|
||||||
|
ValueError,
|
||||||
|
):
|
||||||
|
invalid_total += 1
|
||||||
|
return {
|
||||||
|
**_empty_catalog(True),
|
||||||
|
"items": items,
|
||||||
|
"candidate_total": len(candidates),
|
||||||
|
"invalid_total": invalid_total,
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.get("/e33/results")
|
||||||
|
def list_e33_results(
|
||||||
|
limit: int = Query(default=1, ge=1, le=10),
|
||||||
|
) -> dict[str, object]:
|
||||||
|
root = _configured_root(e33_root_provider)
|
||||||
|
e32_root = _configured_root(e32_root_provider)
|
||||||
|
if root is None or e32_root is None:
|
||||||
|
return _empty_catalog(False)
|
||||||
|
candidates = _candidates(root, _E33_RESULT_ID)
|
||||||
|
items: list[dict[str, object]] = []
|
||||||
|
invalid_total = 0
|
||||||
|
for candidate in candidates:
|
||||||
|
try:
|
||||||
|
result = _read_e33_cached(
|
||||||
|
str(candidate.resolve()),
|
||||||
|
_result_signature(candidate),
|
||||||
|
)
|
||||||
|
identity = _object(result.result.get("identity"), "E33 identity")
|
||||||
|
e32_result_id = identity.get("e32_result_id")
|
||||||
|
if (
|
||||||
|
not result.accepted
|
||||||
|
or not isinstance(e32_result_id, str)
|
||||||
|
or _E32_RESULT_ID.fullmatch(e32_result_id) is None
|
||||||
|
):
|
||||||
|
raise ValueError("E33 result is not accepted")
|
||||||
|
linked_root = e32_root / e32_result_id
|
||||||
|
linked_e32 = _read_e32_cached(
|
||||||
|
str(linked_root.resolve(strict=True)),
|
||||||
|
_result_signature(linked_root),
|
||||||
|
)
|
||||||
|
if len(items) < limit:
|
||||||
|
items.append(_project_e33(result, linked_e32))
|
||||||
|
except (
|
||||||
|
E32TrackGeometryReplayError,
|
||||||
|
E33WorkerShadowError,
|
||||||
|
KeyError,
|
||||||
|
OSError,
|
||||||
|
TypeError,
|
||||||
|
ValueError,
|
||||||
|
):
|
||||||
|
invalid_total += 1
|
||||||
|
return {
|
||||||
|
**_empty_catalog(True),
|
||||||
|
"items": items,
|
||||||
|
"candidate_total": len(candidates),
|
||||||
|
"invalid_total": invalid_total,
|
||||||
|
}
|
||||||
|
|
||||||
|
return router
|
||||||
@@ -32,6 +32,7 @@ from k1link.sessions import (
|
|||||||
SessionRecordingPreparationManager,
|
SessionRecordingPreparationManager,
|
||||||
SessionStore,
|
SessionStore,
|
||||||
)
|
)
|
||||||
|
from k1link.web.advanced_laboratory_api import build_advanced_laboratory_router
|
||||||
from k1link.web.device_plugin_composition import load_installed_device_plugins
|
from k1link.web.device_plugin_composition import load_installed_device_plugins
|
||||||
from k1link.web.e30_engineering_api import build_e30_engineering_router
|
from k1link.web.e30_engineering_api import build_e30_engineering_router
|
||||||
from k1link.web.e30_human_review_api import build_e30_human_review_router
|
from k1link.web.e30_human_review_api import build_e30_human_review_router
|
||||||
@@ -496,6 +497,31 @@ app.include_router(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
app.include_router(
|
||||||
|
build_advanced_laboratory_router(
|
||||||
|
e31_root_provider=lambda: (
|
||||||
|
REPOSITORY_ROOT
|
||||||
|
/ ".runtime"
|
||||||
|
/ "compute-experiments"
|
||||||
|
/ "e31"
|
||||||
|
/ "source-qualifications"
|
||||||
|
),
|
||||||
|
e32_root_provider=lambda: (
|
||||||
|
REPOSITORY_ROOT
|
||||||
|
/ ".runtime"
|
||||||
|
/ "compute-experiments"
|
||||||
|
/ "e32"
|
||||||
|
/ "results"
|
||||||
|
),
|
||||||
|
e33_root_provider=lambda: (
|
||||||
|
REPOSITORY_ROOT
|
||||||
|
/ ".runtime"
|
||||||
|
/ "compute-experiments"
|
||||||
|
/ "e33"
|
||||||
|
/ "results"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
app.include_router(
|
app.include_router(
|
||||||
build_e30_review_router(
|
build_e30_review_router(
|
||||||
materialization_root_provider=lambda: (
|
materialization_root_provider=lambda: (
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from fastapi.routing import APIRoute
|
||||||
|
|
||||||
|
from k1link.web.advanced_laboratory_api import build_advanced_laboratory_router
|
||||||
|
|
||||||
|
|
||||||
|
def _endpoint(router: APIRouter, path: str) -> object:
|
||||||
|
for route in router.routes:
|
||||||
|
if (
|
||||||
|
isinstance(route, APIRoute)
|
||||||
|
and route.path == path
|
||||||
|
and route.methods is not None
|
||||||
|
and "GET" in route.methods
|
||||||
|
):
|
||||||
|
return route.endpoint
|
||||||
|
raise AssertionError(f"GET {path} route is missing")
|
||||||
|
|
||||||
|
|
||||||
|
def test_advanced_catalogs_are_empty_when_not_configured() -> None:
|
||||||
|
router = build_advanced_laboratory_router()
|
||||||
|
|
||||||
|
for name in ("e31", "e32", "e33"):
|
||||||
|
route = _endpoint(router, f"/api/v1/laboratory/{name}/results")
|
||||||
|
catalog = route(limit=1) # type: ignore[operator]
|
||||||
|
assert catalog == {
|
||||||
|
"schema_version": "missioncore.laboratory-advanced-catalog/v1",
|
||||||
|
"configured": False,
|
||||||
|
"items": [],
|
||||||
|
"candidate_total": 0,
|
||||||
|
"invalid_total": 0,
|
||||||
|
"access": "read-only",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_advanced_catalogs_fail_closed_on_incomplete_results(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
e31 = tmp_path / "e31"
|
||||||
|
e32 = tmp_path / "e32"
|
||||||
|
e33 = tmp_path / "e33"
|
||||||
|
for root in (e31, e32, e33):
|
||||||
|
root.mkdir()
|
||||||
|
(e31 / f"e31-source-qualification-{'1' * 64}").mkdir()
|
||||||
|
(e32 / f"e32-track-geometry-{'2' * 64}").mkdir()
|
||||||
|
(e33 / f"e33-worker-shadow-{'3' * 64}").mkdir()
|
||||||
|
router = build_advanced_laboratory_router(
|
||||||
|
e31_root_provider=lambda: e31,
|
||||||
|
e32_root_provider=lambda: e32,
|
||||||
|
e33_root_provider=lambda: e33,
|
||||||
|
)
|
||||||
|
|
||||||
|
for name in ("e31", "e32", "e33"):
|
||||||
|
route = _endpoint(router, f"/api/v1/laboratory/{name}/results")
|
||||||
|
catalog = route(limit=1) # type: ignore[operator]
|
||||||
|
assert catalog["configured"] is True
|
||||||
|
assert catalog["candidate_total"] == 1
|
||||||
|
assert catalog["invalid_total"] == 1
|
||||||
|
assert catalog["items"] == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_advanced_catalog_does_not_follow_a_configured_symlink(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
actual = tmp_path / "actual"
|
||||||
|
actual.mkdir()
|
||||||
|
linked = tmp_path / "linked"
|
||||||
|
linked.symlink_to(actual, target_is_directory=True)
|
||||||
|
router = build_advanced_laboratory_router(
|
||||||
|
e31_root_provider=lambda: linked,
|
||||||
|
)
|
||||||
|
|
||||||
|
route = _endpoint(router, "/api/v1/laboratory/e31/results")
|
||||||
|
catalog = route(limit=1) # type: ignore[operator]
|
||||||
|
|
||||||
|
assert catalog["configured"] is False
|
||||||
Reference in New Issue
Block a user