fix(lab): restore semantic overlay in M4.7
This commit is contained in:
@@ -360,27 +360,65 @@ function parseResult(value: unknown): E47SemanticSlamResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchE47SemanticSlamResult({
|
export async function fetchE47SemanticSlamResult({
|
||||||
|
resultId: requestedResultId,
|
||||||
fetcher = fetch,
|
fetcher = fetch,
|
||||||
signal,
|
signal,
|
||||||
}: {
|
}: {
|
||||||
|
resultId?: string;
|
||||||
fetcher?: LaboratoryFetch;
|
fetcher?: LaboratoryFetch;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
} = {}): Promise<E47SemanticSlamResult | null> {
|
} = {}): Promise<E47SemanticSlamResult | null> {
|
||||||
const response = await fetcher("/api/v1/laboratory/e47-semantic-slam/results?limit=1", {
|
if (requestedResultId !== undefined) resultId(requestedResultId);
|
||||||
headers: { Accept: "application/json" },
|
let response = await fetcher(
|
||||||
signal,
|
requestedResultId === undefined
|
||||||
});
|
? "/api/v1/laboratory/e47-semantic-slam/results?limit=1"
|
||||||
|
: `/api/v1/laboratory/e47-semantic-slam/results/${requestedResultId}`,
|
||||||
|
{ headers: { Accept: "application/json" }, signal },
|
||||||
|
);
|
||||||
|
let catalogFallback = false;
|
||||||
|
if (requestedResultId !== undefined && response.status === 404) {
|
||||||
|
response = await fetcher("/api/v1/laboratory/e47-semantic-slam/results?limit=10", {
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
catalogFallback = true;
|
||||||
|
}
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new E47SemanticSlamContractError(`E47 LAB недоступен: HTTP ${response.status}.`);
|
throw new E47SemanticSlamContractError(`E47 LAB недоступен: HTTP ${response.status}.`);
|
||||||
}
|
}
|
||||||
const payload = object(await response.json(), "E47 catalog");
|
const payload = await response.json();
|
||||||
exact(
|
if (requestedResultId === undefined || catalogFallback) {
|
||||||
payload.schema_version,
|
const catalog = object(payload, "E47 catalog");
|
||||||
"missioncore.e47-semantic-slam-catalog/v1",
|
exact(
|
||||||
"E47 catalog schema",
|
catalog.schema_version,
|
||||||
);
|
"missioncore.e47-semantic-slam-catalog/v1",
|
||||||
const items = array(payload.items, "E47 catalog items");
|
"E47 catalog schema",
|
||||||
return items.length ? parseResult(items[0]) : null;
|
);
|
||||||
|
const items = array(catalog.items, "E47 catalog items");
|
||||||
|
if (!items.length) return null;
|
||||||
|
const selected = requestedResultId === undefined
|
||||||
|
? items[0]
|
||||||
|
: items.find((candidate) => (
|
||||||
|
typeof candidate === "object"
|
||||||
|
&& candidate !== null
|
||||||
|
&& (candidate as Record<string, unknown>).result_id === requestedResultId
|
||||||
|
));
|
||||||
|
if (selected === undefined) {
|
||||||
|
throw new E47SemanticSlamContractError(
|
||||||
|
"E47 requested result: точный semantic-result отсутствует в каталоге.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const parsed = parseResult(selected);
|
||||||
|
if (requestedResultId !== undefined && parsed.resultId !== requestedResultId) {
|
||||||
|
throw new E47SemanticSlamContractError("E47 requested result: нарушена идентичность.");
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
const parsed = parseResult(payload);
|
||||||
|
if (parsed.resultId !== requestedResultId) {
|
||||||
|
throw new E47SemanticSlamContractError("E47 requested result: нарушена идентичность.");
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFrame(
|
function parseFrame(
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import {
|
|||||||
fetchM4ThreatReplayResult,
|
fetchM4ThreatReplayResult,
|
||||||
type M4ThreatReplayResult,
|
type M4ThreatReplayResult,
|
||||||
} from "./m4ReplayThreat";
|
} from "./m4ReplayThreat";
|
||||||
|
import {
|
||||||
|
fetchE47SemanticSlamResult,
|
||||||
|
type E47SemanticSlamResult,
|
||||||
|
} from "./e47SemanticSlam";
|
||||||
|
|
||||||
export interface M47ReferenceGraphLabResult {
|
export interface M47ReferenceGraphLabResult {
|
||||||
resultId: string;
|
resultId: string;
|
||||||
@@ -41,6 +45,7 @@ export interface M47ReferenceGraphLabResult {
|
|||||||
>>;
|
>>;
|
||||||
limitations: readonly string[];
|
limitations: readonly string[];
|
||||||
visual: M4ThreatReplayResult;
|
visual: M4ThreatReplayResult;
|
||||||
|
semantic: E47SemanticSlamResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
type LaboratoryFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
type LaboratoryFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
||||||
@@ -154,7 +159,7 @@ export async function fetchM47ReferenceGraphLab({
|
|||||||
const report = evidence.rawReport;
|
const report = evidence.rawReport;
|
||||||
exact(
|
exact(
|
||||||
report.schema_version,
|
report.schema_version,
|
||||||
"missioncore.reference-perception-graph-lab-report/v1",
|
"missioncore.reference-perception-graph-lab-report/v2",
|
||||||
"M4.7 report schema",
|
"M4.7 report schema",
|
||||||
);
|
);
|
||||||
exact(report.result_id, resultId, "M4.7 report result");
|
exact(report.result_id, resultId, "M4.7 report result");
|
||||||
@@ -176,6 +181,16 @@ export async function fetchM47ReferenceGraphLab({
|
|||||||
true,
|
true,
|
||||||
"M4.7 visual evidence",
|
"M4.7 visual evidence",
|
||||||
);
|
);
|
||||||
|
exact(
|
||||||
|
visualEvidence.camera_semantic_overlay_available,
|
||||||
|
true,
|
||||||
|
"M4.7 camera semantic overlay",
|
||||||
|
);
|
||||||
|
exact(
|
||||||
|
visualEvidence.semantic_binding,
|
||||||
|
"exact-sequence-and-session-time",
|
||||||
|
"M4.7 semantic binding",
|
||||||
|
);
|
||||||
const linkedVisualResultId = text(
|
const linkedVisualResultId = text(
|
||||||
visualEvidence.linked_result_id,
|
visualEvidence.linked_result_id,
|
||||||
"M4.7 linked visual result",
|
"M4.7 linked visual result",
|
||||||
@@ -193,6 +208,31 @@ export async function fetchM47ReferenceGraphLab({
|
|||||||
"M4.7 visual binding: сервер вернул другой replay result.",
|
"M4.7 visual binding: сервер вернул другой replay result.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const semanticResultId = text(
|
||||||
|
visualEvidence.semantic_result_id,
|
||||||
|
"M4.7 linked semantic result",
|
||||||
|
);
|
||||||
|
if (!/^e47-semantic-slam-[a-f0-9]{64}$/.test(semanticResultId)) {
|
||||||
|
throw new M47ReferenceGraphContractError(
|
||||||
|
"M4.7 semantic binding: нарушена идентичность.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const semantic = await fetchE47SemanticSlamResult({
|
||||||
|
resultId: semanticResultId,
|
||||||
|
fetcher,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
!semantic
|
||||||
|
|| semantic.resultId !== semanticResultId
|
||||||
|
|| semantic.baseM4ResultId !== linkedVisualResultId
|
||||||
|
|| semantic.metrics.frames.total !== 4489
|
||||||
|
|| semantic.metrics.frames.maskAvailable !== 4489
|
||||||
|
) {
|
||||||
|
throw new M47ReferenceGraphContractError(
|
||||||
|
"M4.7 semantic binding: сервер вернул несвязанный semantic-result.",
|
||||||
|
);
|
||||||
|
}
|
||||||
const graphResultId = text(source.graph_result_id, "M4.7 graph result");
|
const graphResultId = text(source.graph_result_id, "M4.7 graph result");
|
||||||
if (!/^m47-reference-graph-[a-f0-9]{64}$/.test(graphResultId)) {
|
if (!/^m47-reference-graph-[a-f0-9]{64}$/.test(graphResultId)) {
|
||||||
throw new M47ReferenceGraphContractError("M4.7 graph result: нарушена идентичность.");
|
throw new M47ReferenceGraphContractError("M4.7 graph result: нарушена идентичность.");
|
||||||
@@ -219,5 +259,6 @@ export async function fetchM47ReferenceGraphLab({
|
|||||||
queueHighWatermarks: queueWatermarks(metrics.queue_high_watermarks),
|
queueHighWatermarks: queueWatermarks(metrics.queue_high_watermarks),
|
||||||
limitations: stringArray(report.limitations, "M4.7 limitations"),
|
limitations: stringArray(report.limitations, "M4.7 limitations"),
|
||||||
visual,
|
visual,
|
||||||
|
semantic,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function M47ReferenceGraphResultView({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Визуал",
|
label: "Визуал",
|
||||||
value: "VIDEO/CAMERA/3D/PLAN · единый recorded clock · exact M4.6 ledger binding",
|
value: "VIDEO/CAMERA/3D/PLAN · единый recorded clock · exact M4.6 + E47 semantic binding",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
brief={{
|
brief={{
|
||||||
@@ -72,6 +72,13 @@ export function M47ReferenceGraphResultView({
|
|||||||
role: "synchronized VIDEO/CAMERA/3D/PLAN evidence",
|
role: "synchronized VIDEO/CAMERA/3D/PLAN evidence",
|
||||||
identitySha256: result.linkedVisualResultId.split("-").at(-1) ?? null,
|
identitySha256: result.linkedVisualResultId.split("-").at(-1) ?? null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
kind: "source",
|
||||||
|
name: result.semantic.resultId,
|
||||||
|
version: "4489 exact camera masks",
|
||||||
|
role: "synchronized diagnostic semantic overlay",
|
||||||
|
identitySha256: result.semantic.resultId.split("-").at(-1) ?? null,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
kind: "algorithm",
|
kind: "algorithm",
|
||||||
name: "canonical reference perception graph",
|
name: "canonical reference perception graph",
|
||||||
@@ -90,7 +97,13 @@ export function M47ReferenceGraphResultView({
|
|||||||
kind="diagnostic-model"
|
kind="diagnostic-model"
|
||||||
resizable
|
resizable
|
||||||
>
|
>
|
||||||
<M4ReplayThreatVisual resultId={result.visual.resultId} />
|
<M4ReplayThreatVisual
|
||||||
|
resultId={result.visual.resultId}
|
||||||
|
semantic={{
|
||||||
|
resultId: result.semantic.resultId,
|
||||||
|
taxonomy: result.semantic.taxonomy,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</LaboratoryEvidence>
|
</LaboratoryEvidence>
|
||||||
)}
|
)}
|
||||||
result={(
|
result={(
|
||||||
|
|||||||
@@ -127,6 +127,21 @@ test("E47 accepts only a fully accounted diagnostic semantic/SLAM view", async (
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("E47 fetches one exact content-addressed semantic result", async () => {
|
||||||
|
const requests = [];
|
||||||
|
const result = await fetchE47SemanticSlamResult({
|
||||||
|
resultId,
|
||||||
|
fetcher: async (input) => {
|
||||||
|
requests.push(String(input));
|
||||||
|
return new Response(JSON.stringify(resultView()));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.equal(result.resultId, resultId);
|
||||||
|
assert.deepEqual(requests, [
|
||||||
|
`/api/v1/laboratory/e47-semantic-slam/results/${resultId}`,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test("E47 rejects result-level point accounting drift", async () => {
|
test("E47 rejects result-level point accounting drift", async () => {
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
fetchE47SemanticSlamResult({
|
fetchE47SemanticSlamResult({
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ let fetchM47ReferenceGraphLab;
|
|||||||
const resultId = `m47-reference-graph-lab-${"a".repeat(64)}`;
|
const resultId = `m47-reference-graph-lab-${"a".repeat(64)}`;
|
||||||
const graphResultId = `m47-reference-graph-${"b".repeat(64)}`;
|
const graphResultId = `m47-reference-graph-${"b".repeat(64)}`;
|
||||||
const visualResultId = `m4-threat-replay-${"c".repeat(64)}`;
|
const visualResultId = `m4-threat-replay-${"c".repeat(64)}`;
|
||||||
|
const semanticResultId = `e47-semantic-slam-${"d".repeat(64)}`;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
server = await createServer({
|
server = await createServer({
|
||||||
@@ -81,6 +82,62 @@ function m4Result() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function semanticResult() {
|
||||||
|
return {
|
||||||
|
schema_version: "missioncore.e47-semantic-slam-view/v1",
|
||||||
|
result_id: semanticResultId,
|
||||||
|
created_at_utc: "2026-08-06T08:13:48.601Z",
|
||||||
|
status: "diagnostic-semantic-slam-shadow",
|
||||||
|
profile_id: "ravnoves00-eomt-kb4-slam-shadow/v1",
|
||||||
|
base_m4_result_id: visualResultId,
|
||||||
|
semantic_result_id: `result-${"e".repeat(64)}`,
|
||||||
|
geometry_result_id: `m4-geometry-replay-${"f".repeat(64)}`,
|
||||||
|
source_pack_id: `e10-lidar-pack-${"1".repeat(64)}`,
|
||||||
|
calibration_content_sha256: "2".repeat(64),
|
||||||
|
provider: {
|
||||||
|
provider_id: "eomt-cityscapes-semantic-control/v1",
|
||||||
|
model_id: "cityscapes-semantic-eomt",
|
||||||
|
model_revision: "3".repeat(40),
|
||||||
|
model_weights_sha256: "4".repeat(64),
|
||||||
|
preprocess_id: "raw-kb4-valid-fov-semantic/v1",
|
||||||
|
},
|
||||||
|
temporal_binding: {
|
||||||
|
semantic_to_camera: "exact-sequence-and-session-time",
|
||||||
|
camera_to_lidar: "accepted-e6-nearest-host-arrival-best-effort",
|
||||||
|
clock_basis: "recorded-host-monotonic-arrival",
|
||||||
|
maximum_lidar_camera_delta_ms: 100,
|
||||||
|
maximum_pose_point_delta_ms: 100,
|
||||||
|
physical_synchronization_proven: false,
|
||||||
|
},
|
||||||
|
taxonomy: [{
|
||||||
|
class_id: 1,
|
||||||
|
label: "road",
|
||||||
|
disposition: "labeled",
|
||||||
|
color_rgb: [128, 64, 128],
|
||||||
|
}],
|
||||||
|
metrics: {
|
||||||
|
frames: { total: 4489, mask_available: 4489, source_available: 3928 },
|
||||||
|
points: { total: 1, projected: 1, labeled: 1, ambiguous: 0, unprojected: 0, absent: 0 },
|
||||||
|
observations: { total: 1, labeled: 1, ambiguous: 0, unprojected: 0, absent: 0 },
|
||||||
|
runtime: { elapsed_ms: 1000, frames_per_second: 4.489 },
|
||||||
|
},
|
||||||
|
acceptance: {
|
||||||
|
artifact_contract_passed: true,
|
||||||
|
frame_accounting_passed: true,
|
||||||
|
point_accounting_passed: true,
|
||||||
|
observation_binding_passed: true,
|
||||||
|
temporal_binding_passed: true,
|
||||||
|
independent_semantic_truth_passed: false,
|
||||||
|
provider_promoted: false,
|
||||||
|
},
|
||||||
|
limitations: ["diagnostic only"],
|
||||||
|
ground_truth: false,
|
||||||
|
semantic_authority: "diagnostic-only",
|
||||||
|
navigation_or_safety_accepted: false,
|
||||||
|
actuation_allowed: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function evidenceReport() {
|
function evidenceReport() {
|
||||||
const mismatch = {
|
const mismatch = {
|
||||||
source_binding: 0,
|
source_binding: 0,
|
||||||
@@ -92,7 +149,7 @@ function evidenceReport() {
|
|||||||
threat_assessments: 0,
|
threat_assessments: 0,
|
||||||
};
|
};
|
||||||
const raw = {
|
const raw = {
|
||||||
schema_version: "missioncore.reference-perception-graph-lab-report/v1",
|
schema_version: "missioncore.reference-perception-graph-lab-report/v2",
|
||||||
result_id: resultId,
|
result_id: resultId,
|
||||||
source: { graph_result_id: graphResultId },
|
source: { graph_result_id: graphResultId },
|
||||||
method: { graph_id: "reference-perception-graph/v2" },
|
method: { graph_id: "reference-perception-graph/v2" },
|
||||||
@@ -122,8 +179,11 @@ function evidenceReport() {
|
|||||||
},
|
},
|
||||||
visual_evidence: {
|
visual_evidence: {
|
||||||
linked_result_id: visualResultId,
|
linked_result_id: visualResultId,
|
||||||
|
semantic_result_id: semanticResultId,
|
||||||
shared_recorded_clock: true,
|
shared_recorded_clock: true,
|
||||||
video_camera_3d_plan_available: true,
|
video_camera_3d_plan_available: true,
|
||||||
|
camera_semantic_overlay_available: true,
|
||||||
|
semantic_binding: "exact-sequence-and-session-time",
|
||||||
},
|
},
|
||||||
limitations: ["recorded replay only"],
|
limitations: ["recorded replay only"],
|
||||||
};
|
};
|
||||||
@@ -134,13 +194,13 @@ function evidenceReport() {
|
|||||||
created_at_utc: "2026-08-23T18:00:16.061Z",
|
created_at_utc: "2026-08-23T18:00:16.061Z",
|
||||||
access: "read-only",
|
access: "read-only",
|
||||||
proof: {
|
proof: {
|
||||||
document_schema_version: "missioncore.reference-perception-graph-lab/v1",
|
document_schema_version: "missioncore.reference-perception-graph-lab/v2",
|
||||||
document_sha256: "3".repeat(64),
|
document_sha256: "3".repeat(64),
|
||||||
identity_sha256: "a".repeat(64),
|
identity_sha256: "a".repeat(64),
|
||||||
report_schema_version: raw.schema_version,
|
report_schema_version: raw.schema_version,
|
||||||
report_sha256: "4".repeat(64),
|
report_sha256: "4".repeat(64),
|
||||||
artifact_count: 6,
|
artifact_count: 9,
|
||||||
verified_artifact_count: 6,
|
verified_artifact_count: 9,
|
||||||
},
|
},
|
||||||
completeness: { identity: "recorded" },
|
completeness: { identity: "recorded" },
|
||||||
identity: { authority: raw.authority },
|
identity: { authority: raw.authority },
|
||||||
@@ -167,14 +227,16 @@ test("M4.7 binds exact Worker graph proof to the synchronized M4.6 visual", asyn
|
|||||||
fetcher: async (input) => {
|
fetcher: async (input) => {
|
||||||
const path = String(input);
|
const path = String(input);
|
||||||
requests.push(path);
|
requests.push(path);
|
||||||
return new Response(JSON.stringify(
|
const payload = path.includes("/evidence-reports/")
|
||||||
path.includes("/evidence-reports/") ? evidenceReport() : m4Result(),
|
? evidenceReport()
|
||||||
), { status: 200 });
|
: path.includes("/e47-semantic-slam/") ? semanticResult() : m4Result();
|
||||||
|
return new Response(JSON.stringify(payload), { status: 200 });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.graphResultId, graphResultId);
|
assert.equal(result.graphResultId, graphResultId);
|
||||||
assert.equal(result.visual.resultId, visualResultId);
|
assert.equal(result.visual.resultId, visualResultId);
|
||||||
|
assert.equal(result.semantic.resultId, semanticResultId);
|
||||||
assert.equal(result.worker.id, "worker-006");
|
assert.equal(result.worker.id, "worker-006");
|
||||||
assert.equal(result.frames.delivered, 4489);
|
assert.equal(result.frames.delivered, 4489);
|
||||||
assert.deepEqual(Object.values(result.parityMismatchCounts), Array(7).fill(0));
|
assert.deepEqual(Object.values(result.parityMismatchCounts), Array(7).fill(0));
|
||||||
@@ -182,6 +244,7 @@ test("M4.7 binds exact Worker graph proof to the synchronized M4.6 visual", asyn
|
|||||||
assert.deepEqual(requests, [
|
assert.deepEqual(requests, [
|
||||||
`/api/v1/laboratory/evidence-reports/m47-reference-graph-shadow/${resultId}`,
|
`/api/v1/laboratory/evidence-reports/m47-reference-graph-shadow/${resultId}`,
|
||||||
`/api/v1/laboratory/m4-threat/results/${visualResultId}`,
|
`/api/v1/laboratory/m4-threat/results/${visualResultId}`,
|
||||||
|
`/api/v1/laboratory/e47-semantic-slam/results/${semanticResultId}`,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -198,6 +261,9 @@ test("M4.7 keeps exact visual binding during a rolling API deployment", async ()
|
|||||||
if (path.endsWith(`/${visualResultId}`)) {
|
if (path.endsWith(`/${visualResultId}`)) {
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
}
|
}
|
||||||
|
if (path.includes("/e47-semantic-slam/")) {
|
||||||
|
return new Response(JSON.stringify(semanticResult()), { status: 200 });
|
||||||
|
}
|
||||||
return new Response(JSON.stringify({
|
return new Response(JSON.stringify({
|
||||||
schema_version: "missioncore.m4-threat-replay-catalog/v1",
|
schema_version: "missioncore.m4-threat-replay-catalog/v1",
|
||||||
items: [m4Result()],
|
items: [m4Result()],
|
||||||
@@ -210,5 +276,6 @@ test("M4.7 keeps exact visual binding during a rolling API deployment", async ()
|
|||||||
`/api/v1/laboratory/evidence-reports/m47-reference-graph-shadow/${resultId}`,
|
`/api/v1/laboratory/evidence-reports/m47-reference-graph-shadow/${resultId}`,
|
||||||
`/api/v1/laboratory/m4-threat/results/${visualResultId}`,
|
`/api/v1/laboratory/m4-threat/results/${visualResultId}`,
|
||||||
"/api/v1/laboratory/m4-threat/results?limit=10",
|
"/api/v1/laboratory/m4-threat/results?limit=10",
|
||||||
|
`/api/v1/laboratory/e47-semantic-slam/results/${semanticResultId}`,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
"runtime_relative_root": "m47/reference-graph-labs",
|
"runtime_relative_root": "m47/reference-graph-labs",
|
||||||
"result_id_prefix": "m47-reference-graph-lab",
|
"result_id_prefix": "m47-reference-graph-lab",
|
||||||
"document_name": "manifest.json",
|
"document_name": "manifest.json",
|
||||||
"schema_version": "missioncore.reference-perception-graph-lab/v1"
|
"schema_version": "missioncore.reference-perception-graph-lab/v2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ def main() -> int:
|
|||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("--graph-result-root", type=Path, required=True)
|
parser.add_argument("--graph-result-root", type=Path, required=True)
|
||||||
parser.add_argument("--visual-result-root", type=Path, required=True)
|
parser.add_argument("--visual-result-root", type=Path, required=True)
|
||||||
|
parser.add_argument("--semantic-result-root", type=Path, required=True)
|
||||||
parser.add_argument("--output-root", type=Path, required=True)
|
parser.add_argument("--output-root", type=Path, required=True)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
result = publish_m47_reference_graph_lab(
|
result = publish_m47_reference_graph_lab(
|
||||||
graph_result_root=args.graph_result_root,
|
graph_result_root=args.graph_result_root,
|
||||||
visual_result_root=args.visual_result_root,
|
visual_result_root=args.visual_result_root,
|
||||||
|
semantic_result_root=args.semantic_result_root,
|
||||||
output_root=args.output_root,
|
output_root=args.output_root,
|
||||||
)
|
)
|
||||||
print(
|
print(
|
||||||
|
|||||||
@@ -12,11 +12,17 @@ from pathlib import Path
|
|||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from k1link.perception.reference_graph_result import read_reference_graph_result
|
from k1link.perception.reference_graph_result import read_reference_graph_result
|
||||||
|
from k1link.perception.semantic_slam_replay import (
|
||||||
|
SEMANTIC_SLAM_MANIFEST_NAME,
|
||||||
|
SEMANTIC_SLAM_REPORT_NAME,
|
||||||
|
SEMANTIC_SLAM_TAXONOMY_NAME,
|
||||||
|
read_semantic_slam_replay_result,
|
||||||
|
)
|
||||||
from k1link.perception.threat_replay import read_threat_replay_result
|
from k1link.perception.threat_replay import read_threat_replay_result
|
||||||
|
|
||||||
M47_REFERENCE_GRAPH_LAB_SCHEMA: Final = "missioncore.reference-perception-graph-lab/v1"
|
M47_REFERENCE_GRAPH_LAB_SCHEMA: Final = "missioncore.reference-perception-graph-lab/v2"
|
||||||
M47_REFERENCE_GRAPH_LAB_REPORT_SCHEMA: Final = (
|
M47_REFERENCE_GRAPH_LAB_REPORT_SCHEMA: Final = (
|
||||||
"missioncore.reference-perception-graph-lab-report/v1"
|
"missioncore.reference-perception-graph-lab-report/v2"
|
||||||
)
|
)
|
||||||
M47_REFERENCE_GRAPH_LAB_PREFIX: Final = "m47-reference-graph-lab-"
|
M47_REFERENCE_GRAPH_LAB_PREFIX: Final = "m47-reference-graph-lab-"
|
||||||
|
|
||||||
@@ -37,10 +43,12 @@ def publish_m47_reference_graph_lab(
|
|||||||
*,
|
*,
|
||||||
graph_result_root: Path,
|
graph_result_root: Path,
|
||||||
visual_result_root: Path,
|
visual_result_root: Path,
|
||||||
|
semantic_result_root: Path,
|
||||||
output_root: Path,
|
output_root: Path,
|
||||||
) -> M47ReferenceGraphLab:
|
) -> M47ReferenceGraphLab:
|
||||||
graph = read_reference_graph_result(graph_result_root)
|
graph = read_reference_graph_result(graph_result_root)
|
||||||
visual = read_threat_replay_result(visual_result_root)
|
visual = read_threat_replay_result(visual_result_root)
|
||||||
|
semantic = read_semantic_slam_replay_result(semantic_result_root)
|
||||||
if not graph.accepted or not visual.accepted:
|
if not graph.accepted or not visual.accepted:
|
||||||
raise M47ReferenceGraphLabError("both graph and visual replay must be accepted")
|
raise M47ReferenceGraphLabError("both graph and visual replay must be accepted")
|
||||||
graph_report = graph.report
|
graph_report = graph.report
|
||||||
@@ -49,6 +57,9 @@ def publish_m47_reference_graph_lab(
|
|||||||
parity = _object(graph_report.get("accepted_parity"), "graph parity")
|
parity = _object(graph_report.get("accepted_parity"), "graph parity")
|
||||||
mismatches = _object(parity.get("mismatch_counts"), "graph parity mismatches")
|
mismatches = _object(parity.get("mismatch_counts"), "graph parity mismatches")
|
||||||
visual_identity = _object(visual.manifest.get("identity"), "visual identity")
|
visual_identity = _object(visual.manifest.get("identity"), "visual identity")
|
||||||
|
semantic_identity = _object(semantic.manifest.get("identity"), "semantic identity")
|
||||||
|
semantic_frames = _object(semantic_identity.get("counts"), "semantic counts")
|
||||||
|
semantic_frame_counts = _object(semantic_frames.get("frames"), "semantic frame counts")
|
||||||
if (
|
if (
|
||||||
parity.get("accepted") is not True
|
parity.get("accepted") is not True
|
||||||
or parity.get("expected_frames") != 4489
|
or parity.get("expected_frames") != 4489
|
||||||
@@ -68,6 +79,13 @@ def publish_m47_reference_graph_lab(
|
|||||||
or parity.get("temporal_frames_sha256") != visual_identity.get("temporal_frames_sha256")
|
or parity.get("temporal_frames_sha256") != visual_identity.get("temporal_frames_sha256")
|
||||||
):
|
):
|
||||||
raise M47ReferenceGraphLabError("graph-to-visual parity proof changed")
|
raise M47ReferenceGraphLabError("graph-to-visual parity proof changed")
|
||||||
|
if (
|
||||||
|
semantic_identity.get("base_m4_result_id") != visual.result_id
|
||||||
|
or semantic_identity.get("source_session_id") != visual_identity.get("source_session_id")
|
||||||
|
or semantic_frame_counts.get("total") != 4489
|
||||||
|
or semantic_frame_counts.get("mask_available") != 4489
|
||||||
|
):
|
||||||
|
raise M47ReferenceGraphLabError("semantic-to-visual binding proof changed")
|
||||||
terminal = _object(graph_report.get("terminal_outcomes"), "terminal outcomes")
|
terminal = _object(graph_report.get("terminal_outcomes"), "terminal outcomes")
|
||||||
queues = _object(graph_report.get("queue_high_watermarks"), "queue high watermarks")
|
queues = _object(graph_report.get("queue_high_watermarks"), "queue high watermarks")
|
||||||
execution = _object(graph_report.get("execution"), "graph execution")
|
execution = _object(graph_report.get("execution"), "graph execution")
|
||||||
@@ -81,23 +99,30 @@ def publish_m47_reference_graph_lab(
|
|||||||
}
|
}
|
||||||
visual_evidence = {
|
visual_evidence = {
|
||||||
"linked_result_id": visual.result_id,
|
"linked_result_id": visual.result_id,
|
||||||
|
"semantic_result_id": semantic.result_id,
|
||||||
"binding": "exact-threat-and-temporal-ledger-parity",
|
"binding": "exact-threat-and-temporal-ledger-parity",
|
||||||
|
"semantic_binding": "exact-sequence-and-session-time",
|
||||||
"timeline_frames": 4489,
|
"timeline_frames": 4489,
|
||||||
"shared_recorded_clock": True,
|
"shared_recorded_clock": True,
|
||||||
"video_camera_3d_plan_available": True,
|
"video_camera_3d_plan_available": True,
|
||||||
|
"camera_semantic_overlay_available": True,
|
||||||
"regression_sequences": [138, 274, 1880, 2584],
|
"regression_sequences": [138, 274, 1880, 2584],
|
||||||
"independent_ground_truth": False,
|
"independent_ground_truth": False,
|
||||||
}
|
}
|
||||||
identity: dict[str, object] = {
|
identity: dict[str, object] = {
|
||||||
"schema_version": M47_REFERENCE_GRAPH_LAB_SCHEMA,
|
"schema_version": M47_REFERENCE_GRAPH_LAB_SCHEMA,
|
||||||
"binding_id": "m47-reference-graph-visual-binding/v1",
|
"binding_id": "m47-reference-graph-visual-binding/v2",
|
||||||
"source": {
|
"source": {
|
||||||
"source_id": "RAVNOVES00",
|
"source_id": "RAVNOVES00",
|
||||||
"source_session_id": visual_identity.get("source_session_id"),
|
"source_session_id": visual_identity.get("source_session_id"),
|
||||||
"graph_result_id": graph.result_id,
|
"graph_result_id": graph.result_id,
|
||||||
"visual_result_id": visual.result_id,
|
"visual_result_id": visual.result_id,
|
||||||
|
"semantic_result_id": semantic.result_id,
|
||||||
"temporal_frames_sha256": parity.get("temporal_frames_sha256"),
|
"temporal_frames_sha256": parity.get("temporal_frames_sha256"),
|
||||||
"threat_frames_sha256": parity.get("threat_frames_sha256"),
|
"threat_frames_sha256": parity.get("threat_frames_sha256"),
|
||||||
|
"semantic_frames_sha256": semantic_identity.get("semantic_frames_sha256"),
|
||||||
|
"semantic_masks_sha256": semantic_identity.get("semantic_masks_sha256"),
|
||||||
|
"semantic_taxonomy_sha256": semantic_identity.get("taxonomy_sha256"),
|
||||||
},
|
},
|
||||||
"method": {
|
"method": {
|
||||||
"graph_id": graph_manifest.get("graph_id"),
|
"graph_id": graph_manifest.get("graph_id"),
|
||||||
@@ -179,6 +204,9 @@ def publish_m47_reference_graph_lab(
|
|||||||
"graph-runtime.json": graph.result_root / "runtime.json",
|
"graph-runtime.json": graph.result_root / "runtime.json",
|
||||||
"visual-manifest.json": visual.result_root / "manifest.json",
|
"visual-manifest.json": visual.result_root / "manifest.json",
|
||||||
"visual-report.json": visual.result_root / "report.json",
|
"visual-report.json": visual.result_root / "report.json",
|
||||||
|
"semantic-manifest.json": semantic.result_root / SEMANTIC_SLAM_MANIFEST_NAME,
|
||||||
|
"semantic-report.json": semantic.result_root / SEMANTIC_SLAM_REPORT_NAME,
|
||||||
|
"semantic-taxonomy.json": semantic.result_root / SEMANTIC_SLAM_TAXONOMY_NAME,
|
||||||
}
|
}
|
||||||
for name, source in sources.items():
|
for name, source in sources.items():
|
||||||
shutil.copyfile(source, staging / name)
|
shutil.copyfile(source, staging / name)
|
||||||
@@ -190,6 +218,9 @@ def publish_m47_reference_graph_lab(
|
|||||||
"graph-runtime.json": "m47-runtime",
|
"graph-runtime.json": "m47-runtime",
|
||||||
"visual-manifest.json": "linked-visual-manifest",
|
"visual-manifest.json": "linked-visual-manifest",
|
||||||
"visual-report.json": "linked-visual-report",
|
"visual-report.json": "linked-visual-report",
|
||||||
|
"semantic-manifest.json": "linked-semantic-manifest",
|
||||||
|
"semantic-report.json": "linked-semantic-report",
|
||||||
|
"semantic-taxonomy.json": "linked-semantic-taxonomy",
|
||||||
}
|
}
|
||||||
schemas = {
|
schemas = {
|
||||||
"graph-manifest.json": graph_manifest.get("schema_version"),
|
"graph-manifest.json": graph_manifest.get("schema_version"),
|
||||||
@@ -197,6 +228,11 @@ def publish_m47_reference_graph_lab(
|
|||||||
"graph-runtime.json": graph_runtime.get("schema_version"),
|
"graph-runtime.json": graph_runtime.get("schema_version"),
|
||||||
"visual-manifest.json": visual.manifest.get("schema_version"),
|
"visual-manifest.json": visual.manifest.get("schema_version"),
|
||||||
"visual-report.json": visual.report.get("schema_version"),
|
"visual-report.json": visual.report.get("schema_version"),
|
||||||
|
"semantic-manifest.json": semantic.manifest.get("schema_version"),
|
||||||
|
"semantic-report.json": semantic.report.get("schema_version"),
|
||||||
|
"semantic-taxonomy.json": _read_json(
|
||||||
|
semantic.result_root / SEMANTIC_SLAM_TAXONOMY_NAME
|
||||||
|
).get("schema_version"),
|
||||||
"report.json": M47_REFERENCE_GRAPH_LAB_REPORT_SCHEMA,
|
"report.json": M47_REFERENCE_GRAPH_LAB_REPORT_SCHEMA,
|
||||||
}
|
}
|
||||||
artifacts = [_artifact(staging / name, role, schemas[name]) for name, role in roles.items()]
|
artifacts = [_artifact(staging / name, role, schemas[name]) for name, role in roles.items()]
|
||||||
@@ -247,7 +283,7 @@ def read_m47_reference_graph_lab(result_root: Path) -> M47ReferenceGraphLab:
|
|||||||
):
|
):
|
||||||
raise M47ReferenceGraphLabError("M4.7 LAB identity changed")
|
raise M47ReferenceGraphLabError("M4.7 LAB identity changed")
|
||||||
artifacts = manifest.get("artifacts")
|
artifacts = manifest.get("artifacts")
|
||||||
if not isinstance(artifacts, list) or len(artifacts) != 6:
|
if not isinstance(artifacts, list) or len(artifacts) != 9:
|
||||||
raise M47ReferenceGraphLabError("M4.7 LAB artifact inventory changed")
|
raise M47ReferenceGraphLabError("M4.7 LAB artifact inventory changed")
|
||||||
for value in artifacts:
|
for value in artifacts:
|
||||||
descriptor = _object(value, "LAB artifact")
|
descriptor = _object(value, "LAB artifact")
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ def build_e47_semantic_slam_router(
|
|||||||
"access": "read-only-diagnostic-shadow",
|
"access": "read-only-diagnostic-shadow",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@router.get("/results/{result_id}")
|
||||||
|
def get_result(result_id: str) -> dict[str, object]:
|
||||||
|
return _project_result(result(result_id))
|
||||||
|
|
||||||
@router.get("/results/{result_id}/timeline/chunk")
|
@router.get("/results/{result_id}/timeline/chunk")
|
||||||
def get_timeline_chunk(
|
def get_timeline_chunk(
|
||||||
result_id: str,
|
result_id: str,
|
||||||
|
|||||||
@@ -173,9 +173,7 @@ def test_catalog_projects_exact_diagnostic_only_view(
|
|||||||
assert item["status"] == "diagnostic-semantic-slam-shadow"
|
assert item["status"] == "diagnostic-semantic-slam-shadow"
|
||||||
assert item["base_m4_result_id"] == M4_RESULT_ID
|
assert item["base_m4_result_id"] == M4_RESULT_ID
|
||||||
assert item["provider"]["provider_id"] == "eomt-cityscapes-semantic-control/v1"
|
assert item["provider"]["provider_id"] == "eomt-cityscapes-semantic-control/v1"
|
||||||
assert item["temporal_binding"]["semantic_to_camera"] == (
|
assert item["temporal_binding"]["semantic_to_camera"] == ("exact-sequence-and-session-time")
|
||||||
"exact-sequence-and-session-time"
|
|
||||||
)
|
|
||||||
assert item["temporal_binding"]["physical_synchronization_proven"] is False
|
assert item["temporal_binding"]["physical_synchronization_proven"] is False
|
||||||
assert [entry["label"] for entry in item["taxonomy"]] == ["ambiguous", "road"]
|
assert [entry["label"] for entry in item["taxonomy"]] == ["ambiguous", "road"]
|
||||||
assert item["acceptance"] == {
|
assert item["acceptance"] == {
|
||||||
@@ -191,6 +189,13 @@ def test_catalog_projects_exact_diagnostic_only_view(
|
|||||||
assert item["navigation_or_safety_accepted"] is False
|
assert item["navigation_or_safety_accepted"] is False
|
||||||
assert item["actuation_allowed"] is False
|
assert item["actuation_allowed"] is False
|
||||||
|
|
||||||
|
get_result = _endpoint(
|
||||||
|
"/api/v1/laboratory/e47-semantic-slam/results/{result_id}",
|
||||||
|
root,
|
||||||
|
)
|
||||||
|
exact = get_result(RESULT_ID)
|
||||||
|
assert exact == item
|
||||||
|
|
||||||
|
|
||||||
def test_timeline_chunk_preserves_point_index_space_and_unavailable_sentinel(
|
def test_timeline_chunk_preserves_point_index_space_and_unavailable_sentinel(
|
||||||
publication: tuple[Path, Path],
|
publication: tuple[Path, Path],
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ def test_m47_lab_binds_worker_graph_to_exact_visual_replay(
|
|||||||
) -> None:
|
) -> None:
|
||||||
graph_root = tmp_path / "graph"
|
graph_root = tmp_path / "graph"
|
||||||
visual_root = tmp_path / "visual"
|
visual_root = tmp_path / "visual"
|
||||||
|
semantic_root = tmp_path / "semantic"
|
||||||
graph_root.mkdir()
|
graph_root.mkdir()
|
||||||
visual_root.mkdir()
|
visual_root.mkdir()
|
||||||
|
semantic_root.mkdir()
|
||||||
runtime = {
|
runtime = {
|
||||||
"schema_version": "missioncore.reference-graph-runtime-identity/v3",
|
"schema_version": "missioncore.reference-graph-runtime-identity/v3",
|
||||||
"worker_id": "worker-006",
|
"worker_id": "worker-006",
|
||||||
@@ -99,6 +101,20 @@ def test_m47_lab_binds_worker_graph_to_exact_visual_replay(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
visual_report = {"schema_version": "missioncore.perception-threat-replay-report/v2"}
|
visual_report = {"schema_version": "missioncore.perception-threat-replay-report/v2"}
|
||||||
|
semantic_identity = {
|
||||||
|
"base_m4_result_id": "m4-threat-replay-" + "6" * 64,
|
||||||
|
"source_session_id": "20260720T065719Z_viewer_live",
|
||||||
|
"semantic_frames_sha256": "7" * 64,
|
||||||
|
"semantic_masks_sha256": "8" * 64,
|
||||||
|
"taxonomy_sha256": "9" * 64,
|
||||||
|
"counts": {"frames": {"total": 4489, "mask_available": 4489}},
|
||||||
|
}
|
||||||
|
semantic_manifest = {
|
||||||
|
"schema_version": "missioncore.e47-semantic-slam-result/v1",
|
||||||
|
"identity": semantic_identity,
|
||||||
|
}
|
||||||
|
semantic_report = {"schema_version": "missioncore.e47-semantic-slam-report/v1"}
|
||||||
|
semantic_taxonomy = {"schema_version": "missioncore.e47-semantic-taxonomy/v1"}
|
||||||
for root, documents in (
|
for root, documents in (
|
||||||
(
|
(
|
||||||
graph_root,
|
graph_root,
|
||||||
@@ -112,6 +128,14 @@ def test_m47_lab_binds_worker_graph_to_exact_visual_replay(
|
|||||||
visual_root,
|
visual_root,
|
||||||
{"manifest.json": visual_manifest, "report.json": visual_report},
|
{"manifest.json": visual_manifest, "report.json": visual_report},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
semantic_root,
|
||||||
|
{
|
||||||
|
"manifest.json": semantic_manifest,
|
||||||
|
"report.json": semantic_report,
|
||||||
|
"taxonomy.json": semantic_taxonomy,
|
||||||
|
},
|
||||||
|
),
|
||||||
):
|
):
|
||||||
for name, document in documents.items():
|
for name, document in documents.items():
|
||||||
_write_json(root / name, document)
|
_write_json(root / name, document)
|
||||||
@@ -129,6 +153,12 @@ def test_m47_lab_binds_worker_graph_to_exact_visual_replay(
|
|||||||
manifest=visual_manifest,
|
manifest=visual_manifest,
|
||||||
report=visual_report,
|
report=visual_report,
|
||||||
)
|
)
|
||||||
|
semantic = SimpleNamespace(
|
||||||
|
result_id="e47-semantic-slam-" + "a" * 64,
|
||||||
|
result_root=semantic_root,
|
||||||
|
manifest=semantic_manifest,
|
||||||
|
report=semantic_report,
|
||||||
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"k1link.laboratory.m47_reference_graph.read_reference_graph_result",
|
"k1link.laboratory.m47_reference_graph.read_reference_graph_result",
|
||||||
lambda _: graph,
|
lambda _: graph,
|
||||||
@@ -137,16 +167,22 @@ def test_m47_lab_binds_worker_graph_to_exact_visual_replay(
|
|||||||
"k1link.laboratory.m47_reference_graph.read_threat_replay_result",
|
"k1link.laboratory.m47_reference_graph.read_threat_replay_result",
|
||||||
lambda _: visual,
|
lambda _: visual,
|
||||||
)
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"k1link.laboratory.m47_reference_graph.read_semantic_slam_replay_result",
|
||||||
|
lambda _: semantic,
|
||||||
|
)
|
||||||
|
|
||||||
runtime_root = tmp_path / "runtime"
|
runtime_root = tmp_path / "runtime"
|
||||||
result = publish_m47_reference_graph_lab(
|
result = publish_m47_reference_graph_lab(
|
||||||
graph_result_root=graph_root,
|
graph_result_root=graph_root,
|
||||||
visual_result_root=visual_root,
|
visual_result_root=visual_root,
|
||||||
|
semantic_result_root=semantic_root,
|
||||||
output_root=runtime_root / "m47" / "reference-graph-labs",
|
output_root=runtime_root / "m47" / "reference-graph-labs",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert read_m47_reference_graph_lab(result.result_root) == result
|
assert read_m47_reference_graph_lab(result.result_root) == result
|
||||||
assert result.report["visual_evidence"]["linked_result_id"] == visual.result_id
|
assert result.report["visual_evidence"]["linked_result_id"] == visual.result_id
|
||||||
|
assert result.report["visual_evidence"]["semantic_result_id"] == semantic.result_id
|
||||||
assert result.report["execution"]["worker_id"] == "worker-006"
|
assert result.report["execution"]["worker_id"] == "worker-006"
|
||||||
assert result.report["metrics"]["parity_mismatch_counts"] == mismatch_counts
|
assert result.report["metrics"]["parity_mismatch_counts"] == mismatch_counts
|
||||||
repository_root = Path(__file__).resolve().parents[1]
|
repository_root = Path(__file__).resolve().parents[1]
|
||||||
@@ -157,11 +193,11 @@ def test_m47_lab_binds_worker_graph_to_exact_visual_replay(
|
|||||||
item for item in registry.definitions if item.work_id == "m47-reference-graph-shadow"
|
item for item in registry.definitions if item.work_id == "m47-reference-graph-shadow"
|
||||||
)
|
)
|
||||||
proof = verify_laboratory_evidence_result(definition, result.result_root)
|
proof = verify_laboratory_evidence_result(definition, result.result_root)
|
||||||
assert proof["artifact_count"] == 6
|
assert proof["artifact_count"] == 9
|
||||||
projected = LaboratoryEvidenceReportService(registry, lambda: runtime_root).read(
|
projected = LaboratoryEvidenceReportService(registry, lambda: runtime_root).read(
|
||||||
definition.work_id,
|
definition.work_id,
|
||||||
result.result_id,
|
result.result_id,
|
||||||
)
|
)
|
||||||
assert projected["raw_report"]["schema_version"] == (
|
assert projected["raw_report"]["schema_version"] == (
|
||||||
"missioncore.reference-perception-graph-lab-report/v1"
|
"missioncore.reference-perception-graph-lab-report/v2"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user