feat(lab): add lazy E40 evidence review

This commit is contained in:
DCCONSTRUCTIONS
2026-07-30 21:53:04 +03:00
parent e9bbfb9a41
commit fdcaf57ae7
24 changed files with 3570 additions and 165 deletions
@@ -460,7 +460,7 @@ export function E30EvidencePointCloud({ detail }: E30EvidencePointCloudProps) {
</span>
<span data-point="rejected">Отклонено · {rejectedCount}</span>
<span data-point="selected">
Выбрано E29 · {detail.selected.pointsMapXyzM.length}
Проверяемый кластер E29 · {detail.selected.pointsMapXyzM.length}
</span>
</div>
</div>
@@ -206,7 +206,7 @@ export function E30EvidenceProjection({
</span>
<span data-point="rejected">Кандидаты · {rejectedCount}</span>
<span data-point="selected">
Выбрано E29 · {detail.selected.pointsMapXyzM.length}
Проверяемый кластер E29 · {detail.selected.pointsMapXyzM.length}
</span>
</div>
) : null}
@@ -1,6 +1,11 @@
import type { ComponentType } from "react";
import type { LaboratoryOption } from "../../components/laboratory/LaboratoryPresentation";
import {
isAdvancedLaboratoryWorkId,
type AdvancedLaboratoryIndexItem,
type AdvancedLaboratoryWorkId,
} from "../../core/laboratory/advancedIndex";
import type { AdvancedLaboratoryResults } from "../../core/laboratory/advancedResults";
import type { ObservationSessionSummary } from "../../core/observation/sessionArchive";
import type { WorkspaceRendererProps } from "../contracts";
@@ -15,97 +20,29 @@ import { E39Result } from "./E39Result";
import { E40Result } from "./E40Result";
import { RecordedReplayEvidence } from "./RecordedReplayEvidence";
export type AdvancedLaboratoryWorkId =
| "e31-source-binding"
| "e32-track-geometry"
| "e33-worker-shadow"
| "e34-temporal-layer"
| "e35-degradation-recovery"
| "e37-ravnoves-acceptance"
| "e38-perception-baseline"
| "e39-perception-refinement"
| "e40-perception-product-gate";
export { isAdvancedLaboratoryWorkId };
export type { AdvancedLaboratoryWorkId };
type LaboratoryWorkspaceProps = WorkspaceRendererProps & {
SpatialView: ComponentType<WorkspaceRendererProps>;
};
export function isAdvancedLaboratoryWorkId(
value: string,
): value is AdvancedLaboratoryWorkId {
return (
value === "e31-source-binding"
|| value === "e32-track-geometry"
|| value === "e33-worker-shadow"
|| value === "e34-temporal-layer"
|| value === "e35-degradation-recovery"
|| value === "e37-ravnoves-acceptance"
|| value === "e38-perception-baseline"
|| value === "e39-perception-refinement"
|| value === "e40-perception-product-gate"
);
}
export function advancedLaboratoryWorkOptions(
results: AdvancedLaboratoryResults,
sourceSessions: ReadonlyMap<string, ObservationSessionSummary>,
index: readonly AdvancedLaboratoryIndexItem[],
): readonly LaboratoryOption<AdvancedLaboratoryWorkId>[] {
const options: LaboratoryOption<AdvancedLaboratoryWorkId>[] = [];
if (results.e31 && sourceSessions.has(results.e31.sourceSessionId)) {
options.push({
id: "e31-source-binding",
label: "LAB E31 · source binding",
});
}
if (results.e32 && sourceSessions.has(results.e32.sourceSessionId)) {
options.push({
id: "e32-track-geometry",
label: "LAB E32 · TrackGeometry v1",
});
}
if (results.e33 && sourceSessions.has(results.e33.sourceSessionId)) {
options.push({
id: "e33-worker-shadow",
label: "LAB E33 · worker shadow 1×",
});
}
if (results.e34) {
options.push({
id: "e34-temporal-layer",
label: "LAB E34 · temporal occupied/unknown",
});
}
if (results.e35) {
options.push({
id: "e35-degradation-recovery",
label: "LAB E35 · degradation recovery",
});
}
if (results.e37) {
options.push({
id: "e37-ravnoves-acceptance",
label: "LAB E37 · RAVNOVES00 acceptance R0",
});
}
if (results.e38) {
options.push({
id: "e38-perception-baseline",
label: "LAB E38 · perception quality R1",
});
}
if (results.e39) {
options.push({
id: "e39-perception-refinement",
label: "LAB E39 · perception refinement R1",
});
}
if (results.e40) {
options.push({
id: "e40-perception-product-gate",
label: "LAB E40 · leakage-resistant product gate",
});
}
return options;
const available = new Set(index.map(({ workId }) => workId));
const options: readonly LaboratoryOption<AdvancedLaboratoryWorkId>[] = [
{ 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×" },
{ id: "e34-temporal-layer", label: "LAB E34 · temporal occupied/unknown" },
{ id: "e35-degradation-recovery", label: "LAB E35 · degradation recovery" },
{ id: "e37-ravnoves-acceptance", label: "LAB E37 · RAVNOVES00 acceptance R0" },
{ id: "e38-perception-baseline", label: "LAB E38 · perception quality R1" },
{ id: "e39-perception-refinement", label: "LAB E39 · perception refinement R1" },
{ id: "e40-perception-product-gate", label: "LAB E40 · leakage-resistant product gate" },
];
return options.filter(({ id }) => available.has(id));
}
export function advancedLaboratorySourceSession(
@@ -0,0 +1,550 @@
import { useEffect, useState } from "react";
import {
Button,
Icon,
IconButton,
Select,
StatusBadge,
} from "@nodedc/ui-react";
import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer";
import {
fetchE30ReviewItemDetail,
type E30ReviewItemDetail,
} from "../../core/laboratory/e30Review";
import {
fetchE40CaseCatalog,
fetchE40OperatorReview,
saveE40OperatorVerdict,
type E40CaseCatalog,
type E40CaseDimension,
type E40CaseSeverity,
type E40CaseState,
type E40CaseStratum,
type E40ErrorCase,
type E40OperatorReview,
type E40OperatorVerdict,
} from "../../core/laboratory/e40CaseReview";
import { E30EvidencePointCloud } from "../E30EvidencePointCloud";
import { E30EvidenceProjection } from "../E30EvidenceProjection";
type EvidenceMode = "camera" | "3d";
const SEVERITY_LABELS: Record<E40CaseSeverity, string> = {
high: "Высокий приоритет",
medium: "Средний приоритет",
standard: "Стандартный приоритет",
};
const STRATUM_LABELS: Record<E40CaseStratum, string> = {
agree: "согласованные источники",
"camera-only": "только камера",
conflict: "конфликт источников",
"geometry-only": "только геометрия",
unknown: "неопределённый источник",
};
const DIMENSION_LABELS: Record<E40CaseDimension, string> = {
presence: "Присутствие",
geometry_association: "Связь с геометрией",
freshness: "Актуальность",
};
const VALUE_LABELS: Readonly<Record<string, string>> = {
"background-or-noise": "Фон или шум",
"object-present": "Объект присутствует",
"occupied-environment": "Занятая среда",
"independent-occupied": "Независимая геометрия",
"insufficient-support": "Недостаточно опоры",
"object-associated": "Связано с объектом",
"rejected-nonobject": "Не объект",
unknown: "Не определено",
current: "Актуально",
stale: "Устарело",
unavailable: "Недоступно",
};
function stateValue(
state: E40CaseState,
dimension: E40CaseDimension,
): string {
if (dimension === "presence") return state.presence;
if (dimension === "geometry_association") {
return state.geometryAssociation;
}
return state.freshness;
}
function caseLabel(item: E40ErrorCase): string {
const dimensions = item.mismatchedDimensions
.map((dimension) => DIMENSION_LABELS[dimension])
.join(" + ");
return `${SEVERITY_LABELS[item.severity]} · кадр ${item.sourceFrameIndex} · ${dimensions}`;
}
function reviewPrompt(
item: E40ErrorCase,
detail: E30ReviewItemDetail,
): {
question: string;
evidence: string;
confirm: string;
reject: string;
} {
const hasSelectedPoints = detail.selected.pointsMapXyzM.length > 0;
const subject = hasSelectedPoints
? "Белый LiDAR-кластер"
: "Выделенная область";
const evidence = hasSelectedPoints
? detail.locatorKind === "geometry-only-cluster"
? (
"Белые точки — самостоятельный кластер занятой геометрии E29 без "
+ "класса. Это проверяемая LiDAR-опора, а не отметки ошибки."
)
: (
"Белые точки — LiDAR-опора, которую E29 связал с наблюдением. "
+ "Это проверяемые исходные точки, а не отметки ошибки."
)
: (
"Белого LiDAR-кластера в этом кейсе нет: решение принимается по "
+ "кадру и рамке наблюдения."
);
if (item.reference.presence === "object-present") {
return {
question: `${subject} относится к отдельному видимому объекту?`,
evidence,
confirm: (
"Да — объект виден; эталон верен, расхождение E40 подтверждается."
),
reject: (
"Нет — это фон или часть среды; предполагаемая ошибка отклоняется."
),
};
}
if (item.reference.presence === "background-or-noise") {
return {
question: `${subject} является фоном или шумом, а не объектом?`,
evidence,
confirm: (
"Да — это фон или шум; эталон верен, расхождение E40 подтверждается."
),
reject: (
"Нет — отдельный объект есть; предполагаемая ошибка отклоняется."
),
};
}
return {
question: "Проверяемый эталон точнее описывает кадр, чем решение E40?",
evidence,
confirm: "Да — эталон верен, расхождение E40 подтверждается.",
reject: "Нет — эталон спорен, предполагаемая ошибка отклоняется.",
};
}
export function E40CaseReview({ resultId }: { resultId: string }) {
const [catalog, setCatalog] = useState<E40CaseCatalog | null>(null);
const [selectedItemId, setSelectedItemId] = useState<string | null>(null);
const [detail, setDetail] = useState<E30ReviewItemDetail | null>(null);
const [review, setReview] = useState<E40OperatorReview | null>(null);
const [catalogLoading, setCatalogLoading] = useState(true);
const [detailLoading, setDetailLoading] = useState(false);
const [verdictPending, setVerdictPending] = useState(false);
const [error, setError] = useState<string | null>(null);
const [reviewError, setReviewError] = useState<string | null>(null);
const [mode, setMode] = useState<EvidenceMode>("camera");
const [pointLayerVisible, setPointLayerVisible] = useState(true);
const [expanded, setExpanded] = useState(false);
useEffect(() => {
const controller = new AbortController();
setCatalogLoading(true);
setCatalog(null);
setDetail(null);
setReview(null);
setSelectedItemId(null);
setError(null);
setReviewError(null);
void Promise.all([
fetchE40CaseCatalog(resultId, { signal: controller.signal }),
fetchE40OperatorReview(resultId, { signal: controller.signal }),
]).then(([nextCatalog, nextReview]) => {
if (
nextReview.materializationId !== nextCatalog.materializationId
|| nextReview.reviewedItemCount > nextCatalog.total
) {
throw new Error(
"Операторская проверка E40 не совпадает с каталогом ошибок.",
);
}
setCatalog(nextCatalog);
setReview(nextReview);
setSelectedItemId(nextCatalog.items[0]?.itemId ?? null);
}).catch((caught: unknown) => {
if (controller.signal.aborted) return;
setError(
caught instanceof Error
? caught.message
: "Case-review E40 недоступен.",
);
}).finally(() => {
if (!controller.signal.aborted) setCatalogLoading(false);
});
return () => controller.abort();
}, [resultId]);
useEffect(() => {
if (!catalog || !selectedItemId) {
setDetail(null);
return;
}
const controller = new AbortController();
setDetailLoading(true);
setDetail(null);
setError(null);
void fetchE30ReviewItemDetail(
catalog.materializationId,
selectedItemId,
{ signal: controller.signal },
).then((next) => {
setDetail(next);
setMode(next.cameraFrame ? "camera" : "3d");
}).catch((caught: unknown) => {
if (controller.signal.aborted) return;
setError(
caught instanceof Error
? caught.message
: "Визуальное доказательство кейса недоступно.",
);
}).finally(() => {
if (!controller.signal.aborted) setDetailLoading(false);
});
return () => controller.abort();
}, [catalog, selectedItemId]);
const selectedCase = catalog?.items.find(
(item) => item.itemId === selectedItemId,
) ?? null;
const selectedIndex = selectedCase && catalog
? catalog.items.findIndex((item) => item.itemId === selectedCase.itemId)
: -1;
const selectedDecision = selectedCase
? review?.decisions.find(
(decision) => decision.itemId === selectedCase.itemId,
) ?? null
: null;
const prompt = selectedCase && detail
? reviewPrompt(selectedCase, detail)
: null;
const navigate = (offset: -1 | 1) => {
if (!catalog?.items.length || selectedIndex < 0) return;
const index = (
selectedIndex + offset + catalog.items.length
) % catalog.items.length;
setSelectedItemId(catalog.items[index].itemId);
};
const saveVerdict = async (verdict: E40OperatorVerdict) => {
if (!review || !selectedCase || verdictPending) return;
setVerdictPending(true);
setReviewError(null);
try {
setReview(await saveE40OperatorVerdict(
resultId,
selectedCase.itemId,
{
expectedRevision: review.revision,
idempotencyKey: `ui-${crypto.randomUUID()}`,
verdict,
},
));
} catch (caught) {
setReviewError(
caught instanceof Error
? caught.message
: "Решение оператора не сохранено.",
);
} finally {
setVerdictPending(false);
}
};
if (catalogLoading) {
return (
<div className="e40-case-review__state" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Проверяем индекс исторической видимой оценки E40</span>
</div>
);
}
if (error && !selectedCase) {
return (
<div className="e40-case-review__state" role="status">
<Icon name="alert" size={18} />
<span>{error}</span>
</div>
);
}
if (!catalog?.items.length) {
return (
<div className="e40-case-review__state" role="status">
<Icon name="check" size={18} />
<span>Расхождений исторической оценки E40 не опубликовано.</span>
</div>
);
}
return (
<div className="e40-case-review">
<header className="e40-case-review__header">
<div>
<span className="section-eyebrow">
CASE REVIEW · HISTORICAL VISIBLE EVALUATION
</span>
<strong>
Визуальная проверка {catalog.items.length} из {catalog.total}{" "}
расхождений
</strong>
<small>
Загружается только выбранный camera/LiDAR-кейс. Проверено
оператором: {review?.reviewedItemCount ?? 0} из {catalog.total}.
</small>
</div>
{selectedCase ? (
<StatusBadge
tone={selectedCase.severity === "high"
? "danger"
: selectedCase.severity === "medium"
? "warning"
: "neutral"}
>
{SEVERITY_LABELS[selectedCase.severity]}
</StatusBadge>
) : null}
</header>
<div className="e40-case-review__viewer">
{detailLoading ? (
<div className="e40-case-review__state" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Проверяем точный кадр и LiDAR-геометрию</span>
</div>
) : error || !detail || !selectedCase ? (
<div className="e40-case-review__state" role="status">
<Icon name="alert" size={18} />
<span>{error ?? "Выберите ошибку для проверки."}</span>
</div>
) : (
<LaboratoryEvidenceViewer
label="Визуальная проверка расхождения E40"
mode={mode}
modes={[
{ value: "camera", label: "Камера" },
{ value: "3d", label: "3D" },
]}
expanded={expanded}
onModeChange={setMode}
onExpandedChange={setExpanded}
actions={(
<div className="e40-case-review__controls">
<div
className="e40-case-review__pagination"
aria-label="Последовательная проверка ошибок"
>
<IconButton
className="e40-case-review__glass-button"
label="Предыдущее расхождение"
disabled={catalog.items.length < 2}
onClick={() => navigate(-1)}
>
<Icon name="chevron-left" size={16} />
</IconButton>
<IconButton
className="e40-case-review__glass-button"
label="Следующее расхождение"
disabled={catalog.items.length < 2}
onClick={() => navigate(1)}
>
<Icon name="chevron-right" size={16} />
</IconButton>
</div>
<Select
className="e40-case-review__select"
menuClassName="e40-case-review__select-menu"
label="Расхождение исторической оценки E40"
value={selectedCase.itemId}
options={catalog.items.map((item) => ({
value: item.itemId,
label: caseLabel(item),
}))}
variant="split"
menuWidth="anchor"
onChange={setSelectedItemId}
/>
{mode === "camera" ? (
<Button
className="e40-case-review__layer-button"
size="compact"
variant="secondary"
shape="pill"
icon={<Icon name="sliders" size={16} />}
data-active={pointLayerVisible ? "true" : undefined}
aria-pressed={pointLayerVisible}
onClick={() => setPointLayerVisible((visible) => !visible)}
>
LiDAR
</Button>
) : null}
</div>
)}
overlay={(
<aside
className="e40-case-review__telemetry"
aria-label="Эталон и результат E40"
>
<div className="e40-case-review__telemetry-content">
<span>
Кадр {detail.sourceFrameIndex}
{" · "}
{STRATUM_LABELS[selectedCase.sourceStratum]}
{selectedCase.predictionBasis === "camera-only-softmax"
? (
<>
{" · "}
вероятность camera-only softmax{" "}
{(selectedCase.presenceConfidence * 100).toLocaleString(
"ru-RU",
{ maximumFractionDigits: 1 },
)}%
</>
)
: " · фиксированная stratum-policy, не вероятность"}
</span>
{prompt ? (
<div className="e40-case-review__question">
<span>ЧТО ПРОВЕРЯЕМ</span>
<strong>{prompt.question}</strong>
<small>{prompt.evidence}</small>
</div>
) : null}
<dl>
{selectedCase.mismatchedDimensions.map((dimension) => (
<div key={dimension}>
<dt>{DIMENSION_LABELS[dimension]}</dt>
<dd>
<span>
Эталон ·{" "}
{VALUE_LABELS[
stateValue(selectedCase.reference, dimension)
]}
</span>
<strong>
{selectedCase.predictionBasis
=== "camera-only-softmax"
? "Модель E40"
: "Правило E40"}{" "}
·{" "}
{VALUE_LABELS[
stateValue(selectedCase.prediction, dimension)
]}
</strong>
</dd>
</div>
))}
</dl>
</div>
<div className="e40-case-review__verdict">
<span>
{verdictPending
? "Сохраняем…"
: selectedDecision?.verdict === "confirmed-error"
? "Ошибка подтверждена"
: selectedDecision?.verdict === "rejected-error"
? "Расхождение отклонено"
: "Решение оператора"}
</span>
<div
className="e40-case-review__verdict-buttons"
aria-label="Решение оператора"
>
<IconButton
className="e40-case-review__verdict-button"
data-verdict="confirmed"
data-active={
selectedDecision?.verdict === "confirmed-error"
? "true"
: undefined
}
label={prompt?.confirm ?? "Подтвердить ошибку модели"}
aria-pressed={
selectedDecision?.verdict === "confirmed-error"
}
disabled={verdictPending}
onClick={() => void saveVerdict("confirmed-error")}
>
<Icon name="check" size={16} />
</IconButton>
<IconButton
className="e40-case-review__verdict-button"
data-verdict="rejected"
data-active={
selectedDecision?.verdict === "rejected-error"
? "true"
: undefined
}
label={prompt?.reject ?? "Отклонить ошибку модели"}
aria-pressed={
selectedDecision?.verdict === "rejected-error"
}
disabled={verdictPending}
onClick={() => void saveVerdict("rejected-error")}
>
<Icon name="close" size={16} />
</IconButton>
</div>
{prompt ? (
<div className="e40-case-review__verdict-guide">
<span data-verdict="confirmed">
<Icon name="check" size={12} />
{prompt.confirm}
</span>
<span data-verdict="rejected">
<Icon name="close" size={12} />
{prompt.reject}
</span>
</div>
) : null}
<small className="e40-case-review__commit-note">
Выбор сохраняется сразу; исходные E30/E40 не изменяются.
</small>
{reviewError ? (
<small
className="e40-case-review__verdict-error"
role="alert"
>
{reviewError}
</small>
) : null}
</div>
</aside>
)}
>
{mode === "camera" ? (
<E30EvidenceProjection
detail={detail}
projectionWidth={detail.materialization.projectionWidth}
projectionHeight={detail.materialization.projectionHeight}
pointLayerVisible={pointLayerVisible}
/>
) : (
<E30EvidencePointCloud detail={detail} />
)}
</LaboratoryEvidenceViewer>
)}
</div>
</div>
);
}
@@ -1,6 +1,5 @@
import {
LaboratoryEvidence,
LaboratoryMetricGrid,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
@@ -9,6 +8,7 @@ import type {
E40PerceptionProductGateResult,
} from "../../core/laboratory/e40ProductGate";
import { formatNumber } from "../../presentation";
import { E40CaseReview } from "./E40CaseReview";
function percent(value: number): string {
return `${(value * 100).toLocaleString("ru-RU", {
@@ -32,14 +32,14 @@ export function E40Result({
result.developmentCrossValidation.protocols.wholeTrackOrSceneWindow.dimensions
);
const gateLabel = result.qualityGatePassed
? "RAVNOVES00 product gate пройден"
: "RAVNOVES00 product gate не пройден";
? "Историческая оценка E40 пройдена"
: "Историческая оценка E40 не пройдена";
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="LAB E40 · leakage-resistant product gate"
description="После E39 проверена route-coordinate-free модель: смежные кадры, целые track и scene-window изолированы при development-проверке, а sealed validation исполнен один раз на Worker 006."
title="LAB E40 · historical visible engineering evaluation"
description="Историческая source-scoped оценка E40: camera-only softmax и фиксированная stratum-policy измерены на уже видимом инженерном контракте RAVNOVES00. Это не operational camera→LiDAR pipeline и не blind validation."
status={gateLabel}
statusTone={result.qualityGatePassed ? "success" : "warning"}
facts={[
@@ -53,7 +53,7 @@ export function E40Result({
},
{
label: "Validation",
value: `${formatNumber(metrics.validationItems, 0)} · sealed`,
value: `${formatNumber(metrics.validationItems, 0)} · historical visible`,
},
{
label: "Исполнение",
@@ -61,10 +61,10 @@ export function E40Result({
},
]}
brief={{
question: "Удерживает ли perception выбранного сенсорного рига не менее 90% по presence, geometry association и freshness после удаления route-coordinate leakage и группировки зависимых кадров?",
approach: "Для 340 development-кейсов зафиксированы 125 признаков без frame index, session time, review ordinal, track ID и абсолютных map-координат. Консервативная stratum-policy и camera-only softmax прошли два групповых пятифолдовых протокола; validation labels при выборе и обучении не использовались.",
principalResult: `Development: contiguous presence ${percent(contiguous.presence.accuracy)}, grouped presence ${percent(grouped.presence.accuracy)}. Sealed validation: presence ${percent(dimensions.presence.accuracy)}, geometry ${percent(dimensions.geometryAssociation.accuracy)}, freshness ${percent(dimensions.freshness.accuracy)}.`,
limitation: "Это source-scoped доказательство только для RAVNOVES00 на инженерно проверенной разметке. Оно не доказывает перенос на другой маршрут, растительность, камеру, риг или живой ровер и не выдаёт навигационных либо safety-полномочий.",
question: "Как camera-only softmax и фиксированная политика страт согласуются с уже видимым инженерным контрактом RAVNOVES00?",
approach: "Для 340 development-кейсов зафиксированы 125 признаков без frame index, session time, review ordinal, track ID и абсолютных map-координат. Только camera-only использует softmax; agree, conflict, geometry-only и unknown получают детерминированные policy-state, а не вероятность operational detector.",
principalResult: `Development: contiguous presence ${percent(contiguous.presence.accuracy)}, grouped presence ${percent(grouped.presence.accuracy)}. Historical visible evaluation: presence ${percent(dimensions.presence.accuracy)}, geometry ${percent(dimensions.geometryAssociation.accuracy)}, freshness ${percent(dimensions.freshness.accuracy)}.`,
limitation: "E40 — историческая source-scoped инженерная оценка поверх E29/E30, а не сама camera-first perception. Она не является blind validation и не доказывает независимую точность, перенос, навигацию или safety.",
}}
method={{
completeness: "complete",
@@ -74,7 +74,7 @@ export function E40Result({
{
kind: "source",
name: "E37 frozen acceptance contract",
version: `${formatNumber(metrics.developmentItems, 0)} development + ${formatNumber(metrics.validationItems, 0)} sealed validation`,
version: `${formatNumber(metrics.developmentItems, 0)} development + ${formatNumber(metrics.validationItems, 0)} historical visible evaluation`,
role: "неизменяемый denominator и reference-метрики трёх измерений",
identitySha256: null,
},
@@ -96,7 +96,7 @@ export function E40Result({
kind: "runtime",
name: "Worker 006",
version: result.workerNode,
role: "одно sealed evaluation в package-bound контейнере без командных полномочий",
role: "одно historical visible evaluation в package-bound контейнере без командных полномочий",
identitySha256: null,
},
],
@@ -105,34 +105,11 @@ export function E40Result({
)}
evidence={(
<LaboratoryEvidence
eyebrow="GROUPED DEVELOPMENT → SEALED VALIDATION"
eyebrow="GROUPED DEVELOPMENT → HISTORICAL VISIBLE EVALUATION"
title="Устойчивость без route-coordinate leakage"
kind="diagnostic-model"
>
<LaboratoryMetricGrid
metrics={[
{
label: "Contiguous time CV",
value: percent(contiguous.presence.accuracy),
hint: `presence · geometry ${percent(contiguous.geometryAssociation.accuracy)}`,
},
{
label: "Whole track / scene CV",
value: percent(grouped.presence.accuracy),
hint: `presence · geometry ${percent(grouped.geometryAssociation.accuracy)}`,
},
{
label: "Sealed presence",
value: percent(dimensions.presence.accuracy),
hint: `${formatNumber(dimensions.presence.correct, 0)} / ${formatNumber(dimensions.presence.total, 0)} · цель 90%`,
},
{
label: "Sealed freshness",
value: percent(dimensions.freshness.accuracy),
hint: `${formatNumber(dimensions.freshness.correct, 0)} / ${formatNumber(dimensions.freshness.total, 0)} · цель 90%`,
},
]}
/>
<E40CaseReview resultId={result.resultId} />
</LaboratoryEvidence>
)}
result={(
@@ -167,7 +144,7 @@ export function E40Result({
conclusion={{
proved: result.qualityGatePassed
? "На неизменяемом RAVNOVES00 все три task-level dimension достигли 90%, полный denominator учтён, ложное свободное пространство не опубликовано и high-severity ошибок нет."
: `Development-профиль устойчив к двум зависимым разбиениям; sealed evaluation завершён с полным учётом ${formatNumber(metrics.validationItems, 0)} кейсов и без ложного свободного пространства.`,
: `Development-профиль устойчив к двум зависимым разбиениям; historical visible evaluation завершён с полным учётом ${formatNumber(metrics.validationItems, 0)} кейсов и без ложного свободного пространства.`,
notProved: "Не доказаны независимая физическая ground truth, второй маршрут, другой риг, растительная среда, живой rover runtime, навигация, команды или safety.",
decision: result.qualityGatePassed
? "Зафиксировать RAVNOVES00 perception gate как закрытый source-scoped этап. Следующий шаг — temporal product state и live-rover replay без расширения полномочий."
@@ -29,7 +29,6 @@ import {
type E30ReviewResult,
} from "../../core/laboratory/e30Review";
import {
fetchAdvancedLaboratoryResults,
type AdvancedLaboratoryResults,
} from "../../core/laboratory/advancedResults";
import {
@@ -51,6 +50,7 @@ import {
e28LaboratoryBrief, e29LaboratoryBrief,
e30LaboratoryBrief, PUBLISHED_LABORATORY_BRIEF,
} from "./laboratoryArchiveBriefs";
import { useAdvancedLaboratoryCatalog } from "./useAdvancedLaboratoryCatalog";
type LaboratoryWorkspaceProps = WorkspaceRendererProps & {
SpatialView: ComponentType<WorkspaceRendererProps>;
};
@@ -63,18 +63,6 @@ type LaboratoryWorkId =
| AdvancedLaboratoryWorkId
| `session:${string}`;
const EMPTY_ADVANCED_RESULTS: AdvancedLaboratoryResults = {
e31: null,
e32: null,
e33: null,
e34: null,
e35: null,
e37: null,
e38: null,
e39: null,
e40: null,
};
function laboratoryWorkOrdinal(value: string): number {
const match = value.match(/\bE(\d+)\b/i);
return match ? Number(match[1]) : -1;
@@ -572,13 +560,11 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
const [e28Model, setE28Model] = useState<LidarLocalSurfaceModel | null>(null);
const [e29Result, setE29Result] = useState<E29EvidenceResult | null>(null);
const [e30Result, setE30Result] = useState<E30ReviewResult | null>(null);
const [advancedResults, setAdvancedResults] = useState(
EMPTY_ADVANCED_RESULTS,
);
const [evidenceLoading, setEvidenceLoading] = useState(true);
const [evidenceError, setEvidenceError] = useState<string | null>(null);
const sessions = useObservationSessions({
limit: 100,
pollingEnabled: false,
replayEnabled: props.sessionArchive.blockedReason === null,
onReplayBegin: props.sessionArchive.onReplayBegin,
onReplayAccepted: props.sessionArchive.onReplayAccepted,
@@ -603,6 +589,18 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
() => new Map(sessions.items.map((session) => [session.id, session])),
[sessions.items],
);
const advanced = useAdvancedLaboratoryCatalog({
selectedWorkId: workId,
onResultLoaded: (nextWorkId, result) => {
const sourceSession = advancedLaboratorySourceSession(
nextWorkId,
result,
sourceSessions,
);
if (sourceSession) void sessions.replay(sourceSession.id);
},
});
const advancedResults: AdvancedLaboratoryResults = advanced.results;
useEffect(() => {
const controller = new AbortController();
@@ -612,22 +610,18 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
fetchLidarLocalSurfaces({ signal: controller.signal }),
fetchE29EvidenceCatalog({ signal: controller.signal }),
fetchE30ReviewCatalog({ signal: controller.signal }),
fetchAdvancedLaboratoryResults({ signal: controller.signal }),
]).then(([e28, e29, e30, advanced]) => {
]).then(([e28, e29, e30]) => {
if (controller.signal.aborted) return;
const nextE28 = e28.status === "fulfilled" ? e28.value.items[0] ?? null : null;
const nextE29 = e29.status === "fulfilled" ? e29.value.items[0] ?? null : null;
const nextE30 = e30.status === "fulfilled" ? e30.value.items[0] ?? null : null;
const nextAdvanced = advanced.status === "fulfilled" ? advanced.value : null;
setE28Model(nextE28);
setE29Result(nextE29);
setE30Result(nextE30);
setAdvancedResults(nextAdvanced ?? EMPTY_ADVANCED_RESULTS);
const failures = [
e28.status === "rejected" ? "E28" : null,
e29.status === "rejected" ? "E29" : null,
e30.status === "rejected" ? "E30" : null,
advanced.status === "rejected" ? "E31–E40" : null,
].filter(Boolean);
setEvidenceError(
failures.length
@@ -673,19 +667,15 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
label: "LAB E30 · evidence review A2",
});
}
items.push(...advancedLaboratoryWorkOptions(
advancedResults,
sourceSessions,
));
items.push(...advancedLaboratoryWorkOptions(advanced.index));
return items.sort(
(left, right) => laboratoryWorkOrdinal(right.label) - laboratoryWorkOrdinal(left.label),
);
}, [
advancedResults,
advanced.index,
e28Model,
e29Result,
e30Result,
sourceSessions,
]);
const profiles = useMemo(() => {
const items: LaboratoryOption<LaboratoryProfileId>[] = [];
@@ -726,6 +716,7 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
useEffect(() => {
if (
evidenceLoading
|| advanced.indexLoading
|| sessions.state === "idle"
|| sessions.state === "loading"
) return;
@@ -761,6 +752,7 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
}
}, [
evidenceLoading,
advanced.indexLoading,
profileId,
profiles,
publishedWorks,
@@ -813,6 +805,7 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
if (
evidenceLoading
|| advanced.indexLoading
|| sessions.state === "idle"
|| sessions.state === "loading"
) {
@@ -831,7 +824,10 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
<Icon name="database" size={20} />
<strong>Подтверждённых лабораторных работ нет</strong>
<p>
{evidenceError ?? sessions.error ?? "Непроверенные и отсутствующие результаты скрыты."}
{evidenceError
?? advanced.indexError
?? sessions.error
?? "Непроверенные и отсутствующие результаты скрыты."}
</p>
</div>
);
@@ -953,6 +949,18 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
result={e30Result}
sourceSession={e30SourceSession}
/>
) : advanced.loadingWorkId === workId ? (
<div className="laboratory-result-pending" role="status">
<span className="busy-indicator" aria-hidden="true" />
<strong>Подготавливаем выбранную лабораторную работу</strong>
<p>Сервер проверяет только её доказательства и связанные артефакты.</p>
</div>
) : advanced.failedWorkId === workId ? (
<div className="laboratory-result-pending">
<Icon name="database" size={20} />
<strong>Работа не прошла ревизию</strong>
<p>{advanced.resultError}</p>
</div>
) : isAdvancedLaboratoryWorkId(workId) ? (
<AdvancedLaboratoryResult
props={props}
@@ -0,0 +1,118 @@
import { useEffect, useRef, useState } from "react";
import {
advancedLaboratoryResultAvailable,
emptyAdvancedLaboratoryResults,
fetchAdvancedLaboratoryIndex,
fetchAdvancedLaboratoryResult,
isAdvancedLaboratoryWorkId,
type AdvancedLaboratoryIndexItem,
type AdvancedLaboratoryWorkId,
} from "../../core/laboratory/advancedIndex";
import type {
AdvancedLaboratoryResults,
} from "../../core/laboratory/advancedResults";
function mergeResults(
current: AdvancedLaboratoryResults,
next: AdvancedLaboratoryResults,
): AdvancedLaboratoryResults {
return {
e31: next.e31 ?? current.e31,
e32: next.e32 ?? current.e32,
e33: next.e33 ?? current.e33,
e34: next.e34 ?? current.e34,
e35: next.e35 ?? current.e35,
e37: next.e37 ?? current.e37,
e38: next.e38 ?? current.e38,
e39: next.e39 ?? current.e39,
e40: next.e40 ?? current.e40,
};
}
function errorMessage(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
: "Выбранная лабораторная работа не прошла серверную проверку.";
}
export function useAdvancedLaboratoryCatalog({
selectedWorkId,
onResultLoaded,
}: {
selectedWorkId: string;
onResultLoaded?: (
workId: AdvancedLaboratoryWorkId,
result: AdvancedLaboratoryResults,
) => void | Promise<void>;
}) {
const [index, setIndex] = useState<readonly AdvancedLaboratoryIndexItem[]>([]);
const [indexLoading, setIndexLoading] = useState(true);
const [indexError, setIndexError] = useState<string | null>(null);
const [results, setResults] = useState<AdvancedLaboratoryResults>(
emptyAdvancedLaboratoryResults,
);
const [loadingWorkId, setLoadingWorkId] =
useState<AdvancedLaboratoryWorkId | null>(null);
const [failedWorkId, setFailedWorkId] =
useState<AdvancedLaboratoryWorkId | null>(null);
const [resultError, setResultError] = useState<string | null>(null);
const onResultLoadedRef = useRef(onResultLoaded);
onResultLoadedRef.current = onResultLoaded;
useEffect(() => {
const controller = new AbortController();
setIndexLoading(true);
setIndexError(null);
void fetchAdvancedLaboratoryIndex({ signal: controller.signal })
.then((items) => {
if (!controller.signal.aborted) setIndex(items);
})
.catch((error: unknown) => {
if (!controller.signal.aborted) {
setIndex([]);
setIndexError(errorMessage(error));
}
})
.finally(() => {
if (!controller.signal.aborted) setIndexLoading(false);
});
return () => controller.abort();
}, []);
useEffect(() => {
if (
!isAdvancedLaboratoryWorkId(selectedWorkId)
|| advancedLaboratoryResultAvailable(selectedWorkId, results)
) return;
const controller = new AbortController();
setLoadingWorkId(selectedWorkId);
setFailedWorkId(null);
setResultError(null);
void fetchAdvancedLaboratoryResult(selectedWorkId, {
signal: controller.signal,
}).then(async (next) => {
if (controller.signal.aborted) return;
setResults((current) => mergeResults(current, next));
await onResultLoadedRef.current?.(selectedWorkId, next);
}).catch((error: unknown) => {
if (!controller.signal.aborted) {
setFailedWorkId(selectedWorkId);
setResultError(errorMessage(error));
}
}).finally(() => {
if (!controller.signal.aborted) setLoadingWorkId(null);
});
return () => controller.abort();
}, [results, selectedWorkId]);
return {
index,
indexLoading,
indexError,
results,
loadingWorkId,
failedWorkId,
resultError,
} as const;
}