feat(perception): add E34 temporal occupied layer

This commit is contained in:
DCCONSTRUCTIONS
2026-07-27 16:55:40 +03:00
parent 95c6540691
commit 621084fcd6
22 changed files with 4732 additions and 104 deletions
@@ -93,6 +93,7 @@ export interface AdvancedLaboratoryResults {
e31: E31LaboratoryResult | null;
e32: E32LaboratoryResult | null;
e33: E33LaboratoryResult | null;
e34: E34TemporalLayerResult | null;
}
export class AdvancedLaboratoryContractError extends Error {
@@ -371,10 +372,15 @@ export async function fetchAdvancedLaboratoryResults({
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {}): Promise<AdvancedLaboratoryResults> {
const [e31, e32, e33] = await Promise.all([
const [e31, e32, e33, e34] = await Promise.all([
fetchOne("/api/v1/laboratory/e31/results?limit=1", parseE31, fetcher, signal),
fetchOne("/api/v1/laboratory/e32/results?limit=1", parseE32, fetcher, signal),
fetchOne("/api/v1/laboratory/e33/results?limit=1", parseE33, fetcher, signal),
fetchE34TemporalLayerResult({ fetcher, signal }),
]);
return { e31, e32, e33 };
return { e31, e32, e33, e34 };
}
import {
fetchE34TemporalLayerResult,
type E34TemporalLayerResult,
} from "./e34TemporalLayer";
@@ -0,0 +1,480 @@
export type E34TemporalState = "current" | "held" | "expired";
export type E34OccupancyState = "occupied" | "unknown";
export type E34OwnerKind = "camera-track" | "geometry-cluster";
export type E34Point3 = readonly [number, number, number];
export interface E34TemporalHistoryPoint {
frameIndex: number;
sessionSeconds: number;
centroidMapXyzM: E34Point3;
}
export interface E34TemporalComponent {
temporalId: number;
state: E34TemporalState;
occupancyState: E34OccupancyState;
ownerKind: E34OwnerKind;
centroidMapXyzM: E34Point3;
lastObservedAgeSeconds: number;
associationReason: string;
history: readonly E34TemporalHistoryPoint[];
}
export interface E34TemporalReviewFrame {
frameIndex: number;
sessionSeconds: number;
sourceAvailable: boolean;
layerState: "current" | "held" | "unknown";
counts: {
current: number;
held: number;
expired: number;
};
cellCentersMapXyzM: readonly E34Point3[];
components: readonly E34TemporalComponent[];
}
export interface E34TemporalLayerResult {
resultId: string;
createdAtUtc: string | null;
sourceSessionId: string;
status: "accepted-bounded-occupied-unknown-temporal-layer";
e32ResultId: string;
e33ResultId: string;
profileId: string;
pipelineId: string;
coordinateFrame: "map";
configuration: {
voxelSizeM: number;
occupiedTtlSeconds: number;
maximumActiveComponents: number;
maximumCellsPerComponent: number;
geometryMaximumGapSeconds: number;
geometryMaximumCentroidDistanceM: number;
};
metrics: {
sourceFrames: number;
processedFrames: number;
framesWithCurrentLayer: number;
heldOnlyFrames: number;
unknownEmptyFrames: number;
createdComponents: number;
cameraCreatedComponents: number;
geometryCreatedComponents: number;
geometryReassociatedComponents: number;
exactCameraAssociations: number;
geometrySpatialReassociations: number;
heldPublications: number;
expiredComponents: number;
peakActiveComponents: number;
maximumObservedSpanSeconds: number;
continuityFraction: number;
geometryOnlyReassociationFraction: number;
e32CurrentPointRows: number;
e34ConsumedCurrentPointRows: number;
currentCellRows: number;
heldCellRows: number;
storedCellRows: number;
freeCellRows: number;
peakCellsPerComponent: number;
maximumHeldAgeSeconds: number;
maximumExpiryDelaySeconds: number;
maximumReplayMaterializationDelaySeconds: number;
mapFrameJumpCandidates: number;
frameProcessingP95Ms: number;
buildElapsedMs: number;
};
reviewFrames: readonly E34TemporalReviewFrame[];
access: "read-only";
}
type LaboratoryFetch = (
input: RequestInfo | URL,
init?: RequestInit,
) => Promise<Response>;
class E34TemporalLayerContractError extends Error {
constructor(message: string) {
super(message);
this.name = "E34TemporalLayerContractError";
}
}
function record(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new E34TemporalLayerContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function list(value: unknown, label: string): readonly unknown[] {
if (!Array.isArray(value)) {
throw new E34TemporalLayerContractError(`${label}: ожидался массив.`);
}
return value;
}
function stringValue(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new E34TemporalLayerContractError(`${label}: ожидалась строка.`);
}
return value;
}
function optionalString(value: unknown, label: string): string | null {
return value === null ? null : stringValue(value, label);
}
function finiteNumber(value: unknown, label: string): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new E34TemporalLayerContractError(`${label}: ожидалось число.`);
}
return value;
}
function numberValue(value: unknown, label: string): number {
const parsed = finiteNumber(value, label);
if (parsed < 0) {
throw new E34TemporalLayerContractError(`${label}: число отрицательно.`);
}
return parsed;
}
function integerValue(value: unknown, label: string): number {
const parsed = numberValue(value, label);
if (!Number.isSafeInteger(parsed)) {
throw new E34TemporalLayerContractError(`${label}: ожидалось целое число.`);
}
return parsed;
}
function booleanValue(value: unknown, label: string): boolean {
if (typeof value !== "boolean") {
throw new E34TemporalLayerContractError(`${label}: ожидался boolean.`);
}
return value;
}
function exactString<T extends string>(
value: unknown,
expected: T,
label: string,
): T {
if (value !== expected) {
throw new E34TemporalLayerContractError(`${label}: неверное значение.`);
}
return expected;
}
function oneOf<T extends string>(
value: unknown,
expected: readonly T[],
label: string,
): T {
if (typeof value !== "string" || !expected.includes(value as T)) {
throw new E34TemporalLayerContractError(`${label}: неверное значение.`);
}
return value as T;
}
function contentId(value: unknown, prefix: string, label: string): string {
const parsed = stringValue(value, label);
if (!new RegExp(`^${prefix}-[a-f0-9]{64}$`).test(parsed)) {
throw new E34TemporalLayerContractError(`${label}: неверный content id.`);
}
return parsed;
}
function point(value: unknown, label: string): E34Point3 {
const coordinates = list(value, label);
if (coordinates.length !== 3) {
throw new E34TemporalLayerContractError(`${label}: ожидались XYZ.`);
}
return [
finiteNumber(coordinates[0], `${label}[0]`),
finiteNumber(coordinates[1], `${label}[1]`),
finiteNumber(coordinates[2], `${label}[2]`),
];
}
function parseHistory(value: unknown, label: string): E34TemporalHistoryPoint {
const item = record(value, label);
return {
frameIndex: integerValue(item.frame_index, `${label}.frame_index`),
sessionSeconds: numberValue(
item.session_seconds,
`${label}.session_seconds`,
),
centroidMapXyzM: point(
item.centroid_map_xyz_m,
`${label}.centroid_map_xyz_m`,
),
};
}
function parseComponent(value: unknown, label: string): E34TemporalComponent {
const item = record(value, label);
return {
temporalId: integerValue(item.temporal_id, `${label}.temporal_id`),
state: oneOf(
item.state,
["current", "held", "expired"] as const,
`${label}.state`,
),
occupancyState: oneOf(
item.occupancy_state,
["occupied", "unknown"] as const,
`${label}.occupancy_state`,
),
ownerKind: oneOf(
item.owner_kind,
["camera-track", "geometry-cluster"] as const,
`${label}.owner_kind`,
),
centroidMapXyzM: point(
item.centroid_map_xyz_m,
`${label}.centroid_map_xyz_m`,
),
lastObservedAgeSeconds: numberValue(
item.last_observed_age_seconds,
`${label}.last_observed_age_seconds`,
),
associationReason: stringValue(
item.association_reason,
`${label}.association_reason`,
),
history: list(item.history_tail, `${label}.history_tail`).map(
(entry, index) => parseHistory(entry, `${label}.history_tail[${index}]`),
),
};
}
function parseReviewFrame(
value: unknown,
label: string,
): E34TemporalReviewFrame {
const item = record(value, label);
const counts = record(item.counts, `${label}.counts`);
return {
frameIndex: integerValue(item.frame_index, `${label}.frame_index`),
sessionSeconds: numberValue(
item.session_seconds,
`${label}.session_seconds`,
),
sourceAvailable: booleanValue(
item.source_available,
`${label}.source_available`,
),
layerState: oneOf(
item.layer_state,
["current", "held", "unknown"] as const,
`${label}.layer_state`,
),
counts: {
current: integerValue(counts.current, `${label}.counts.current`),
held: integerValue(counts.held, `${label}.counts.held`),
expired: integerValue(counts.expired, `${label}.counts.expired`),
},
cellCentersMapXyzM: list(
item.cell_centers_map_xyz_m,
`${label}.cell_centers_map_xyz_m`,
).map((entry, index) => point(
entry,
`${label}.cell_centers_map_xyz_m[${index}]`,
)),
components: list(item.components, `${label}.components`).map(
(entry, index) => parseComponent(
entry,
`${label}.components[${index}]`,
),
),
};
}
function diagnosticAuthority(value: unknown, label: string): void {
const authority = record(value, label);
if (
authority.commands_enabled !== false
|| authority.navigation_or_safety_accepted !== false
) {
throw new E34TemporalLayerContractError(
`${label}: запрещённые полномочия.`,
);
}
}
function metricRecord(
value: Record<string, unknown>,
): E34TemporalLayerResult["metrics"] {
const metric = (key: string) => numberValue(
value[key],
`E34.metrics.${key}`,
);
const integer = (key: string) => integerValue(
value[key],
`E34.metrics.${key}`,
);
return {
sourceFrames: integer("source_frames"),
processedFrames: integer("processed_frames"),
framesWithCurrentLayer: integer("frames_with_current_layer"),
heldOnlyFrames: integer("held_only_frames"),
unknownEmptyFrames: integer("unknown_empty_frames"),
createdComponents: integer("created_components"),
cameraCreatedComponents: integer("camera_created_components"),
geometryCreatedComponents: integer("geometry_created_components"),
geometryReassociatedComponents: integer("geometry_reassociated_components"),
exactCameraAssociations: integer("exact_camera_associations"),
geometrySpatialReassociations: integer("geometry_spatial_reassociations"),
heldPublications: integer("held_publications"),
expiredComponents: integer("expired_components"),
peakActiveComponents: integer("peak_active_components"),
maximumObservedSpanSeconds: metric("maximum_observed_span_seconds"),
continuityFraction: metric("continuity_fraction"),
geometryOnlyReassociationFraction: metric(
"geometry_only_reassociation_fraction",
),
e32CurrentPointRows: integer("e32_current_point_rows"),
e34ConsumedCurrentPointRows: integer("e34_consumed_current_point_rows"),
currentCellRows: integer("current_cell_rows"),
heldCellRows: integer("held_cell_rows"),
storedCellRows: integer("stored_cell_rows"),
freeCellRows: integer("free_cell_rows"),
peakCellsPerComponent: integer("peak_cells_per_component"),
maximumHeldAgeSeconds: metric("maximum_held_age_seconds"),
maximumExpiryDelaySeconds: metric("maximum_expiry_delay_seconds"),
maximumReplayMaterializationDelaySeconds: metric(
"maximum_replay_materialization_delay_seconds",
),
mapFrameJumpCandidates: integer("map_frame_jump_candidates"),
frameProcessingP95Ms: metric("frame_processing_p95_ms"),
buildElapsedMs: metric("build_elapsed_ms"),
};
}
function parseResult(value: unknown): E34TemporalLayerResult {
const item = record(value, "E34");
const configuration = record(item.configuration, "E34.configuration");
const review = record(item.review, "E34.review");
const acceptance = record(item.acceptance, "E34.acceptance");
diagnosticAuthority(item.authority, "E34.authority");
if (acceptance.accepted !== true) {
throw new E34TemporalLayerContractError("E34.acceptance: результат отклонён.");
}
exactString(
review.schema_version,
"missioncore.e34-temporal-review-timeline/v1",
"E34.review.schema_version",
);
return {
resultId: contentId(
item.result_id,
"e34-temporal-occupied",
"E34.result_id",
),
createdAtUtc: optionalString(item.created_at_utc, "E34.created_at_utc"),
sourceSessionId: stringValue(
item.source_session_id,
"E34.source_session_id",
),
status: exactString(
item.status,
"accepted-bounded-occupied-unknown-temporal-layer",
"E34.status",
),
e32ResultId: contentId(
item.e32_result_id,
"e32-track-geometry",
"E34.e32_result_id",
),
e33ResultId: contentId(
item.e33_result_id,
"e33-worker-shadow",
"E34.e33_result_id",
),
profileId: stringValue(item.profile_id, "E34.profile_id"),
pipelineId: stringValue(item.pipeline_id, "E34.pipeline_id"),
coordinateFrame: exactString(
item.coordinate_frame,
"map",
"E34.coordinate_frame",
),
configuration: {
voxelSizeM: numberValue(
configuration.voxel_size_m,
"E34.configuration.voxel_size_m",
),
occupiedTtlSeconds: numberValue(
configuration.occupied_ttl_seconds,
"E34.configuration.occupied_ttl_seconds",
),
maximumActiveComponents: integerValue(
configuration.maximum_active_components,
"E34.configuration.maximum_active_components",
),
maximumCellsPerComponent: integerValue(
configuration.maximum_cells_per_component,
"E34.configuration.maximum_cells_per_component",
),
geometryMaximumGapSeconds: numberValue(
configuration.geometry_maximum_gap_seconds,
"E34.configuration.geometry_maximum_gap_seconds",
),
geometryMaximumCentroidDistanceM: numberValue(
configuration.geometry_maximum_centroid_distance_m,
"E34.configuration.geometry_maximum_centroid_distance_m",
),
},
metrics: metricRecord(record(item.metrics, "E34.metrics")),
reviewFrames: list(review.frames, "E34.review.frames").map(
(entry, index) => parseReviewFrame(
entry,
`E34.review.frames[${index}]`,
),
),
access: exactString(item.access, "read-only", "E34.access"),
};
}
export async function fetchE34TemporalLayerResult({
fetcher = fetch,
signal,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {}): Promise<E34TemporalLayerResult | null> {
const response = await fetcher(
"/api/v1/laboratory/e34/results?limit=1",
{
method: "GET",
headers: { Accept: "application/json" },
signal,
},
);
if (!response.ok) {
throw new E34TemporalLayerContractError(
`Каталог LAB E34 недоступен: HTTP ${response.status}.`,
);
}
const catalog = record(await response.json(), "Каталог LAB E34");
exactString(
catalog.schema_version,
"missioncore.laboratory-advanced-catalog/v1",
"Каталог LAB E34.schema_version",
);
booleanValue(catalog.configured, "Каталог LAB E34.configured");
integerValue(catalog.candidate_total, "Каталог LAB E34.candidate_total");
integerValue(catalog.invalid_total, "Каталог LAB E34.invalid_total");
exactString(
catalog.access,
"read-only",
"Каталог LAB E34.access",
);
const items = list(catalog.items, "Каталог LAB E34.items");
if (items.length > 1) {
throw new E34TemporalLayerContractError(
"Каталог LAB E34: нарушен limit=1.",
);
}
return items.length ? parseResult(items[0]) : null;
}