feat(ui): stabilize shared M4.8 review viewer
This commit is contained in:
@@ -38,8 +38,14 @@ import { fetchE46JRawFisheyeRealtime } from "./e46jRawFisheyeRealtime";
|
||||
import { fetchE47SemanticSlamResult } from "./e47SemanticSlam";
|
||||
import { fetchM4ThreatReplayResult } from "./m4ReplayThreat";
|
||||
import { fetchM47ReferenceGraphLab } from "./m47ReferenceGraph";
|
||||
import {
|
||||
fetchM48LifecycleResult,
|
||||
} from "./m48ObjectCentricQuality";
|
||||
import { fetchM48SmallStaticRegression } from "./m48SmallStaticRegression";
|
||||
|
||||
export type AdvancedLaboratoryWorkId =
|
||||
| "m48-object-centric-quality"
|
||||
| "m48-small-static-passage-regression"
|
||||
| "m47-reference-graph-shadow"
|
||||
| "m4-replay-threat"
|
||||
| "l3-pointpillars-visual-audit"
|
||||
@@ -82,6 +88,8 @@ export interface AdvancedLaboratoryIndexItem {
|
||||
}
|
||||
|
||||
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
|
||||
"m48-object-centric-quality",
|
||||
"m48-small-static-passage-regression",
|
||||
"m47-reference-graph-shadow",
|
||||
"m4-replay-threat",
|
||||
"l3-pointpillars-visual-audit",
|
||||
@@ -119,6 +127,8 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
|
||||
];
|
||||
|
||||
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",
|
||||
"m47-reference-graph-shadow": "m47-reference-graph-lab",
|
||||
"m4-replay-threat": "m4-threat-replay",
|
||||
"l3-pointpillars-visual-audit": "l3-pointpillars-visual-audit",
|
||||
@@ -164,6 +174,8 @@ export function isAdvancedLaboratoryWorkId(
|
||||
export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
|
||||
return {
|
||||
m47Graph: null,
|
||||
m48: null,
|
||||
m48SmallStatic: null,
|
||||
m4Threat: null,
|
||||
l3: null,
|
||||
l31: null,
|
||||
@@ -288,7 +300,9 @@ export function advancedLaboratoryResultAvailable(
|
||||
workId: AdvancedLaboratoryWorkId,
|
||||
results: AdvancedLaboratoryResults,
|
||||
): boolean {
|
||||
return workId === "m47-reference-graph-shadow" ? results.m47Graph !== null
|
||||
return workId === "m48-object-centric-quality" ? results.m48 !== null
|
||||
: workId === "m48-small-static-passage-regression" ? results.m48SmallStatic !== null
|
||||
: workId === "m47-reference-graph-shadow" ? results.m47Graph !== null
|
||||
: workId === "m4-replay-threat" ? results.m4Threat !== null
|
||||
: workId === "l3-pointpillars-visual-audit" ? results.l3 !== null
|
||||
: workId === "l31-pointpillars-ravnoves" ? results.l31 !== null
|
||||
@@ -337,7 +351,13 @@ export async function fetchAdvancedLaboratoryResult(
|
||||
} = {},
|
||||
): Promise<AdvancedLaboratoryResults> {
|
||||
const results = emptyAdvancedLaboratoryResults();
|
||||
if (workId === "m47-reference-graph-shadow") {
|
||||
if (workId === "m48-object-centric-quality") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8 lifecycle evidence identity не выбрана.");
|
||||
results.m48 = await fetchM48LifecycleResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48-small-static-passage-regression") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8R1 regression identity не выбрана.");
|
||||
results.m48SmallStatic = await fetchM48SmallStaticRegression(resultId, { fetcher, signal });
|
||||
} else if (workId === "m47-reference-graph-shadow") {
|
||||
if (!resultId) {
|
||||
throw new AdvancedLaboratoryContractError("M4.7 LAB identity не выбрана.");
|
||||
}
|
||||
|
||||
@@ -34,9 +34,13 @@ import type { E46JRawFisheyeRealtimeResult } from "./e46jRawFisheyeRealtime";
|
||||
import type { E47SemanticSlamResult } from "./e47SemanticSlam";
|
||||
import type { M4ThreatReplayResult } from "./m4ReplayThreat";
|
||||
import type { M47ReferenceGraphLabResult } from "./m47ReferenceGraph";
|
||||
import type { M48AdvancedResult } from "./m48ObjectCentricQuality";
|
||||
import type { M48SmallStaticRegressionResult } from "./m48SmallStaticRegression";
|
||||
|
||||
export interface AdvancedLaboratoryResults {
|
||||
m47Graph: M47ReferenceGraphLabResult | null;
|
||||
m48: M48AdvancedResult | null;
|
||||
m48SmallStatic: M48SmallStaticRegressionResult | null;
|
||||
m4Threat: M4ThreatReplayResult | null;
|
||||
l3: L3PointPillarsVisualAuditResult | null;
|
||||
l31: L31PointPillarsRavnovesResult | null;
|
||||
|
||||
@@ -967,7 +967,7 @@ export async function fetchAdvancedLaboratoryResults({
|
||||
const e39 = settledCatalogValue(settled[7]);
|
||||
const e40 = settledCatalogValue(settled[8]);
|
||||
return {
|
||||
m47Graph: null, m4Threat: null,
|
||||
m47Graph: null, m48: null, m48SmallStatic: null, m4Threat: null,
|
||||
l3: null, l31: null,
|
||||
l32: null,
|
||||
l33: null,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,364 @@
|
||||
import type { LaboratoryFetch } from "./advancedResults";
|
||||
import type {
|
||||
M48Authority,
|
||||
M48Freshness,
|
||||
M48GeometryAssociation,
|
||||
M48Motion,
|
||||
M48Threat,
|
||||
M48Visibility,
|
||||
} from "./m48ObjectCentricQuality";
|
||||
|
||||
const RESULT_ID = /^m48-small-static-passage-regression-[a-f0-9]{64}$/;
|
||||
const PACK_ID = /^m48-object-quality-pack-[a-f0-9]{64}$/;
|
||||
const ANCHOR_ID = /^anchor-[a-f0-9]{24}$/;
|
||||
const SAFE_URL = /^\/api\/v1\/laboratory\/m48\/[A-Za-z0-9/_?&=.%:-]+$/;
|
||||
|
||||
export interface M48SmallStaticRegressionMetrics {
|
||||
assistedAnchorCount: number;
|
||||
assistedTrackletCount: number;
|
||||
anchorClipCount: number;
|
||||
requiresAvoidanceOrClearanceCount: number;
|
||||
workerRecalledAnchorCount: number;
|
||||
workerMissedAnchorCount: number;
|
||||
assistedAnchorRecall: number;
|
||||
extentIouThreshold: number;
|
||||
minimumAssistedAnchorRecall: number;
|
||||
}
|
||||
|
||||
export interface M48SmallStaticRegressionResult {
|
||||
resultId: string;
|
||||
packId: string;
|
||||
createdAtUtc: string;
|
||||
runLabel: "M4.8R1";
|
||||
pipelineId: "m48-class-free-object-quality/v1";
|
||||
experimentId: "m48-small-static-passage-regression/v1";
|
||||
accepted: boolean;
|
||||
metrics: M48SmallStaticRegressionMetrics;
|
||||
gates: {
|
||||
anchorSetNonEmpty: boolean;
|
||||
developmentAnchorRecallTarget: boolean;
|
||||
independentTruthAvailable: false;
|
||||
};
|
||||
decision: {
|
||||
state: "accepted-development-regression-baseline" | "failed-development-regression-baseline";
|
||||
summary: string;
|
||||
nextAction: string;
|
||||
};
|
||||
groundTruth: false;
|
||||
independentTruth: false;
|
||||
authority: M48Authority;
|
||||
}
|
||||
|
||||
export interface M48SmallStaticRegressionCaseSummary {
|
||||
anchorId: string;
|
||||
clipId: string;
|
||||
sequence: number;
|
||||
requiresAvoidanceOrClearance: boolean;
|
||||
workerCandidateCount: number;
|
||||
bestIou: number;
|
||||
matchedAtThreshold: boolean;
|
||||
outcome: "recalled" | "missed-assisted-anchor";
|
||||
}
|
||||
|
||||
export interface M48SmallStaticRegressionObject {
|
||||
predictionId: string;
|
||||
extentXyxy: readonly [number, number, number, number];
|
||||
geometryAssociation: M48GeometryAssociation;
|
||||
freshness: M48Freshness;
|
||||
motion: M48Motion;
|
||||
threat: M48Threat;
|
||||
}
|
||||
|
||||
export interface M48SmallStaticRegressionCase {
|
||||
resultId: string;
|
||||
packId: string;
|
||||
anchor: {
|
||||
anchorId: string;
|
||||
clipId: string;
|
||||
objectId: string;
|
||||
sequence: number;
|
||||
extentXyxy: readonly [number, number, number, number];
|
||||
visibility: M48Visibility;
|
||||
geometryAssociation: M48GeometryAssociation;
|
||||
freshness: M48Freshness;
|
||||
motion: M48Motion;
|
||||
threat: M48Threat;
|
||||
requiresAvoidanceOrClearance: boolean;
|
||||
};
|
||||
comparison: M48SmallStaticRegressionCaseSummary & {
|
||||
sourceTimeNs: number;
|
||||
anchorExtentXyxy: readonly [number, number, number, number];
|
||||
workerObjects: readonly M48SmallStaticRegressionObject[];
|
||||
bestPredictionId: string | null;
|
||||
extentIouThreshold: number;
|
||||
};
|
||||
cameraUrl: string | null;
|
||||
spatialUrl: string | null;
|
||||
groundTruth: false;
|
||||
authority: M48Authority;
|
||||
}
|
||||
|
||||
export class M48SmallStaticRegressionContractError extends Error {}
|
||||
|
||||
function objectValue(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new M48SmallStaticRegressionContractError(`${label}: ожидался объект.`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function arrayValue(value: unknown, label: string): readonly unknown[] {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new M48SmallStaticRegressionContractError(`${label}: ожидался список.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function exact(value: unknown, expected: string | boolean, label: string): void {
|
||||
if (value !== expected) {
|
||||
throw new M48SmallStaticRegressionContractError(`${label}: нарушен контракт.`);
|
||||
}
|
||||
}
|
||||
|
||||
function textValue(value: unknown, label: string): string {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
throw new M48SmallStaticRegressionContractError(`${label}: ожидалась строка.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function numberValue(value: unknown, label: string): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
throw new M48SmallStaticRegressionContractError(`${label}: ожидалось число.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function integerValue(value: unknown, label: string): number {
|
||||
const result = numberValue(value, label);
|
||||
if (!Number.isInteger(result) || result < 0) {
|
||||
throw new M48SmallStaticRegressionContractError(`${label}: ожидалось целое число.`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function booleanValue(value: unknown, label: string): boolean {
|
||||
if (typeof value !== "boolean") {
|
||||
throw new M48SmallStaticRegressionContractError(`${label}: ожидался флаг.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function extentValue(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): readonly [number, number, number, number] {
|
||||
const rows = arrayValue(value, label).map((item, index) => numberValue(item, `${label}[${index}]`));
|
||||
if (
|
||||
rows.length !== 4
|
||||
|| rows.some((item) => item < 0 || item > 1)
|
||||
|| rows[0]! >= rows[2]!
|
||||
|| rows[1]! >= rows[3]!
|
||||
) {
|
||||
throw new M48SmallStaticRegressionContractError(`${label}: рамка недопустима.`);
|
||||
}
|
||||
return rows as unknown as readonly [number, number, number, number];
|
||||
}
|
||||
|
||||
function authorityValue(value: unknown): M48Authority {
|
||||
const authority = objectValue(value, "M4.8R1.authority");
|
||||
exact(authority.mode, "replay-simulated", "M4.8R1.authority.mode");
|
||||
exact(authority.physical_live, false, "M4.8R1.authority.physical_live");
|
||||
exact(authority.commands_enabled, false, "M4.8R1.authority.commands_enabled");
|
||||
exact(authority.actuation_allowed, false, "M4.8R1.authority.actuation_allowed");
|
||||
exact(authority.navigation_or_safety_accepted, false, "M4.8R1.authority.navigation_or_safety_accepted");
|
||||
return {
|
||||
mode: "replay-simulated",
|
||||
physicalLive: false,
|
||||
commandsEnabled: false,
|
||||
actuationAllowed: false,
|
||||
navigationOrSafetyAccepted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {
|
||||
if (typeof value !== "string" || !allowed.includes(value as T)) {
|
||||
throw new M48SmallStaticRegressionContractError(`${label}: неизвестное значение.`);
|
||||
}
|
||||
return value as T;
|
||||
}
|
||||
|
||||
function parseSummary(value: unknown): M48SmallStaticRegressionCaseSummary {
|
||||
const row = objectValue(value, "M4.8R1.case");
|
||||
const anchorId = textValue(row.anchor_id, "M4.8R1.case.anchor_id");
|
||||
if (!ANCHOR_ID.test(anchorId)) {
|
||||
throw new M48SmallStaticRegressionContractError("M4.8R1.case.anchor_id: нарушена идентичность.");
|
||||
}
|
||||
return {
|
||||
anchorId,
|
||||
clipId: textValue(row.clip_id, "M4.8R1.case.clip_id"),
|
||||
sequence: integerValue(row.sequence, "M4.8R1.case.sequence"),
|
||||
requiresAvoidanceOrClearance: booleanValue(row.requires_avoidance_or_clearance, "M4.8R1.case.requires_avoidance_or_clearance"),
|
||||
workerCandidateCount: integerValue(row.worker_candidate_count, "M4.8R1.case.worker_candidate_count"),
|
||||
bestIou: numberValue(row.best_iou, "M4.8R1.case.best_iou"),
|
||||
matchedAtThreshold: booleanValue(row.matched_at_threshold, "M4.8R1.case.matched_at_threshold"),
|
||||
outcome: enumValue(row.outcome, ["recalled", "missed-assisted-anchor"] as const, "M4.8R1.case.outcome"),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchM48SmallStaticRegression(
|
||||
resultId: string,
|
||||
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<M48SmallStaticRegressionResult> {
|
||||
if (!RESULT_ID.test(resultId)) {
|
||||
throw new M48SmallStaticRegressionContractError("M4.8R1 result identity недопустима.");
|
||||
}
|
||||
const response = await fetcher(`/api/v1/laboratory/m48/regressions/small-static/${encodeURIComponent(resultId)}`, {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal,
|
||||
});
|
||||
if (!response.ok) throw new M48SmallStaticRegressionContractError(`M4.8R1 недоступен: HTTP ${response.status}.`);
|
||||
const payload = objectValue(await response.json(), "M4.8R1");
|
||||
exact(payload.schema_version, "missioncore.m48-small-static-passage-regression-result-view/v1", "M4.8R1.schema_version");
|
||||
const packId = textValue(payload.pack_id, "M4.8R1.pack_id");
|
||||
if (!PACK_ID.test(packId)) throw new M48SmallStaticRegressionContractError("M4.8R1.pack_id: нарушена идентичность.");
|
||||
const metrics = objectValue(payload.metrics, "M4.8R1.metrics");
|
||||
const gates = objectValue(payload.gates, "M4.8R1.gates");
|
||||
const decision = objectValue(payload.decision, "M4.8R1.decision");
|
||||
exact(payload.run_label, "M4.8R1", "M4.8R1.run_label");
|
||||
exact(payload.pipeline_id, "m48-class-free-object-quality/v1", "M4.8R1.pipeline_id");
|
||||
exact(payload.experiment_id, "m48-small-static-passage-regression/v1", "M4.8R1.experiment_id");
|
||||
exact(payload.ground_truth, false, "M4.8R1.ground_truth");
|
||||
exact(payload.independent_truth, false, "M4.8R1.independent_truth");
|
||||
exact(gates.independent_truth_available, false, "M4.8R1.gates.independent_truth_available");
|
||||
return {
|
||||
resultId,
|
||||
packId,
|
||||
createdAtUtc: textValue(payload.created_at_utc, "M4.8R1.created_at_utc"),
|
||||
runLabel: "M4.8R1",
|
||||
pipelineId: "m48-class-free-object-quality/v1",
|
||||
experimentId: "m48-small-static-passage-regression/v1",
|
||||
accepted: booleanValue(payload.accepted, "M4.8R1.accepted"),
|
||||
metrics: {
|
||||
assistedAnchorCount: integerValue(metrics.assisted_anchor_count, "M4.8R1.metrics.assisted_anchor_count"),
|
||||
assistedTrackletCount: integerValue(metrics.assisted_tracklet_count, "M4.8R1.metrics.assisted_tracklet_count"),
|
||||
anchorClipCount: integerValue(metrics.anchor_clip_count, "M4.8R1.metrics.anchor_clip_count"),
|
||||
requiresAvoidanceOrClearanceCount: integerValue(metrics.requires_avoidance_or_clearance_count, "M4.8R1.metrics.requires_avoidance_or_clearance_count"),
|
||||
workerRecalledAnchorCount: integerValue(metrics.worker_recalled_anchor_count, "M4.8R1.metrics.worker_recalled_anchor_count"),
|
||||
workerMissedAnchorCount: integerValue(metrics.worker_missed_anchor_count, "M4.8R1.metrics.worker_missed_anchor_count"),
|
||||
assistedAnchorRecall: numberValue(metrics.assisted_anchor_recall, "M4.8R1.metrics.assisted_anchor_recall"),
|
||||
extentIouThreshold: numberValue(metrics.extent_iou_threshold, "M4.8R1.metrics.extent_iou_threshold"),
|
||||
minimumAssistedAnchorRecall: numberValue(metrics.minimum_assisted_anchor_recall, "M4.8R1.metrics.minimum_assisted_anchor_recall"),
|
||||
},
|
||||
gates: {
|
||||
anchorSetNonEmpty: booleanValue(gates.anchor_set_non_empty, "M4.8R1.gates.anchor_set_non_empty"),
|
||||
developmentAnchorRecallTarget: booleanValue(gates.development_anchor_recall_target, "M4.8R1.gates.development_anchor_recall_target"),
|
||||
independentTruthAvailable: false,
|
||||
},
|
||||
decision: {
|
||||
state: enumValue(decision.state, ["accepted-development-regression-baseline", "failed-development-regression-baseline"] as const, "M4.8R1.decision.state"),
|
||||
summary: textValue(decision.summary, "M4.8R1.decision.summary"),
|
||||
nextAction: textValue(decision.next_action, "M4.8R1.decision.next_action"),
|
||||
},
|
||||
groundTruth: false,
|
||||
independentTruth: false,
|
||||
authority: authorityValue(payload.authority),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchM48SmallStaticRegressionCases(
|
||||
resultId: string,
|
||||
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<readonly M48SmallStaticRegressionCaseSummary[]> {
|
||||
if (!RESULT_ID.test(resultId)) throw new M48SmallStaticRegressionContractError("M4.8R1 result identity недопустима.");
|
||||
const response = await fetcher(`/api/v1/laboratory/m48/regressions/small-static/${encodeURIComponent(resultId)}/cases`, {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal,
|
||||
});
|
||||
if (!response.ok) throw new M48SmallStaticRegressionContractError(`M4.8R1 cases недоступны: HTTP ${response.status}.`);
|
||||
const payload = objectValue(await response.json(), "M4.8R1 cases");
|
||||
exact(payload.schema_version, "missioncore.m48-small-static-passage-regression-case-catalog/v1", "M4.8R1 cases.schema_version");
|
||||
const cases = arrayValue(payload.cases, "M4.8R1 cases.items").map(parseSummary);
|
||||
if (integerValue(payload.case_count, "M4.8R1 cases.case_count") !== cases.length) {
|
||||
throw new M48SmallStaticRegressionContractError("M4.8R1 cases: размер изменился.");
|
||||
}
|
||||
exact(payload.ground_truth, false, "M4.8R1 cases.ground_truth");
|
||||
authorityValue(payload.authority);
|
||||
return cases;
|
||||
}
|
||||
|
||||
export async function fetchM48SmallStaticRegressionCase(
|
||||
resultId: string,
|
||||
anchorId: string,
|
||||
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<M48SmallStaticRegressionCase> {
|
||||
if (!RESULT_ID.test(resultId) || !ANCHOR_ID.test(anchorId)) {
|
||||
throw new M48SmallStaticRegressionContractError("M4.8R1 case identity недопустима.");
|
||||
}
|
||||
const response = await fetcher(`/api/v1/laboratory/m48/regressions/small-static/${encodeURIComponent(resultId)}/cases/${encodeURIComponent(anchorId)}`, {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal,
|
||||
});
|
||||
if (!response.ok) throw new M48SmallStaticRegressionContractError(`M4.8R1 case недоступен: HTTP ${response.status}.`);
|
||||
const payload = objectValue(await response.json(), "M4.8R1 case");
|
||||
exact(payload.schema_version, "missioncore.m48-small-static-passage-regression-case-view/v1", "M4.8R1 case.schema_version");
|
||||
const anchor = objectValue(payload.anchor, "M4.8R1 case.anchor");
|
||||
const comparison = objectValue(payload.comparison, "M4.8R1 case.comparison");
|
||||
const summary = parseSummary(comparison);
|
||||
const workerObjects = arrayValue(comparison.worker_objects, "M4.8R1 case.worker_objects").map((raw) => {
|
||||
const object = objectValue(raw, "M4.8R1 worker object");
|
||||
return {
|
||||
predictionId: textValue(object.prediction_id, "M4.8R1 worker object.prediction_id"),
|
||||
extentXyxy: extentValue(object.extent_xyxy, "M4.8R1 worker object.extent_xyxy"),
|
||||
geometryAssociation: enumValue(object.geometry_association, ["associated", "unavailable", "ineligible", "unknown"] as const, "M4.8R1 worker object.geometry_association"),
|
||||
freshness: enumValue(object.freshness, ["current", "held", "stale", "unavailable"] as const, "M4.8R1 worker object.freshness"),
|
||||
motion: enumValue(object.motion, ["moving", "static", "unknown", "unsupported"] as const, "M4.8R1 worker object.motion"),
|
||||
threat: enumValue(object.threat, ["threat", "not-threat", "unknown"] as const, "M4.8R1 worker object.threat"),
|
||||
};
|
||||
});
|
||||
const cameraUrl = payload.camera_url === null ? null : textValue(payload.camera_url, "M4.8R1 case.camera_url");
|
||||
const spatialUrl = payload.spatial_url === null ? null : textValue(payload.spatial_url, "M4.8R1 case.spatial_url");
|
||||
if ((cameraUrl && !SAFE_URL.test(cameraUrl)) || (spatialUrl && !SAFE_URL.test(spatialUrl))) {
|
||||
throw new M48SmallStaticRegressionContractError("M4.8R1 case evidence URL недопустим.");
|
||||
}
|
||||
exact(payload.ground_truth, false, "M4.8R1 case.ground_truth");
|
||||
const packId = textValue(payload.pack_id, "M4.8R1 case.pack_id");
|
||||
if (!PACK_ID.test(packId)) {
|
||||
throw new M48SmallStaticRegressionContractError("M4.8R1 case.pack_id: нарушена идентичность.");
|
||||
}
|
||||
if (textValue(anchor.anchor_id, "M4.8R1 case.anchor.anchor_id") !== anchorId) {
|
||||
throw new M48SmallStaticRegressionContractError("M4.8R1 case.anchor_id: ответ не соответствует запросу.");
|
||||
}
|
||||
return {
|
||||
resultId,
|
||||
packId,
|
||||
anchor: {
|
||||
anchorId: textValue(anchor.anchor_id, "M4.8R1 case.anchor.anchor_id"),
|
||||
clipId: textValue(anchor.clip_id, "M4.8R1 case.anchor.clip_id"),
|
||||
objectId: textValue(anchor.object_id, "M4.8R1 case.anchor.object_id"),
|
||||
sequence: integerValue(anchor.sequence, "M4.8R1 case.anchor.sequence"),
|
||||
extentXyxy: extentValue(anchor.extent_xyxy, "M4.8R1 case.anchor.extent_xyxy"),
|
||||
visibility: enumValue(anchor.visibility, ["visible", "partial", "occluded"] as const, "M4.8R1 case.anchor.visibility"),
|
||||
geometryAssociation: enumValue(anchor.geometry_association, ["associated", "unavailable", "ineligible", "unknown"] as const, "M4.8R1 case.anchor.geometry_association"),
|
||||
freshness: enumValue(anchor.freshness, ["current", "held", "stale", "unavailable"] as const, "M4.8R1 case.anchor.freshness"),
|
||||
motion: enumValue(anchor.motion, ["moving", "static", "unknown", "unsupported"] as const, "M4.8R1 case.anchor.motion"),
|
||||
threat: enumValue(anchor.threat, ["threat", "not-threat", "unknown"] as const, "M4.8R1 case.anchor.threat"),
|
||||
requiresAvoidanceOrClearance: booleanValue(anchor.requires_avoidance_or_clearance, "M4.8R1 case.anchor.requires_avoidance_or_clearance"),
|
||||
},
|
||||
comparison: {
|
||||
...summary,
|
||||
sourceTimeNs: integerValue(comparison.source_time_ns, "M4.8R1 case.comparison.source_time_ns"),
|
||||
anchorExtentXyxy: extentValue(comparison.anchor_extent_xyxy, "M4.8R1 case.comparison.anchor_extent_xyxy"),
|
||||
workerObjects,
|
||||
bestPredictionId: comparison.best_prediction_id === null ? null : textValue(comparison.best_prediction_id, "M4.8R1 case.comparison.best_prediction_id"),
|
||||
extentIouThreshold: numberValue(comparison.extent_iou_threshold, "M4.8R1 case.comparison.extent_iou_threshold"),
|
||||
},
|
||||
cameraUrl,
|
||||
spatialUrl,
|
||||
groundTruth: false,
|
||||
authority: authorityValue(payload.authority),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user