feat(lab): visualize dual evidence replay

This commit is contained in:
DCCONSTRUCTIONS
2026-08-05 19:18:14 +03:00
parent b0d0bc8d7f
commit de19229895
17 changed files with 1825 additions and 115 deletions
@@ -35,8 +35,10 @@ import { fetchE46GRectifiedDetectorBakeoff } from "./e46gRectifiedDetectorBakeof
import { fetchE46HFullRectifiedFrontReplay } from "./e46hFullRectifiedFrontReplay";
import { fetchE46IGroundingDinoFullReplay } from "./e46iGroundingDinoFullReplay";
import { fetchE46JRawFisheyeRealtime } from "./e46jRawFisheyeRealtime";
import { fetchM4ThreatReplayResult } from "./m4ReplayThreat";
export type AdvancedLaboratoryWorkId =
| "m4-replay-threat"
| "l3-pointpillars-visual-audit"
| "l31-pointpillars-ravnoves"
| "l32-pointpillars-camera-review"
@@ -76,6 +78,7 @@ export interface AdvancedLaboratoryIndexItem {
}
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
"m4-replay-threat",
"l3-pointpillars-visual-audit",
"l31-pointpillars-ravnoves",
"l32-pointpillars-camera-review",
@@ -110,6 +113,7 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
];
const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
"m4-replay-threat": "m4-threat-replay",
"l3-pointpillars-visual-audit": "l3-pointpillars-visual-audit",
"l31-pointpillars-ravnoves": "l31-pointpillars-ravnoves",
"l32-pointpillars-camera-review": "l32-pointpillars-camera-review",
@@ -151,6 +155,7 @@ export function isAdvancedLaboratoryWorkId(
export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
return {
m4Threat: null,
l3: null,
l31: null,
l32: null,
@@ -273,7 +278,8 @@ export function advancedLaboratoryResultAvailable(
workId: AdvancedLaboratoryWorkId,
results: AdvancedLaboratoryResults,
): boolean {
return workId === "l3-pointpillars-visual-audit" ? results.l3 !== null
return workId === "m4-replay-threat" ? results.m4Threat !== null
: workId === "l3-pointpillars-visual-audit" ? results.l3 !== null
: workId === "l31-pointpillars-ravnoves" ? results.l31 !== null
: workId === "l32-pointpillars-camera-review" ? results.l32 !== null
: workId === "l33-camera-first-detector-review" ? results.l33 !== null
@@ -317,7 +323,9 @@ export async function fetchAdvancedLaboratoryResult(
} = {},
): Promise<AdvancedLaboratoryResults> {
const results = emptyAdvancedLaboratoryResults();
if (workId === "l3-pointpillars-visual-audit") {
if (workId === "m4-replay-threat") {
results.m4Threat = await fetchM4ThreatReplayResult({ fetcher, signal });
} else if (workId === "l3-pointpillars-visual-audit") {
results.l3 = await fetchL3PointPillarsVisualAudit({ fetcher, signal });
} else if (workId === "l31-pointpillars-ravnoves") {
results.l31 = await fetchL31PointPillarsRavnoves({ fetcher, signal });
@@ -31,8 +31,10 @@ import type { E46GRectifiedDetectorBakeoffResult } from "./e46gRectifiedDetector
import type { E46HFullRectifiedFrontReplayResult } from "./e46hFullRectifiedFrontReplay";
import type { E46IGroundingDinoFullReplayResult } from "./e46iGroundingDinoFullReplay";
import type { E46JRawFisheyeRealtimeResult } from "./e46jRawFisheyeRealtime";
import type { M4ThreatReplayResult } from "./m4ReplayThreat";
export interface AdvancedLaboratoryResults {
m4Threat: M4ThreatReplayResult | null;
l3: L3PointPillarsVisualAuditResult | null;
l31: L31PointPillarsRavnovesResult | null;
l32: L32PointPillarsCameraReviewResult | null;
@@ -235,7 +235,6 @@ export class AdvancedLaboratoryContractError extends Error {
this.name = "AdvancedLaboratoryContractError";
}
}
export type LaboratoryFetch = (
input: RequestInfo | URL,
init?: RequestInit,
@@ -968,6 +967,7 @@ export async function fetchAdvancedLaboratoryResults({
const e39 = settledCatalogValue(settled[7]);
const e40 = settledCatalogValue(settled[8]);
return {
m4Threat: null,
l3: null, l31: null,
l32: null,
l33: null,
@@ -0,0 +1,460 @@
export type M4ThreatDecision = "threat" | "not-threat" | "unknown";
export type M4ThreatMotion = "moving" | "stationary" | "unknown";
export type M4Point3 = readonly [number, number, number];
export interface M4ThreatReplayResult {
resultId: string;
createdAtUtc: string;
profileId: string;
rigProfileId: string;
corridorProfileId: string;
sourceResultIds: {
detector: string;
geometry: string;
temporal: string;
};
metrics: {
decisions: Record<M4ThreatDecision, number>;
evidence: {
cameraOnly: number;
currentMetric: number;
staleOrHeld: number;
};
fixtures: {
critical: number;
criticalFalseNotThreat: number;
passed: number;
total: number;
};
runtime: {
framesPerSecond: number;
providerLatencyP50Ms: number;
providerLatencyP95Ms: number;
providerLatencyMaxMs: number;
};
reasonCounts: Readonly<Record<string, number>>;
};
configuration: {
virtualBodyM: readonly [number, number];
nominalSensorHeightM: number;
forwardCorridorM: number;
predictionHorizonSeconds: number;
};
limitations: readonly string[];
}
export interface M4ThreatAssessment {
componentId: string;
decision: M4ThreatDecision;
corridorIntersection: "intersects" | "clear" | "unknown";
relativeSpeedMps: number | null;
closestApproachM: number | null;
ttcSeconds: number | null;
reasonCodes: readonly string[];
}
export interface M4ThreatMetricVisual {
componentId: string;
state: "current" | "held" | "expired";
motion: M4ThreatMotion;
centroidBodyXyzM: M4Point3;
cellCentersBodyXyzM: readonly M4Point3[];
assessment: M4ThreatAssessment;
}
export interface M4ThreatCameraProposal {
proposalId: string;
bboxXyxy: readonly [number, number, number, number];
objectness: number;
semanticHint: string | null;
occupiedSupport: boolean;
rangeM: number | null;
threatDecision: M4ThreatDecision | null;
threatReasonCodes: readonly string[];
}
export interface M4ThreatVisualFrame {
resultId: string;
ordinal: number;
sequence: number;
frameId: string;
sourceTimeNs: number;
pointCloudBodyXyzM: readonly M4Point3[];
pointCloudSourceCount: number;
pointCloudSampleCount: number;
metricObstacles: readonly M4ThreatMetricVisual[];
cameraProposals: readonly M4ThreatCameraProposal[];
rig: {
lengthM: number;
widthM: number;
nominalSensorHeightM: number;
};
corridor: {
forwardLengthM: number;
rearMarginM: number;
halfWidthM: number;
predictionHorizonSeconds: number;
};
}
export interface M4ThreatVisualIndexItem {
ordinal: number;
sequence: number;
frameId: string;
sourceTimeNs: number;
metricObstacleCount: number;
cameraProposalCount: number;
pointCloudSampleCount: number;
}
export interface M4ThreatVideoFrame {
frameIndex: number;
sessionSeconds: number;
sourceAvailable: boolean;
cameraProposals: readonly M4ThreatCameraProposal[];
decisionCounts: Record<M4ThreatDecision, number>;
}
export interface M4ThreatVideoOverlay {
resultId: string;
recordedSourceSessionId: "20260720T065719Z_viewer_live";
imageWidth: 800;
imageHeight: 600;
timelineStartSeconds: number;
timelineEndSeconds: number;
frames: readonly M4ThreatVideoFrame[];
}
type LaboratoryFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
class M4ThreatContractError extends Error {}
const object = (value: unknown, label: string): Record<string, unknown> => {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new M4ThreatContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
};
const array = (value: unknown, label: string): readonly unknown[] => {
if (!Array.isArray(value)) throw new M4ThreatContractError(`${label}: ожидался массив.`);
return value;
};
const text = (value: unknown, label: string): string => {
if (typeof value !== "string" || !value.trim()) {
throw new M4ThreatContractError(`${label}: ожидалась строка.`);
}
return value;
};
const number = (value: unknown, label: string): number => {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new M4ThreatContractError(`${label}: ожидалось число.`);
}
return value;
};
const integer = (value: unknown, label: string): number => {
const parsed = number(value, label);
if (!Number.isInteger(parsed) || parsed < 0) {
throw new M4ThreatContractError(`${label}: ожидалось целое.`);
}
return parsed;
};
const exact = <T extends string | number | boolean>(
value: unknown,
expected: T,
label: string,
): T => {
if (value !== expected) throw new M4ThreatContractError(`${label}: нарушен контракт.`);
return expected;
};
const optionalNumber = (value: unknown, label: string): number | null => (
value === null ? null : number(value, label)
);
const vector = (value: unknown, size: number, label: string): number[] => {
const parsed = array(value, label).map((item) => number(item, label));
if (parsed.length !== size) throw new M4ThreatContractError(`${label}: неверная размерность.`);
return parsed;
};
const decision = (value: unknown, label: string): M4ThreatDecision => {
if (value !== "threat" && value !== "not-threat" && value !== "unknown") {
throw new M4ThreatContractError(`${label}: неизвестное решение.`);
}
return value;
};
const motion = (value: unknown): M4ThreatMotion => {
if (value !== "moving" && value !== "stationary" && value !== "unknown") {
throw new M4ThreatContractError("M4.6 motion: неизвестное состояние.");
}
return value;
};
const resultId = (value: unknown): string => {
const parsed = text(value, "M4.6 result id");
if (!/^m4-threat-replay-[a-f0-9]{64}$/.test(parsed)) {
throw new M4ThreatContractError("M4.6 result id: нарушена идентичность.");
}
return parsed;
};
function parseAssessment(value: unknown): M4ThreatAssessment {
const item = object(value, "M4.6 assessment");
const intersection = text(item.corridor_intersection, "M4.6 intersection");
if (intersection !== "intersects" && intersection !== "clear" && intersection !== "unknown") {
throw new M4ThreatContractError("M4.6 intersection: неизвестное состояние.");
}
return {
componentId: text(item.component_id, "M4.6 component"),
decision: decision(item.decision, "M4.6 decision"),
corridorIntersection: intersection,
relativeSpeedMps: optionalNumber(item.relative_speed_mps, "M4.6 relative speed"),
closestApproachM: optionalNumber(item.closest_approach_m, "M4.6 closest approach"),
ttcSeconds: optionalNumber(item.ttc_seconds, "M4.6 TTC"),
reasonCodes: array(item.reason_codes, "M4.6 reasons").map((reason) => text(reason, "M4.6 reason")),
};
}
function parseCameraProposal(value: unknown): M4ThreatCameraProposal {
const item = object(value, "M4.6 camera proposal");
return {
proposalId: text(item.proposal_id, "M4.6 proposal id"),
bboxXyxy: vector(item.bbox_xyxy, 4, "M4.6 bbox") as [number, number, number, number],
objectness: number(item.objectness, "M4.6 objectness"),
semanticHint: item.semantic_hint === null ? null : text(item.semantic_hint, "M4.6 hint"),
occupiedSupport: typeof item.occupied_support === "boolean" ? item.occupied_support : false,
rangeM: optionalNumber(item.range_m, "M4.6 range"),
threatDecision: item.threat_decision === null
? null
: decision(item.threat_decision, "M4.6 camera threat"),
threatReasonCodes: array(item.threat_reason_codes, "M4.6 threat reasons").map(
(reason) => text(reason, "M4.6 threat reason"),
),
};
}
export async function fetchM4ThreatReplayResult({
fetcher = fetch,
signal,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {}): Promise<M4ThreatReplayResult | null> {
const response = await fetcher("/api/v1/laboratory/m4-threat/results?limit=1", {
headers: { Accept: "application/json" },
signal,
});
if (!response.ok) throw new M4ThreatContractError(`M4.6 LAB недоступен: HTTP ${response.status}.`);
const catalog = object(await response.json(), "M4.6 catalog");
exact(catalog.schema_version, "missioncore.m4-threat-replay-catalog/v1", "M4.6 catalog schema");
const items = array(catalog.items, "M4.6 results");
if (!items.length) return null;
const item = object(items[0], "M4.6 result");
exact(item.schema_version, "missioncore.m4-threat-replay-view/v1", "M4.6 view schema");
exact(item.accepted, true, "M4.6 acceptance");
exact(item.authority, "replay-simulated", "M4.6 authority");
exact(item.physical_collision_accepted, false, "M4.6 physical authority");
exact(item.actuation_allowed, false, "M4.6 actuation");
const metrics = object(item.metrics, "M4.6 metrics");
const decisions = object(metrics.decisions, "M4.6 decisions");
const evidence = object(metrics.evidence, "M4.6 evidence");
const fixtures = object(metrics.fixtures, "M4.6 fixtures");
const runtime = object(metrics.runtime, "M4.6 runtime");
const configuration = object(item.configuration, "M4.6 configuration");
const sourceResultIds = object(item.source_result_ids, "M4.6 sources");
return {
resultId: resultId(item.result_id),
createdAtUtc: text(item.created_at_utc, "M4.6 created"),
profileId: text(item.profile_id, "M4.6 profile"),
rigProfileId: text(item.rig_profile_id, "M4.6 rig"),
corridorProfileId: text(item.corridor_profile_id, "M4.6 corridor"),
sourceResultIds: {
detector: text(sourceResultIds.detector, "M4.6 detector"),
geometry: text(sourceResultIds.geometry, "M4.6 geometry"),
temporal: text(sourceResultIds.temporal, "M4.6 temporal"),
},
metrics: {
decisions: {
threat: integer(decisions.threat, "M4.6 threat count"),
"not-threat": integer(decisions["not-threat"], "M4.6 clear count"),
unknown: integer(decisions.unknown, "M4.6 unknown count"),
},
evidence: {
cameraOnly: integer(evidence["camera-only"], "M4.6 camera-only"),
currentMetric: integer(evidence["current-metric"], "M4.6 metric"),
staleOrHeld: integer(evidence["stale-or-held"], "M4.6 stale"),
},
fixtures: {
critical: integer(fixtures.critical, "M4.6 critical fixtures"),
criticalFalseNotThreat: integer(fixtures.critical_false_not_threat, "M4.6 false-safe"),
passed: integer(fixtures.passed, "M4.6 fixtures passed"),
total: integer(fixtures.total, "M4.6 fixtures total"),
},
runtime: {
framesPerSecond: number(runtime.frames_per_second, "M4.6 FPS"),
providerLatencyP50Ms: number(runtime.provider_latency_p50_ms, "M4.6 p50"),
providerLatencyP95Ms: number(runtime.provider_latency_p95_ms, "M4.6 p95"),
providerLatencyMaxMs: number(runtime.provider_latency_max_ms, "M4.6 max"),
},
reasonCounts: Object.fromEntries(
Object.entries(object(metrics.reason_counts, "M4.6 reasons")).map(
([key, value]) => [key, integer(value, `M4.6 ${key}`)],
),
),
},
configuration: {
virtualBodyM: vector(configuration.virtual_body_m, 2, "M4.6 body") as [number, number],
nominalSensorHeightM: number(configuration.nominal_sensor_height_m, "M4.6 height"),
forwardCorridorM: number(configuration.forward_corridor_m, "M4.6 corridor"),
predictionHorizonSeconds: number(configuration.prediction_horizon_seconds, "M4.6 horizon"),
},
limitations: array(item.limitations, "M4.6 limitations").map((value) => text(value, "M4.6 limitation")),
};
}
export async function fetchM4ThreatVisualIndex(
result: string,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<readonly M4ThreatVisualIndexItem[]> {
const response = await fetcher(`/api/v1/laboratory/m4-threat/results/${result}/visuals`, {
headers: { Accept: "application/json" },
signal,
});
if (!response.ok) throw new M4ThreatContractError(`M4.6 visual index: HTTP ${response.status}.`);
const payload = object(await response.json(), "M4.6 visual index");
exact(payload.schema_version, "missioncore.m4-threat-visual-catalog/v1", "M4.6 visual schema");
exact(payload.result_id, result, "M4.6 visual result");
return array(payload.items, "M4.6 visual items").map((raw) => {
const item = object(raw, "M4.6 visual item");
return {
ordinal: integer(item.ordinal, "M4.6 visual ordinal"),
sequence: integer(item.sequence, "M4.6 visual sequence"),
frameId: text(item.frame_id, "M4.6 visual frame"),
sourceTimeNs: integer(item.source_time_ns, "M4.6 visual time"),
metricObstacleCount: integer(item.metric_obstacle_count, "M4.6 visual metric"),
cameraProposalCount: integer(item.camera_proposal_count, "M4.6 visual camera"),
pointCloudSampleCount: integer(item.point_cloud_sample_count, "M4.6 visual points"),
};
});
}
export async function fetchM4ThreatVisual(
result: string,
ordinal: number,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M4ThreatVisualFrame> {
const response = await fetcher(
`/api/v1/laboratory/m4-threat/results/${result}/visuals/${ordinal}`,
{ headers: { Accept: "application/json" }, signal },
);
if (!response.ok) throw new M4ThreatContractError(`M4.6 visual frame: HTTP ${response.status}.`);
const item = object(await response.json(), "M4.6 visual frame");
exact(item.schema_version, "missioncore.perception-threat-visual-frame/v1", "M4.6 frame schema");
exact(item.result_id, result, "M4.6 frame result");
const rig = object(item.rig, "M4.6 visual rig");
const corridor = object(item.corridor, "M4.6 visual corridor");
return {
resultId: result,
ordinal: integer(item.ordinal, "M4.6 ordinal"),
sequence: integer(item.sequence, "M4.6 sequence"),
frameId: text(item.frame_id, "M4.6 frame id"),
sourceTimeNs: integer(item.source_time_ns, "M4.6 frame time"),
pointCloudBodyXyzM: array(item.point_cloud_body_xyz_m, "M4.6 points").map(
(point) => vector(point, 3, "M4.6 point") as [number, number, number],
),
pointCloudSourceCount: integer(item.point_cloud_source_count, "M4.6 source points"),
pointCloudSampleCount: integer(item.point_cloud_sample_count, "M4.6 sample points"),
metricObstacles: array(item.metric_obstacles, "M4.6 metric visuals").map((raw) => {
const value = object(raw, "M4.6 metric visual");
const state = text(value.state, "M4.6 temporal state");
if (state !== "current" && state !== "held" && state !== "expired") {
throw new M4ThreatContractError("M4.6 temporal state: неизвестное состояние.");
}
return {
componentId: text(value.component_id, "M4.6 visual component"),
state,
motion: motion(value.motion),
centroidBodyXyzM: vector(value.centroid_body_xyz_m, 3, "M4.6 centroid") as [number, number, number],
cellCentersBodyXyzM: array(value.cell_centers_body_xyz_m, "M4.6 cells").map(
(point) => vector(point, 3, "M4.6 cell") as [number, number, number],
),
assessment: parseAssessment(value.assessment),
};
}),
cameraProposals: array(item.camera_proposals, "M4.6 camera proposals").map(parseCameraProposal),
rig: {
lengthM: number(rig.length_m, "M4.6 rig length"),
widthM: number(rig.width_m, "M4.6 rig width"),
nominalSensorHeightM: number(rig.nominal_sensor_height_m, "M4.6 sensor height"),
},
corridor: {
forwardLengthM: number(corridor.forward_length_m, "M4.6 forward corridor"),
rearMarginM: number(corridor.rear_margin_m, "M4.6 rear corridor"),
halfWidthM: number(corridor.half_width_m, "M4.6 half width"),
predictionHorizonSeconds: number(corridor.prediction_horizon_seconds, "M4.6 visual horizon"),
},
};
}
export async function fetchM4ThreatVideoOverlay(
result: string,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M4ThreatVideoOverlay> {
const response = await fetcher(
`/api/v1/laboratory/m4-threat/results/${result}/video-overlay`,
{ headers: { Accept: "application/json" }, signal },
);
if (!response.ok) throw new M4ThreatContractError(`M4.6 video overlay: HTTP ${response.status}.`);
const payload = object(await response.json(), "M4.6 video overlay");
exact(payload.schema_version, "missioncore.m4-threat-video-overlay/v1", "M4.6 video schema");
exact(payload.result_id, result, "M4.6 video result");
exact(payload.authority, "replay-simulated", "M4.6 video authority");
const recorded = object(payload.recorded_source, "M4.6 recorded source");
exact(
recorded.session_id,
"20260720T065719Z_viewer_live",
"M4.6 recorded session",
);
const frames = array(payload.frames, "M4.6 video frames").map((raw, expectedIndex) => {
const item = object(raw, "M4.6 video frame");
const frameIndex = integer(item.frame_index, "M4.6 video index");
if (frameIndex !== expectedIndex) throw new M4ThreatContractError("M4.6 video order.");
const counts = object(item.decision_counts, "M4.6 video decisions");
return {
frameIndex,
sessionSeconds: number(item.session_seconds, "M4.6 video time"),
sourceAvailable: typeof item.source_available === "boolean" ? item.source_available : false,
cameraProposals: array(item.camera_proposals, "M4.6 video proposals").map(parseCameraProposal),
decisionCounts: {
threat: integer(counts.threat, "M4.6 video threat"),
"not-threat": integer(counts["not-threat"], "M4.6 video clear"),
unknown: integer(counts.unknown, "M4.6 video unknown"),
},
};
});
exact(payload.frame_count, 4489, "M4.6 video frame count");
return {
resultId: result,
recordedSourceSessionId: "20260720T065719Z_viewer_live",
imageWidth: exact(payload.image_width, 800, "M4.6 image width"),
imageHeight: exact(payload.image_height, 600, "M4.6 image height"),
timelineStartSeconds: number(payload.timeline_start_seconds, "M4.6 video start"),
timelineEndSeconds: number(payload.timeline_end_seconds, "M4.6 video end"),
frames,
};
}
export function selectM4ThreatVideoFrame(
frames: readonly M4ThreatVideoFrame[],
seconds: number,
): M4ThreatVideoFrame | null {
if (!frames.length) return null;
let low = 0;
let high = frames.length - 1;
while (low < high) {
const middle = Math.floor((low + high) / 2);
const current = frames[middle];
if (!current || current.sessionSeconds < seconds) low = middle + 1;
else high = middle;
}
const current = frames[low] ?? frames[frames.length - 1] ?? null;
const previous = frames[Math.max(0, low - 1)] ?? null;
if (!current || !previous) return current;
return Math.abs(previous.sessionSeconds - seconds) <= Math.abs(current.sessionSeconds - seconds)
? previous
: current;
}