feat(lab): visualize M4.8R3 system occupancy
This commit is contained in:
@@ -43,6 +43,7 @@ import {
|
||||
} from "./m48ObjectCentricQuality";
|
||||
import { fetchM48SmallStaticRegression } from "./m48SmallStaticRegression";
|
||||
import { fetchM48StaticOccupancyQualification } from "./m48StaticOccupancyQualification";
|
||||
import { fetchM48R3StaticOccupancyShadow } from "./m48r3StaticOccupancyShadow";
|
||||
import { fetchM48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
|
||||
import { fetchM48TRiskQualityResult } from "./m48tRiskQuality";
|
||||
|
||||
@@ -50,6 +51,7 @@ export type AdvancedLaboratoryWorkId =
|
||||
| "m48-object-centric-quality"
|
||||
| "m48-small-static-passage-regression"
|
||||
| "m48-static-occupancy-qualification"
|
||||
| "m48r3-static-occupancy-shadow"
|
||||
| "m48s-fixed-class-detector"
|
||||
| "m48t-risk-quality-temporal"
|
||||
| "m47-reference-graph-shadow"
|
||||
@@ -97,6 +99,7 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
|
||||
"m48-object-centric-quality",
|
||||
"m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification",
|
||||
"m48r3-static-occupancy-shadow",
|
||||
"m48s-fixed-class-detector",
|
||||
"m48t-risk-quality-temporal",
|
||||
"m47-reference-graph-shadow",
|
||||
@@ -139,6 +142,7 @@ const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
|
||||
"m48-object-centric-quality": "m48-object-quality-(?:pack|result)",
|
||||
"m48-small-static-passage-regression": "m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification": "m48-static-occupancy-qualification",
|
||||
"m48r3-static-occupancy-shadow": "m48r3-static-occupancy-shadow",
|
||||
"m48s-fixed-class-detector": "m48s-fixed-class-detector-lab",
|
||||
"m48t-risk-quality-temporal": "(?:m48t-risk-quality-temporal-lab|m48q-native-risk-quality-lab)",
|
||||
"m47-reference-graph-shadow": "m47-reference-graph-lab",
|
||||
@@ -189,6 +193,7 @@ export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
|
||||
m48: null,
|
||||
m48SmallStatic: null,
|
||||
m48StaticOccupancy: null,
|
||||
m48r3StaticOccupancy: null,
|
||||
m48s: null,
|
||||
m48t: null,
|
||||
m4Threat: null,
|
||||
@@ -318,6 +323,7 @@ export function advancedLaboratoryResultAvailable(
|
||||
return workId === "m48-object-centric-quality" ? results.m48 !== null
|
||||
: workId === "m48-small-static-passage-regression" ? results.m48SmallStatic !== null
|
||||
: workId === "m48-static-occupancy-qualification" ? results.m48StaticOccupancy !== null
|
||||
: workId === "m48r3-static-occupancy-shadow" ? results.m48r3StaticOccupancy !== null
|
||||
: workId === "m48s-fixed-class-detector" ? results.m48s !== null
|
||||
: workId === "m48t-risk-quality-temporal" ? results.m48t !== null
|
||||
: workId === "m47-reference-graph-shadow" ? results.m47Graph !== null
|
||||
@@ -378,6 +384,9 @@ export async function fetchAdvancedLaboratoryResult(
|
||||
} else if (workId === "m48-static-occupancy-qualification") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8R2 qualification identity не выбрана.");
|
||||
results.m48StaticOccupancy = await fetchM48StaticOccupancyQualification(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48r3-static-occupancy-shadow") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8R3 Worker shadow identity не выбрана.");
|
||||
results.m48r3StaticOccupancy = await fetchM48R3StaticOccupancyShadow(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48s-fixed-class-detector") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8S LAB identity не выбрана.");
|
||||
results.m48s = await fetchM48SFixedClassDetectorResult(resultId, { fetcher, signal });
|
||||
|
||||
@@ -37,6 +37,7 @@ import type { M47ReferenceGraphLabResult } from "./m47ReferenceGraph";
|
||||
import type { M48AdvancedResult } from "./m48ObjectCentricQuality";
|
||||
import type { M48SmallStaticRegressionResult } from "./m48SmallStaticRegression";
|
||||
import type { M48StaticOccupancyQualificationResult } from "./m48StaticOccupancyQualification";
|
||||
import type { M48R3StaticOccupancyShadowResult } from "./m48r3StaticOccupancyShadow";
|
||||
import type { M48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
|
||||
import type { M48TRiskQualityResult } from "./m48tRiskQuality";
|
||||
|
||||
@@ -45,6 +46,7 @@ export interface AdvancedLaboratoryResults {
|
||||
m48: M48AdvancedResult | null;
|
||||
m48SmallStatic: M48SmallStaticRegressionResult | null;
|
||||
m48StaticOccupancy: M48StaticOccupancyQualificationResult | null;
|
||||
m48r3StaticOccupancy: M48R3StaticOccupancyShadowResult | null;
|
||||
m48s: M48SFixedClassDetectorResult | null;
|
||||
m48t: M48TRiskQualityResult | null;
|
||||
m4Threat: M4ThreatReplayResult | null;
|
||||
|
||||
@@ -968,6 +968,7 @@ export async function fetchAdvancedLaboratoryResults({
|
||||
const e40 = settledCatalogValue(settled[8]);
|
||||
return {
|
||||
m47Graph: null, m48: null, m48SmallStatic: null, m48StaticOccupancy: null,
|
||||
m48r3StaticOccupancy: null,
|
||||
m48s: null, m48t: null, m4Threat: null,
|
||||
l3: null, l31: null, l32: null, l33: null,
|
||||
e31,
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
import type { LaboratoryFetch } from "./advancedResults";
|
||||
import type { M48Authority } from "./m48ObjectCentricQuality";
|
||||
|
||||
const RESULT_ID = /^m48r3-static-occupancy-shadow-[a-f0-9]{64}$/;
|
||||
|
||||
interface M48R3WorkerPerformance {
|
||||
admittedFrames: number;
|
||||
deliveredFrames: number;
|
||||
fps: number;
|
||||
worldP95Ms: number;
|
||||
worldP99Ms: number;
|
||||
geometryP95Ms: number;
|
||||
geometryP99Ms: number;
|
||||
}
|
||||
|
||||
export interface M48R3StaticOccupancyShadowResult {
|
||||
resultId: string;
|
||||
createdAtUtc: string;
|
||||
accepted: boolean;
|
||||
profile: {
|
||||
id: string;
|
||||
sha256: string;
|
||||
minimumPoints: number;
|
||||
};
|
||||
metrics: {
|
||||
frames: { expected: number; baselineDelivered: number; candidateDelivered: number };
|
||||
performance: {
|
||||
baseline: M48R3WorkerPerformance;
|
||||
candidate: M48R3WorkerPerformance;
|
||||
fpsRegressionFraction: number;
|
||||
worldStateP95DeltaMs: number;
|
||||
};
|
||||
occupancy: {
|
||||
baselineCellTotal: number;
|
||||
candidateCellTotal: number;
|
||||
addedCellTotal: number;
|
||||
lostCellTotal: number;
|
||||
baselineComponentTotal: number;
|
||||
candidateComponentTotal: number;
|
||||
meanCellGrowthFraction: number;
|
||||
meanComponentGrowthFraction: number;
|
||||
maximumAddedCellsPerFrame: number;
|
||||
maximumCandidateComponentsPerFrame: number;
|
||||
};
|
||||
provider: {
|
||||
additiveObservationCount: number;
|
||||
additiveVoxelCount: number;
|
||||
framesWithAdditions: number;
|
||||
additiveMeanMsPerFrame: number;
|
||||
peakTemporalComponents: number;
|
||||
peakRollingCells: number;
|
||||
};
|
||||
anchors: {
|
||||
count: number;
|
||||
criticalNearCount: number;
|
||||
criticalNearRecall: number;
|
||||
matchedCount: number;
|
||||
canonicalEngineeringRecall: number;
|
||||
separation: readonly {
|
||||
displayFrame: number;
|
||||
expectedMinimumComponents: number;
|
||||
observedComponents: number;
|
||||
passed: boolean;
|
||||
interpretation: string;
|
||||
}[];
|
||||
};
|
||||
capacityDropCount: number;
|
||||
falseFreeCount: number;
|
||||
};
|
||||
gates: Readonly<Record<string, boolean>>;
|
||||
decision: {
|
||||
state: "accepted-bounded-worker-shadow" | "rejected-bounded-worker-shadow";
|
||||
candidateAccepted: boolean;
|
||||
productionAccepted: false;
|
||||
nextAction: string;
|
||||
};
|
||||
limitations: readonly string[];
|
||||
authority: M48Authority;
|
||||
}
|
||||
|
||||
export interface M48R3StaticOccupancyCase {
|
||||
anchorId: string;
|
||||
displayFrame: number;
|
||||
sourceSequence: number;
|
||||
extentXyxy: readonly [number, number, number, number];
|
||||
distanceBand: "critical-near" | "approach" | "outside-qualified-bands";
|
||||
componentCount: number;
|
||||
matched: boolean;
|
||||
}
|
||||
|
||||
export class M48R3StaticOccupancyContractError extends Error {}
|
||||
|
||||
function objectValue(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new M48R3StaticOccupancyContractError(`${label}: ожидался объект.`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function text(value: unknown, label: string): string {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
throw new M48R3StaticOccupancyContractError(`${label}: ожидалась строка.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function numberValue(value: unknown, label: string): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
throw new M48R3StaticOccupancyContractError(`${label}: ожидалось число.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function integer(value: unknown, label: string): number {
|
||||
const parsed = numberValue(value, label);
|
||||
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
||||
throw new M48R3StaticOccupancyContractError(`${label}: ожидалось целое число.`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function bool(value: unknown, label: string): boolean {
|
||||
if (typeof value !== "boolean") {
|
||||
throw new M48R3StaticOccupancyContractError(`${label}: ожидался флаг.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function exact(value: unknown, expected: string | boolean, label: string): void {
|
||||
if (value !== expected) {
|
||||
throw new M48R3StaticOccupancyContractError(`${label}: нарушен контракт.`);
|
||||
}
|
||||
}
|
||||
|
||||
function sha256(value: unknown, label: string): string {
|
||||
const parsed = text(value, label);
|
||||
if (!/^[a-f0-9]{64}$/.test(parsed)) {
|
||||
throw new M48R3StaticOccupancyContractError(`${label}: неверный SHA-256.`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function extent(value: unknown): readonly [number, number, number, number] {
|
||||
if (!Array.isArray(value) || value.length !== 4) {
|
||||
throw new M48R3StaticOccupancyContractError("M4.8R3 extent: нарушен контракт.");
|
||||
}
|
||||
const parsed = value.map((item) => numberValue(item, "M4.8R3 extent"));
|
||||
return [parsed[0]!, parsed[1]!, parsed[2]!, parsed[3]!];
|
||||
}
|
||||
|
||||
function authority(value: unknown): M48Authority {
|
||||
const row = objectValue(value, "M4.8R3 authority");
|
||||
exact(row.mode, "replay-simulated", "M4.8R3 authority.mode");
|
||||
exact(row.physical_live, false, "M4.8R3 authority.physical_live");
|
||||
exact(row.commands_enabled, false, "M4.8R3 authority.commands_enabled");
|
||||
exact(row.actuation_allowed, false, "M4.8R3 authority.actuation_allowed");
|
||||
exact(
|
||||
row.navigation_or_safety_accepted,
|
||||
false,
|
||||
"M4.8R3 authority.navigation_or_safety_accepted",
|
||||
);
|
||||
return {
|
||||
mode: "replay-simulated",
|
||||
physicalLive: false,
|
||||
commandsEnabled: false,
|
||||
actuationAllowed: false,
|
||||
navigationOrSafetyAccepted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function workerPerformance(value: unknown, label: string): M48R3WorkerPerformance {
|
||||
const row = objectValue(value, label);
|
||||
return {
|
||||
admittedFrames: integer(row.admitted_frames, `${label}.admitted_frames`),
|
||||
deliveredFrames: integer(row.delivered_frames, `${label}.delivered_frames`),
|
||||
fps: numberValue(row.fps, `${label}.fps`),
|
||||
worldP95Ms: numberValue(row.world_p95_ms, `${label}.world_p95_ms`),
|
||||
worldP99Ms: numberValue(row.world_p99_ms, `${label}.world_p99_ms`),
|
||||
geometryP95Ms: numberValue(row.geometry_p95_ms, `${label}.geometry_p95_ms`),
|
||||
geometryP99Ms: numberValue(row.geometry_p99_ms, `${label}.geometry_p99_ms`),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchM48R3StaticOccupancyShadow(
|
||||
resultId: string,
|
||||
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<M48R3StaticOccupancyShadowResult> {
|
||||
if (!RESULT_ID.test(resultId)) {
|
||||
throw new M48R3StaticOccupancyContractError("M4.8R3 identity недопустима.");
|
||||
}
|
||||
const response = await fetcher(
|
||||
`/api/v1/laboratory/m48r3/static-occupancy/${encodeURIComponent(resultId)}`,
|
||||
{ method: "GET", headers: { Accept: "application/json" }, signal },
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new M48R3StaticOccupancyContractError(`M4.8R3 недоступен: HTTP ${response.status}.`);
|
||||
}
|
||||
const payload = objectValue(await response.json(), "M4.8R3");
|
||||
exact(
|
||||
payload.schema_version,
|
||||
"missioncore.m48r3-static-occupancy-shadow-view/v1",
|
||||
"M4.8R3 schema",
|
||||
);
|
||||
exact(payload.result_id, resultId, "M4.8R3 result");
|
||||
exact(payload.ground_truth, false, "M4.8R3 ground truth");
|
||||
const profile = objectValue(payload.profile, "M4.8R3 profile");
|
||||
const componentization = objectValue(profile.componentization, "M4.8R3 componentization");
|
||||
const metrics = objectValue(payload.metrics, "M4.8R3 metrics");
|
||||
const frames = objectValue(metrics.frames, "M4.8R3 frames");
|
||||
const performance = objectValue(metrics.performance, "M4.8R3 performance");
|
||||
const occupancy = objectValue(metrics.occupancy, "M4.8R3 occupancy");
|
||||
const provider = objectValue(metrics.provider, "M4.8R3 provider");
|
||||
const anchors = objectValue(metrics.assisted_anchors, "M4.8R3 anchors");
|
||||
if (!Array.isArray(anchors.separation)) {
|
||||
throw new M48R3StaticOccupancyContractError("M4.8R3 separation: ожидался массив.");
|
||||
}
|
||||
const gates = objectValue(payload.gates, "M4.8R3 gates");
|
||||
const parsedGates = Object.fromEntries(
|
||||
Object.entries(gates).map(([key, value]) => [key, bool(value, `M4.8R3 gate ${key}`)]),
|
||||
);
|
||||
const decision = objectValue(payload.decision, "M4.8R3 decision");
|
||||
const state = text(decision.state, "M4.8R3 decision.state");
|
||||
if (state !== "accepted-bounded-worker-shadow" && state !== "rejected-bounded-worker-shadow") {
|
||||
throw new M48R3StaticOccupancyContractError("M4.8R3 decision.state: неизвестное состояние.");
|
||||
}
|
||||
exact(decision.production_accepted, false, "M4.8R3 production acceptance");
|
||||
if (!Array.isArray(payload.limitations)) {
|
||||
throw new M48R3StaticOccupancyContractError("M4.8R3 limitations: ожидался массив.");
|
||||
}
|
||||
return {
|
||||
resultId,
|
||||
createdAtUtc: text(payload.created_at_utc, "M4.8R3 created"),
|
||||
accepted: bool(payload.accepted, "M4.8R3 accepted"),
|
||||
profile: {
|
||||
id: text(profile.id, "M4.8R3 profile id"),
|
||||
sha256: sha256(profile.sha256, "M4.8R3 profile sha"),
|
||||
minimumPoints: integer(componentization.minimum_points, "M4.8R3 minimum points"),
|
||||
},
|
||||
metrics: {
|
||||
frames: {
|
||||
expected: integer(frames.expected, "M4.8R3 expected frames"),
|
||||
baselineDelivered: integer(frames.baseline_delivered, "M4.8R3 baseline frames"),
|
||||
candidateDelivered: integer(frames.candidate_delivered, "M4.8R3 candidate frames"),
|
||||
},
|
||||
performance: {
|
||||
baseline: workerPerformance(performance.baseline, "M4.8R3 baseline"),
|
||||
candidate: workerPerformance(performance.candidate, "M4.8R3 candidate"),
|
||||
fpsRegressionFraction: numberValue(performance.fps_regression_fraction, "M4.8R3 FPS regression"),
|
||||
worldStateP95DeltaMs: numberValue(performance.world_state_p95_delta_ms, "M4.8R3 p95 delta"),
|
||||
},
|
||||
occupancy: {
|
||||
baselineCellTotal: integer(occupancy.baseline_cell_total, "M4.8R3 baseline cells"),
|
||||
candidateCellTotal: integer(occupancy.candidate_cell_total, "M4.8R3 candidate cells"),
|
||||
addedCellTotal: integer(occupancy.added_cell_total, "M4.8R3 added cells"),
|
||||
lostCellTotal: integer(occupancy.lost_cell_total, "M4.8R3 lost cells"),
|
||||
baselineComponentTotal: integer(occupancy.baseline_component_total, "M4.8R3 baseline components"),
|
||||
candidateComponentTotal: integer(occupancy.candidate_component_total, "M4.8R3 candidate components"),
|
||||
meanCellGrowthFraction: numberValue(occupancy.mean_cell_growth_fraction, "M4.8R3 cell growth"),
|
||||
meanComponentGrowthFraction: numberValue(occupancy.mean_component_growth_fraction, "M4.8R3 component growth"),
|
||||
maximumAddedCellsPerFrame: integer(occupancy.maximum_added_cells_per_frame, "M4.8R3 maximum added cells"),
|
||||
maximumCandidateComponentsPerFrame: integer(occupancy.maximum_candidate_components_per_frame, "M4.8R3 maximum components"),
|
||||
},
|
||||
provider: {
|
||||
additiveObservationCount: integer(provider.additive_observation_count, "M4.8R3 observations"),
|
||||
additiveVoxelCount: integer(provider.additive_voxel_count, "M4.8R3 voxels"),
|
||||
framesWithAdditions: integer(provider.frames_with_additions, "M4.8R3 added frames"),
|
||||
additiveMeanMsPerFrame: numberValue(provider.additive_mean_ms_per_frame, "M4.8R3 additive mean"),
|
||||
peakTemporalComponents: integer(provider.peak_temporal_components, "M4.8R3 temporal peak"),
|
||||
peakRollingCells: integer(provider.peak_rolling_cells, "M4.8R3 rolling peak"),
|
||||
},
|
||||
anchors: {
|
||||
count: integer(anchors.count, "M4.8R3 anchor count"),
|
||||
criticalNearCount: integer(anchors.critical_near_count, "M4.8R3 near anchors"),
|
||||
criticalNearRecall: numberValue(anchors.critical_near_recall, "M4.8R3 near recall"),
|
||||
matchedCount: integer(anchors.matched_count, "M4.8R3 matched anchors"),
|
||||
canonicalEngineeringRecall: numberValue(anchors.canonical_engineering_recall, "M4.8R3 canonical recall"),
|
||||
separation: anchors.separation.map((value, index) => {
|
||||
const row = objectValue(value, `M4.8R3 separation ${index}`);
|
||||
return {
|
||||
displayFrame: integer(row.display_frame, "M4.8R3 separation frame"),
|
||||
expectedMinimumComponents: integer(row.expected_minimum_components, "M4.8R3 expected components"),
|
||||
observedComponents: integer(row.observed_components, "M4.8R3 observed components"),
|
||||
passed: bool(row.passed, "M4.8R3 separation passed"),
|
||||
interpretation: text(row.interpretation, "M4.8R3 separation interpretation"),
|
||||
};
|
||||
}),
|
||||
},
|
||||
capacityDropCount: integer(metrics.capacity_drop_count, "M4.8R3 capacity drops"),
|
||||
falseFreeCount: integer(metrics.false_free_count, "M4.8R3 false free"),
|
||||
},
|
||||
gates: parsedGates,
|
||||
decision: {
|
||||
state,
|
||||
candidateAccepted: bool(decision.candidate_accepted, "M4.8R3 candidate accepted"),
|
||||
productionAccepted: false,
|
||||
nextAction: text(decision.next_action, "M4.8R3 next action"),
|
||||
},
|
||||
limitations: payload.limitations.map((value, index) => text(value, `M4.8R3 limitation ${index}`)),
|
||||
authority: authority(payload.authority),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchM48R3StaticOccupancyCases(
|
||||
resultId: string,
|
||||
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<readonly M48R3StaticOccupancyCase[]> {
|
||||
if (!RESULT_ID.test(resultId)) {
|
||||
throw new M48R3StaticOccupancyContractError("M4.8R3 identity недопустима.");
|
||||
}
|
||||
const response = await fetcher(
|
||||
`/api/v1/laboratory/m48r3/static-occupancy/${encodeURIComponent(resultId)}/cases`,
|
||||
{ method: "GET", headers: { Accept: "application/json" }, signal },
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new M48R3StaticOccupancyContractError(`M4.8R3 cases недоступны: HTTP ${response.status}.`);
|
||||
}
|
||||
const payload = objectValue(await response.json(), "M4.8R3 cases");
|
||||
exact(
|
||||
payload.schema_version,
|
||||
"missioncore.m48r3-static-occupancy-shadow-cases/v1",
|
||||
"M4.8R3 cases schema",
|
||||
);
|
||||
exact(payload.result_id, resultId, "M4.8R3 cases result");
|
||||
if (!Array.isArray(payload.cases) || payload.cases.length !== integer(payload.case_count, "M4.8R3 case count")) {
|
||||
throw new M48R3StaticOccupancyContractError("M4.8R3 cases: нарушен размер.");
|
||||
}
|
||||
return payload.cases.map((value, index) => {
|
||||
const row = objectValue(value, `M4.8R3 case ${index}`);
|
||||
const distanceBand = text(row.distance_band, "M4.8R3 distance band");
|
||||
if (distanceBand !== "critical-near" && distanceBand !== "approach" && distanceBand !== "outside-qualified-bands") {
|
||||
throw new M48R3StaticOccupancyContractError("M4.8R3 distance band: неизвестное значение.");
|
||||
}
|
||||
return {
|
||||
anchorId: text(row.anchor_id, "M4.8R3 anchor id"),
|
||||
displayFrame: integer(row.display_frame, "M4.8R3 display frame"),
|
||||
sourceSequence: integer(row.source_sequence, "M4.8R3 source sequence"),
|
||||
extentXyxy: extent(row.extent_xyxy),
|
||||
distanceBand,
|
||||
componentCount: integer(row.component_count, "M4.8R3 component count"),
|
||||
matched: bool(row.matched, "M4.8R3 matched"),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export type M4ThreatDecision = "threat" | "not-threat" | "unknown";
|
||||
export type M4ThreatMotion = "moving" | "stationary" | "unknown";
|
||||
export type M4OccupancySource = "baseline" | "mixed" | "additive-low-step";
|
||||
export type M4Point3 = readonly [number, number, number];
|
||||
export type M4Matrix3 = readonly [M4Point3, M4Point3, M4Point3];
|
||||
|
||||
@@ -76,6 +77,7 @@ export interface M4ThreatMetricVisual {
|
||||
centroidBodyXyzM: M4Point3;
|
||||
cellCentersBodyXyzM: readonly M4Point3[];
|
||||
assessment: M4ThreatAssessment;
|
||||
occupancySource: M4OccupancySource;
|
||||
}
|
||||
|
||||
export interface M4ThreatCameraProposal {
|
||||
@@ -174,6 +176,7 @@ export interface M4ThreatTimeline {
|
||||
cameraPointWindowSeconds: number;
|
||||
cameraPointSampleLimit: number;
|
||||
worldStateDelivery: "source-paced-latest-wins" | null;
|
||||
occupancyProvenanceDelivery: "baseline-versus-additive-component-diff" | null;
|
||||
worldStateFrameCount: number;
|
||||
supersededFrameCount: number;
|
||||
sourceRepresentationId: "registered-map-increment-v1";
|
||||
@@ -331,6 +334,9 @@ function parseMetricVisual(value: unknown): M4ThreatMetricVisual {
|
||||
) {
|
||||
throw new M4ThreatContractError("M4.6 temporal state: неизвестное состояние.");
|
||||
}
|
||||
const occupancySource = item.occupancy_source === undefined
|
||||
? "baseline"
|
||||
: memberOccupancySource(item.occupancy_source);
|
||||
return {
|
||||
componentId: text(item.component_id, "M4.6 visual component"),
|
||||
state,
|
||||
@@ -340,9 +346,17 @@ function parseMetricVisual(value: unknown): M4ThreatMetricVisual {
|
||||
(point) => vector(point, 3, "M4.6 cell") as [number, number, number],
|
||||
),
|
||||
assessment: parseAssessment(item.assessment),
|
||||
occupancySource,
|
||||
};
|
||||
}
|
||||
|
||||
function memberOccupancySource(value: unknown): M4OccupancySource {
|
||||
if (value !== "baseline" && value !== "mixed" && value !== "additive-low-step") {
|
||||
throw new M4ThreatContractError("M4.8R3 occupancy source: неизвестное состояние.");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export async function fetchM4ThreatReplayResult({
|
||||
resultId: requestedResultId,
|
||||
fetcher = fetch,
|
||||
@@ -683,6 +697,13 @@ export async function fetchM4ThreatTimeline(
|
||||
"source-paced-latest-wins",
|
||||
"M4.6 world-state delivery",
|
||||
),
|
||||
occupancyProvenanceDelivery: payload.occupancy_provenance_delivery == null
|
||||
? null
|
||||
: exact(
|
||||
payload.occupancy_provenance_delivery,
|
||||
"baseline-versus-additive-component-diff",
|
||||
"M4.8R3 occupancy provenance",
|
||||
),
|
||||
worldStateFrameCount: payload.world_state_frame_count === undefined
|
||||
? frameCount
|
||||
: integer(payload.world_state_frame_count, "M4.6 world-state frames"),
|
||||
|
||||
Reference in New Issue
Block a user