feat(lab): bind M4.8 regression to reference graph

This commit is contained in:
DCCONSTRUCTIONS
2026-08-25 00:07:58 +03:00
parent eb416ff80a
commit 669bb64423
3 changed files with 36 additions and 1 deletions
@@ -10,6 +10,7 @@ import type {
const RESULT_ID = /^m48-small-static-passage-regression-[a-f0-9]{64}$/;
const PACK_ID = /^m48-object-quality-pack-[a-f0-9]{64}$/;
const REFERENCE_GRAPH_LAB_RESULT_ID = /^m47-reference-graph-lab-[a-f0-9]{64}$/;
const ANCHOR_ID = /^anchor-[a-f0-9]{24}$/;
const SAFE_URL = /^\/api\/v1\/laboratory\/m48\/[A-Za-z0-9/_?&=.%:-]+$/;
@@ -28,6 +29,7 @@ export interface M48SmallStaticRegressionMetrics {
export interface M48SmallStaticRegressionResult {
resultId: string;
packId: string;
referenceGraphLabResultId: string;
createdAtUtc: string;
runLabel: "M4.8R1";
pipelineId: "m48-class-free-object-quality/v1";
@@ -53,6 +55,7 @@ export interface M48SmallStaticRegressionCaseSummary {
anchorId: string;
clipId: string;
sequence: number;
anchorExtentXyxy: readonly [number, number, number, number];
requiresAvoidanceOrClearance: boolean;
workerCandidateCount: number;
bestIou: number;
@@ -198,6 +201,7 @@ function parseSummary(value: unknown): M48SmallStaticRegressionCaseSummary {
anchorId,
clipId: textValue(row.clip_id, "M4.8R1.case.clip_id"),
sequence: integerValue(row.sequence, "M4.8R1.case.sequence"),
anchorExtentXyxy: extentValue(row.anchor_extent_xyxy, "M4.8R1.case.anchor_extent_xyxy"),
requiresAvoidanceOrClearance: booleanValue(row.requires_avoidance_or_clearance, "M4.8R1.case.requires_avoidance_or_clearance"),
workerCandidateCount: integerValue(row.worker_candidate_count, "M4.8R1.case.worker_candidate_count"),
bestIou: numberValue(row.best_iou, "M4.8R1.case.best_iou"),
@@ -223,6 +227,15 @@ export async function fetchM48SmallStaticRegression(
exact(payload.schema_version, "missioncore.m48-small-static-passage-regression-result-view/v1", "M4.8R1.schema_version");
const packId = textValue(payload.pack_id, "M4.8R1.pack_id");
if (!PACK_ID.test(packId)) throw new M48SmallStaticRegressionContractError("M4.8R1.pack_id: нарушена идентичность.");
const referenceGraphLabResultId = textValue(
payload.reference_graph_lab_result_id,
"M4.8R1.reference_graph_lab_result_id",
);
if (!REFERENCE_GRAPH_LAB_RESULT_ID.test(referenceGraphLabResultId)) {
throw new M48SmallStaticRegressionContractError(
"M4.8R1.reference_graph_lab_result_id: нарушена идентичность.",
);
}
const metrics = objectValue(payload.metrics, "M4.8R1.metrics");
const gates = objectValue(payload.gates, "M4.8R1.gates");
const decision = objectValue(payload.decision, "M4.8R1.decision");
@@ -235,6 +248,7 @@ export async function fetchM48SmallStaticRegression(
return {
resultId,
packId,
referenceGraphLabResultId,
createdAtUtc: textValue(payload.created_at_utc, "M4.8R1.created_at_utc"),
runLabel: "M4.8R1",
pipelineId: "m48-class-free-object-quality/v1",
+14
View File
@@ -57,6 +57,7 @@ _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}$")
_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}$")
_CORRECTION_SESSION_ID = re.compile(r"^m48-correction-session-[a-f0-9]{64}$")
@@ -1478,6 +1479,17 @@ def build_m48_object_quality_router(
metrics = _object(result.report.get("metrics"), "M4.8 regression metrics")
gates = _object(result.report.get("gates"), "M4.8 regression gates")
decision = _object(result.report.get("decision"), "M4.8 regression decision")
pack_identity = _object(pack.manifest.get("identity"), "M4.8 pack identity")
pack_source = _object(pack_identity.get("source"), "M4.8 pack source")
reference_graph_lab_result_id = pack_source.get("m47_lab_result_id")
if (
not isinstance(reference_graph_lab_result_id, str)
or _M47_LAB_RESULT_ID.fullmatch(reference_graph_lab_result_id) is None
):
raise HTTPException(
status_code=409,
detail="M4.8 regression lost its canonical reference graph binding",
)
return {
"schema_version": _SMALL_STATIC_RESULT_VIEW_SCHEMA,
"result_id": result.result_id,
@@ -1486,6 +1498,7 @@ def build_m48_object_quality_router(
"run_label": result.manifest["identity"].get("run_label"),
"pipeline_id": result.manifest["identity"].get("pipeline_id"),
"experiment_id": result.manifest["identity"].get("experiment_id"),
"reference_graph_lab_result_id": reference_graph_lab_result_id,
"accepted": result.manifest.get("accepted"),
"metrics": copy.deepcopy(metrics),
"gates": copy.deepcopy(gates),
@@ -1513,6 +1526,7 @@ def build_m48_object_quality_router(
"anchor_id": row["anchor_id"],
"clip_id": row["clip_id"],
"sequence": row["sequence"],
"anchor_extent_xyxy": copy.deepcopy(row["anchor_extent_xyxy"]),
"requires_avoidance_or_clearance": row[
"requires_avoidance_or_clearance"
],
+8 -1
View File
@@ -92,7 +92,10 @@ def _fixture(
"identity_sha256": "a" * 64,
"created_at_utc": "2026-08-23T20:00:00Z",
"identity": {
"source": {"source_session_id": "recorded-session"},
"source": {
"source_session_id": "recorded-session",
"m47_lab_result_id": f"m47-reference-graph-lab-{'c' * 64}",
},
"freeze": {"prediction_rows_sha256": "8" * 64},
},
},
@@ -1101,6 +1104,9 @@ def test_m48_small_static_regression_is_separate_read_only_assisted_evidence(
assert summary.json()["experiment_id"] == (
"m48-small-static-passage-regression/v1"
)
assert summary.json()["reference_graph_lab_result_id"] == (
f"m47-reference-graph-lab-{'c' * 64}"
)
assert summary.json()["independent_truth"] is False
assert summary.json()["metrics"]["worker_missed_anchor_count"] == 1
@@ -1110,6 +1116,7 @@ def test_m48_small_static_regression_is_separate_read_only_assisted_evidence(
assert catalog.status_code == 200
assert catalog.json()["case_count"] == 1
assert catalog.json()["cases"][0]["outcome"] == "missed-assisted-anchor"
assert catalog.json()["cases"][0]["anchor_extent_xyxy"] == [0.2, 0.2, 0.3, 0.4]
case = client.get(
f"/api/v1/laboratory/m48/regressions/small-static/{result_id}/cases/{anchor_id}"