feat(lab): canonize versioned report template

This commit is contained in:
DCCONSTRUCTIONS
2026-07-27 16:05:32 +03:00
parent 2c016b117d
commit 95c6540691
20 changed files with 582 additions and 128 deletions
@@ -31,6 +31,28 @@ export interface LaboratoryMethod {
components: readonly LaboratoryMethodComponent[];
}
export interface LaboratoryBrief {
question: string;
approach: string;
principalResult: string;
limitation: string;
}
export interface LaboratoryResultMetric {
label: string;
value: string;
hint: string;
}
export interface LaboratoryResultConclusion {
proved: string;
notProved: string;
decision: string;
}
export const LABORATORY_REPORT_TEMPLATE_VERSION =
"missioncore.laboratory-report/v1";
const EXECUTION_LABELS: Record<LaboratoryExecutionClass, string> = {
deterministic: "Детерминированный",
"ai-inference": "AI inference",
@@ -96,6 +118,7 @@ export function LaboratorySummary({
status,
statusTone = "neutral",
facts,
brief,
method,
}: {
title: string;
@@ -103,6 +126,7 @@ export function LaboratorySummary({
status: string;
statusTone?: "neutral" | "success" | "warning" | "danger" | "accent";
facts: readonly { label: string; value: string }[];
brief: LaboratoryBrief;
method?: LaboratoryMethod | null;
}) {
const methodComplete = method?.completeness === "complete";
@@ -126,6 +150,25 @@ export function LaboratorySummary({
))}
</dl>
<dl className="laboratory-summary__brief">
<div>
<dt>Задача</dt>
<dd>{brief.question}</dd>
</div>
<div>
<dt>Как проверяли</dt>
<dd>{brief.approach}</dd>
</div>
<div>
<dt>Главный результат</dt>
<dd>{brief.principalResult}</dd>
</div>
<div>
<dt>Ограничение</dt>
<dd>{brief.limitation}</dd>
</div>
</dl>
{method ? (
<div className="laboratory-summary__method">
<header>
@@ -193,6 +236,69 @@ export function LaboratoryEvidence({
);
}
export function LaboratoryConclusion({
proved,
notProved,
decision,
}: {
proved: string;
notProved: string;
decision: string;
}) {
return (
<dl className="laboratory-result-conclusion">
<div>
<dt>Доказано</dt>
<dd>{proved}</dd>
</div>
<div>
<dt>Не доказано</dt>
<dd>{notProved}</dd>
</div>
<div>
<dt>Решение</dt>
<dd>{decision}</dd>
</div>
</dl>
);
}
export function LaboratoryResultSummary({
title,
status,
statusTone = "neutral",
metrics,
conclusion,
}: {
title: string;
status: string;
statusTone?: "neutral" | "success" | "warning" | "danger" | "accent";
metrics: readonly LaboratoryResultMetric[];
conclusion: LaboratoryResultConclusion;
}) {
return (
<section className="laboratory-result-summary">
<header>
<div>
<span className="section-eyebrow">РЕЗУЛЬТАТ И ВЫВОД</span>
<h2>{title}</h2>
</div>
<StatusBadge tone={statusTone}>{status}</StatusBadge>
</header>
<div className="laboratory-result-metrics">
{metrics.map((metric) => (
<div key={metric.label}>
<span>{metric.label}</span>
<strong>{metric.value}</strong>
<small>{metric.hint}</small>
</div>
))}
</div>
<LaboratoryConclusion {...conclusion} />
</section>
);
}
export function LaboratoryWorkTemplate({
summary,
evidence,
@@ -205,7 +311,10 @@ export function LaboratoryWorkTemplate({
details?: ReactNode;
}) {
return (
<div className="laboratory-work-template">
<div
className="laboratory-work-template"
data-template-contract={LABORATORY_REPORT_TEMPLATE_VERSION}
>
{summary}
{evidence}
{result}
@@ -64,15 +64,22 @@ export interface E33LaboratoryResult {
};
metrics: {
sourceFrames: number;
sourceAvailableFrames: number;
sourceUnavailableFrames: number;
deliveredFrames: number;
inputSuperseded: number;
resultSuperseded: number;
effectiveDeliveryFps: number;
sourceSpanSeconds: number;
deadlineMissFraction: number;
processingP95Ms: number;
releaseLagP95Ms: number;
resultAgeP95Ms: number;
resultAgeMaxMs: number;
resultDeadlineMs: number;
processRssP95Mib: number;
processRssMaxMib: number;
processRssLimitMib: number;
gpuUtilizationP95Percent: number;
gpuVisible: boolean;
workQueueCapacity: number;
@@ -314,15 +321,22 @@ function parseE33(value: unknown): E33LaboratoryResult {
},
metrics: {
sourceFrames: integerValue(metrics.source_frames, "E33.metrics.source_frames"),
sourceAvailableFrames: integerValue(metrics.source_available_frames, "E33.metrics.source_available_frames"),
sourceUnavailableFrames: integerValue(metrics.source_unavailable_frames, "E33.metrics.source_unavailable_frames"),
deliveredFrames: integerValue(metrics.delivered_frames, "E33.metrics.delivered_frames"),
inputSuperseded: integerValue(metrics.input_superseded, "E33.metrics.input_superseded"),
resultSuperseded: integerValue(metrics.result_superseded, "E33.metrics.result_superseded"),
effectiveDeliveryFps: numberValue(metrics.effective_delivery_fps, "E33.metrics.effective_delivery_fps"),
sourceSpanSeconds: numberValue(metrics.source_span_seconds, "E33.metrics.source_span_seconds"),
deadlineMissFraction: numberValue(metrics.deadline_miss_fraction, "E33.metrics.deadline_miss_fraction"),
processingP95Ms: numberValue(metrics.processing_p95_ms, "E33.metrics.processing_p95_ms"),
releaseLagP95Ms: numberValue(metrics.release_lag_p95_ms, "E33.metrics.release_lag_p95_ms"),
resultAgeP95Ms: numberValue(metrics.result_age_p95_ms, "E33.metrics.result_age_p95_ms"),
resultAgeMaxMs: numberValue(metrics.result_age_max_ms, "E33.metrics.result_age_max_ms"),
resultDeadlineMs: numberValue(metrics.result_deadline_ms, "E33.metrics.result_deadline_ms"),
processRssP95Mib: numberValue(metrics.process_rss_p95_mib, "E33.metrics.process_rss_p95_mib"),
processRssMaxMib: numberValue(metrics.process_rss_max_mib, "E33.metrics.process_rss_max_mib"),
processRssLimitMib: numberValue(metrics.process_rss_limit_mib, "E33.metrics.process_rss_limit_mib"),
gpuUtilizationP95Percent: numberValue(metrics.gpu_utilization_p95_percent, "E33.metrics.gpu_utilization_p95_percent"),
gpuVisible: booleanValue(metrics.gpu_visible, "E33.metrics.gpu_visible"),
workQueueCapacity: integerValue(metrics.work_queue_capacity, "E33.metrics.work_queue_capacity"),
+1
View File
@@ -2,6 +2,7 @@
@import "./styles/shell.css";
@import "./styles/workspaces.css";
@import "./styles/laboratory.css";
@import "./styles/laboratory-reporting.css";
@import "./styles/e30-human-review.css";
@import "./styles/spatial.css";
@import "./styles/device.css";
@@ -0,0 +1,54 @@
.laboratory-summary__brief {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.4rem;
margin-top: 0.65rem;
}
.laboratory-summary__brief > div {
display: grid;
gap: 0.28rem;
min-width: 0;
border-radius: 0.75rem;
background: rgb(255 255 255 / 0.025);
padding: 0.7rem;
}
.laboratory-summary__brief dt,
.laboratory-result-conclusion dt {
color: var(--nodedc-text-muted);
font-size: 0.54rem;
text-transform: uppercase;
}
.laboratory-summary__brief dd,
.laboratory-result-conclusion dd {
margin: 0;
color: var(--nodedc-text-secondary);
font-size: 0.64rem;
line-height: 1.5;
}
.laboratory-result-conclusion {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.4rem;
}
.laboratory-result-conclusion > div {
display: grid;
gap: 0.28rem;
min-width: 0;
border-top: 1px solid rgb(255 255 255 / 0.08);
padding: 0.75rem 0.1rem 0;
}
@media (max-width: 1100px) {
.laboratory-summary__brief {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.laboratory-result-conclusion {
grid-template-columns: 1fr;
}
}
@@ -98,7 +98,8 @@
.laboratory-summary p,
.laboratory-summary dl,
.laboratory-result-summary h2,
.laboratory-result-summary p {
.laboratory-result-summary p,
.laboratory-result-summary dl {
margin: 0;
}
@@ -219,6 +219,16 @@ function SpatialWorkspace({
const pointCloudFocused = Boolean(
pointCloudSource && observationLayout.focusedSourceId === pointCloudSource.id,
);
useEffect(() => {
if (!pointCloudFocused) return;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key !== "Escape") return;
event.preventDefault();
observationLayout.setFocusedSourceId(null);
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [observationLayout.setFocusedSourceId, pointCloudFocused]);
const floatingSourceMaximized = !unifiedPerception &&
Boolean(observationLayout.maximizedFloatingSourceId);
const timeline = state?.observationTimeline;
@@ -1,8 +1,8 @@
import type { ReactNode } from "react";
import { StatusBadge } from "@nodedc/ui-react";
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
@@ -36,6 +36,12 @@ export function E31Result({
},
{ label: "Контур", value: "Source-scoped · read-only" },
]}
brief={{
question: "Можно ли однозначно связать camera, LiDAR и pose всей записи RAVNOVES00 без подбора соответствий вручную?",
approach: "Для каждого кадра проверены временная привязка, чувствительность к offset, доступность pose и self-mask на одном неизменяемом источнике.",
principalResult: `${formatNumber(metrics.availableBindingCount, 0)} из ${formatNumber(metrics.frameCount, 0)} кадров получили воспроизводимую source-scoped привязку.`,
limitation: "Время основано на host-arrival, профиль действителен только для этой записи и этого монтажа сенсоров.",
}}
method={{
completeness: "complete",
executionClass: "deterministic",
@@ -70,38 +76,38 @@ export function E31Result({
</LaboratoryEvidence>
)}
result={(
<section className="laboratory-result-summary">
<header>
<div>
<span className="section-eyebrow">РЕЗУЛЬТАТ И ВЫВОД</span>
<h2>Привязка воспроизводима в границах этого источника</h2>
</div>
<StatusBadge tone="success">Source profile принят</StatusBadge>
</header>
<div className="laboratory-result-metrics">
<div>
<span>Camera LiDAR p95</span>
<strong>{metrics.lidarCameraP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 2 })} мс</strong>
<small>host-arrival binding</small>
</div>
<div>
<span>Pose point p95</span>
<strong>{metrics.posePointP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 2 })} мс</strong>
<small>по доступным кадрам</small>
</div>
<div>
<span>Поддержка соответствий</span>
<strong>{(metrics.supportedFraction * 100).toLocaleString("ru-RU", { maximumFractionDigits: 1 })}%</strong>
<small>{formatNumber(metrics.correspondenceCount, 0)} соответствий</small>
</div>
<div>
<span>Self-mask</span>
<strong>{formatNumber(metrics.semanticSelfSampleCount, 0)} образцов</strong>
<small>{formatNumber(metrics.semanticSelfCollateralCount, 0)} collateral</small>
</div>
</div>
<p>Результат диагностический: он не переносится на другой монтаж сенсоров и не выдаёт командных полномочий.</p>
</section>
<LaboratoryResultSummary
title="Привязка воспроизводима в границах этого источника"
status="Source profile принят"
statusTone="success"
metrics={[
{
label: "Camera ↔ LiDAR p95",
value: `${metrics.lidarCameraP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 2 })} мс`,
hint: "host-arrival binding",
},
{
label: "Pose ↔ point p95",
value: `${metrics.posePointP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 2 })} мс`,
hint: "по доступным кадрам",
},
{
label: "Поддержка соответствий",
value: `${(metrics.supportedFraction * 100).toLocaleString("ru-RU", { maximumFractionDigits: 1 })}%`,
hint: `${formatNumber(metrics.correspondenceCount, 0)} соответствий`,
},
{
label: "Self-mask",
value: `${formatNumber(metrics.semanticSelfSampleCount, 0)} образцов`,
hint: `${formatNumber(metrics.semanticSelfCollateralCount, 0)} collateral`,
},
]}
conclusion={{
proved: "Camera, LiDAR и pose воспроизводимо связаны для неизменяемой записи RAVNOVES00 в заявленных границах source profile.",
notProved: "Не доказаны аппаратная синхронизация, переносимость профиля на другую запись или другой монтаж и пригодность результата для управления.",
decision: "Принять source-scoped профиль как диагностический вход E32; команды, навигация и safety остаются запрещены.",
}}
/>
)}
/>
);
@@ -1,8 +1,8 @@
import type { ReactNode } from "react";
import { StatusBadge } from "@nodedc/ui-react";
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
@@ -33,6 +33,12 @@ export function E32Result({
{ label: "Кадры", value: formatNumber(metrics.framesTotal, 0) },
{ label: "Контур", value: "Diagnostic replay · read-only" },
]}
brief={{
question: "Можно ли детерминированно превратить полную принятую запись E29/E31 в покадровый TrackGeometry без повторной настройки порогов?",
approach: "Для каждого кадра применены один source profile, единый арбитраж перекрывающихся наблюдений, self-mask и запрет публикации диапазонов без квалифицированного источника.",
principalResult: `TrackGeometry построен для ${formatNumber(metrics.framesTotal, 0)} кадров; опубликовано ${formatNumber(metrics.qualifiedPointsPublished, 0)} квалифицированных точек.`,
limitation: "Результат наследует качество входных наблюдений и не считает отсутствие LiDAR-точек доказательством свободного пространства.",
}}
method={{
completeness: "complete",
executionClass: "deterministic",
@@ -67,38 +73,38 @@ export function E32Result({
</LaboratoryEvidence>
)}
result={(
<section className="laboratory-result-summary">
<header>
<div>
<span className="section-eyebrow">РЕЗУЛЬТАТ И ВЫВОД</span>
<h2>TrackGeometry построен без повторной настройки порогов</h2>
</div>
<StatusBadge tone="success">Детерминированный replay</StatusBadge>
</header>
<div className="laboratory-result-metrics">
<div>
<span>Опубликовано точек</span>
<strong>{formatNumber(metrics.qualifiedPointsPublished, 0)}</strong>
<small>{formatNumber(metrics.qualifiedPointsWithheld, 0)} withheld</small>
</div>
<div>
<span>Арбитраж наблюдений</span>
<strong>{formatNumber(metrics.observationsArbitrated, 0)}</strong>
<small>{formatNumber(metrics.overlappingClaimsRemoved, 0)} overlap снято</small>
</div>
<div>
<span>Semantic</span>
<strong>{formatNumber(metrics.semanticPublished, 0)}</strong>
<small>{formatNumber(metrics.semanticMasked, 0)} self-mask</small>
</div>
<div>
<span>Frame processing p95</span>
<strong>{metrics.frameProcessingP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс</strong>
<small>{(metrics.buildElapsedMs / 1000).toLocaleString("ru-RU", { maximumFractionDigits: 2 })} с build</small>
</div>
</div>
<p>Отсутствие LiDAR-точек по-прежнему не трактуется как свободное пространство; результат не является навигационным ground truth.</p>
</section>
<LaboratoryResultSummary
title="TrackGeometry построен без повторной настройки порогов"
status="Детерминированный replay"
statusTone="success"
metrics={[
{
label: "Опубликовано точек",
value: formatNumber(metrics.qualifiedPointsPublished, 0),
hint: `${formatNumber(metrics.qualifiedPointsWithheld, 0)} withheld`,
},
{
label: "Арбитраж наблюдений",
value: formatNumber(metrics.observationsArbitrated, 0),
hint: `${formatNumber(metrics.overlappingClaimsRemoved, 0)} overlap снято`,
},
{
label: "Semantic",
value: formatNumber(metrics.semanticPublished, 0),
hint: `${formatNumber(metrics.semanticMasked, 0)} self-mask`,
},
{
label: "Frame processing p95",
value: `${metrics.frameProcessingP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс`,
hint: `${(metrics.buildElapsedMs / 1000).toLocaleString("ru-RU", { maximumFractionDigits: 2 })} с build`,
},
]}
conclusion={{
proved: "Принятый source profile воспроизводимо материализует покадровый TrackGeometry с единственным владельцем перекрывающихся LiDAR-точек.",
notProved: "Не доказаны полнота препятствий, свободное пространство при отсутствии точек и навигационная точность геометрии.",
decision: "Принять TrackGeometry как неизменяемый диагностический вход worker shadow; не использовать как navigation ground truth.",
}}
/>
)}
/>
);
@@ -1,8 +1,8 @@
import type { ReactNode } from "react";
import { StatusBadge } from "@nodedc/ui-react";
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
@@ -23,33 +23,52 @@ export function E33Result({
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="LAB E33 · worker shadow 1×"
description=олная запись TrackGeometry выполнена отдельным bounded worker в темпе исходника 1×. Проверены очереди, доставка каждого кадра, задержка результата и ресурсы без подключения командного канала."
status="Worker gate пройден"
title="LAB E33 · проверка TrackGeometry worker в реальном темпе"
description=роверяли не качество распознавания объектов, а способность отдельного worker принять уже готовый TrackGeometry и вернуть явный результат по каждому кадру без накопления задержки и скрытых потерь."
status="Принят как worker shadow"
statusTone="success"
facts={[
{ label: "Конфигурация", value: `${rigLabel} · worker shadow` },
{ label: "Worker", value: result.worker.node },
{ label: "Доставка", value: `${formatNumber(metrics.deliveredFrames, 0)} / ${formatNumber(metrics.sourceFrames, 0)} кадров` },
{ label: "Контур", value: "Recorded-source-paced · 1×" },
{ label: "Источник", value: `${rigLabel} · RAVNOVES00` },
{
label: "Объём проверки",
value: `${formatNumber(metrics.sourceFrames, 0)} кадров · ${metrics.sourceSpanSeconds.toLocaleString("ru-RU", { maximumFractionDigits: 1 })} с`,
},
{
label: "Геометрия доступна",
value: `${formatNumber(metrics.sourceAvailableFrames, 0)} / ${formatNumber(metrics.sourceFrames, 0)} кадров`,
},
{ label: "Полномочия", value: "Read-only · команды и safety выключены" },
]}
brief={{
question: "Сможет ли отдельный worker обработать всю запись в исходном темпе 1×, не теряя кадры и не раздувая очереди?",
approach: "Готовый TrackGeometry из E32 выпускался по исходным временным меткам через отдельные work/result очереди ёмкостью 2. Для каждого кадра фиксировался один терминальный исход, задержка и использование памяти.",
principalResult: `Да. Доставлены все ${formatNumber(metrics.deliveredFrames, 0)} исходов, замен и deadline miss не было; p95 возраста результата — ${metrics.resultAgeP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс.`,
limitation: `Детектор, semantic-модели и качество геометрии здесь не пересчитывались. В ${formatNumber(metrics.sourceUnavailableFrames, 0)} кадрах источник уже не содержал квалифицированной геометрии.`,
}}
method={{
completeness: "complete",
executionClass: "deterministic",
pipelineId: result.pipelineId,
pipelineId: "TrackGeometry → recorded-paced bounded worker",
components: [
{
kind: "source",
name: result.e32ResultId,
version: "accepted TrackGeometry replay",
role: "immutable frame stream",
identitySha256: result.e32ResultId.split("-").at(-1) ?? null,
name: "Принятый TrackGeometry из LAB E32",
version: `${formatNumber(metrics.sourceFrames, 0)} кадров · неизменяемая запись`,
role: "готовый вход; detector и semantic inference не запускались повторно",
identitySha256: null,
},
{
kind: "algorithm",
name: "Recorded pacing + bounded latest-wins queues",
version: `1× · очереди ${metrics.workQueueCapacity}/${metrics.resultQueueCapacity}`,
role: "выпуск по исходному времени, закрытый учёт исходов и deadline-контроль",
identitySha256: null,
},
{
kind: "runtime",
name: result.worker.node,
version: `${result.worker.python} · NumPy ${result.worker.numpy}`,
role: "bounded worker, work/result queues 2/2",
name: "Изолированный worker",
version: `${result.worker.node} · Python ${result.worker.python} · NumPy ${result.worker.numpy}`,
role: "проверка структуры TrackGeometry, SHA-256 диапазонов PointSlab и публикация результата",
identitySha256: null,
},
],
@@ -59,7 +78,7 @@ export function E33Result({
evidence={(
<LaboratoryEvidence
eyebrow="ИСХОДНОЕ ДОКАЗАТЕЛЬСТВО"
title="Запись, доставленная worker без потерь"
title="Контрольная запись: кадры и TrackGeometry, которые доставлял worker"
kind="recorded-replay"
resizable
>
@@ -67,40 +86,38 @@ export function E33Result({
</LaboratoryEvidence>
)}
result={(
<section className="laboratory-result-summary">
<header>
<div>
<span className="section-eyebrow">РЕЗУЛЬТАТ И ВЫВОД</span>
<h2>Полный replay выдержан в темпе источника</h2>
</div>
<StatusBadge tone="success">
{formatNumber(metrics.deliveredFrames, 0)} / {formatNumber(metrics.sourceFrames, 0)}
</StatusBadge>
</header>
<div className="laboratory-result-metrics">
<div>
<span>Эффективная частота</span>
<strong>{metrics.effectiveDeliveryFps.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} FPS</strong>
<small>скорость источника 1×</small>
</div>
<div>
<span>Result age p95</span>
<strong>{metrics.resultAgeP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс</strong>
<small>{(metrics.deadlineMissFraction * 100).toLocaleString("ru-RU", { maximumFractionDigits: 2 })}% deadline miss</small>
</div>
<div>
<span>Processing p95</span>
<strong>{metrics.processingP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс</strong>
<small>{metrics.releaseLagP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс release lag</small>
</div>
<div>
<span>Ресурсы p95</span>
<strong>{metrics.processRssP95Mib.toLocaleString("ru-RU", { maximumFractionDigits: 1 })} MiB</strong>
<small>GPU {metrics.gpuUtilizationP95Percent.toLocaleString("ru-RU", { maximumFractionDigits: 1 })}%</small>
</div>
</div>
<p>Очереди остались bounded, пропусков нет. Результат подтверждает только worker shadow и не даёт навигационных или safety-полномочий.</p>
</section>
<LaboratoryResultSummary
title="Worker выдержал запись; качество восприятия этим прогоном не проверялось"
status="Worker shadow принят"
statusTone="success"
metrics={[
{
label: "Доставка исходов",
value: `${formatNumber(metrics.deliveredFrames, 0)} / ${formatNumber(metrics.sourceFrames, 0)}`,
hint: "0 замен · 0 пропусков",
},
{
label: "Темп обработки",
value: `${metrics.effectiveDeliveryFps.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} FPS`,
hint: "исходная скорость 10 FPS · 1×",
},
{
label: "Возраст результата",
value: `${metrics.resultAgeP95Ms.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс p95`,
hint: `${metrics.resultAgeMaxMs.toLocaleString("ru-RU", { maximumFractionDigits: 3 })} мс max · лимит ${formatNumber(metrics.resultDeadlineMs, 0)} мс`,
},
{
label: "Память процесса",
value: `${metrics.processRssMaxMib.toLocaleString("ru-RU", { maximumFractionDigits: 1 })} MiB max`,
hint: `лимит ${formatNumber(metrics.processRssLimitMib, 0)} MiB`,
},
]}
conclusion={{
proved: "Финальная стадия публикации TrackGeometry на этом worker успевает за данной записью: очереди ограничены, каждый кадр учтён, задержка остаётся ниже заявленного deadline.",
notProved: "Прогон не измеряет точность машин, кубов или сегментации, не запускает заново detector/semantic pipeline и не доказывает переносимость на другую запись или монтаж сенсоров.",
decision: "Результат принимается только как диагностический worker shadow. Он допускает дальнейшие лабораторные испытания временного occupied/unknown слоя, но не включает команды, навигацию или safety.",
}}
/>
)}
/>
);
@@ -44,8 +44,11 @@ import type { WorkspaceRendererProps } from "../contracts";
import { E31Result } from "./E31Result";
import { E32Result } from "./E32Result";
import { E33Result } from "./E33Result";
import {
e28LaboratoryBrief, e29LaboratoryBrief,
e30LaboratoryBrief, PUBLISHED_LABORATORY_BRIEF,
} from "./laboratoryArchiveBriefs";
import { RecordedReplayEvidence } from "./RecordedReplayEvidence";
type LaboratoryWorkspaceProps = WorkspaceRendererProps & {
SpatialView: ComponentType<WorkspaceRendererProps>;
};
@@ -227,6 +230,7 @@ function E29LaboratoryResult({
value: "Read-only · hash verified",
},
]}
brief={e29LaboratoryBrief(formatNumber(semantic.total, 0))}
method={{
completeness: "complete",
executionClass: "hybrid",
@@ -411,6 +415,7 @@ function E30LaboratoryResult({
{ label: "Кейсов", value: formatNumber(result.itemCount, 0) },
{ label: "Контур", value: "Read-only · без LAB publish" },
]}
brief={e30LaboratoryBrief(formatNumber(result.itemCount, 0))}
/>
)}
evidence={(
@@ -483,6 +488,7 @@ function PublishedLaboratoryResult({
{ label: "Конфигурация", value: lab?.configSha256?.slice(0, 16) ?? "Не зафиксирована" },
{ label: "Контур", value: "Диагностика · без команд" },
]}
brief={PUBLISHED_LABORATORY_BRIEF}
method={publishedLaboratoryMethod(session)}
/>
)}
@@ -867,6 +873,10 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
{ label: "Режим", value: "Recorded-source-paced shadow" },
{ label: "Контур", value: "Read-only · hash verified" },
]}
brief={e28LaboratoryBrief(
formatNumber(e28Model?.metrics.frames.valid ?? 0, 0),
formatNumber(e28Model?.metrics.frames.total ?? 0, 0),
)}
method={{
completeness: "complete",
executionClass: e28Model?.method.executionClass ?? "deterministic",
@@ -0,0 +1,44 @@
import type {
LaboratoryBrief,
} from "../../components/laboratory/LaboratoryPresentation";
export function e28LaboratoryBrief(
validFrames: string,
totalFrames: string,
): LaboratoryBrief {
return {
question: "Можно ли устойчиво оценивать локальную поверхность и временные остатки на полной записи без изменения источника?",
approach: "LiDAR и pose воспроизводятся через bounded recorded-source-paced shadow; rolling local plane оценивает поверхность, препятствия и temporal residuals.",
principalResult: `${validFrames} из ${totalFrames} кадров прошли диагностическую обработку L2.6.`,
limitation: "Модель проверена только на RAVNOVES00 и не получает командных, навигационных или safety-полномочий.",
};
}
export function e29LaboratoryBrief(
observationCount: string,
): LaboratoryBrief {
return {
question: "Совпадают ли camera-семантика объекта и независимая LiDAR-геометрия в одном точном кадре?",
approach: "Camera сохраняет класс и идентичность, а локальная поверхность L2.6 отдельно проверяет дальность, occupied support и конфликт принадлежности точек.",
principalResult: `${observationCount} наблюдений разложены по согласованию camera и geometry без объявления пустого LiDAR свободным пространством.`,
limitation: "Результат зависит от точности исходной camera-семантики и локальной поверхности; он остаётся диагностическим и read-only.",
};
}
export function e30LaboratoryBrief(
itemCount: string,
): LaboratoryBrief {
return {
question: "Можно ли человеку проверить спорный camera/LiDAR-кейс по понятному совмещённому доказательству, а не по изолированному облаку точек?",
approach: "Каждый кейс связан с точным camera frame, переключаемой LiDAR-проекцией, frame-local индексами и синхронным 3D в одном viewer.",
principalResult: `${itemCount} кейсов доступны для camera-first проверки и отдельного разбора только неоднозначных исключений.`,
limitation: "Рабочее место не меняет исходный A2 и не превращает решение оператора в navigation или safety ground truth.",
};
}
export const PUBLISHED_LABORATORY_BRIEF: LaboratoryBrief = {
question: "Что было зафиксировано опубликованной лабораторной работой на её неизменяемом источнике?",
approach: "Mission Core открывает связанный source replay и проецирует только опубликованные method/provenance и метрики без реконструкции отсутствующих данных.",
principalResult: "Оператор получает исходное визуальное доказательство и зафиксированный результат одного LAB run в общей продуктовой форме.",
limitation: "Для legacy-результатов часть идентичностей и метрик могла не публиковаться; приложение их не выдумывает задним числом.",
};
@@ -96,15 +96,22 @@ function e33() {
},
metrics: {
source_frames: 4489,
source_available_frames: 3928,
source_unavailable_frames: 561,
delivered_frames: 4489,
input_superseded: 0,
result_superseded: 0,
effective_delivery_fps: 10.006,
source_span_seconds: 448.623,
deadline_miss_fraction: 0,
processing_p95_ms: 0.749,
release_lag_p95_ms: 0.461,
result_age_p95_ms: 2.668,
result_age_max_ms: 13.638,
result_deadline_ms: 100,
process_rss_p95_mib: 90.32,
process_rss_max_mib: 92.34,
process_rss_limit_mib: 1024,
gpu_utilization_p95_percent: 52,
gpu_visible: true,
work_queue_capacity: 2,
@@ -147,6 +154,8 @@ test("decodes E31, E32 and E33 from separate read-only catalogs", async () => {
assert.equal(decoded.e31.metrics.availableBindingCount, 3928);
assert.equal(decoded.e32.metrics.qualifiedPointsPublished, 2119302);
assert.equal(decoded.e33.metrics.deliveredFrames, 4489);
assert.equal(decoded.e33.metrics.sourceAvailableFrames, 3928);
assert.equal(decoded.e33.metrics.resultAgeMaxMs, 13.638);
assert.deepEqual(requests, [
{ input: "/api/v1/laboratory/e31/results?limit=1", method: "GET" },
{ input: "/api/v1/laboratory/e32/results?limit=1", method: "GET" },
@@ -75,6 +75,7 @@ test("laboratory UI is a bounded feature slice, not a central workspace branch",
);
const workspaceCss = await read("styles/workspaces.css");
const laboratoryCss = await read("styles/laboratory.css");
const laboratoryReportingCss = await read("styles/laboratory-reporting.css");
const e30Review = await read("workspaces/E30ReviewWorkspace.tsx");
const e30HumanReview = await read(
"workspaces/E30HumanReviewPanel.tsx",
@@ -94,6 +95,8 @@ test("laboratory UI is a bounded feature slice, not a central workspace branch",
assert.doesNotMatch(workspaceCss, /\.(?:lab-|laboratory-|e30-)/);
assert.match(laboratoryCss, /\.laboratory-work-template/);
assert.match(laboratoryCss, /\.e30-review-workspace/);
assert.match(laboratoryReportingCss, /\.laboratory-summary__brief/);
assert.match(laboratoryReportingCss, /\.laboratory-result-conclusion/);
assert.doesNotMatch(laboratory, /E30HumanReviewPanel/);
assert.match(e30Review, /<E30HumanReviewPanel/);
assert.doesNotMatch(e30Review, /E30EngineeringAuditPanel/);
@@ -109,6 +112,7 @@ test("central composition files cannot silently become monoliths again", async (
["workspaces/laboratory/LaboratoryArchiveWorkspace.tsx", 1_000],
["styles/workspaces.css", 4_350],
["styles/laboratory.css", 900],
["styles/laboratory-reporting.css", 100],
];
for (const [relativePath, maximumLines] of ratchets) {
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { readdir, readFile } from "node:fs/promises";
import { test } from "node:test";
const workspaceSourceUrl = new URL(
@@ -18,6 +18,18 @@ const laboratoryStylesUrl = new URL(
"../src/styles/laboratory.css",
import.meta.url,
);
const e33ResultUrl = new URL(
"../src/workspaces/laboratory/E33Result.tsx",
import.meta.url,
);
const workspacesUrl = new URL(
"../src/workspaces/Workspaces.tsx",
import.meta.url,
);
const laboratoryResultsUrl = new URL(
"../src/workspaces/laboratory/",
import.meta.url,
);
test("E30 uses one reusable evidence viewer with camera, 3D and expand controls", async () => {
const [workspaceSource, viewerSource, laboratoryStyles] = await Promise.all([
@@ -75,6 +87,60 @@ test("LAB product surface has a compact canonical summary and no roadmap footer"
assert.match(presentationSource, /export function LaboratorySummary/);
assert.match(presentationSource, /export function LaboratoryWorkTemplate/);
assert.match(
presentationSource,
/LABORATORY_REPORT_TEMPLATE_VERSION\s*=\s*\n?\s*"missioncore\.laboratory-report\/v1"/,
);
assert.match(presentationSource, /brief: LaboratoryBrief;/);
assert.doesNotMatch(presentationSource, /brief\?: LaboratoryBrief/);
assert.match(presentationSource, /data-template-contract=\{LABORATORY_REPORT_TEMPLATE_VERSION\}/);
assert.doesNotMatch(workspaceSource, /СЛЕДУЮЩИЙ GATE/);
assert.doesNotMatch(workspaceSource, /e30-review-workspace__taxonomy/);
});
test("bounded LAB result modules use the versioned shared report anatomy", async () => {
const resultNames = (await readdir(laboratoryResultsUrl))
.filter((name) => /^E\d+Result\.tsx$/.test(name))
.sort();
assert.ok(resultNames.length >= 3);
for (const resultName of resultNames) {
const source = await readFile(new URL(resultName, laboratoryResultsUrl), "utf8");
assert.match(source, /<LaboratoryWorkTemplate/, `${resultName} bypasses the LAB template`);
assert.match(source, /brief=\{\{/, `${resultName} omits the canonical four-part brief`);
assert.match(source, /<LaboratoryResultSummary/, `${resultName} invents a result layout`);
assert.doesNotMatch(
source,
/className="laboratory-(?:summary|result-summary)"/,
`${resultName} owns canonical report geometry`,
);
}
});
test("E33 explains the experiment, its method and its retained limits", async () => {
const [presentationSource, e33Source] = await Promise.all([
readFile(presentationSourceUrl, "utf8"),
readFile(e33ResultUrl, "utf8"),
]);
assert.match(presentationSource, /export interface LaboratoryBrief/);
assert.match(presentationSource, /export function LaboratoryConclusion/);
assert.match(presentationSource, /export function LaboratoryResultSummary/);
assert.match(e33Source, /Проверяли не качество распознавания объектов/);
assert.match(e33Source, /Recorded pacing \+ bounded latest-wins queues/);
assert.match(e33Source, /Детектор, semantic-модели и качество геометрии здесь не пересчитывались/);
assert.match(e33Source, /sourceUnavailableFrames/);
assert.match(e33Source, /не включает команды, навигацию или safety/);
assert.doesNotMatch(e33Source, /GPU \{metrics\.gpuUtilizationP95Percent/);
assert.doesNotMatch(e33Source, /name: result\.e32ResultId/);
});
test("the primary point-cloud viewer restores from fullscreen on Escape", async () => {
const workspacesSource = await readFile(workspacesUrl, "utf8");
assert.match(workspacesSource, /if \(!pointCloudFocused\) return;/);
assert.match(workspacesSource, /event\.key !== "Escape"/);
assert.match(workspacesSource, /observationLayout\.setFocusedSourceId\(null\)/);
assert.match(workspacesSource, /window\.addEventListener\("keydown", onKeyDown\)/);
});