feat(control-station): present static occupancy qualification
This commit is contained in:
@@ -44,6 +44,7 @@ import { M4ReplayThreatResultView } from "./M4ReplayThreatResult";
|
||||
import { M47ReferenceGraphResultView } from "./M47ReferenceGraphResult";
|
||||
import { M48ObjectCentricQualityResultView } from "./M48ObjectCentricQualityResult";
|
||||
import { M48SmallStaticPassageRegressionResultView } from "./M48SmallStaticPassageRegressionResult";
|
||||
import { M48StaticOccupancyQualificationResultView } from "./M48StaticOccupancyQualificationResult";
|
||||
import { M48SFixedClassDetectorResultView } from "./M48SFixedClassDetectorResult";
|
||||
import { M48TRiskQualityResultView } from "./M48TRiskQualityResult";
|
||||
|
||||
@@ -94,6 +95,9 @@ export function AdvancedLaboratoryResult({
|
||||
if (workId === "m48-small-static-passage-regression" && results.m48SmallStatic) {
|
||||
return <M48SmallStaticPassageRegressionResultView rigLabel={rigLabel} result={results.m48SmallStatic} />;
|
||||
}
|
||||
if (workId === "m48-static-occupancy-qualification" && results.m48StaticOccupancy) {
|
||||
return <M48StaticOccupancyQualificationResultView rigLabel={rigLabel} result={results.m48StaticOccupancy} />;
|
||||
}
|
||||
if (workId === "m48s-fixed-class-detector" && results.m48s) {
|
||||
return <M48SFixedClassDetectorResultView rigLabel={rigLabel} result={results.m48s} />;
|
||||
}
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Icon } from "@nodedc/ui-react";
|
||||
|
||||
import { fetchM47ReferenceGraphLab, type M47ReferenceGraphLabResult } from "../../core/laboratory/m47ReferenceGraph";
|
||||
import {
|
||||
fetchM48StaticOccupancyCases,
|
||||
type M48StaticOccupancyCase,
|
||||
type M48StaticOccupancyQualificationResult,
|
||||
} from "../../core/laboratory/m48StaticOccupancyQualification";
|
||||
import {
|
||||
M4ReplayThreatVisual,
|
||||
type M4ReplayThreatReviewAnchor,
|
||||
} from "./M4ReplayThreatVisual";
|
||||
|
||||
function message(error: unknown): string {
|
||||
return error instanceof Error && error.message.trim()
|
||||
? error.message
|
||||
: "Каноническое graph evidence M4.8R2 недоступно.";
|
||||
}
|
||||
|
||||
export function M48StaticOccupancyQualificationEvidence({
|
||||
result,
|
||||
}: {
|
||||
result: M48StaticOccupancyQualificationResult;
|
||||
}) {
|
||||
const [graph, setGraph] = useState<M47ReferenceGraphLabResult | null>(null);
|
||||
const [cases, setCases] = useState<readonly M48StaticOccupancyCase[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
void Promise.all([
|
||||
fetchM47ReferenceGraphLab({
|
||||
resultId: result.referenceGraphLabResultId,
|
||||
signal: controller.signal,
|
||||
}),
|
||||
fetchM48StaticOccupancyCases(result.resultId, { signal: controller.signal }),
|
||||
])
|
||||
.then(([nextGraph, nextCases]) => {
|
||||
if (controller.signal.aborted) return;
|
||||
if (nextCases.some((item) => item.sequence < 1 || item.sequence > nextGraph.frames.expected)) {
|
||||
throw new Error("M4.8R2 anchor вышел за immutable timeline Canonical Reference Graph.");
|
||||
}
|
||||
setGraph(nextGraph);
|
||||
setCases(nextCases);
|
||||
})
|
||||
.catch((caught: unknown) => {
|
||||
if (!controller.signal.aborted) setError(message(caught));
|
||||
})
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) setLoading(false);
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [result.referenceGraphLabResultId, result.resultId]);
|
||||
|
||||
const reviewAnchors = useMemo<readonly M4ReplayThreatReviewAnchor[]>(() => (
|
||||
cases.map((item) => ({
|
||||
id: item.anchorId,
|
||||
sourceSequence: item.sequence - 1,
|
||||
extentXyxyNormalized: item.extentXyxy,
|
||||
matchedAtThreshold: item.candidateQualified,
|
||||
}))
|
||||
), [cases]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="l3-visual-audit__state" role="status">
|
||||
<span className="busy-indicator" aria-hidden="true" />
|
||||
<span>Открываем M4.7 graph и M4.8R2 static anchors</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (error || !graph) {
|
||||
return (
|
||||
<div className="l3-visual-audit__state" role="alert">
|
||||
<Icon name="alert" size={18} />
|
||||
<span>{error ?? "Canonical Reference Graph не связан с M4.8R2."}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<M4ReplayThreatVisual
|
||||
resultId={graph.visual.resultId}
|
||||
semantic={{ resultId: graph.semantic.resultId, taxonomy: graph.semantic.taxonomy }}
|
||||
reviewAnchors={reviewAnchors}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
LaboratoryEvidence,
|
||||
LaboratoryResultSummary,
|
||||
LaboratorySummary,
|
||||
LaboratoryWorkTemplate,
|
||||
} from "../../components/laboratory/LaboratoryPresentation";
|
||||
import type { M48StaticOccupancyQualificationResult } from "../../core/laboratory/m48StaticOccupancyQualification";
|
||||
import { M48StaticOccupancyQualificationEvidence } from "./M48StaticOccupancyQualificationEvidence";
|
||||
|
||||
function percent(value: number): string {
|
||||
return `${(value * 100).toLocaleString("ru-RU", { maximumFractionDigits: 1 })}%`;
|
||||
}
|
||||
|
||||
export function M48StaticOccupancyQualificationResultView({
|
||||
rigLabel,
|
||||
result,
|
||||
}: {
|
||||
rigLabel: string;
|
||||
result: M48StaticOccupancyQualificationResult;
|
||||
}) {
|
||||
const nearReady = result.decision.criticalNearCandidateReadyForShadow;
|
||||
const status = nearReady
|
||||
? "0–8 м: кандидат закрыл все assisted-якоря; готов к Worker shadow"
|
||||
: "0–8 м: статическая occupancy ещё не закрыта";
|
||||
return (
|
||||
<LaboratoryWorkTemplate
|
||||
summary={(
|
||||
<LaboratorySummary
|
||||
title="M4.8R2 · консервативная static occupancy"
|
||||
description="RF-DETR не изменяется. Отдельный детерминированный прогон проверяет, удерживает ли current/rolling LiDAR низкие полусферы, столбы и другие безымянные ограничения, и оценивает уже вычисляемый CPU-признак low-step как additive occupied-only слой."
|
||||
status={status}
|
||||
statusTone={nearReady ? "success" : "warning"}
|
||||
facts={[
|
||||
{ label: "Конфигурация", value: `${rigLabel} RIGHT · Canonical Reference Graph · VIDEO/CAMERA/3D/PLAN/SEMANTICS` },
|
||||
{ label: "Пайплайн", value: result.pipelineId },
|
||||
{ label: "Прогон", value: `${result.runLabel} · immutable ${result.resultId}` },
|
||||
{ label: "Нагрузка", value: "CPU-only geometry · 0 дополнительных detector inference" },
|
||||
{ label: "Authority", value: "REPLAY-SIMULATED · free-space/commands/actuation OFF" },
|
||||
]}
|
||||
brief={{
|
||||
question: "Не превратятся ли близкие полусферы, столбы и другие статические ограничения в пропуск только потому, что камера не знает их название?",
|
||||
approach: `На ${result.metrics.operatorStaticAnchorCount} существующих operator-assisted static anchors принятую current/rolling occupancy сравнили с additive low-step evidence. Отдельно сохранены четыре канонических hemisphere-якоря кадров 1880/2584.`,
|
||||
principalResult: `Принятый граф покрывает ${result.metrics.baselineQualifiedCount}/${result.metrics.operatorStaticAnchorCount}; additive кандидат — ${result.metrics.candidateQualifiedCount}/${result.metrics.operatorStaticAnchorCount}. В критической зоне 0–8 м: ${percent(result.metrics.criticalNearCandidateRecall)}.`,
|
||||
limitation: "Это assisted diagnostic, а не independent truth. Кандидат пока не включён в Worker и может увеличить ложную занятость; требуются полный shadow-прогон, FPS и occupancy-volume gate.",
|
||||
}}
|
||||
method={{
|
||||
completeness: "complete",
|
||||
executionClass: "deterministic",
|
||||
pipelineId: result.pipelineId,
|
||||
components: [
|
||||
{ kind: "source", name: result.referenceGraphLabResultId, version: "accepted M4.7", role: "immutable current/rolling occupied/unknown + threat graph", identitySha256: result.referenceGraphLabResultId.split("-").at(-1) ?? null },
|
||||
{ kind: "source", name: result.smallStaticResultId, version: "M4.8R1 assisted seed", role: "static avoidance anchors · not independent truth", identitySha256: result.smallStaticResultId.split("-").at(-1) ?? null },
|
||||
{ kind: "algorithm", name: "low-step occupied-only candidate", version: "v1", role: "additive CPU geometry; never clearing", identitySha256: result.resultId.split("-").at(-1) ?? null },
|
||||
],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
evidence={(
|
||||
<LaboratoryEvidence eyebrow="M4.8R2 VISUAL EVIDENCE · CANONICAL REFERENCE GRAPH" title="Те же CAMERA/3D/PLAN, 11 static anchors и неизменённый M4.7 timeline" kind="recorded-replay" resizable>
|
||||
<M48StaticOccupancyQualificationEvidence result={result} />
|
||||
</LaboratoryEvidence>
|
||||
)}
|
||||
result={(
|
||||
<LaboratoryResultSummary
|
||||
title="Что дал прогон: локализован лёгкий геометрический слой для близких препятствий"
|
||||
status={status}
|
||||
statusTone={nearReady ? "success" : "warning"}
|
||||
metrics={[
|
||||
{ label: "Current graph", value: `${result.metrics.baselineQualifiedCount}/${result.metrics.operatorStaticAnchorCount}`, hint: `${percent(result.metrics.criticalNearBaselineRecall)} в 0–8 м` },
|
||||
{ label: "+ low-step candidate", value: `${result.metrics.candidateQualifiedCount}/${result.metrics.operatorStaticAnchorCount}`, hint: `${percent(result.metrics.criticalNearCandidateRecall)} в 0–8 м` },
|
||||
{ label: "8–12 м", value: percent(result.metrics.approachCandidateRecall), hint: `${result.metrics.approachAnchorCount} assisted anchors · ещё не gate` },
|
||||
{ label: "False free", value: result.metrics.falseFreeCount.toLocaleString("ru-RU"), hint: `${result.metrics.canonicalEngineeringAnchorCount}/${result.metrics.canonicalEngineeringAnchorCount} canonical anchors retained` },
|
||||
]}
|
||||
conclusion={{
|
||||
proved: `На замороженном assisted seed additive low-step evidence поднял покрытие близких static anchors до ${percent(result.metrics.criticalNearCandidateRecall)} без дополнительной нейросети и без единого free-space утверждения.`,
|
||||
notProved: "Не доказаны independent precision/recall, приемлемый рост консервативной occupancy, realtime Worker FPS после интеграции, физический clearance и collision safety.",
|
||||
decision: nearReady
|
||||
? "Интегрировать low-step только как occupied/unknown Worker shadow. Затем прогнать все 4 489 кадров и принять по FPS, росту компонентов, отсутствию capacity drops и повторному static-anchor gate."
|
||||
: "Не интегрировать в Worker до исправления близких пропусков.",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -77,6 +77,13 @@ const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`
|
||||
experimentName: "M4.8 · small static passage regression",
|
||||
variantName: "M4.8R1 · Worker 006 small-static assisted baseline",
|
||||
},
|
||||
"m48-static-occupancy-qualification": {
|
||||
profileId: "rig-dual-evidence-virtual-corridor-v1",
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Canonical Reference Graph`,
|
||||
experimentId: "m48-static-occupancy-qualification",
|
||||
experimentName: "M4.8 · conservative static occupancy qualification",
|
||||
variantName: "M4.8R2 · current/rolling + low-step occupied-only candidate",
|
||||
},
|
||||
"m48s-fixed-class-detector": {
|
||||
profileId: "rig-ravnoves-perception-gate-v1",
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · RAVNOVES00 perception gate`,
|
||||
|
||||
@@ -21,6 +21,7 @@ function mergeResults(
|
||||
m47Graph: next.m47Graph ?? current.m47Graph,
|
||||
m48: next.m48 ?? current.m48,
|
||||
m48SmallStatic: next.m48SmallStatic ?? current.m48SmallStatic,
|
||||
m48StaticOccupancy: next.m48StaticOccupancy ?? current.m48StaticOccupancy,
|
||||
m48s: next.m48s ?? current.m48s,
|
||||
m48t: next.m48t ?? current.m48t,
|
||||
m4Threat: next.m4Threat ?? current.m4Threat,
|
||||
@@ -120,6 +121,7 @@ export function useAdvancedLaboratoryCatalog({
|
||||
"m47-reference-graph-shadow",
|
||||
"m48-object-centric-quality",
|
||||
"m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification",
|
||||
].includes(selectedWorkId)
|
||||
&& !indexedResultId
|
||||
) return;
|
||||
|
||||
Reference in New Issue
Block a user