feat(lab): verify and expose real evidence

This commit is contained in:
DCCONSTRUCTIONS
2026-07-26 15:36:48 +03:00
parent 453d760be4
commit dc55ff16c9
9 changed files with 2007 additions and 47 deletions
@@ -0,0 +1,540 @@
export interface E29ReviewFrame {
frameIndex: number;
sourceFrameIndex: number;
sessionSeconds: number;
conflictCount: number;
semanticObservationCount: number;
geometryOnlyClusterCount: number;
}
export interface E29EvidenceResult {
resultId: string;
createdAtUtc: string | null;
status: "diagnostic-replay-complete";
identity: {
frameCount: number;
timelineStartSeconds: number;
timelineEndSeconds: number;
};
metrics: {
frames: {
total: number;
sourceAvailable: number;
localSurfaceValid: number;
};
semanticObservations: {
total: number;
current: number;
agreementFractionOfCurrent: number;
geometryStatus: {
agree: number;
cameraOnly: number;
conflict: number;
unknown: number;
};
};
geometryOnlyOccupied: {
clusterCount: number;
pointCount: number;
};
runtime: {
buildElapsedMs: number;
frameProcessingP95Ms: number;
};
};
decision: {
cameraFirstContractImplemented: boolean;
parallelGeometryOnlyLayerImplemented: boolean;
productionPromotion: boolean;
nextGate: string;
};
limitations: readonly string[];
authority: {
commandsEnabled: false;
navigationOrSafetyAccepted: false;
};
reviewFrames: readonly E29ReviewFrame[];
linkedEvidence: {
sourceSessionId: string;
localSurfaceModelId: string;
sourcePackId: string;
sourceResultId: string;
publishedOnControlPlane: true;
workerCopyRequired: false;
};
groundTruth: false;
}
export interface E29EvidenceCatalog {
configured: boolean;
candidateTotal: number;
invalidTotal: number;
items: readonly E29EvidenceResult[];
}
export interface E29SemanticObservation {
trackId: number;
label: string;
geometryStatus: string;
geometryReason: string;
rangeM: number | null;
score: number;
support: {
classifiedPoints: number;
occupiedPoints: number;
surfacePoints: number;
};
}
export interface E29GeometryOnlyCluster {
nearestRangeM: number;
pointCount: number;
voxelCount: number;
heightRangeM: readonly [number, number];
}
export interface E29EvidenceFrame {
resultId: string;
frameIndex: number;
sourceFrameIndex: number;
sessionSeconds: number;
semanticObservations: readonly E29SemanticObservation[];
geometryOnlyOccupied: readonly E29GeometryOnlyCluster[];
}
export class E29EvidenceContractError extends Error {
constructor(message: string) {
super(message);
this.name = "E29EvidenceContractError";
}
}
type E29Fetch = (
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 E29EvidenceContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function stringValue(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new E29EvidenceContractError(`${label}: ожидалась строка.`);
}
return value;
}
function numberValue(value: unknown, label: string): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new E29EvidenceContractError(`${label}: ожидалось число.`);
}
return value;
}
function integerValue(value: unknown, label: string): number {
const parsed = numberValue(value, label);
if (!Number.isSafeInteger(parsed) || parsed < 0) {
throw new E29EvidenceContractError(`${label}: ожидалось целое число.`);
}
return parsed;
}
function booleanValue(value: unknown, label: string): boolean {
if (typeof value !== "boolean") {
throw new E29EvidenceContractError(`${label}: ожидался boolean.`);
}
return value;
}
function falseValue(value: unknown, label: string): false {
if (booleanValue(value, label) !== false) {
throw new E29EvidenceContractError(`${label}: ожидалось false.`);
}
return false;
}
function trueValue(value: unknown, label: string): true {
if (booleanValue(value, label) !== true) {
throw new E29EvidenceContractError(`${label}: ожидалось true.`);
}
return true;
}
function nullableNumber(value: unknown, label: string): number | null {
return value === null ? null : numberValue(value, label);
}
function tuple2(value: unknown, label: string): readonly [number, number] {
if (!Array.isArray(value) || value.length !== 2) {
throw new E29EvidenceContractError(`${label}: ожидалась пара чисел.`);
}
return [
numberValue(value[0], `${label}[0]`),
numberValue(value[1], `${label}[1]`),
];
}
function parseReviewFrame(value: unknown): E29ReviewFrame {
const source = record(value, "review_frame");
return {
frameIndex: integerValue(source.frame_index, "review_frame.frame_index"),
sourceFrameIndex: integerValue(
source.source_frame_index,
"review_frame.source_frame_index",
),
sessionSeconds: numberValue(
source.session_seconds,
"review_frame.session_seconds",
),
conflictCount: integerValue(
source.conflict_count,
"review_frame.conflict_count",
),
semanticObservationCount: integerValue(
source.semantic_observation_count,
"review_frame.semantic_observation_count",
),
geometryOnlyClusterCount: integerValue(
source.geometry_only_cluster_count,
"review_frame.geometry_only_cluster_count",
),
};
}
function parseEvidenceResult(value: unknown): E29EvidenceResult {
const source = record(value, "E29 result");
const identity = record(source.identity, "E29 identity");
const metrics = record(source.metrics, "E29 metrics");
const frames = record(metrics.frames, "E29 metrics.frames");
const semantic = record(
metrics.semantic_observations,
"E29 metrics.semantic_observations",
);
const geometryStatus = record(
semantic.geometry_status,
"E29 metrics.semantic_observations.geometry_status",
);
const geometryOnly = record(
metrics.geometry_only_occupied,
"E29 metrics.geometry_only_occupied",
);
const runtime = record(metrics.runtime, "E29 metrics.runtime");
const frameProcessing = record(
runtime.frame_processing_ms,
"E29 metrics.runtime.frame_processing_ms",
);
const decision = record(source.decision, "E29 decision");
const authority = record(source.authority, "E29 authority");
const linked = record(source.linked_evidence, "E29 linked evidence");
const status = stringValue(source.status, "E29 status");
if (status !== "diagnostic-replay-complete") {
throw new E29EvidenceContractError("E29 status не завершён.");
}
if (!Array.isArray(source.limitations) || !Array.isArray(source.review_frames)) {
throw new E29EvidenceContractError("E29 collections имеют неверный формат.");
}
return {
resultId: stringValue(source.result_id, "E29 result_id"),
createdAtUtc: source.created_at_utc === null
? null
: stringValue(source.created_at_utc, "E29 created_at_utc"),
status,
identity: {
frameCount: integerValue(identity.frame_count, "E29 identity.frame_count"),
timelineStartSeconds: numberValue(
identity.timeline_start_seconds,
"E29 identity.timeline_start_seconds",
),
timelineEndSeconds: numberValue(
identity.timeline_end_seconds,
"E29 identity.timeline_end_seconds",
),
},
metrics: {
frames: {
total: integerValue(frames.total, "E29 metrics.frames.total"),
sourceAvailable: integerValue(
frames.source_available,
"E29 metrics.frames.source_available",
),
localSurfaceValid: integerValue(
frames.local_surface_valid,
"E29 metrics.frames.local_surface_valid",
),
},
semanticObservations: {
total: integerValue(
semantic.total,
"E29 metrics.semantic_observations.total",
),
current: integerValue(
semantic.current,
"E29 metrics.semantic_observations.current",
),
agreementFractionOfCurrent: numberValue(
semantic.agreement_fraction_of_current,
"E29 metrics.semantic_observations.agreement_fraction_of_current",
),
geometryStatus: {
agree: integerValue(geometryStatus.agree, "E29 geometry_status.agree"),
cameraOnly: integerValue(
geometryStatus["single-source-camera"],
"E29 geometry_status.single-source-camera",
),
conflict: integerValue(
geometryStatus.conflict,
"E29 geometry_status.conflict",
),
unknown: integerValue(
geometryStatus.unknown,
"E29 geometry_status.unknown",
),
},
},
geometryOnlyOccupied: {
clusterCount: integerValue(
geometryOnly.cluster_count,
"E29 geometry_only_occupied.cluster_count",
),
pointCount: integerValue(
geometryOnly.point_count,
"E29 geometry_only_occupied.point_count",
),
},
runtime: {
buildElapsedMs: numberValue(
runtime.build_elapsed_ms,
"E29 runtime.build_elapsed_ms",
),
frameProcessingP95Ms: numberValue(
frameProcessing.p95,
"E29 runtime.frame_processing_ms.p95",
),
},
},
decision: {
cameraFirstContractImplemented: booleanValue(
decision.camera_first_contract_implemented,
"E29 decision.camera_first_contract_implemented",
),
parallelGeometryOnlyLayerImplemented: booleanValue(
decision.parallel_geometry_only_layer_implemented,
"E29 decision.parallel_geometry_only_layer_implemented",
),
productionPromotion: booleanValue(
decision.production_promotion,
"E29 decision.production_promotion",
),
nextGate: stringValue(decision.next_gate, "E29 decision.next_gate"),
},
limitations: source.limitations.map((item, index) => (
stringValue(item, `E29 limitations[${index}]`)
)),
authority: {
commandsEnabled: falseValue(
authority.commands_enabled,
"E29 authority.commands_enabled",
),
navigationOrSafetyAccepted: falseValue(
authority.navigation_or_safety_accepted,
"E29 authority.navigation_or_safety_accepted",
),
},
reviewFrames: source.review_frames.map(parseReviewFrame),
linkedEvidence: {
sourceSessionId: stringValue(
linked.source_session_id,
"E29 linked_evidence.source_session_id",
),
localSurfaceModelId: stringValue(
linked.local_surface_model_id,
"E29 linked_evidence.local_surface_model_id",
),
sourcePackId: stringValue(
linked.source_pack_id,
"E29 linked_evidence.source_pack_id",
),
sourceResultId: stringValue(
linked.source_result_id,
"E29 linked_evidence.source_result_id",
),
publishedOnControlPlane: trueValue(
linked.published_on_control_plane,
"E29 linked_evidence.published_on_control_plane",
),
workerCopyRequired: falseValue(
linked.worker_copy_required,
"E29 linked_evidence.worker_copy_required",
),
},
groundTruth: falseValue(source.ground_truth, "E29 ground_truth"),
};
}
export function parseE29EvidenceCatalog(value: unknown): E29EvidenceCatalog {
const source = record(value, "E29 catalog");
if (source.schema_version !== "missioncore.laboratory-e29-catalog/v1") {
throw new E29EvidenceContractError("E29 catalog schema не поддерживается.");
}
if (!Array.isArray(source.items)) {
throw new E29EvidenceContractError("E29 catalog.items имеет неверный формат.");
}
return {
configured: booleanValue(source.configured, "E29 catalog.configured"),
candidateTotal: integerValue(
source.candidate_total,
"E29 catalog.candidate_total",
),
invalidTotal: integerValue(
source.invalid_total,
"E29 catalog.invalid_total",
),
items: source.items.map(parseEvidenceResult),
};
}
function parseSemanticObservation(value: unknown): E29SemanticObservation {
const source = record(value, "E29 semantic observation");
const support = record(source.support, "E29 semantic observation.support");
return {
trackId: integerValue(source.track_id, "E29 semantic observation.track_id"),
label: stringValue(source.label, "E29 semantic observation.label"),
geometryStatus: stringValue(
source.geometry_status,
"E29 semantic observation.geometry_status",
),
geometryReason: stringValue(
source.geometry_reason,
"E29 semantic observation.geometry_reason",
),
rangeM: nullableNumber(source.range_m, "E29 semantic observation.range_m"),
score: numberValue(source.score, "E29 semantic observation.score"),
support: {
classifiedPoints: integerValue(
support.classified_points_in_bbox,
"E29 support.classified_points_in_bbox",
),
occupiedPoints: integerValue(
support.occupied_points_in_bbox,
"E29 support.occupied_points_in_bbox",
),
surfacePoints: integerValue(
support.surface_points_in_bbox,
"E29 support.surface_points_in_bbox",
),
},
};
}
function parseGeometryOnly(value: unknown): E29GeometryOnlyCluster {
const source = record(value, "E29 geometry-only cluster");
return {
nearestRangeM: numberValue(
source.nearest_range_m,
"E29 geometry-only.nearest_range_m",
),
pointCount: integerValue(
source.point_count,
"E29 geometry-only.point_count",
),
voxelCount: integerValue(
source.voxel_count,
"E29 geometry-only.voxel_count",
),
heightRangeM: tuple2(
source.height_range_m,
"E29 geometry-only.height_range_m",
),
};
}
export function parseE29EvidenceFrame(value: unknown): E29EvidenceFrame {
const source = record(value, "E29 frame response");
if (source.schema_version !== "missioncore.laboratory-e29-frame/v1") {
throw new E29EvidenceContractError("E29 frame schema не поддерживается.");
}
const frame = record(source.frame, "E29 frame");
if (
frame.schema_version !== "missioncore.e29-camera-geometry-frame/v1"
|| !Array.isArray(frame.semantic_observations)
|| !Array.isArray(frame.geometry_only_occupied)
) {
throw new E29EvidenceContractError("E29 frame имеет неверный формат.");
}
return {
resultId: stringValue(source.result_id, "E29 frame result_id"),
frameIndex: integerValue(frame.frame_index, "E29 frame.frame_index"),
sourceFrameIndex: integerValue(
frame.source_frame_index,
"E29 frame.source_frame_index",
),
sessionSeconds: numberValue(
frame.session_seconds,
"E29 frame.session_seconds",
),
semanticObservations: frame.semantic_observations.map(
parseSemanticObservation,
),
geometryOnlyOccupied: frame.geometry_only_occupied.map(parseGeometryOnly),
};
}
async function responseJson(response: Response, fallback: string): Promise<unknown> {
let payload: unknown = null;
try {
payload = await response.json();
} catch {
// Preserve the status-aware fallback below.
}
if (!response.ok) {
const detail = payload && typeof payload === "object" && "detail" in payload
? String((payload as { detail?: unknown }).detail)
: fallback;
throw new E29EvidenceContractError(detail);
}
return payload;
}
export async function fetchE29EvidenceCatalog(
options: { signal?: AbortSignal; fetcher?: E29Fetch } = {},
): Promise<E29EvidenceCatalog> {
const fetcher = options.fetcher ?? fetch;
const response = await fetcher("/api/v1/laboratory/e29/results?limit=1", {
method: "GET",
headers: { Accept: "application/json" },
signal: options.signal,
});
return parseE29EvidenceCatalog(
await responseJson(response, "Не удалось получить LAB E29."),
);
}
export async function fetchE29EvidenceFrame(
resultId: string,
frameIndex: number,
options: { signal?: AbortSignal; fetcher?: E29Fetch } = {},
): Promise<E29EvidenceFrame> {
if (
!/^e29-camera-geometry-[a-f0-9]{64}$/.test(resultId)
|| !Number.isSafeInteger(frameIndex)
|| frameIndex < 0
) {
throw new E29EvidenceContractError("Некорректный LAB E29 frame id.");
}
const fetcher = options.fetcher ?? fetch;
const response = await fetcher(
`/api/v1/laboratory/e29/results/${resultId}/frames/${frameIndex}`,
{
method: "GET",
headers: { Accept: "application/json" },
signal: options.signal,
},
);
return parseE29EvidenceFrame(
await responseJson(response, "Не удалось получить кадр LAB E29."),
);
}
@@ -1488,7 +1488,7 @@ export async function fetchLidarLocalSurfaces(
options: { signal?: AbortSignal; fetcher?: LidarFetch } = {},
): Promise<LidarLocalSurfaceCatalog> {
const fetcher = options.fetcher ?? fetch;
const response = await fetcher("/api/v1/lidar/local-surfaces?limit=10", {
const response = await fetcher("/api/v1/lidar/local-surfaces?limit=1", {
method: "GET",
headers: { Accept: "application/json" },
signal: options.signal,