feat(control-station): review native raw fisheye risk cases
This commit is contained in:
@@ -136,7 +136,7 @@ const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
|
||||
"m48-object-centric-quality": "m48-object-quality-(?:pack|result)",
|
||||
"m48-small-static-passage-regression": "m48-small-static-passage-regression",
|
||||
"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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user