feat(lab): bind M4.8 regression to reference graph
This commit is contained in:
@@ -10,6 +10,7 @@ import type {
|
|||||||
|
|
||||||
const RESULT_ID = /^m48-small-static-passage-regression-[a-f0-9]{64}$/;
|
const RESULT_ID = /^m48-small-static-passage-regression-[a-f0-9]{64}$/;
|
||||||
const PACK_ID = /^m48-object-quality-pack-[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 ANCHOR_ID = /^anchor-[a-f0-9]{24}$/;
|
||||||
const SAFE_URL = /^\/api\/v1\/laboratory\/m48\/[A-Za-z0-9/_?&=.%:-]+$/;
|
const SAFE_URL = /^\/api\/v1\/laboratory\/m48\/[A-Za-z0-9/_?&=.%:-]+$/;
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ export interface M48SmallStaticRegressionMetrics {
|
|||||||
export interface M48SmallStaticRegressionResult {
|
export interface M48SmallStaticRegressionResult {
|
||||||
resultId: string;
|
resultId: string;
|
||||||
packId: string;
|
packId: string;
|
||||||
|
referenceGraphLabResultId: string;
|
||||||
createdAtUtc: string;
|
createdAtUtc: string;
|
||||||
runLabel: "M4.8R1";
|
runLabel: "M4.8R1";
|
||||||
pipelineId: "m48-class-free-object-quality/v1";
|
pipelineId: "m48-class-free-object-quality/v1";
|
||||||
@@ -53,6 +55,7 @@ export interface M48SmallStaticRegressionCaseSummary {
|
|||||||
anchorId: string;
|
anchorId: string;
|
||||||
clipId: string;
|
clipId: string;
|
||||||
sequence: number;
|
sequence: number;
|
||||||
|
anchorExtentXyxy: readonly [number, number, number, number];
|
||||||
requiresAvoidanceOrClearance: boolean;
|
requiresAvoidanceOrClearance: boolean;
|
||||||
workerCandidateCount: number;
|
workerCandidateCount: number;
|
||||||
bestIou: number;
|
bestIou: number;
|
||||||
@@ -198,6 +201,7 @@ function parseSummary(value: unknown): M48SmallStaticRegressionCaseSummary {
|
|||||||
anchorId,
|
anchorId,
|
||||||
clipId: textValue(row.clip_id, "M4.8R1.case.clip_id"),
|
clipId: textValue(row.clip_id, "M4.8R1.case.clip_id"),
|
||||||
sequence: integerValue(row.sequence, "M4.8R1.case.sequence"),
|
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"),
|
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"),
|
workerCandidateCount: integerValue(row.worker_candidate_count, "M4.8R1.case.worker_candidate_count"),
|
||||||
bestIou: numberValue(row.best_iou, "M4.8R1.case.best_iou"),
|
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");
|
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");
|
const packId = textValue(payload.pack_id, "M4.8R1.pack_id");
|
||||||
if (!PACK_ID.test(packId)) throw new M48SmallStaticRegressionContractError("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 metrics = objectValue(payload.metrics, "M4.8R1.metrics");
|
||||||
const gates = objectValue(payload.gates, "M4.8R1.gates");
|
const gates = objectValue(payload.gates, "M4.8R1.gates");
|
||||||
const decision = objectValue(payload.decision, "M4.8R1.decision");
|
const decision = objectValue(payload.decision, "M4.8R1.decision");
|
||||||
@@ -235,6 +248,7 @@ export async function fetchM48SmallStaticRegression(
|
|||||||
return {
|
return {
|
||||||
resultId,
|
resultId,
|
||||||
packId,
|
packId,
|
||||||
|
referenceGraphLabResultId,
|
||||||
createdAtUtc: textValue(payload.created_at_utc, "M4.8R1.created_at_utc"),
|
createdAtUtc: textValue(payload.created_at_utc, "M4.8R1.created_at_utc"),
|
||||||
runLabel: "M4.8R1",
|
runLabel: "M4.8R1",
|
||||||
pipelineId: "m48-class-free-object-quality/v1",
|
pipelineId: "m48-class-free-object-quality/v1",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ _SMALL_STATIC_RESULT_ID = re.compile(
|
|||||||
r"^m48-small-static-passage-regression-[a-f0-9]{64}$"
|
r"^m48-small-static-passage-regression-[a-f0-9]{64}$"
|
||||||
)
|
)
|
||||||
_SMALL_STATIC_ANCHOR_ID = re.compile(r"^anchor-[a-f0-9]{24}$")
|
_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}$")
|
_FAILURE_ID = re.compile(r"^m48-failure-[a-f0-9]{64}$")
|
||||||
_REVIEW_SESSION_ID = re.compile(r"^m48-review-session-[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}$")
|
_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")
|
metrics = _object(result.report.get("metrics"), "M4.8 regression metrics")
|
||||||
gates = _object(result.report.get("gates"), "M4.8 regression gates")
|
gates = _object(result.report.get("gates"), "M4.8 regression gates")
|
||||||
decision = _object(result.report.get("decision"), "M4.8 regression decision")
|
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 {
|
return {
|
||||||
"schema_version": _SMALL_STATIC_RESULT_VIEW_SCHEMA,
|
"schema_version": _SMALL_STATIC_RESULT_VIEW_SCHEMA,
|
||||||
"result_id": result.result_id,
|
"result_id": result.result_id,
|
||||||
@@ -1486,6 +1498,7 @@ def build_m48_object_quality_router(
|
|||||||
"run_label": result.manifest["identity"].get("run_label"),
|
"run_label": result.manifest["identity"].get("run_label"),
|
||||||
"pipeline_id": result.manifest["identity"].get("pipeline_id"),
|
"pipeline_id": result.manifest["identity"].get("pipeline_id"),
|
||||||
"experiment_id": result.manifest["identity"].get("experiment_id"),
|
"experiment_id": result.manifest["identity"].get("experiment_id"),
|
||||||
|
"reference_graph_lab_result_id": reference_graph_lab_result_id,
|
||||||
"accepted": result.manifest.get("accepted"),
|
"accepted": result.manifest.get("accepted"),
|
||||||
"metrics": copy.deepcopy(metrics),
|
"metrics": copy.deepcopy(metrics),
|
||||||
"gates": copy.deepcopy(gates),
|
"gates": copy.deepcopy(gates),
|
||||||
@@ -1513,6 +1526,7 @@ def build_m48_object_quality_router(
|
|||||||
"anchor_id": row["anchor_id"],
|
"anchor_id": row["anchor_id"],
|
||||||
"clip_id": row["clip_id"],
|
"clip_id": row["clip_id"],
|
||||||
"sequence": row["sequence"],
|
"sequence": row["sequence"],
|
||||||
|
"anchor_extent_xyxy": copy.deepcopy(row["anchor_extent_xyxy"]),
|
||||||
"requires_avoidance_or_clearance": row[
|
"requires_avoidance_or_clearance": row[
|
||||||
"requires_avoidance_or_clearance"
|
"requires_avoidance_or_clearance"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -92,7 +92,10 @@ def _fixture(
|
|||||||
"identity_sha256": "a" * 64,
|
"identity_sha256": "a" * 64,
|
||||||
"created_at_utc": "2026-08-23T20:00:00Z",
|
"created_at_utc": "2026-08-23T20:00:00Z",
|
||||||
"identity": {
|
"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},
|
"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"] == (
|
assert summary.json()["experiment_id"] == (
|
||||||
"m48-small-static-passage-regression/v1"
|
"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()["independent_truth"] is False
|
||||||
assert summary.json()["metrics"]["worker_missed_anchor_count"] == 1
|
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.status_code == 200
|
||||||
assert catalog.json()["case_count"] == 1
|
assert catalog.json()["case_count"] == 1
|
||||||
assert catalog.json()["cases"][0]["outcome"] == "missed-assisted-anchor"
|
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(
|
case = client.get(
|
||||||
f"/api/v1/laboratory/m48/regressions/small-static/{result_id}/cases/{anchor_id}"
|
f"/api/v1/laboratory/m48/regressions/small-static/{result_id}/cases/{anchor_id}"
|
||||||
|
|||||||
Reference in New Issue
Block a user