feat(lab): visualize M4.8R3 system occupancy

This commit is contained in:
DCCONSTRUCTIONS
2026-08-26 13:40:24 +03:00
parent b82a97fe8b
commit 936ee479ed
13 changed files with 641 additions and 7 deletions
@@ -45,6 +45,7 @@ import { M47ReferenceGraphResultView } from "./M47ReferenceGraphResult";
import { M48ObjectCentricQualityResultView } from "./M48ObjectCentricQualityResult";
import { M48SmallStaticPassageRegressionResultView } from "./M48SmallStaticPassageRegressionResult";
import { M48StaticOccupancyQualificationResultView } from "./M48StaticOccupancyQualificationResult";
import { M48R3StaticOccupancyShadowResultView } from "./M48R3StaticOccupancyShadowResult";
import { M48SFixedClassDetectorResultView } from "./M48SFixedClassDetectorResult";
import { M48TRiskQualityResultView } from "./M48TRiskQualityResult";
@@ -98,6 +99,9 @@ export function AdvancedLaboratoryResult({
if (workId === "m48-static-occupancy-qualification" && results.m48StaticOccupancy) {
return <M48StaticOccupancyQualificationResultView rigLabel={rigLabel} result={results.m48StaticOccupancy} />;
}
if (workId === "m48r3-static-occupancy-shadow" && results.m48r3StaticOccupancy) {
return <M48R3StaticOccupancyShadowResultView rigLabel={rigLabel} result={results.m48r3StaticOccupancy} />;
}
if (workId === "m48s-fixed-class-detector" && results.m48s) {
return <M48SFixedClassDetectorResultView rigLabel={rigLabel} result={results.m48s} />;
}
@@ -0,0 +1,81 @@
import { useEffect, useMemo, useState } from "react";
import { Icon } from "@nodedc/ui-react";
import {
fetchM48R3StaticOccupancyCases,
type M48R3StaticOccupancyCase,
type M48R3StaticOccupancyShadowResult,
} from "../../core/laboratory/m48r3StaticOccupancyShadow";
import {
M4ReplayThreatVisual,
type M4ReplayThreatReviewAnchor,
} from "./M4ReplayThreatVisual";
function message(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
: "M4.8R3 timeline недоступен.";
}
export function M48R3StaticOccupancyShadowEvidence({
result,
}: {
result: M48R3StaticOccupancyShadowResult;
}) {
const [cases, setCases] = useState<readonly M48R3StaticOccupancyCase[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
setError(null);
void fetchM48R3StaticOccupancyCases(result.resultId, { signal: controller.signal })
.then((nextCases) => {
if (!controller.signal.aborted) setCases(nextCases);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted) setError(message(caught));
})
.finally(() => {
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort();
}, [result.resultId]);
const reviewAnchors = useMemo<readonly M4ReplayThreatReviewAnchor[]>(() => (
cases.map((item) => ({
id: item.anchorId,
sourceSequence: item.sourceSequence,
extentXyxyNormalized: item.extentXyxy,
matchedAtThreshold: item.matched,
}))
), [cases]);
if (loading) {
return (
<div className="l3-visual-audit__state" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Открываем полный M4.8R3 Worker timeline</span>
</div>
);
}
if (error) {
return (
<div className="l3-visual-audit__state" role="alert">
<Icon name="alert" size={18} />
<span>{error}</span>
</div>
);
}
return (
<M4ReplayThreatVisual
resultId={result.resultId}
reviewAnchors={reviewAnchors}
showReviewAnchorBoxes={false}
reviewLabel="Контрольные кадры M4.8R3 · без ручных рамок"
timelineEndpointRoot="/api/v1/laboratory/m48r3/static-occupancy"
evidenceLabel="M4.8R3"
/>
);
}
@@ -0,0 +1,101 @@
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type { M48R3StaticOccupancyShadowResult } from "../../core/laboratory/m48r3StaticOccupancyShadow";
import { M48R3StaticOccupancyShadowEvidence } from "./M48R3StaticOccupancyShadowEvidence";
function number(value: number, digits = 2): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
}
function percent(value: number): string {
return `${number(value * 100, 1)}%`;
}
export function M48R3StaticOccupancyShadowResultView({
rigLabel,
result,
}: {
rigLabel: string;
result: M48R3StaticOccupancyShadowResult;
}) {
const candidate = result.metrics.performance.candidate;
const occupancy = result.metrics.occupancy;
const separation = result.metrics.anchors.separation;
const separated = separation.every((item) => item.passed);
const status = result.accepted
? "Полный Worker shadow принят: realtime и раздельные препятствия сохранены"
: "Worker shadow не прошёл один или несколько предобъявленных gate";
const separatedLabel = separation.length
? separation.map((item) => `${item.observedComponents}/${item.expectedMinimumComponents}`).join(" · ")
: "—";
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="M4.8R3 · full Worker static occupancy shadow"
description="Полный 4 489-кадровый прогон проверяет additive low-step occupied-only слой внутри штатного graph pipeline. Камера и RF-DETR не получают дополнительного inference; ручные прямоугольники используются только для перехода к контрольным кадрам и не рисуются как системный результат."
status={status}
statusTone={result.accepted ? "success" : "warning"}
facts={[
{ label: "Конфигурация", value: `${rigLabel} RIGHT · VIDEO/CAMERA/3D/PLAN · LOW-STEP provenance` },
{ label: "Профиль", value: `${result.profile.id} · minimum points ${result.profile.minimumPoints}` },
{ label: "Прогон", value: `${result.metrics.frames.candidateDelivered}/${result.metrics.frames.expected} · immutable ${result.resultId}` },
{ label: "Нагрузка", value: `12 Hz source-paced · ${number(candidate.fps, 3)} effective FPS · +0 inference` },
{ label: "Authority", value: "REPLAY-SIMULATED · production/navigation/actuation OFF" },
]}
brief={{
question: "Можно ли добавить геометрическое обнаружение низких статических препятствий, не разрушив realtime и не склеив отдельные столбики/шары в один объект?",
approach: `Кандидат сравнен покадрово с native baseline на всех ${result.metrics.frames.expected} кадрах. Проверены latency, FPS, рост occupancy, capacity drops, отсутствие потерянных baseline-ячеек и отдельные компоненты на кадре 1856.`,
principalResult: `${number(candidate.fps, 3)} FPS; world-state p95 ${number(candidate.worldP95Ms, 2)} мс; geometry p95/p99 ${number(candidate.geometryP95Ms, 2)}/${number(candidate.geometryP99Ms, 2)} мс; разделение ${separatedLabel}.`,
limitation: "Это воспроизводимый Worker shadow, а не доказательство физической проходимости. Просвет между компонентами сохраняется как геометрия, но допустимость проезда зависит от будущего габарита шасси и отдельного free-space контракта.",
}}
method={{
completeness: "complete",
executionClass: "deterministic",
pipelineId: "m48r3-native-plus-low-step-reference-graph/v1",
components: [
{ kind: "source", name: "native reference graph baseline", version: "M4.7/M4.8R2", role: "immutable occupied/unknown baseline", identitySha256: null },
{ kind: "algorithm", name: "additive low-step occupied-only", version: "v1", role: "CPU geometry; never clearing; no semantic class", identitySha256: result.profile.sha256 },
{ kind: "runtime", name: "full source-paced Worker shadow", version: "4 489 frames", role: "predeclared realtime, growth, separation and safety gates", identitySha256: result.resultId.split("-").at(-1) ?? null },
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="M4.8R3 VISUAL EVIDENCE · SYSTEM COMPONENTS"
title="Полный timeline; LOW-STEP показывает добавленные системой компоненты, ручные рамки скрыты"
kind="recorded-replay"
resizable
>
<M48R3StaticOccupancyShadowEvidence result={result} />
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Что доказал прогон"
status={status}
statusTone={result.accepted ? "success" : "warning"}
metrics={[
{ label: "Realtime", value: `${number(candidate.fps, 3)} FPS`, hint: `p95 ${number(candidate.worldP95Ms, 2)} мс · gate ≥11,5 FPS / ≤60 мс` },
{ label: "Geometry", value: `${number(candidate.geometryP95Ms, 2)} / ${number(candidate.geometryP99Ms, 2)} мс`, hint: "p95 / p99 · gates 9 / 16 мс" },
{ label: "Occupancy delta", value: `+${occupancy.addedCellTotal.toLocaleString("ru-RU")}`, hint: `${percent(occupancy.meanCellGrowthFraction)} cells · ${percent(occupancy.meanComponentGrowthFraction)} components` },
{ label: "Кадр 1856", value: separated ? `раздельно · ${separatedLabel}` : `не принят · ${separatedLabel}`, hint: "два столбика и две полусферы проверяются отдельными component gates" },
{ label: "Потери / false free", value: `${occupancy.lostCellTotal} / ${result.metrics.falseFreeCount}`, hint: `capacity drops ${result.metrics.capacityDropCount}` },
]}
conclusion={{
proved: `Система сама добавила ${result.metrics.provider.additiveObservationCount.toLocaleString("ru-RU")} low-step observations на ${result.metrics.provider.framesWithAdditions.toLocaleString("ru-RU")} кадрах, сохранила baseline без потерь и выдержала полный realtime shadow.`,
notProved: "Не доказаны физический clearance, planner-authoritative free space, независимые precision/recall и безопасность движения на реальном шасси.",
decision: result.accepted
? "Worker-кандидат принят в ограниченном replay-shadow контуре. Следующий шаг — отдельное решение о cutover и регрессия на новых сценах; ручная разметка не становится runtime-зависимостью."
: "Cutover запрещён. Исправить провалившийся gate и повторить полный immutable shadow без ослабления порогов.",
}}
/>
)}
/>
);
}
@@ -110,12 +110,16 @@ export function M4ReplayThreatVisual({
resultId,
semantic,
reviewAnchors = EMPTY_REVIEW_ANCHORS,
showReviewAnchorBoxes = true,
reviewLabel = "Контрольные примеры M4.8R1",
timelineEndpointRoot,
evidenceLabel = "M4.6",
}: {
resultId: string;
semantic?: M4ReplayThreatSemanticLayer;
reviewAnchors?: readonly M4ReplayThreatReviewAnchor[];
showReviewAnchorBoxes?: boolean;
reviewLabel?: string;
timelineEndpointRoot?: string;
evidenceLabel?: string;
}) {
@@ -124,6 +128,7 @@ export function M4ReplayThreatVisual({
const [showCurrentIncrement, setShowCurrentIncrement] = useState(true);
const [showLocalSurface, setShowLocalSurface] = useState(true);
const [showRollingMap, setShowRollingMap] = useState(true);
const [showLowStep, setShowLowStep] = useState(true);
const [showMediaSemantic, setShowMediaSemantic] = useState(true);
const [showSpatialSemantic, setShowSpatialSemantic] = useState(true);
const [showMediaPoints, setShowMediaPoints] = useState(false);
@@ -262,7 +267,7 @@ export function M4ReplayThreatVisual({
}, [metadata.timeline, resultId, reviewAnchorIdentity, reviewAnchors, seekPlayback, setPlaybackPlaying]);
const reviewAnchorBoxes = useMemo<readonly RecordedEvidenceBox[]>(() => {
const timeline = metadata.timeline;
if (!frame || !timeline) return [];
if (!frame || !timeline || !showReviewAnchorBoxes) return [];
return reviewAnchors
.filter((anchor) => anchor.sourceSequence === frame.sequence)
.map((anchor) => {
@@ -281,7 +286,7 @@ export function M4ReplayThreatVisual({
dashed: true,
};
});
}, [frame, metadata.timeline, reviewAnchors]);
}, [frame, metadata.timeline, reviewAnchors, showReviewAnchorBoxes]);
const activeBoxes = useMemo(
() => [...boxes(frame?.cameraProposals ?? []), ...reviewAnchorBoxes],
[frame, reviewAnchorBoxes],
@@ -345,6 +350,7 @@ export function M4ReplayThreatVisual({
state: obstacle.state,
centroidBodyXyzM: obstacle.centroidBodyXyzM,
cellCentersBodyXyzM: obstacle.cellCentersBodyXyzM,
occupancySource: obstacle.occupancySource,
})) ?? [], [spatialFrame]);
const currentIncrementObstacles = spatialFrame?.metricObstacles.filter(
(item) => item.state === "current",
@@ -352,6 +358,9 @@ export function M4ReplayThreatVisual({
const rollingMapObstacles = spatialFrame?.metricObstacles.filter(
(item) => item.state === "retained",
) ?? [];
const lowStepObstacles = spatialFrame?.metricObstacles.filter(
(item) => item.occupancySource !== "baseline",
) ?? [];
const nearest = spatialFrame?.metricObstacles
.map((item) => item.assessment.closestApproachM)
.filter((value): value is number => value !== null)
@@ -513,6 +522,18 @@ export function M4ReplayThreatVisual({
>
ROLLING
</Button>
{metadata.timeline?.occupancyProvenanceDelivery ? (
<Button
size="compact"
shape="pill"
variant={showLowStep ? "primary" : "secondary"}
aria-pressed={showLowStep}
title="Добавочные occupied-only компоненты low-step; без ручных рамок"
onClick={() => setShowLowStep((visible) => !visible)}
>
LOW-STEP
</Button>
) : null}
{semantic ? (
<Button
size="compact"
@@ -567,7 +588,7 @@ export function M4ReplayThreatVisual({
<Icon name="chevron-right" size={16} />
</IconButton>
<Select
label="Контрольные примеры M4.8R1"
label={reviewLabel}
value={String(selectedReviewAnchorIndex)}
options={reviewAnchors.map((anchor, index) => ({
value: String(index),
@@ -618,6 +639,9 @@ export function M4ReplayThreatVisual({
<span>Spatial evidence</span>
<strong>
{currentIncrementObstacles.length} current · {rollingMapObstacles.length} rolling
{metadata.timeline.occupancyProvenanceDelivery
? ` · ${lowStepObstacles.length} low-step`
: ""}
</strong>
<small>
{spatialFrame
@@ -758,6 +782,7 @@ export function M4ReplayThreatVisual({
showCurrentIncrement={showCurrentIncrement}
showLocalSurface={showLocalSurface}
showRollingMap={showRollingMap}
showLowStep={showLowStep}
pointSemanticClassIds={alignedSemanticPointIds}
semanticClasses={semanticClasses}
semanticPalette={semanticPalette}
@@ -84,6 +84,13 @@ const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`
experimentName: "M4.8 · conservative static occupancy qualification",
variantName: "M4.8R2 · current/rolling + low-step occupied-only candidate",
},
"m48r3-static-occupancy-shadow": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Canonical Reference Graph`,
experimentId: "m48r3-static-occupancy-shadow",
experimentName: "M4.8R3 · full Worker static occupancy shadow",
variantName: "M4.8R3 · 4 489 frames · additive low-step occupied-only",
},
"m48s-fixed-class-detector": {
profileId: "rig-ravnoves-perception-gate-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · RAVNOVES00 perception gate`,
@@ -22,6 +22,7 @@ function mergeResults(
m48: next.m48 ?? current.m48,
m48SmallStatic: next.m48SmallStatic ?? current.m48SmallStatic,
m48StaticOccupancy: next.m48StaticOccupancy ?? current.m48StaticOccupancy,
m48r3StaticOccupancy: next.m48r3StaticOccupancy ?? current.m48r3StaticOccupancy,
m48s: next.m48s ?? current.m48s,
m48t: next.m48t ?? current.m48t,
m4Threat: next.m4Threat ?? current.m4Threat,
@@ -122,6 +123,7 @@ export function useAdvancedLaboratoryCatalog({
"m48-object-centric-quality",
"m48-small-static-passage-regression",
"m48-static-occupancy-qualification",
"m48r3-static-occupancy-shadow",
].includes(selectedWorkId)
&& !indexedResultId
) return;