Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5983883ffe | ||
|
|
6290c75210 | ||
|
|
3c81c39a1c | ||
|
|
932c5216dc | ||
|
|
f9a76fed0e | ||
|
|
c05db1fbe0 |
@@ -42,12 +42,14 @@ import {
|
||||
fetchM48LifecycleResult,
|
||||
} from "./m48ObjectCentricQuality";
|
||||
import { fetchM48SmallStaticRegression } from "./m48SmallStaticRegression";
|
||||
import { fetchM48StaticOccupancyQualification } from "./m48StaticOccupancyQualification";
|
||||
import { fetchM48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
|
||||
import { fetchM48TRiskQualityResult } from "./m48tRiskQuality";
|
||||
|
||||
export type AdvancedLaboratoryWorkId =
|
||||
| "m48-object-centric-quality"
|
||||
| "m48-small-static-passage-regression"
|
||||
| "m48-static-occupancy-qualification"
|
||||
| "m48s-fixed-class-detector"
|
||||
| "m48t-risk-quality-temporal"
|
||||
| "m47-reference-graph-shadow"
|
||||
@@ -94,6 +96,7 @@ export interface AdvancedLaboratoryIndexItem {
|
||||
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
|
||||
"m48-object-centric-quality",
|
||||
"m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification",
|
||||
"m48s-fixed-class-detector",
|
||||
"m48t-risk-quality-temporal",
|
||||
"m47-reference-graph-shadow",
|
||||
@@ -135,8 +138,9 @@ 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",
|
||||
"m48-static-occupancy-qualification": "m48-static-occupancy-qualification",
|
||||
"m48s-fixed-class-detector": "m48s-fixed-class-detector-lab",
|
||||
"m48t-risk-quality-temporal": "m48t-risk-quality-temporal-lab",
|
||||
"m48t-risk-quality-temporal": "(?:m48t-risk-quality-temporal-lab|m48q-native-risk-quality-lab)",
|
||||
"m47-reference-graph-shadow": "m47-reference-graph-lab",
|
||||
"m4-replay-threat": "m4-threat-replay",
|
||||
"l3-pointpillars-visual-audit": "l3-pointpillars-visual-audit",
|
||||
@@ -184,6 +188,7 @@ export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
|
||||
m47Graph: null,
|
||||
m48: null,
|
||||
m48SmallStatic: null,
|
||||
m48StaticOccupancy: null,
|
||||
m48s: null,
|
||||
m48t: null,
|
||||
m4Threat: null,
|
||||
@@ -312,6 +317,7 @@ export function advancedLaboratoryResultAvailable(
|
||||
): boolean {
|
||||
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 === "m48s-fixed-class-detector" ? results.m48s !== null
|
||||
: workId === "m48t-risk-quality-temporal" ? results.m48t !== null
|
||||
: workId === "m47-reference-graph-shadow" ? results.m47Graph !== null
|
||||
@@ -369,6 +375,9 @@ export async function fetchAdvancedLaboratoryResult(
|
||||
} 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 === "m48-static-occupancy-qualification") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8R2 qualification identity не выбрана.");
|
||||
results.m48StaticOccupancy = await fetchM48StaticOccupancyQualification(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 });
|
||||
|
||||
@@ -36,6 +36,7 @@ import type { M4ThreatReplayResult } from "./m4ReplayThreat";
|
||||
import type { M47ReferenceGraphLabResult } from "./m47ReferenceGraph";
|
||||
import type { M48AdvancedResult } from "./m48ObjectCentricQuality";
|
||||
import type { M48SmallStaticRegressionResult } from "./m48SmallStaticRegression";
|
||||
import type { M48StaticOccupancyQualificationResult } from "./m48StaticOccupancyQualification";
|
||||
import type { M48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
|
||||
import type { M48TRiskQualityResult } from "./m48tRiskQuality";
|
||||
|
||||
@@ -43,6 +44,7 @@ export interface AdvancedLaboratoryResults {
|
||||
m47Graph: M47ReferenceGraphLabResult | null;
|
||||
m48: M48AdvancedResult | null;
|
||||
m48SmallStatic: M48SmallStaticRegressionResult | null;
|
||||
m48StaticOccupancy: M48StaticOccupancyQualificationResult | null;
|
||||
m48s: M48SFixedClassDetectorResult | null;
|
||||
m48t: M48TRiskQualityResult | null;
|
||||
m4Threat: M4ThreatReplayResult | null;
|
||||
|
||||
@@ -967,10 +967,9 @@ export async function fetchAdvancedLaboratoryResults({
|
||||
const e39 = settledCatalogValue(settled[7]);
|
||||
const e40 = settledCatalogValue(settled[8]);
|
||||
return {
|
||||
m47Graph: null, m48: null, m48SmallStatic: null, m48s: null, m48t: null, m4Threat: null,
|
||||
l3: null, l31: null,
|
||||
l32: null,
|
||||
l33: null,
|
||||
m47Graph: null, m48: null, m48SmallStatic: null, m48StaticOccupancy: null,
|
||||
m48s: null, m48t: null, m4Threat: null,
|
||||
l3: null, l31: null, l32: null, l33: null,
|
||||
e31,
|
||||
e32,
|
||||
e33,
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
import type { LaboratoryFetch } from "./advancedResults";
|
||||
import type { M48Authority } from "./m48ObjectCentricQuality";
|
||||
|
||||
const RESULT_ID = /^m48-static-occupancy-qualification-[a-f0-9]{64}$/;
|
||||
const M47_RESULT_ID = /^m47-reference-graph-lab-[a-f0-9]{64}$/;
|
||||
const M48R1_RESULT_ID = /^m48-small-static-passage-regression-[a-f0-9]{64}$/;
|
||||
const ANCHOR_ID = /^anchor-[a-f0-9]{24}$/;
|
||||
|
||||
export interface M48StaticOccupancyMetrics {
|
||||
operatorStaticAnchorCount: number;
|
||||
baselineQualifiedCount: number;
|
||||
candidateQualifiedCount: number;
|
||||
unresolvedUnknownCount: number;
|
||||
criticalNearAnchorCount: number;
|
||||
criticalNearBaselineRecall: number;
|
||||
criticalNearCandidateRecall: number;
|
||||
approachAnchorCount: number;
|
||||
approachBaselineRecall: number;
|
||||
approachCandidateRecall: number;
|
||||
canonicalEngineeringAnchorCount: number;
|
||||
canonicalEngineeringRecall: number;
|
||||
falseFreeCount: number;
|
||||
}
|
||||
|
||||
export interface M48StaticOccupancyQualificationResult {
|
||||
resultId: string;
|
||||
referenceGraphLabResultId: string;
|
||||
smallStaticResultId: string;
|
||||
createdAtUtc: string;
|
||||
runLabel: "M4.8R2";
|
||||
pipelineId: "m4-current-rolling-plus-step-static-occupancy/v1";
|
||||
experimentId: "m48-static-occupancy-qualification/v1";
|
||||
accepted: boolean;
|
||||
metrics: M48StaticOccupancyMetrics;
|
||||
gates: {
|
||||
criticalNearCandidateRecall: boolean;
|
||||
approachCandidateRecall: boolean;
|
||||
canonicalEngineeringRecall: boolean;
|
||||
zeroFalseFree: boolean;
|
||||
independentTruthAvailable: false;
|
||||
};
|
||||
decision: {
|
||||
state: "accepted-bounded-static-occupancy-qualification" | "partial-static-occupancy-qualification";
|
||||
criticalNearCandidateReadyForShadow: boolean;
|
||||
productionAccepted: false;
|
||||
summary: string;
|
||||
nextAction: string;
|
||||
};
|
||||
authority: M48Authority;
|
||||
}
|
||||
|
||||
export interface M48StaticOccupancyCase {
|
||||
anchorId: string;
|
||||
clipId: string;
|
||||
sequence: number;
|
||||
extentXyxy: readonly [number, number, number, number];
|
||||
distanceM: number;
|
||||
distanceBand: "critical-near" | "approach" | "outside-qualified-bands";
|
||||
baselineQualified: boolean;
|
||||
candidateQualified: boolean;
|
||||
outcome: "candidate-qualified" | "unresolved-unknown-never-free";
|
||||
graphMatched: boolean;
|
||||
}
|
||||
|
||||
export class M48StaticOccupancyContractError extends Error {}
|
||||
|
||||
function objectValue(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new M48StaticOccupancyContractError(`${label}: ожидался объект.`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function text(value: unknown, label: string): string {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
throw new M48StaticOccupancyContractError(`${label}: ожидалась строка.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function numberValue(value: unknown, label: string): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
throw new M48StaticOccupancyContractError(`${label}: ожидалось число.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function integer(value: unknown, label: string): number {
|
||||
const parsed = numberValue(value, label);
|
||||
if (!Number.isInteger(parsed) || parsed < 0) {
|
||||
throw new M48StaticOccupancyContractError(`${label}: ожидалось целое число.`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function bool(value: unknown, label: string): boolean {
|
||||
if (typeof value !== "boolean") {
|
||||
throw new M48StaticOccupancyContractError(`${label}: ожидался флаг.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function exact(value: unknown, expected: string | boolean, label: string): void {
|
||||
if (value !== expected) throw new M48StaticOccupancyContractError(`${label}: нарушен контракт.`);
|
||||
}
|
||||
|
||||
function member<T extends string>(value: unknown, allowed: readonly T[], label: string): T {
|
||||
const parsed = text(value, label);
|
||||
if (!allowed.includes(parsed as T)) {
|
||||
throw new M48StaticOccupancyContractError(`${label}: недопустимое значение.`);
|
||||
}
|
||||
return parsed as T;
|
||||
}
|
||||
|
||||
function extent(value: unknown): readonly [number, number, number, number] {
|
||||
if (!Array.isArray(value) || value.length !== 4 || value.some((item) => typeof item !== "number" || !Number.isFinite(item))) {
|
||||
throw new M48StaticOccupancyContractError("M4.8R2.case.extent: нарушен контракт.");
|
||||
}
|
||||
const result = value as number[];
|
||||
if (result.some((item) => item < 0 || item > 1) || result[0]! >= result[2]! || result[1]! >= result[3]!) {
|
||||
throw new M48StaticOccupancyContractError("M4.8R2.case.extent: нарушена геометрия.");
|
||||
}
|
||||
return result as unknown as readonly [number, number, number, number];
|
||||
}
|
||||
|
||||
function authority(value: unknown): M48Authority {
|
||||
const row = objectValue(value, "M4.8R2.authority");
|
||||
exact(row.mode, "replay-simulated", "M4.8R2.authority.mode");
|
||||
exact(row.physical_live, false, "M4.8R2.authority.physical_live");
|
||||
exact(row.commands_enabled, false, "M4.8R2.authority.commands_enabled");
|
||||
exact(row.actuation_allowed, false, "M4.8R2.authority.actuation_allowed");
|
||||
exact(row.navigation_or_safety_accepted, false, "M4.8R2.authority.navigation_or_safety_accepted");
|
||||
return { mode: "replay-simulated", physicalLive: false, commandsEnabled: false, actuationAllowed: false, navigationOrSafetyAccepted: false };
|
||||
}
|
||||
|
||||
function metrics(value: unknown): M48StaticOccupancyMetrics {
|
||||
const row = objectValue(value, "M4.8R2.metrics");
|
||||
return {
|
||||
operatorStaticAnchorCount: integer(row.operator_static_anchor_count, "operator_static_anchor_count"),
|
||||
baselineQualifiedCount: integer(row.baseline_qualified_count, "baseline_qualified_count"),
|
||||
candidateQualifiedCount: integer(row.candidate_qualified_count, "candidate_qualified_count"),
|
||||
unresolvedUnknownCount: integer(row.unresolved_unknown_count, "unresolved_unknown_count"),
|
||||
criticalNearAnchorCount: integer(row.critical_near_anchor_count, "critical_near_anchor_count"),
|
||||
criticalNearBaselineRecall: numberValue(row.critical_near_baseline_recall, "critical_near_baseline_recall"),
|
||||
criticalNearCandidateRecall: numberValue(row.critical_near_candidate_recall, "critical_near_candidate_recall"),
|
||||
approachAnchorCount: integer(row.approach_anchor_count, "approach_anchor_count"),
|
||||
approachBaselineRecall: numberValue(row.approach_baseline_recall, "approach_baseline_recall"),
|
||||
approachCandidateRecall: numberValue(row.approach_candidate_recall, "approach_candidate_recall"),
|
||||
canonicalEngineeringAnchorCount: integer(row.canonical_engineering_anchor_count, "canonical_engineering_anchor_count"),
|
||||
canonicalEngineeringRecall: numberValue(row.canonical_engineering_recall, "canonical_engineering_recall"),
|
||||
falseFreeCount: integer(row.false_free_count, "false_free_count"),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchM48StaticOccupancyQualification(
|
||||
resultId: string,
|
||||
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<M48StaticOccupancyQualificationResult> {
|
||||
if (!RESULT_ID.test(resultId)) throw new M48StaticOccupancyContractError("M4.8R2 identity недопустима.");
|
||||
const response = await fetcher(`/api/v1/laboratory/m48/regressions/static-occupancy/${encodeURIComponent(resultId)}`, { method: "GET", headers: { Accept: "application/json" }, signal });
|
||||
if (!response.ok) throw new M48StaticOccupancyContractError(`M4.8R2 недоступен: HTTP ${response.status}.`);
|
||||
const payload = objectValue(await response.json(), "M4.8R2");
|
||||
exact(payload.schema_version, "missioncore.m48-static-occupancy-qualification-result-view/v1", "M4.8R2.schema_version");
|
||||
const m47 = text(payload.reference_graph_lab_result_id, "M4.8R2.reference_graph_lab_result_id");
|
||||
const m48r1 = text(payload.small_static_result_id, "M4.8R2.small_static_result_id");
|
||||
if (!M47_RESULT_ID.test(m47) || !M48R1_RESULT_ID.test(m48r1)) throw new M48StaticOccupancyContractError("M4.8R2 source identity нарушена.");
|
||||
exact(payload.run_label, "M4.8R2", "M4.8R2.run_label");
|
||||
exact(payload.pipeline_id, "m4-current-rolling-plus-step-static-occupancy/v1", "M4.8R2.pipeline_id");
|
||||
exact(payload.experiment_id, "m48-static-occupancy-qualification/v1", "M4.8R2.experiment_id");
|
||||
exact(payload.ground_truth, false, "M4.8R2.ground_truth");
|
||||
exact(payload.independent_truth, false, "M4.8R2.independent_truth");
|
||||
const gates = objectValue(payload.gates, "M4.8R2.gates");
|
||||
const decision = objectValue(payload.decision, "M4.8R2.decision");
|
||||
exact(decision.production_accepted, false, "M4.8R2.decision.production_accepted");
|
||||
return {
|
||||
resultId,
|
||||
referenceGraphLabResultId: m47,
|
||||
smallStaticResultId: m48r1,
|
||||
createdAtUtc: text(payload.created_at_utc, "M4.8R2.created_at_utc"),
|
||||
runLabel: "M4.8R2",
|
||||
pipelineId: "m4-current-rolling-plus-step-static-occupancy/v1",
|
||||
experimentId: "m48-static-occupancy-qualification/v1",
|
||||
accepted: bool(payload.accepted, "M4.8R2.accepted"),
|
||||
metrics: metrics(payload.metrics),
|
||||
gates: {
|
||||
criticalNearCandidateRecall: bool(gates.critical_near_candidate_recall, "critical_near_candidate_recall"),
|
||||
approachCandidateRecall: bool(gates.approach_candidate_recall, "approach_candidate_recall"),
|
||||
canonicalEngineeringRecall: bool(gates.canonical_engineering_recall, "canonical_engineering_recall"),
|
||||
zeroFalseFree: bool(gates.zero_false_free, "zero_false_free"),
|
||||
independentTruthAvailable: false,
|
||||
},
|
||||
decision: {
|
||||
state: member(
|
||||
decision.state,
|
||||
[
|
||||
"accepted-bounded-static-occupancy-qualification",
|
||||
"partial-static-occupancy-qualification",
|
||||
] as const,
|
||||
"M4.8R2.decision.state",
|
||||
),
|
||||
criticalNearCandidateReadyForShadow: bool(decision.critical_near_candidate_ready_for_shadow, "critical_near_candidate_ready_for_shadow"),
|
||||
productionAccepted: false,
|
||||
summary: text(decision.summary, "M4.8R2.decision.summary"),
|
||||
nextAction: text(decision.next_action, "M4.8R2.decision.next_action"),
|
||||
},
|
||||
authority: authority(payload.authority),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchM48StaticOccupancyCases(
|
||||
resultId: string,
|
||||
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<readonly M48StaticOccupancyCase[]> {
|
||||
if (!RESULT_ID.test(resultId)) throw new M48StaticOccupancyContractError("M4.8R2 identity недопустима.");
|
||||
const response = await fetcher(`/api/v1/laboratory/m48/regressions/static-occupancy/${encodeURIComponent(resultId)}/cases`, { method: "GET", headers: { Accept: "application/json" }, signal });
|
||||
if (!response.ok) throw new M48StaticOccupancyContractError(`M4.8R2 cases недоступны: HTTP ${response.status}.`);
|
||||
const payload = objectValue(await response.json(), "M4.8R2 cases");
|
||||
exact(payload.schema_version, "missioncore.m48-static-occupancy-case-catalog/v1", "M4.8R2 cases.schema_version");
|
||||
if (!Array.isArray(payload.cases) || payload.cases.length !== integer(payload.case_count, "M4.8R2.case_count")) throw new M48StaticOccupancyContractError("M4.8R2 cases: размер изменился.");
|
||||
return payload.cases.map((value, index) => {
|
||||
const row = objectValue(value, `M4.8R2.case[${index}]`);
|
||||
const anchorId = text(row.anchor_id, "anchor_id");
|
||||
if (!ANCHOR_ID.test(anchorId)) throw new M48StaticOccupancyContractError("M4.8R2 anchor identity нарушена.");
|
||||
const graph = objectValue(row.accepted_graph, "accepted_graph");
|
||||
const band = member(
|
||||
row.distance_band,
|
||||
["critical-near", "approach", "outside-qualified-bands"] as const,
|
||||
"distance_band",
|
||||
);
|
||||
const outcome = member(
|
||||
row.outcome,
|
||||
["candidate-qualified", "unresolved-unknown-never-free"] as const,
|
||||
"outcome",
|
||||
);
|
||||
return {
|
||||
anchorId,
|
||||
clipId: text(row.clip_id, "clip_id"),
|
||||
sequence: integer(row.sequence, "sequence"),
|
||||
extentXyxy: extent(row.extent_xyxy),
|
||||
distanceM: numberValue(row.distance_m, "distance_m"),
|
||||
distanceBand: band,
|
||||
baselineQualified: bool(row.baseline_qualified, "baseline_qualified"),
|
||||
candidateQualified: bool(row.candidate_qualified, "candidate_qualified"),
|
||||
outcome,
|
||||
graphMatched: bool(graph.matched, "accepted_graph.matched"),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -19,7 +19,8 @@ export interface M48TReviewCase {
|
||||
sha256: string;
|
||||
}
|
||||
|
||||
export interface M48TRiskQualityResult {
|
||||
export interface M48TLegacyRiskQualityResult {
|
||||
variant: "legacy-coco-quality";
|
||||
resultId: string;
|
||||
createdAtUtc: string;
|
||||
source: {
|
||||
@@ -93,6 +94,90 @@ export interface M48TRiskQualityResult {
|
||||
limitations: readonly string[];
|
||||
}
|
||||
|
||||
export type M48QRiskFamily = "person" | "animal" | "light-road-user" | "vehicle";
|
||||
|
||||
export interface M48QNativeProposal {
|
||||
proposalId: string;
|
||||
className: string;
|
||||
riskFamily: M48QRiskFamily;
|
||||
score: number;
|
||||
boxXyxy: readonly [number, number, number, number];
|
||||
}
|
||||
|
||||
export interface M48QNativeReviewCase {
|
||||
caseId: string;
|
||||
sequence: number;
|
||||
frameId: string;
|
||||
evidenceTimeNs: number;
|
||||
imageUrl: string;
|
||||
width: 800;
|
||||
height: 600;
|
||||
byteLength: number;
|
||||
sha256: string;
|
||||
selectionBuckets: readonly string[];
|
||||
nativeDetectionCount: number;
|
||||
legacyDetectionCount: number;
|
||||
matchedDetectionCount: number;
|
||||
proposals: readonly M48QNativeProposal[];
|
||||
}
|
||||
|
||||
export interface M48QNativeRiskQualityResult {
|
||||
variant: "native-risk-review";
|
||||
resultId: string;
|
||||
createdAtUtc: string;
|
||||
source: {
|
||||
sourceId: "RAVNOVES00";
|
||||
frameCount: 4489;
|
||||
width: 800;
|
||||
height: 600;
|
||||
geometricResampling: false;
|
||||
};
|
||||
candidate: {
|
||||
providerId: string;
|
||||
modelId: "rf_detr_large_native_kb4:1";
|
||||
preprocessId: "raw-kb4-uint8-fused-mask-rgb-pad8-imagenet-trt/v0";
|
||||
minimumScore: 0.25;
|
||||
};
|
||||
execution: {
|
||||
effectiveWorldStateFps: number;
|
||||
worldStateCompletionP95Ms: number;
|
||||
detectorTotalP95Ms: number;
|
||||
gpuUtilizationP95Percent: number;
|
||||
gpuMemoryMaximumMib: number;
|
||||
additionalInferencePasses: 0;
|
||||
};
|
||||
selection: {
|
||||
caseCount: 24;
|
||||
minimumSequenceSeparation: number;
|
||||
bucketCoverage: Readonly<Record<string, number>>;
|
||||
classCounts: Readonly<Record<string, number>>;
|
||||
};
|
||||
runtime: {
|
||||
deliveryRatio: number;
|
||||
integratedGatePassed: true;
|
||||
operatingTargetGatePassed: true;
|
||||
};
|
||||
parity: {
|
||||
precision: number;
|
||||
recall: number;
|
||||
meanIou: number;
|
||||
};
|
||||
acceptance: {
|
||||
reviewReady: true;
|
||||
independentQualityEvaluated: false;
|
||||
semanticCandidateAccepted: false;
|
||||
};
|
||||
review: {
|
||||
cases: readonly M48QNativeReviewCase[];
|
||||
};
|
||||
method: M48TLaboratoryMethod;
|
||||
limitations: readonly string[];
|
||||
}
|
||||
|
||||
export type M48TRiskQualityResult =
|
||||
| M48TLegacyRiskQualityResult
|
||||
| M48QNativeRiskQualityResult;
|
||||
|
||||
type LaboratoryFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
||||
|
||||
export class M48TContractError extends Error {}
|
||||
@@ -173,9 +258,10 @@ function method(value: unknown): M48TLaboratoryMethod {
|
||||
};
|
||||
}
|
||||
|
||||
function parseResult(value: unknown, expectedResultId: string): M48TRiskQualityResult {
|
||||
function parseLegacyResult(value: unknown, expectedResultId: string): M48TLegacyRiskQualityResult {
|
||||
const raw = object(value, "M4.8T result");
|
||||
exact(raw.schema_version, "missioncore.m48t-risk-quality-temporal-view/v1", "M4.8T schema");
|
||||
exact(raw.variant, "legacy-coco-quality", "M4.8T variant");
|
||||
exact(raw.result_id, expectedResultId, "M4.8T identity");
|
||||
exact(raw.status, "complete-quality-gate-failed-temporal-invariant-passed", "M4.8T status");
|
||||
exact(raw.access, "read-only", "M4.8T access");
|
||||
@@ -234,6 +320,7 @@ function parseResult(value: unknown, expectedResultId: string): M48TRiskQualityR
|
||||
for (const [key, count] of Object.entries(failures)) parsedFailures[key] = integer(count, `M4.8T failure ${key}`);
|
||||
const temporalConfiguration = object(configuration.temporal, "M4.8T temporal configuration");
|
||||
return {
|
||||
variant: "legacy-coco-quality",
|
||||
resultId: expectedResultId,
|
||||
createdAtUtc: text(raw.created_at_utc, "M4.8T created at"),
|
||||
source: {
|
||||
@@ -306,6 +393,207 @@ function parseResult(value: unknown, expectedResultId: string): M48TRiskQualityR
|
||||
};
|
||||
}
|
||||
|
||||
function numberRecord(value: unknown, label: string): Readonly<Record<string, number>> {
|
||||
const raw = object(value, label);
|
||||
const parsed: Record<string, number> = {};
|
||||
for (const [key, item] of Object.entries(raw)) parsed[key] = integer(item, `${label}.${key}`);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function nativeRiskFamily(value: unknown): M48QRiskFamily {
|
||||
const parsed = text(value, "M4.8Q risk family");
|
||||
if (!["person", "animal", "light-road-user", "vehicle"].includes(parsed)) {
|
||||
throw new M48TContractError("M4.8Q risk family: неизвестное значение.");
|
||||
}
|
||||
return parsed as M48QRiskFamily;
|
||||
}
|
||||
|
||||
function nativeProposal(value: unknown): M48QNativeProposal {
|
||||
const raw = object(value, "M4.8Q proposal");
|
||||
const coordinates = array(raw.box_xyxy, "M4.8Q proposal box").map((item) =>
|
||||
number(item, "M4.8Q proposal coordinate")
|
||||
);
|
||||
if (
|
||||
coordinates.length !== 4
|
||||
|| coordinates[0] >= coordinates[2]
|
||||
|| coordinates[1] >= coordinates[3]
|
||||
|| coordinates[2] > 800
|
||||
|| coordinates[3] > 600
|
||||
) {
|
||||
throw new M48TContractError("M4.8Q proposal box: нарушена raw-raster геометрия.");
|
||||
}
|
||||
const score = number(raw.score, "M4.8Q proposal score");
|
||||
if (score < 0.25 || score > 1) throw new M48TContractError("M4.8Q proposal score: нарушен threshold.");
|
||||
return {
|
||||
proposalId: text(raw.proposal_id, "M4.8Q proposal id"),
|
||||
className: text(raw.class_name, "M4.8Q proposal class"),
|
||||
riskFamily: nativeRiskFamily(raw.risk_family),
|
||||
score,
|
||||
boxXyxy: coordinates as [number, number, number, number],
|
||||
};
|
||||
}
|
||||
|
||||
function parseNativeResult(value: unknown, expectedResultId: string): M48QNativeRiskQualityResult {
|
||||
const raw = object(value, "M4.8Q result");
|
||||
exact(raw.schema_version, "missioncore.m48q-native-risk-quality-view/v1", "M4.8Q schema");
|
||||
exact(raw.variant, "native-risk-review", "M4.8Q variant");
|
||||
exact(raw.result_id, expectedResultId, "M4.8Q identity");
|
||||
exact(raw.status, "complete-review-ready-quality-not-adjudicated", "M4.8Q status");
|
||||
exact(raw.access, "read-only", "M4.8Q access");
|
||||
exact(raw.ground_truth, false, "M4.8Q ground truth");
|
||||
|
||||
const source = object(raw.source, "M4.8Q source");
|
||||
exact(source.source_id, "RAVNOVES00", "M4.8Q source id");
|
||||
exact(source.frame_count, 4489, "M4.8Q source frames");
|
||||
exact(source.raster_width, 800, "M4.8Q source width");
|
||||
exact(source.raster_height, 600, "M4.8Q source height");
|
||||
exact(source.geometric_resampling, false, "M4.8Q source resampling");
|
||||
exact(source.rectification, false, "M4.8Q source rectification");
|
||||
exact(source.warp, false, "M4.8Q source warp");
|
||||
|
||||
const configuration = object(raw.configuration, "M4.8Q configuration");
|
||||
const candidate = object(configuration.candidate, "M4.8Q candidate");
|
||||
const execution = object(raw.execution, "M4.8Q execution");
|
||||
const metrics = object(raw.metrics, "M4.8Q metrics");
|
||||
const selection = object(metrics.selection, "M4.8Q selection");
|
||||
const runtime = object(metrics.runtime, "M4.8Q runtime");
|
||||
const parity = object(metrics.native_tensor_parity, "M4.8Q parity");
|
||||
const acceptance = object(raw.acceptance, "M4.8Q acceptance");
|
||||
exact(acceptance.review_ready, true, "M4.8Q review readiness");
|
||||
exact(acceptance.integrated_runtime_gate_passed, true, "M4.8Q runtime gate");
|
||||
exact(acceptance.independent_quality_evaluated, false, "M4.8Q quality state");
|
||||
exact(acceptance.semantic_candidate_accepted, false, "M4.8Q candidate state");
|
||||
exact(selection.case_count, 24, "M4.8Q selected cases");
|
||||
|
||||
const review = object(raw.review, "M4.8Q review");
|
||||
const raster = object(review.source_raster, "M4.8Q review raster");
|
||||
exact(raster.width, 800, "M4.8Q review width");
|
||||
exact(raster.height, 600, "M4.8Q review height");
|
||||
const overlay = object(review.overlay, "M4.8Q overlay");
|
||||
exact(overlay.client_rendered, true, "M4.8Q client overlay");
|
||||
exact(overlay.toggleable, true, "M4.8Q overlay toggle");
|
||||
const cases = array(review.cases, "M4.8Q review cases").map((value) => {
|
||||
const item = object(value, "M4.8Q review case");
|
||||
const caseId = text(item.case_id, "M4.8Q case id");
|
||||
if (!/^[0-9]{6}$/.test(caseId)) throw new M48TContractError("M4.8Q case identity нарушена.");
|
||||
const sequence = integer(item.sequence, "M4.8Q case sequence");
|
||||
exact(caseId, sequence.toString().padStart(6, "0"), "M4.8Q case/sequence identity");
|
||||
exact(item.frame_id, `frame-${caseId}`, "M4.8Q frame identity");
|
||||
exact(item.media_type, "image/jpeg", "M4.8Q case media");
|
||||
exact(item.width, 800, "M4.8Q case width");
|
||||
exact(item.height, 600, "M4.8Q case height");
|
||||
exact(item.geometric_resampling, false, "M4.8Q case resampling");
|
||||
const comparison = object(item.comparison, "M4.8Q comparison");
|
||||
const proposals = array(item.proposals, "M4.8Q proposals").map(nativeProposal);
|
||||
if (!proposals.length) throw new M48TContractError("M4.8Q proposals: пустой review case.");
|
||||
return {
|
||||
caseId,
|
||||
sequence,
|
||||
frameId: `frame-${caseId}`,
|
||||
evidenceTimeNs: integer(item.evidence_time_ns, "M4.8Q evidence time"),
|
||||
imageUrl: text(item.image_url, "M4.8Q image URL"),
|
||||
width: 800 as const,
|
||||
height: 600 as const,
|
||||
byteLength: integer(item.byte_length, "M4.8Q image bytes"),
|
||||
sha256: sha(item.sha256, "M4.8Q image SHA"),
|
||||
selectionBuckets: array(item.selection_buckets, "M4.8Q selection buckets").map((bucket) =>
|
||||
text(bucket, "M4.8Q selection bucket")
|
||||
),
|
||||
nativeDetectionCount: integer(comparison.native_detection_count, "M4.8Q native count"),
|
||||
legacyDetectionCount: integer(comparison.legacy_704_detection_count, "M4.8Q legacy count"),
|
||||
matchedDetectionCount: integer(
|
||||
comparison.matched_detection_count_iou_at_least_0_5,
|
||||
"M4.8Q matched count",
|
||||
),
|
||||
proposals,
|
||||
};
|
||||
});
|
||||
if (cases.length !== 24 || new Set(cases.map(({ caseId }) => caseId)).size !== 24) {
|
||||
throw new M48TContractError("M4.8Q review catalog: нарушен размер.");
|
||||
}
|
||||
|
||||
return {
|
||||
variant: "native-risk-review",
|
||||
resultId: expectedResultId,
|
||||
createdAtUtc: text(raw.created_at_utc, "M4.8Q created at"),
|
||||
source: {
|
||||
sourceId: "RAVNOVES00",
|
||||
frameCount: 4489,
|
||||
width: 800,
|
||||
height: 600,
|
||||
geometricResampling: false,
|
||||
},
|
||||
candidate: {
|
||||
providerId: text(candidate.provider_id, "M4.8Q provider"),
|
||||
modelId: exact(candidate.model_id, "rf_detr_large_native_kb4:1", "M4.8Q model"),
|
||||
preprocessId: exact(
|
||||
candidate.preprocess_id,
|
||||
"raw-kb4-uint8-fused-mask-rgb-pad8-imagenet-trt/v0",
|
||||
"M4.8Q preprocess",
|
||||
),
|
||||
minimumScore: exact(candidate.minimum_score, 0.25, "M4.8Q threshold"),
|
||||
},
|
||||
execution: {
|
||||
effectiveWorldStateFps: number(execution.effective_world_state_fps, "M4.8Q FPS"),
|
||||
worldStateCompletionP95Ms: number(
|
||||
execution.world_state_completion_p95_ms,
|
||||
"M4.8Q world-state p95",
|
||||
),
|
||||
detectorTotalP95Ms: number(execution.detector_total_p95_ms, "M4.8Q detector p95"),
|
||||
gpuUtilizationP95Percent: number(
|
||||
execution.gpu_utilization_p95_percent,
|
||||
"M4.8Q GPU p95",
|
||||
),
|
||||
gpuMemoryMaximumMib: number(
|
||||
execution.gpu_memory_used_maximum_mib,
|
||||
"M4.8Q VRAM maximum",
|
||||
),
|
||||
additionalInferencePasses: exact(
|
||||
execution.additional_inference_passes,
|
||||
0,
|
||||
"M4.8Q inference passes",
|
||||
),
|
||||
},
|
||||
selection: {
|
||||
caseCount: 24,
|
||||
minimumSequenceSeparation: integer(
|
||||
selection.minimum_sequence_separation,
|
||||
"M4.8Q case separation",
|
||||
),
|
||||
bucketCoverage: numberRecord(selection.selected_bucket_coverage, "M4.8Q bucket coverage"),
|
||||
classCounts: numberRecord(selection.selected_class_counts, "M4.8Q class counts"),
|
||||
},
|
||||
runtime: {
|
||||
deliveryRatio: number(runtime.delivery_ratio, "M4.8Q delivery ratio"),
|
||||
integratedGatePassed: exact(
|
||||
runtime.integrated_runtime_gate_passed,
|
||||
true,
|
||||
"M4.8Q integrated gate",
|
||||
),
|
||||
operatingTargetGatePassed: exact(
|
||||
runtime.operating_target_gate_passed,
|
||||
true,
|
||||
"M4.8Q target gate",
|
||||
),
|
||||
},
|
||||
parity: {
|
||||
precision: number(parity.risk_detection_precision, "M4.8Q parity precision"),
|
||||
recall: number(parity.risk_detection_recall, "M4.8Q parity recall"),
|
||||
meanIou: number(parity.matched_mean_iou, "M4.8Q parity IoU"),
|
||||
},
|
||||
acceptance: {
|
||||
reviewReady: true,
|
||||
independentQualityEvaluated: false,
|
||||
semanticCandidateAccepted: false,
|
||||
},
|
||||
review: { cases },
|
||||
method: method(raw.method),
|
||||
limitations: array(raw.limitations, "M4.8Q limitations").map((item) =>
|
||||
text(item, "M4.8Q limitation")
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchM48TRiskQualityResult(
|
||||
resultId: string,
|
||||
{
|
||||
@@ -313,7 +601,7 @@ export async function fetchM48TRiskQualityResult(
|
||||
signal,
|
||||
}: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<M48TRiskQualityResult> {
|
||||
if (!/^m48t-risk-quality-temporal-lab-[a-f0-9]{64}$/.test(resultId)) {
|
||||
if (!/^(?:m48t-risk-quality-temporal-lab|m48q-native-risk-quality-lab)-[a-f0-9]{64}$/.test(resultId)) {
|
||||
throw new M48TContractError("M4.8T result identity недопустима.");
|
||||
}
|
||||
const response = await fetcher(`/api/v1/laboratory/m48t/risk-quality/results/${resultId}`, {
|
||||
@@ -322,5 +610,9 @@ export async function fetchM48TRiskQualityResult(
|
||||
signal,
|
||||
});
|
||||
if (!response.ok) throw new M48TContractError(`M4.8T недоступен: HTTP ${response.status}.`);
|
||||
return parseResult(await response.json(), resultId);
|
||||
const payload: unknown = await response.json();
|
||||
const raw = object(payload, "M4.8T/M4.8Q result");
|
||||
return raw.schema_version === "missioncore.m48q-native-risk-quality-view/v1"
|
||||
? parseNativeResult(payload, resultId)
|
||||
: parseLegacyResult(payload, resultId);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import { M4ReplayThreatResultView } from "./M4ReplayThreatResult";
|
||||
import { M47ReferenceGraphResultView } from "./M47ReferenceGraphResult";
|
||||
import { M48ObjectCentricQualityResultView } from "./M48ObjectCentricQualityResult";
|
||||
import { M48SmallStaticPassageRegressionResultView } from "./M48SmallStaticPassageRegressionResult";
|
||||
import { M48StaticOccupancyQualificationResultView } from "./M48StaticOccupancyQualificationResult";
|
||||
import { M48SFixedClassDetectorResultView } from "./M48SFixedClassDetectorResult";
|
||||
import { M48TRiskQualityResultView } from "./M48TRiskQualityResult";
|
||||
|
||||
@@ -94,6 +95,9 @@ export function AdvancedLaboratoryResult({
|
||||
if (workId === "m48-small-static-passage-regression" && results.m48SmallStatic) {
|
||||
return <M48SmallStaticPassageRegressionResultView rigLabel={rigLabel} result={results.m48SmallStatic} />;
|
||||
}
|
||||
if (workId === "m48-static-occupancy-qualification" && results.m48StaticOccupancy) {
|
||||
return <M48StaticOccupancyQualificationResultView rigLabel={rigLabel} result={results.m48StaticOccupancy} />;
|
||||
}
|
||||
if (workId === "m48s-fixed-class-detector" && results.m48s) {
|
||||
return <M48SFixedClassDetectorResultView rigLabel={rigLabel} result={results.m48s} />;
|
||||
}
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Icon } from "@nodedc/ui-react";
|
||||
|
||||
import { fetchM47ReferenceGraphLab, type M47ReferenceGraphLabResult } from "../../core/laboratory/m47ReferenceGraph";
|
||||
import {
|
||||
fetchM48StaticOccupancyCases,
|
||||
type M48StaticOccupancyCase,
|
||||
type M48StaticOccupancyQualificationResult,
|
||||
} from "../../core/laboratory/m48StaticOccupancyQualification";
|
||||
import {
|
||||
M4ReplayThreatVisual,
|
||||
type M4ReplayThreatReviewAnchor,
|
||||
} from "./M4ReplayThreatVisual";
|
||||
|
||||
function message(error: unknown): string {
|
||||
return error instanceof Error && error.message.trim()
|
||||
? error.message
|
||||
: "Каноническое graph evidence M4.8R2 недоступно.";
|
||||
}
|
||||
|
||||
export function M48StaticOccupancyQualificationEvidence({
|
||||
result,
|
||||
}: {
|
||||
result: M48StaticOccupancyQualificationResult;
|
||||
}) {
|
||||
const [graph, setGraph] = useState<M47ReferenceGraphLabResult | null>(null);
|
||||
const [cases, setCases] = useState<readonly M48StaticOccupancyCase[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
void Promise.all([
|
||||
fetchM47ReferenceGraphLab({
|
||||
resultId: result.referenceGraphLabResultId,
|
||||
signal: controller.signal,
|
||||
}),
|
||||
fetchM48StaticOccupancyCases(result.resultId, { signal: controller.signal }),
|
||||
])
|
||||
.then(([nextGraph, nextCases]) => {
|
||||
if (controller.signal.aborted) return;
|
||||
if (nextCases.some((item) => item.sequence < 1 || item.sequence > nextGraph.frames.expected)) {
|
||||
throw new Error("M4.8R2 anchor вышел за immutable timeline Canonical Reference Graph.");
|
||||
}
|
||||
setGraph(nextGraph);
|
||||
setCases(nextCases);
|
||||
})
|
||||
.catch((caught: unknown) => {
|
||||
if (!controller.signal.aborted) setError(message(caught));
|
||||
})
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) setLoading(false);
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [result.referenceGraphLabResultId, result.resultId]);
|
||||
|
||||
const reviewAnchors = useMemo<readonly M4ReplayThreatReviewAnchor[]>(() => (
|
||||
cases.map((item) => ({
|
||||
id: item.anchorId,
|
||||
sourceSequence: item.sequence - 1,
|
||||
extentXyxyNormalized: item.extentXyxy,
|
||||
matchedAtThreshold: item.candidateQualified,
|
||||
}))
|
||||
), [cases]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="l3-visual-audit__state" role="status">
|
||||
<span className="busy-indicator" aria-hidden="true" />
|
||||
<span>Открываем M4.7 graph и M4.8R2 static anchors</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (error || !graph) {
|
||||
return (
|
||||
<div className="l3-visual-audit__state" role="alert">
|
||||
<Icon name="alert" size={18} />
|
||||
<span>{error ?? "Canonical Reference Graph не связан с M4.8R2."}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<M4ReplayThreatVisual
|
||||
resultId={graph.visual.resultId}
|
||||
semantic={{ resultId: graph.semantic.resultId, taxonomy: graph.semantic.taxonomy }}
|
||||
reviewAnchors={reviewAnchors}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
LaboratoryEvidence,
|
||||
LaboratoryResultSummary,
|
||||
LaboratorySummary,
|
||||
LaboratoryWorkTemplate,
|
||||
} from "../../components/laboratory/LaboratoryPresentation";
|
||||
import type { M48StaticOccupancyQualificationResult } from "../../core/laboratory/m48StaticOccupancyQualification";
|
||||
import { M48StaticOccupancyQualificationEvidence } from "./M48StaticOccupancyQualificationEvidence";
|
||||
|
||||
function percent(value: number): string {
|
||||
return `${(value * 100).toLocaleString("ru-RU", { maximumFractionDigits: 1 })}%`;
|
||||
}
|
||||
|
||||
export function M48StaticOccupancyQualificationResultView({
|
||||
rigLabel,
|
||||
result,
|
||||
}: {
|
||||
rigLabel: string;
|
||||
result: M48StaticOccupancyQualificationResult;
|
||||
}) {
|
||||
const nearReady = result.decision.criticalNearCandidateReadyForShadow;
|
||||
const status = nearReady
|
||||
? "0–8 м: кандидат закрыл все assisted-якоря; готов к Worker shadow"
|
||||
: "0–8 м: статическая occupancy ещё не закрыта";
|
||||
return (
|
||||
<LaboratoryWorkTemplate
|
||||
summary={(
|
||||
<LaboratorySummary
|
||||
title="M4.8R2 · консервативная static occupancy"
|
||||
description="RF-DETR не изменяется. Отдельный детерминированный прогон проверяет, удерживает ли current/rolling LiDAR низкие полусферы, столбы и другие безымянные ограничения, и оценивает уже вычисляемый CPU-признак low-step как additive occupied-only слой."
|
||||
status={status}
|
||||
statusTone={nearReady ? "success" : "warning"}
|
||||
facts={[
|
||||
{ label: "Конфигурация", value: `${rigLabel} RIGHT · Canonical Reference Graph · VIDEO/CAMERA/3D/PLAN/SEMANTICS` },
|
||||
{ label: "Пайплайн", value: result.pipelineId },
|
||||
{ label: "Прогон", value: `${result.runLabel} · immutable ${result.resultId}` },
|
||||
{ label: "Нагрузка", value: "CPU-only geometry · 0 дополнительных detector inference" },
|
||||
{ label: "Authority", value: "REPLAY-SIMULATED · free-space/commands/actuation OFF" },
|
||||
]}
|
||||
brief={{
|
||||
question: "Не превратятся ли близкие полусферы, столбы и другие статические ограничения в пропуск только потому, что камера не знает их название?",
|
||||
approach: `На ${result.metrics.operatorStaticAnchorCount} существующих operator-assisted static anchors принятую current/rolling occupancy сравнили с additive low-step evidence. Отдельно сохранены четыре канонических hemisphere-якоря кадров 1880/2584.`,
|
||||
principalResult: `Принятый граф покрывает ${result.metrics.baselineQualifiedCount}/${result.metrics.operatorStaticAnchorCount}; additive кандидат — ${result.metrics.candidateQualifiedCount}/${result.metrics.operatorStaticAnchorCount}. В критической зоне 0–8 м: ${percent(result.metrics.criticalNearCandidateRecall)}.`,
|
||||
limitation: "Это assisted diagnostic, а не independent truth. Кандидат пока не включён в Worker и может увеличить ложную занятость; требуются полный shadow-прогон, FPS и occupancy-volume gate.",
|
||||
}}
|
||||
method={{
|
||||
completeness: "complete",
|
||||
executionClass: "deterministic",
|
||||
pipelineId: result.pipelineId,
|
||||
components: [
|
||||
{ kind: "source", name: result.referenceGraphLabResultId, version: "accepted M4.7", role: "immutable current/rolling occupied/unknown + threat graph", identitySha256: result.referenceGraphLabResultId.split("-").at(-1) ?? null },
|
||||
{ kind: "source", name: result.smallStaticResultId, version: "M4.8R1 assisted seed", role: "static avoidance anchors · not independent truth", identitySha256: result.smallStaticResultId.split("-").at(-1) ?? null },
|
||||
{ kind: "algorithm", name: "low-step occupied-only candidate", version: "v1", role: "additive CPU geometry; never clearing", identitySha256: result.resultId.split("-").at(-1) ?? null },
|
||||
],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
evidence={(
|
||||
<LaboratoryEvidence eyebrow="M4.8R2 VISUAL EVIDENCE · CANONICAL REFERENCE GRAPH" title="Те же CAMERA/3D/PLAN, 11 static anchors и неизменённый M4.7 timeline" kind="recorded-replay" resizable>
|
||||
<M48StaticOccupancyQualificationEvidence result={result} />
|
||||
</LaboratoryEvidence>
|
||||
)}
|
||||
result={(
|
||||
<LaboratoryResultSummary
|
||||
title="Что дал прогон: локализован лёгкий геометрический слой для близких препятствий"
|
||||
status={status}
|
||||
statusTone={nearReady ? "success" : "warning"}
|
||||
metrics={[
|
||||
{ label: "Current graph", value: `${result.metrics.baselineQualifiedCount}/${result.metrics.operatorStaticAnchorCount}`, hint: `${percent(result.metrics.criticalNearBaselineRecall)} в 0–8 м` },
|
||||
{ label: "+ low-step candidate", value: `${result.metrics.candidateQualifiedCount}/${result.metrics.operatorStaticAnchorCount}`, hint: `${percent(result.metrics.criticalNearCandidateRecall)} в 0–8 м` },
|
||||
{ label: "8–12 м", value: percent(result.metrics.approachCandidateRecall), hint: `${result.metrics.approachAnchorCount} assisted anchors · ещё не gate` },
|
||||
{ label: "False free", value: result.metrics.falseFreeCount.toLocaleString("ru-RU"), hint: `${result.metrics.canonicalEngineeringAnchorCount}/${result.metrics.canonicalEngineeringAnchorCount} canonical anchors retained` },
|
||||
]}
|
||||
conclusion={{
|
||||
proved: `На замороженном assisted seed additive low-step evidence поднял покрытие близких static anchors до ${percent(result.metrics.criticalNearCandidateRecall)} без дополнительной нейросети и без единого free-space утверждения.`,
|
||||
notProved: "Не доказаны independent precision/recall, приемлемый рост консервативной occupancy, realtime Worker FPS после интеграции, физический clearance и collision safety.",
|
||||
decision: nearReady
|
||||
? "Интегрировать low-step только как occupied/unknown Worker shadow. Затем прогнать все 4 489 кадров и принять по FPS, росту компонентов, отсутствию capacity drops и повторному static-anchor gate."
|
||||
: "Не интегрировать в Worker до исправления близких пропусков.",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
LaboratorySummary,
|
||||
LaboratoryWorkTemplate,
|
||||
} from "../../components/laboratory/LaboratoryPresentation";
|
||||
import type { M48TRiskQualityResult } from "../../core/laboratory/m48tRiskQuality";
|
||||
import type {
|
||||
M48QNativeRiskQualityResult,
|
||||
M48TLegacyRiskQualityResult,
|
||||
M48TRiskQualityResult,
|
||||
} from "../../core/laboratory/m48tRiskQuality";
|
||||
import { M48TRiskQualityVisual } from "./M48TRiskQualityVisual";
|
||||
|
||||
function percent(value: number, digits = 1): string {
|
||||
@@ -21,6 +25,19 @@ export function M48TRiskQualityResultView({
|
||||
}: {
|
||||
rigLabel: string;
|
||||
result: M48TRiskQualityResult;
|
||||
}) {
|
||||
if (result.variant === "native-risk-review") {
|
||||
return <M48QNativeRiskQualityResultView rigLabel={rigLabel} result={result} />;
|
||||
}
|
||||
return <M48TLegacyRiskQualityResultView rigLabel={rigLabel} result={result} />;
|
||||
}
|
||||
|
||||
function M48TLegacyRiskQualityResultView({
|
||||
rigLabel,
|
||||
result,
|
||||
}: {
|
||||
rigLabel: string;
|
||||
result: M48TLegacyRiskQualityResult;
|
||||
}) {
|
||||
return (
|
||||
<LaboratoryWorkTemplate
|
||||
@@ -77,3 +94,69 @@ export function M48TRiskQualityResultView({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function M48QNativeRiskQualityResultView({
|
||||
rigLabel,
|
||||
result,
|
||||
}: {
|
||||
rigLabel: string;
|
||||
result: M48QNativeRiskQualityResult;
|
||||
}) {
|
||||
const classes = Object.entries(result.selection.classCounts)
|
||||
.map(([name, count]) => `${name} ${count}`)
|
||||
.join(" · ");
|
||||
return (
|
||||
<LaboratoryWorkTemplate
|
||||
summary={(
|
||||
<LaboratorySummary
|
||||
title="M4.8Q · native raw-fisheye risk review"
|
||||
description="Нативный RF-DETR прогнан внутри полного reference graph прямо на исходном KB4 raster 800×600: без rectification, warp, resize и обратного преобразования. Из immutable frame ledger детерминированно отобраны реальные risk-кейсы для операторской проверки в существующем M4.8 image-case instrument."
|
||||
status="Runtime принят · 24 native cases готовы · quality ещё не adjudicated"
|
||||
statusTone="success"
|
||||
facts={[
|
||||
{ label: "Source", value: `${rigLabel} · ${result.source.frameCount.toLocaleString("ru-RU")} raw frames · 800×600` },
|
||||
{ label: "Candidate", value: `${result.candidate.modelId} · score ≥ ${decimal(result.candidate.minimumScore, 2)}` },
|
||||
{ label: "Image path", value: "RAW KB4 → fused GPU graph · geometric resampling NO" },
|
||||
{ label: "Authority", value: "SHADOW ONLY · independent quality NO · production NO" },
|
||||
]}
|
||||
brief={{
|
||||
question: "Работает ли нативная risk-классификация на нашей исходной fisheye-картинке в realtime envelope, и какие реальные кадры надо проверить человеком?",
|
||||
approach: `Полный ${result.source.frameCount.toLocaleString("ru-RU")}-кадровый native graph оставлен неизменным. Повторной инференс-сессии для лабы нет: 24 кадра выбраны из его hash-bound ledger по person, animal, light-road-user, vehicle, low-confidence, fisheye-edge и расхождениям с диагностической legacy 704 веткой.`,
|
||||
principalResult: `${decimal(result.execution.effectiveWorldStateFps, 3)} FPS при detector p95 ${decimal(result.execution.detectorTotalP95Ms, 2)} ms и world-state p95 ${decimal(result.execution.worldStateCompletionP95Ms, 2)} ms. В review pack реально попали ${classes}.`,
|
||||
limitation: "Это диагностическая выборка маршрута без независимой разметки. Наличие бокса и класса можно осмотреть, но precision/recall и корректность каждого класса этой фазой ещё не доказаны.",
|
||||
}}
|
||||
method={result.method}
|
||||
/>
|
||||
)}
|
||||
evidence={(
|
||||
<LaboratoryEvidence
|
||||
eyebrow="M4.8Q VISUAL EVIDENCE · NATIVE RAW KB4"
|
||||
title="24 исходных fisheye-кадра с отключаемым client-side overlay"
|
||||
kind="diagnostic-model"
|
||||
resizable
|
||||
>
|
||||
<M48TRiskQualityVisual result={result} />
|
||||
</LaboratoryEvidence>
|
||||
)}
|
||||
result={(
|
||||
<LaboratoryResultSummary
|
||||
title="Native runtime закрыт; следующий честный шаг — adjudication этих кейсов"
|
||||
status="Review-ready · quality-not-adjudicated"
|
||||
statusTone="success"
|
||||
metrics={[
|
||||
{ label: "Full graph", value: `${decimal(result.execution.effectiveWorldStateFps, 3)} FPS`, hint: `delivery ${percent(result.runtime.deliveryRatio, 2)} · 4 489/4 489` },
|
||||
{ label: "Detector / world-state p95", value: `${decimal(result.execution.detectorTotalP95Ms, 2)} / ${decimal(result.execution.worldStateCompletionP95Ms, 2)} ms`, hint: "один native pass · additional inference 0" },
|
||||
{ label: "GPU / VRAM peak", value: `${decimal(result.execution.gpuUtilizationP95Percent, 1)}% / ${decimal(result.execution.gpuMemoryMaximumMib / 1024, 2)} GiB`, hint: "Worker 006 · RTX 4090 envelope" },
|
||||
{ label: "Native parity", value: `${percent(result.parity.precision, 2)} / ${percent(result.parity.recall, 2)}`, hint: `precision / recall · mean IoU ${percent(result.parity.meanIou, 2)}` },
|
||||
{ label: "Review pack", value: `${result.selection.caseCount}/24 cases`, hint: "8 diagnostic buckets · raw 800×600" },
|
||||
]}
|
||||
conclusion={{
|
||||
proved: `Полный reference graph доставил все ${result.source.frameCount.toLocaleString("ru-RU")} world states на ${decimal(result.execution.effectiveWorldStateFps, 3)} FPS. Детектор использовал ровно один native 800×600 KB4 pass без геометрического resampling. Hash-bound review содержит person, dog, bicycle, skateboard, car и truck; боксы рисуются поверх чистого кадра и отключаются кнопкой.`,
|
||||
notProved: "Не доказаны route-domain precision/recall, истинность 24 классов и боксов, child/adult, поведение объектов, физическая track identity и безопасность навигации. Legacy 704 count delta остаётся только диагностикой, потому что старая ветка растягивала 4:3 raster.",
|
||||
decision: "Не добавлять второй detector и не менять realtime graph. Использовать эти 24 кейса как вход существующего M4.8 review/correction workflow; только после независимой adjudication считать route-domain semantic quality.",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Icon, IconButton } from "@nodedc/ui-react";
|
||||
|
||||
import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer";
|
||||
import type { M48TRiskQualityResult } from "../../core/laboratory/m48tRiskQuality";
|
||||
import { RecordedEvidenceImageScene } from "../../components/laboratory/RecordedEvidenceImageScene";
|
||||
import type {
|
||||
RecordedEvidenceBox,
|
||||
RecordedEvidenceBoxTone,
|
||||
} from "../../components/laboratory/RecordedEvidenceBoxOverlay";
|
||||
import type {
|
||||
M48QNativeRiskQualityResult,
|
||||
M48QRiskFamily,
|
||||
M48TLegacyRiskQualityResult,
|
||||
M48TRiskQualityResult,
|
||||
} from "../../core/laboratory/m48tRiskQuality";
|
||||
|
||||
export function M48TRiskQualityVisual({ result }: { result: M48TRiskQualityResult }) {
|
||||
function navigateIndex(current: number, offset: -1 | 1, length: number): number {
|
||||
return (current + offset + length) % length;
|
||||
}
|
||||
|
||||
function familyTone(family: M48QRiskFamily): RecordedEvidenceBoxTone {
|
||||
if (family === "person" || family === "animal") return "danger";
|
||||
if (family === "light-road-user") return "warning";
|
||||
return "accent";
|
||||
}
|
||||
|
||||
function LegacyVisual({ result }: { result: M48TLegacyRiskQualityResult }) {
|
||||
const [index, setIndex] = useState(0);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const item = result.review.cases[index] ?? null;
|
||||
const navigate = (offset: -1 | 1) => {
|
||||
setIndex((current) => (
|
||||
current + offset + result.review.cases.length
|
||||
) % result.review.cases.length);
|
||||
};
|
||||
const navigate = (offset: -1 | 1) =>
|
||||
setIndex((current) => navigateIndex(current, offset, result.review.cases.length));
|
||||
|
||||
return (
|
||||
<div className="l3-visual-audit">
|
||||
@@ -61,3 +78,85 @@ export function M48TRiskQualityVisual({ result }: { result: M48TRiskQualityResul
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NativeVisual({ result }: { result: M48QNativeRiskQualityResult }) {
|
||||
const [index, setIndex] = useState(0);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [boxesVisible, setBoxesVisible] = useState(true);
|
||||
const item = result.review.cases[index] ?? null;
|
||||
const boxes = useMemo<readonly RecordedEvidenceBox[]>(() => {
|
||||
if (!item || !boxesVisible) return [];
|
||||
return item.proposals.map((proposal) => ({
|
||||
boxXyxy: proposal.boxXyxy,
|
||||
label: `${proposal.className} · ${proposal.score.toFixed(2)}`,
|
||||
tone: familyTone(proposal.riskFamily),
|
||||
}));
|
||||
}, [boxesVisible, item]);
|
||||
const navigate = (offset: -1 | 1) =>
|
||||
setIndex((current) => navigateIndex(current, offset, result.review.cases.length));
|
||||
|
||||
return (
|
||||
<div className="l3-visual-audit">
|
||||
<LaboratoryEvidenceViewer
|
||||
label="M4.8Q native raw-fisheye risk review"
|
||||
mode="native"
|
||||
modes={[{ value: "native", label: "NATIVE RF-DETR" }]}
|
||||
expanded={expanded}
|
||||
onModeChange={() => undefined}
|
||||
onExpandedChange={setExpanded}
|
||||
actions={(
|
||||
<div className="l3-visual-audit__pagination">
|
||||
<IconButton label="Предыдущий M4.8Q review case" onClick={() => navigate(-1)}>
|
||||
<Icon name="chevron-left" size={16} />
|
||||
</IconButton>
|
||||
<IconButton label="Следующий M4.8Q review case" onClick={() => navigate(1)}>
|
||||
<Icon name="chevron-right" size={16} />
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
trailingActions={(
|
||||
<IconButton
|
||||
label={boxesVisible ? "Скрыть native RF-DETR боксы" : "Показать native RF-DETR боксы"}
|
||||
onClick={() => setBoxesVisible((visible) => !visible)}
|
||||
>
|
||||
<Icon name={boxesVisible ? "eye-off" : "eye"} size={16} />
|
||||
</IconButton>
|
||||
)}
|
||||
overlay={item ? (
|
||||
<div className="l3-visual-audit__overlay">
|
||||
<div>
|
||||
<span>RAVNOVES00 · raw KB4 800×600 · resampling NO</span>
|
||||
<strong>
|
||||
case {index + 1}/{result.review.cases.length} · frame {item.sequence} · {item.proposals.length} native boxes
|
||||
</strong>
|
||||
<small>
|
||||
{item.selectionBuckets.join(" · ")} · native/legacy {item.nativeDetectionCount}/{item.legacyDetectionCount} (diagnostic only)
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
>
|
||||
{item ? (
|
||||
<RecordedEvidenceImageScene
|
||||
src={item.imageUrl}
|
||||
imageWidth={item.width}
|
||||
imageHeight={item.height}
|
||||
boxes={boxes}
|
||||
ariaLabel={`M4.8Q frame ${item.sequence}: ${boxes.length} visible native risk boxes`}
|
||||
/>
|
||||
) : (
|
||||
<div className="l3-visual-audit__state" role="alert">
|
||||
<Icon name="alert" size={18} />
|
||||
M4.8Q native visual evidence недоступно.
|
||||
</div>
|
||||
)}
|
||||
</LaboratoryEvidenceViewer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function M48TRiskQualityVisual({ result }: { result: M48TRiskQualityResult }) {
|
||||
return result.variant === "native-risk-review"
|
||||
? <NativeVisual result={result} />
|
||||
: <LegacyVisual result={result} />;
|
||||
}
|
||||
|
||||
@@ -77,6 +77,13 @@ const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`
|
||||
experimentName: "M4.8 · small static passage regression",
|
||||
variantName: "M4.8R1 · Worker 006 small-static assisted baseline",
|
||||
},
|
||||
"m48-static-occupancy-qualification": {
|
||||
profileId: "rig-dual-evidence-virtual-corridor-v1",
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Canonical Reference Graph`,
|
||||
experimentId: "m48-static-occupancy-qualification",
|
||||
experimentName: "M4.8 · conservative static occupancy qualification",
|
||||
variantName: "M4.8R2 · current/rolling + low-step occupied-only candidate",
|
||||
},
|
||||
"m48s-fixed-class-detector": {
|
||||
profileId: "rig-ravnoves-perception-gate-v1",
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · RAVNOVES00 perception gate`,
|
||||
@@ -86,10 +93,10 @@ const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`
|
||||
},
|
||||
"m48t-risk-quality-temporal": {
|
||||
profileId: "rig-ravnoves-perception-gate-v1",
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · COCO quality + bounded temporal identity`,
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Native raw-fisheye perception gate`,
|
||||
experimentId: "m48t-risk-quality-temporal",
|
||||
experimentName: "RF-DETR independent semantic quality and temporal identity",
|
||||
variantName: "M4.8T · COCO val2017 truth + RAVNOVES00 temporal shadow",
|
||||
experimentName: "RF-DETR native risk review and temporal identity",
|
||||
variantName: "M4.8Q · native raw KB4 review · quality not adjudicated",
|
||||
},
|
||||
"m47-reference-graph-shadow": {
|
||||
profileId: "rig-dual-evidence-virtual-corridor-v1",
|
||||
|
||||
@@ -21,6 +21,7 @@ function mergeResults(
|
||||
m47Graph: next.m47Graph ?? current.m47Graph,
|
||||
m48: next.m48 ?? current.m48,
|
||||
m48SmallStatic: next.m48SmallStatic ?? current.m48SmallStatic,
|
||||
m48StaticOccupancy: next.m48StaticOccupancy ?? current.m48StaticOccupancy,
|
||||
m48s: next.m48s ?? current.m48s,
|
||||
m48t: next.m48t ?? current.m48t,
|
||||
m4Threat: next.m4Threat ?? current.m4Threat,
|
||||
@@ -120,6 +121,7 @@ export function useAdvancedLaboratoryCatalog({
|
||||
"m47-reference-graph-shadow",
|
||||
"m48-object-centric-quality",
|
||||
"m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification",
|
||||
].includes(selectedWorkId)
|
||||
&& !indexedResultId
|
||||
) return;
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { after, before, test } from "node:test";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { createServer } from "vite";
|
||||
|
||||
let server;
|
||||
let fetchM48TRiskQualityResult;
|
||||
|
||||
const resultId = `m48q-native-risk-quality-lab-${"a".repeat(64)}`;
|
||||
|
||||
before(async () => {
|
||||
server = await createServer({
|
||||
appType: "custom",
|
||||
logLevel: "silent",
|
||||
server: { middlewareMode: true },
|
||||
});
|
||||
({ fetchM48TRiskQualityResult } = await server.ssrLoadModule(
|
||||
"/src/core/laboratory/m48tRiskQuality.ts",
|
||||
));
|
||||
});
|
||||
|
||||
after(async () => server?.close());
|
||||
|
||||
function response(value) {
|
||||
return { ok: true, status: 200, json: async () => value };
|
||||
}
|
||||
|
||||
function payload() {
|
||||
return {
|
||||
schema_version: "missioncore.m48q-native-risk-quality-view/v1",
|
||||
variant: "native-risk-review",
|
||||
result_id: resultId,
|
||||
created_at_utc: "2026-08-26T10:00:00Z",
|
||||
status: "complete-review-ready-quality-not-adjudicated",
|
||||
access: "read-only",
|
||||
ground_truth: false,
|
||||
source: {
|
||||
source_id: "RAVNOVES00",
|
||||
frame_count: 4489,
|
||||
raster_width: 800,
|
||||
raster_height: 600,
|
||||
geometric_resampling: false,
|
||||
rectification: false,
|
||||
warp: false,
|
||||
},
|
||||
configuration: {
|
||||
candidate: {
|
||||
provider_id: "triton-rf-detr-large-coco-native-kb4-risk-fp16-shadow/v0",
|
||||
model_id: "rf_detr_large_native_kb4:1",
|
||||
preprocess_id: "raw-kb4-uint8-fused-mask-rgb-pad8-imagenet-trt/v0",
|
||||
minimum_score: 0.25,
|
||||
},
|
||||
},
|
||||
method: {
|
||||
schema_version: "missioncore.laboratory-method/v1",
|
||||
completeness: "complete",
|
||||
execution_class: "hybrid",
|
||||
pipeline_id: "m48q-native-raw-fisheye-risk-case-review/v1",
|
||||
components: [{
|
||||
kind: "model",
|
||||
name: "RF-DETR-L native KB4 TensorRT",
|
||||
version: "rf_detr_large_native_kb4:1",
|
||||
role: "risk proposals",
|
||||
identity_sha256: "b".repeat(64),
|
||||
}],
|
||||
},
|
||||
execution: {
|
||||
effective_world_state_fps: 11.84338,
|
||||
world_state_completion_p95_ms: 47.940779,
|
||||
detector_total_p95_ms: 21.19895,
|
||||
gpu_utilization_p95_percent: 53,
|
||||
gpu_memory_used_maximum_mib: 9718,
|
||||
additional_inference_passes: 0,
|
||||
},
|
||||
metrics: {
|
||||
selection: {
|
||||
case_count: 24,
|
||||
minimum_sequence_separation: 12,
|
||||
selected_bucket_coverage: { person: 22, animal: 3 },
|
||||
selected_class_counts: { person: 44, dog: 3 },
|
||||
},
|
||||
runtime: {
|
||||
delivery_ratio: 1,
|
||||
integrated_runtime_gate_passed: true,
|
||||
operating_target_gate_passed: true,
|
||||
},
|
||||
native_tensor_parity: {
|
||||
risk_detection_precision: 0.9887,
|
||||
risk_detection_recall: 0.9831,
|
||||
matched_mean_iou: 0.9882,
|
||||
},
|
||||
},
|
||||
acceptance: {
|
||||
review_ready: true,
|
||||
integrated_runtime_gate_passed: true,
|
||||
independent_quality_evaluated: false,
|
||||
semantic_candidate_accepted: false,
|
||||
},
|
||||
review: {
|
||||
source_raster: { width: 800, height: 600 },
|
||||
overlay: { client_rendered: true, toggleable: true },
|
||||
cases: Array.from({ length: 24 }, (_, index) => {
|
||||
const caseId = String(index * 20 + 12).padStart(6, "0");
|
||||
return {
|
||||
case_id: caseId,
|
||||
sequence: Number(caseId),
|
||||
frame_id: `frame-${caseId}`,
|
||||
evidence_time_ns: 35_000_000_000 + index * 1_000_000,
|
||||
image_url: `/review/${caseId}.jpg`,
|
||||
media_type: "image/jpeg",
|
||||
width: 800,
|
||||
height: 600,
|
||||
byte_length: 1000 + index,
|
||||
sha256: String(index.toString(16)).padStart(64, "0"),
|
||||
geometric_resampling: false,
|
||||
selection_buckets: [index % 2 ? "person" : "animal"],
|
||||
comparison: {
|
||||
native_detection_count: 1,
|
||||
legacy_704_detection_count: 1,
|
||||
matched_detection_count_iou_at_least_0_5: 1,
|
||||
},
|
||||
proposals: [{
|
||||
proposal_id: `proposal-${index}-0`,
|
||||
class_name: index % 2 ? "person" : "dog",
|
||||
risk_family: index % 2 ? "person" : "animal",
|
||||
score: 0.75,
|
||||
box_xyxy: [10, 20, 100, 200],
|
||||
}],
|
||||
};
|
||||
}),
|
||||
},
|
||||
limitations: ["No independent route truth."],
|
||||
};
|
||||
}
|
||||
|
||||
test("M4.8Q parser accepts only the native 800x600 no-resampling review contract", async () => {
|
||||
const result = await fetchM48TRiskQualityResult(resultId, {
|
||||
fetcher: async () => response(payload()),
|
||||
});
|
||||
|
||||
assert.equal(result.variant, "native-risk-review");
|
||||
assert.equal(result.review.cases.length, 24);
|
||||
assert.equal(result.review.cases[0].width, 800);
|
||||
assert.equal(result.review.cases[0].proposals[0].className, "dog");
|
||||
assert.equal(result.execution.additionalInferencePasses, 0);
|
||||
assert.equal(result.acceptance.independentQualityEvaluated, false);
|
||||
});
|
||||
|
||||
test("M4.8Q parser fails closed if raw-fisheye geometry is resampled", async () => {
|
||||
const invalid = payload();
|
||||
invalid.source.geometric_resampling = true;
|
||||
|
||||
await assert.rejects(
|
||||
fetchM48TRiskQualityResult(resultId, {
|
||||
fetcher: async () => response(invalid),
|
||||
}),
|
||||
/source resampling/,
|
||||
);
|
||||
});
|
||||
|
||||
test("M4.8Q reuses the canonical image scene and viewer overlay toggle", async () => {
|
||||
const source = await readFile(
|
||||
new URL("../src/workspaces/laboratory/M48TRiskQualityVisual.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert.match(source, /<LaboratoryEvidenceViewer/);
|
||||
assert.match(source, /<RecordedEvidenceImageScene/);
|
||||
assert.match(source, /name=\{boxesVisible \? "eye-off" : "eye"\}/);
|
||||
assert.match(source, /raw KB4 800×600 · resampling NO/);
|
||||
assert.doesNotMatch(source, /<canvas/);
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"schema_version": "missioncore.laboratory-evidence-definition/v1",
|
||||
"work_id": "m48-static-occupancy-qualification",
|
||||
"evidence": {
|
||||
"runtime_relative_root": "m48/static-occupancy-qualification-results",
|
||||
"result_id_prefix": "m48-static-occupancy-qualification",
|
||||
"document_name": "manifest.json",
|
||||
"schema_version": "missioncore.m48-static-occupancy-qualification-result/v1"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,20 @@
|
||||
{
|
||||
"schema_version": "missioncore.laboratory-evidence-definition/v1",
|
||||
"schema_version": "missioncore.laboratory-evidence-definition/v2",
|
||||
"work_id": "m48t-risk-quality-temporal",
|
||||
"evidence": {
|
||||
"runtime_relative_root": "m48t-risk-quality/lab-results",
|
||||
"result_id_prefix": "m48t-risk-quality-temporal-lab",
|
||||
"document_name": "manifest.json",
|
||||
"schema_version": "missioncore.m48t-risk-quality-temporal-lab/v1"
|
||||
}
|
||||
"evidence_lifecycle": [
|
||||
{
|
||||
"phase": "legacy-quality",
|
||||
"runtime_relative_root": "m48t-risk-quality/lab-results",
|
||||
"result_id_prefix": "m48t-risk-quality-temporal-lab",
|
||||
"document_name": "manifest.json",
|
||||
"schema_version": "missioncore.m48t-risk-quality-temporal-lab/v1"
|
||||
},
|
||||
{
|
||||
"phase": "result",
|
||||
"runtime_relative_root": "m48t-risk-quality/native-lab-results",
|
||||
"result_id_prefix": "m48q-native-risk-quality-lab",
|
||||
"document_name": "manifest.json",
|
||||
"schema_version": "missioncore.m48q-native-risk-quality-lab/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
{
|
||||
"schema_version": "missioncore.laboratory-execution-registry/v1",
|
||||
"definitions": [
|
||||
{
|
||||
"work_id": "m48-static-occupancy-qualification",
|
||||
"lifecycle": "canonical",
|
||||
"isolation": "core-adapter",
|
||||
"adapter_id": "canonical.m48-static-occupancy-qualification/v1",
|
||||
"input_roles": [
|
||||
"repository_root",
|
||||
"profile_path",
|
||||
"m47_lab_root",
|
||||
"graph_result_root",
|
||||
"small_static_result_root"
|
||||
],
|
||||
"contracts": {
|
||||
"source": "missioncore.m48-static-occupancy-source-set/v1",
|
||||
"provider": "missioncore.m48-additive-low-step-occupancy/v1",
|
||||
"graph": "missioncore.m48-static-occupancy-case/v1",
|
||||
"run": "missioncore.laboratory-run/v1",
|
||||
"evidence": "missioncore.m48-static-occupancy-qualification-result/v1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"work_id": "m48-small-static-passage-regression",
|
||||
"lifecycle": "canonical",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "missioncore.laboratory-value-review-registry/v1",
|
||||
"reviewed_at_utc": "2026-08-25T11:06:28Z",
|
||||
"reviewed_at_utc": "2026-08-26T08:34:34Z",
|
||||
"entries": [
|
||||
{
|
||||
"catalog_id": "e28-local-surface",
|
||||
@@ -247,6 +247,13 @@
|
||||
"lifecycle": "current",
|
||||
"visual_evidence": "available"
|
||||
},
|
||||
{
|
||||
"catalog_id": "m48-static-occupancy-qualification",
|
||||
"evidence_id": "m48-static-occupancy-qualification-568024554cff011332ff19ca4739f70555a6232c0607dec2a71be6db408ea69a",
|
||||
"signal": "progress",
|
||||
"lifecycle": "current",
|
||||
"visual_evidence": "available"
|
||||
},
|
||||
{
|
||||
"catalog_id": "m48s-fixed-class-detector",
|
||||
"evidence_id": "m48s-fixed-class-detector-lab-4a901d811f53734540337f8d1f2c01666539aa0a9b7786f26d4f1f2e08cc858a",
|
||||
@@ -256,7 +263,7 @@
|
||||
},
|
||||
{
|
||||
"catalog_id": "m48t-risk-quality-temporal",
|
||||
"evidence_id": "m48t-risk-quality-temporal-lab-ed5355fe0adb9b18942d75aff2362d79190b9e864f070ebe7a1c15fccbc0fbfb",
|
||||
"evidence_id": "m48q-native-risk-quality-lab-e8961d4db8ffb751503b9646bc8eeebec6c80c948ce2f5b4fa5b89c471e673c1",
|
||||
"signal": "progress",
|
||||
"lifecycle": "current",
|
||||
"visual_evidence": "available"
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"schema_version": "missioncore.m48-static-occupancy-qualification-profile/v1",
|
||||
"profile_id": "m48-conservative-static-occupancy/v1",
|
||||
"pipeline_id": "m4-current-rolling-plus-step-static-occupancy/v1",
|
||||
"experiment_id": "m48-static-occupancy-qualification/v1",
|
||||
"human_lab_id": "M4.8",
|
||||
"run_label": "M4.8R2",
|
||||
"source": {
|
||||
"source_id": "RAVNOVES00",
|
||||
"source_session_id": "20260720T065719Z_viewer_live",
|
||||
"m47_lab_result_id": "m47-reference-graph-lab-49678f0a7c628c7e991af0964fa57d005baa027d2d1eea19f38bbfe27ed39ce5",
|
||||
"m47_graph_result_id": "m47-reference-graph-5f6a851cd655c7cf07c3025dacadbc188018b0afa97eda3a08802266e12da87d",
|
||||
"m47_graph_frames_sha256": "d2cd53f8cff555410959600a79eb9a101aad4a8009ba87bdd0f3c5f122948071",
|
||||
"small_static_result_id": "m48-small-static-passage-regression-3e3a2001f87fd3adcb736de52a65e42515044d83faa3515f892705b40915c084",
|
||||
"small_static_anchors_sha256": "6a317aa75dde8204d5574a56166bebc9347e8797934dbc82f076a0abfdfaa95a"
|
||||
},
|
||||
"selection": {
|
||||
"motion": "static",
|
||||
"requires_avoidance_or_clearance": true,
|
||||
"canonical_engineering_sequences": [1880, 2584],
|
||||
"independent_truth": false
|
||||
},
|
||||
"distance_bands_m": {
|
||||
"critical_near": [0.0, 8.0],
|
||||
"approach": [8.0, 12.0]
|
||||
},
|
||||
"candidate": {
|
||||
"point_sources": ["local-surface-occupied", "local-surface-step-candidate"],
|
||||
"minimum_points": 2,
|
||||
"minimum_voxels": 1,
|
||||
"voxel_size_m": 0.35,
|
||||
"depth_cluster_minimum_gap_m": 0.65,
|
||||
"depth_cluster_gap_fraction": 0.08,
|
||||
"spatial_cluster_radius_m": 0.75
|
||||
},
|
||||
"acceptance": {
|
||||
"minimum_critical_near_candidate_recall": 1.0,
|
||||
"minimum_approach_candidate_recall": 0.95,
|
||||
"minimum_canonical_engineering_recall": 1.0,
|
||||
"maximum_false_free_count": 0
|
||||
},
|
||||
"policy": {
|
||||
"absence_of_points_means_free": false,
|
||||
"absence_of_camera_detection_means_free": false,
|
||||
"step_candidate_can_only_add_occupied_or_unknown": true,
|
||||
"semantic_class_used": false,
|
||||
"planner_authoritative_free_space_claimed": false
|
||||
},
|
||||
"authority": {
|
||||
"mode": "replay-simulated",
|
||||
"physical_live": false,
|
||||
"commands_enabled": false,
|
||||
"actuation_allowed": false,
|
||||
"navigation_or_safety_accepted": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"schema_version": "missioncore.m48q-native-risk-case-mining-profile/v1",
|
||||
"profile_id": "m48q-ravnoves00-native-raw-kb4-risk-review/v1",
|
||||
"question": "Which real RAVNOVES00 raw-fisheye frames expose the native RF-DETR risk semantics that require operator review before independent route truth exists?",
|
||||
"source": {
|
||||
"source_id": "RAVNOVES00",
|
||||
"frame_count": 4489,
|
||||
"raster_width": 800,
|
||||
"raster_height": 600,
|
||||
"video_sha256": "cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8",
|
||||
"geometric_resampling": false,
|
||||
"rectification": false,
|
||||
"warp": false
|
||||
},
|
||||
"candidate": {
|
||||
"provider_id": "triton-rf-detr-large-coco-native-kb4-risk-fp16-shadow/v0",
|
||||
"model_id": "rf_detr_large_native_kb4:1",
|
||||
"preprocess_id": "raw-kb4-uint8-fused-mask-rgb-pad8-imagenet-trt/v0",
|
||||
"engine_sha256": "b8a40b3580edff001ec9680de68707242294ff590ab296000fae371f1083f695",
|
||||
"minimum_score": 0.25
|
||||
},
|
||||
"risk_families": {
|
||||
"person": ["person"],
|
||||
"animal": ["bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe"],
|
||||
"light-road-user": ["bicycle", "motorcycle", "skateboard"],
|
||||
"vehicle": ["car", "bus", "truck"]
|
||||
},
|
||||
"selection": {
|
||||
"case_count": 24,
|
||||
"minimum_sequence_separation": 12,
|
||||
"low_confidence_maximum_score": 0.4,
|
||||
"fisheye_edge_margin_fraction": 0.12,
|
||||
"bucket_quotas": {
|
||||
"person": 3,
|
||||
"animal": 3,
|
||||
"light-road-user": 3,
|
||||
"vehicle": 3,
|
||||
"low-confidence": 3,
|
||||
"fisheye-edge": 3,
|
||||
"native-fewer-than-legacy": 3,
|
||||
"native-more-than-legacy": 3
|
||||
}
|
||||
},
|
||||
"scope": {
|
||||
"quality_evaluated": false,
|
||||
"ground_truth": false,
|
||||
"candidate_accepted": false,
|
||||
"production_accepted": false,
|
||||
"purpose": "bounded-native-risk-case-review"
|
||||
},
|
||||
"authority": {
|
||||
"ground_truth": false,
|
||||
"candidate_accepted": false,
|
||||
"commands_enabled": false,
|
||||
"actuation_allowed": false,
|
||||
"navigation_or_safety_accepted": false
|
||||
}
|
||||
}
|
||||
@@ -912,6 +912,17 @@ authority.
|
||||
Exit: local collision-space quality and deadline gates pass in replay and
|
||||
shadow. This still does not authorize control.
|
||||
|
||||
M4.8R2 now supplies the bounded pre-shadow qualification for the first item.
|
||||
On eleven operator-assisted static passage anchors, the accepted current/rolling
|
||||
graph covers `6/11`, while the additive sealed low-step signal covers `10/11`;
|
||||
the critical `0–8 m` subset is `9/9`, four canonical engineering anchors remain
|
||||
matched and false-free count is zero. The item remains unchecked because this
|
||||
is not independent truth and the candidate is not yet part of the Worker
|
||||
pipeline. Its next gate is a full `4,489`-frame occupied-only Worker 006 shadow
|
||||
with FPS, latency, occupancy growth, component-count and capacity-drop bounds.
|
||||
Dynamic decay, Nav2 publication and planner-authoritative free space remain
|
||||
separate later work.
|
||||
|
||||
### L6 — alternative odometry/mapping
|
||||
|
||||
This stage starts only when a source supplies unregistered sensor scans. Add
|
||||
|
||||
@@ -179,6 +179,13 @@ only on the annotated frame unless a separately evidenced and reviewable
|
||||
tracking result exists; ordinary model proposals and graph layers continue on
|
||||
their own full recorded timeline.
|
||||
|
||||
M4.8R2 is the canonical static-occupancy example of this rule. Its report and
|
||||
eleven typed anchors bind to the accepted M4.7 LAB identity, use the same
|
||||
`VIDEO/CAMERA/3D/PLAN/SEMANTICS` timeline and add no renderer or transport. A
|
||||
qualified anchor means the additive occupied-only candidate has LiDAR support
|
||||
inside that exact reviewed camera extent; it does not turn the extent into a 3D
|
||||
collider or grant clearance, free-space, navigation or safety authority.
|
||||
|
||||
A third perception instrument is a product-surface change. It requires explicit
|
||||
product-owner agreement before implementation and cannot be introduced by a LAB
|
||||
adapter, result component or experiment configuration.
|
||||
|
||||
@@ -1445,6 +1445,65 @@ configuration, dataset release, metrics and ONNX identity; only then is a
|
||||
candidate loaded through the existing Triton seam and measured by a new
|
||||
append-only M4.8R run plus an independent validation gate.
|
||||
|
||||
### 2026-08-26 — M4.8Q native raw-fisheye risk review ready
|
||||
|
||||
The terminal phase of the existing `m48t-risk-quality-temporal` work is now the
|
||||
content-addressed M4.8Q result
|
||||
`m48q-native-risk-quality-lab-e8961d4db8ffb751503b9646bc8eeebec6c80c948ce2f5b4fa5b89c471e673c1`.
|
||||
It replaces neither the historical M4.8T evidence nor either admitted M4 viewer.
|
||||
The result projects `24` deterministic diagnostic cases into the existing M4.8
|
||||
image-case review instrument, with source-pixel boxes rendered by the client and
|
||||
an explicit clean-image toggle. The JPEG review derivatives retain the exact
|
||||
`800×600` raster geometry; rectification, warp and geometric resampling are all
|
||||
false. Boxes are not baked into the images.
|
||||
|
||||
Worker 006 selected the cases from the immutable full native reference-graph
|
||||
ledger without another inference pass. The selection covers person, animal,
|
||||
light-road-user, vehicle, low-confidence, fisheye-edge and both directions of
|
||||
native-versus-legacy count divergence, with at least 12 source frames between
|
||||
selected cases. The source graph delivered all `4,489/4,489` frames at
|
||||
`11.84338 FPS`; detector p95 was `21.19895 ms`, world-state completion p95 was
|
||||
`47.940779 ms`, GPU utilization p95 was `53%`, and maximum used GPU memory was
|
||||
`9,718 MiB`. The isolated case-mining container was removed after execution and
|
||||
the canonical Triton predecessor was verified healthy and unchanged.
|
||||
|
||||
This closes evidence preparation, not semantic quality. The cases are
|
||||
diagnostic samples rather than independent route truth; native-versus-legacy
|
||||
count differences are not a verdict because the legacy `704×704` path stretches
|
||||
the raw 4:3 image. Accordingly `review_ready=true`, while ground truth,
|
||||
candidate acceptance, production acceptance, navigation, safety, commands and
|
||||
actuation authority remain false. The next action is operator adjudication in
|
||||
the existing M4.8 review instrument.
|
||||
|
||||
### 2026-08-26 — M4.8R2 conservative static-occupancy qualification
|
||||
|
||||
M4.8R2 moves the small-static problem out of semantic detector tuning and into
|
||||
the existing LiDAR occupancy boundary. RF-DETR, native fisheye input and its
|
||||
risk taxonomy are unchanged. The deterministic qualification consumes the
|
||||
accepted M4.7 current/rolling graph plus the frozen M4.8R1 operator-assisted
|
||||
static anchors. It evaluates one already sealed local-surface signal,
|
||||
`point_step_candidate`, only as additive occupied-or-unknown evidence. It never
|
||||
clears a cell, claims free space, starts detector inference or uses the GPU.
|
||||
|
||||
Immutable result
|
||||
`m48-static-occupancy-qualification-568024554cff011332ff19ca4739f70555a6232c0607dec2a71be6db408ea69a`
|
||||
contains `11` static passage anchors. The accepted graph qualifies `6/11`; the
|
||||
additive low-step candidate qualifies `10/11`. In the critical `0–8 m` band the
|
||||
change is `6/9 → 9/9`; in the `8–12 m` approach band it is `0/2 → 1/2`. All
|
||||
four existing canonical engineering anchors remain matched and the result
|
||||
contains zero false-free claims. One approach anchor remains explicitly
|
||||
`unresolved-unknown-never-free`.
|
||||
|
||||
This is candidate-visible development evidence, not independent truth or a
|
||||
production cutover. The result accepts only a bounded next action: integrate
|
||||
the low-step contribution as an occupied-only Worker shadow and replay all
|
||||
`4,489` frames on Worker 006 while measuring effective FPS, stage latency,
|
||||
occupancy/component growth, capacity drops and the same static anchors. The LAB
|
||||
reuses the M4.7 `VIDEO/CAMERA/3D/PLAN/SEMANTICS` viewer and projects the eleven
|
||||
anchors onto its immutable timeline; no third perception instrument exists.
|
||||
Navigation, commands, actuation, physical clearance and collision-safety
|
||||
authority remain false.
|
||||
|
||||
## Implementation order
|
||||
|
||||
The implementation sequence is intentionally strict:
|
||||
|
||||
@@ -0,0 +1,677 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Mine bounded native raw-fisheye risk cases from immutable Worker 006 ledgers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
from collections import Counter
|
||||
from collections.abc import Iterable, Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
PROFILE_SCHEMA: Final = "missioncore.m48q-native-risk-case-mining-profile/v1"
|
||||
RESULT_SCHEMA: Final = "missioncore.m48q-native-risk-case-mining-result/v1"
|
||||
CASE_SCHEMA: Final = "missioncore.m48q-native-risk-review-case/v1"
|
||||
GRAPH_FRAME_SCHEMA: Final = "missioncore.m48s-reference-graph-frame-evidence/v1"
|
||||
COMPARISON_FRAME_SCHEMA: Final = "missioncore.m48n-native-vs-704-frame/v0"
|
||||
FALSE_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"actuation_allowed": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class M48QCaseMiningError(RuntimeError):
|
||||
"""Raised when immutable native review evidence is inconsistent."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Proposal:
|
||||
proposal_id: str
|
||||
class_name: str
|
||||
risk_family: str
|
||||
score: float
|
||||
box_xyxy: tuple[float, float, float, float]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class FrameCandidate:
|
||||
sequence: int
|
||||
frame_id: str
|
||||
evidence_time_ns: int
|
||||
proposals: tuple[Proposal, ...]
|
||||
native_count: int
|
||||
legacy_count: int
|
||||
matched_count: int
|
||||
buckets: frozenset[str]
|
||||
|
||||
|
||||
def canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def sha256_path(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _read_object(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value: object = json.loads(path.read_text("utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise M48QCaseMiningError(f"cannot read JSON object: {path.name}") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise M48QCaseMiningError(f"JSON evidence is not an object: {path.name}")
|
||||
return value
|
||||
|
||||
|
||||
def _verify_hash(path: Path, expected: str, label: str) -> str:
|
||||
actual = sha256_path(path)
|
||||
if actual != expected:
|
||||
raise M48QCaseMiningError(f"{label} SHA-256 changed: {actual}")
|
||||
return actual
|
||||
|
||||
|
||||
def _risk_family_map(profile: Mapping[str, Any]) -> dict[str, str]:
|
||||
raw = profile.get("risk_families")
|
||||
if not isinstance(raw, dict):
|
||||
raise M48QCaseMiningError("risk family profile is invalid")
|
||||
result: dict[str, str] = {}
|
||||
for family, classes in raw.items():
|
||||
if (
|
||||
family not in {"person", "animal", "light-road-user", "vehicle"}
|
||||
or not isinstance(classes, list)
|
||||
or not classes
|
||||
):
|
||||
raise M48QCaseMiningError("risk family profile changed")
|
||||
for class_name in classes:
|
||||
if not isinstance(class_name, str) or class_name in result:
|
||||
raise M48QCaseMiningError("risk classes are invalid or duplicated")
|
||||
result[class_name] = family
|
||||
return result
|
||||
|
||||
|
||||
def _proposal(
|
||||
value: object,
|
||||
*,
|
||||
sequence: int,
|
||||
profile: Mapping[str, Any],
|
||||
families: Mapping[str, str],
|
||||
) -> Proposal:
|
||||
if not isinstance(value, dict):
|
||||
raise M48QCaseMiningError("detector proposal is not an object")
|
||||
candidate = profile["candidate"]
|
||||
expected_frame_id = f"frame-{sequence:06d}"
|
||||
if (
|
||||
value.get("frame_id") != expected_frame_id
|
||||
or value.get("provider_id") != candidate["provider_id"]
|
||||
or value.get("model_id") != candidate["model_id"]
|
||||
or value.get("preprocess_id") != candidate["preprocess_id"]
|
||||
):
|
||||
raise M48QCaseMiningError("native detector proposal identity changed")
|
||||
proposal_id = value.get("proposal_id")
|
||||
class_name = value.get("semantic_hint")
|
||||
score = value.get("objectness")
|
||||
region = value.get("region")
|
||||
if (
|
||||
not isinstance(proposal_id, str)
|
||||
or not isinstance(class_name, str)
|
||||
or class_name not in families
|
||||
or not isinstance(score, (float, int))
|
||||
or isinstance(score, bool)
|
||||
or not math.isfinite(float(score))
|
||||
or float(score) < candidate["minimum_score"]
|
||||
or not isinstance(region, dict)
|
||||
):
|
||||
raise M48QCaseMiningError("native detector proposal contract changed")
|
||||
coordinates = tuple(region.get(key) for key in ("x_min", "y_min", "x_max", "y_max"))
|
||||
if not all(
|
||||
isinstance(item, (float, int)) and not isinstance(item, bool) and math.isfinite(float(item))
|
||||
for item in coordinates
|
||||
):
|
||||
raise M48QCaseMiningError("native detector box is invalid")
|
||||
left, top, right, bottom = (float(item) for item in coordinates)
|
||||
source = profile["source"]
|
||||
if not (
|
||||
0 <= left < right <= source["raster_width"] and 0 <= top < bottom <= source["raster_height"]
|
||||
):
|
||||
raise M48QCaseMiningError("native detector box escaped the raw source raster")
|
||||
return Proposal(
|
||||
proposal_id=proposal_id,
|
||||
class_name=class_name,
|
||||
risk_family=families[class_name],
|
||||
score=float(score),
|
||||
box_xyxy=(left, top, right, bottom),
|
||||
)
|
||||
|
||||
|
||||
def load_comparison_rows(path: Path, expected_count: int) -> dict[int, tuple[int, int, int]]:
|
||||
rows: dict[int, tuple[int, int, int]] = {}
|
||||
try:
|
||||
with path.open("r", encoding="utf-8") as stream:
|
||||
for line_number, line in enumerate(stream, start=1):
|
||||
value = json.loads(line)
|
||||
if not isinstance(value, dict):
|
||||
raise M48QCaseMiningError("comparison frame is not an object")
|
||||
if value.get("schema_version") not in {None, COMPARISON_FRAME_SCHEMA}:
|
||||
raise M48QCaseMiningError("comparison frame schema changed")
|
||||
sequence = value.get("frame_index")
|
||||
counts = tuple(
|
||||
value.get(key)
|
||||
for key in (
|
||||
"native_detection_count",
|
||||
"baseline_detection_count",
|
||||
"matched_detection_count_iou_at_least_0_5",
|
||||
)
|
||||
)
|
||||
if (
|
||||
not isinstance(sequence, int)
|
||||
or isinstance(sequence, bool)
|
||||
or sequence in rows
|
||||
or not all(
|
||||
isinstance(item, int) and not isinstance(item, bool) and item >= 0
|
||||
for item in counts
|
||||
)
|
||||
):
|
||||
raise M48QCaseMiningError(f"comparison frame {line_number} is invalid")
|
||||
rows[sequence] = counts
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise M48QCaseMiningError("comparison frame ledger cannot be read") from exc
|
||||
if set(rows) != set(range(expected_count)):
|
||||
raise M48QCaseMiningError("comparison frame ledger is incomplete")
|
||||
return rows
|
||||
|
||||
|
||||
def _case_buckets(
|
||||
proposals: Sequence[Proposal],
|
||||
*,
|
||||
native_count: int,
|
||||
legacy_count: int,
|
||||
profile: Mapping[str, Any],
|
||||
) -> frozenset[str]:
|
||||
selection = profile["selection"]
|
||||
buckets = {item.risk_family for item in proposals}
|
||||
if any(item.score <= selection["low_confidence_maximum_score"] for item in proposals):
|
||||
buckets.add("low-confidence")
|
||||
width = profile["source"]["raster_width"]
|
||||
height = profile["source"]["raster_height"]
|
||||
margin = selection["fisheye_edge_margin_fraction"]
|
||||
if any(
|
||||
item.box_xyxy[0] <= width * margin
|
||||
or item.box_xyxy[2] >= width * (1 - margin)
|
||||
or item.box_xyxy[1] <= height * margin
|
||||
or item.box_xyxy[3] >= height * (1 - margin)
|
||||
for item in proposals
|
||||
):
|
||||
buckets.add("fisheye-edge")
|
||||
if native_count < legacy_count:
|
||||
buckets.add("native-fewer-than-legacy")
|
||||
elif native_count > legacy_count:
|
||||
buckets.add("native-more-than-legacy")
|
||||
return frozenset(buckets)
|
||||
|
||||
|
||||
def load_graph_candidates(
|
||||
path: Path,
|
||||
*,
|
||||
profile: Mapping[str, Any],
|
||||
comparisons: Mapping[int, tuple[int, int, int]],
|
||||
) -> list[FrameCandidate]:
|
||||
families = _risk_family_map(profile)
|
||||
expected_count = profile["source"]["frame_count"]
|
||||
candidates: list[FrameCandidate] = []
|
||||
try:
|
||||
with path.open("r", encoding="utf-8") as stream:
|
||||
for sequence, line in enumerate(stream):
|
||||
value = json.loads(line)
|
||||
if not isinstance(value, dict) or value.get("schema_version") != GRAPH_FRAME_SCHEMA:
|
||||
raise M48QCaseMiningError("graph frame schema changed")
|
||||
source = value.get("source_envelope")
|
||||
raw_proposals = value.get("detector_proposals")
|
||||
if (
|
||||
not isinstance(source, dict)
|
||||
or source.get("sequence") != sequence
|
||||
or source.get("frame_id") != f"frame-{sequence:06d}"
|
||||
or not isinstance(raw_proposals, list)
|
||||
or value.get("authority") != FALSE_AUTHORITY
|
||||
):
|
||||
raise M48QCaseMiningError("graph frame identity or authority changed")
|
||||
timestamps = source.get("timestamps")
|
||||
evidence_time_ns = (
|
||||
timestamps.get("source_ns") if isinstance(timestamps, dict) else None
|
||||
)
|
||||
if (
|
||||
not isinstance(evidence_time_ns, int)
|
||||
or isinstance(evidence_time_ns, bool)
|
||||
or evidence_time_ns < 0
|
||||
):
|
||||
raise M48QCaseMiningError("graph frame evidence time is invalid")
|
||||
proposals = tuple(
|
||||
_proposal(item, sequence=sequence, profile=profile, families=families)
|
||||
for item in raw_proposals
|
||||
)
|
||||
native_count, legacy_count, matched_count = comparisons[sequence]
|
||||
if len(proposals) != native_count:
|
||||
raise M48QCaseMiningError("native graph/comparison detector count changed")
|
||||
buckets = _case_buckets(
|
||||
proposals,
|
||||
native_count=native_count,
|
||||
legacy_count=legacy_count,
|
||||
profile=profile,
|
||||
)
|
||||
candidates.append(
|
||||
FrameCandidate(
|
||||
sequence=sequence,
|
||||
frame_id=source["frame_id"],
|
||||
evidence_time_ns=evidence_time_ns,
|
||||
proposals=proposals,
|
||||
native_count=native_count,
|
||||
legacy_count=legacy_count,
|
||||
matched_count=matched_count,
|
||||
buckets=buckets,
|
||||
)
|
||||
)
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise M48QCaseMiningError("graph frame ledger cannot be read") from exc
|
||||
if len(candidates) != expected_count:
|
||||
raise M48QCaseMiningError("graph frame ledger is incomplete")
|
||||
return candidates
|
||||
|
||||
|
||||
def _quantile_order(items: Sequence[FrameCandidate]) -> Iterable[FrameCandidate]:
|
||||
if not items:
|
||||
return ()
|
||||
indexes: list[int] = []
|
||||
left = 0
|
||||
right = len(items) - 1
|
||||
while left <= right:
|
||||
middle = (left + right) // 2
|
||||
indexes.append(middle)
|
||||
if middle - left > 0:
|
||||
indexes.append((left + middle - 1) // 2)
|
||||
if right - middle > 0:
|
||||
indexes.append((middle + 1 + right) // 2)
|
||||
left += 1
|
||||
right -= 1
|
||||
seen: set[int] = set()
|
||||
return (items[index] for index in indexes if not (index in seen or seen.add(index)))
|
||||
|
||||
|
||||
def select_cases(
|
||||
candidates: Sequence[FrameCandidate],
|
||||
*,
|
||||
bucket_quotas: Mapping[str, int],
|
||||
minimum_sequence_separation: int,
|
||||
) -> list[FrameCandidate]:
|
||||
selected: list[FrameCandidate] = []
|
||||
selected_sequences: set[int] = set()
|
||||
for bucket, quota in bucket_quotas.items():
|
||||
eligible = [item for item in candidates if bucket in item.buckets]
|
||||
admitted = 0
|
||||
for item in _quantile_order(eligible):
|
||||
if item.sequence in selected_sequences:
|
||||
continue
|
||||
if any(
|
||||
abs(item.sequence - prior.sequence) < minimum_sequence_separation
|
||||
for prior in selected
|
||||
):
|
||||
continue
|
||||
selected.append(item)
|
||||
selected_sequences.add(item.sequence)
|
||||
admitted += 1
|
||||
if admitted == quota:
|
||||
break
|
||||
if admitted != quota:
|
||||
raise M48QCaseMiningError(
|
||||
f"selection bucket {bucket} produced {admitted}/{quota} separated cases"
|
||||
)
|
||||
expected = sum(bucket_quotas.values())
|
||||
if len(selected) != expected:
|
||||
raise M48QCaseMiningError("selected case count changed")
|
||||
return sorted(selected, key=lambda item: item.sequence)
|
||||
|
||||
|
||||
def render_selected_frames(
|
||||
*,
|
||||
video_path: Path,
|
||||
selected: Sequence[FrameCandidate],
|
||||
cases_root: Path,
|
||||
width: int,
|
||||
height: int,
|
||||
) -> dict[int, dict[str, object]]:
|
||||
import av
|
||||
from av.error import FFmpegError
|
||||
from PIL import Image
|
||||
|
||||
wanted = {item.sequence for item in selected}
|
||||
rendered: dict[int, dict[str, object]] = {}
|
||||
cases_root.mkdir(mode=0o700, parents=True, exist_ok=False)
|
||||
try:
|
||||
with av.open(str(video_path), mode="r") as container:
|
||||
streams = list(container.streams.video)
|
||||
if len(streams) != 1:
|
||||
raise M48QCaseMiningError("RAVNOVES00 video stream contract changed")
|
||||
for sequence, frame in enumerate(container.decode(streams[0])):
|
||||
if sequence not in wanted:
|
||||
continue
|
||||
array = frame.to_ndarray(format="rgb24")
|
||||
if array.shape != (height, width, 3):
|
||||
raise M48QCaseMiningError("decoded raw-fisheye raster changed")
|
||||
name = f"frame-{sequence:06d}.jpg"
|
||||
path = cases_root / name
|
||||
Image.fromarray(array, mode="RGB").save(
|
||||
path,
|
||||
format="JPEG",
|
||||
quality=94,
|
||||
subsampling=0,
|
||||
optimize=False,
|
||||
)
|
||||
rendered[sequence] = {
|
||||
"path": f"cases/{name}",
|
||||
"media_type": "image/jpeg",
|
||||
"width": width,
|
||||
"height": height,
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": sha256_path(path),
|
||||
"geometric_resampling": False,
|
||||
}
|
||||
if len(rendered) == len(wanted):
|
||||
break
|
||||
except (FFmpegError, OSError) as exc:
|
||||
raise M48QCaseMiningError("RAVNOVES00 raw-fisheye frames cannot be decoded") from exc
|
||||
if set(rendered) != wanted:
|
||||
raise M48QCaseMiningError("not every selected raw-fisheye frame was decoded")
|
||||
return rendered
|
||||
|
||||
|
||||
def _case_document(
|
||||
candidate: FrameCandidate,
|
||||
*,
|
||||
image: Mapping[str, object],
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"schema_version": CASE_SCHEMA,
|
||||
"case_id": f"{candidate.sequence:06d}",
|
||||
"sequence": candidate.sequence,
|
||||
"frame_id": candidate.frame_id,
|
||||
"evidence_time_ns": candidate.evidence_time_ns,
|
||||
"selection_buckets": sorted(candidate.buckets),
|
||||
"comparison": {
|
||||
"native_detection_count": candidate.native_count,
|
||||
"legacy_704_detection_count": candidate.legacy_count,
|
||||
"matched_detection_count_iou_at_least_0_5": candidate.matched_count,
|
||||
"quality_interpretation": "diagnostic-only",
|
||||
},
|
||||
"image": dict(image),
|
||||
"proposals": [
|
||||
{
|
||||
"proposal_id": item.proposal_id,
|
||||
"class_name": item.class_name,
|
||||
"risk_family": item.risk_family,
|
||||
"score": round(item.score, 9),
|
||||
"box_xyxy": [round(value, 6) for value in item.box_xyxy],
|
||||
}
|
||||
for item in candidate.proposals
|
||||
],
|
||||
"ground_truth": False,
|
||||
"quality_evaluated": False,
|
||||
"authority": dict(FALSE_AUTHORITY),
|
||||
}
|
||||
|
||||
|
||||
def run(args: argparse.Namespace) -> dict[str, object]:
|
||||
for path in (
|
||||
args.profile,
|
||||
args.graph_result,
|
||||
args.graph_frames,
|
||||
args.comparison_result,
|
||||
args.comparison_frames,
|
||||
args.video,
|
||||
):
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise M48QCaseMiningError(f"required evidence is missing: {path.name}")
|
||||
profile_sha256 = _verify_hash(args.profile, args.expected_profile_sha256, "profile")
|
||||
graph_result_sha256 = _verify_hash(
|
||||
args.graph_result, args.expected_graph_result_sha256, "graph result"
|
||||
)
|
||||
graph_frames_sha256 = _verify_hash(
|
||||
args.graph_frames, args.expected_graph_frames_sha256, "graph frames"
|
||||
)
|
||||
comparison_result_sha256 = _verify_hash(
|
||||
args.comparison_result,
|
||||
args.expected_comparison_result_sha256,
|
||||
"comparison result",
|
||||
)
|
||||
comparison_frames_sha256 = _verify_hash(
|
||||
args.comparison_frames,
|
||||
args.expected_comparison_frames_sha256,
|
||||
"comparison frames",
|
||||
)
|
||||
video_sha256 = _verify_hash(args.video, args.expected_video_sha256, "video")
|
||||
runner_sha256 = _verify_hash(Path(__file__), args.expected_runner_sha256, "runner")
|
||||
|
||||
profile = _read_object(args.profile)
|
||||
graph_result = _read_object(args.graph_result)
|
||||
comparison_result = _read_object(args.comparison_result)
|
||||
source = profile.get("source")
|
||||
candidate = profile.get("candidate")
|
||||
selection = profile.get("selection")
|
||||
scope = profile.get("scope")
|
||||
if (
|
||||
profile.get("schema_version") != PROFILE_SCHEMA
|
||||
or not isinstance(source, dict)
|
||||
or source.get("video_sha256") != video_sha256
|
||||
or source.get("raster_width") != 800
|
||||
or source.get("raster_height") != 600
|
||||
or source.get("frame_count") != 4489
|
||||
or source.get("geometric_resampling") is not False
|
||||
or source.get("rectification") is not False
|
||||
or source.get("warp") is not False
|
||||
or not isinstance(candidate, dict)
|
||||
or candidate.get("engine_sha256")
|
||||
!= "b8a40b3580edff001ec9680de68707242294ff590ab296000fae371f1083f695"
|
||||
or not isinstance(selection, dict)
|
||||
or selection.get("case_count") != 24
|
||||
or not isinstance(selection.get("bucket_quotas"), dict)
|
||||
or sum(selection["bucket_quotas"].values()) != 24
|
||||
or scope
|
||||
!= {
|
||||
"quality_evaluated": False,
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"production_accepted": False,
|
||||
"purpose": "bounded-native-risk-case-review",
|
||||
}
|
||||
or profile.get("authority") != FALSE_AUTHORITY
|
||||
):
|
||||
raise M48QCaseMiningError("M4.8Q profile contract changed")
|
||||
if (
|
||||
graph_result.get("schema_version") != "missioncore.m48s-reference-graph-shadow-load/v5"
|
||||
or graph_result.get("completed") is not True
|
||||
or graph_result.get("identity", {}).get("detector_provider_id") != candidate["provider_id"]
|
||||
or graph_result.get("identity", {}).get("inputs", {}).get("video") != video_sha256
|
||||
or graph_result.get("execution", {}).get("frame_evidence", {}).get("sha256")
|
||||
!= graph_frames_sha256
|
||||
or graph_result.get("authority") != FALSE_AUTHORITY
|
||||
or graph_result.get("production_accepted") is not False
|
||||
):
|
||||
raise M48QCaseMiningError("native graph result contract changed")
|
||||
if (
|
||||
comparison_result.get("schema_version") != "missioncore.m48n-native-vs-704-ravnoves00/v0"
|
||||
or comparison_result.get("completed") is not True
|
||||
or comparison_result.get("source", {}).get("video_sha256") != video_sha256
|
||||
or comparison_result.get("source", {}).get("frame_count") != source["frame_count"]
|
||||
or comparison_result.get("execution", {}).get("frames_sha256") != comparison_frames_sha256
|
||||
or comparison_result.get("authority") != FALSE_AUTHORITY
|
||||
):
|
||||
raise M48QCaseMiningError("native/legacy comparison result contract changed")
|
||||
|
||||
output_root = args.output_root
|
||||
if output_root.exists():
|
||||
raise M48QCaseMiningError("M4.8Q output root already exists")
|
||||
output_root.mkdir(mode=0o700, parents=True)
|
||||
comparisons = load_comparison_rows(args.comparison_frames, source["frame_count"])
|
||||
candidates = load_graph_candidates(
|
||||
args.graph_frames,
|
||||
profile=profile,
|
||||
comparisons=comparisons,
|
||||
)
|
||||
selected = select_cases(
|
||||
candidates,
|
||||
bucket_quotas=selection["bucket_quotas"],
|
||||
minimum_sequence_separation=selection["minimum_sequence_separation"],
|
||||
)
|
||||
rendered = render_selected_frames(
|
||||
video_path=args.video,
|
||||
selected=selected,
|
||||
cases_root=output_root / "cases",
|
||||
width=source["raster_width"],
|
||||
height=source["raster_height"],
|
||||
)
|
||||
cases = [
|
||||
_case_document(
|
||||
item,
|
||||
image=rendered[item.sequence],
|
||||
)
|
||||
for item in selected
|
||||
]
|
||||
cases_path = output_root / "cases.jsonl"
|
||||
with cases_path.open("xb") as stream:
|
||||
for case in cases:
|
||||
stream.write(canonical_json(case) + b"\n")
|
||||
|
||||
class_counts = Counter(proposal.class_name for item in selected for proposal in item.proposals)
|
||||
selected_bucket_coverage = Counter(bucket for item in selected for bucket in item.buckets)
|
||||
identity = {
|
||||
"schema_version": RESULT_SCHEMA,
|
||||
"profile": {"profile_id": profile["profile_id"], "sha256": profile_sha256},
|
||||
"source": {
|
||||
"source_id": source["source_id"],
|
||||
"video_sha256": video_sha256,
|
||||
"graph_result_sha256": graph_result_sha256,
|
||||
"graph_frames_sha256": graph_frames_sha256,
|
||||
"comparison_result_sha256": comparison_result_sha256,
|
||||
"comparison_frames_sha256": comparison_frames_sha256,
|
||||
},
|
||||
"candidate": dict(candidate),
|
||||
"runner_sha256": runner_sha256,
|
||||
"cases_sha256": sha256_path(cases_path),
|
||||
"authority": dict(FALSE_AUTHORITY),
|
||||
}
|
||||
result: dict[str, object] = {
|
||||
"schema_version": RESULT_SCHEMA,
|
||||
"status": "complete-review-ready-quality-not-adjudicated",
|
||||
"completed": True,
|
||||
"worker": {"worker_id": "worker-006", "node": "DESKTOP-OPJ8J04"},
|
||||
"identity": identity,
|
||||
"report_identity_sha256": hashlib.sha256(canonical_json(identity)).hexdigest(),
|
||||
"source": {
|
||||
**dict(source),
|
||||
"graph_result_sha256": graph_result_sha256,
|
||||
"graph_frames_sha256": graph_frames_sha256,
|
||||
"comparison_result_sha256": comparison_result_sha256,
|
||||
"comparison_frames_sha256": comparison_frames_sha256,
|
||||
},
|
||||
"candidate": dict(candidate),
|
||||
"selection": {
|
||||
"case_count": len(cases),
|
||||
"minimum_sequence_separation": selection["minimum_sequence_separation"],
|
||||
"configured_bucket_quotas": selection["bucket_quotas"],
|
||||
"selected_bucket_coverage": dict(sorted(selected_bucket_coverage.items())),
|
||||
"selected_sequences": [item.sequence for item in selected],
|
||||
"selected_class_counts": dict(sorted(class_counts.items())),
|
||||
},
|
||||
"artifacts": {
|
||||
"cases": {
|
||||
"path": "cases.jsonl",
|
||||
"schema_version": CASE_SCHEMA,
|
||||
"row_count": len(cases),
|
||||
"sha256": sha256_path(cases_path),
|
||||
},
|
||||
"images": {
|
||||
"root": "cases",
|
||||
"count": len(rendered),
|
||||
"geometric_resampling": False,
|
||||
},
|
||||
},
|
||||
"decision": {
|
||||
"quality_evaluated": False,
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"production_accepted": False,
|
||||
"next_action": "operator-adjudication-in-existing-m48-review-instrument",
|
||||
},
|
||||
"limitations": [
|
||||
"The selected RAVNOVES00 cases are diagnostic samples, not independent truth.",
|
||||
(
|
||||
"Native-versus-legacy detection-count differences are not quality "
|
||||
"verdicts because legacy 704 stretches the raw 4:3 raster."
|
||||
),
|
||||
(
|
||||
"No child/adult, behavior, physical track identity, navigation or "
|
||||
"actuation claim is made."
|
||||
),
|
||||
(
|
||||
"JPEG review derivatives preserve the 800x600 raster without geometric "
|
||||
"resampling but are not lossless source frames."
|
||||
),
|
||||
],
|
||||
"authority": dict(FALSE_AUTHORITY),
|
||||
}
|
||||
(output_root / "result.json").write_bytes(canonical_json(result) + b"\n")
|
||||
return result
|
||||
|
||||
|
||||
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
for name in (
|
||||
"profile",
|
||||
"graph-result",
|
||||
"graph-frames",
|
||||
"comparison-result",
|
||||
"comparison-frames",
|
||||
"video",
|
||||
"output-root",
|
||||
):
|
||||
parser.add_argument(f"--{name}", type=Path, required=True)
|
||||
for name in (
|
||||
"profile",
|
||||
"graph-result",
|
||||
"graph-frames",
|
||||
"comparison-result",
|
||||
"comparison-frames",
|
||||
"video",
|
||||
"runner",
|
||||
):
|
||||
parser.add_argument(f"--expected-{name}-sha256", required=True)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
try:
|
||||
result = run(parse_args(argv))
|
||||
except M48QCaseMiningError as exc:
|
||||
print(f"M4.8Q case mining refused: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Publish the immutable M4.8Q native raw-fisheye review LAB result."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
build_m48q_native_risk_quality_lab,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--repository-root", type=Path, required=True)
|
||||
parser.add_argument("--output-root", type=Path, required=True)
|
||||
arguments = parser.parse_args()
|
||||
result = build_m48q_native_risk_quality_lab(
|
||||
repository_root=arguments.repository_root,
|
||||
output_root=arguments.output_root,
|
||||
)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"result_id": result.result_id,
|
||||
"result_root": str(result.result_root),
|
||||
"status": result.manifest["status"],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,207 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ReleaseRoot,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidatePattern("^[A-Za-z0-9._-]{1,96}$")]
|
||||
[string]$RunId,
|
||||
[string]$OutputRoot = "D:\NDC_MISSIONCORE\runtime\results\m48q-native-risk-case-mining"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
function Assert-LastExitCode([string]$Operation) {
|
||||
if ($LASTEXITCODE -ne 0) { throw "$Operation failed with exit code $LASTEXITCODE" }
|
||||
}
|
||||
|
||||
function Get-Sha256([string]$Path) {
|
||||
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
}
|
||||
|
||||
function Resolve-DDirectory([string]$Path, [string]$Label, [bool]$Create) {
|
||||
if ($Create -and -not (Test-Path -LiteralPath $Path)) {
|
||||
$null = New-Item -ItemType Directory -Path $Path
|
||||
}
|
||||
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
|
||||
if (
|
||||
-not $item.PSIsContainer -or
|
||||
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
|
||||
[IO.Path]::GetPathRoot($item.FullName).TrimEnd("\") -ine "D:"
|
||||
) {
|
||||
throw "$Label must be a real D: directory"
|
||||
}
|
||||
return $item.FullName
|
||||
}
|
||||
|
||||
function Assert-RegularFile([string]$Path, [string]$Label) {
|
||||
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
|
||||
if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
|
||||
throw "$Label must be a regular file"
|
||||
}
|
||||
return $item.FullName
|
||||
}
|
||||
|
||||
function Convert-ToDockerPath([string]$Path) { return $Path.Replace("\", "/") }
|
||||
|
||||
function Get-Container([string]$Name) {
|
||||
$rows = @((& docker inspect $Name) | ConvertFrom-Json)
|
||||
Assert-LastExitCode "Docker inspection for $Name"
|
||||
if ($rows.Count -ne 1) { throw "Container identity for $Name is not unique" }
|
||||
return $rows[0]
|
||||
}
|
||||
|
||||
if ($env:COMPUTERNAME -cne "DESKTOP-OPJ8J04") {
|
||||
throw "M4.8Q native risk case mining is pinned to DESKTOP-OPJ8J04"
|
||||
}
|
||||
|
||||
$release = Resolve-DDirectory $ReleaseRoot "M4.8Q release root" $false
|
||||
$output = Resolve-DDirectory $OutputRoot "M4.8Q output root" $true
|
||||
$runOutput = Join-Path $output $RunId
|
||||
if (Test-Path -LiteralPath $runOutput) { throw "M4.8Q output already exists" }
|
||||
|
||||
$runner = Assert-RegularFile (
|
||||
Join-Path $release "run_m48q_native_risk_case_mining_worker.py"
|
||||
) "M4.8Q runner"
|
||||
$profile = Assert-RegularFile (
|
||||
Join-Path $release "m48q-native-risk-case-mining-v1.json"
|
||||
) "M4.8Q profile"
|
||||
$runnerSha256 = Get-Sha256 $runner
|
||||
$profileSha256 = Get-Sha256 $profile
|
||||
if ($runnerSha256 -cne "39bb1d810b167d6e97dcb091505b52d4a812d69419dcb32fe23ea5d14d605d67") {
|
||||
throw "M4.8Q runner SHA-256 changed"
|
||||
}
|
||||
if ($profileSha256 -cne "c455055e63578d505edc80b66d67e795916a3d0297cc18bfee5a6af7b032ceda") {
|
||||
throw "M4.8Q profile SHA-256 changed"
|
||||
}
|
||||
|
||||
$graphRoot = Resolve-DDirectory (
|
||||
"D:\NDC_MISSIONCORE\runtime\results\m48n-native-reference-graph-shadow\ravnoves00-full-12fps-v0"
|
||||
) "M4.8Q graph evidence root" $false
|
||||
$comparisonRoot = Resolve-DDirectory (
|
||||
"D:\NDC_MISSIONCORE\runtime\results\m48n-native-vs-704\ravnoves00-full-v0"
|
||||
) "M4.8Q comparison evidence root" $false
|
||||
$graphResult = Assert-RegularFile (Join-Path $graphRoot "result.json") "graph result"
|
||||
$graphFrames = Assert-RegularFile (Join-Path $graphRoot "frames.jsonl") "graph frames"
|
||||
$comparisonResult = Assert-RegularFile (Join-Path $comparisonRoot "result.json") "comparison result"
|
||||
$comparisonFrames = Assert-RegularFile (Join-Path $comparisonRoot "frames.jsonl") "comparison frames"
|
||||
$video = Assert-RegularFile (
|
||||
"D:\NDC_MISSIONCORE\runtime\experiments\e46e\inputs\right-cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8.mp4"
|
||||
) "RAVNOVES00 video"
|
||||
|
||||
$graphResultSha256 = Get-Sha256 $graphResult
|
||||
$graphFramesSha256 = Get-Sha256 $graphFrames
|
||||
$comparisonResultSha256 = Get-Sha256 $comparisonResult
|
||||
$comparisonFramesSha256 = Get-Sha256 $comparisonFrames
|
||||
$videoSha256 = Get-Sha256 $video
|
||||
if ($graphResultSha256 -cne "c5c3a831b1d1c3271161c91fa5c5c40533eb663ca288ed0220334a147aed6e15") {
|
||||
throw "M4.8Q graph result SHA-256 changed"
|
||||
}
|
||||
if ($graphFramesSha256 -cne "b245af969600670d0975e89cae02b44206e3f1328eff48d5b4639b1b2ab57346") {
|
||||
throw "M4.8Q graph frame evidence SHA-256 changed"
|
||||
}
|
||||
if ($comparisonResultSha256 -cne "fd91c2f1f477038d5af51656d510aba39d69ad2e1a387f15d98ade19dd3a0c49") {
|
||||
throw "M4.8Q comparison result SHA-256 changed"
|
||||
}
|
||||
if ($comparisonFramesSha256 -cne "6a35125d4e003edfd8a33b4239bad0910f137fde3a770e9d5baab721cdc145b2") {
|
||||
throw "M4.8Q comparison frame evidence SHA-256 changed"
|
||||
}
|
||||
if ($videoSha256 -cne "cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8") {
|
||||
throw "RAVNOVES00 video SHA-256 changed"
|
||||
}
|
||||
|
||||
$media = Resolve-DDirectory (
|
||||
"D:\NDC_MISSIONCORE\runtime\derived\perception-e15-media-pyav180-lz445-v1"
|
||||
) "PyAV dependency" $false
|
||||
$pillow = Resolve-DDirectory (
|
||||
"D:\NDC_MISSIONCORE\runtime\derived\perception-p0-env-v1"
|
||||
) "Pillow dependency" $false
|
||||
$image = (
|
||||
"nvcr.io/nvidia/tritonserver:26.06-py3@" +
|
||||
"sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
||||
)
|
||||
& docker image inspect $image *> $null
|
||||
Assert-LastExitCode "pinned M4.8Q image inspection"
|
||||
|
||||
$canonicalTriton = Get-Container "ndc-mission-core-triton"
|
||||
if (-not $canonicalTriton.State.Running -or $canonicalTriton.State.Health.Status -cne "healthy") {
|
||||
throw "Canonical Triton must remain healthy during M4.8Q mining"
|
||||
}
|
||||
$canonicalTritonId = [string]$canonicalTriton.Id
|
||||
$runnerName = "ndc-mission-core-m48q-native-risk-case-mining"
|
||||
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$runnerName$") {
|
||||
throw "M4.8Q bounded container already exists"
|
||||
}
|
||||
|
||||
try {
|
||||
& docker run `
|
||||
--name $runnerName `
|
||||
--label "com.nodedc.product=mission-core" `
|
||||
--label "com.nodedc.stack=ndc-mission-core-compute" `
|
||||
--label "com.nodedc.role=bounded-native-risk-case-mining" `
|
||||
--label "com.nodedc.managed-by=codex-bounded-experiment" `
|
||||
--network none `
|
||||
--read-only `
|
||||
--security-opt "no-new-privileges:true" `
|
||||
--cap-drop ALL `
|
||||
--pids-limit 128 `
|
||||
--cpus 4 `
|
||||
--memory 4g `
|
||||
--tmpfs "/tmp:rw,noexec,nosuid,size=512m" `
|
||||
-e "PYTHONDONTWRITEBYTECODE=1" `
|
||||
-e "PYTHONPATH=/opt/media:/opt/pillow" `
|
||||
-v ((Convert-ToDockerPath $release) + ":/release:ro") `
|
||||
-v ((Convert-ToDockerPath $output) + ":/output:rw") `
|
||||
-v ((Convert-ToDockerPath $media) + ":/opt/media:ro") `
|
||||
-v ((Convert-ToDockerPath $pillow) + ":/opt/pillow:ro") `
|
||||
-v ((Convert-ToDockerPath $graphRoot) + ":/evidence/graph:ro") `
|
||||
-v ((Convert-ToDockerPath $comparisonRoot) + ":/evidence/comparison:ro") `
|
||||
-v ((Convert-ToDockerPath $video) + ":/source/right.mp4:ro") `
|
||||
--entrypoint python3 `
|
||||
$image `
|
||||
/release/run_m48q_native_risk_case_mining_worker.py `
|
||||
--profile /release/m48q-native-risk-case-mining-v1.json `
|
||||
--graph-result /evidence/graph/result.json `
|
||||
--graph-frames /evidence/graph/frames.jsonl `
|
||||
--comparison-result /evidence/comparison/result.json `
|
||||
--comparison-frames /evidence/comparison/frames.jsonl `
|
||||
--video /source/right.mp4 `
|
||||
--output-root ("/output/{0}" -f $RunId) `
|
||||
--expected-profile-sha256 $profileSha256 `
|
||||
--expected-graph-result-sha256 $graphResultSha256 `
|
||||
--expected-graph-frames-sha256 $graphFramesSha256 `
|
||||
--expected-comparison-result-sha256 $comparisonResultSha256 `
|
||||
--expected-comparison-frames-sha256 $comparisonFramesSha256 `
|
||||
--expected-video-sha256 $videoSha256 `
|
||||
--expected-runner-sha256 $runnerSha256
|
||||
Assert-LastExitCode "M4.8Q native risk case mining"
|
||||
if (
|
||||
-not (Test-Path -LiteralPath (Join-Path $runOutput "result.json") -PathType Leaf) -or
|
||||
-not (Test-Path -LiteralPath (Join-Path $runOutput "cases.jsonl") -PathType Leaf) -or
|
||||
@(Get-ChildItem -LiteralPath (Join-Path $runOutput "cases") -Filter "*.jpg" -File).Count -ne 24
|
||||
) {
|
||||
throw "M4.8Q result artifacts are incomplete"
|
||||
}
|
||||
} finally {
|
||||
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$runnerName$") {
|
||||
& docker rm -f $runnerName *> $null
|
||||
}
|
||||
$canonicalAfter = Get-Container "ndc-mission-core-triton"
|
||||
if (
|
||||
[string]$canonicalAfter.Id -cne $canonicalTritonId -or
|
||||
-not $canonicalAfter.State.Running -or
|
||||
$canonicalAfter.State.Health.Status -cne "healthy"
|
||||
) {
|
||||
throw "Canonical Triton changed during M4.8Q mining"
|
||||
}
|
||||
}
|
||||
|
||||
$result = Get-Content -LiteralPath (Join-Path $runOutput "result.json") -Raw | ConvertFrom-Json
|
||||
[pscustomobject]@{
|
||||
run_id = $RunId
|
||||
output_root = $runOutput
|
||||
status = $result.status
|
||||
case_count = $result.selection.case_count
|
||||
report_identity_sha256 = $result.report_identity_sha256
|
||||
canonical_triton = "healthy-and-unchanged"
|
||||
} | ConvertTo-Json -Depth 4
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Publish one canonical append-only M4.8R2 static-occupancy qualification."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import socket
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.compute.pipeline_telemetry import JsonlPipelineTelemetrySink
|
||||
from k1link.laboratory import (
|
||||
LaboratoryEvidenceRegistry,
|
||||
LaboratoryExecutionRegistry,
|
||||
LaboratoryRunner,
|
||||
LaboratoryRunRequest,
|
||||
)
|
||||
|
||||
|
||||
def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--profile", type=Path, required=True)
|
||||
parser.add_argument("--m47-lab-root", type=Path, required=True)
|
||||
parser.add_argument("--graph-result-root", type=Path, required=True)
|
||||
parser.add_argument("--small-static-result-root", type=Path, required=True)
|
||||
parser.add_argument("--output-root", type=Path, required=True)
|
||||
parser.add_argument("--receipt-root", type=Path, required=True)
|
||||
parser.add_argument("--telemetry-path", type=Path, required=True)
|
||||
parser.add_argument("--run-id", required=True)
|
||||
parser.add_argument("--request-id", required=True)
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = _parser().parse_args()
|
||||
repository_root = Path(__file__).resolve().parents[1]
|
||||
evidence = LaboratoryEvidenceRegistry.from_directory(
|
||||
repository_root / "config" / "laboratories"
|
||||
)
|
||||
execution = LaboratoryExecutionRegistry.from_file(
|
||||
repository_root / "config" / "laboratory-execution.json",
|
||||
evidence,
|
||||
)
|
||||
runner = LaboratoryRunner(
|
||||
registry=execution,
|
||||
evidence_registry=evidence,
|
||||
sink=JsonlPipelineTelemetrySink(args.telemetry_path),
|
||||
)
|
||||
result = runner.run(
|
||||
LaboratoryRunRequest(
|
||||
work_id="m48-static-occupancy-qualification",
|
||||
run_id=args.run_id,
|
||||
request_id=args.request_id,
|
||||
contour_id="mission-core-laboratory",
|
||||
agent_id="local-control-plane",
|
||||
node_id=socket.gethostname(),
|
||||
source_id="RAVNOVES00",
|
||||
source_package_id=args.m47_lab_root.name,
|
||||
method_id="m48-conservative-static-occupancy/v1",
|
||||
inputs={
|
||||
"repository_root": repository_root,
|
||||
"profile_path": args.profile,
|
||||
"m47_lab_root": args.m47_lab_root,
|
||||
"graph_result_root": args.graph_result_root,
|
||||
"small_static_result_root": args.small_static_result_root,
|
||||
},
|
||||
output_root=args.output_root,
|
||||
receipt_root=args.receipt_root,
|
||||
)
|
||||
)
|
||||
print(json.dumps({
|
||||
"result_id": result.result_id,
|
||||
"result_root": str(result.result_root),
|
||||
"receipt_id": result.receipt_id,
|
||||
"receipt_root": str(result.receipt_root),
|
||||
}, ensure_ascii=False, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -312,6 +312,9 @@ class LaboratoryRunner:
|
||||
|
||||
def canonical_laboratory_adapters() -> dict[str, LaboratoryAdapter]:
|
||||
return {
|
||||
"canonical.m48-static-occupancy-qualification/v1": (
|
||||
_run_m48_static_occupancy_qualification
|
||||
),
|
||||
"canonical.m48-small-static-passage-regression/v1": (
|
||||
_run_m48_small_static_passage_regression
|
||||
),
|
||||
@@ -326,6 +329,27 @@ def canonical_laboratory_adapters() -> dict[str, LaboratoryAdapter]:
|
||||
}
|
||||
|
||||
|
||||
def _run_m48_static_occupancy_qualification(
|
||||
request: LaboratoryRunRequest,
|
||||
) -> LaboratoryAdapterResult:
|
||||
from k1link.laboratory.m48_static_occupancy_qualification import (
|
||||
build_m48_static_occupancy_qualification,
|
||||
)
|
||||
|
||||
result = build_m48_static_occupancy_qualification(
|
||||
repository_root=request.inputs["repository_root"],
|
||||
profile_path=request.inputs["profile_path"],
|
||||
m47_lab_root=request.inputs["m47_lab_root"],
|
||||
graph_result_root=request.inputs["graph_result_root"],
|
||||
small_static_result_root=request.inputs["small_static_result_root"],
|
||||
output_root=request.output_root,
|
||||
)
|
||||
return LaboratoryAdapterResult(
|
||||
result_root=result.result_root,
|
||||
result_id=result.result_id,
|
||||
)
|
||||
|
||||
|
||||
def _run_m48s_fixed_class_detector(
|
||||
request: LaboratoryRunRequest,
|
||||
) -> LaboratoryAdapterResult:
|
||||
|
||||
@@ -0,0 +1,754 @@
|
||||
"""Immutable M4.8R2 qualification of conservative static LiDAR occupancy.
|
||||
|
||||
The experiment does not run a detector and does not mutate the accepted M4.7
|
||||
graph. It measures the accepted current/rolling occupied output on the frozen
|
||||
operator-assisted small-static anchors, then evaluates one additive CPU-only
|
||||
candidate already present in the local-surface artifact: low step candidates.
|
||||
Neither missing evidence nor a camera miss is ever converted to free space.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
from dataclasses import dataclass, replace
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
import numpy as np
|
||||
|
||||
from k1link.laboratory.m47_reference_graph import (
|
||||
M47ReferenceGraphLabError,
|
||||
read_m47_reference_graph_lab,
|
||||
)
|
||||
from k1link.laboratory.m48_small_static_regression import (
|
||||
M48SmallStaticRegressionError,
|
||||
read_m48_small_static_passage_regression,
|
||||
)
|
||||
from k1link.perception.geometry import RecordedGeometryStore
|
||||
from k1link.perception.geometry_math import (
|
||||
POINT_OCCUPIED,
|
||||
project_map_points_kb4,
|
||||
semantic_geometry_support,
|
||||
)
|
||||
|
||||
M48_STATIC_OCCUPANCY_PROFILE_SCHEMA: Final = (
|
||||
"missioncore.m48-static-occupancy-qualification-profile/v1"
|
||||
)
|
||||
M48_STATIC_OCCUPANCY_RESULT_SCHEMA: Final = (
|
||||
"missioncore.m48-static-occupancy-qualification-result/v1"
|
||||
)
|
||||
M48_STATIC_OCCUPANCY_REPORT_SCHEMA: Final = (
|
||||
"missioncore.m48-static-occupancy-qualification-report/v1"
|
||||
)
|
||||
M48_STATIC_OCCUPANCY_CASE_SCHEMA: Final = "missioncore.m48-static-occupancy-case/v1"
|
||||
M48_STATIC_OCCUPANCY_CANONICAL_SCHEMA: Final = (
|
||||
"missioncore.m48-static-occupancy-canonical-anchor/v1"
|
||||
)
|
||||
M48_STATIC_OCCUPANCY_PREFIX: Final = "m48-static-occupancy-qualification-"
|
||||
|
||||
_METHOD_SCHEMA: Final = "missioncore.laboratory-method/v1"
|
||||
_AUTHORITY: Final = {
|
||||
"mode": "replay-simulated",
|
||||
"physical_live": False,
|
||||
"commands_enabled": False,
|
||||
"actuation_allowed": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class M48StaticOccupancyQualificationError(RuntimeError):
|
||||
"""The static-occupancy source, method, or immutable result is invalid."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class M48StaticOccupancyQualificationResult:
|
||||
result_id: str
|
||||
result_root: Path
|
||||
manifest: dict[str, Any]
|
||||
report: dict[str, Any]
|
||||
cases: tuple[dict[str, Any], ...]
|
||||
canonical_anchors: tuple[dict[str, Any], ...]
|
||||
|
||||
|
||||
def build_m48_static_occupancy_qualification(
|
||||
*,
|
||||
repository_root: Path,
|
||||
profile_path: Path,
|
||||
m47_lab_root: Path,
|
||||
graph_result_root: Path,
|
||||
small_static_result_root: Path,
|
||||
output_root: Path,
|
||||
run_created_at_utc: str | None = None,
|
||||
) -> M48StaticOccupancyQualificationResult:
|
||||
"""Publish one deterministic, append-only M4.8R2 qualification result."""
|
||||
|
||||
repository = repository_root.resolve(strict=True)
|
||||
profile_bytes, profile = _read_profile(profile_path)
|
||||
source = _object(profile["source"], "M4.8R2 source")
|
||||
try:
|
||||
m47 = read_m47_reference_graph_lab(m47_lab_root)
|
||||
small_static = read_m48_small_static_passage_regression(small_static_result_root)
|
||||
except (M47ReferenceGraphLabError, M48SmallStaticRegressionError) as exc:
|
||||
raise M48StaticOccupancyQualificationError(
|
||||
"accepted M4.7/M4.8R1 evidence is invalid"
|
||||
) from exc
|
||||
if (
|
||||
m47.result_id != source.get("m47_lab_result_id")
|
||||
or small_static.result_id != source.get("small_static_result_id")
|
||||
or m47.manifest.get("accepted") is not True
|
||||
or m47.manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 source identity changed")
|
||||
|
||||
anchors_path = small_static.result_root / "anchors.jsonl"
|
||||
if _file_sha256(anchors_path) != source.get("small_static_anchors_sha256"):
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 anchor ledger changed")
|
||||
|
||||
graph_root = graph_result_root.resolve(strict=True)
|
||||
graph_manifest = _read_json(graph_root / "manifest.json", maximum=2 * 1024 * 1024)
|
||||
frames_path = graph_root / "frames.jsonl"
|
||||
graph_files = _object(graph_manifest.get("files"), "M4.8R2 graph files")
|
||||
graph_frames = _object(graph_files.get("frames.jsonl"), "M4.8R2 graph frame artifact")
|
||||
if (
|
||||
graph_root.name != source.get("m47_graph_result_id")
|
||||
or graph_manifest.get("result_id") != graph_root.name
|
||||
or graph_manifest.get("accepted") is not True
|
||||
or graph_frames.get("sha256") != source.get("m47_graph_frames_sha256")
|
||||
or _file_sha256(frames_path) != source.get("m47_graph_frames_sha256")
|
||||
):
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 graph binding changed")
|
||||
|
||||
selection = _object(profile["selection"], "M4.8R2 selection")
|
||||
anchors = tuple(
|
||||
row
|
||||
for row in small_static.anchors
|
||||
if row.get("motion") == selection.get("motion")
|
||||
and row.get("requires_avoidance_or_clearance")
|
||||
is selection.get("requires_avoidance_or_clearance")
|
||||
)
|
||||
if not anchors:
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 selected no static anchors")
|
||||
frame_rows = _selected_graph_frames(frames_path, {int(row["sequence"]) for row in anchors})
|
||||
store = RecordedGeometryStore.from_repository(repository)
|
||||
candidate = _object(profile["candidate"], "M4.8R2 candidate")
|
||||
candidate_association = replace(
|
||||
store.profile.association,
|
||||
semantic_minimum_occupied_points=int(candidate["minimum_points"]),
|
||||
semantic_minimum_occupied_voxels=int(candidate["minimum_voxels"]),
|
||||
semantic_voxel_size_m=float(candidate["voxel_size_m"]),
|
||||
depth_cluster_minimum_gap_m=float(candidate["depth_cluster_minimum_gap_m"]),
|
||||
depth_cluster_gap_fraction=float(candidate["depth_cluster_gap_fraction"]),
|
||||
spatial_cluster_radius_m=float(candidate["spatial_cluster_radius_m"]),
|
||||
)
|
||||
|
||||
cases: list[dict[str, Any]] = []
|
||||
for anchor in anchors:
|
||||
sequence = int(anchor["sequence"])
|
||||
graph_row = frame_rows[sequence]
|
||||
frame = store.frame_for_index(sequence)
|
||||
if frame is None or not frame.surface_valid:
|
||||
raise M48StaticOccupancyQualificationError(
|
||||
"selected M4.8R2 anchor lacks qualified current LiDAR"
|
||||
)
|
||||
projected = project_map_points_kb4(
|
||||
frame.points_map,
|
||||
position_map_xyz=frame.sensor_position_map,
|
||||
orientation_map_from_lidar_xyzw=frame.sensor_orientation_xyzw,
|
||||
profile=frame.projection,
|
||||
)
|
||||
bbox = _pixel_bbox(anchor["extent_xyxy"], frame.projection.width, frame.projection.height)
|
||||
baseline = semantic_geometry_support(
|
||||
bbox,
|
||||
projected=projected,
|
||||
frame_points_map=frame.points_map,
|
||||
point_class=frame.point_class,
|
||||
profile=store.profile.association,
|
||||
)
|
||||
step_candidates = store.point_step_candidates_for_frame(sequence)
|
||||
if step_candidates is None:
|
||||
raise M48StaticOccupancyQualificationError(
|
||||
"selected M4.8R2 anchor lacks low-step evidence"
|
||||
)
|
||||
union_classes = np.array(frame.point_class, copy=True)
|
||||
union_classes[step_candidates > 0] = POINT_OCCUPIED
|
||||
additive = semantic_geometry_support(
|
||||
bbox,
|
||||
projected=projected,
|
||||
frame_points_map=frame.points_map,
|
||||
point_class=union_classes,
|
||||
profile=candidate_association,
|
||||
)
|
||||
graph_matches = _graph_component_matches(
|
||||
graph_row=graph_row,
|
||||
frame=frame,
|
||||
bbox=bbox,
|
||||
voxel_size_m=0.45,
|
||||
)
|
||||
raw_depths = _depths_in_bbox(projected.pixels_xy, projected.depths_m, bbox)
|
||||
distance = _support_distance(
|
||||
additive.occupied_depths_m, baseline.occupied_depths_m, raw_depths
|
||||
)
|
||||
band = _distance_band(distance, _object(profile["distance_bands_m"], "distance bands"))
|
||||
accepted_graph = bool(graph_matches)
|
||||
baseline_qualified = bool(baseline.qualified or accepted_graph)
|
||||
candidate_qualified = bool(additive.qualified or accepted_graph)
|
||||
false_free = graph_row["obstacle_map"].get("free_space_claimed") is True
|
||||
cases.append(
|
||||
{
|
||||
"schema_version": M48_STATIC_OCCUPANCY_CASE_SCHEMA,
|
||||
"anchor_id": anchor["anchor_id"],
|
||||
"clip_id": anchor["clip_id"],
|
||||
"sequence": sequence,
|
||||
"extent_xyxy": anchor["extent_xyxy"],
|
||||
"distance_m": distance,
|
||||
"distance_band": band,
|
||||
"accepted_graph": {
|
||||
"matched": accepted_graph,
|
||||
"component_count": len(graph_matches),
|
||||
"components": graph_matches,
|
||||
"free_space_claimed": false_free,
|
||||
},
|
||||
"current_local_surface": _support_projection(baseline),
|
||||
"additive_step_candidate": _support_projection(additive),
|
||||
"baseline_qualified": baseline_qualified,
|
||||
"candidate_qualified": candidate_qualified,
|
||||
"outcome": (
|
||||
"candidate-qualified"
|
||||
if candidate_qualified
|
||||
else "unresolved-unknown-never-free"
|
||||
),
|
||||
"authority": "operator-assisted-development-anchor-not-truth",
|
||||
}
|
||||
)
|
||||
cases.sort(key=lambda row: (int(row["sequence"]), str(row["anchor_id"])))
|
||||
|
||||
visual_report = _read_json(
|
||||
m47.result_root / "visual-report.json",
|
||||
maximum=2 * 1024 * 1024,
|
||||
)
|
||||
canonical = _canonical_anchors(visual_report, selection)
|
||||
metrics = _metrics(cases, canonical)
|
||||
acceptance = _object(profile["acceptance"], "M4.8R2 acceptance")
|
||||
gates = {
|
||||
"critical_near_candidate_recall": (
|
||||
metrics["critical_near_candidate_recall"]
|
||||
>= float(acceptance["minimum_critical_near_candidate_recall"])
|
||||
),
|
||||
"approach_candidate_recall": (
|
||||
metrics["approach_candidate_recall"]
|
||||
>= float(acceptance["minimum_approach_candidate_recall"])
|
||||
),
|
||||
"canonical_engineering_recall": (
|
||||
metrics["canonical_engineering_recall"]
|
||||
>= float(acceptance["minimum_canonical_engineering_recall"])
|
||||
),
|
||||
"zero_false_free": metrics["false_free_count"]
|
||||
<= int(acceptance["maximum_false_free_count"]),
|
||||
"independent_truth_available": False,
|
||||
}
|
||||
near_ready = bool(
|
||||
gates["critical_near_candidate_recall"]
|
||||
and gates["canonical_engineering_recall"]
|
||||
and gates["zero_false_free"]
|
||||
)
|
||||
accepted = bool(near_ready and gates["approach_candidate_recall"])
|
||||
created_at = _utc_timestamp(run_created_at_utc or datetime.now(UTC).isoformat())
|
||||
profile_sha256 = hashlib.sha256(profile_bytes).hexdigest()
|
||||
producer_sha256 = _file_sha256(Path(__file__).resolve())
|
||||
identity = {
|
||||
"schema_version": M48_STATIC_OCCUPANCY_RESULT_SCHEMA,
|
||||
"human_lab_id": profile["human_lab_id"],
|
||||
"run_label": profile["run_label"],
|
||||
"run_created_at_utc": created_at,
|
||||
"pipeline_id": profile["pipeline_id"],
|
||||
"experiment_id": profile["experiment_id"],
|
||||
"profile_id": profile["profile_id"],
|
||||
"profile_sha256": profile_sha256,
|
||||
"producer_sha256": producer_sha256,
|
||||
"source": source,
|
||||
"selection": {
|
||||
"operator_static_anchor_count": len(cases),
|
||||
"canonical_engineering_anchor_count": len(canonical),
|
||||
},
|
||||
"authority": dict(_AUTHORITY),
|
||||
}
|
||||
result_id = M48_STATIC_OCCUPANCY_PREFIX + _canonical_sha256(identity)
|
||||
method = {
|
||||
"schema_version": _METHOD_SCHEMA,
|
||||
"completeness": "complete",
|
||||
"execution_class": "deterministic",
|
||||
"pipeline_id": profile["pipeline_id"],
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": "accepted M4.7 current/rolling obstacle graph",
|
||||
"version": source["m47_graph_result_id"],
|
||||
"role": "immutable baseline occupied/unknown and threat decisions",
|
||||
"identity_sha256": source["m47_graph_frames_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "source",
|
||||
"name": "M4.8R1 operator-assisted static anchors",
|
||||
"version": source["small_static_result_id"],
|
||||
"role": "candidate-visible diagnostic anchors; not independent truth",
|
||||
"identity_sha256": source["small_static_anchors_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "additive low-step static occupancy candidate",
|
||||
"version": profile["profile_id"],
|
||||
"role": "CPU-only occupied-or-unknown evidence; never clearing",
|
||||
"identity_sha256": producer_sha256,
|
||||
},
|
||||
],
|
||||
}
|
||||
report = {
|
||||
"schema_version": M48_STATIC_OCCUPANCY_REPORT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"source": source,
|
||||
"configuration": {
|
||||
key: profile[key]
|
||||
for key in (
|
||||
"profile_id",
|
||||
"pipeline_id",
|
||||
"experiment_id",
|
||||
"human_lab_id",
|
||||
"run_label",
|
||||
"distance_bands_m",
|
||||
"candidate",
|
||||
"acceptance",
|
||||
"policy",
|
||||
)
|
||||
},
|
||||
"method": method,
|
||||
"metrics": metrics,
|
||||
"gates": gates,
|
||||
"decision": {
|
||||
"state": "accepted-bounded-static-occupancy-qualification"
|
||||
if accepted
|
||||
else "partial-static-occupancy-qualification",
|
||||
"critical_near_candidate_ready_for_shadow": near_ready,
|
||||
"production_accepted": False,
|
||||
"summary": (
|
||||
"Accepted graph covers "
|
||||
f"{metrics['baseline_qualified_count']}/{len(cases)} static assisted "
|
||||
"anchors; the additive step candidate covers "
|
||||
f"{metrics['candidate_qualified_count']}/{len(cases)}."
|
||||
),
|
||||
"next_action": (
|
||||
"Integrate the additive step evidence as an occupied-only Worker shadow, "
|
||||
"then measure full replay FPS, occupancy growth and the unresolved 8-12 m "
|
||||
"case."
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
(
|
||||
"Operator-assisted anchors are candidate-visible development evidence, "
|
||||
"not independent truth."
|
||||
),
|
||||
"Projected camera rectangles do not define physical 3D colliders or chassis clearance.",
|
||||
(
|
||||
"The step candidate may add conservative false occupancy and therefore "
|
||||
"requires a full replay load/volume shadow before cutover."
|
||||
),
|
||||
(
|
||||
"No ray clearing, planner-authoritative free space, physical navigation, "
|
||||
"command, actuation or collision-safety authority is granted."
|
||||
),
|
||||
],
|
||||
"authority": dict(_AUTHORITY),
|
||||
}
|
||||
destination = output_root.resolve(strict=False) / result_id
|
||||
_publish_result(destination, identity, created_at, accepted, report, tuple(cases), canonical)
|
||||
return read_m48_static_occupancy_qualification(destination)
|
||||
|
||||
|
||||
def read_m48_static_occupancy_qualification(
|
||||
result_root: Path,
|
||||
) -> M48StaticOccupancyQualificationResult:
|
||||
if result_root.is_symlink():
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 result root is invalid")
|
||||
root = result_root.resolve(strict=True)
|
||||
if root.name.startswith(M48_STATIC_OCCUPANCY_PREFIX) is False:
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 result root is invalid")
|
||||
manifest = _read_json(root / "manifest.json", maximum=2 * 1024 * 1024)
|
||||
report = _read_json(root / "report.json", maximum=4 * 1024 * 1024)
|
||||
cases = tuple(_read_jsonl(root / "cases.jsonl"))
|
||||
canonical = tuple(_read_jsonl(root / "canonical-anchors.jsonl"))
|
||||
if (
|
||||
manifest.get("schema_version") != M48_STATIC_OCCUPANCY_RESULT_SCHEMA
|
||||
or manifest.get("result_id") != root.name
|
||||
or report.get("schema_version") != M48_STATIC_OCCUPANCY_REPORT_SCHEMA
|
||||
or report.get("result_id") != root.name
|
||||
or manifest.get("ground_truth") is not False
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 result identity changed")
|
||||
identity = _object(manifest.get("identity"), "M4.8R2 identity")
|
||||
identity_sha256 = _canonical_sha256(identity)
|
||||
if root.name != M48_STATIC_OCCUPANCY_PREFIX + identity_sha256:
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 result identity changed")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list):
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 artifact proof changed")
|
||||
artifact_paths = [
|
||||
str(_object(item, "M4.8R2 artifact").get("path")) for item in artifacts
|
||||
]
|
||||
if sorted(artifact_paths) != [
|
||||
"canonical-anchors.jsonl",
|
||||
"cases.jsonl",
|
||||
"report.json",
|
||||
]:
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 artifact proof changed")
|
||||
for artifact in artifacts:
|
||||
item = _object(artifact, "M4.8R2 artifact")
|
||||
path = root / str(item.get("path"))
|
||||
if path.parent != root or _file_sha256(path) != item.get("sha256"):
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 artifact proof changed")
|
||||
if manifest.get("identity_sha256") != identity_sha256:
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 identity digest changed")
|
||||
if not cases or any(
|
||||
row.get("schema_version") != M48_STATIC_OCCUPANCY_CASE_SCHEMA for row in cases
|
||||
):
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 case ledger changed")
|
||||
if any(
|
||||
row.get("schema_version") != M48_STATIC_OCCUPANCY_CANONICAL_SCHEMA
|
||||
for row in canonical
|
||||
):
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 canonical ledger changed")
|
||||
return M48StaticOccupancyQualificationResult(
|
||||
root.name, root, manifest, report, cases, canonical
|
||||
)
|
||||
|
||||
|
||||
def _support_projection(value: Any) -> dict[str, object]:
|
||||
depths = value.occupied_depths_m
|
||||
return {
|
||||
"qualified": bool(value.qualified),
|
||||
"projected_point_count": int(value.projected_points_in_region),
|
||||
"occupied_point_count": int(value.occupied_points_in_region),
|
||||
"clustered_occupied_point_count": int(value.occupied_source_indices.size),
|
||||
"nearest_depth_m": None if not depths.size else round(float(np.min(depths)), 6),
|
||||
"median_depth_m": None if not depths.size else round(float(np.median(depths)), 6),
|
||||
}
|
||||
|
||||
|
||||
def _graph_component_matches(
|
||||
*,
|
||||
graph_row: dict[str, Any],
|
||||
frame: Any,
|
||||
bbox: tuple[float, float, float, float],
|
||||
voxel_size_m: float,
|
||||
) -> list[dict[str, object]]:
|
||||
obstacle_map = _object(graph_row.get("obstacle_map"), "M4.8R2 obstacle map")
|
||||
threats = {
|
||||
str(row.get("component_id")): row
|
||||
for row in graph_row.get("threats", [])
|
||||
if isinstance(row, dict)
|
||||
}
|
||||
matches: list[dict[str, object]] = []
|
||||
for obstacle in obstacle_map.get("occupied", []):
|
||||
item = _object(obstacle, "M4.8R2 occupied component")
|
||||
cells = item.get("cells")
|
||||
if not isinstance(cells, list) or not cells:
|
||||
continue
|
||||
points = np.asarray(
|
||||
[
|
||||
[
|
||||
(int(cell["x"]) + 0.5) * voxel_size_m,
|
||||
(int(cell["y"]) + 0.5) * voxel_size_m,
|
||||
(int(cell["z"]) + 0.5) * voxel_size_m,
|
||||
]
|
||||
for cell in cells
|
||||
],
|
||||
dtype=np.float64,
|
||||
)
|
||||
projected = project_map_points_kb4(
|
||||
points,
|
||||
position_map_xyz=frame.sensor_position_map,
|
||||
orientation_map_from_lidar_xyzw=frame.sensor_orientation_xyzw,
|
||||
profile=frame.projection,
|
||||
)
|
||||
depths = _depths_in_bbox(projected.pixels_xy, projected.depths_m, bbox)
|
||||
if not depths.size:
|
||||
continue
|
||||
component_id = str(item.get("component_id"))
|
||||
assessment = _object(threats.get(component_id), "M4.8R2 threat assessment")
|
||||
matches.append(
|
||||
{
|
||||
"component_id": component_id,
|
||||
"state": item.get("state"),
|
||||
"decision": assessment.get("decision"),
|
||||
"projected_cell_count": int(depths.size),
|
||||
"nearest_depth_m": round(float(np.min(depths)), 6),
|
||||
}
|
||||
)
|
||||
matches.sort(key=lambda row: (float(row["nearest_depth_m"]), str(row["component_id"])))
|
||||
return matches
|
||||
|
||||
|
||||
def _canonical_anchors(
|
||||
report: dict[str, Any], selection: dict[str, Any]
|
||||
) -> tuple[dict[str, Any], ...]:
|
||||
metrics = _object(report.get("metrics"), "M4.7 visual metrics")
|
||||
visual = _object(metrics.get("visual_evidence"), "M4.7 visual evidence")
|
||||
rows: list[dict[str, Any]] = []
|
||||
for sequence in selection.get("canonical_engineering_sequences", []):
|
||||
regression = _object(
|
||||
visual.get(f"frame_{sequence}_regression"),
|
||||
"canonical regression",
|
||||
)
|
||||
for anchor in regression.get("engineering_anchors", []):
|
||||
item = _object(anchor, "canonical anchor")
|
||||
rows.append(
|
||||
{
|
||||
"schema_version": M48_STATIC_OCCUPANCY_CANONICAL_SCHEMA,
|
||||
"sequence": int(sequence),
|
||||
"anchor_id": item.get("anchor_id"),
|
||||
"matched": item.get("matched") is True,
|
||||
"decision": item.get("decision"),
|
||||
"must_assert_threat": item.get("must_assert_threat") is True,
|
||||
"authority": "camera-reviewed-engineering-anchor-not-truth",
|
||||
}
|
||||
)
|
||||
return tuple(rows)
|
||||
|
||||
|
||||
def _metrics(
|
||||
cases: list[dict[str, Any]], canonical: tuple[dict[str, Any], ...]
|
||||
) -> dict[str, object]:
|
||||
def band_rows(name: str) -> list[dict[str, Any]]:
|
||||
return [row for row in cases if row["distance_band"] == name]
|
||||
|
||||
def rate(rows: list[dict[str, Any]], key: str) -> float:
|
||||
if not rows:
|
||||
return 0.0
|
||||
return sum(bool(row[key]) for row in rows) / len(rows)
|
||||
|
||||
near = band_rows("critical-near")
|
||||
approach = band_rows("approach")
|
||||
return {
|
||||
"operator_static_anchor_count": len(cases),
|
||||
"baseline_qualified_count": sum(bool(row["baseline_qualified"]) for row in cases),
|
||||
"candidate_qualified_count": sum(bool(row["candidate_qualified"]) for row in cases),
|
||||
"unresolved_unknown_count": sum(not bool(row["candidate_qualified"]) for row in cases),
|
||||
"critical_near_anchor_count": len(near),
|
||||
"critical_near_baseline_recall": rate(near, "baseline_qualified"),
|
||||
"critical_near_candidate_recall": rate(near, "candidate_qualified"),
|
||||
"approach_anchor_count": len(approach),
|
||||
"approach_baseline_recall": rate(approach, "baseline_qualified"),
|
||||
"approach_candidate_recall": rate(approach, "candidate_qualified"),
|
||||
"canonical_engineering_anchor_count": len(canonical),
|
||||
"canonical_engineering_recall": sum(bool(row["matched"]) for row in canonical)
|
||||
/ len(canonical)
|
||||
if canonical
|
||||
else 0.0,
|
||||
"false_free_count": sum(bool(row["accepted_graph"]["free_space_claimed"]) for row in cases),
|
||||
"independent_truth": False,
|
||||
}
|
||||
|
||||
|
||||
def _selected_graph_frames(path: Path, sequences: set[int]) -> dict[int, dict[str, Any]]:
|
||||
rows: dict[int, dict[str, Any]] = {}
|
||||
with path.open("r", encoding="utf-8") as stream:
|
||||
for line in stream:
|
||||
row = _object(json.loads(line), "M4.8R2 graph frame")
|
||||
sequence = row.get("sequence")
|
||||
if isinstance(sequence, int) and sequence in sequences:
|
||||
rows[sequence] = row
|
||||
if len(rows) == len(sequences):
|
||||
break
|
||||
if set(rows) != sequences:
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 graph frames are incomplete")
|
||||
return rows
|
||||
|
||||
|
||||
def _pixel_bbox(value: object, width: int, height: int) -> tuple[float, float, float, float]:
|
||||
extent = value if isinstance(value, list) else None
|
||||
if extent is None or len(extent) != 4:
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 anchor extent is invalid")
|
||||
return (
|
||||
float(extent[0]) * width,
|
||||
float(extent[1]) * height,
|
||||
float(extent[2]) * width,
|
||||
float(extent[3]) * height,
|
||||
)
|
||||
|
||||
|
||||
def _depths_in_bbox(
|
||||
pixels: np.ndarray, depths: np.ndarray, bbox: tuple[float, float, float, float]
|
||||
) -> np.ndarray:
|
||||
if not pixels.size:
|
||||
return np.empty(0, dtype=np.float64)
|
||||
inside = (
|
||||
(pixels[:, 0] >= bbox[0])
|
||||
& (pixels[:, 0] <= bbox[2])
|
||||
& (pixels[:, 1] >= bbox[1])
|
||||
& (pixels[:, 1] <= bbox[3])
|
||||
)
|
||||
return depths[inside]
|
||||
|
||||
|
||||
def _support_distance(*values: np.ndarray) -> float:
|
||||
for value in values:
|
||||
if value.size:
|
||||
return round(float(np.median(value)), 6)
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 anchor has no projected LiDAR depth")
|
||||
|
||||
|
||||
def _distance_band(distance: float, bands: dict[str, Any]) -> str:
|
||||
for key, label in (("critical_near", "critical-near"), ("approach", "approach")):
|
||||
bounds = bands.get(key)
|
||||
if (
|
||||
isinstance(bounds, list)
|
||||
and len(bounds) == 2
|
||||
and float(bounds[0]) <= distance < float(bounds[1])
|
||||
):
|
||||
return label
|
||||
return "outside-qualified-bands"
|
||||
|
||||
|
||||
def _read_profile(path: Path) -> tuple[bytes, dict[str, Any]]:
|
||||
encoded = path.resolve(strict=True).read_bytes()
|
||||
profile = _object(json.loads(encoded), "M4.8R2 profile")
|
||||
if (
|
||||
profile.get("schema_version") != M48_STATIC_OCCUPANCY_PROFILE_SCHEMA
|
||||
or profile.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 profile is invalid")
|
||||
return encoded, profile
|
||||
|
||||
|
||||
def _publish_result(
|
||||
destination: Path,
|
||||
identity: dict[str, Any],
|
||||
created_at: str,
|
||||
accepted: bool,
|
||||
report: dict[str, Any],
|
||||
cases: tuple[dict[str, Any], ...],
|
||||
canonical: tuple[dict[str, Any], ...],
|
||||
) -> None:
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{destination.name}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700)
|
||||
try:
|
||||
_write_json(staging / "report.json", report)
|
||||
_write_jsonl(staging / "cases.jsonl", cases)
|
||||
_write_jsonl(staging / "canonical-anchors.jsonl", canonical)
|
||||
artifacts = [
|
||||
_artifact(staging / name, role)
|
||||
for name, role in (
|
||||
("cases.jsonl", "operator-static-anchor-comparisons"),
|
||||
("canonical-anchors.jsonl", "accepted-canonical-engineering-anchors"),
|
||||
("report.json", "m48-static-occupancy-report"),
|
||||
)
|
||||
]
|
||||
manifest = {
|
||||
"schema_version": M48_STATIC_OCCUPANCY_RESULT_SCHEMA,
|
||||
"result_id": destination.name,
|
||||
"identity_sha256": _canonical_sha256(identity),
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at,
|
||||
"accepted": accepted,
|
||||
"ground_truth": False,
|
||||
"authority": dict(_AUTHORITY),
|
||||
"artifacts": artifacts,
|
||||
}
|
||||
_write_json(staging / "manifest.json", manifest)
|
||||
if destination.exists():
|
||||
existing = {
|
||||
path.name: _file_sha256(path) for path in destination.iterdir() if path.is_file()
|
||||
}
|
||||
proposed = {
|
||||
path.name: _file_sha256(path) for path in staging.iterdir() if path.is_file()
|
||||
}
|
||||
if existing != proposed:
|
||||
raise M48StaticOccupancyQualificationError("immutable M4.8R2 identity collided")
|
||||
shutil.rmtree(staging)
|
||||
return
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, object]:
|
||||
return {
|
||||
"path": path.name,
|
||||
"role": role,
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": _file_sha256(path),
|
||||
"media_type": "application/x-ndjson" if path.suffix == ".jsonl" else "application/json",
|
||||
}
|
||||
|
||||
|
||||
def _read_json(path: Path, *, maximum: int) -> dict[str, Any]:
|
||||
if path.is_symlink() or not path.is_file() or path.stat().st_size > maximum:
|
||||
raise M48StaticOccupancyQualificationError(f"{path.name} is unavailable")
|
||||
return _object(json.loads(path.read_text("utf-8")), path.name)
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
if path.is_symlink() or not path.is_file() or path.stat().st_size > 8 * 1024 * 1024:
|
||||
raise M48StaticOccupancyQualificationError(f"{path.name} is unavailable")
|
||||
return [
|
||||
_object(json.loads(line), path.name)
|
||||
for line in path.read_text("utf-8").splitlines()
|
||||
if line.strip()
|
||||
]
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_text(
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
def _write_jsonl(path: Path, rows: tuple[dict[str, Any], ...]) -> None:
|
||||
path.write_text(
|
||||
"".join(
|
||||
json.dumps(row, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
||||
for row in rows
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _file_sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _canonical_sha256(value: object) -> str:
|
||||
return hashlib.sha256(
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
||||
).hexdigest()
|
||||
|
||||
|
||||
def _object(value: object, label: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise M48StaticOccupancyQualificationError(f"{label} is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _utc_timestamp(value: str) -> str:
|
||||
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
if parsed.tzinfo is None:
|
||||
raise M48StaticOccupancyQualificationError("M4.8R2 creation time is invalid")
|
||||
return parsed.astimezone(UTC).isoformat().replace("+00:00", "Z")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"M48StaticOccupancyQualificationError",
|
||||
"M48StaticOccupancyQualificationResult",
|
||||
"build_m48_static_occupancy_qualification",
|
||||
"read_m48_static_occupancy_qualification",
|
||||
]
|
||||
@@ -0,0 +1,471 @@
|
||||
"""Seal native raw-fisheye M4.8Q risk review evidence as a terminal LAB phase."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.perception.fixed_class_detector_tournament import (
|
||||
canonical_json,
|
||||
false_authority,
|
||||
sha256_path,
|
||||
)
|
||||
|
||||
LAB_SCHEMA: Final = "missioncore.m48q-native-risk-quality-lab/v1"
|
||||
REPORT_SCHEMA: Final = "missioncore.m48q-native-risk-quality-report/v1"
|
||||
CATALOG_SCHEMA: Final = "missioncore.m48q-native-risk-review-catalog/v1"
|
||||
CASE_SCHEMA: Final = "missioncore.m48q-native-risk-review-case/v1"
|
||||
RESULT_PREFIX: Final = "m48q-native-risk-quality-lab-"
|
||||
PROFILE_RELATIVE_PATH: Final = Path("config/perception/m48q-native-risk-case-mining-v1.json")
|
||||
DETECTOR_PROFILE_RELATIVE_PATH: Final = Path(
|
||||
"config/perception/rf-detr-large-native-kb4-risk-shadow-v0.json"
|
||||
)
|
||||
WORKER_RELATIVE_ROOT: Final = Path(
|
||||
".runtime/worker-results/m48q-native-risk-case-mining/ravnoves00-native-raw-review-v3"
|
||||
)
|
||||
EXPECTED_PROFILE_SHA256: Final = "c455055e63578d505edc80b66d67e795916a3d0297cc18bfee5a6af7b032ceda"
|
||||
EXPECTED_DETECTOR_PROFILE_SHA256: Final = (
|
||||
"dbf4da5dbad6c3c22b1280b46ffcad81719bd183c81c263a4859847d829019b6"
|
||||
)
|
||||
EXPECTED_WORKER_RESULT_SHA256: Final = (
|
||||
"a8f98d6d68abd6fd627caa05aaa3f048d69279a989b07fc2552b508af909a623"
|
||||
)
|
||||
EXPECTED_CASES_SHA256: Final = "9cea2a4a4558d761bc76b0d70474673ff07210473f53bd32cf178e8e0c8e179c"
|
||||
EXPECTED_REPORT_IDENTITY_SHA256: Final = (
|
||||
"b744775c0e08d18d728f04021cd2acf9b1ed6fcacf1520352ee04e06a6d5d324"
|
||||
)
|
||||
CASE_ID: Final = re.compile(r"^[0-9]{6}$")
|
||||
|
||||
|
||||
class M48QNativeRiskQualityLabError(RuntimeError):
|
||||
"""Raised when native M4.8Q evidence cannot be sealed honestly."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class M48QNativeRiskQualityLabResult:
|
||||
result_root: Path
|
||||
result_id: str
|
||||
manifest: dict[str, Any]
|
||||
|
||||
|
||||
def build_m48q_native_risk_quality_lab(
|
||||
*,
|
||||
repository_root: Path,
|
||||
output_root: Path,
|
||||
) -> M48QNativeRiskQualityLabResult:
|
||||
repository = repository_root.expanduser().resolve(strict=True)
|
||||
profile_path = repository / PROFILE_RELATIVE_PATH
|
||||
detector_profile_path = repository / DETECTOR_PROFILE_RELATIVE_PATH
|
||||
worker_root = repository / WORKER_RELATIVE_ROOT
|
||||
worker_result_path = worker_root / "result.json"
|
||||
cases_path = worker_root / "cases.jsonl"
|
||||
images_root = worker_root / "cases"
|
||||
for path in (
|
||||
profile_path,
|
||||
detector_profile_path,
|
||||
worker_result_path,
|
||||
cases_path,
|
||||
):
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise M48QNativeRiskQualityLabError(f"required M4.8Q evidence is missing: {path.name}")
|
||||
if images_root.is_symlink() or not images_root.is_dir():
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q image evidence is missing")
|
||||
expected_hashes = {
|
||||
profile_path: EXPECTED_PROFILE_SHA256,
|
||||
detector_profile_path: EXPECTED_DETECTOR_PROFILE_SHA256,
|
||||
worker_result_path: EXPECTED_WORKER_RESULT_SHA256,
|
||||
cases_path: EXPECTED_CASES_SHA256,
|
||||
}
|
||||
if any(sha256_path(path) != expected for path, expected in expected_hashes.items()):
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q source evidence identity changed")
|
||||
|
||||
profile = _read_object(profile_path)
|
||||
detector_profile = _read_object(detector_profile_path)
|
||||
worker_result = _read_object(worker_result_path)
|
||||
cases = _read_cases(cases_path)
|
||||
_validate_inputs(
|
||||
profile=profile,
|
||||
detector_profile=detector_profile,
|
||||
worker_result=worker_result,
|
||||
cases=cases,
|
||||
images_root=images_root,
|
||||
)
|
||||
method = _method(profile, detector_profile, worker_result)
|
||||
identity = {
|
||||
"schema_version": LAB_SCHEMA,
|
||||
"profile": {
|
||||
"profile_id": profile["profile_id"],
|
||||
"sha256": EXPECTED_PROFILE_SHA256,
|
||||
},
|
||||
"detector_profile": {
|
||||
"profile_id": detector_profile["profile_id"],
|
||||
"sha256": EXPECTED_DETECTOR_PROFILE_SHA256,
|
||||
},
|
||||
"source": {
|
||||
"source_id": "RAVNOVES00",
|
||||
"video_sha256": profile["source"]["video_sha256"],
|
||||
"graph_result_sha256": worker_result["source"]["graph_result_sha256"],
|
||||
"graph_frames_sha256": worker_result["source"]["graph_frames_sha256"],
|
||||
"comparison_result_sha256": worker_result["source"]["comparison_result_sha256"],
|
||||
"comparison_frames_sha256": worker_result["source"]["comparison_frames_sha256"],
|
||||
"worker_result_sha256": EXPECTED_WORKER_RESULT_SHA256,
|
||||
"cases_sha256": EXPECTED_CASES_SHA256,
|
||||
},
|
||||
"method": method,
|
||||
"authority": false_authority(),
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest()
|
||||
result_id = RESULT_PREFIX + identity_sha256
|
||||
root = output_root.expanduser().absolute()
|
||||
root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
destination = root / result_id
|
||||
if destination.exists():
|
||||
manifest = _read_object(destination / "manifest.json")
|
||||
if (
|
||||
manifest.get("schema_version") != LAB_SCHEMA
|
||||
or manifest.get("identity_sha256") != identity_sha256
|
||||
or manifest.get("result_id") != result_id
|
||||
):
|
||||
raise M48QNativeRiskQualityLabError("existing M4.8Q LAB identity conflicts")
|
||||
return M48QNativeRiskQualityLabResult(destination, result_id, manifest)
|
||||
|
||||
created_at_utc = datetime.now(UTC).isoformat(timespec="microseconds").replace("+00:00", "Z")
|
||||
temporary = Path(tempfile.mkdtemp(prefix=".m48q-native-risk-lab-", dir=root))
|
||||
try:
|
||||
(temporary / "cases").mkdir(mode=0o700)
|
||||
shutil.copyfile(profile_path, temporary / "profile.json")
|
||||
shutil.copyfile(detector_profile_path, temporary / "detector-profile.json")
|
||||
shutil.copyfile(worker_result_path, temporary / "worker-result.json")
|
||||
shutil.copyfile(cases_path, temporary / "cases.jsonl")
|
||||
|
||||
catalog_cases: list[dict[str, object]] = []
|
||||
for case in cases:
|
||||
image = case["image"]
|
||||
source_image = worker_root / image["path"]
|
||||
destination_image = temporary / image["path"]
|
||||
shutil.copyfile(source_image, destination_image)
|
||||
catalog_cases.append(
|
||||
{
|
||||
"case_id": case["case_id"],
|
||||
"sequence": case["sequence"],
|
||||
"frame_id": case["frame_id"],
|
||||
"evidence_time_ns": case["evidence_time_ns"],
|
||||
"path": image["path"],
|
||||
"media_type": "image/jpeg",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"byte_length": destination_image.stat().st_size,
|
||||
"sha256": sha256_path(destination_image),
|
||||
"geometric_resampling": False,
|
||||
"selection_buckets": case["selection_buckets"],
|
||||
"comparison": case["comparison"],
|
||||
"proposals": case["proposals"],
|
||||
}
|
||||
)
|
||||
catalog = {
|
||||
"schema_version": CATALOG_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"case_count": len(catalog_cases),
|
||||
"source_raster": {"width": 800, "height": 600},
|
||||
"overlay": {
|
||||
"client_rendered": True,
|
||||
"toggleable": True,
|
||||
"box_coordinates": "source-pixel-xyxy",
|
||||
"class_names": True,
|
||||
"scores": True,
|
||||
},
|
||||
"ground_truth": False,
|
||||
"cases": catalog_cases,
|
||||
}
|
||||
catalog_path = temporary / "catalog.json"
|
||||
catalog_path.write_bytes(canonical_json(catalog) + b"\n")
|
||||
|
||||
graph_qualification = detector_profile["qualification"][
|
||||
"full_ravnoves00_integrated_reference_graph"
|
||||
]
|
||||
decision = {
|
||||
"review_ready": True,
|
||||
"quality_evaluated": False,
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"production_accepted": False,
|
||||
"next_action": "operator-adjudication-in-existing-m48-review-instrument",
|
||||
}
|
||||
report = {
|
||||
"schema_version": REPORT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"source": {
|
||||
**profile["source"],
|
||||
"graph_result_sha256": worker_result["source"]["graph_result_sha256"],
|
||||
"graph_frames_sha256": worker_result["source"]["graph_frames_sha256"],
|
||||
"independent_route_truth_available": False,
|
||||
},
|
||||
"configuration": {
|
||||
"candidate": profile["candidate"],
|
||||
"selection": profile["selection"],
|
||||
},
|
||||
"method": method,
|
||||
"execution": {
|
||||
"worker": worker_result["worker"],
|
||||
"full_graph_frames": graph_qualification["frame_count"],
|
||||
"requested_source_rate_hz": graph_qualification["requested_source_rate_hz"],
|
||||
"effective_world_state_fps": graph_qualification["effective_world_state_fps"],
|
||||
"world_state_completion_p95_ms": graph_qualification[
|
||||
"world_state_completion_p95_ms"
|
||||
],
|
||||
"detector_total_p95_ms": graph_qualification["detector_total_p95_ms"],
|
||||
"gpu_utilization_p95_percent": graph_qualification["gpu_utilization_p95_percent"],
|
||||
"gpu_memory_used_maximum_mib": graph_qualification["gpu_memory_used_maximum_mib"],
|
||||
"additional_inference_passes": 0,
|
||||
},
|
||||
"metrics": {
|
||||
"selection": worker_result["selection"],
|
||||
"runtime": {
|
||||
"delivery_ratio": graph_qualification["delivery_ratio"],
|
||||
"integrated_runtime_gate_passed": graph_qualification[
|
||||
"integrated_runtime_gate_passed"
|
||||
],
|
||||
"operating_target_gate_passed": graph_qualification[
|
||||
"operating_target_gate_passed"
|
||||
],
|
||||
},
|
||||
"native_tensor_parity": detector_profile["qualification"][
|
||||
"native_pytorch_tensorrt_parity"
|
||||
],
|
||||
"native_vs_legacy_704": detector_profile["qualification"][
|
||||
"full_ravnoves00_native_vs_legacy_704"
|
||||
],
|
||||
},
|
||||
"acceptance": {
|
||||
"review_ready": True,
|
||||
"integrated_runtime_gate_passed": True,
|
||||
"independent_quality_evaluated": False,
|
||||
"semantic_candidate_accepted": False,
|
||||
},
|
||||
"decision": decision,
|
||||
"limitations": worker_result["limitations"],
|
||||
"authority": false_authority(),
|
||||
"visual_evidence": {
|
||||
"kind": "native-raw-fisheye-risk-case-review",
|
||||
"case_count": 24,
|
||||
"source_raster": "800x600",
|
||||
"geometric_resampling": False,
|
||||
"boxes_baked_into_images": False,
|
||||
"ground_truth": False,
|
||||
},
|
||||
}
|
||||
report_path = temporary / "report.json"
|
||||
report_path.write_bytes(canonical_json(report) + b"\n")
|
||||
artifacts = _artifact_manifest(temporary)
|
||||
manifest = {
|
||||
"schema_version": LAB_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"status": "complete-review-ready-quality-not-adjudicated",
|
||||
"completed": True,
|
||||
"bounded_question_accepted": True,
|
||||
"ground_truth": False,
|
||||
"method": method,
|
||||
"metrics": report["metrics"],
|
||||
"decision": decision,
|
||||
"limitations": worker_result["limitations"],
|
||||
"authority": false_authority(),
|
||||
"artifacts": artifacts,
|
||||
}
|
||||
(temporary / "manifest.json").write_bytes(canonical_json(manifest) + b"\n")
|
||||
temporary.replace(destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(temporary, ignore_errors=True)
|
||||
raise
|
||||
return M48QNativeRiskQualityLabResult(destination, result_id, manifest)
|
||||
|
||||
|
||||
def _validate_inputs(
|
||||
*,
|
||||
profile: dict[str, Any],
|
||||
detector_profile: dict[str, Any],
|
||||
worker_result: dict[str, Any],
|
||||
cases: list[dict[str, Any]],
|
||||
images_root: Path,
|
||||
) -> None:
|
||||
if (
|
||||
profile.get("schema_version") != "missioncore.m48q-native-risk-case-mining-profile/v1"
|
||||
or profile.get("source", {}).get("geometric_resampling") is not False
|
||||
or profile.get("source", {}).get("raster_width") != 800
|
||||
or profile.get("source", {}).get("raster_height") != 600
|
||||
or profile.get("authority") != false_authority()
|
||||
or detector_profile.get("schema_version")
|
||||
!= "missioncore.rf-detr-native-risk-shadow-profile/v0"
|
||||
or detector_profile.get("preprocessing", {}).get("resize") is not False
|
||||
or detector_profile.get("preprocessing", {}).get("geometric_resampling") is not False
|
||||
or detector_profile.get("status", {}).get("integrated_world_state_gate_passed") is not True
|
||||
or worker_result.get("schema_version")
|
||||
!= "missioncore.m48q-native-risk-case-mining-result/v1"
|
||||
or worker_result.get("status") != "complete-review-ready-quality-not-adjudicated"
|
||||
or worker_result.get("completed") is not True
|
||||
or worker_result.get("report_identity_sha256") != EXPECTED_REPORT_IDENTITY_SHA256
|
||||
or worker_result.get("artifacts", {}).get("cases", {}).get("sha256")
|
||||
!= EXPECTED_CASES_SHA256
|
||||
or worker_result.get("selection", {}).get("case_count") != 24
|
||||
or worker_result.get("decision", {}).get("quality_evaluated") is not False
|
||||
or worker_result.get("authority") != false_authority()
|
||||
or len(cases) != 24
|
||||
or len({case.get("case_id") for case in cases}) != 24
|
||||
):
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q evidence contract changed")
|
||||
for case in cases:
|
||||
image = case.get("image")
|
||||
proposals = case.get("proposals")
|
||||
if (
|
||||
case.get("schema_version") != CASE_SCHEMA
|
||||
or not isinstance(case.get("case_id"), str)
|
||||
or CASE_ID.fullmatch(case["case_id"]) is None
|
||||
or case.get("case_id") != f"{case.get('sequence'):06d}"
|
||||
or case.get("frame_id") != f"frame-{case.get('sequence'):06d}"
|
||||
or case.get("ground_truth") is not False
|
||||
or case.get("quality_evaluated") is not False
|
||||
or case.get("authority") != false_authority()
|
||||
or not isinstance(image, dict)
|
||||
or image.get("path") != f"cases/frame-{case['case_id']}.jpg"
|
||||
or image.get("width") != 800
|
||||
or image.get("height") != 600
|
||||
or image.get("geometric_resampling") is not False
|
||||
or not isinstance(proposals, list)
|
||||
or not proposals
|
||||
):
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q review case contract changed")
|
||||
image_path = images_root.parent / image["path"]
|
||||
if (
|
||||
image_path.is_symlink()
|
||||
or not image_path.is_file()
|
||||
or image_path.stat().st_size != image.get("byte_length")
|
||||
or sha256_path(image_path) != image.get("sha256")
|
||||
):
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q review image proof changed")
|
||||
|
||||
|
||||
def _method(
|
||||
profile: dict[str, Any],
|
||||
detector_profile: dict[str, Any],
|
||||
worker_result: dict[str, Any],
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": "m48q-native-raw-fisheye-risk-case-review/v1",
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": "RAVNOVES00 raw KB4 video",
|
||||
"version": "800x600 immutable recording",
|
||||
"role": "unrectified review raster",
|
||||
"identity_sha256": profile["source"]["video_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "model",
|
||||
"name": "RF-DETR-L native KB4 TensorRT",
|
||||
"version": profile["candidate"]["model_id"],
|
||||
"role": "fixed-class behavior-risk shadow proposals",
|
||||
"identity_sha256": profile["candidate"]["engine_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "runtime",
|
||||
"name": "M4.7 native reference graph",
|
||||
"version": "full RAVNOVES00 at recorded 12 FPS",
|
||||
"role": "proposal and realtime evidence source",
|
||||
"identity_sha256": worker_result["source"]["graph_result_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "bounded diagnostic case miner",
|
||||
"version": profile["profile_id"],
|
||||
"role": "deterministic risk-family and edge-case sampling",
|
||||
"identity_sha256": worker_result["identity"]["runner_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "tool",
|
||||
"name": "M4.8 existing image-case review instrument",
|
||||
"version": "client-rendered source-pixel overlay/v1",
|
||||
"role": "operator review without baked boxes",
|
||||
"identity_sha256": EXPECTED_CASES_SHA256,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _artifact_manifest(root: Path) -> list[dict[str, object]]:
|
||||
artifacts: list[dict[str, object]] = []
|
||||
for path in sorted(item for item in root.rglob("*") if item.is_file()):
|
||||
relative = path.relative_to(root).as_posix()
|
||||
if relative == "manifest.json":
|
||||
continue
|
||||
media_type = "application/json"
|
||||
schema_version: str | None = None
|
||||
role = "supporting-evidence"
|
||||
if relative == "report.json":
|
||||
role = "laboratory-report"
|
||||
schema_version = REPORT_SCHEMA
|
||||
elif relative == "catalog.json":
|
||||
role = "visual-evidence-catalog"
|
||||
schema_version = CATALOG_SCHEMA
|
||||
elif relative == "profile.json":
|
||||
role = "predeclared-native-risk-review-profile"
|
||||
schema_version = "missioncore.m48q-native-risk-case-mining-profile/v1"
|
||||
elif relative == "detector-profile.json":
|
||||
role = "qualified-native-detector-profile"
|
||||
schema_version = "missioncore.rf-detr-native-risk-shadow-profile/v0"
|
||||
elif relative == "worker-result.json":
|
||||
role = "upstream-worker-case-mining-result"
|
||||
schema_version = "missioncore.m48q-native-risk-case-mining-result/v1"
|
||||
elif relative == "cases.jsonl":
|
||||
media_type = "application/x-ndjson"
|
||||
role = "native-risk-review-case-ledger"
|
||||
schema_version = CASE_SCHEMA
|
||||
elif relative.endswith(".jpg"):
|
||||
media_type = "image/jpeg"
|
||||
role = "visual-evidence-native-raw-fisheye-frame"
|
||||
artifacts.append(
|
||||
{
|
||||
"role": role,
|
||||
"path": relative,
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": sha256_path(path),
|
||||
"media_type": media_type,
|
||||
"schema_version": schema_version,
|
||||
}
|
||||
)
|
||||
return artifacts
|
||||
|
||||
|
||||
def _read_object(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value: object = json.loads(path.read_text("utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise M48QNativeRiskQualityLabError(f"invalid JSON evidence: {path.name}") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise M48QNativeRiskQualityLabError(f"JSON evidence must be an object: {path.name}")
|
||||
return value
|
||||
|
||||
|
||||
def _read_cases(path: Path) -> list[dict[str, Any]]:
|
||||
cases: list[dict[str, Any]] = []
|
||||
try:
|
||||
with path.open("r", encoding="utf-8") as stream:
|
||||
for line in stream:
|
||||
value: object = json.loads(line)
|
||||
if not isinstance(value, dict):
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q case ledger row is not an object")
|
||||
cases.append(value)
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise M48QNativeRiskQualityLabError("M4.8Q case ledger cannot be read") from exc
|
||||
return cases
|
||||
@@ -344,6 +344,28 @@ class RecordedGeometryStore:
|
||||
points.setflags(write=False)
|
||||
return points
|
||||
|
||||
def point_step_candidates_for_frame(self, frame_index: int) -> UInt8Array | None:
|
||||
"""Expose the sealed low-step diagnostic in the source point index space.
|
||||
|
||||
The array is evidence only: a non-zero value may add conservative
|
||||
occupied/unknown support, but it never clears a cell or claims free
|
||||
space. Unavailable and surface-invalid frames remain unavailable.
|
||||
"""
|
||||
|
||||
frame = self.frame_for_index(frame_index)
|
||||
if frame is None or not frame.surface_valid:
|
||||
return None
|
||||
offsets = self._source["cloud_offsets"]
|
||||
start, end = int(offsets[frame_index]), int(offsets[frame_index + 1])
|
||||
values = np.asarray(
|
||||
self._surface["point_step_candidate"][start:end],
|
||||
dtype=np.uint8,
|
||||
)
|
||||
if values.shape != (frame.source_point_count,):
|
||||
raise GeometryProviderError("local-surface step evidence changed")
|
||||
values.setflags(write=False)
|
||||
return values
|
||||
|
||||
@property
|
||||
def maximum_current_point_count(self) -> int:
|
||||
"""Return the immutable source-pack upper bound for one recorded increment."""
|
||||
|
||||
+15
-1
@@ -153,8 +153,8 @@ from k1link.web.runtime_readiness import (
|
||||
build_runtime_readiness,
|
||||
)
|
||||
from k1link.web.session_api import build_session_router
|
||||
from k1link.web.simulation_world_provider_api import build_simulation_world_provider_router
|
||||
from k1link.web.simulation_projects_api import build_simulation_projects_router
|
||||
from k1link.web.simulation_world_provider_api import build_simulation_world_provider_router
|
||||
from k1link.web.system_telemetry_api import build_system_telemetry_router
|
||||
from k1link.web.viewer_diagnostics_api import build_viewer_diagnostics_router
|
||||
|
||||
@@ -934,6 +934,13 @@ app.include_router(
|
||||
/ "m48"
|
||||
/ "small-static-passage-regression-results"
|
||||
),
|
||||
static_occupancy_result_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "m48"
|
||||
/ "static-occupancy-qualification-results"
|
||||
),
|
||||
camera_frame_provider=(
|
||||
session_recorded_camera_frame_service.extract
|
||||
if session_recorded_camera_frame_service is not None
|
||||
@@ -980,6 +987,13 @@ app.include_router(
|
||||
/ "m48t-risk-quality"
|
||||
/ "lab-results"
|
||||
),
|
||||
native_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "m48t-risk-quality"
|
||||
/ "native-lab-results"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
|
||||
@@ -48,6 +48,11 @@ from k1link.laboratory.m48_small_static_regression import (
|
||||
M48SmallStaticRegressionResult,
|
||||
read_m48_small_static_passage_regression,
|
||||
)
|
||||
from k1link.laboratory.m48_static_occupancy_qualification import (
|
||||
M48StaticOccupancyQualificationError,
|
||||
M48StaticOccupancyQualificationResult,
|
||||
read_m48_static_occupancy_qualification,
|
||||
)
|
||||
from k1link.sessions import RecordedCameraPlaybackSource
|
||||
|
||||
_PACK_ID = re.compile(r"^m48-object-quality-pack-[a-f0-9]{64}$")
|
||||
@@ -57,6 +62,9 @@ _SMALL_STATIC_RESULT_ID = re.compile(
|
||||
r"^m48-small-static-passage-regression-[a-f0-9]{64}$"
|
||||
)
|
||||
_SMALL_STATIC_ANCHOR_ID = re.compile(r"^anchor-[a-f0-9]{24}$")
|
||||
_STATIC_OCCUPANCY_RESULT_ID = re.compile(
|
||||
r"^m48-static-occupancy-qualification-[a-f0-9]{64}$"
|
||||
)
|
||||
_M47_LAB_RESULT_ID = re.compile(r"^m47-reference-graph-lab-[a-f0-9]{64}$")
|
||||
_FAILURE_ID = re.compile(r"^m48-failure-[a-f0-9]{64}$")
|
||||
_REVIEW_SESSION_ID = re.compile(r"^m48-review-session-[a-f0-9]{64}$")
|
||||
@@ -93,6 +101,12 @@ _SMALL_STATIC_CASE_CATALOG_SCHEMA: Final = (
|
||||
_SMALL_STATIC_CASE_VIEW_SCHEMA: Final = (
|
||||
"missioncore.m48-small-static-passage-regression-case-view/v1"
|
||||
)
|
||||
_STATIC_OCCUPANCY_RESULT_VIEW_SCHEMA: Final = (
|
||||
"missioncore.m48-static-occupancy-qualification-result-view/v1"
|
||||
)
|
||||
_STATIC_OCCUPANCY_CASE_CATALOG_SCHEMA: Final = (
|
||||
"missioncore.m48-static-occupancy-case-catalog/v1"
|
||||
)
|
||||
_FAILURE_ATLAS_VIEW_SCHEMA: Final = "missioncore.m48-object-quality-failure-atlas-view/v1"
|
||||
_FAILURE_CASE_VIEW_SCHEMA: Final = "missioncore.m48-object-quality-failure-case-view/v1"
|
||||
_REVIEW_CAPABILITY_HEADER: Final = "X-M48-Review-Capability"
|
||||
@@ -438,6 +452,7 @@ def build_m48_object_quality_router(
|
||||
truth_root_provider: RootProvider = lambda: None,
|
||||
result_root_provider: RootProvider = lambda: None,
|
||||
small_static_result_root_provider: RootProvider = lambda: None,
|
||||
static_occupancy_result_root_provider: RootProvider = lambda: None,
|
||||
camera_frame_provider: CameraFrameProvider | None = None,
|
||||
camera_playback_provider: CameraPlaybackProvider | None = None,
|
||||
spatial_evidence_provider: SpatialEvidenceProvider | None = None,
|
||||
@@ -1593,6 +1608,56 @@ def build_m48_object_quality_router(
|
||||
"access": "assisted-development-regression-case-read-only",
|
||||
}
|
||||
|
||||
@router.get("/regressions/static-occupancy/{result_id}")
|
||||
def get_static_occupancy_qualification(
|
||||
result_id: Annotated[str, ApiPath(pattern=_STATIC_OCCUPANCY_RESULT_ID.pattern)],
|
||||
) -> dict[str, object]:
|
||||
result = _resolve_static_occupancy_result(
|
||||
static_occupancy_result_root_provider,
|
||||
result_id,
|
||||
)
|
||||
source = _object(result.report.get("source"), "M4.8R2 source")
|
||||
configuration = _object(
|
||||
result.report.get("configuration"),
|
||||
"M4.8R2 configuration",
|
||||
)
|
||||
return {
|
||||
"schema_version": _STATIC_OCCUPANCY_RESULT_VIEW_SCHEMA,
|
||||
"result_id": result.result_id,
|
||||
"created_at_utc": result.manifest.get("created_at_utc"),
|
||||
"reference_graph_lab_result_id": source.get("m47_lab_result_id"),
|
||||
"small_static_result_id": source.get("small_static_result_id"),
|
||||
"run_label": configuration.get("run_label"),
|
||||
"pipeline_id": configuration.get("pipeline_id"),
|
||||
"experiment_id": configuration.get("experiment_id"),
|
||||
"accepted": result.manifest.get("accepted"),
|
||||
"metrics": copy.deepcopy(result.report.get("metrics")),
|
||||
"gates": copy.deepcopy(result.report.get("gates")),
|
||||
"decision": copy.deepcopy(result.report.get("decision")),
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"authority": dict(_AUTHORITY),
|
||||
"access": "static-occupancy-qualification-read-only",
|
||||
}
|
||||
|
||||
@router.get("/regressions/static-occupancy/{result_id}/cases")
|
||||
def get_static_occupancy_qualification_cases(
|
||||
result_id: Annotated[str, ApiPath(pattern=_STATIC_OCCUPANCY_RESULT_ID.pattern)],
|
||||
) -> dict[str, object]:
|
||||
result = _resolve_static_occupancy_result(
|
||||
static_occupancy_result_root_provider,
|
||||
result_id,
|
||||
)
|
||||
return {
|
||||
"schema_version": _STATIC_OCCUPANCY_CASE_CATALOG_SCHEMA,
|
||||
"result_id": result.result_id,
|
||||
"cases": copy.deepcopy(result.cases),
|
||||
"case_count": len(result.cases),
|
||||
"ground_truth": False,
|
||||
"authority": dict(_AUTHORITY),
|
||||
"access": "static-occupancy-qualification-read-only",
|
||||
}
|
||||
|
||||
return router
|
||||
|
||||
|
||||
@@ -1691,6 +1756,27 @@ def _resolve_small_static_result(
|
||||
) from None
|
||||
|
||||
|
||||
def _resolve_static_occupancy_result(
|
||||
provider: RootProvider,
|
||||
result_id: str,
|
||||
) -> M48StaticOccupancyQualificationResult:
|
||||
if _STATIC_OCCUPANCY_RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="M4.8R2 result was not found")
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="M4.8R2 result was not found")
|
||||
path = root / result_id
|
||||
if path.is_symlink() or not path.is_dir():
|
||||
raise HTTPException(status_code=404, detail="M4.8R2 result was not found")
|
||||
try:
|
||||
resolved = path.resolve(strict=True)
|
||||
if resolved.parent != root:
|
||||
raise OSError("M4.8R2 result escaped root")
|
||||
return read_m48_static_occupancy_qualification(resolved)
|
||||
except (M48StaticOccupancyQualificationError, OSError, TypeError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="M4.8R2 result was not found") from None
|
||||
|
||||
|
||||
def _result_sources(
|
||||
result: M48ObjectQualityResult,
|
||||
*,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Read-only API for the sealed M4.8T quality and temporal LAB."""
|
||||
"""Read-only API for the M4.8T legacy quality and M4.8Q native review lifecycle."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -8,7 +8,7 @@ import json
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any, Final
|
||||
from typing import Any, Final, Literal
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
@@ -18,30 +18,61 @@ from k1link.laboratory.evidence_report import (
|
||||
LaboratoryEvidenceReportError,
|
||||
verify_laboratory_evidence_result,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
CATALOG_SCHEMA as NATIVE_CATALOG_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
LAB_SCHEMA as NATIVE_LAB_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
REPORT_SCHEMA as NATIVE_REPORT_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
RESULT_PREFIX as NATIVE_RESULT_PREFIX,
|
||||
)
|
||||
from k1link.laboratory.m48t_risk_quality_lab import (
|
||||
CATALOG_SCHEMA,
|
||||
LAB_SCHEMA,
|
||||
REPORT_SCHEMA,
|
||||
RESULT_PREFIX,
|
||||
CATALOG_SCHEMA as LEGACY_CATALOG_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48t_risk_quality_lab import (
|
||||
LAB_SCHEMA as LEGACY_LAB_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48t_risk_quality_lab import (
|
||||
REPORT_SCHEMA as LEGACY_REPORT_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48t_risk_quality_lab import (
|
||||
RESULT_PREFIX as LEGACY_RESULT_PREFIX,
|
||||
)
|
||||
from k1link.perception.fixed_class_detector_tournament import false_authority
|
||||
|
||||
RootProvider = Callable[[], Path | None]
|
||||
RESULT_ID: Final = re.compile(rf"^{re.escape(RESULT_PREFIX)}[a-f0-9]{{64}}$")
|
||||
CASE_ID: Final = re.compile(r"^[0-9]{12}$")
|
||||
VIEW_SCHEMA: Final = "missioncore.m48t-risk-quality-temporal-view/v1"
|
||||
CATALOG_VIEW_SCHEMA: Final = "missioncore.m48t-risk-quality-temporal-catalog/v1"
|
||||
_DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
Variant = Literal["legacy", "native"]
|
||||
LEGACY_RESULT_ID: Final = re.compile(rf"^{re.escape(LEGACY_RESULT_PREFIX)}[a-f0-9]{{64}}$")
|
||||
NATIVE_RESULT_ID: Final = re.compile(rf"^{re.escape(NATIVE_RESULT_PREFIX)}[a-f0-9]{{64}}$")
|
||||
LEGACY_CASE_ID: Final = re.compile(r"^[0-9]{12}$")
|
||||
NATIVE_CASE_ID: Final = re.compile(r"^[0-9]{6}$")
|
||||
LEGACY_VIEW_SCHEMA: Final = "missioncore.m48t-risk-quality-temporal-view/v1"
|
||||
NATIVE_VIEW_SCHEMA: Final = "missioncore.m48q-native-risk-quality-view/v1"
|
||||
CATALOG_VIEW_SCHEMA: Final = "missioncore.m48t-risk-quality-lifecycle-catalog/v1"
|
||||
_LEGACY_DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
work_id="m48t-risk-quality-temporal",
|
||||
runtime_relative_root=PurePosixPath("m48t-risk-quality/lab-results"),
|
||||
result_id_prefix="m48t-risk-quality-temporal-lab",
|
||||
document_name="manifest.json",
|
||||
result_schema_version=LAB_SCHEMA,
|
||||
result_schema_version=LEGACY_LAB_SCHEMA,
|
||||
)
|
||||
_NATIVE_DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
work_id="m48t-risk-quality-temporal",
|
||||
runtime_relative_root=PurePosixPath("m48t-risk-quality/native-lab-results"),
|
||||
result_id_prefix="m48q-native-risk-quality-lab",
|
||||
document_name="manifest.json",
|
||||
result_schema_version=NATIVE_LAB_SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
def build_m48t_risk_quality_lab_router(
|
||||
*, root_provider: RootProvider = lambda: None
|
||||
*,
|
||||
root_provider: RootProvider = lambda: None,
|
||||
native_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(
|
||||
prefix="/api/v1/laboratory/m48t/risk-quality",
|
||||
@@ -50,15 +81,23 @@ def build_m48t_risk_quality_lab_router(
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
return _empty_catalog(False)
|
||||
candidates = _candidates(root)
|
||||
configured = False
|
||||
candidates: list[tuple[Path, Variant]] = []
|
||||
providers: tuple[tuple[RootProvider, Variant, re.Pattern[str]], ...] = (
|
||||
(root_provider, "legacy", LEGACY_RESULT_ID),
|
||||
(native_root_provider, "native", NATIVE_RESULT_ID),
|
||||
)
|
||||
for provider, variant, pattern in providers:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
continue
|
||||
configured = True
|
||||
candidates.extend((item, variant) for item in _candidates(root, pattern))
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for candidate in candidates:
|
||||
for candidate, variant in candidates:
|
||||
try:
|
||||
items.append(_project_result(candidate))
|
||||
items.append(_project_result(candidate, variant))
|
||||
except RuntimeError:
|
||||
invalid_total += 1
|
||||
items.sort(
|
||||
@@ -67,7 +106,7 @@ def build_m48t_risk_quality_lab_router(
|
||||
)
|
||||
return {
|
||||
"schema_version": CATALOG_VIEW_SCHEMA,
|
||||
"configured": True,
|
||||
"configured": configured,
|
||||
"items": items[:limit],
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
@@ -77,18 +116,37 @@ def build_m48t_risk_quality_lab_router(
|
||||
@router.get("/results/{result_id}")
|
||||
def get_result(result_id: str) -> dict[str, object]:
|
||||
try:
|
||||
return _project_result(_resolve_candidate(root_provider, result_id))
|
||||
candidate, variant = _resolve_candidate(
|
||||
root_provider,
|
||||
native_root_provider,
|
||||
result_id,
|
||||
)
|
||||
return _project_result(candidate, variant)
|
||||
except RuntimeError:
|
||||
raise HTTPException(status_code=404, detail="M4.8T result not found") from None
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="M4.8T/M4.8Q result not found",
|
||||
) from None
|
||||
|
||||
@router.get("/results/{result_id}/review/{case_id}.jpg")
|
||||
def get_review_image(result_id: str, case_id: str) -> FileResponse:
|
||||
if CASE_ID.fullmatch(case_id) is None:
|
||||
raise HTTPException(status_code=404, detail="M4.8T review case not found")
|
||||
try:
|
||||
loaded = _load_result(_resolve_candidate(root_provider, result_id))
|
||||
candidate, variant = _resolve_candidate(
|
||||
root_provider,
|
||||
native_root_provider,
|
||||
result_id,
|
||||
)
|
||||
invalid_case = (variant == "legacy" and LEGACY_CASE_ID.fullmatch(case_id) is None) or (
|
||||
variant == "native" and NATIVE_CASE_ID.fullmatch(case_id) is None
|
||||
)
|
||||
if invalid_case:
|
||||
raise RuntimeError("case identity is invalid")
|
||||
loaded = _load_result(candidate, variant)
|
||||
except RuntimeError:
|
||||
raise HTTPException(status_code=404, detail="M4.8T result not found") from None
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="M4.8T/M4.8Q review case not found",
|
||||
) from None
|
||||
descriptor = next(
|
||||
(
|
||||
item
|
||||
@@ -98,8 +156,10 @@ def build_m48t_risk_quality_lab_router(
|
||||
None,
|
||||
)
|
||||
if not isinstance(descriptor, dict):
|
||||
raise HTTPException(status_code=404, detail="M4.8T review case not found")
|
||||
candidate = loaded["root"]
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="M4.8T/M4.8Q review case not found",
|
||||
)
|
||||
path = (candidate / str(descriptor["path"])).resolve()
|
||||
if (
|
||||
not path.is_relative_to(candidate)
|
||||
@@ -108,7 +168,10 @@ def build_m48t_risk_quality_lab_router(
|
||||
or descriptor.get("byte_length") != path.stat().st_size
|
||||
or descriptor.get("sha256") != _sha256(path)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="M4.8T review case not found")
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="M4.8T/M4.8Q review case not found",
|
||||
)
|
||||
return FileResponse(
|
||||
path,
|
||||
media_type="image/jpeg",
|
||||
@@ -122,8 +185,12 @@ def build_m48t_risk_quality_lab_router(
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(candidate: Path) -> dict[str, object]:
|
||||
loaded = _load_result(candidate)
|
||||
def _project_result(candidate: Path, variant: Variant) -> dict[str, object]:
|
||||
loaded = _load_result(candidate, variant)
|
||||
return _project_native(loaded) if variant == "native" else _project_legacy(loaded)
|
||||
|
||||
|
||||
def _project_legacy(loaded: dict[str, Any]) -> dict[str, object]:
|
||||
manifest = loaded["manifest"]
|
||||
report = loaded["report"]
|
||||
catalog = loaded["catalog"]
|
||||
@@ -143,7 +210,8 @@ def _project_result(candidate: Path) -> dict[str, object]:
|
||||
for item in catalog["cases"]
|
||||
]
|
||||
return {
|
||||
"schema_version": VIEW_SCHEMA,
|
||||
"schema_version": LEGACY_VIEW_SCHEMA,
|
||||
"variant": "legacy-coco-quality",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": manifest["created_at_utc"],
|
||||
"status": manifest["status"],
|
||||
@@ -165,26 +233,97 @@ def _project_result(candidate: Path) -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
def _load_result(candidate: Path) -> dict[str, Any]:
|
||||
def _project_native(loaded: dict[str, Any]) -> dict[str, object]:
|
||||
manifest = loaded["manifest"]
|
||||
report = loaded["report"]
|
||||
catalog = loaded["catalog"]
|
||||
result_id = str(manifest["result_id"])
|
||||
review_cases = [
|
||||
{
|
||||
"case_id": item["case_id"],
|
||||
"sequence": item["sequence"],
|
||||
"frame_id": item["frame_id"],
|
||||
"evidence_time_ns": item["evidence_time_ns"],
|
||||
"image_url": (
|
||||
f"/api/v1/laboratory/m48t/risk-quality/results/{result_id}"
|
||||
f"/review/{item['case_id']}.jpg"
|
||||
),
|
||||
"media_type": item["media_type"],
|
||||
"width": item["width"],
|
||||
"height": item["height"],
|
||||
"byte_length": item["byte_length"],
|
||||
"sha256": item["sha256"],
|
||||
"geometric_resampling": item["geometric_resampling"],
|
||||
"selection_buckets": copy.deepcopy(item["selection_buckets"]),
|
||||
"comparison": copy.deepcopy(item["comparison"]),
|
||||
"proposals": copy.deepcopy(item["proposals"]),
|
||||
}
|
||||
for item in catalog["cases"]
|
||||
]
|
||||
return {
|
||||
"schema_version": NATIVE_VIEW_SCHEMA,
|
||||
"variant": "native-risk-review",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": manifest["created_at_utc"],
|
||||
"status": manifest["status"],
|
||||
"source": copy.deepcopy(report["source"]),
|
||||
"configuration": copy.deepcopy(report["configuration"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"execution": copy.deepcopy(report["execution"]),
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"acceptance": copy.deepcopy(report["acceptance"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"review": {
|
||||
"source_raster": copy.deepcopy(catalog["source_raster"]),
|
||||
"overlay": copy.deepcopy(catalog["overlay"]),
|
||||
"cases": review_cases,
|
||||
},
|
||||
"ground_truth": False,
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _load_result(candidate: Path, variant: Variant) -> dict[str, Any]:
|
||||
pattern = NATIVE_RESULT_ID if variant == "native" else LEGACY_RESULT_ID
|
||||
definition = _NATIVE_DEFINITION if variant == "native" else _LEGACY_DEFINITION
|
||||
if (
|
||||
not candidate.is_dir()
|
||||
or candidate.is_symlink()
|
||||
or RESULT_ID.fullmatch(candidate.name) is None
|
||||
or pattern.fullmatch(candidate.name) is None
|
||||
):
|
||||
raise RuntimeError("M4.8T result candidate is invalid")
|
||||
raise RuntimeError("M4.8 result candidate is invalid")
|
||||
try:
|
||||
verify_laboratory_evidence_result(_DEFINITION, candidate)
|
||||
verify_laboratory_evidence_result(definition, candidate)
|
||||
manifest = _read_object(candidate / "manifest.json")
|
||||
report = _read_object(candidate / "report.json")
|
||||
catalog = _read_object(candidate / "catalog.json")
|
||||
except (LaboratoryEvidenceReportError, OSError, ValueError) as exc:
|
||||
raise RuntimeError("M4.8T result integrity failed") from exc
|
||||
raise RuntimeError("M4.8 result integrity failed") from exc
|
||||
if variant == "native":
|
||||
_validate_native(candidate, manifest, report, catalog)
|
||||
else:
|
||||
_validate_legacy(candidate, manifest, report, catalog)
|
||||
return {
|
||||
"root": candidate,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"catalog": catalog,
|
||||
}
|
||||
|
||||
|
||||
def _validate_legacy(
|
||||
candidate: Path,
|
||||
manifest: dict[str, Any],
|
||||
report: dict[str, Any],
|
||||
catalog: dict[str, Any],
|
||||
) -> None:
|
||||
identity = manifest.get("identity")
|
||||
if (
|
||||
manifest.get("schema_version") != LAB_SCHEMA
|
||||
manifest.get("schema_version") != LEGACY_LAB_SCHEMA
|
||||
or manifest.get("result_id") != candidate.name
|
||||
or manifest.get("status")
|
||||
!= "complete-quality-gate-failed-temporal-invariant-passed"
|
||||
or manifest.get("status") != "complete-quality-gate-failed-temporal-invariant-passed"
|
||||
or manifest.get("completed") is not True
|
||||
or manifest.get("bounded_question_accepted") is not False
|
||||
or manifest.get("ground_truth") is not False
|
||||
@@ -194,47 +333,143 @@ def _load_result(candidate: Path) -> dict[str, Any]:
|
||||
or not candidate.name.endswith(str(manifest.get("identity_sha256")))
|
||||
or identity.get("authority") != false_authority()
|
||||
or manifest.get("authority") != false_authority()
|
||||
or report.get("schema_version") != REPORT_SCHEMA
|
||||
or report.get("schema_version") != LEGACY_REPORT_SCHEMA
|
||||
or report.get("result_id") != candidate.name
|
||||
or report.get("authority") != false_authority()
|
||||
or report.get("decision", {}).get("quality_accepted") is not False
|
||||
or report.get("decision", {}).get("temporal_invariant_passed") is not True
|
||||
or catalog.get("schema_version") != CATALOG_SCHEMA
|
||||
or catalog.get("schema_version") != LEGACY_CATALOG_SCHEMA
|
||||
or catalog.get("result_id") != candidate.name
|
||||
or catalog.get("case_count") != 16
|
||||
or not isinstance(catalog.get("cases"), list)
|
||||
or len(catalog["cases"]) != 16
|
||||
or any(not _valid_case(item) for item in catalog["cases"])
|
||||
or any(not _valid_legacy_case(item) for item in catalog["cases"])
|
||||
):
|
||||
raise RuntimeError("M4.8T result contract changed")
|
||||
return {"root": candidate, "manifest": manifest, "report": report, "catalog": catalog}
|
||||
|
||||
|
||||
def _valid_case(value: object) -> bool:
|
||||
def _validate_native(
|
||||
candidate: Path,
|
||||
manifest: dict[str, Any],
|
||||
report: dict[str, Any],
|
||||
catalog: dict[str, Any],
|
||||
) -> None:
|
||||
identity = manifest.get("identity")
|
||||
cases = catalog.get("cases")
|
||||
if (
|
||||
manifest.get("schema_version") != NATIVE_LAB_SCHEMA
|
||||
or manifest.get("result_id") != candidate.name
|
||||
or manifest.get("status") != "complete-review-ready-quality-not-adjudicated"
|
||||
or manifest.get("completed") is not True
|
||||
or manifest.get("bounded_question_accepted") is not True
|
||||
or manifest.get("ground_truth") is not False
|
||||
or not isinstance(manifest.get("created_at_utc"), str)
|
||||
or not isinstance(identity, dict)
|
||||
or manifest.get("identity_sha256") != _canonical_sha256(identity)
|
||||
or not candidate.name.endswith(str(manifest.get("identity_sha256")))
|
||||
or identity.get("authority") != false_authority()
|
||||
or manifest.get("authority") != false_authority()
|
||||
or report.get("schema_version") != NATIVE_REPORT_SCHEMA
|
||||
or report.get("result_id") != candidate.name
|
||||
or report.get("authority") != false_authority()
|
||||
or report.get("decision", {}).get("review_ready") is not True
|
||||
or report.get("decision", {}).get("quality_evaluated") is not False
|
||||
or report.get("decision", {}).get("candidate_accepted") is not False
|
||||
or catalog.get("schema_version") != NATIVE_CATALOG_SCHEMA
|
||||
or catalog.get("result_id") != candidate.name
|
||||
or catalog.get("case_count") != 24
|
||||
or catalog.get("ground_truth") is not False
|
||||
or not isinstance(cases, list)
|
||||
or len(cases) != 24
|
||||
or any(not _valid_native_case(item) for item in cases)
|
||||
):
|
||||
raise RuntimeError("M4.8Q result contract changed")
|
||||
|
||||
|
||||
def _valid_legacy_case(value: object) -> bool:
|
||||
return (
|
||||
isinstance(value, dict)
|
||||
and isinstance(value.get("case_id"), str)
|
||||
and CASE_ID.fullmatch(value["case_id"]) is not None
|
||||
and LEGACY_CASE_ID.fullmatch(value["case_id"]) is not None
|
||||
and value.get("image_id") == int(value["case_id"])
|
||||
and value.get("path") == f"review/review-{value['case_id']}.jpg"
|
||||
and value.get("media_type") == "image/jpeg"
|
||||
and isinstance(value.get("byte_length"), int)
|
||||
and _valid_file_proof(value)
|
||||
)
|
||||
|
||||
|
||||
def _valid_native_case(value: object) -> bool:
|
||||
if not isinstance(value, dict) or not isinstance(value.get("case_id"), str):
|
||||
return False
|
||||
case_id = value["case_id"]
|
||||
proposals = value.get("proposals")
|
||||
return (
|
||||
NATIVE_CASE_ID.fullmatch(case_id) is not None
|
||||
and value.get("sequence") == int(case_id)
|
||||
and value.get("frame_id") == f"frame-{case_id}"
|
||||
and isinstance(value.get("evidence_time_ns"), int)
|
||||
and value.get("path") == f"cases/frame-{case_id}.jpg"
|
||||
and value.get("media_type") == "image/jpeg"
|
||||
and value.get("width") == 800
|
||||
and value.get("height") == 600
|
||||
and value.get("geometric_resampling") is False
|
||||
and isinstance(value.get("selection_buckets"), list)
|
||||
and isinstance(value.get("comparison"), dict)
|
||||
and isinstance(proposals, list)
|
||||
and len(proposals) > 0
|
||||
and all(_valid_native_proposal(item) for item in proposals)
|
||||
and _valid_file_proof(value)
|
||||
)
|
||||
|
||||
|
||||
def _valid_native_proposal(value: object) -> bool:
|
||||
if not isinstance(value, dict):
|
||||
return False
|
||||
box = value.get("box_xyxy")
|
||||
return (
|
||||
isinstance(value.get("proposal_id"), str)
|
||||
and isinstance(value.get("class_name"), str)
|
||||
and isinstance(value.get("risk_family"), str)
|
||||
and isinstance(value.get("score"), (int, float))
|
||||
and not isinstance(value.get("score"), bool)
|
||||
and 0.25 <= value["score"] <= 1
|
||||
and isinstance(box, list)
|
||||
and len(box) == 4
|
||||
and all(isinstance(item, (int, float)) and not isinstance(item, bool) for item in box)
|
||||
and 0 <= box[0] < box[2] <= 800
|
||||
and 0 <= box[1] < box[3] <= 600
|
||||
)
|
||||
|
||||
|
||||
def _valid_file_proof(value: dict[str, Any]) -> bool:
|
||||
return (
|
||||
isinstance(value.get("byte_length"), int)
|
||||
and value["byte_length"] > 0
|
||||
and isinstance(value.get("sha256"), str)
|
||||
and re.fullmatch(r"[a-f0-9]{64}", value["sha256"]) is not None
|
||||
)
|
||||
|
||||
|
||||
def _resolve_candidate(provider: RootProvider, result_id: str) -> Path:
|
||||
if RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="M4.8T result not found")
|
||||
def _resolve_candidate(
|
||||
legacy_provider: RootProvider,
|
||||
native_provider: RootProvider,
|
||||
result_id: str,
|
||||
) -> tuple[Path, Variant]:
|
||||
provider: RootProvider
|
||||
variant: Variant
|
||||
if NATIVE_RESULT_ID.fullmatch(result_id) is not None:
|
||||
provider, variant = native_provider, "native"
|
||||
elif LEGACY_RESULT_ID.fullmatch(result_id) is not None:
|
||||
provider, variant = legacy_provider, "legacy"
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="M4.8 result not found")
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="M4.8T result not found")
|
||||
raise HTTPException(status_code=404, detail="M4.8 result not found")
|
||||
candidate = (root / result_id).resolve()
|
||||
if candidate.parent != root or candidate.is_symlink() or not candidate.is_dir():
|
||||
raise HTTPException(status_code=404, detail="M4.8T result not found")
|
||||
return candidate
|
||||
raise HTTPException(status_code=404, detail="M4.8 result not found")
|
||||
return candidate, variant
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
@@ -251,29 +486,18 @@ def _configured_root(provider: RootProvider) -> Path | None:
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(root: Path) -> list[Path]:
|
||||
def _candidates(root: Path, pattern: re.Pattern[str]) -> list[Path]:
|
||||
return sorted(
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and RESULT_ID.fullmatch(item.name)
|
||||
if item.is_dir() and not item.is_symlink() and pattern.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
|
||||
def _empty_catalog(configured: bool) -> dict[str, object]:
|
||||
return {
|
||||
"schema_version": CATALOG_VIEW_SCHEMA,
|
||||
"configured": configured,
|
||||
"items": [],
|
||||
"candidate_total": 0,
|
||||
"invalid_total": 0,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _read_object(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text("utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
|
||||
@@ -152,6 +152,15 @@ def test_profile_is_strict_digest_bound_and_store_accepts_exact_evidence() -> No
|
||||
assert store.profile.local_surface_sha256 == (
|
||||
"f57eb2485b6cef47f2a97a2d9ff1aa9fd9265fe1eb69cd5852d12f39e13b8bc6"
|
||||
)
|
||||
step_candidates = store.point_step_candidates_for_frame(0)
|
||||
assert step_candidates is not None
|
||||
assert step_candidates.shape == (2389,)
|
||||
assert step_candidates.dtype == np.uint8
|
||||
assert step_candidates.flags.writeable is False
|
||||
with pytest.raises(ValueError):
|
||||
step_candidates[0] = 0
|
||||
with pytest.raises(GeometryProviderError, match="frame index"):
|
||||
store.point_step_candidates_for_frame(True)
|
||||
|
||||
|
||||
def test_provider_arbitrates_points_and_publishes_classless_geometry_only() -> None:
|
||||
|
||||
@@ -127,7 +127,7 @@ def test_product_registry_declares_every_advanced_evidence_source() -> None:
|
||||
repository_root / "config" / "laboratories"
|
||||
)
|
||||
|
||||
assert len(registry.definitions) == 38
|
||||
assert len(registry.definitions) == 39
|
||||
assert {item.work_id for item in registry.definitions} >= {
|
||||
"e31-source-binding",
|
||||
"e46j-raw-fisheye-realtime",
|
||||
@@ -141,15 +141,26 @@ def test_product_registry_declares_every_advanced_evidence_source() -> None:
|
||||
"m47-reference-graph-shadow",
|
||||
"m48-object-centric-quality",
|
||||
"m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification",
|
||||
"m48s-fixed-class-detector",
|
||||
"m48t-risk-quality-temporal",
|
||||
}
|
||||
m48 = next(
|
||||
item for item in registry.definitions
|
||||
if item.work_id == "m48-object-centric-quality"
|
||||
item for item in registry.definitions if item.work_id == "m48-object-centric-quality"
|
||||
)
|
||||
assert [variant.phase for variant in m48.evidence_variants] == ["review", "result"]
|
||||
assert [variant.result_id_prefix for variant in m48.evidence_variants] == [
|
||||
"m48-object-quality-pack",
|
||||
"m48-object-quality-result",
|
||||
]
|
||||
m48t = next(
|
||||
item for item in registry.definitions if item.work_id == "m48t-risk-quality-temporal"
|
||||
)
|
||||
assert [variant.phase for variant in m48t.evidence_variants] == [
|
||||
"legacy-quality",
|
||||
"result",
|
||||
]
|
||||
assert [variant.result_id_prefix for variant in m48t.evidence_variants] == [
|
||||
"m48t-risk-quality-temporal-lab",
|
||||
"m48q-native-risk-quality-lab",
|
||||
]
|
||||
|
||||
@@ -92,6 +92,7 @@ def test_repository_registry_classifies_every_evidence_definition() -> None:
|
||||
|
||||
assert {row.work_id for row in execution.definitions} == {
|
||||
"m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification",
|
||||
"m48-object-centric-quality",
|
||||
"m4-replay-threat",
|
||||
"e33-worker-shadow",
|
||||
@@ -108,6 +109,9 @@ def test_repository_registry_classifies_every_evidence_definition() -> None:
|
||||
assert by_work_id["m48-object-centric-quality"].evidence_contract == (
|
||||
"missioncore.m48-object-centric-quality-result/v1"
|
||||
)
|
||||
assert by_work_id["m48-static-occupancy-qualification"].evidence_contract == (
|
||||
"missioncore.m48-static-occupancy-qualification-result/v1"
|
||||
)
|
||||
assert by_work_id["e47-semantic-slam-shadow"].lifecycle == "experimental"
|
||||
assert by_work_id["e47-semantic-slam-shadow"].isolation == "bounded-adapter"
|
||||
assert by_work_id["m48s-fixed-class-detector"].lifecycle == "experimental"
|
||||
|
||||
@@ -80,7 +80,7 @@ def test_product_value_review_registry_covers_reviewed_laboratory_families() ->
|
||||
root / "config" / "laboratory-value-review.json"
|
||||
)
|
||||
|
||||
assert len(registry.entries) == 37
|
||||
assert len(registry.entries) == 38
|
||||
assert {entry.catalog_id for entry in registry.entries} >= {
|
||||
"e28-local-surface",
|
||||
"e46d-temporal-failure-audit",
|
||||
@@ -88,6 +88,7 @@ def test_product_value_review_registry_covers_reviewed_laboratory_families() ->
|
||||
"l31-pointpillars-ravnoves",
|
||||
"l34f-adjudicated-reference",
|
||||
"m4-replay-threat",
|
||||
"m48-static-occupancy-qualification",
|
||||
"m48s-fixed-class-detector",
|
||||
"m48t-risk-quality-temporal",
|
||||
}
|
||||
|
||||
@@ -217,6 +217,82 @@ def _fixture(
|
||||
lambda _: regression_result,
|
||||
)
|
||||
|
||||
static_occupancy_result_id = f"m48-static-occupancy-qualification-{'e' * 64}"
|
||||
static_occupancy_root = tmp_path / "static-occupancy" / static_occupancy_result_id
|
||||
static_occupancy_root.mkdir(parents=True)
|
||||
static_occupancy_result = SimpleNamespace(
|
||||
result_id=static_occupancy_result_id,
|
||||
result_root=static_occupancy_root,
|
||||
manifest={
|
||||
"created_at_utc": "2026-08-26T12:00:00Z",
|
||||
"accepted": False,
|
||||
},
|
||||
report={
|
||||
"source": {
|
||||
"m47_lab_result_id": f"m47-reference-graph-lab-{'c' * 64}",
|
||||
"small_static_result_id": regression_result_id,
|
||||
},
|
||||
"configuration": {
|
||||
"run_label": "M4.8R2",
|
||||
"pipeline_id": "m4-current-rolling-plus-step-static-occupancy/v1",
|
||||
"experiment_id": "m48-static-occupancy-qualification/v1",
|
||||
},
|
||||
"metrics": {
|
||||
"operator_static_anchor_count": 1,
|
||||
"baseline_qualified_count": 0,
|
||||
"candidate_qualified_count": 1,
|
||||
"unresolved_unknown_count": 0,
|
||||
"critical_near_anchor_count": 1,
|
||||
"critical_near_baseline_recall": 0.0,
|
||||
"critical_near_candidate_recall": 1.0,
|
||||
"approach_anchor_count": 0,
|
||||
"approach_baseline_recall": 0.0,
|
||||
"approach_candidate_recall": 0.0,
|
||||
"canonical_engineering_anchor_count": 4,
|
||||
"canonical_engineering_recall": 1.0,
|
||||
"false_free_count": 0,
|
||||
},
|
||||
"gates": {
|
||||
"critical_near_candidate_recall": True,
|
||||
"approach_candidate_recall": False,
|
||||
"canonical_engineering_recall": True,
|
||||
"zero_false_free": True,
|
||||
"independent_truth_available": False,
|
||||
},
|
||||
"decision": {
|
||||
"state": "partial-static-occupancy-qualification",
|
||||
"critical_near_candidate_ready_for_shadow": True,
|
||||
"production_accepted": False,
|
||||
"summary": "fixture",
|
||||
"next_action": "worker shadow",
|
||||
},
|
||||
},
|
||||
cases=(
|
||||
{
|
||||
"anchor_id": regression_anchor_id,
|
||||
"clip_id": "neutral-clip-01",
|
||||
"sequence": 1,
|
||||
"extent_xyxy": [0.2, 0.2, 0.3, 0.4],
|
||||
"distance_m": 3.0,
|
||||
"distance_band": "critical-near",
|
||||
"accepted_graph": {
|
||||
"matched": False,
|
||||
"component_count": 0,
|
||||
"components": [],
|
||||
"free_space_claimed": False,
|
||||
},
|
||||
"baseline_qualified": False,
|
||||
"candidate_qualified": True,
|
||||
"outcome": "candidate-qualified",
|
||||
},
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
api,
|
||||
"read_m48_static_occupancy_qualification",
|
||||
lambda _: static_occupancy_result,
|
||||
)
|
||||
|
||||
observations: dict[str, list[dict[str, Any]]] = {
|
||||
"reviews": [],
|
||||
"adjudications": [],
|
||||
@@ -444,6 +520,7 @@ def _fixture(
|
||||
truth_root_provider=lambda: tmp_path / "truth",
|
||||
result_root_provider=lambda: tmp_path / "results",
|
||||
small_static_result_root_provider=lambda: tmp_path / "small-static",
|
||||
static_occupancy_result_root_provider=lambda: tmp_path / "static-occupancy",
|
||||
camera_frame_provider=camera,
|
||||
camera_playback_provider=camera_playback, # type: ignore[arg-type]
|
||||
spatial_evidence_provider=spatial_frame if spatial else None,
|
||||
@@ -1128,3 +1205,31 @@ def test_m48_small_static_regression_is_separate_read_only_assisted_evidence(
|
||||
assert body["ground_truth"] is False
|
||||
assert body["camera_url"].endswith("/frames/1/camera")
|
||||
assert body["spatial_url"].endswith("/frames/1/spatial")
|
||||
|
||||
|
||||
def test_m48_static_occupancy_qualification_is_read_only_bounded_evidence(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
client, _, _ = _fixture(tmp_path, monkeypatch, spatial=True)
|
||||
result_id = f"m48-static-occupancy-qualification-{'e' * 64}"
|
||||
|
||||
summary = client.get(
|
||||
f"/api/v1/laboratory/m48/regressions/static-occupancy/{result_id}"
|
||||
)
|
||||
assert summary.status_code == 200
|
||||
assert summary.headers["cache-control"] == "no-store"
|
||||
assert summary.json()["run_label"] == "M4.8R2"
|
||||
assert summary.json()["accepted"] is False
|
||||
assert summary.json()["ground_truth"] is False
|
||||
assert summary.json()["independent_truth"] is False
|
||||
assert summary.json()["metrics"]["critical_near_candidate_recall"] == 1.0
|
||||
assert summary.json()["decision"]["production_accepted"] is False
|
||||
|
||||
catalog = client.get(
|
||||
f"/api/v1/laboratory/m48/regressions/static-occupancy/{result_id}/cases"
|
||||
)
|
||||
assert catalog.status_code == 200
|
||||
assert catalog.json()["case_count"] == 1
|
||||
assert catalog.json()["cases"][0]["outcome"] == "candidate-qualified"
|
||||
assert catalog.json()["cases"][0]["accepted_graph"]["free_space_claimed"] is False
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import k1link.laboratory.m48_static_occupancy_qualification as qualification
|
||||
from k1link.laboratory.evidence_registry import LaboratoryEvidenceRegistry
|
||||
from k1link.laboratory.evidence_report import verify_laboratory_evidence_result
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
AUTHORITY = {
|
||||
"mode": "replay-simulated",
|
||||
"physical_live": False,
|
||||
"commands_enabled": False,
|
||||
"actuation_allowed": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
def _sealed_result(tmp_path: Path) -> qualification.M48StaticOccupancyQualificationResult:
|
||||
identity = {
|
||||
"schema_version": qualification.M48_STATIC_OCCUPANCY_RESULT_SCHEMA,
|
||||
"human_lab_id": "M4.8R2",
|
||||
"run_label": "fixture",
|
||||
"run_created_at_utc": "2026-08-26T12:00:00Z",
|
||||
"authority": AUTHORITY,
|
||||
}
|
||||
result_id = qualification.M48_STATIC_OCCUPANCY_PREFIX + qualification._canonical_sha256(
|
||||
identity
|
||||
)
|
||||
report = {
|
||||
"schema_version": qualification.M48_STATIC_OCCUPANCY_REPORT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"metrics": {
|
||||
"operator_static_anchor_count": 1,
|
||||
"candidate_qualified_count": 1,
|
||||
},
|
||||
"decision": {"production_accepted": False},
|
||||
"authority": AUTHORITY,
|
||||
}
|
||||
cases = (
|
||||
{
|
||||
"schema_version": qualification.M48_STATIC_OCCUPANCY_CASE_SCHEMA,
|
||||
"anchor_id": "anchor-" + "a" * 24,
|
||||
"sequence": 10,
|
||||
"baseline_qualified": False,
|
||||
"candidate_qualified": True,
|
||||
"outcome": "candidate-qualified",
|
||||
},
|
||||
)
|
||||
canonical = (
|
||||
{
|
||||
"schema_version": qualification.M48_STATIC_OCCUPANCY_CANONICAL_SCHEMA,
|
||||
"anchor_id": "hemisphere-01",
|
||||
"sequence": 1880,
|
||||
"matched": True,
|
||||
},
|
||||
)
|
||||
destination = tmp_path / "results" / result_id
|
||||
qualification._publish_result(
|
||||
destination,
|
||||
identity,
|
||||
"2026-08-26T12:00:00Z",
|
||||
False,
|
||||
report,
|
||||
cases,
|
||||
canonical,
|
||||
)
|
||||
return qualification.read_m48_static_occupancy_qualification(destination)
|
||||
|
||||
|
||||
def test_seals_bounded_static_occupancy_evidence_without_production_authority(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
result = _sealed_result(tmp_path)
|
||||
|
||||
assert result.manifest["accepted"] is False
|
||||
assert result.manifest["ground_truth"] is False
|
||||
assert result.manifest["authority"] == AUTHORITY
|
||||
assert result.report["decision"]["production_accepted"] is False
|
||||
assert result.cases[0]["outcome"] == "candidate-qualified"
|
||||
|
||||
registry = LaboratoryEvidenceRegistry.from_directory(
|
||||
REPOSITORY_ROOT / "config/laboratories"
|
||||
)
|
||||
definition = next(
|
||||
row
|
||||
for row in registry.definitions
|
||||
if row.work_id == "m48-static-occupancy-qualification"
|
||||
)
|
||||
proof = verify_laboratory_evidence_result(definition, result.result_root)
|
||||
assert proof["result_id"] == result.result_id
|
||||
assert proof["artifact_count"] == 3
|
||||
|
||||
|
||||
def test_reader_rejects_changed_static_occupancy_case_ledger(tmp_path: Path) -> None:
|
||||
result = _sealed_result(tmp_path)
|
||||
cases_path = result.result_root / "cases.jsonl"
|
||||
cases_path.write_bytes(cases_path.read_bytes() + b"{}\n")
|
||||
|
||||
with pytest.raises(
|
||||
qualification.M48StaticOccupancyQualificationError,
|
||||
match="artifact proof",
|
||||
):
|
||||
qualification.read_m48_static_occupancy_qualification(result.result_root)
|
||||
@@ -0,0 +1,90 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
RUNNER_PATH = (
|
||||
REPOSITORY_ROOT / "experiments" / "perception" / "run_m48q_native_risk_case_mining_worker.py"
|
||||
)
|
||||
SPEC = importlib.util.spec_from_file_location("m48q_case_mining", RUNNER_PATH)
|
||||
assert SPEC is not None and SPEC.loader is not None
|
||||
MODULE = importlib.util.module_from_spec(SPEC)
|
||||
sys.modules[SPEC.name] = MODULE
|
||||
SPEC.loader.exec_module(MODULE)
|
||||
|
||||
|
||||
def candidate(sequence: int, *buckets: str):
|
||||
return MODULE.FrameCandidate(
|
||||
sequence=sequence,
|
||||
frame_id=f"frame-{sequence:06d}",
|
||||
evidence_time_ns=sequence * 1_000_000,
|
||||
proposals=(),
|
||||
native_count=0,
|
||||
legacy_count=0,
|
||||
matched_count=0,
|
||||
buckets=frozenset(buckets),
|
||||
)
|
||||
|
||||
|
||||
def test_selection_is_deterministic_balanced_and_separated() -> None:
|
||||
candidates = [
|
||||
candidate(sequence, "person" if sequence % 2 == 0 else "vehicle")
|
||||
for sequence in range(0, 400, 5)
|
||||
]
|
||||
quotas = {"person": 3, "vehicle": 3}
|
||||
|
||||
first = MODULE.select_cases(
|
||||
candidates,
|
||||
bucket_quotas=quotas,
|
||||
minimum_sequence_separation=10,
|
||||
)
|
||||
second = MODULE.select_cases(
|
||||
candidates,
|
||||
bucket_quotas=quotas,
|
||||
minimum_sequence_separation=10,
|
||||
)
|
||||
|
||||
assert [item.sequence for item in first] == [item.sequence for item in second]
|
||||
assert len(first) == 6
|
||||
assert all(
|
||||
abs(left.sequence - right.sequence) >= 10
|
||||
for index, left in enumerate(first)
|
||||
for right in first[index + 1 :]
|
||||
)
|
||||
|
||||
|
||||
def test_selection_refuses_missing_bucket_coverage() -> None:
|
||||
with pytest.raises(MODULE.M48QCaseMiningError, match="animal produced 0/1"):
|
||||
MODULE.select_cases(
|
||||
[candidate(0, "person")],
|
||||
bucket_quotas={"animal": 1},
|
||||
minimum_sequence_separation=1,
|
||||
)
|
||||
|
||||
|
||||
def test_profile_freezes_raw_raster_and_false_authority() -> None:
|
||||
import json
|
||||
|
||||
profile = json.loads(
|
||||
(
|
||||
REPOSITORY_ROOT / "config" / "perception" / "m48q-native-risk-case-mining-v1.json"
|
||||
).read_text("utf-8")
|
||||
)
|
||||
|
||||
assert profile["source"] == {
|
||||
"source_id": "RAVNOVES00",
|
||||
"frame_count": 4489,
|
||||
"raster_width": 800,
|
||||
"raster_height": 600,
|
||||
"video_sha256": "cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8",
|
||||
"geometric_resampling": False,
|
||||
"rectification": False,
|
||||
"warp": False,
|
||||
}
|
||||
assert sum(profile["selection"]["bucket_quotas"].values()) == 24
|
||||
assert profile["scope"]["quality_evaluated"] is False
|
||||
assert profile["authority"] == MODULE.FALSE_AUTHORITY
|
||||
@@ -6,6 +6,18 @@ from pathlib import Path
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
CATALOG_SCHEMA as NATIVE_CATALOG_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
LAB_SCHEMA as NATIVE_LAB_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
REPORT_SCHEMA as NATIVE_REPORT_SCHEMA,
|
||||
)
|
||||
from k1link.laboratory.m48q_native_risk_quality_lab import (
|
||||
RESULT_PREFIX as NATIVE_RESULT_PREFIX,
|
||||
)
|
||||
from k1link.laboratory.m48t_risk_quality_lab import (
|
||||
CATALOG_SCHEMA,
|
||||
LAB_SCHEMA,
|
||||
@@ -150,3 +162,129 @@ def test_m48t_lab_api_fails_closed_after_visual_tamper(tmp_path: Path) -> None:
|
||||
catalog = client.get("/api/v1/laboratory/m48t/risk-quality/results")
|
||||
assert catalog.json()["items"] == []
|
||||
assert catalog.json()["invalid_total"] == 1
|
||||
|
||||
|
||||
def _native_fixture(tmp_path: Path) -> tuple[TestClient, Path, str]:
|
||||
root = tmp_path / "native-results"
|
||||
root.mkdir()
|
||||
identity = {"schema_version": NATIVE_LAB_SCHEMA, "authority": false_authority()}
|
||||
identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest()
|
||||
result_id = NATIVE_RESULT_PREFIX + identity_sha256
|
||||
result_root = root / result_id
|
||||
cases_root = result_root / "cases"
|
||||
cases_root.mkdir(parents=True)
|
||||
cases = []
|
||||
image_paths = []
|
||||
for index in range(24):
|
||||
case_id = f"{index * 20 + 12:06d}"
|
||||
image_path = cases_root / f"frame-{case_id}.jpg"
|
||||
image_path.write_bytes(b"native-jpeg" + bytes([index]))
|
||||
image_paths.append(image_path)
|
||||
cases.append(
|
||||
{
|
||||
"case_id": case_id,
|
||||
"sequence": int(case_id),
|
||||
"frame_id": f"frame-{case_id}",
|
||||
"evidence_time_ns": 35_000_000_000 + index * 1_000_000,
|
||||
"path": f"cases/{image_path.name}",
|
||||
"media_type": "image/jpeg",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"byte_length": image_path.stat().st_size,
|
||||
"sha256": hashlib.sha256(image_path.read_bytes()).hexdigest(),
|
||||
"geometric_resampling": False,
|
||||
"selection_buckets": ["person"],
|
||||
"comparison": {
|
||||
"native_detection_count": 1,
|
||||
"legacy_704_detection_count": 1,
|
||||
"matched_detection_count_iou_at_least_0_5": 1,
|
||||
},
|
||||
"proposals": [
|
||||
{
|
||||
"proposal_id": f"proposal-{index}-0",
|
||||
"class_name": "person",
|
||||
"risk_family": "person",
|
||||
"score": 0.75,
|
||||
"box_xyxy": [10.0, 20.0, 100.0, 200.0],
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
catalog = {
|
||||
"schema_version": NATIVE_CATALOG_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"case_count": 24,
|
||||
"source_raster": {"width": 800, "height": 600},
|
||||
"overlay": {"client_rendered": True, "toggleable": True},
|
||||
"ground_truth": False,
|
||||
"cases": cases,
|
||||
}
|
||||
report = {
|
||||
"schema_version": NATIVE_REPORT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"source": {},
|
||||
"configuration": {},
|
||||
"method": {},
|
||||
"execution": {},
|
||||
"metrics": {},
|
||||
"acceptance": {},
|
||||
"decision": {
|
||||
"review_ready": True,
|
||||
"quality_evaluated": False,
|
||||
"candidate_accepted": False,
|
||||
},
|
||||
"limitations": [],
|
||||
"authority": false_authority(),
|
||||
}
|
||||
catalog_path = result_root / "catalog.json"
|
||||
report_path = result_root / "report.json"
|
||||
_write_json(catalog_path, catalog)
|
||||
_write_json(report_path, report)
|
||||
artifacts = [
|
||||
_descriptor(catalog_path, result_root, "visual-evidence-catalog", "application/json"),
|
||||
_descriptor(report_path, result_root, "laboratory-report", "application/json"),
|
||||
*[
|
||||
_descriptor(path, result_root, "visual-evidence-native-raw-fisheye-frame", "image/jpeg")
|
||||
for path in image_paths
|
||||
],
|
||||
]
|
||||
manifest = {
|
||||
"schema_version": NATIVE_LAB_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": "2026-08-26T09:45:15.574709Z",
|
||||
"status": "complete-review-ready-quality-not-adjudicated",
|
||||
"completed": True,
|
||||
"bounded_question_accepted": True,
|
||||
"ground_truth": False,
|
||||
"authority": false_authority(),
|
||||
"artifacts": artifacts,
|
||||
}
|
||||
_write_json(result_root / "manifest.json", manifest)
|
||||
app = FastAPI()
|
||||
app.include_router(build_m48t_risk_quality_lab_router(native_root_provider=lambda: root))
|
||||
return TestClient(app), result_root, result_id
|
||||
|
||||
|
||||
def test_m48q_lab_api_projects_native_raw_cases_without_quality_promotion(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
client, result_root, result_id = _native_fixture(tmp_path)
|
||||
|
||||
catalog = client.get("/api/v1/laboratory/m48t/risk-quality/results")
|
||||
assert catalog.status_code == 200
|
||||
assert catalog.json()["items"][0]["variant"] == "native-risk-review"
|
||||
result = client.get(f"/api/v1/laboratory/m48t/risk-quality/results/{result_id}")
|
||||
assert result.status_code == 200
|
||||
assert result.json()["ground_truth"] is False
|
||||
assert result.json()["decision"]["quality_evaluated"] is False
|
||||
assert len(result.json()["review"]["cases"]) == 24
|
||||
assert result.json()["review"]["cases"][0]["geometric_resampling"] is False
|
||||
|
||||
case_id = "000012"
|
||||
image = client.get(
|
||||
f"/api/v1/laboratory/m48t/risk-quality/results/{result_id}/review/{case_id}.jpg"
|
||||
)
|
||||
assert image.status_code == 200
|
||||
assert image.content == (result_root / f"cases/frame-{case_id}.jpg").read_bytes()
|
||||
|
||||
Reference in New Issue
Block a user