feat(lab): visualize M4.8R3 system occupancy
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user