feat(perception): add PointPillars visual audit

This commit is contained in:
DCCONSTRUCTIONS
2026-07-31 11:47:04 +03:00
parent 8fe6184c52
commit a2cb50d1ad
16 changed files with 2351 additions and 4 deletions
@@ -13,8 +13,10 @@ import {
import { fetchE34TemporalLayerResult } from "./e34TemporalLayer";
import { fetchE35DegradationRecoveryResult } from "./e35DegradationRecovery";
import { fetchE40ProductGateResult } from "./e40ProductGate";
import { fetchL3PointPillarsVisualAudit } from "./l3PointPillarsVisualAudit";
export type AdvancedLaboratoryWorkId =
| "l3-pointpillars-visual-audit"
| "e31-source-binding"
| "e32-track-geometry"
| "e33-worker-shadow"
@@ -32,6 +34,7 @@ export interface AdvancedLaboratoryIndexItem {
}
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
"l3-pointpillars-visual-audit",
"e31-source-binding",
"e32-track-geometry",
"e33-worker-shadow",
@@ -44,6 +47,7 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
];
const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
"l3-pointpillars-visual-audit": "l3-pointpillars-visual-audit",
"e31-source-binding": "e31-source-qualification",
"e32-track-geometry": "e32-track-geometry",
"e33-worker-shadow": "e33-worker-shadow",
@@ -63,6 +67,7 @@ export function isAdvancedLaboratoryWorkId(
export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
return {
l3: null,
e31: null,
e32: null,
e33: null,
@@ -163,7 +168,8 @@ export function advancedLaboratoryResultAvailable(
workId: AdvancedLaboratoryWorkId,
results: AdvancedLaboratoryResults,
): boolean {
return workId === "e31-source-binding" ? results.e31 !== null
return workId === "l3-pointpillars-visual-audit" ? results.l3 !== null
: workId === "e31-source-binding" ? results.e31 !== null
: workId === "e32-track-geometry" ? results.e32 !== null
: workId === "e33-worker-shadow" ? results.e33 !== null
: workId === "e34-temporal-layer" ? results.e34 !== null
@@ -185,7 +191,9 @@ export async function fetchAdvancedLaboratoryResult(
} = {},
): Promise<AdvancedLaboratoryResults> {
const results = emptyAdvancedLaboratoryResults();
if (workId === "e31-source-binding") {
if (workId === "l3-pointpillars-visual-audit") {
results.l3 = await fetchL3PointPillarsVisualAudit({ fetcher, signal });
} else if (workId === "e31-source-binding") {
results.e31 = await fetchOne(
"/api/v1/laboratory/e31/results?limit=1",
parseE31,
@@ -11,6 +11,7 @@ import {
type E40PerceptionProductGateResult,
} from "./e40ProductGate";
import { settledCatalogValue } from "./catalogTransport";
import type { L3PointPillarsVisualAuditResult } from "./l3PointPillarsVisualAudit";
export interface E31LaboratoryResult {
resultId: string;
@@ -238,6 +239,7 @@ export interface E39PerceptionRefinementResult {
}
export interface AdvancedLaboratoryResults {
l3: L3PointPillarsVisualAuditResult | null;
e31: E31LaboratoryResult | null;
e32: E32LaboratoryResult | null;
e33: E33LaboratoryResult | null;
@@ -988,5 +990,5 @@ export async function fetchAdvancedLaboratoryResults({
const e38 = settledCatalogValue(settled[6]);
const e39 = settledCatalogValue(settled[7]);
const e40 = settledCatalogValue(settled[8]);
return { e31, e32, e33, e34, e35, e37, e38, e39, e40 };
return { l3: null, e31, e32, e33, e34, e35, e37, e38, e39, e40 };
}
@@ -0,0 +1,384 @@
import {
AdvancedLaboratoryContractError,
type LaboratoryFetch,
} from "./advancedResults";
export interface L3VisualFrameSummary {
frameId: string;
inferenceMs: number;
predictionCount: number;
evaluatedPredictionCount: number;
outsideSharedRangeCount: number;
truthCount: number;
truePositiveCount: number;
falsePositiveCount: number;
falseNegativeCount: number;
truthClasses: readonly string[];
}
export interface L3VisualBox {
benchmarkClass: string;
centerXyzM: readonly [number, number, number];
sizeLwhM: readonly [number, number, number];
yawRad: number;
status: "matched" | "false-negative" | "true-positive" | "false-positive";
score: number | null;
}
export interface L3VisualFrame {
frameId: string;
summary: L3VisualFrameSummary;
sourcePointCount: number;
sharedRangePointCount: number;
sampledPointCount: number;
pointsXyzi: readonly number[];
truthBoxes: readonly L3VisualBox[];
predictionBoxes: readonly L3VisualBox[];
}
export interface L3PointPillarsVisualAuditResult {
resultId: string;
createdAtUtc: string;
status: "operator-visual-review-required";
sourceRunId: string;
sourceFrameResultsIdentitySha256: string;
datasetSourceId: string;
datasetReleaseIdentitySha256: string;
metrics: {
frameCount: number;
bevMap40: number;
threeDMap40: number;
falseOccupiedRate: number;
inferenceP95Ms: number;
modelOutputBoxCount: number;
evaluatedBoxCount: number;
outsideSharedRangeCount: number;
};
frames: readonly L3VisualFrameSummary[];
}
const RESULT_ID = /^l3-pointpillars-visual-audit-[a-f0-9]{64}$/;
const RUN_ID = /^l3-pointpillars-kitti-[a-f0-9]{64}$/;
const SHA256 = /^[a-f0-9]{64}$/;
const FRAME_ID = /^[0-9]{6}$/;
function objectValue(
value: unknown,
label: string,
): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new AdvancedLaboratoryContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function arrayValue(value: unknown, label: string): readonly unknown[] {
if (!Array.isArray(value)) {
throw new AdvancedLaboratoryContractError(`${label}: ожидался массив.`);
}
return value;
}
function exact(value: unknown, expected: string, label: string): string {
if (value !== expected) {
throw new AdvancedLaboratoryContractError(`${label}: нарушен контракт.`);
}
return expected;
}
function stringValue(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new AdvancedLaboratoryContractError(`${label}: ожидалась строка.`);
}
return value;
}
function numberValue(value: unknown, label: string, minimum = 0): number {
if (
typeof value !== "number"
|| !Number.isFinite(value)
|| value < minimum
) {
throw new AdvancedLaboratoryContractError(`${label}: неверное число.`);
}
return value;
}
function integerValue(value: unknown, label: string): number {
const parsed = numberValue(value, label);
if (!Number.isInteger(parsed)) {
throw new AdvancedLaboratoryContractError(`${label}: ожидалось целое.`);
}
return parsed;
}
function tuple3(
value: unknown,
label: string,
positive = false,
): readonly [number, number, number] {
const values = arrayValue(value, label);
if (values.length !== 3) {
throw new AdvancedLaboratoryContractError(`${label}: ожидалось 3 числа.`);
}
return [
numberValue(values[0], `${label}[0]`, positive ? Number.MIN_VALUE : -Infinity),
numberValue(values[1], `${label}[1]`, positive ? Number.MIN_VALUE : -Infinity),
numberValue(values[2], `${label}[2]`, positive ? Number.MIN_VALUE : -Infinity),
];
}
function parseSummary(value: unknown): L3VisualFrameSummary {
const item = objectValue(value, "L3 visual frame");
const frameId = stringValue(item.frame_id, "L3 frame_id");
if (!FRAME_ID.test(frameId)) {
throw new AdvancedLaboratoryContractError("L3 frame_id: неверный формат.");
}
const truthClasses = arrayValue(item.truth_classes, "L3 truth_classes")
.map((entry) => stringValue(entry, "L3 truth class"));
if (
truthClasses.some(
(entry) => !["Car", "Pedestrian", "Cyclist"].includes(entry),
)
) {
throw new AdvancedLaboratoryContractError("L3 truth class: неизвестен.");
}
return {
frameId,
inferenceMs: numberValue(item.inference_ms, "L3 inference_ms", Number.MIN_VALUE),
predictionCount: integerValue(item.prediction_count, "L3 prediction_count"),
evaluatedPredictionCount: integerValue(
item.evaluated_prediction_count,
"L3 evaluated_prediction_count",
),
outsideSharedRangeCount: integerValue(
item.outside_shared_range_count,
"L3 outside_shared_range_count",
),
truthCount: integerValue(item.truth_count, "L3 truth_count"),
truePositiveCount: integerValue(
item.true_positive_count,
"L3 true_positive_count",
),
falsePositiveCount: integerValue(
item.false_positive_count,
"L3 false_positive_count",
),
falseNegativeCount: integerValue(
item.false_negative_count,
"L3 false_negative_count",
),
truthClasses,
};
}
function parseBox(value: unknown, truth: boolean): L3VisualBox {
const box = objectValue(value, "L3 visual box");
const benchmarkClass = stringValue(
box.benchmark_class,
"L3 benchmark_class",
);
if (!["Car", "Pedestrian", "Cyclist"].includes(benchmarkClass)) {
throw new AdvancedLaboratoryContractError("L3 box class: неизвестен.");
}
const status = stringValue(box.status, "L3 box status");
const allowed = truth
? ["matched", "false-negative"]
: ["true-positive", "false-positive"];
if (!allowed.includes(status)) {
throw new AdvancedLaboratoryContractError("L3 box status: неизвестен.");
}
return {
benchmarkClass,
centerXyzM: tuple3(box.center_xyz_m, "L3 center_xyz_m"),
sizeLwhM: tuple3(box.size_lwh_m, "L3 size_lwh_m", true),
yawRad: numberValue(box.yaw_rad, "L3 yaw_rad", -Infinity),
status: status as L3VisualBox["status"],
score: truth ? null : numberValue(box.score, "L3 score"),
};
}
function parseResult(value: unknown): L3PointPillarsVisualAuditResult {
const result = objectValue(value, "L3 visual result");
exact(
result.schema_version,
"missioncore.l3-pointpillars-visual-audit-result/v1",
"L3 result.schema_version",
);
exact(result.access, "read-only", "L3 result.access");
const resultId = stringValue(result.result_id, "L3 result_id");
const sourceRunId = stringValue(result.source_run_id, "L3 source_run_id");
const frameIdentity = stringValue(
result.source_frame_results_identity_sha256,
"L3 frame identity",
);
const datasetIdentity = stringValue(
result.dataset_release_identity_sha256,
"L3 dataset identity",
);
if (
!RESULT_ID.test(resultId)
|| !RUN_ID.test(sourceRunId)
|| !SHA256.test(frameIdentity)
|| !SHA256.test(datasetIdentity)
) {
throw new AdvancedLaboratoryContractError(
"L3 result: нарушена идентичность.",
);
}
const metrics = objectValue(result.metrics, "L3 metrics");
const aggregates = objectValue(metrics.aggregates, "L3 aggregates");
const volume = objectValue(
aggregates.prediction_volume,
"L3 prediction_volume",
);
const latency = objectValue(
aggregates.inference_latency_ms,
"L3 latency",
);
const frames = arrayValue(result.frames, "L3 frames").map(parseSummary);
if (
!frames.length
|| frames.length > 24
|| new Set(frames.map(({ frameId }) => frameId)).size !== frames.length
) {
throw new AdvancedLaboratoryContractError(
"L3 frames: нарушен ограниченный каталог.",
);
}
return {
resultId,
createdAtUtc: stringValue(result.created_at_utc, "L3 created_at_utc"),
status: exact(
result.status,
"operator-visual-review-required",
"L3 status",
) as "operator-visual-review-required",
sourceRunId,
sourceFrameResultsIdentitySha256: frameIdentity,
datasetSourceId: stringValue(result.dataset_source_id, "L3 dataset_source_id"),
datasetReleaseIdentitySha256: datasetIdentity,
metrics: {
frameCount: integerValue(metrics.frame_count, "L3 frame_count"),
bevMap40: numberValue(aggregates.bev_map40, "L3 bev_map40"),
threeDMap40: numberValue(aggregates["3d_map40"], "L3 3d_map40"),
falseOccupiedRate: numberValue(
aggregates.false_occupied_rate,
"L3 false_occupied_rate",
),
inferenceP95Ms: numberValue(latency.p95, "L3 latency.p95"),
modelOutputBoxCount: integerValue(
volume.model_output_box_count,
"L3 model_output_box_count",
),
evaluatedBoxCount: integerValue(
volume.evaluated_box_count,
"L3 evaluated_box_count",
),
outsideSharedRangeCount: integerValue(
volume.outside_shared_range_count,
"L3 outside_shared_range_count",
),
},
frames,
};
}
export async function fetchL3PointPillarsVisualAudit({
fetcher = fetch,
signal,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {}): Promise<L3PointPillarsVisualAuditResult | null> {
const response = await fetcher(
"/api/v1/laboratory/l3/pointpillars-visual-audits/results?limit=1",
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) {
throw new AdvancedLaboratoryContractError(
`L3 visual audit недоступен: HTTP ${response.status}.`,
);
}
const catalog = objectValue(await response.json(), "L3 result catalog");
exact(
catalog.schema_version,
"missioncore.l3-pointpillars-visual-audit-catalog-results/v1",
"L3 catalog.schema_version",
);
exact(catalog.access, "read-only", "L3 catalog.access");
const items = arrayValue(catalog.items, "L3 catalog.items");
if (items.length > 1) {
throw new AdvancedLaboratoryContractError("L3 catalog: лишние результаты.");
}
return items.length ? parseResult(items[0]) : null;
}
export async function fetchL3PointPillarsVisualFrame(
resultId: string,
frameId: string,
{
fetcher = fetch,
signal,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {},
): Promise<L3VisualFrame> {
if (!RESULT_ID.test(resultId) || !FRAME_ID.test(frameId)) {
throw new AdvancedLaboratoryContractError(
"L3 visual frame: неверная идентичность.",
);
}
const response = await fetcher(
`/api/v1/laboratory/l3/pointpillars-visual-audits/${resultId}/frames/${frameId}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) {
throw new AdvancedLaboratoryContractError(
`L3 visual frame недоступен: HTTP ${response.status}.`,
);
}
const payload = objectValue(await response.json(), "L3 visual frame");
exact(
payload.schema_version,
"missioncore.l3-pointpillars-visual-frame/v1",
"L3 frame.schema_version",
);
exact(payload.access, "read-only", "L3 frame.access");
exact(payload.frame_id, frameId, "L3 frame.frame_id");
const points = objectValue(payload.points, "L3 points");
exact(points.layout, "flat-xyzi", "L3 points.layout");
const pointValues = arrayValue(points.values, "L3 points.values").map(
(value, index) => numberValue(value, `L3 points[${index}]`, -Infinity),
);
const sampledPointCount = integerValue(
points.sampled_point_count,
"L3 sampled_point_count",
);
if (sampledPointCount > 12_000 || pointValues.length !== sampledPointCount * 4) {
throw new AdvancedLaboratoryContractError(
"L3 points: нарушен ограниченный массив.",
);
}
return {
frameId,
summary: parseSummary(payload.summary),
sourcePointCount: integerValue(
points.source_point_count,
"L3 source_point_count",
),
sharedRangePointCount: integerValue(
points.shared_range_point_count,
"L3 shared_range_point_count",
),
sampledPointCount,
pointsXyzi: pointValues,
truthBoxes: arrayValue(payload.truth_boxes, "L3 truth_boxes")
.map((box) => parseBox(box, true)),
predictionBoxes: arrayValue(
payload.prediction_boxes,
"L3 prediction_boxes",
).map((box) => parseBox(box, false)),
};
}
+1
View File
@@ -4,6 +4,7 @@
@import "./styles/workspaces.css";
@import "./styles/laboratory.css";
@import "./styles/e40-case-review.css";
@import "./styles/l3-pointpillars-visual-audit.css";
@import "./styles/laboratory-reporting.css";
@import "./styles/e34-temporal-layer.css";
@import "./styles/e35-degradation-recovery.css";
@@ -0,0 +1,142 @@
.l3-visual-audit {
display: grid;
height: clamp(36rem, 68vh, 54rem);
min-height: 36rem;
}
.l3-visual-audit > .laboratory-evidence-viewer {
min-height: 0;
}
.l3-visual-audit__scene {
position: relative;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
background: var(--nodedc-canvas);
}
.l3-visual-audit__scene canvas {
display: block;
width: 100%;
height: 100%;
}
.l3-visual-audit__state {
display: flex;
width: 100%;
height: 100%;
min-height: 22rem;
align-items: center;
justify-content: center;
gap: 0.55rem;
background: var(--nodedc-canvas);
color: var(--nodedc-text-muted);
font-size: 0.62rem;
}
.l3-visual-audit__actions {
display: flex;
flex: 1;
min-width: 0;
align-items: center;
gap: 0.45rem;
}
.l3-visual-audit__pagination {
display: flex;
flex: none;
gap: 0.35rem;
}
.l3-visual-audit__actions .nodedc-select-anchor,
.l3-visual-audit__actions .nodedc-select {
width: clamp(17rem, 34vw, 31rem);
}
.l3-visual-audit
.laboratory-evidence-viewer__controls:has(.l3-visual-audit__actions) {
right: 0.6rem;
left: 0.6rem;
}
.l3-visual-audit__overlay {
position: absolute;
z-index: 3;
left: 0.6rem;
bottom: 0.6rem;
display: grid;
width: min(58rem, calc(100% - 1.2rem));
grid-template-columns: minmax(8rem, 0.55fr) minmax(13rem, 1fr) minmax(19rem, 1.2fr);
align-items: end;
gap: 0.7rem;
border-radius: var(--nodedc-radius-control-compact);
background: var(--nodedc-floating-surface);
padding: 0.55rem 0.65rem;
color: var(--nodedc-text-secondary);
backdrop-filter: blur(var(--nodedc-blur-control));
pointer-events: none;
}
.l3-visual-audit__overlay > div {
display: grid;
min-width: 0;
gap: 0.12rem;
}
.l3-visual-audit__overlay span,
.l3-visual-audit__overlay small {
color: var(--nodedc-text-muted);
font-size: 0.49rem;
line-height: 1.35;
}
.l3-visual-audit__overlay strong {
color: var(--nodedc-text-primary);
font-size: 0.6rem;
}
.l3-visual-audit__legend {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.35rem 0.6rem;
}
.l3-visual-audit__legend span {
display: flex;
align-items: center;
gap: 0.35rem;
color: var(--nodedc-text-secondary);
white-space: nowrap;
}
.l3-visual-audit__legend span::before {
width: 0.42rem;
height: 0.42rem;
flex: none;
border-radius: 50%;
background: var(--nodedc-text-primary);
content: "";
}
.l3-visual-audit__legend span[data-tone="tp"]::before {
background: rgb(var(--nodedc-success-rgb));
}
.l3-visual-audit__legend span[data-tone="fp"]::before {
background: rgb(var(--nodedc-danger-rgb));
}
.l3-visual-audit__legend span[data-tone="fn"]::before {
background: rgb(var(--nodedc-warning-rgb));
}
@media (max-width: 900px) {
.l3-visual-audit__overlay {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.l3-visual-audit__legend {
display: none;
}
}
@@ -18,6 +18,7 @@ import { E37Result } from "./E37Result";
import { E38Result } from "./E38Result";
import { E39Result } from "./E39Result";
import { E40Result } from "./E40Result";
import { L3PointPillarsResult } from "./L3PointPillarsResult";
import { RecordedReplayEvidence } from "./RecordedReplayEvidence";
export { isAdvancedLaboratoryWorkId };
@@ -32,6 +33,10 @@ export function advancedLaboratoryWorkOptions(
): readonly LaboratoryOption<AdvancedLaboratoryWorkId>[] {
const available = new Set(index.map(({ workId }) => workId));
const options: readonly LaboratoryOption<AdvancedLaboratoryWorkId>[] = [
{
id: "l3-pointpillars-visual-audit",
label: "L3 · визуальный аудит PointPillars",
},
{ id: "e31-source-binding", label: "LAB E31 · source binding" },
{ id: "e32-track-geometry", label: "LAB E32 · TrackGeometry v1" },
{ id: "e33-worker-shadow", label: "LAB E33 · worker shadow 1×" },
@@ -82,6 +87,9 @@ export function AdvancedLaboratoryResult({
failedSessionId: string | null;
replayError: string | null;
}) {
if (workId === "l3-pointpillars-visual-audit" && results.l3) {
return <L3PointPillarsResult result={results.l3} />;
}
if (workId === "e40-perception-product-gate" && results.e40) {
return <E40Result rigLabel={rigLabel} result={results.e40} />;
}
@@ -0,0 +1,141 @@
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type {
L3PointPillarsVisualAuditResult,
} from "../../core/laboratory/l3PointPillarsVisualAudit";
import { L3PointPillarsVisualAudit } from "./L3PointPillarsVisualAudit";
function percent(value: number, digits = 3): string {
return `${(value * 100).toLocaleString("ru-RU", {
maximumFractionDigits: digits,
})}%`;
}
export function L3PointPillarsResult({
result,
}: {
result: L3PointPillarsVisualAuditResult;
}) {
const metrics = result.metrics;
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="L3 · визуальный аудит PointPillars"
description="Визуальная производная полного KITTI transfer-прогона: исходные LiDAR-точки, независимые truth-боксы и предсказания модели сопоставлены тем же глобальным 3D IoU-контрактом. Производная не меняет метрики и не выдает результат за K1 accuracy."
status="Требуется визуальная проверка"
statusTone="warning"
facts={[
{
label: "Источник",
value: `${result.datasetSourceId} · ${metrics.frameCount.toLocaleString("ru-RU")} кадров`,
},
{
label: "Визуальная выборка",
value: `${result.frames.length} доказательных кадров · lazy-load`,
},
{
label: "Исполнение",
value: "Worker 006 · последовательная производная",
},
{
label: "Полномочия",
value: "Read-only · без navigation/safety acceptance",
},
]}
brief={{
question: "Соответствуют ли измеренные провал переноса и почти сплошная ложная занятость фактической геометрии исходных LiDAR-кадров?",
approach: "Полный sealed run проверен по hash identity. Global score-order matching повторён с исходными порогами IoU, после чего детерминированно выбраны TP-, FP-, FN- и class-coverage кадры. В браузер поступает только выбранный кадр.",
principalResult: `Полный прогон: BEV mAP40 ${percent(metrics.bevMap40)}, 3D mAP40 ${percent(metrics.threeDMap40, 6)}, false occupied ${percent(metrics.falseOccupiedRate)}. Визуальный аудит теперь доступен в 3D и BEV.`,
limitation: "Это cross-domain KITTI probe модели, обученной на proprietary solid-state LiDAR. Он проверяет перенос и корректность измерителя, но не доказывает точность K1, camera-first детектор, навигацию или safety.",
}}
method={{
completeness: "complete",
executionClass: "ai-inference",
pipelineId: "l3-pointpillars-kitti-transfer/visual-audit-v1",
components: [
{
kind: "source",
name: result.sourceRunId,
version: "sealed 3769-frame transfer run",
role: "неизменяемые предсказания и latency",
identitySha256: result.sourceFrameResultsIdentitySha256,
},
{
kind: "source",
name: result.datasetSourceId,
version: "admitted public release",
role: "LiDAR и независимые ориентированные 3D truth-боксы",
identitySha256: result.datasetReleaseIdentitySha256,
},
{
kind: "algorithm",
name: "Global score-order oriented 3D IoU matching",
version: "Car 0.7 · Pedestrian/Cyclist 0.5",
role: "единая классификация TP, FP и FN",
identitySha256: null,
},
{
kind: "runtime",
name: "Worker 006 → Mission Core lazy evidence",
version: result.resultId,
role: "append-only visual derivative без повторного inference",
identitySha256: result.resultId.split("-").at(-1) ?? null,
},
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="SEALED RUN → ВИЗУАЛЬНОЕ ДОКАЗАТЕЛЬСТВО"
title="LiDAR, truth и предсказания PointPillars"
kind="diagnostic-model"
resizable
>
<L3PointPillarsVisualAudit result={result} />
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Численный transfer gate не пройден; визуальная ревизия открыта"
status="Не допущено"
statusTone="danger"
metrics={[
{
label: "BEV mAP40",
value: percent(metrics.bevMap40),
hint: "полный denominator · public cross-domain",
},
{
label: "3D mAP40",
value: percent(metrics.threeDMap40, 6),
hint: `${metrics.evaluatedBoxCount.toLocaleString("ru-RU")} оценённых боксов`,
},
{
label: "False occupied",
value: percent(metrics.falseOccupiedRate),
hint: `${metrics.modelOutputBoxCount.toLocaleString("ru-RU")} post-NMS · ${metrics.outsideSharedRangeCount.toLocaleString("ru-RU")} вне range`,
},
{
label: "Inference p95",
value: `${metrics.inferenceP95Ms.toLocaleString("ru-RU", {
maximumFractionDigits: 2,
})} мс`,
hint: "Worker 006 · последовательное исполнение",
},
]}
conclusion={{
proved: "Полный cross-domain прогон воспроизводим, его численные артефакты связаны с исходными LiDAR-кадрами, а TP/FP/FN можно проверить в 3D и BEV без повторного inference.",
notProved: "Не доказаны пригодность этой модели для K1, точность camera-first семантики, метрическая геометрия K1 в других условиях, навигация, команды или safety.",
decision: "Не переносить этот публичный PointPillars-кандидат в operational pipeline. Использовать визуальный аудит для проверки природы провала и сохранить архитектуру camera-first semantics + LiDAR metric geometry как основной продуктовый путь.",
}}
/>
)}
/>
);
}
@@ -0,0 +1,256 @@
import { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import type {
L3VisualBox,
L3VisualFrame,
} from "../../core/laboratory/l3PointPillarsVisualAudit";
export type L3VisualMode = "3d" | "bev";
function tokenColor(
host: HTMLElement,
token: string,
fallback: readonly [number, number, number],
): THREE.Color {
const value = getComputedStyle(host).getPropertyValue(token).trim();
if (value.startsWith("#")) return new THREE.Color(value);
const channels = value.match(/[\d.]+/g)?.slice(0, 3).map(Number);
const [red, green, blue] = channels?.length === 3 ? channels : fallback;
return new THREE.Color(red / 255, green / 255, blue / 255);
}
function pointPositions(values: readonly number[]): Float32Array {
const positions = new Float32Array((values.length / 4) * 3);
for (
let source = 0, target = 0;
source < values.length;
source += 4, target += 3
) {
positions[target] = values[source];
positions[target + 1] = values[source + 2];
positions[target + 2] = -values[source + 1];
}
return positions;
}
function boxSegments(box: L3VisualBox): Float32Array {
const [centerX, centerY, centerZ] = box.centerXyzM;
const [length, width, height] = box.sizeLwhM;
const cosine = Math.cos(box.yawRad);
const sine = Math.sin(box.yawRad);
const corners: THREE.Vector3[] = [];
for (const zOffset of [-height / 2, height / 2]) {
for (const [xOffset, yOffset] of [
[-length / 2, -width / 2],
[length / 2, -width / 2],
[length / 2, width / 2],
[-length / 2, width / 2],
]) {
const x = centerX + xOffset * cosine - yOffset * sine;
const y = centerY + xOffset * sine + yOffset * cosine;
corners.push(new THREE.Vector3(x, centerZ + zOffset, -y));
}
}
const edges = [
[0, 1], [1, 2], [2, 3], [3, 0],
[4, 5], [5, 6], [6, 7], [7, 4],
[0, 4], [1, 5], [2, 6], [3, 7],
];
const positions = new Float32Array(edges.length * 6);
edges.forEach(([from, to], index) => {
corners[from].toArray(positions, index * 6);
corners[to].toArray(positions, index * 6 + 3);
});
return positions;
}
function addBoxes(
scene: THREE.Scene,
boxes: readonly L3VisualBox[],
colors: Readonly<Record<L3VisualBox["status"], THREE.Color>>,
opacity: number,
): THREE.LineSegments[] {
return boxes.map((box) => {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.BufferAttribute(boxSegments(box), 3),
);
const material = new THREE.LineBasicMaterial({
color: colors[box.status],
transparent: true,
opacity,
depthTest: true,
depthWrite: false,
});
const lines = new THREE.LineSegments(geometry, material);
scene.add(lines);
return lines;
});
}
export function L3PointPillarsScene({
frame,
mode,
}: {
frame: L3VisualFrame;
mode: L3VisualMode;
}) {
const hostRef = useRef<HTMLDivElement | null>(null);
const [renderError, setRenderError] = useState<string | null>(null);
useEffect(() => {
const host = hostRef.current;
if (!host) return;
setRenderError(null);
let renderer: THREE.WebGLRenderer;
try {
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: false,
powerPreference: "high-performance",
});
} catch {
setRenderError("Браузер не смог открыть WebGL-сцену L3.");
return;
}
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.setClearColor(
tokenColor(host, "--nodedc-canvas", [5, 5, 6]),
1,
);
renderer.domElement.setAttribute("role", "img");
renderer.domElement.setAttribute(
"aria-label",
`L3 PointPillars: кадр ${frame.frameId}, режим ${mode}`,
);
host.append(renderer.domElement);
const scene = new THREE.Scene();
const positions = pointPositions(frame.pointsXyzi);
const pointsGeometry = new THREE.BufferGeometry();
pointsGeometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3),
);
const pointsMaterial = new THREE.PointsMaterial({
color: tokenColor(host, "--nodedc-text-secondary", [187, 190, 196]),
size: mode === "bev" ? 1.4 : 1.8,
sizeAttenuation: false,
transparent: true,
opacity: 0.52,
depthWrite: false,
});
scene.add(new THREE.Points(pointsGeometry, pointsMaterial));
const colors: Readonly<Record<L3VisualBox["status"], THREE.Color>> = {
matched: tokenColor(host, "--nodedc-text-primary", [247, 248, 244]),
"false-negative": tokenColor(
host,
"--nodedc-warning-rgb",
[255, 209, 102],
),
"true-positive": tokenColor(
host,
"--nodedc-success-rgb",
[143, 255, 93],
),
"false-positive": tokenColor(
host,
"--nodedc-danger-rgb",
[255, 98, 112],
),
};
const truthLines = addBoxes(scene, frame.truthBoxes, colors, 0.9);
const predictionLines = addBoxes(
scene,
frame.predictionBoxes,
colors,
0.72,
);
const grid = new THREE.GridHelper(
80,
40,
tokenColor(host, "--nodedc-text-muted", [96, 99, 106]),
tokenColor(host, "--nodedc-glass-outline", [48, 50, 56]),
);
const gridMaterials = Array.isArray(grid.material)
? grid.material
: [grid.material];
gridMaterials.forEach((material) => {
material.transparent = true;
material.opacity = 0.22;
material.depthWrite = false;
});
scene.add(grid);
const perspective = new THREE.PerspectiveCamera(52, 1, 0.1, 500);
perspective.position.set(-12, 18, 36);
const orthographic = new THREE.OrthographicCamera(-40, 40, 40, -40, 0.1, 500);
orthographic.position.set(35, 100, 0);
orthographic.up.set(1, 0, 0);
const camera = mode === "bev" ? orthographic : perspective;
camera.lookAt(30, 0, 0);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = false;
controls.enableRotate = mode === "3d";
controls.enablePan = true;
controls.enableZoom = true;
controls.screenSpacePanning = true;
controls.target.set(30, 0, 0);
controls.update();
const render = () => renderer.render(scene, camera);
controls.addEventListener("change", render);
const resize = () => {
const width = Math.max(host.clientWidth, 1);
const height = Math.max(host.clientHeight, 1);
renderer.setSize(width, height, false);
if (camera instanceof THREE.PerspectiveCamera) {
camera.aspect = width / height;
camera.updateProjectionMatrix();
} else {
const horizontal = 42;
camera.left = -horizontal;
camera.right = horizontal;
camera.top = horizontal / (width / height);
camera.bottom = -horizontal / (width / height);
camera.updateProjectionMatrix();
}
render();
};
const observer = new ResizeObserver(resize);
observer.observe(host);
resize();
return () => {
observer.disconnect();
controls.removeEventListener("change", render);
controls.dispose();
pointsGeometry.dispose();
pointsMaterial.dispose();
[...truthLines, ...predictionLines].forEach((lines) => {
lines.geometry.dispose();
(lines.material as THREE.Material).dispose();
});
grid.geometry.dispose();
gridMaterials.forEach((material) => material.dispose());
renderer.dispose();
renderer.domElement.remove();
};
}, [frame, mode]);
return (
<div className="l3-visual-audit__scene" ref={hostRef}>
{renderError ? (
<div className="l3-visual-audit__state" role="status">
{renderError}
</div>
) : null}
</div>
);
}
@@ -0,0 +1,171 @@
import { useEffect, useState } from "react";
import {
Icon,
IconButton,
Select,
} from "@nodedc/ui-react";
import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer";
import {
fetchL3PointPillarsVisualFrame,
type L3PointPillarsVisualAuditResult,
type L3VisualFrame,
} from "../../core/laboratory/l3PointPillarsVisualAudit";
import {
L3PointPillarsScene,
type L3VisualMode,
} from "./L3PointPillarsScene";
function frameLabel(
frame: L3PointPillarsVisualAuditResult["frames"][number],
): string {
return (
`Кадр ${frame.frameId} · TP ${frame.truePositiveCount}`
+ ` · FP ${frame.falsePositiveCount} · FN ${frame.falseNegativeCount}`
);
}
export function L3PointPillarsVisualAudit({
result,
}: {
result: L3PointPillarsVisualAuditResult;
}) {
const [selectedFrameId, setSelectedFrameId] = useState(
result.frames[0]?.frameId ?? "",
);
const [frame, setFrame] = useState<L3VisualFrame | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [mode, setMode] = useState<L3VisualMode>("3d");
const [expanded, setExpanded] = useState(false);
useEffect(() => {
if (!selectedFrameId) return;
const controller = new AbortController();
setFrame(null);
setLoading(true);
setError(null);
void fetchL3PointPillarsVisualFrame(
result.resultId,
selectedFrameId,
{ signal: controller.signal },
).then((next) => {
if (!controller.signal.aborted) setFrame(next);
}).catch((caught: unknown) => {
if (controller.signal.aborted) return;
setError(
caught instanceof Error
? caught.message
: "Визуальный кадр L3 недоступен.",
);
}).finally(() => {
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort();
}, [result.resultId, selectedFrameId]);
const selectedIndex = result.frames.findIndex(
({ frameId }) => frameId === selectedFrameId,
);
const navigate = (offset: -1 | 1) => {
if (!result.frames.length || selectedIndex < 0) return;
const index = (
selectedIndex + offset + result.frames.length
) % result.frames.length;
setSelectedFrameId(result.frames[index].frameId);
};
const controls = (
<div className="l3-visual-audit__actions">
<div className="l3-visual-audit__pagination">
<IconButton
label="Предыдущий кадр L3"
onClick={() => navigate(-1)}
>
<Icon name="chevron-left" size={16} />
</IconButton>
<IconButton
label="Следующий кадр L3"
onClick={() => navigate(1)}
>
<Icon name="chevron-right" size={16} />
</IconButton>
</div>
<Select
label="Выбрать кадр визуального аудита L3"
value={selectedFrameId}
options={result.frames.map((item) => ({
value: item.frameId,
label: frameLabel(item),
}))}
variant="split"
menuWidth="anchor"
onChange={setSelectedFrameId}
/>
</div>
);
const overlay = frame ? (
<div className="l3-visual-audit__overlay">
<div>
<span>Кадр</span>
<strong>{frame.frameId}</strong>
<small>
{frame.sampledPointCount.toLocaleString("ru-RU")} из{" "}
{frame.sharedRangePointCount.toLocaleString("ru-RU")} точек
</small>
</div>
<div>
<span>Сопоставление 3D</span>
<strong>
TP {frame.summary.truePositiveCount}
{" · "}FP {frame.summary.falsePositiveCount}
{" · "}FN {frame.summary.falseNegativeCount}
</strong>
<small>
{frame.summary.inferenceMs.toLocaleString("ru-RU", {
maximumFractionDigits: 2,
})} мс · {frame.summary.outsideSharedRangeCount} вне общего range
</small>
</div>
<div className="l3-visual-audit__legend" aria-label="Легенда L3">
<span data-tone="truth">Truth · совпало</span>
<span data-tone="tp">TP · предсказание</span>
<span data-tone="fp">FP · ложный бокс</span>
<span data-tone="fn">FN · пропущенный truth</span>
</div>
</div>
) : undefined;
return (
<div className="l3-visual-audit">
<LaboratoryEvidenceViewer
label="визуальный аудит PointPillars"
mode={mode}
modes={[
{ value: "3d", label: "3D" },
{ value: "bev", label: "BEV" },
]}
expanded={expanded}
onModeChange={setMode}
onExpandedChange={setExpanded}
actions={controls}
overlay={overlay}
>
{loading ? (
<div className="l3-visual-audit__state" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Проверяем и открываем выбранный кадр L3</span>
</div>
) : error || !frame ? (
<div className="l3-visual-audit__state" role="status">
<Icon name="alert" size={18} />
<span>{error ?? "Визуальный кадр L3 недоступен."}</span>
</div>
) : (
<L3PointPillarsScene frame={frame} mode={mode} />
)}
</LaboratoryEvidenceViewer>
</div>
);
}
@@ -18,6 +18,7 @@ function mergeResults(
next: AdvancedLaboratoryResults,
): AdvancedLaboratoryResults {
return {
l3: next.l3 ?? current.l3,
e31: next.e31 ?? current.e31,
e32: next.e32 ?? current.e32,
e33: next.e33 ?? current.e33,
@@ -46,6 +46,18 @@ const e40CaseReviewUrl = new URL(
"../src/workspaces/laboratory/E40CaseReview.tsx",
import.meta.url,
);
const l3ResultUrl = new URL(
"../src/workspaces/laboratory/L3PointPillarsResult.tsx",
import.meta.url,
);
const l3VisualAuditUrl = new URL(
"../src/workspaces/laboratory/L3PointPillarsVisualAudit.tsx",
import.meta.url,
);
const l3SceneUrl = new URL(
"../src/workspaces/laboratory/L3PointPillarsScene.tsx",
import.meta.url,
);
const e35StylesUrl = new URL(
"../src/styles/e35-degradation-recovery.css",
import.meta.url,
@@ -154,6 +166,30 @@ test("bounded LAB result modules use the versioned shared report anatomy", async
}
});
test("L3 visual audit binds the sealed metrics to one lazy 3D/BEV viewer", async () => {
const [result, audit, scene, advanced] = await Promise.all([
readFile(l3ResultUrl, "utf8"),
readFile(l3VisualAuditUrl, "utf8"),
readFile(l3SceneUrl, "utf8"),
readFile(advancedLaboratoryResultUrl, "utf8"),
]);
assert.match(result, /<LaboratoryWorkTemplate/);
assert.match(result, /<LaboratoryEvidence/);
assert.match(result, /<LaboratoryResultSummary/);
assert.match(result, /Производная не меняет метрики/);
assert.match(audit, /fetchL3PointPillarsVisualFrame/);
assert.match(audit, /\{ value: "3d", label: "3D" \}/);
assert.match(audit, /\{ value: "bev", label: "BEV" \}/);
assert.match(audit, /Предыдущий кадр L3/);
assert.match(audit, /Следующий кадр L3/);
assert.match(scene, /new THREE\.OrthographicCamera/);
assert.match(scene, /"false-positive"/);
assert.match(scene, /"false-negative"/);
assert.match(advanced, /id: "l3-pointpillars-visual-audit"/);
assert.match(advanced, /<L3PointPillarsResult/);
});
test("E33 explains the experiment, its method and its retained limits", async () => {
const [presentationSource, e33Source] = await Promise.all([
readFile(presentationSourceUrl, "utf8"),
@@ -0,0 +1,664 @@
#!/usr/bin/env python3
"""Build a bounded visual-audit derivative of a sealed L3 transfer run."""
from __future__ import annotations
import argparse
import hashlib
import json
import math
import os
import tempfile
import zipfile
from contextlib import suppress
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Final
import numpy as np
from k1link.compute.kitti_pointpillars_benchmark import (
CROSS_DOMAIN_EVALUATION_RANGE,
KITTI_BENCHMARK_CLASSES,
KITTI_IOU_THRESHOLDS,
MODEL_TO_KITTI_CLASS,
KittiLidarTruth,
read_kitti_validation_truth,
)
from k1link.compute.pointpillars_postprocess import (
PointPillarsBox,
oriented_3d_iou,
)
from k1link.datasets.kitti_3d_admission import (
KITTI_3D_RELEASE_ROOT,
KITTI_CALIB_ARCHIVE,
KITTI_LABEL_ARCHIVE,
KITTI_VELODYNE_ARCHIVE,
read_kitti_3d_admission,
read_kitti_standard_splits,
)
SOURCE_MANIFEST_SCHEMA: Final = (
"missioncore.l3-pointpillars-kitti-transfer-result/v1"
)
SOURCE_FRAME_SCHEMA: Final = (
"missioncore.l3-pointpillars-kitti-transfer-frame/v1"
)
VISUAL_AUDIT_SCHEMA: Final = "missioncore.l3-pointpillars-visual-audit/v1"
VISUAL_CATALOG_SCHEMA: Final = (
"missioncore.l3-pointpillars-visual-audit-catalog/v1"
)
VISUAL_FRAME_SCHEMA: Final = "missioncore.l3-pointpillars-visual-frame/v1"
EXPECTED_SOURCE_RUN_ID: Final = (
"l3-pointpillars-kitti-"
"1a6b499e194a363644854dc324bd1b565c100b809f145c1324a25328e7ae0910"
)
EXPECTED_FRAME_RESULTS_IDENTITY: Final = (
"30b1933d508a09025a7d3c3c460fc2d06128e4bbe96a753bec7ba8545fda3e9c"
)
MAX_SELECTED_FRAMES: Final = 18
MAX_SAMPLED_POINTS: Final = 12_000
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source-run", type=Path, required=True)
parser.add_argument("--dataset-root", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
result = build_visual_audit(
source_run=args.source_run,
dataset_root=args.dataset_root,
output_root=args.output_root,
)
print(json.dumps(result, sort_keys=True), flush=True)
return 0
def build_visual_audit(
*,
source_run: Path,
dataset_root: Path,
output_root: Path,
) -> dict[str, object]:
run_root = source_run.expanduser().resolve(strict=True)
if not run_root.is_dir() or run_root.is_symlink():
raise RuntimeError("sealed L3 source run is unavailable")
source_manifest_path = run_root / "manifest.json"
source_report_path = run_root / "report.json"
source_manifest = _read_json(source_manifest_path)
source_report = _read_json(source_report_path)
_validate_source_manifest(
source_manifest,
source_report,
source_manifest_path=source_manifest_path,
source_report_path=source_report_path,
run_root=run_root,
)
dataset = read_kitti_3d_admission(dataset_root.expanduser().absolute())
validation_ids = read_kitti_standard_splits(
dataset_root.expanduser().absolute()
)["validation"]
if (
dataset["release_identity_sha256"]
!= source_manifest["identity"]["dataset_release_identity_sha256"]
or len(validation_ids) != source_manifest["frame_result_count"]
):
raise RuntimeError("sealed run and admitted KITTI release diverge")
archive_root = (
dataset_root.expanduser().absolute()
/ KITTI_3D_RELEASE_ROOT
/ "archives"
)
truths = read_kitti_validation_truth(
labels_archive=archive_root / KITTI_LABEL_ARCHIVE,
calibrations_archive=archive_root / KITTI_CALIB_ARCHIVE,
validation_frame_ids=validation_ids,
)
predictions = _read_predictions(run_root / "frames", validation_ids)
matched_predictions, matched_truth = _global_matches(predictions, truths)
summaries = _frame_summaries(
predictions,
truths,
matched_predictions,
matched_truth,
)
selected_ids = _select_frames(summaries)
identity = {
"source_run_id": source_manifest["run_id"],
"source_manifest_sha256": _sha256(source_manifest_path),
"source_frame_results_identity_sha256": source_manifest[
"frame_results_identity_sha256"
],
"dataset_source_id": dataset["source_id"],
"dataset_release_identity_sha256": dataset[
"release_identity_sha256"
],
"matching": {
"metric": "oriented-3d-iou",
"ordering": "global-score-descending",
"class_iou_thresholds": KITTI_IOU_THRESHOLDS,
"shared_evaluation_range": list(CROSS_DOMAIN_EVALUATION_RANGE),
},
"selection": {
"policy": "tp-first-then-fp-fn-class-coverage/v1",
"maximum_frames": MAX_SELECTED_FRAMES,
"selected_frame_ids": list(selected_ids),
},
"point_sampling": {
"policy": "shared-range-even-index/v1",
"maximum_points_per_frame": MAX_SAMPLED_POINTS,
"fields": ["x_m", "y_m", "z_m", "intensity"],
},
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
"authority": {
"read_only": True,
"commands_enabled": False,
"navigation_or_safety_accepted": False,
},
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"l3-pointpillars-visual-audit-{identity_sha256}"
result_root = output_root.expanduser().absolute() / result_id
if result_root.exists():
raise RuntimeError("visual-audit derivative already exists")
frames_root = result_root / "frames"
frames_root.mkdir(mode=0o700, parents=True)
descriptors: list[dict[str, object]] = []
points_path = archive_root / KITTI_VELODYNE_ARCHIVE
try:
with zipfile.ZipFile(points_path.resolve(strict=True)) as points_zip:
for frame_id in selected_ids:
raw = points_zip.read(f"training/velodyne/{frame_id}.bin")
source_frame = predictions[frame_id]["payload"]
if hashlib.sha256(raw).hexdigest() != source_frame["point_sha256"]:
raise RuntimeError(
f"KITTI points changed for selected frame {frame_id}"
)
detail = _frame_detail(
frame_id=frame_id,
point_bytes=raw,
prediction=predictions[frame_id],
truths=truths[frame_id],
matched_predictions=matched_predictions,
matched_truth=matched_truth,
summary=summaries[frame_id],
)
path = frames_root / f"{frame_id}.json"
_write_once(path, detail)
descriptors.append(
{
**summaries[frame_id],
"detail_path": f"frames/{frame_id}.json",
"detail_sha256": _sha256(path),
"detail_byte_length": path.stat().st_size,
}
)
except (OSError, KeyError, zipfile.BadZipFile) as exc:
raise RuntimeError("selected KITTI points could not be read") from exc
catalog = {
"schema_version": VISUAL_CATALOG_SCHEMA,
"result_id": result_id,
"source_run_id": source_manifest["run_id"],
"frame_count": len(descriptors),
"frames": descriptors,
}
catalog_path = result_root / "catalog.json"
_write_once(catalog_path, catalog)
manifest = {
"schema_version": VISUAL_AUDIT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
"status": "operator-visual-review-required",
"source_metrics": source_report["metrics"],
"catalog": _artifact(catalog_path, "visual-frame-catalog"),
"authority": identity["authority"],
}
_write_once(result_root / "manifest.json", manifest)
return {
"result_id": result_id,
"selected_frame_count": len(descriptors),
"status": manifest["status"],
}
def _validate_source_manifest(
manifest: dict[str, Any],
report: dict[str, Any],
*,
source_manifest_path: Path,
source_report_path: Path,
run_root: Path,
) -> None:
artifacts = manifest.get("artifacts")
if (
manifest.get("schema_version") != SOURCE_MANIFEST_SCHEMA
or manifest.get("run_id") != EXPECTED_SOURCE_RUN_ID
or run_root.name != EXPECTED_SOURCE_RUN_ID
or manifest.get("frame_result_count") != 3769
or manifest.get("frame_results_identity_sha256")
!= EXPECTED_FRAME_RESULTS_IDENTITY
or manifest.get("status")
!= "public-cross-domain-transfer-probe-measured"
or report.get("run_id") != manifest.get("run_id")
or report.get("status") != manifest.get("status")
or not isinstance(artifacts, list)
):
raise RuntimeError("sealed L3 source manifest is invalid")
observed = {
descriptor.get("role"): descriptor
for descriptor in artifacts
if isinstance(descriptor, dict)
}
for role, path in (
("run-identity", run_root / "identity.json"),
("benchmark-report", source_report_path),
):
descriptor = observed.get(role)
if (
not isinstance(descriptor, dict)
or descriptor.get("sha256") != _sha256(path)
or descriptor.get("byte_length") != path.stat().st_size
):
raise RuntimeError(f"sealed L3 {role} changed")
if _frame_results_identity(run_root / "frames") != EXPECTED_FRAME_RESULTS_IDENTITY:
raise RuntimeError("sealed L3 frame set changed")
if _sha256(source_manifest_path) != _sha256(run_root / "manifest.json"):
raise RuntimeError("sealed L3 manifest path changed")
def _read_predictions(
frames_root: Path,
validation_ids: tuple[str, ...],
) -> dict[str, dict[str, Any]]:
expected = {f"{frame_id}.json" for frame_id in validation_ids}
actual = {path.name for path in frames_root.glob("*.json")}
if actual != expected:
raise RuntimeError("sealed L3 frame set does not match KITTI validation")
predictions: dict[str, dict[str, Any]] = {}
for frame_id in validation_ids:
payload = _read_json(frames_root / f"{frame_id}.json")
raw_boxes = payload.get("boxes")
if (
payload.get("schema_version") != SOURCE_FRAME_SCHEMA
or payload.get("frame_id") != frame_id
or not isinstance(payload.get("point_sha256"), str)
or not isinstance(raw_boxes, list)
):
raise RuntimeError(f"sealed L3 frame {frame_id} is invalid")
try:
boxes = tuple(PointPillarsBox(**box) for box in raw_boxes)
inference_ms = float(payload["inference_ms"])
except (KeyError, TypeError, ValueError) as exc:
raise RuntimeError(
f"sealed L3 frame {frame_id} is invalid"
) from exc
if (
not math.isfinite(inference_ms)
or inference_ms <= 0
or any(not _valid_box(box) for box in boxes)
):
raise RuntimeError(f"sealed L3 frame {frame_id} is invalid")
predictions[frame_id] = {
"payload": payload,
"boxes": boxes,
"inference_ms": inference_ms,
}
return predictions
def _global_matches(
predictions: dict[str, dict[str, Any]],
truths: dict[str, tuple[KittiLidarTruth, ...]],
) -> tuple[dict[tuple[str, int], tuple[int, float]], set[tuple[str, int]]]:
matched_predictions: dict[tuple[str, int], tuple[int, float]] = {}
matched_truth: set[tuple[str, int]] = set()
for class_name in KITTI_BENCHMARK_CLASSES:
ordered = sorted(
(
(box.score, frame_id, index, box)
for frame_id, frame in predictions.items()
for index, box in enumerate(frame["boxes"])
if _inside_shared_range(box)
and MODEL_TO_KITTI_CLASS[box.model_class] == class_name
),
key=lambda item: (-item[0], item[1], item[2]),
)
for _, frame_id, index, box in ordered:
best_index = -1
best_iou = -1.0
for truth_index, truth in enumerate(truths[frame_id]):
if (
truth.benchmark_class != class_name
or (frame_id, truth_index) in matched_truth
):
continue
overlap = oriented_3d_iou(box, _truth_box(truth))
if overlap > best_iou:
best_index = truth_index
best_iou = overlap
if (
best_index >= 0
and best_iou >= KITTI_IOU_THRESHOLDS[class_name]
):
matched_truth.add((frame_id, best_index))
matched_predictions[(frame_id, index)] = (
best_index,
best_iou,
)
return matched_predictions, matched_truth
def _frame_summaries(
predictions: dict[str, dict[str, Any]],
truths: dict[str, tuple[KittiLidarTruth, ...]],
matched_predictions: dict[tuple[str, int], tuple[int, float]],
matched_truth: set[tuple[str, int]],
) -> dict[str, dict[str, object]]:
result: dict[str, dict[str, object]] = {}
for frame_id, frame in predictions.items():
evaluated = [
(index, box)
for index, box in enumerate(frame["boxes"])
if _inside_shared_range(box)
]
true_positive_count = sum(
(frame_id, index) in matched_predictions
for index, _ in evaluated
)
false_negative_count = sum(
(frame_id, index) not in matched_truth
for index in range(len(truths[frame_id]))
)
result[frame_id] = {
"frame_id": frame_id,
"inference_ms": frame["inference_ms"],
"prediction_count": len(frame["boxes"]),
"evaluated_prediction_count": len(evaluated),
"outside_shared_range_count": len(frame["boxes"]) - len(evaluated),
"truth_count": len(truths[frame_id]),
"true_positive_count": true_positive_count,
"false_positive_count": len(evaluated) - true_positive_count,
"false_negative_count": false_negative_count,
"truth_classes": sorted(
{truth.benchmark_class for truth in truths[frame_id]}
),
}
return result
def _select_frames(
summaries: dict[str, dict[str, object]],
) -> tuple[str, ...]:
selected: list[str] = []
def add(frame_id: str) -> None:
if frame_id not in selected and len(selected) < MAX_SELECTED_FRAMES:
selected.append(frame_id)
for frame_id in sorted(
summaries,
key=lambda item: (
-int(summaries[item]["true_positive_count"]),
item,
),
):
if int(summaries[frame_id]["true_positive_count"]) > 0:
add(frame_id)
for metric in ("false_positive_count", "false_negative_count"):
for frame_id in sorted(
summaries,
key=lambda item: (-int(summaries[item][metric]), item),
)[:6]:
add(frame_id)
for class_name in KITTI_BENCHMARK_CLASSES:
candidates = [
frame_id
for frame_id, summary in summaries.items()
if class_name in summary["truth_classes"]
]
if candidates:
add(
max(
candidates,
key=lambda item: (
int(summaries[item]["false_negative_count"]),
int(summaries[item]["false_positive_count"]),
item,
),
)
)
for frame_id in sorted(
summaries,
key=lambda item: (
-int(summaries[item]["false_positive_count"])
- int(summaries[item]["false_negative_count"]),
item,
),
):
add(frame_id)
if not selected:
raise RuntimeError("visual-audit selection is empty")
return tuple(selected)
def _frame_detail(
*,
frame_id: str,
point_bytes: bytes,
prediction: dict[str, Any],
truths: tuple[KittiLidarTruth, ...],
matched_predictions: dict[tuple[str, int], tuple[int, float]],
matched_truth: set[tuple[str, int]],
summary: dict[str, object],
) -> dict[str, object]:
points = np.frombuffer(point_bytes, dtype="<f4")
if points.size % 4:
raise RuntimeError(f"KITTI point frame {frame_id} is malformed")
points = points.reshape(-1, 4)
bounds = CROSS_DOMAIN_EVALUATION_RANGE
mask = (
(points[:, 0] >= bounds[0])
& (points[:, 0] <= bounds[3])
& (points[:, 1] >= bounds[1])
& (points[:, 1] <= bounds[4])
& (points[:, 2] >= bounds[2])
& (points[:, 2] <= bounds[5])
)
bounded = points[mask]
if len(bounded) > MAX_SAMPLED_POINTS:
indices = np.linspace(
0,
len(bounded) - 1,
MAX_SAMPLED_POINTS,
dtype=np.int64,
)
sampled = bounded[indices]
else:
sampled = bounded
flat_points = np.round(sampled, decimals=4).reshape(-1).tolist()
prediction_boxes: list[dict[str, object]] = []
for index, box in enumerate(prediction["boxes"]):
if not _inside_shared_range(box):
continue
match = matched_predictions.get((frame_id, index))
prediction_boxes.append(
{
**_box_payload(box, MODEL_TO_KITTI_CLASS[box.model_class]),
"score": box.score,
"status": "true-positive" if match else "false-positive",
"matched_truth_index": match[0] if match else None,
"matched_iou_3d": match[1] if match else None,
}
)
truth_boxes = [
{
**_truth_payload(truth),
"truth_index": index,
"status": (
"matched" if (frame_id, index) in matched_truth
else "false-negative"
),
}
for index, truth in enumerate(truths)
]
return {
"schema_version": VISUAL_FRAME_SCHEMA,
"frame_id": frame_id,
"summary": summary,
"points": {
"layout": "flat-xyzi",
"source_point_count": len(points),
"shared_range_point_count": len(bounded),
"sampled_point_count": len(sampled),
"values": flat_points,
},
"truth_boxes": truth_boxes,
"prediction_boxes": prediction_boxes,
}
def _valid_box(box: PointPillarsBox) -> bool:
values = (
box.x_m,
box.y_m,
box.z_m,
box.length_m,
box.width_m,
box.height_m,
box.yaw_rad,
box.score,
)
return (
box.model_class in MODEL_TO_KITTI_CLASS
and all(math.isfinite(value) for value in values)
and min(box.length_m, box.width_m, box.height_m) > 0
and 0 <= box.score <= 1
)
def _inside_shared_range(box: PointPillarsBox) -> bool:
bounds = CROSS_DOMAIN_EVALUATION_RANGE
return (
bounds[0] <= box.x_m <= bounds[3]
and bounds[1] <= box.y_m <= bounds[4]
and bounds[2] <= box.z_m <= bounds[5]
)
def _truth_box(truth: KittiLidarTruth) -> PointPillarsBox:
return PointPillarsBox(
x_m=truth.x_m,
y_m=truth.y_m,
z_m=truth.z_m,
length_m=truth.length_m,
width_m=truth.width_m,
height_m=truth.height_m,
yaw_rad=truth.yaw_rad,
class_id=-1,
model_class=truth.benchmark_class,
score=1.0,
)
def _box_payload(
box: PointPillarsBox,
benchmark_class: str,
) -> dict[str, object]:
return {
"benchmark_class": benchmark_class,
"center_xyz_m": [box.x_m, box.y_m, box.z_m],
"size_lwh_m": [box.length_m, box.width_m, box.height_m],
"yaw_rad": box.yaw_rad,
}
def _truth_payload(truth: KittiLidarTruth) -> dict[str, object]:
return {
"benchmark_class": truth.benchmark_class,
"center_xyz_m": [truth.x_m, truth.y_m, truth.z_m],
"size_lwh_m": [truth.length_m, truth.width_m, truth.height_m],
"yaw_rad": truth.yaw_rad,
}
def _frame_results_identity(frames_root: Path) -> str:
descriptors = [
{
"name": path.name,
"sha256": _sha256(path),
"byte_length": path.stat().st_size,
}
for path in sorted(frames_root.glob("*.json"))
]
return hashlib.sha256(_canonical_json(descriptors)).hexdigest()
def _artifact(path: Path, role: str) -> dict[str, object]:
return {
"path": path.name,
"role": role,
"media_type": "application/json",
"sha256": _sha256(path),
"byte_length": path.stat().st_size,
}
def _read_json(path: Path) -> dict[str, Any]:
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise RuntimeError(f"{path.name} is invalid") from exc
if not isinstance(payload, dict):
raise RuntimeError(f"{path.name} is not an object")
return payload
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _canonical_json(payload: Any) -> bytes:
return json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")
def _write_once(path: Path, payload: dict[str, Any]) -> None:
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
if path.exists():
raise RuntimeError(f"{path.name} already exists")
descriptor, temporary = tempfile.mkstemp(
prefix=f".{path.name}.",
suffix=".tmp",
dir=path.parent,
)
try:
with os.fdopen(descriptor, "wb") as target:
target.write(_canonical_json(payload))
target.flush()
os.fsync(target.fileno())
os.chmod(temporary, 0o600)
os.replace(temporary, path)
finally:
with suppress(FileNotFoundError):
os.unlink(temporary)
if __name__ == "__main__":
raise SystemExit(main())
+14 -1
View File
@@ -10,6 +10,8 @@ from typing import Any, Final
from fastapi import APIRouter, Query
from k1link.web.l3_pointpillars_visual_api import latest_l3_visual_identity
from k1link.compute.e31_source_qualification import (
E31SourceQualification,
E31SourceQualificationError,
@@ -825,12 +827,13 @@ def build_advanced_laboratory_router(
e38_root_provider: RootProvider = lambda: None,
e39_root_provider: RootProvider = lambda: None,
e40_root_provider: RootProvider = lambda: None,
l3_visual_root_provider: RootProvider = lambda: None,
) -> APIRouter:
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
@router.get("/advanced-index")
def list_advanced_results() -> dict[str, object]:
return _advanced_index(
result = _advanced_index(
(
(
"e31-source-binding",
@@ -897,6 +900,16 @@ def build_advanced_laboratory_router(
),
)
)
l3_identity = latest_l3_visual_identity(l3_visual_root_provider)
if l3_identity is not None:
result["items"].append(
{
"work_id": "l3-pointpillars-visual-audit",
**l3_identity,
"access": "read-only",
}
)
return result
@router.get("/e31/results")
def list_e31_results(
+21
View File
@@ -34,6 +34,9 @@ from k1link.sessions import (
SessionStore,
)
from k1link.web.advanced_laboratory_api import build_advanced_laboratory_router
from k1link.web.l3_pointpillars_visual_api import (
build_l3_pointpillars_visual_router,
)
from k1link.web.artifact_health_api import build_artifact_health_router
from k1link.web.compute_contour_api import build_compute_contour_router
from k1link.web.device_plugin_composition import load_installed_device_plugins
@@ -599,6 +602,24 @@ app.include_router(
/ "e40"
/ "results"
),
l3_visual_root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "l3"
/ "visual-audits"
),
)
)
app.include_router(
build_l3_pointpillars_visual_router(
root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "l3"
/ "visual-audits"
)
)
)
app.include_router(
@@ -0,0 +1,329 @@
"""Read-only projection of sealed L3 PointPillars visual-audit evidence."""
from __future__ import annotations
import copy
import hashlib
import json
import re
from collections.abc import Callable
from pathlib import Path
from typing import Any, Final
from fastapi import APIRouter, HTTPException, Query
RootProvider = Callable[[], Path | None]
VISUAL_AUDIT_SCHEMA: Final = "missioncore.l3-pointpillars-visual-audit/v1"
VISUAL_CATALOG_SCHEMA: Final = (
"missioncore.l3-pointpillars-visual-audit-catalog/v1"
)
VISUAL_FRAME_SCHEMA: Final = "missioncore.l3-pointpillars-visual-frame/v1"
VISUAL_RESULT_SCHEMA: Final = (
"missioncore.l3-pointpillars-visual-audit-result/v1"
)
VISUAL_RESULT_ID: Final = re.compile(
r"^l3-pointpillars-visual-audit-[a-f0-9]{64}$"
)
FRAME_ID: Final = re.compile(r"^[0-9]{6}$")
MAX_JSON_BYTES: Final = 16 * 1024 * 1024
MAX_CANDIDATES: Final = 64
MAX_FRAMES: Final = 24
def build_l3_pointpillars_visual_router(
*,
root_provider: RootProvider = lambda: None,
) -> APIRouter:
router = APIRouter(
prefix="/api/v1/laboratory/l3/pointpillars-visual-audits",
tags=["laboratory"],
)
@router.get("/results")
def list_results(
limit: int = Query(default=1, ge=1, le=10),
) -> dict[str, object]:
root = _configured_root(root_provider)
if root is None:
return _empty_catalog(False)
candidates = _candidates(root)
items: list[dict[str, object]] = []
invalid_total = 0
for candidate in candidates:
try:
items.append(_project_result(candidate))
except RuntimeError:
invalid_total += 1
items.sort(
key=lambda item: (
str(item["created_at_utc"]),
str(item["result_id"]),
),
reverse=True,
)
return {
"schema_version": (
"missioncore.l3-pointpillars-visual-audit-catalog-results/v1"
),
"configured": True,
"items": items[:limit],
"candidate_total": len(candidates),
"invalid_total": invalid_total,
"access": "read-only",
}
@router.get("/{result_id}/frames/{frame_id}")
def get_frame(result_id: str, frame_id: str) -> dict[str, object]:
if not VISUAL_RESULT_ID.fullmatch(result_id):
raise HTTPException(status_code=404, detail="visual audit not found")
if not FRAME_ID.fullmatch(frame_id):
raise HTTPException(status_code=404, detail="visual frame not found")
root = _configured_root(root_provider)
if root is None:
raise HTTPException(status_code=404, detail="visual audit not found")
candidate = root / result_id
try:
result = _load_result(candidate)
descriptor = next(
item
for item in result["catalog"]["frames"]
if item["frame_id"] == frame_id
)
relative = descriptor["detail_path"]
if relative != f"frames/{frame_id}.json":
raise RuntimeError("visual frame path changed")
path = candidate / relative
payload = _read_json(path)
if (
payload.get("schema_version") != VISUAL_FRAME_SCHEMA
or payload.get("frame_id") != frame_id
or descriptor["detail_sha256"] != _sha256(path)
or descriptor["detail_byte_length"] != path.stat().st_size
):
raise RuntimeError("visual frame identity changed")
except (RuntimeError, StopIteration):
raise HTTPException(
status_code=404,
detail="visual frame not found",
) from None
return {**copy.deepcopy(payload), "access": "read-only"}
return router
def latest_l3_visual_identity(
root_provider: RootProvider,
) -> dict[str, str] | None:
"""Return the newest verified identity for the shared LAB index."""
root = _configured_root(root_provider)
if root is None:
return None
valid: list[dict[str, object]] = []
for candidate in _candidates(root):
try:
valid.append(_project_result(candidate))
except RuntimeError:
continue
if not valid:
return None
latest = max(
valid,
key=lambda item: (
str(item["created_at_utc"]),
str(item["result_id"]),
),
)
return {
"result_id": str(latest["result_id"]),
"created_at_utc": str(latest["created_at_utc"]),
}
def _project_result(candidate: Path) -> dict[str, object]:
result = _load_result(candidate)
manifest = result["manifest"]
catalog = result["catalog"]
metrics = manifest["source_metrics"]
return {
"schema_version": VISUAL_RESULT_SCHEMA,
"result_id": manifest["result_id"],
"created_at_utc": manifest["created_at_utc"],
"status": manifest["status"],
"source_run_id": manifest["identity"]["source_run_id"],
"source_frame_results_identity_sha256": manifest["identity"][
"source_frame_results_identity_sha256"
],
"dataset_source_id": manifest["identity"]["dataset_source_id"],
"dataset_release_identity_sha256": manifest["identity"][
"dataset_release_identity_sha256"
],
"metrics": copy.deepcopy(metrics),
"frames": copy.deepcopy(catalog["frames"]),
"matching": copy.deepcopy(manifest["identity"]["matching"]),
"point_sampling": copy.deepcopy(
manifest["identity"]["point_sampling"]
),
"authority": copy.deepcopy(manifest["authority"]),
"access": "read-only",
}
def _load_result(candidate: Path) -> dict[str, Any]:
if (
not candidate.is_dir()
or candidate.is_symlink()
or not VISUAL_RESULT_ID.fullmatch(candidate.name)
):
raise RuntimeError("visual audit candidate is invalid")
manifest_path = candidate / "manifest.json"
manifest = _read_json(manifest_path)
identity = manifest.get("identity")
authority = manifest.get("authority")
catalog_descriptor = manifest.get("catalog")
if (
manifest.get("schema_version") != VISUAL_AUDIT_SCHEMA
or manifest.get("result_id") != candidate.name
or manifest.get("status") != "operator-visual-review-required"
or not isinstance(manifest.get("created_at_utc"), str)
or not isinstance(identity, dict)
or not isinstance(authority, dict)
or authority.get("read_only") is not True
or authority.get("commands_enabled") is not False
or authority.get("navigation_or_safety_accepted") is not False
or manifest.get("identity_sha256")
!= hashlib.sha256(_canonical_json(identity)).hexdigest()
or candidate.name
!= f"l3-pointpillars-visual-audit-{manifest.get('identity_sha256')}"
or not isinstance(catalog_descriptor, dict)
or catalog_descriptor.get("path") != "catalog.json"
or catalog_descriptor.get("role") != "visual-frame-catalog"
):
raise RuntimeError("visual audit manifest is invalid")
catalog_path = candidate / "catalog.json"
if (
catalog_descriptor.get("sha256") != _sha256(catalog_path)
or catalog_descriptor.get("byte_length") != catalog_path.stat().st_size
):
raise RuntimeError("visual audit catalog changed")
catalog = _read_json(catalog_path)
frames = catalog.get("frames")
if (
catalog.get("schema_version") != VISUAL_CATALOG_SCHEMA
or catalog.get("result_id") != candidate.name
or catalog.get("source_run_id") != identity.get("source_run_id")
or not isinstance(frames, list)
or not 1 <= len(frames) <= MAX_FRAMES
or catalog.get("frame_count") != len(frames)
or len({item.get("frame_id") for item in frames if isinstance(item, dict)})
!= len(frames)
or any(not _valid_frame_descriptor(item) for item in frames)
):
raise RuntimeError("visual audit catalog is invalid")
if not isinstance(manifest.get("source_metrics"), dict):
raise RuntimeError("visual audit metrics are unavailable")
return {"manifest": manifest, "catalog": catalog}
def _valid_frame_descriptor(value: object) -> bool:
if not isinstance(value, dict):
return False
frame_id = value.get("frame_id")
counts = (
"prediction_count",
"evaluated_prediction_count",
"outside_shared_range_count",
"truth_count",
"true_positive_count",
"false_positive_count",
"false_negative_count",
"detail_byte_length",
)
return (
isinstance(frame_id, str)
and FRAME_ID.fullmatch(frame_id) is not None
and value.get("detail_path") == f"frames/{frame_id}.json"
and isinstance(value.get("detail_sha256"), str)
and re.fullmatch(r"[a-f0-9]{64}", value["detail_sha256"]) is not None
and all(
isinstance(value.get(key), int)
and not isinstance(value.get(key), bool)
and value[key] >= 0
for key in counts
)
and 0 < value["detail_byte_length"] <= MAX_JSON_BYTES
and isinstance(value.get("inference_ms"), (int, float))
and not isinstance(value.get("inference_ms"), bool)
and 0 < value["inference_ms"] < 60_000
and isinstance(value.get("truth_classes"), list)
)
def _configured_root(provider: RootProvider) -> Path | None:
value = provider()
if value is None:
return None
root = value.expanduser().absolute()
if not root.is_dir() or root.is_symlink():
return None
return root
def _candidates(root: Path) -> list[Path]:
candidates = [
path
for path in root.iterdir()
if path.is_dir()
and not path.is_symlink()
and VISUAL_RESULT_ID.fullmatch(path.name)
]
if len(candidates) > MAX_CANDIDATES:
raise RuntimeError("visual audit candidate bound exceeded")
return candidates
def _empty_catalog(configured: bool) -> dict[str, object]:
return {
"schema_version": (
"missioncore.l3-pointpillars-visual-audit-catalog-results/v1"
),
"configured": configured,
"items": [],
"candidate_total": 0,
"invalid_total": 0,
"access": "read-only",
}
def _read_json(path: Path) -> dict[str, Any]:
try:
if (
not path.is_file()
or path.is_symlink()
or not 0 < path.stat().st_size <= MAX_JSON_BYTES
):
raise RuntimeError(f"{path.name} is unavailable")
payload = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise RuntimeError(f"{path.name} is invalid") from exc
if not isinstance(payload, dict):
raise RuntimeError(f"{path.name} is not an object")
return payload
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _canonical_json(payload: object) -> bytes:
return json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")
+170
View File
@@ -0,0 +1,170 @@
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from fastapi import APIRouter
from fastapi.routing import APIRoute
from pytest import raises
from k1link.web.l3_pointpillars_visual_api import (
build_l3_pointpillars_visual_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 _canonical(payload: object) -> bytes:
return json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")
def _write(path: Path, payload: object) -> dict[str, object]:
content = _canonical(payload)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(content)
return {
"sha256": hashlib.sha256(content).hexdigest(),
"byte_length": len(content),
}
def _result(root: Path) -> tuple[str, str]:
frame_id = "000001"
identity = {
"source_run_id": f"l3-pointpillars-kitti-{'1' * 64}",
"source_frame_results_identity_sha256": "2" * 64,
"dataset_source_id": "kitti-3d-object/v2017",
"dataset_release_identity_sha256": "3" * 64,
"matching": {"metric": "oriented-3d-iou"},
"point_sampling": {"maximum_points_per_frame": 12000},
"authority": {
"read_only": True,
"commands_enabled": False,
"navigation_or_safety_accepted": False,
},
}
identity_sha = hashlib.sha256(_canonical(identity)).hexdigest()
result_id = f"l3-pointpillars-visual-audit-{identity_sha}"
candidate = root / result_id
summary = {
"frame_id": frame_id,
"inference_ms": 12.5,
"prediction_count": 2,
"evaluated_prediction_count": 1,
"outside_shared_range_count": 1,
"truth_count": 1,
"true_positive_count": 0,
"false_positive_count": 1,
"false_negative_count": 1,
"truth_classes": ["Car"],
}
detail = {
"schema_version": "missioncore.l3-pointpillars-visual-frame/v1",
"frame_id": frame_id,
"summary": summary,
"points": {
"layout": "flat-xyzi",
"source_point_count": 1,
"shared_range_point_count": 1,
"sampled_point_count": 1,
"values": [1, 2, 3, 0.5],
},
"truth_boxes": [],
"prediction_boxes": [],
}
detail_descriptor = _write(candidate / "frames" / f"{frame_id}.json", detail)
descriptor = {
**summary,
"detail_path": f"frames/{frame_id}.json",
"detail_sha256": detail_descriptor["sha256"],
"detail_byte_length": detail_descriptor["byte_length"],
}
catalog = {
"schema_version": (
"missioncore.l3-pointpillars-visual-audit-catalog/v1"
),
"result_id": result_id,
"source_run_id": identity["source_run_id"],
"frame_count": 1,
"frames": [descriptor],
}
catalog_descriptor = _write(candidate / "catalog.json", catalog)
manifest = {
"schema_version": "missioncore.l3-pointpillars-visual-audit/v1",
"result_id": result_id,
"identity_sha256": identity_sha,
"identity": identity,
"created_at_utc": "2026-07-31T10:00:00Z",
"status": "operator-visual-review-required",
"source_metrics": {"frame_count": 3769, "aggregates": {}},
"catalog": {
"path": "catalog.json",
"role": "visual-frame-catalog",
**catalog_descriptor,
},
"authority": identity["authority"],
}
_write(candidate / "manifest.json", manifest)
return result_id, frame_id
def test_l3_visual_catalog_and_frame_are_read_only(tmp_path: Path) -> None:
result_id, frame_id = _result(tmp_path)
router = build_l3_pointpillars_visual_router(root_provider=lambda: tmp_path)
catalog_route = _endpoint(
router,
"/api/v1/laboratory/l3/pointpillars-visual-audits/results",
)
frame_route = _endpoint(
router,
(
"/api/v1/laboratory/l3/pointpillars-visual-audits/"
"{result_id}/frames/{frame_id}"
),
)
catalog = catalog_route(limit=1) # type: ignore[operator]
assert catalog["configured"] is True
assert catalog["invalid_total"] == 0
assert catalog["items"][0]["result_id"] == result_id
assert catalog["items"][0]["frames"][0]["frame_id"] == frame_id
frame = frame_route(result_id=result_id, frame_id=frame_id) # type: ignore[operator]
assert frame["schema_version"] == "missioncore.l3-pointpillars-visual-frame/v1"
assert frame["access"] == "read-only"
def test_l3_visual_frame_fails_closed_after_mutation(tmp_path: Path) -> None:
result_id, frame_id = _result(tmp_path)
router = build_l3_pointpillars_visual_router(root_provider=lambda: tmp_path)
frame_route = _endpoint(
router,
(
"/api/v1/laboratory/l3/pointpillars-visual-audits/"
"{result_id}/frames/{frame_id}"
),
)
(tmp_path / result_id / "frames" / f"{frame_id}.json").write_text(
"{}",
encoding="utf-8",
)
with raises(Exception) as caught:
frame_route(result_id=result_id, frame_id=frame_id) # type: ignore[operator]
assert getattr(caught.value, "status_code", None) == 404