feat(api): publish immutable perception lab evidence
This commit is contained in:
@@ -53,11 +53,222 @@ def test_advanced_index_is_empty_when_not_configured() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_advanced_index_publishes_l31_ravnoves_identity(
|
||||
def test_advanced_index_publishes_l34b_shadow_identity(tmp_path: Path) -> None:
|
||||
root = tmp_path / "l34b"
|
||||
digest = "d" * 64
|
||||
result_id = f"l34b-nested-box-consolidation-shadow-{digest}"
|
||||
candidate = root / result_id
|
||||
candidate.mkdir(parents=True)
|
||||
(candidate / "manifest.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": (
|
||||
"missioncore.l34b-nested-box-consolidation-shadow/v1"
|
||||
),
|
||||
"result_id": result_id,
|
||||
"identity_sha256": digest,
|
||||
"identity": {
|
||||
"authority": {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
},
|
||||
"created_at_utc": "2026-08-03T10:00:00Z",
|
||||
"ground_truth": False,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
router = build_advanced_laboratory_router(
|
||||
l34b_root_provider=lambda: root,
|
||||
)
|
||||
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
|
||||
|
||||
assert route()["items"] == [ # type: ignore[index,operator]
|
||||
{
|
||||
"work_id": "l34b-nested-box-consolidation-shadow",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": "2026-08-03T10:00:00Z",
|
||||
"access": "read-only",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_advanced_index_publishes_l34c_shadow_identity(tmp_path: Path) -> None:
|
||||
root = tmp_path / "l34c"
|
||||
digest = "c" * 64
|
||||
result_id = f"l34c-tile-seam-stitch-shadow-{digest}"
|
||||
candidate = root / result_id
|
||||
candidate.mkdir(parents=True)
|
||||
(candidate / "manifest.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": "missioncore.l34c-tile-seam-stitch-shadow/v1",
|
||||
"result_id": result_id,
|
||||
"identity_sha256": digest,
|
||||
"identity": {
|
||||
"authority": {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
},
|
||||
"created_at_utc": "2026-08-03T11:00:00Z",
|
||||
"ground_truth": False,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
router = build_advanced_laboratory_router(
|
||||
l34c_root_provider=lambda: root,
|
||||
)
|
||||
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
|
||||
|
||||
assert route()["items"] == [ # type: ignore[index,operator]
|
||||
{
|
||||
"work_id": "l34c-tile-seam-stitch-shadow",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": "2026-08-03T11:00:00Z",
|
||||
"access": "read-only",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_advanced_index_publishes_l34d_candidate_identity(tmp_path: Path) -> None:
|
||||
root = tmp_path / "l34d"
|
||||
digest = "e" * 64
|
||||
result_id = f"l34d-cumulative-postprocessing-candidate-{digest}"
|
||||
candidate = root / result_id
|
||||
candidate.mkdir(parents=True)
|
||||
(candidate / "manifest.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": (
|
||||
"missioncore.l34d-cumulative-postprocessing-candidate/v1"
|
||||
),
|
||||
"result_id": result_id,
|
||||
"identity_sha256": digest,
|
||||
"identity": {
|
||||
"authority": {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
},
|
||||
"created_at_utc": "2026-08-03T12:00:00Z",
|
||||
"ground_truth": False,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
router = build_advanced_laboratory_router(
|
||||
l34d_root_provider=lambda: root,
|
||||
)
|
||||
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
|
||||
|
||||
assert route()["items"] == [ # type: ignore[index,operator]
|
||||
{
|
||||
"work_id": "l34d-cumulative-postprocessing-candidate",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": "2026-08-03T12:00:00Z",
|
||||
"access": "read-only",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_advanced_index_publishes_l34e_diagnostic_identity(tmp_path: Path) -> None:
|
||||
root = tmp_path / "l34e"
|
||||
digest = "f" * 64
|
||||
result_id = f"l34e-self-review-diagnostic-{digest}"
|
||||
candidate = root / result_id
|
||||
candidate.mkdir(parents=True)
|
||||
(candidate / "manifest.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": "missioncore.l34e-self-review-diagnostic/v1",
|
||||
"result_id": result_id,
|
||||
"identity_sha256": digest,
|
||||
"identity": {
|
||||
"authority": {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
},
|
||||
"created_at_utc": "2026-08-03T13:00:00Z",
|
||||
"ground_truth": False,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
router = build_advanced_laboratory_router(
|
||||
l34e_root_provider=lambda: root,
|
||||
)
|
||||
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
|
||||
|
||||
assert route()["items"] == [ # type: ignore[index,operator]
|
||||
{
|
||||
"work_id": "l34e-self-review-diagnostic",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": "2026-08-03T13:00:00Z",
|
||||
"access": "read-only",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_advanced_index_publishes_l34f_adjudicated_reference_identity(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
root = tmp_path / "l34f"
|
||||
digest = "a" * 64
|
||||
result_id = f"l34f-adjudicated-reference-{digest}"
|
||||
candidate = root / result_id
|
||||
candidate.mkdir(parents=True)
|
||||
(candidate / "manifest.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": "missioncore.l34f-adjudicated-reference/v1",
|
||||
"result_id": result_id,
|
||||
"identity_sha256": digest,
|
||||
"identity": {
|
||||
"authority": {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
},
|
||||
"created_at_utc": "2026-08-03T14:00:00Z",
|
||||
"ground_truth": False,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
router = build_advanced_laboratory_router(
|
||||
l34f_root_provider=lambda: root,
|
||||
)
|
||||
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
|
||||
|
||||
assert route()["items"] == [ # type: ignore[index,operator]
|
||||
{
|
||||
"work_id": "l34f-adjudicated-reference",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": "2026-08-03T14:00:00Z",
|
||||
"access": "read-only",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_advanced_index_publishes_l32_camera_review_identity(
|
||||
tmp_path: Path,
|
||||
monkeypatch: MonkeyPatch,
|
||||
) -> None:
|
||||
result_id = f"l31-pointpillars-ravnoves-{'b' * 64}"
|
||||
result_id = f"l32-pointpillars-camera-review-{'b' * 64}"
|
||||
|
||||
def fake_latest(provider: object) -> dict[str, str]:
|
||||
assert callable(provider)
|
||||
@@ -67,15 +278,15 @@ def test_advanced_index_publishes_l31_ravnoves_identity(
|
||||
"created_at_utc": "2026-07-31T10:56:45.861Z",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(advanced_api, "latest_l31_identity", fake_latest)
|
||||
monkeypatch.setattr(advanced_api, "latest_l32_identity", fake_latest)
|
||||
router = build_advanced_laboratory_router(
|
||||
l31_ravnoves_root_provider=lambda: tmp_path,
|
||||
l32_camera_review_root_provider=lambda: tmp_path,
|
||||
)
|
||||
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
|
||||
|
||||
assert route()["items"] == [ # type: ignore[index,operator]
|
||||
{
|
||||
"work_id": "l31-pointpillars-ravnoves",
|
||||
"work_id": "l32-pointpillars-camera-review",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": "2026-07-31T10:56:45.861Z",
|
||||
"access": "read-only",
|
||||
@@ -83,6 +294,36 @@ def test_advanced_index_publishes_l31_ravnoves_identity(
|
||||
]
|
||||
|
||||
|
||||
def test_advanced_index_publishes_l33_camera_first_identity(
|
||||
tmp_path: Path,
|
||||
monkeypatch: MonkeyPatch,
|
||||
) -> None:
|
||||
result_id = f"l33-camera-first-detector-review-{'c' * 64}"
|
||||
|
||||
def fake_latest(provider: object) -> dict[str, str]:
|
||||
assert callable(provider)
|
||||
assert provider() == tmp_path # type: ignore[operator]
|
||||
return {
|
||||
"result_id": result_id,
|
||||
"created_at_utc": "2026-07-31T13:42:00.000Z",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(advanced_api, "latest_l33_identity", fake_latest)
|
||||
router = build_advanced_laboratory_router(
|
||||
l33_camera_first_review_root_provider=lambda: tmp_path,
|
||||
)
|
||||
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
|
||||
|
||||
assert route()["items"] == [ # type: ignore[index,operator]
|
||||
{
|
||||
"work_id": "l33-camera-first-detector-review",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": "2026-07-31T13:42:00.000Z",
|
||||
"access": "read-only",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_advanced_index_reads_only_bounded_identity_documents(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import struct
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
import k1link.compute.e46_lab_review_submission as submission
|
||||
import k1link.compute.e48_detector_truth_seal as e48
|
||||
import k1link.web.e46_blind_review_api as api
|
||||
from k1link.web.l34_annotation_api import (
|
||||
L34AnnotationCreateRequest,
|
||||
L34AnnotationFrameRequest,
|
||||
L34AnnotationObjectRequest,
|
||||
L34AnnotationSaveRequest,
|
||||
)
|
||||
|
||||
|
||||
def _endpoint(router: APIRouter, path: str, method: str = "GET") -> object:
|
||||
for route in router.routes:
|
||||
if (
|
||||
isinstance(route, APIRoute)
|
||||
and route.path == path
|
||||
and route.methods is not None
|
||||
and method in route.methods
|
||||
):
|
||||
return route.endpoint
|
||||
raise AssertionError(f"{method} {path} route is missing")
|
||||
|
||||
|
||||
def _png(width: int = 8, height: int = 6) -> bytes:
|
||||
signature = b"\x89PNG\r\n\x1a\n"
|
||||
|
||||
def chunk(kind: bytes, payload: bytes) -> bytes:
|
||||
return (
|
||||
struct.pack(">I", len(payload))
|
||||
+ kind
|
||||
+ payload
|
||||
+ struct.pack(">I", zlib.crc32(kind + payload) & 0xFFFFFFFF)
|
||||
)
|
||||
|
||||
pixels = b"".join(b"\x00" + b"\x20\x40\x60" * width for _ in range(height))
|
||||
return (
|
||||
signature
|
||||
+ chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0))
|
||||
+ chunk(b"IDAT", zlib.compress(pixels))
|
||||
+ chunk(b"IEND", b"")
|
||||
)
|
||||
|
||||
|
||||
def _fixture(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> tuple[APIRouter, str]:
|
||||
pack_id = f"evaluation-pack-{'a' * 64}"
|
||||
pack_root = tmp_path / "packs"
|
||||
image = pack_root / pack_id / "images" / "valid-fov-fill" / "frame.png"
|
||||
image.parent.mkdir(parents=True)
|
||||
image.write_bytes(_png())
|
||||
image_sha = hashlib.sha256(image.read_bytes()).hexdigest()
|
||||
truth_id = f"e46-detector-truth-island-{'b' * 64}"
|
||||
truth_root = tmp_path / "truth" / truth_id
|
||||
truth_root.mkdir(parents=True)
|
||||
references = [
|
||||
{
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": sequence,
|
||||
"frame_index": sequence * 10,
|
||||
"session_seconds": float(sequence),
|
||||
"role": "anchor" if sequence <= 16 else "temporal",
|
||||
"group_id": (
|
||||
f"anchor-{sequence:02d}"
|
||||
if sequence <= 16
|
||||
else f"clip-{((sequence - 17) // 4) + 1}"
|
||||
),
|
||||
"source_path": "images/valid-fov-fill/frame.png",
|
||||
"byte_length": image.stat().st_size,
|
||||
"sha256": image_sha,
|
||||
}
|
||||
for sequence in range(1, 33)
|
||||
]
|
||||
(truth_root / "image-references.jsonl").write_text(
|
||||
"".join(json.dumps(row) + "\n" for row in references),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(truth_root / "blind-contract.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"annotation": {
|
||||
"classes": [
|
||||
"person",
|
||||
"bicycle",
|
||||
"motorcycle",
|
||||
"car",
|
||||
"heavy_vehicle",
|
||||
"static_obstacle",
|
||||
"animal",
|
||||
]
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
truth = SimpleNamespace(
|
||||
result_id=truth_id,
|
||||
result_root=truth_root,
|
||||
manifest={
|
||||
"created_at_utc": "2026-08-04T08:00:00Z",
|
||||
"identity": {
|
||||
"source": {
|
||||
"evaluation_pack_id": pack_id,
|
||||
"session_id": "ravnoves00",
|
||||
"source_id": "sensor.camera.right",
|
||||
}
|
||||
},
|
||||
},
|
||||
report={
|
||||
"status": "prepared-awaiting-independent-human-review",
|
||||
"blindness": {"truth_labels_available": False},
|
||||
"selection": {
|
||||
"anchor_count": 16,
|
||||
"temporal_frame_count": 16,
|
||||
"temporal_group_count": 4,
|
||||
},
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(api, "read_e46_detector_truth_island", lambda _: truth)
|
||||
monkeypatch.setattr(submission, "read_e46_detector_truth_island", lambda _: truth)
|
||||
monkeypatch.setattr(e48, "read_e46_detector_truth_island", lambda _: truth)
|
||||
router = api.build_e46_blind_review_router(
|
||||
truth_root_provider=lambda: truth_root.parent,
|
||||
evaluation_pack_root_provider=lambda: pack_root,
|
||||
annotation_root_provider=lambda: tmp_path / "annotations",
|
||||
submission_root_provider=lambda: tmp_path / "submissions",
|
||||
)
|
||||
return router, truth_id
|
||||
|
||||
|
||||
def _complete_frames() -> list[L34AnnotationFrameRequest]:
|
||||
return [
|
||||
L34AnnotationFrameRequest(
|
||||
truth_island_sequence=sequence,
|
||||
reviewed=True,
|
||||
hard_negative=True,
|
||||
objects=[],
|
||||
)
|
||||
for sequence in range(1, 33)
|
||||
]
|
||||
|
||||
|
||||
def test_e46_source_is_candidate_free_and_limits_reviewer_slots(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
router, truth_id = _fixture(tmp_path, monkeypatch)
|
||||
source = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/e46/results/{result_id}/annotation-source",
|
||||
)(result_id=truth_id) # type: ignore[operator]
|
||||
|
||||
assert source["frame_count"] == 32
|
||||
assert source["contract"]["unmapped_class"] is None
|
||||
assert source["candidate_identity_included"] is False
|
||||
assert source["candidate_predictions_included"] is False
|
||||
assert source["prelabels_included"] is False
|
||||
assert all("candidate_id" not in frame for frame in source["frames"])
|
||||
|
||||
create = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/e46/results/{result_id}/annotation-sessions",
|
||||
"POST",
|
||||
)
|
||||
first = create( # type: ignore[operator]
|
||||
result_id=truth_id,
|
||||
request=L34AnnotationCreateRequest(idempotency_key="reviewer-slot-a"),
|
||||
)
|
||||
second = create( # type: ignore[operator]
|
||||
result_id=truth_id,
|
||||
request=L34AnnotationCreateRequest(idempotency_key="reviewer-slot-b"),
|
||||
)
|
||||
assert first["reviewer_slot"] == 1
|
||||
assert second["reviewer_slot"] == 2
|
||||
get_session = _endpoint(
|
||||
router,
|
||||
(
|
||||
"/api/v1/laboratory/e46/results/{result_id}"
|
||||
"/annotation-sessions/{session_id}"
|
||||
),
|
||||
)
|
||||
with pytest.raises(HTTPException) as private:
|
||||
get_session( # type: ignore[operator]
|
||||
result_id=truth_id,
|
||||
session_id=first["session_id"],
|
||||
x_e46_review_capability=second["review_capability"],
|
||||
)
|
||||
assert private.value.status_code == 403
|
||||
with pytest.raises(HTTPException) as caught:
|
||||
create( # type: ignore[operator]
|
||||
result_id=truth_id,
|
||||
request=L34AnnotationCreateRequest(idempotency_key="reviewer-slot-c"),
|
||||
)
|
||||
assert caught.value.status_code == 409
|
||||
|
||||
|
||||
def test_e46_complete_session_freezes_as_valid_e48_input(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
router, truth_id = _fixture(tmp_path, monkeypatch)
|
||||
create = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/e46/results/{result_id}/annotation-sessions",
|
||||
"POST",
|
||||
)
|
||||
session = create( # type: ignore[operator]
|
||||
result_id=truth_id,
|
||||
request=L34AnnotationCreateRequest(idempotency_key="reviewer-slot-a"),
|
||||
)
|
||||
save = _endpoint(
|
||||
router,
|
||||
(
|
||||
"/api/v1/laboratory/e46/results/{result_id}"
|
||||
"/annotation-sessions/{session_id}"
|
||||
),
|
||||
"PUT",
|
||||
)
|
||||
saved = save( # type: ignore[operator]
|
||||
result_id=truth_id,
|
||||
session_id=session["session_id"],
|
||||
x_e46_review_capability=session["review_capability"],
|
||||
request=L34AnnotationSaveRequest(
|
||||
expected_revision=0,
|
||||
idempotency_key="save-complete-review-a",
|
||||
title="E46 review A",
|
||||
assistance_mode="prediction-free-manual",
|
||||
frames=_complete_frames(),
|
||||
),
|
||||
)
|
||||
assert saved["progress"]["complete"] is True
|
||||
|
||||
freeze = _endpoint(
|
||||
router,
|
||||
(
|
||||
"/api/v1/laboratory/e46/results/{result_id}"
|
||||
"/annotation-sessions/{session_id}/freeze"
|
||||
),
|
||||
"POST",
|
||||
)
|
||||
frozen = freeze( # type: ignore[operator]
|
||||
result_id=truth_id,
|
||||
session_id=session["session_id"],
|
||||
x_e46_review_capability=session["review_capability"],
|
||||
request=api.E46ReviewFreezeRequest(
|
||||
expected_revision=1,
|
||||
reviewer_id="reviewer-a",
|
||||
independent_attestation=True,
|
||||
candidate_identity_not_seen=True,
|
||||
model_material_not_seen=True,
|
||||
),
|
||||
)
|
||||
assert frozen["state"] == "completed-e48-review-input-not-truth"
|
||||
assert frozen["frame_count"] == 32
|
||||
assert frozen["blindness"]["candidate_identity_seen"] is False
|
||||
assert frozen["ground_truth"] is False
|
||||
|
||||
with pytest.raises(HTTPException) as locked:
|
||||
save( # type: ignore[operator]
|
||||
result_id=truth_id,
|
||||
session_id=session["session_id"],
|
||||
x_e46_review_capability=session["review_capability"],
|
||||
request=L34AnnotationSaveRequest(
|
||||
expected_revision=1,
|
||||
idempotency_key="cannot-edit-frozen-review-a",
|
||||
title="E46 review A changed",
|
||||
assistance_mode="prediction-free-manual",
|
||||
frames=_complete_frames(),
|
||||
),
|
||||
)
|
||||
assert locked.value.status_code == 409
|
||||
|
||||
review_path = (
|
||||
tmp_path
|
||||
/ "submissions"
|
||||
/ frozen["result_id"]
|
||||
/ submission.E46_LAB_REVIEW_DOCUMENT
|
||||
)
|
||||
validated = e48.validate_e48_detector_review_submission(
|
||||
truth_island_root=tmp_path / "truth" / truth_id,
|
||||
review_path=review_path,
|
||||
)
|
||||
assert validated["reviewer_id"] == "reviewer-a"
|
||||
assert len(validated["images"]) == 32
|
||||
|
||||
|
||||
def test_e46_rejects_unmapped_object(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
router, truth_id = _fixture(tmp_path, monkeypatch)
|
||||
create = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/e46/results/{result_id}/annotation-sessions",
|
||||
"POST",
|
||||
)
|
||||
session = create( # type: ignore[operator]
|
||||
result_id=truth_id,
|
||||
request=L34AnnotationCreateRequest(idempotency_key="reviewer-slot-a"),
|
||||
)
|
||||
save = _endpoint(
|
||||
router,
|
||||
(
|
||||
"/api/v1/laboratory/e46/results/{result_id}"
|
||||
"/annotation-sessions/{session_id}"
|
||||
),
|
||||
"PUT",
|
||||
)
|
||||
frame = L34AnnotationFrameRequest(
|
||||
truth_island_sequence=1,
|
||||
reviewed=True,
|
||||
hard_negative=False,
|
||||
objects=[
|
||||
L34AnnotationObjectRequest(
|
||||
object_id="object-1",
|
||||
category="unmapped",
|
||||
proposed_label="stroller",
|
||||
origin="manual",
|
||||
box_xyxy=[1.0, 1.0, 5.0, 5.0],
|
||||
occluded=False,
|
||||
truncated=False,
|
||||
)
|
||||
],
|
||||
)
|
||||
with pytest.raises(HTTPException) as caught:
|
||||
save( # type: ignore[operator]
|
||||
result_id=truth_id,
|
||||
session_id=session["session_id"],
|
||||
x_e46_review_capability=session["review_capability"],
|
||||
request=L34AnnotationSaveRequest(
|
||||
expected_revision=0,
|
||||
idempotency_key="reject-unmapped",
|
||||
title="E46 review A",
|
||||
assistance_mode="prediction-free-manual",
|
||||
frames=[frame],
|
||||
),
|
||||
)
|
||||
assert caught.value.status_code == 422
|
||||
@@ -0,0 +1,126 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.web.e46c_full_replay_world_tracks_api import _cached_video_overlay
|
||||
|
||||
|
||||
def _canonical(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode()
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def _source(tmp_path: Path) -> tuple[Path, str, str]:
|
||||
root = tmp_path / f"e10-integrated-perception-{'1' * 64}"
|
||||
root.mkdir()
|
||||
start, end = 35.421857292, 484.044857292
|
||||
step = (end - start) / 4488
|
||||
fusion = root / "fusion-frames.jsonl"
|
||||
with fusion.open("wb") as handle:
|
||||
for frame_index in range(4489):
|
||||
objects = []
|
||||
if frame_index == 20:
|
||||
objects.append(
|
||||
{
|
||||
"bbox_xyxy": [100.0, 120.0, 240.0, 360.0],
|
||||
"label": "person",
|
||||
"score": 0.91,
|
||||
"source_track_id": 83,
|
||||
"track_id": 240001,
|
||||
"motion_state": "dynamic",
|
||||
"motion_confidence": 0.88,
|
||||
"track_hits": 12,
|
||||
"track_age_s": 1.2,
|
||||
"camera_evidence_current": True,
|
||||
"occupancy_evidence_current": False,
|
||||
}
|
||||
)
|
||||
row = {
|
||||
"source_frame_index": frame_index,
|
||||
"session_seconds": end if frame_index == 4488 else start + frame_index * step,
|
||||
"fusion_state": "fused",
|
||||
"objects": objects,
|
||||
}
|
||||
handle.write(_canonical(row) + b"\n")
|
||||
fusion_sha256 = _sha256(fusion)
|
||||
result = {
|
||||
"schema_version": "missioncore.e10-integrated-perception-result/v1",
|
||||
"acceptance_state": "accepted",
|
||||
"identity": {
|
||||
"input_sha256": "4" * 64,
|
||||
"source_id": "sensor.camera.right",
|
||||
"selection": {
|
||||
"frame_count": 4489,
|
||||
"timeline_start_seconds": start,
|
||||
"timeline_end_seconds": end,
|
||||
},
|
||||
"configuration": {
|
||||
"source_session_id": "20260720T065719Z_viewer_live",
|
||||
"benchmark": {
|
||||
"events": [
|
||||
{
|
||||
"id": "dynamic-person-window",
|
||||
"kind": "target-motion",
|
||||
"class_group": "person",
|
||||
"window_seconds": [54.0, 62.0],
|
||||
"target_source_track_ids": [83],
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
"artifacts": [
|
||||
{
|
||||
"path": fusion.name,
|
||||
"byte_length": fusion.stat().st_size,
|
||||
"sha256": fusion_sha256,
|
||||
}
|
||||
],
|
||||
}
|
||||
result_path = root / "result.json"
|
||||
result_path.write_bytes(_canonical(result) + b"\n")
|
||||
return root, _sha256(result_path), fusion_sha256
|
||||
|
||||
|
||||
def test_e46c_video_overlay_projects_all_frames_without_paths(tmp_path: Path) -> None:
|
||||
root, result_sha256, fusion_sha256 = _source(tmp_path)
|
||||
result_id = f"e46c-full-replay-world-tracks-{'a' * 64}"
|
||||
|
||||
overlay = _cached_video_overlay(
|
||||
result_id,
|
||||
str(root),
|
||||
result_sha256,
|
||||
fusion_sha256,
|
||||
)
|
||||
|
||||
assert overlay["result_id"] == result_id
|
||||
assert overlay["frame_count"] == 4489
|
||||
frames = overlay["frames"]
|
||||
assert isinstance(frames, list)
|
||||
assert frames[20]["objects"][0]["route_track_id"] == 83
|
||||
assert overlay["review_windows"][0]["target_source_track_ids"] == [83]
|
||||
assert "/" not in json.dumps(overlay["recorded_source"])
|
||||
|
||||
|
||||
def test_e46c_video_overlay_rejects_a_replaced_fusion_artifact(tmp_path: Path) -> None:
|
||||
root, result_sha256, fusion_sha256 = _source(tmp_path)
|
||||
|
||||
with pytest.raises(ValueError, match="identity changed"):
|
||||
_cached_video_overlay(
|
||||
f"e46c-full-replay-world-tracks-{'b' * 64}",
|
||||
str(root),
|
||||
result_sha256,
|
||||
"0" * 64 if fusion_sha256 != "0" * 64 else "f" * 64,
|
||||
)
|
||||
@@ -0,0 +1,90 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from k1link.web import e46e_ready_stack_api
|
||||
|
||||
|
||||
def test_e46e_catalog_and_overlay_are_read_only_hash_bound(
|
||||
tmp_path: Path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
result_id = f"e46e-ready-stack-{'a' * 64}"
|
||||
result_root = tmp_path / result_id
|
||||
result_root.mkdir()
|
||||
overlay = result_root / "overlay.mp4"
|
||||
overlay.write_bytes(b"0123456789abcdef")
|
||||
frozen = {
|
||||
"result_id": result_id,
|
||||
"result_root": result_root,
|
||||
"overlay_path": overlay,
|
||||
"manifest": {
|
||||
"artifacts": [
|
||||
{
|
||||
"role": "visual-overlay-video",
|
||||
"path": "overlay.mp4",
|
||||
"byte_length": 16,
|
||||
"sha256": "b" * 64,
|
||||
}
|
||||
]
|
||||
},
|
||||
"report": {
|
||||
"created_at_utc": "2026-08-04T09:00:00Z",
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"status": "completed-stock-nvidia-recorded-right-replay",
|
||||
"metrics": {"frame_count": 4489},
|
||||
"acceptance": {"full_route_accounted": True},
|
||||
"decision": {"custom_temporal_logic_used": False},
|
||||
"method": {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
},
|
||||
"limitations": ["not truth"],
|
||||
},
|
||||
}
|
||||
monkeypatch.setattr(e46e_ready_stack_api, "read_e46e_ready_stack", lambda _: frozen)
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
e46e_ready_stack_api.build_e46e_ready_stack_router(
|
||||
root_provider=lambda: tmp_path
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
catalog = client.get("/api/v1/laboratory/e46e/results?limit=1")
|
||||
assert catalog.status_code == 200
|
||||
item = catalog.json()["items"][0]
|
||||
assert item["result_id"] == result_id
|
||||
assert item["video"] == {
|
||||
"url": f"/api/v1/laboratory/e46e/results/{result_id}/overlay.mp4",
|
||||
"media_type": "video/mp4",
|
||||
"byte_length": 16,
|
||||
"sha256": "b" * 64,
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
}
|
||||
assert item["ground_truth"] is False
|
||||
|
||||
video = client.get(
|
||||
f"/api/v1/laboratory/e46e/results/{result_id}/overlay.mp4",
|
||||
headers={"Range": "bytes=4-9"},
|
||||
)
|
||||
assert video.status_code == 206
|
||||
assert video.content == b"456789"
|
||||
assert video.headers["content-range"] == "bytes 4-9/16"
|
||||
assert video.headers["etag"] == f'"{"b" * 64}"'
|
||||
assert video.headers["cache-control"].endswith("immutable")
|
||||
|
||||
|
||||
def test_e46e_catalog_is_empty_when_not_configured() -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(e46e_ready_stack_api.build_e46e_ready_stack_router())
|
||||
response = TestClient(app).get("/api/v1/laboratory/e46e/results")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["configured"] is False
|
||||
assert response.json()["items"] == []
|
||||
@@ -0,0 +1,155 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from k1link.web import e46f_dashcam_bakeoff_api
|
||||
|
||||
|
||||
def test_e46f_projects_visual_rejection_against_e46e(tmp_path: Path, monkeypatch) -> None:
|
||||
result_id = f"e46f-dashcam-bakeoff-{'a' * 64}"
|
||||
baseline_id = f"e46e-ready-stack-{'b' * 64}"
|
||||
candidate_root = tmp_path / "candidate"
|
||||
baseline_root = tmp_path / "baseline"
|
||||
(candidate_root / result_id).mkdir(parents=True)
|
||||
(baseline_root / baseline_id).mkdir(parents=True)
|
||||
overlay = candidate_root / result_id / "overlay.mp4"
|
||||
overlay.write_bytes(b"0123456789abcdef")
|
||||
candidate = _frozen(
|
||||
result_id=result_id,
|
||||
result_root=candidate_root / result_id,
|
||||
overlay=overlay,
|
||||
baseline_id=baseline_id,
|
||||
metrics=_metrics(zero_tracks=25, blackouts=1, unique_tracks=545, short_fraction=0.06789),
|
||||
objects=[_large_object("person", 17)],
|
||||
)
|
||||
baseline = {
|
||||
"report": {
|
||||
"metrics": _metrics(
|
||||
zero_tracks=28,
|
||||
blackouts=2,
|
||||
unique_tracks=909,
|
||||
short_fraction=0.093509,
|
||||
)
|
||||
},
|
||||
"frames": ({"frame_index": 0, "objects": []},),
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
e46f_dashcam_bakeoff_api,
|
||||
"read_e46f_dashcam_bakeoff",
|
||||
lambda _: candidate,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
e46f_dashcam_bakeoff_api,
|
||||
"read_e46e_ready_stack",
|
||||
lambda _: baseline,
|
||||
)
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
e46f_dashcam_bakeoff_api.build_e46f_dashcam_bakeoff_router(
|
||||
root_provider=lambda: candidate_root,
|
||||
e46e_root_provider=lambda: baseline_root,
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/laboratory/e46f/results?limit=1")
|
||||
assert response.status_code == 200
|
||||
item = response.json()["items"][0]
|
||||
delta = item["comparison"]["delta"]
|
||||
assert delta["zero_track_frame_count"] == -3
|
||||
assert delta["full_layer_blackout_event_count"] == -1
|
||||
assert delta["unique_track_count"] == -364
|
||||
assert delta["short_track_fraction"] == pytest.approx(-0.025619)
|
||||
assert item["comparison"]["large_box_visual_triage"]["candidate"] == {
|
||||
"observation_count": 1,
|
||||
"frame_count": 1,
|
||||
"track_id_count": 1,
|
||||
"class_observations": {"person": 1},
|
||||
}
|
||||
assert item["comparison"]["verdict"] == ("reject-dashcamnet-on-unrectified-fisheye")
|
||||
assert item["ground_truth"] is False
|
||||
|
||||
video = client.get(
|
||||
f"/api/v1/laboratory/e46f/results/{result_id}/overlay.mp4",
|
||||
headers={"Range": "bytes=4-9"},
|
||||
)
|
||||
assert video.status_code == 206
|
||||
assert video.content == b"456789"
|
||||
assert video.headers["etag"] == f'"{"c" * 64}"'
|
||||
assert video.headers["cache-control"].endswith("immutable")
|
||||
|
||||
|
||||
def test_e46f_catalog_is_empty_when_not_configured() -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(e46f_dashcam_bakeoff_api.build_e46f_dashcam_bakeoff_router())
|
||||
response = TestClient(app).get("/api/v1/laboratory/e46f/results")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["configured"] is False
|
||||
assert response.json()["items"] == []
|
||||
|
||||
|
||||
def _metrics(
|
||||
*, zero_tracks: int, blackouts: int, unique_tracks: int, short_fraction: float
|
||||
) -> dict[str, int | float]:
|
||||
return {
|
||||
"frame_count": 4489,
|
||||
"zero_track_frame_count": zero_tracks,
|
||||
"full_layer_blackout_event_count": blackouts,
|
||||
"unique_track_count": unique_tracks,
|
||||
"short_track_fraction": short_fraction,
|
||||
}
|
||||
|
||||
|
||||
def _large_object(label: str, track_id: int) -> dict[str, object]:
|
||||
return {
|
||||
"bbox": [0.0, 0.0, 400.0, 300.0],
|
||||
"source_track_id": track_id,
|
||||
"class_name": label,
|
||||
}
|
||||
|
||||
|
||||
def _frozen(
|
||||
*,
|
||||
result_id: str,
|
||||
result_root: Path,
|
||||
overlay: Path,
|
||||
baseline_id: str,
|
||||
metrics: dict[str, int | float],
|
||||
objects: list[dict[str, object]],
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"result_id": result_id,
|
||||
"result_root": result_root,
|
||||
"overlay_path": overlay,
|
||||
"manifest": {
|
||||
"artifacts": [
|
||||
{
|
||||
"role": "visual-overlay-video",
|
||||
"path": "overlay.mp4",
|
||||
"byte_length": 16,
|
||||
"sha256": "c" * 64,
|
||||
}
|
||||
]
|
||||
},
|
||||
"report": {
|
||||
"created_at_utc": "2026-08-04T09:00:00Z",
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"status": "completed-stock-nvidia-detector-only-bakeoff",
|
||||
"comparison_contract": {"baseline_result_id": baseline_id},
|
||||
"metrics": metrics,
|
||||
"acceptance": {"full_route_accounted": True},
|
||||
"decision": {"custom_temporal_logic_used": False},
|
||||
"method": {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
},
|
||||
"limitations": ["not truth"],
|
||||
},
|
||||
"frames": ({"frame_index": 0, "objects": objects},),
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from k1link.web import e46g_rectified_detector_bakeoff_api
|
||||
|
||||
|
||||
def test_e46g_projects_visual_front_only_selection(tmp_path: Path, monkeypatch) -> None:
|
||||
result_id = f"e46g-rectified-detector-bakeoff-{'a' * 64}"
|
||||
root = tmp_path / "results"
|
||||
result_root = root / result_id
|
||||
result_root.mkdir(parents=True)
|
||||
traffic = result_root / "trafficcamnet.mp4"
|
||||
dash = result_root / "dashcamnet.mp4"
|
||||
traffic.write_bytes(b"traffic-comparison")
|
||||
dash.write_bytes(b"dash-comparison")
|
||||
frozen = _frozen(result_id, result_root, traffic, dash)
|
||||
monkeypatch.setattr(
|
||||
e46g_rectified_detector_bakeoff_api,
|
||||
"read_e46g_rectified_detector_bakeoff",
|
||||
lambda _: frozen,
|
||||
)
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
e46g_rectified_detector_bakeoff_api.build_e46g_rectified_detector_bakeoff_router(
|
||||
root_provider=lambda: root
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/laboratory/e46g/results?limit=1")
|
||||
assert response.status_code == 200
|
||||
item = response.json()["items"][0]
|
||||
review = item["comparison"]["visual_review"]
|
||||
assert review["selected_candidate"] == "trafficcamnet"
|
||||
assert review["selected_view"] == "front"
|
||||
assert review["excluded_views"] == ["left", "right"]
|
||||
assert item["comparison"]["verdict"] == "select-trafficcamnet-front-only-for-e46h"
|
||||
assert item["ground_truth"] is False
|
||||
assert item["authority"]["navigation_or_safety_accepted"] is False
|
||||
assert item["videos"]["trafficcamnet"]["view_order"] == ["left", "front", "right"]
|
||||
|
||||
video = client.get(
|
||||
f"/api/v1/laboratory/e46g/results/{result_id}/trafficcamnet.mp4",
|
||||
headers={"Range": "bytes=2-8"},
|
||||
)
|
||||
assert video.status_code == 206
|
||||
assert video.content == b"affic-c"
|
||||
assert video.headers["etag"] == f'"{"b" * 64}"'
|
||||
assert video.headers["cache-control"].endswith("immutable")
|
||||
|
||||
|
||||
def test_e46g_catalog_is_empty_when_not_configured() -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
e46g_rectified_detector_bakeoff_api.build_e46g_rectified_detector_bakeoff_router()
|
||||
)
|
||||
response = TestClient(app).get("/api/v1/laboratory/e46g/results")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["configured"] is False
|
||||
assert response.json()["items"] == []
|
||||
|
||||
|
||||
def _frozen(
|
||||
result_id: str,
|
||||
result_root: Path,
|
||||
traffic: Path,
|
||||
dash: Path,
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"result_id": result_id,
|
||||
"result_root": result_root,
|
||||
"comparison_paths": {"trafficcamnet": traffic, "dashcamnet": dash},
|
||||
"manifest": {
|
||||
"artifacts": [
|
||||
{
|
||||
"role": "comparison-video-trafficcamnet",
|
||||
"path": traffic.name,
|
||||
"byte_length": traffic.stat().st_size,
|
||||
"sha256": "b" * 64,
|
||||
},
|
||||
{
|
||||
"role": "comparison-video-dashcamnet",
|
||||
"path": dash.name,
|
||||
"byte_length": dash.stat().st_size,
|
||||
"sha256": "c" * 64,
|
||||
},
|
||||
]
|
||||
},
|
||||
"report": {
|
||||
"created_at_utc": "2026-08-04T13:28:23.707Z",
|
||||
"source_session_id": "20260720T065719Z_viewer_live",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"selection": {
|
||||
"first_source_frame_index": 1000,
|
||||
"last_source_frame_index": 1599,
|
||||
"frame_count": 600,
|
||||
},
|
||||
"rectification": {
|
||||
"provider": "NVIDIA Gst-nvdewarper",
|
||||
"output_resolution": [960, 544],
|
||||
"view_order": ["left", "front", "right"],
|
||||
},
|
||||
"metrics": {
|
||||
"trafficcamnet": {"views": {"front": {"zero_track_frame_count": 0}}},
|
||||
"dashcamnet": {"views": {"front": {"zero_track_frame_count": 34}}},
|
||||
},
|
||||
"acceptance": {"official_nvidia_dewarper_executed": True},
|
||||
"method": {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": "e46g-test/v1",
|
||||
"components": [],
|
||||
},
|
||||
"limitations": ["not truth"],
|
||||
"authority": {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"free_space_authority": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from k1link.web import e46h_full_rectified_front_replay_api
|
||||
|
||||
|
||||
def test_e46h_projects_full_video_and_visual_exception_windows(
|
||||
tmp_path: Path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
result_id = f"e46h-full-rectified-front-replay-{'a' * 64}"
|
||||
root = tmp_path / "results"
|
||||
result_root = root / result_id
|
||||
result_root.mkdir(parents=True)
|
||||
overlay = result_root / "overlay.mp4"
|
||||
overlay.write_bytes(b"0123456789abcdef")
|
||||
frozen = _frozen(result_id, result_root, overlay)
|
||||
monkeypatch.setattr(
|
||||
e46h_full_rectified_front_replay_api,
|
||||
"read_e46h_full_rectified_front_replay",
|
||||
lambda _: frozen,
|
||||
)
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
e46h_full_rectified_front_replay_api.build_e46h_full_rectified_front_replay_router(
|
||||
root_provider=lambda: root
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/laboratory/e46h/results?limit=1")
|
||||
assert response.status_code == 200
|
||||
item = response.json()["items"][0]
|
||||
assert item["selection"]["frame_count"] == 4488
|
||||
assert item["rectification"]["view"] == "front"
|
||||
assert (
|
||||
item["visual_review"]["status"]
|
||||
== "full-continuous-and-targeted-review-completed"
|
||||
)
|
||||
assert item["visual_review"]["complete_video_reviewed"] is True
|
||||
assert item["visual_review"]["reviewed_video_range_seconds"] == [0.0, 448.8]
|
||||
assert len(item["visual_review"]["review_windows"]) == 6
|
||||
assert sum(
|
||||
row["verdict"] == "semantic-false-positive"
|
||||
for row in item["visual_review"]["review_windows"]
|
||||
) == 5
|
||||
assert item["ground_truth"] is False
|
||||
assert item["authority"]["candidate_accepted"] is False
|
||||
|
||||
video = client.get(
|
||||
f"/api/v1/laboratory/e46h/results/{result_id}/overlay.mp4",
|
||||
headers={"Range": "bytes=4-9"},
|
||||
)
|
||||
assert video.status_code == 206
|
||||
assert video.content == b"456789"
|
||||
assert video.headers["etag"] == f'"{"b" * 64}"'
|
||||
assert video.headers["cache-control"].endswith("immutable")
|
||||
|
||||
|
||||
def test_e46h_catalog_is_empty_when_not_configured() -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
e46h_full_rectified_front_replay_api.build_e46h_full_rectified_front_replay_router()
|
||||
)
|
||||
response = TestClient(app).get("/api/v1/laboratory/e46h/results")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["configured"] is False
|
||||
assert response.json()["items"] == []
|
||||
|
||||
|
||||
def _frozen(result_id: str, result_root: Path, overlay: Path) -> dict[str, object]:
|
||||
return {
|
||||
"result_id": result_id,
|
||||
"result_root": result_root,
|
||||
"overlay_path": overlay,
|
||||
"manifest": {
|
||||
"artifacts": [
|
||||
{
|
||||
"role": "visual-overlay-video",
|
||||
"path": overlay.name,
|
||||
"byte_length": overlay.stat().st_size,
|
||||
"sha256": "b" * 64,
|
||||
}
|
||||
]
|
||||
},
|
||||
"report": {
|
||||
"created_at_utc": "2026-08-04T14:17:42.899Z",
|
||||
"source_session_id": "20260720T065719Z_viewer_live",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"baseline_result_id": f"e46g-rectified-detector-bakeoff-{'c' * 64}",
|
||||
"selection": {
|
||||
"first_source_frame_index": 0,
|
||||
"last_source_frame_index": 4487,
|
||||
"frame_count": 4488,
|
||||
"excluded_source_tail_frame_count": 1,
|
||||
},
|
||||
"rectification": {
|
||||
"provider": "NVIDIA Gst-nvdewarper",
|
||||
"provider_version": "DeepStream 9.1",
|
||||
"projection": "fisheye-to-perspective",
|
||||
"view": "front",
|
||||
"output_resolution": [960, 544],
|
||||
"horizontal_fov_degrees": 100,
|
||||
},
|
||||
"metrics": {"frame_count": 4488},
|
||||
"acceptance": {"candidate_accepted": False},
|
||||
"decision": {"provider_promoted": False},
|
||||
"method": {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": "e46h-test/v1",
|
||||
"components": [],
|
||||
},
|
||||
"limitations": ["not truth"],
|
||||
"authority": {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"free_space_authority": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from k1link.web import e46i_grounding_dino_full_replay_api
|
||||
|
||||
|
||||
def test_e46i_projects_full_video_and_visual_comparison(
|
||||
tmp_path: Path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
result_id = f"e46i-grounding-dino-full-replay-{'a' * 64}"
|
||||
root = tmp_path / "results"
|
||||
result_root = root / result_id
|
||||
result_root.mkdir(parents=True)
|
||||
paths = {
|
||||
"overlay_path": result_root / "overlay.mp4",
|
||||
"shadow_sheet_path": result_root / "shadow.png",
|
||||
"route_sheet_path": result_root / "route.png",
|
||||
"targeted_sheet_path": result_root / "targeted.png",
|
||||
}
|
||||
for index, path in enumerate(paths.values(), 1):
|
||||
path.write_bytes(bytes(range(index, index + 16)))
|
||||
frozen = _frozen(result_id, result_root, paths)
|
||||
monkeypatch.setattr(
|
||||
e46i_grounding_dino_full_replay_api,
|
||||
"read_e46i_grounding_dino_full_replay",
|
||||
lambda _: frozen,
|
||||
)
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
e46i_grounding_dino_full_replay_api.build_e46i_grounding_dino_full_replay_router(
|
||||
root_provider=lambda: root
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/laboratory/e46i/results?limit=1")
|
||||
assert response.status_code == 200
|
||||
item = response.json()["items"][0]
|
||||
assert item["status"] == "semantic-regression-suppressed-awaiting-temporal-layer"
|
||||
assert item["source"]["video_frame_count"] == 4488
|
||||
assert item["shadow_gate"]["legacy_large_false_background_cases_suppressed"] == 5
|
||||
assert item["visual_review"]["complete_video_playback_completed"] is True
|
||||
assert len(item["visual_review"]["review_windows"]) == 6
|
||||
assert item["video"]["duration_seconds"] == 448.8
|
||||
assert set(item["visuals"]) == {"shadow_gate", "full_route", "targeted_windows"}
|
||||
assert item["ground_truth"] is False
|
||||
assert item["authority"]["candidate_accepted"] is False
|
||||
|
||||
video = client.get(
|
||||
f"/api/v1/laboratory/e46i/results/{result_id}/overlay.mp4",
|
||||
headers={"Range": "bytes=4-9"},
|
||||
)
|
||||
assert video.status_code == 206
|
||||
assert video.content == bytes(range(5, 11))
|
||||
assert video.headers["cache-control"].endswith("immutable")
|
||||
|
||||
visual = client.get(
|
||||
f"/api/v1/laboratory/e46i/results/{result_id}/visual/targeted-windows.png"
|
||||
)
|
||||
assert visual.status_code == 200
|
||||
assert visual.headers["content-type"] == "image/png"
|
||||
|
||||
|
||||
def test_e46i_catalog_is_empty_when_not_configured() -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
e46i_grounding_dino_full_replay_api.build_e46i_grounding_dino_full_replay_router()
|
||||
)
|
||||
response = TestClient(app).get("/api/v1/laboratory/e46i/results")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["configured"] is False
|
||||
assert response.json()["items"] == []
|
||||
|
||||
|
||||
def _frozen(
|
||||
result_id: str,
|
||||
result_root: Path,
|
||||
paths: dict[str, Path],
|
||||
) -> dict[str, object]:
|
||||
roles = {
|
||||
"overlay_path": "visual-overlay-video",
|
||||
"shadow_sheet_path": "shadow-gate-contact-sheet",
|
||||
"route_sheet_path": "full-route-contact-sheet",
|
||||
"targeted_sheet_path": "targeted-windows-contact-sheet",
|
||||
}
|
||||
artifacts = [
|
||||
{
|
||||
"role": role,
|
||||
"path": paths[key].name,
|
||||
"byte_length": paths[key].stat().st_size,
|
||||
"sha256": f"{index:x}" * 64,
|
||||
}
|
||||
for index, (key, role) in enumerate(roles.items(), 1)
|
||||
]
|
||||
return {
|
||||
"result_id": result_id,
|
||||
"result_root": result_root,
|
||||
**paths,
|
||||
"manifest": {"artifacts": artifacts},
|
||||
"report": {
|
||||
"created_at_utc": "2026-08-04T16:10:00.000Z",
|
||||
"status": "semantic-regression-suppressed-awaiting-temporal-layer",
|
||||
"baseline_result_id": f"e46h-full-rectified-front-replay-{'b' * 64}",
|
||||
"source": {
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"session_id": "20260720T065719Z_viewer_live",
|
||||
"view": "front",
|
||||
"projection_resolution": [960, 544],
|
||||
"video_frame_count": 4488,
|
||||
"video_frame_rate": 10.0,
|
||||
"video_duration_seconds": 448.8,
|
||||
},
|
||||
"provider": {"name": "NVIDIA Grounding DINO"},
|
||||
"inference": {"confidence_threshold": 0.5},
|
||||
"metrics": {"frame_count": 4488},
|
||||
"shadow_gate": {
|
||||
"legacy_large_false_background_cases_suppressed": 5,
|
||||
},
|
||||
"visual_review": {
|
||||
"complete_video_playback_completed": True,
|
||||
"review_windows": [{"id": str(index)} for index in range(6)],
|
||||
},
|
||||
"acceptance": {"candidate_accepted": False},
|
||||
"decision": {"provider_promoted": False},
|
||||
"method": {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": "e46i-test/v1",
|
||||
"components": [],
|
||||
},
|
||||
"limitations": ["not truth"],
|
||||
"authority": {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from k1link.web import e46j_raw_fisheye_realtime_api
|
||||
|
||||
|
||||
def test_e46j_projects_full_raw_video_and_known_shadow_exception(
|
||||
tmp_path: Path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
result_id = f"e46j-raw-fisheye-realtime-{'a' * 64}"
|
||||
root = tmp_path / "results"
|
||||
result_root = root / result_id
|
||||
result_root.mkdir(parents=True)
|
||||
paths = {
|
||||
"overlay_path": result_root / "overlay.mp4",
|
||||
"route_sheet_path": result_root / "route.png",
|
||||
"targeted_sheet_path": result_root / "targeted.png",
|
||||
"shadow_sheet_path": result_root / "shadow.png",
|
||||
}
|
||||
for index, path in enumerate(paths.values(), 1):
|
||||
path.write_bytes(bytes(range(index, index + 16)))
|
||||
frozen = _frozen(result_id, result_root, paths)
|
||||
monkeypatch.setattr(
|
||||
e46j_raw_fisheye_realtime_api,
|
||||
"read_e46j_raw_fisheye_realtime",
|
||||
lambda _: frozen,
|
||||
)
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
e46j_raw_fisheye_realtime_api.build_e46j_raw_fisheye_realtime_router(
|
||||
root_provider=lambda: root
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/laboratory/e46j/results?limit=1")
|
||||
assert response.status_code == 200
|
||||
item = response.json()["items"][0]
|
||||
assert item["status"] == "realtime-capacity-passed-awaiting-temporal-layer"
|
||||
assert item["source"]["resolution"] == [800, 600]
|
||||
assert item["metrics"]["core_capacity_fps"] == 47.84049
|
||||
assert item["metrics"]["operator_shadow_person_frame_count"] == 35
|
||||
assert item["video"]["frame_count"] == 4489
|
||||
assert item["video"]["duration_seconds"] == 448.723
|
||||
assert set(item["visuals"]) == {
|
||||
"full_route",
|
||||
"targeted_windows",
|
||||
"operator_shadow",
|
||||
}
|
||||
assert item["ground_truth"] is False
|
||||
assert item["authority"]["provider_promoted"] is False
|
||||
|
||||
video = client.get(
|
||||
f"/api/v1/laboratory/e46j/results/{result_id}/overlay.mp4",
|
||||
headers={"Range": "bytes=4-9"},
|
||||
)
|
||||
assert video.status_code == 206
|
||||
assert video.content == bytes(range(5, 11))
|
||||
assert video.headers["cache-control"].endswith("immutable")
|
||||
|
||||
visual = client.get(
|
||||
f"/api/v1/laboratory/e46j/results/{result_id}/visual/operator-shadow.png"
|
||||
)
|
||||
assert visual.status_code == 200
|
||||
assert visual.headers["content-type"] == "image/png"
|
||||
|
||||
|
||||
def test_e46j_catalog_is_empty_when_not_configured() -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
e46j_raw_fisheye_realtime_api.build_e46j_raw_fisheye_realtime_router()
|
||||
)
|
||||
response = TestClient(app).get("/api/v1/laboratory/e46j/results")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["configured"] is False
|
||||
assert response.json()["items"] == []
|
||||
|
||||
|
||||
def _frozen(
|
||||
result_id: str,
|
||||
result_root: Path,
|
||||
paths: dict[str, Path],
|
||||
) -> dict[str, object]:
|
||||
roles = {
|
||||
"overlay_path": "visual-overlay-video",
|
||||
"route_sheet_path": "full-route-contact-sheet",
|
||||
"targeted_sheet_path": "targeted-windows-contact-sheet",
|
||||
"shadow_sheet_path": "operator-shadow-contact-sheet",
|
||||
}
|
||||
artifacts = [
|
||||
{
|
||||
"role": role,
|
||||
"path": paths[key].name,
|
||||
"byte_length": paths[key].stat().st_size,
|
||||
"sha256": f"{index:x}" * 64,
|
||||
}
|
||||
for index, (key, role) in enumerate(roles.items(), 1)
|
||||
]
|
||||
authority = {
|
||||
"ground_truth": False,
|
||||
"provider_promoted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
return {
|
||||
"result_id": result_id,
|
||||
"result_root": result_root,
|
||||
**paths,
|
||||
"manifest": {"artifacts": artifacts},
|
||||
"report": {
|
||||
"created_at_utc": "2026-08-04T18:20:00.000Z",
|
||||
"status": "realtime-capacity-passed-awaiting-temporal-layer",
|
||||
"source": {
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"session_id": "20260720T065719Z_viewer_live",
|
||||
"resolution": [800, 600],
|
||||
"frame_count": 4489,
|
||||
"frame_rate": 10.003944527024467,
|
||||
"calibration_model": "KB4",
|
||||
},
|
||||
"detector": {"architecture": "YOLOX-S"},
|
||||
"preprocessing": {"resize": "bilinear-letterbox-top-left"},
|
||||
"detection": {"minimum_score": 0.5},
|
||||
"metrics": {
|
||||
"frame_count": 4489,
|
||||
"core_capacity_fps": 47.84049,
|
||||
"operator_shadow_person_frame_count": 35,
|
||||
},
|
||||
"visual_review": {
|
||||
"review_windows": [{"id": str(index)} for index in range(6)],
|
||||
},
|
||||
"acceptance": {"ten_hz_capacity_gate_passed": True},
|
||||
"decision": {"provider_promoted": False},
|
||||
"method": {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": "e46j-test/v1",
|
||||
"components": [],
|
||||
},
|
||||
"limitations": ["not truth"],
|
||||
"authority": authority,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from fastapi import APIRouter
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
from k1link.compute.l33_camera_first_admission import (
|
||||
RAVNOVES00_ADMITTED_WORLD_STATE_RESULT_ID,
|
||||
RAVNOVES00_SESSION_ID,
|
||||
)
|
||||
from k1link.compute.l33_camera_first_detector_review import (
|
||||
L33CameraFirstDetectorReviewError,
|
||||
build_l33_camera_first_detector_review,
|
||||
)
|
||||
from k1link.web.l33_camera_first_detector_review_api import (
|
||||
build_l33_camera_first_detector_review_router,
|
||||
latest_l33_identity,
|
||||
)
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode(
|
||||
"utf-8"
|
||||
)
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def _endpoint(router: APIRouter, path: str) -> object:
|
||||
for route in router.routes:
|
||||
if (
|
||||
isinstance(route, APIRoute)
|
||||
and route.path == path
|
||||
and route.methods is not None
|
||||
and "GET" in route.methods
|
||||
):
|
||||
return route.endpoint
|
||||
raise AssertionError(f"GET {path} route is missing")
|
||||
|
||||
|
||||
def _write_candidate(
|
||||
root: Path,
|
||||
*,
|
||||
world_state_result_id: str,
|
||||
created_at_utc: str,
|
||||
) -> Path:
|
||||
identity = {
|
||||
"schema_version": "missioncore.l33-camera-first-detector-review/v1",
|
||||
"source_session_id": RAVNOVES00_SESSION_ID,
|
||||
"source_l32_result_id": f"l32-pointpillars-camera-review-{'1' * 64}",
|
||||
"source_e26_result_id": f"e10-integrated-perception-{'2' * 64}",
|
||||
"source_e29_result_id": f"e29-camera-geometry-{'3' * 64}",
|
||||
"source_e10_pack_id": f"e10-lidar-pack-{'4' * 64}",
|
||||
"source_world_state_result_id": world_state_result_id,
|
||||
"detector": {"id": "yolox_s"},
|
||||
"semantic_contract": {"owner": "camera"},
|
||||
"geometry_contract": {"owner": "lidar"},
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"l33-camera-first-detector-review-{identity_sha256}"
|
||||
candidate = root / result_id
|
||||
candidate.mkdir(parents=True)
|
||||
catalog = {
|
||||
"schema_version": "missioncore.l33-camera-first-detector-review-catalog/v1",
|
||||
"result_id": result_id,
|
||||
"source_session_id": RAVNOVES00_SESSION_ID,
|
||||
"frame_count": 1,
|
||||
"frames": [
|
||||
{
|
||||
"frame_id": "000001",
|
||||
"detail_path": "frame-000001.json",
|
||||
"detail_sha256": "5" * 64,
|
||||
"detail_byte_length": 1,
|
||||
"camera_path": "frames/frame-000001.jpg",
|
||||
"camera_sha256": "6" * 64,
|
||||
"semantic_provenance": "rectified-detector-current",
|
||||
}
|
||||
],
|
||||
}
|
||||
catalog_path = candidate / "catalog.json"
|
||||
catalog_path.write_text(json.dumps(catalog), encoding="utf-8")
|
||||
manifest = {
|
||||
"schema_version": "missioncore.l33-camera-first-detector-review/v1",
|
||||
"result_id": result_id,
|
||||
"identity": identity,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"status": "camera-first-boundary-restored-shadow-only",
|
||||
"metrics": {},
|
||||
"catalog": {
|
||||
"path": "catalog.json",
|
||||
"sha256": _sha256(catalog_path),
|
||||
"byte_length": catalog_path.stat().st_size,
|
||||
},
|
||||
"limitations": [],
|
||||
"authority": {
|
||||
"shadow_only": True,
|
||||
"accuracy_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
"ground_truth": False,
|
||||
}
|
||||
(candidate / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
|
||||
return candidate
|
||||
|
||||
|
||||
def test_catalog_and_latest_ignore_newer_unadmitted_parent(tmp_path: Path) -> None:
|
||||
admitted = _write_candidate(
|
||||
tmp_path,
|
||||
world_state_result_id=RAVNOVES00_ADMITTED_WORLD_STATE_RESULT_ID,
|
||||
created_at_utc="2026-07-31T17:23:36.806+00:00",
|
||||
)
|
||||
_write_candidate(
|
||||
tmp_path,
|
||||
world_state_result_id=f"rectified-camera-world-state-{'b' * 64}",
|
||||
created_at_utc="2026-07-31T18:19:40.451+00:00",
|
||||
)
|
||||
router = build_l33_camera_first_detector_review_router(root_provider=lambda: tmp_path)
|
||||
catalog = _endpoint(router, "/api/v1/laboratory/l33/camera-first-detector-review/results")(
|
||||
limit=10
|
||||
) # type: ignore[operator]
|
||||
|
||||
assert catalog["candidate_total"] == 2
|
||||
assert catalog["invalid_total"] == 1
|
||||
assert [item["result_id"] for item in catalog["items"]] == [admitted.name]
|
||||
assert latest_l33_identity(lambda: tmp_path) == {
|
||||
"result_id": admitted.name,
|
||||
"created_at_utc": "2026-07-31T17:23:36.806+00:00",
|
||||
}
|
||||
|
||||
|
||||
def test_builder_rejects_unadmitted_world_state_before_reading_sources(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
wrong_world_state = tmp_path / f"rectified-camera-world-state-{'b' * 64}"
|
||||
wrong_world_state.mkdir()
|
||||
|
||||
with pytest.raises(
|
||||
L33CameraFirstDetectorReviewError,
|
||||
match="not admitted for RAVNOVES00",
|
||||
):
|
||||
build_l33_camera_first_detector_review(
|
||||
l32_result_root=tmp_path / "missing-l32",
|
||||
e26_result_root=tmp_path / "missing-e26",
|
||||
e29_result_root=tmp_path / "missing-e29",
|
||||
e10_pack_root=tmp_path / "missing-pack",
|
||||
output_root=tmp_path / "output",
|
||||
rectified_world_state_root=wrong_world_state,
|
||||
)
|
||||
@@ -0,0 +1,593 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import struct
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
from k1link.compute.e46_detector_truth_island import (
|
||||
E46_CONTRACT_NAME,
|
||||
E46_MANIFEST_NAME,
|
||||
E46_REFERENCES_NAME,
|
||||
E46_REPORT_NAME,
|
||||
E46_REVIEW_NAME,
|
||||
)
|
||||
from k1link.compute.l34_right_yolox_truth_island_freeze import (
|
||||
L34_MANIFEST_NAME,
|
||||
L34_PREDICTIONS_NAME,
|
||||
L34_REPORT_NAME,
|
||||
)
|
||||
from k1link.web.advanced_laboratory_api import build_advanced_laboratory_router
|
||||
from k1link.web.l34_annotation_api import (
|
||||
L34AnnotationCreateRequest,
|
||||
L34AnnotationFrameRequest,
|
||||
L34AnnotationObjectRequest,
|
||||
L34AnnotationSaveRequest,
|
||||
build_l34_annotation_router,
|
||||
)
|
||||
from k1link.web.l34_right_yolox_truth_island_api import (
|
||||
build_l34_right_yolox_truth_island_freeze_router,
|
||||
)
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def _endpoint(router: APIRouter, path: str, method: str = "GET") -> object:
|
||||
for route in router.routes:
|
||||
if (
|
||||
isinstance(route, APIRoute)
|
||||
and route.path == path
|
||||
and route.methods is not None
|
||||
and method in route.methods
|
||||
):
|
||||
return route.endpoint
|
||||
raise AssertionError(f"{method} {path} route is missing")
|
||||
|
||||
|
||||
def _write_result(
|
||||
root: Path,
|
||||
*,
|
||||
truth_result_id: str | None = None,
|
||||
truth_manifest_sha256: str | None = None,
|
||||
source_image_sha256: str = "6" * 64,
|
||||
) -> Path:
|
||||
identity = {
|
||||
"schema_version": "missioncore.l34-right-yolox-truth-island-freeze/v1",
|
||||
"profile": {
|
||||
"profile_id": "RAVNOVES00_RIGHT_YOLOX_TRUTH_ISLAND_V1",
|
||||
"candidate": {
|
||||
"architecture": "YOLOX-S",
|
||||
"model_sha256": "1" * 64,
|
||||
"minimum_score": 0.25,
|
||||
},
|
||||
},
|
||||
"source": {
|
||||
"session_id": "20260720T065719Z_viewer_live",
|
||||
"source_id": "sensor.camera.right",
|
||||
"mode": "recorded-replay-only",
|
||||
},
|
||||
"truth_island": {
|
||||
"result_id": truth_result_id
|
||||
or f"e46-detector-truth-island-{'2' * 64}",
|
||||
"manifest_sha256": truth_manifest_sha256 or "4" * 64,
|
||||
"truth_state": "labels-unavailable",
|
||||
},
|
||||
"candidate": {
|
||||
"l33_result_id": f"l33-camera-first-detector-review-{'3' * 64}",
|
||||
"prediction_rows_sha256": "5" * 64,
|
||||
},
|
||||
"authority": {
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"l34-right-yolox-truth-island-freeze-{identity_sha256}"
|
||||
candidate = root / result_id
|
||||
candidate.mkdir(parents=True)
|
||||
report = {
|
||||
"schema_version": "missioncore.l34-right-yolox-truth-island-report/v1",
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"status": "predictions-frozen-awaiting-independent-truth",
|
||||
"profile_id": "RAVNOVES00_RIGHT_YOLOX_TRUTH_ISLAND_V1",
|
||||
"pipeline_id": "kb4-core3-yolox-eomt-k1-lidar-e23-temporal/v1",
|
||||
"source_session_id": "20260720T065719Z_viewer_live",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"metrics": {
|
||||
"frame_count": 1,
|
||||
"temporal_group_count": 1,
|
||||
"prediction_count": 1,
|
||||
"frames_with_predictions": 1,
|
||||
"class_counts": {"car": 1},
|
||||
"accuracy_metrics_available": False,
|
||||
},
|
||||
"decision": {
|
||||
"candidate_predictions_frozen": True,
|
||||
"truth_labels_read": False,
|
||||
"candidate_accepted": False,
|
||||
"model_retraining_authorized": False,
|
||||
"next_gate": "independent truth",
|
||||
},
|
||||
"limitations": ["recorded right camera only"],
|
||||
"authority": identity["authority"],
|
||||
"access": "read-only",
|
||||
}
|
||||
prediction = {
|
||||
"schema_version": "missioncore.l34-right-yolox-truth-island-prediction/v1",
|
||||
"candidate_id": "yolox-s-kb4-core3",
|
||||
"truth_island_sequence": 1,
|
||||
"image_id": 1,
|
||||
"frame_index": 1,
|
||||
"session_seconds": 1.25,
|
||||
"source_image_sha256": source_image_sha256,
|
||||
"group_id": "anchor-test",
|
||||
"predictions": [
|
||||
{
|
||||
"label": "car",
|
||||
"score": 0.9,
|
||||
"bbox_xyxy": [1.0, 1.0, 6.0, 5.0],
|
||||
}
|
||||
],
|
||||
"truth_joined": False,
|
||||
}
|
||||
report_path = candidate / L34_REPORT_NAME
|
||||
prediction_path = candidate / L34_PREDICTIONS_NAME
|
||||
report_path.write_text(json.dumps(report), encoding="utf-8")
|
||||
prediction_path.write_text(json.dumps(prediction) + "\n", encoding="utf-8")
|
||||
manifest = {
|
||||
"schema_version": "missioncore.l34-right-yolox-truth-island-freeze/v1",
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": "2026-08-01T00:30:00Z",
|
||||
"ground_truth": False,
|
||||
"artifacts": [
|
||||
{
|
||||
"path": L34_REPORT_NAME,
|
||||
"byte_length": report_path.stat().st_size,
|
||||
"sha256": _sha256(report_path),
|
||||
},
|
||||
{
|
||||
"path": L34_PREDICTIONS_NAME,
|
||||
"byte_length": prediction_path.stat().st_size,
|
||||
"sha256": _sha256(prediction_path),
|
||||
},
|
||||
],
|
||||
"authority": identity["authority"],
|
||||
}
|
||||
(candidate / L34_MANIFEST_NAME).write_text(
|
||||
json.dumps(manifest),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return candidate
|
||||
|
||||
|
||||
def _png(width: int, height: int) -> bytes:
|
||||
signature = b"\x89PNG\r\n\x1a\n"
|
||||
|
||||
def chunk(kind: bytes, payload: bytes) -> bytes:
|
||||
return (
|
||||
struct.pack(">I", len(payload))
|
||||
+ kind
|
||||
+ payload
|
||||
+ struct.pack(">I", zlib.crc32(kind + payload) & 0xFFFFFFFF)
|
||||
)
|
||||
|
||||
pixels = b"".join(b"\x00" + b"\x20\x40\x60" * width for _ in range(height))
|
||||
return (
|
||||
signature
|
||||
+ chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0))
|
||||
+ chunk(b"IDAT", zlib.compress(pixels))
|
||||
+ chunk(b"IEND", b"")
|
||||
)
|
||||
|
||||
|
||||
def _write_visual_sources(root: Path) -> tuple[Path, Path, Path]:
|
||||
pack_id = f"evaluation-pack-{'a' * 64}"
|
||||
pack_root = root / "packs"
|
||||
image_path = pack_root / pack_id / "images" / "valid-fov-fill" / "frame.png"
|
||||
image_path.parent.mkdir(parents=True)
|
||||
image_path.write_bytes(_png(8, 6))
|
||||
reference = {
|
||||
"schema_version": "missioncore.e46-truth-island-image-reference/v1",
|
||||
"truth_island_sequence": 1,
|
||||
"image_id": 1,
|
||||
"frame_index": 1,
|
||||
"group_id": "anchor-test",
|
||||
"role": "anchor",
|
||||
"session_seconds": 1.25,
|
||||
"source_path": "images/valid-fov-fill/frame.png",
|
||||
"byte_length": image_path.stat().st_size,
|
||||
"sha256": _sha256(image_path),
|
||||
}
|
||||
truth_parent = root / "truth"
|
||||
truth_parent.mkdir()
|
||||
identity = {
|
||||
"source": {
|
||||
"evaluation_pack_id": pack_id,
|
||||
"source_id": "sensor.camera.right",
|
||||
},
|
||||
"authority": {
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
truth_id = f"e46-detector-truth-island-{identity_sha256}"
|
||||
truth_root = truth_parent / truth_id
|
||||
truth_root.mkdir()
|
||||
documents = {
|
||||
E46_REPORT_NAME: {
|
||||
"schema_version": "missioncore.e46-truth-island-preparation-report/v1",
|
||||
"result_id": truth_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"blindness": {
|
||||
"candidate_comparison_authorized": False,
|
||||
"model_prelabels_included": False,
|
||||
"predictions_included": False,
|
||||
"truth_labels_available": False,
|
||||
},
|
||||
"selection": {"frame_count": 1},
|
||||
},
|
||||
E46_REFERENCES_NAME: None,
|
||||
E46_CONTRACT_NAME: {
|
||||
"schema_version": "missioncore.e46-detector-blind-contract/v1",
|
||||
"truth_state": "labels-unavailable",
|
||||
"reviewer_package": {
|
||||
"model_prelabels_included": False,
|
||||
"model_predictions_included": False,
|
||||
},
|
||||
},
|
||||
E46_REVIEW_NAME: {
|
||||
"schema_version": "missioncore.e46-detector-review-template/v1",
|
||||
"state": "prepared-unreviewed-no-prelabels",
|
||||
"images": [],
|
||||
},
|
||||
}
|
||||
for name, document in documents.items():
|
||||
path = truth_root / name
|
||||
if name == E46_REFERENCES_NAME:
|
||||
path.write_text(json.dumps(reference) + "\n", encoding="utf-8")
|
||||
else:
|
||||
path.write_text(json.dumps(document), encoding="utf-8")
|
||||
authority = identity["authority"]
|
||||
manifest = {
|
||||
"schema_version": "missioncore.e46-detector-truth-island/v1",
|
||||
"result_id": truth_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"acceptance_state": "prepared-not-truth",
|
||||
"artifacts": [
|
||||
{
|
||||
"path": name,
|
||||
"byte_length": (truth_root / name).stat().st_size,
|
||||
"sha256": _sha256(truth_root / name),
|
||||
}
|
||||
for name in documents
|
||||
],
|
||||
"authority": authority,
|
||||
}
|
||||
manifest_path = truth_root / E46_MANIFEST_NAME
|
||||
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
||||
result_root = root / "results"
|
||||
result_root.mkdir()
|
||||
result = _write_result(
|
||||
result_root,
|
||||
truth_result_id=truth_id,
|
||||
truth_manifest_sha256=_sha256(manifest_path),
|
||||
source_image_sha256=_sha256(image_path),
|
||||
)
|
||||
return result, truth_parent, pack_root
|
||||
|
||||
|
||||
def test_l34_catalog_is_path_free_and_truth_closed(tmp_path: Path) -> None:
|
||||
result = _write_result(tmp_path)
|
||||
router = build_l34_right_yolox_truth_island_freeze_router(
|
||||
root_provider=lambda: tmp_path,
|
||||
)
|
||||
catalog = _endpoint(router, "/api/v1/laboratory/l34/results")(
|
||||
limit=1
|
||||
) # type: ignore[operator]
|
||||
|
||||
item = catalog["items"][0]
|
||||
assert item["result_id"] == result.name
|
||||
assert item["camera_source_id"] == "sensor.camera.right"
|
||||
assert item["metrics"]["accuracy_metrics_available"] is False
|
||||
assert item["decision"]["truth_labels_read"] is False
|
||||
assert item["frames"] == [
|
||||
{
|
||||
"truth_island_sequence": 1,
|
||||
"image_id": 1,
|
||||
"frame_index": 1,
|
||||
"group_id": "anchor-test",
|
||||
"prediction_count": 1,
|
||||
"maximum_score": 0.9,
|
||||
}
|
||||
]
|
||||
assert "result_root" not in json.dumps(item)
|
||||
|
||||
|
||||
def test_l34_visual_frame_is_hash_bound_and_path_free(tmp_path: Path) -> None:
|
||||
result, truth_root, pack_root = _write_visual_sources(tmp_path)
|
||||
router = build_l34_right_yolox_truth_island_freeze_router(
|
||||
root_provider=lambda: result.parent,
|
||||
truth_root_provider=lambda: truth_root,
|
||||
evaluation_pack_root_provider=lambda: pack_root,
|
||||
)
|
||||
frame = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/l34/results/{result_id}/frames/{sequence}",
|
||||
)(result_id=result.name, sequence=1) # type: ignore[operator]
|
||||
assert frame["camera"]["width"] == 8
|
||||
assert frame["camera"]["height"] == 6
|
||||
assert frame["predictions"][0]["bbox_xyxy"] == [1.0, 1.0, 6.0, 5.0]
|
||||
assert frame["truth_labels_read"] is False
|
||||
assert "source_path" not in json.dumps(frame)
|
||||
|
||||
image_path = next(pack_root.rglob("frame.png"))
|
||||
image_path.write_bytes(image_path.read_bytes() + b"changed")
|
||||
with pytest.raises(HTTPException) as caught:
|
||||
_endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/l34/results/{result_id}/frames/{sequence}",
|
||||
)(result_id=result.name, sequence=1) # type: ignore[operator]
|
||||
assert caught.value.status_code == 404
|
||||
|
||||
|
||||
def test_l34_annotation_source_is_prediction_free(tmp_path: Path) -> None:
|
||||
result, truth_root, pack_root = _write_visual_sources(tmp_path)
|
||||
router = build_l34_annotation_router(
|
||||
root_provider=lambda: result.parent,
|
||||
truth_root_provider=lambda: truth_root,
|
||||
evaluation_pack_root_provider=lambda: pack_root,
|
||||
annotation_root_provider=lambda: tmp_path / "annotations",
|
||||
)
|
||||
|
||||
catalog = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/l34/results/{result_id}/annotation-source",
|
||||
)(result_id=result.name) # type: ignore[operator]
|
||||
frame = _endpoint(
|
||||
router,
|
||||
(
|
||||
"/api/v1/laboratory/l34/results/{result_id}"
|
||||
"/annotation-source/frames/{sequence}"
|
||||
),
|
||||
)(result_id=result.name, sequence=1) # type: ignore[operator]
|
||||
seed = _endpoint(
|
||||
router,
|
||||
(
|
||||
"/api/v1/laboratory/l34/results/{result_id}"
|
||||
"/annotation-seed/frames/{sequence}"
|
||||
),
|
||||
)(result_id=result.name, sequence=1) # type: ignore[operator]
|
||||
|
||||
serialized = json.dumps({"catalog": catalog, "frame": frame})
|
||||
assert catalog["frame_count"] == 1
|
||||
assert catalog["contract"]["classes"] == [
|
||||
"person",
|
||||
"bicycle",
|
||||
"motorcycle",
|
||||
"car",
|
||||
"heavy_vehicle",
|
||||
"static_obstacle",
|
||||
"animal",
|
||||
]
|
||||
assert catalog["contract"]["unmapped_class"] == {
|
||||
"value": "unmapped",
|
||||
"proposed_label_required": True,
|
||||
"normalization_state": "pending-adjudication",
|
||||
}
|
||||
assert frame["camera"]["width"] == 8
|
||||
assert frame["camera_url"].endswith("/annotation-source/frames/1/camera")
|
||||
assert frame["model_material_included"] is False
|
||||
for forbidden in ("predictions", "prediction_rows", "candidate", "score"):
|
||||
assert forbidden not in serialized
|
||||
assert seed == {
|
||||
"schema_version": "missioncore.l34-annotation-seed/v1",
|
||||
"result_id": result.name,
|
||||
"truth_island_sequence": 1,
|
||||
"source_sha256": _sha256(next(pack_root.rglob("frame.png"))),
|
||||
"objects": [
|
||||
{
|
||||
"object_id": "seed-1-1",
|
||||
"category": "car",
|
||||
"proposed_label": None,
|
||||
"origin": "frozen_candidate_seed",
|
||||
"box_xyxy": [1.0, 1.0, 6.0, 5.0],
|
||||
"occluded": False,
|
||||
"truncated": False,
|
||||
}
|
||||
],
|
||||
"assistance": {
|
||||
"mode": "frozen-candidate-seeded",
|
||||
"independent_truth_eligible": False,
|
||||
},
|
||||
"model_material_included": True,
|
||||
"access": "assisted-annotation-seed-read-only",
|
||||
}
|
||||
assert "score" not in json.dumps(seed)
|
||||
|
||||
|
||||
def test_l34_annotation_sessions_are_revisioned_and_not_truth(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
result, truth_root, pack_root = _write_visual_sources(tmp_path)
|
||||
annotation_root = tmp_path / "annotations"
|
||||
router = build_l34_annotation_router(
|
||||
root_provider=lambda: result.parent,
|
||||
truth_root_provider=lambda: truth_root,
|
||||
evaluation_pack_root_provider=lambda: pack_root,
|
||||
annotation_root_provider=lambda: annotation_root,
|
||||
)
|
||||
create = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/l34/results/{result_id}/annotation-sessions",
|
||||
"POST",
|
||||
)
|
||||
created = create( # type: ignore[operator]
|
||||
result_id=result.name,
|
||||
request=L34AnnotationCreateRequest(idempotency_key="browser-create-1"),
|
||||
)
|
||||
repeated = create( # type: ignore[operator]
|
||||
result_id=result.name,
|
||||
request=L34AnnotationCreateRequest(idempotency_key="browser-create-1"),
|
||||
)
|
||||
assert repeated["session_id"] == created["session_id"]
|
||||
assert created["title"] == "Разметка · LAB L3.4 · Рецензент 1"
|
||||
assert created["revision"] == 0
|
||||
assert created["authority"]["ground_truth"] is False
|
||||
assert created["assistance"] == {
|
||||
"mode": "frozen-candidate-seeded",
|
||||
"independent_truth_eligible": False,
|
||||
}
|
||||
|
||||
save = _endpoint(
|
||||
router,
|
||||
(
|
||||
"/api/v1/laboratory/l34/results/{result_id}"
|
||||
"/annotation-sessions/{session_id}"
|
||||
),
|
||||
"PUT",
|
||||
)
|
||||
request = L34AnnotationSaveRequest(
|
||||
expected_revision=0,
|
||||
idempotency_key="browser-save-1",
|
||||
title="Разметка · LAB L3.4 · Рецензент 1",
|
||||
assistance_mode="frozen-candidate-seeded",
|
||||
frames=[
|
||||
L34AnnotationFrameRequest(
|
||||
truth_island_sequence=1,
|
||||
reviewed=True,
|
||||
hard_negative=False,
|
||||
objects=[
|
||||
L34AnnotationObjectRequest(
|
||||
object_id="box-1",
|
||||
category="car",
|
||||
origin="frozen_candidate_seed",
|
||||
box_xyxy=[1.0, 1.0, 6.0, 5.0],
|
||||
occluded=False,
|
||||
truncated=True,
|
||||
),
|
||||
L34AnnotationObjectRequest(
|
||||
object_id="box-stroller",
|
||||
category="unmapped",
|
||||
proposed_label="Детская коляска",
|
||||
box_xyxy=[0.0, 0.0, 2.0, 2.0],
|
||||
occluded=False,
|
||||
truncated=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
saved = save( # type: ignore[operator]
|
||||
result_id=result.name,
|
||||
session_id=created["session_id"],
|
||||
request=request,
|
||||
)
|
||||
replayed = save( # type: ignore[operator]
|
||||
result_id=result.name,
|
||||
session_id=created["session_id"],
|
||||
request=request,
|
||||
)
|
||||
assert saved["revision"] == 1
|
||||
assert replayed["revision"] == 1
|
||||
assert saved["progress"] == {
|
||||
"reviewed_frame_count": 1,
|
||||
"frame_count": 32,
|
||||
"object_count": 2,
|
||||
"complete": False,
|
||||
}
|
||||
assert saved["frames"][0]["source_sha256"] == _sha256(
|
||||
next(pack_root.rglob("frame.png"))
|
||||
)
|
||||
assert saved["frames"][0]["objects"][0]["proposed_label"] is None
|
||||
assert saved["frames"][0]["objects"][0]["origin"] == "frozen_candidate_seed"
|
||||
assert saved["frames"][0]["objects"][1]["proposed_label"] == "Детская коляска"
|
||||
assert saved["contract_id"] == "l34-assisted-candidate-review/v1"
|
||||
assert saved["blindness"]["model_prelabels_seen"] is True
|
||||
assert "last_save_idempotency_key" not in json.dumps(saved)
|
||||
|
||||
labels = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/l34/results/{result_id}/annotation-labels",
|
||||
)(result_id=result.name) # type: ignore[operator]
|
||||
assert labels["items"] == [
|
||||
{
|
||||
"value": "Детская коляска",
|
||||
"normalization_state": "pending-adjudication",
|
||||
}
|
||||
]
|
||||
|
||||
with pytest.raises(HTTPException) as missing_label:
|
||||
save( # type: ignore[operator]
|
||||
result_id=result.name,
|
||||
session_id=created["session_id"],
|
||||
request=L34AnnotationSaveRequest(
|
||||
expected_revision=1,
|
||||
idempotency_key="browser-save-unmapped-without-label",
|
||||
title=request.title,
|
||||
frames=[
|
||||
L34AnnotationFrameRequest(
|
||||
truth_island_sequence=1,
|
||||
reviewed=True,
|
||||
hard_negative=False,
|
||||
objects=[
|
||||
L34AnnotationObjectRequest(
|
||||
object_id="box-unmapped",
|
||||
category="unmapped",
|
||||
box_xyxy=[0.0, 0.0, 2.0, 2.0],
|
||||
occluded=False,
|
||||
truncated=False,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
assert missing_label.value.status_code == 422
|
||||
|
||||
with pytest.raises(HTTPException) as caught:
|
||||
save( # type: ignore[operator]
|
||||
result_id=result.name,
|
||||
session_id=created["session_id"],
|
||||
request=request.model_copy(
|
||||
update={"idempotency_key": "browser-save-stale"}
|
||||
),
|
||||
)
|
||||
assert caught.value.status_code == 409
|
||||
|
||||
|
||||
def test_advanced_index_includes_l34_by_real_creation_time(tmp_path: Path) -> None:
|
||||
result = _write_result(tmp_path)
|
||||
router = build_advanced_laboratory_router(
|
||||
l34_root_provider=lambda: tmp_path,
|
||||
)
|
||||
index = _endpoint(router, "/api/v1/laboratory/advanced-index")()
|
||||
|
||||
assert index["items"] == [
|
||||
{
|
||||
"work_id": "l34-right-yolox-truth-island-freeze",
|
||||
"result_id": result.name,
|
||||
"created_at_utc": "2026-08-01T00:30:00Z",
|
||||
"access": "read-only",
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,214 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import struct
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
from k1link.web import l34_annotation_api
|
||||
from k1link.web.l34_annotation_api import (
|
||||
L34AnnotationCreateRequest,
|
||||
L34AnnotationFrameRequest,
|
||||
L34AnnotationObjectRequest,
|
||||
L34AnnotationSaveRequest,
|
||||
build_l34d_blind_annotation_router,
|
||||
)
|
||||
|
||||
|
||||
def _endpoint(router: APIRouter, path: str, method: str = "GET") -> object:
|
||||
for route in router.routes:
|
||||
if (
|
||||
isinstance(route, APIRoute)
|
||||
and route.path == path
|
||||
and route.methods is not None
|
||||
and method in route.methods
|
||||
):
|
||||
return route.endpoint
|
||||
raise AssertionError(f"{method} {path} route is missing")
|
||||
|
||||
|
||||
def _png_header(width: int, height: int) -> bytes:
|
||||
return (
|
||||
b"\x89PNG\r\n\x1a\n" + struct.pack(">I", 13) + b"IHDR" + struct.pack(">II", width, height)
|
||||
)
|
||||
|
||||
|
||||
def _router(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> tuple[APIRouter, str]:
|
||||
result_id = f"l34d-cumulative-postprocessing-candidate-{'d' * 64}"
|
||||
image_path = tmp_path / "clean-source.png"
|
||||
image_path.write_bytes(_png_header(800, 600))
|
||||
reference = {
|
||||
"truth_island_sequence": 1,
|
||||
"image_id": 15,
|
||||
"frame_index": 1248,
|
||||
"group_id": "clip-stroller-person",
|
||||
"role": "temporal",
|
||||
"session_seconds": 160.142857292,
|
||||
"sha256": "a" * 64,
|
||||
}
|
||||
case = {
|
||||
"truth_island_sequence": 1,
|
||||
"image_id": 15,
|
||||
"frame_index": 1248,
|
||||
"group_id": "clip-stroller-person",
|
||||
"session_seconds": 160.142857292,
|
||||
"source_image_sha256": "a" * 64,
|
||||
}
|
||||
result = {
|
||||
"result_id": result_id,
|
||||
"cases": [case],
|
||||
"manifest": {
|
||||
"identity": {
|
||||
"l34_freeze": {
|
||||
"result_id": f"l34-right-yolox-truth-island-freeze-{'b' * 64}",
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
l34_result = SimpleNamespace(
|
||||
manifest={
|
||||
"identity": {
|
||||
"truth_island": {
|
||||
"result_id": f"e46-detector-truth-island-{'c' * 64}",
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def resolve(**kwargs: object):
|
||||
assert kwargs["result_id"] == result_id
|
||||
assert kwargs["sequence"] == 1
|
||||
return result, case, l34_result, reference, image_path
|
||||
|
||||
monkeypatch.setattr(l34_annotation_api, "resolve_l34d_case_source", resolve)
|
||||
router = build_l34d_blind_annotation_router(
|
||||
annotation_root_provider=lambda: tmp_path / "blind-annotations",
|
||||
)
|
||||
return router, result_id
|
||||
|
||||
|
||||
def test_l34d_blind_source_physically_excludes_candidate_material(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
router, result_id = _router(tmp_path, monkeypatch)
|
||||
catalog = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/l34d/results/{result_id}/annotation-source",
|
||||
)(result_id=result_id) # type: ignore[operator]
|
||||
frame = _endpoint(
|
||||
router,
|
||||
("/api/v1/laboratory/l34d/results/{result_id}/annotation-source/frames/{sequence}"),
|
||||
)(result_id=result_id, sequence=1) # type: ignore[operator]
|
||||
|
||||
assert catalog["contract"]["contract_id"] == ("l34d-prediction-hidden-review/v1")
|
||||
assert catalog["candidate_predictions_included"] is False
|
||||
assert catalog["prelabels_included"] is False
|
||||
assert frame["candidate_predictions_included"] is False
|
||||
assert frame["prelabels_included"] is False
|
||||
serialized = json.dumps({"catalog": catalog, "frame": frame})
|
||||
for forbidden in ("bbox_xyxy", "objects", "score", "origin"):
|
||||
assert forbidden not in serialized
|
||||
with pytest.raises(AssertionError):
|
||||
_endpoint(
|
||||
router,
|
||||
("/api/v1/laboratory/l34d/results/{result_id}/annotation-seed/frames/{sequence}"),
|
||||
)
|
||||
|
||||
|
||||
def test_l34d_blind_session_rejects_seeded_objects(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
router, result_id = _router(tmp_path, monkeypatch)
|
||||
created = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/l34d/results/{result_id}/annotation-sessions",
|
||||
"POST",
|
||||
)(
|
||||
result_id=result_id,
|
||||
request=L34AnnotationCreateRequest(idempotency_key="blind-create-1"),
|
||||
) # type: ignore[operator]
|
||||
|
||||
assert created["title"] == "Разметка · LAB L3.4D · Рецензент 1"
|
||||
assert created["contract_id"] == "l34d-prediction-hidden-review/v1"
|
||||
assert created["assistance"] == {
|
||||
"mode": "prediction-hidden-manual",
|
||||
"independent_truth_eligible": False,
|
||||
}
|
||||
assert created["blindness"] == {
|
||||
"candidate_identity_seen": True,
|
||||
"model_prelabels_seen": False,
|
||||
"model_predictions_seen": False,
|
||||
"model_scores_seen": False,
|
||||
}
|
||||
|
||||
save = _endpoint(
|
||||
router,
|
||||
("/api/v1/laboratory/l34d/results/{result_id}/annotation-sessions/{session_id}"),
|
||||
"PUT",
|
||||
)
|
||||
saved = save( # type: ignore[operator]
|
||||
result_id=result_id,
|
||||
session_id=created["session_id"],
|
||||
request=L34AnnotationSaveRequest(
|
||||
expected_revision=0,
|
||||
idempotency_key="blind-save-1",
|
||||
title=created["title"],
|
||||
assistance_mode="prediction-hidden-manual",
|
||||
frames=[
|
||||
L34AnnotationFrameRequest(
|
||||
truth_island_sequence=1,
|
||||
reviewed=True,
|
||||
hard_negative=False,
|
||||
objects=[
|
||||
L34AnnotationObjectRequest(
|
||||
object_id="box-stroller",
|
||||
category="unmapped",
|
||||
proposed_label="Детская коляска",
|
||||
origin="manual",
|
||||
box_xyxy=[40.0, 50.0, 240.0, 320.0],
|
||||
occluded=False,
|
||||
truncated=False,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
assert saved["revision"] == 1
|
||||
assert saved["frames"][0]["objects"][0]["origin"] == "manual"
|
||||
|
||||
with pytest.raises(HTTPException) as caught:
|
||||
save( # type: ignore[operator]
|
||||
result_id=result_id,
|
||||
session_id=created["session_id"],
|
||||
request=L34AnnotationSaveRequest(
|
||||
expected_revision=1,
|
||||
idempotency_key="blind-save-seed",
|
||||
title=created["title"],
|
||||
assistance_mode="prediction-hidden-manual",
|
||||
frames=[
|
||||
L34AnnotationFrameRequest(
|
||||
truth_island_sequence=1,
|
||||
reviewed=True,
|
||||
hard_negative=False,
|
||||
objects=[
|
||||
L34AnnotationObjectRequest(
|
||||
object_id="seed-1-1",
|
||||
category="car",
|
||||
origin="frozen_candidate_seed",
|
||||
box_xyxy=[10.0, 10.0, 100.0, 100.0],
|
||||
occluded=False,
|
||||
truncated=False,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
assert caught.value.status_code == 422
|
||||
@@ -0,0 +1,123 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.routing import APIRoute
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
import k1link.web.l34e_self_review_diagnostic_api as l34e_api
|
||||
from k1link.web.l34e_self_review_diagnostic_api import (
|
||||
build_l34e_self_review_diagnostic_router,
|
||||
)
|
||||
|
||||
|
||||
def _endpoint(router: APIRouter, path: str) -> object:
|
||||
for route in router.routes:
|
||||
if (
|
||||
isinstance(route, APIRoute)
|
||||
and route.path == path
|
||||
and route.methods is not None
|
||||
and "GET" in route.methods
|
||||
):
|
||||
return route.endpoint
|
||||
raise AssertionError(f"GET {path} route is missing")
|
||||
|
||||
|
||||
def test_l34e_catalog_projects_read_only_diagnostic(
|
||||
tmp_path: Path,
|
||||
monkeypatch: MonkeyPatch,
|
||||
) -> None:
|
||||
digest = "a" * 64
|
||||
result_id = f"l34e-self-review-diagnostic-{digest}"
|
||||
candidate = tmp_path / result_id
|
||||
candidate.mkdir()
|
||||
(candidate / "manifest.json").write_text("{}", encoding="utf-8")
|
||||
case = {
|
||||
"truth_island_sequence": 1,
|
||||
"image_id": 2,
|
||||
"frame_index": 70,
|
||||
"group_id": "anchor-002",
|
||||
"session_seconds": 10.0,
|
||||
"source_image_sha256": "b" * 64,
|
||||
"strict_summary": {"true_positive": 1},
|
||||
"diagnostic_summary": {
|
||||
"associated_pair_count": 1,
|
||||
"prediction_only": 0,
|
||||
"reference_only": 0,
|
||||
},
|
||||
}
|
||||
result = {
|
||||
"result_id": result_id,
|
||||
"report": {
|
||||
"created_at_utc": "2026-08-03T17:21:06Z",
|
||||
"status": "completed-self-review-diagnostic-not-truth",
|
||||
"profile": {"profile_id": "l34e-self-review-diagnostic/v1"},
|
||||
"method": {
|
||||
"pipeline_id": "ravnoves00-right-yolox-self-review-diagnostic/v1"
|
||||
},
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"metrics": {
|
||||
"reference_quality": "not-metric-grade-self-review"
|
||||
},
|
||||
"case_order": list(range(1, 33)),
|
||||
"decision": {
|
||||
"candidate_accepted": False,
|
||||
"detector_retuning_authorized": False,
|
||||
},
|
||||
"limitations": ["not independent truth"],
|
||||
"authority": {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
},
|
||||
"cases": tuple(
|
||||
{
|
||||
**case,
|
||||
"truth_island_sequence": sequence,
|
||||
}
|
||||
for sequence in range(1, 33)
|
||||
),
|
||||
}
|
||||
|
||||
def fake_read(path: Path) -> dict[str, object]:
|
||||
assert path == candidate
|
||||
return result
|
||||
|
||||
monkeypatch.setattr(l34e_api, "read_l34e_self_review_diagnostic", fake_read)
|
||||
router = build_l34e_self_review_diagnostic_router(
|
||||
diagnostic_root_provider=lambda: tmp_path,
|
||||
)
|
||||
route = _endpoint(router, "/api/v1/laboratory/l34e/results")
|
||||
|
||||
catalog = route(limit=1) # type: ignore[operator]
|
||||
assert catalog["schema_version"] == (
|
||||
"missioncore.l34e-self-review-diagnostic-catalog/v1"
|
||||
)
|
||||
assert catalog["candidate_total"] == 1
|
||||
assert catalog["invalid_total"] == 0
|
||||
item = catalog["items"][0]
|
||||
assert item["result_id"] == result_id
|
||||
assert item["ground_truth"] is False
|
||||
assert item["access"] == "read-only"
|
||||
assert item["metrics"]["reference_quality"] == (
|
||||
"not-metric-grade-self-review"
|
||||
)
|
||||
assert len(item["cases"]) == 32
|
||||
|
||||
|
||||
def test_l34e_catalog_is_empty_when_not_configured() -> None:
|
||||
router = build_l34e_self_review_diagnostic_router()
|
||||
route = _endpoint(router, "/api/v1/laboratory/l34e/results")
|
||||
|
||||
assert route(limit=1) == { # type: ignore[operator]
|
||||
"schema_version": "missioncore.l34e-self-review-diagnostic-catalog/v1",
|
||||
"configured": False,
|
||||
"items": [],
|
||||
"candidate_total": 0,
|
||||
"invalid_total": 0,
|
||||
"access": "read-only",
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
from k1link.web import l34f_adjudication_api
|
||||
from k1link.web.l34f_adjudication_api import (
|
||||
L34FCreateRequest,
|
||||
L34FFrameRequest,
|
||||
L34FFreezeRequest,
|
||||
L34FObjectRequest,
|
||||
L34FSaveRequest,
|
||||
build_l34f_adjudication_router,
|
||||
)
|
||||
|
||||
|
||||
def _endpoint(router: APIRouter, path: str, method: str = "GET") -> object:
|
||||
for route in router.routes:
|
||||
if (
|
||||
isinstance(route, APIRoute)
|
||||
and route.path == path
|
||||
and route.methods is not None
|
||||
and method in route.methods
|
||||
):
|
||||
return route.endpoint
|
||||
raise AssertionError(f"{method} {path} route is missing")
|
||||
|
||||
|
||||
def _png_header(width: int, height: int) -> bytes:
|
||||
return (
|
||||
b"\x89PNG\r\n\x1a\n" + struct.pack(">I", 13) + b"IHDR" + struct.pack(">II", width, height)
|
||||
)
|
||||
|
||||
|
||||
def _fixture(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
diagnostic_id = f"l34e-self-review-diagnostic-{'e' * 64}"
|
||||
diagnostic_root = tmp_path / "diagnostics"
|
||||
result_root = diagnostic_root / diagnostic_id
|
||||
result_root.mkdir(parents=True)
|
||||
image = tmp_path / "frame.png"
|
||||
image.write_bytes(_png_header(800, 600))
|
||||
cases = []
|
||||
for sequence in range(1, 33):
|
||||
cases.append(
|
||||
{
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": sequence,
|
||||
"frame_index": sequence * 10,
|
||||
"group_id": f"group-{sequence}",
|
||||
"session_seconds": float(sequence),
|
||||
"source_image_sha256": "a" * 64,
|
||||
"predictions": [
|
||||
{
|
||||
"prediction_index": 1,
|
||||
"category": "car",
|
||||
"score": 0.91,
|
||||
"box_xyxy": [10.0, 20.0, 100.0, 120.0],
|
||||
"diagnostic_verdict": "localization_disagreement",
|
||||
"associated_object_id": f"self-{sequence:02d}-01",
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"object_id": f"self-{sequence:02d}-01",
|
||||
"category": "car",
|
||||
"proposed_label": None,
|
||||
"box_xyxy": [12.0, 22.0, 102.0, 122.0],
|
||||
"occluded": False,
|
||||
"truncated": False,
|
||||
}
|
||||
],
|
||||
"diagnostic_summary": {"severity_score": 1},
|
||||
}
|
||||
)
|
||||
diagnostic = {
|
||||
"result_id": diagnostic_id,
|
||||
"result_root": result_root,
|
||||
"manifest": {
|
||||
"identity": {"self_review": {"session_id": f"l34-annotation-session-{'b' * 64}"}}
|
||||
},
|
||||
"report": {"source_session_id": "RAVNOVES00"},
|
||||
"cases": tuple(cases),
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
l34f_adjudication_api,
|
||||
"read_l34e_self_review_diagnostic",
|
||||
lambda path: diagnostic,
|
||||
)
|
||||
|
||||
def resolve(**kwargs: object):
|
||||
sequence = int(kwargs["sequence"])
|
||||
return diagnostic, cases[sequence - 1], image
|
||||
|
||||
monkeypatch.setattr(l34f_adjudication_api, "resolve_l34e_case_source", resolve)
|
||||
router = build_l34f_adjudication_router(
|
||||
diagnostic_root_provider=lambda: diagnostic_root,
|
||||
adjudication_root_provider=lambda: tmp_path / "adjudication",
|
||||
result_root_provider=lambda: tmp_path / "frozen",
|
||||
)
|
||||
return router, diagnostic_id, diagnostic
|
||||
|
||||
|
||||
def test_l34f_case_separates_candidate_and_hides_scores(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
router, diagnostic_id, _ = _fixture(tmp_path, monkeypatch)
|
||||
case = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/l34f/diagnostics/{diagnostic_id}/cases/{sequence}",
|
||||
)(diagnostic_id=diagnostic_id, sequence=1) # type: ignore[operator]
|
||||
|
||||
assert case["model_scores_included"] is False
|
||||
assert case["ground_truth"] is False
|
||||
assert case["candidate_objects"][0]["category"] == "car"
|
||||
assert "score" not in case["candidate_objects"][0]
|
||||
assert "references" not in case
|
||||
|
||||
|
||||
def test_l34f_session_is_revisioned_and_freezes_only_at_32_of_32(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
router, diagnostic_id, diagnostic = _fixture(tmp_path, monkeypatch)
|
||||
created = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/l34f/diagnostics/{diagnostic_id}/sessions",
|
||||
"POST",
|
||||
)(
|
||||
diagnostic_id=diagnostic_id,
|
||||
request=L34FCreateRequest(idempotency_key="create-l34f-1"),
|
||||
) # type: ignore[operator]
|
||||
assert created["revision"] == 0
|
||||
assert created["progress"]["reviewed_frame_count"] == 0
|
||||
assert len(created["frames"]) == 32
|
||||
assert created["frames"][0]["objects"][0]["origin"] == "self_review_seed"
|
||||
assert created["authority"]["ground_truth"] is False
|
||||
|
||||
frames = []
|
||||
for source in diagnostic["cases"]:
|
||||
reference = source["references"][0]
|
||||
frames.append(
|
||||
L34FFrameRequest(
|
||||
truth_island_sequence=source["truth_island_sequence"],
|
||||
reviewed=source["truth_island_sequence"] != 32,
|
||||
objects=[
|
||||
L34FObjectRequest(
|
||||
object_id=reference["object_id"],
|
||||
category="car",
|
||||
proposed_label=None,
|
||||
origin="self_review_seed",
|
||||
box_xyxy=reference["box_xyxy"],
|
||||
occluded=False,
|
||||
truncated=False,
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
save = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/l34f/diagnostics/{diagnostic_id}/sessions/{session_id}",
|
||||
"PUT",
|
||||
)
|
||||
saved = save( # type: ignore[operator]
|
||||
diagnostic_id=diagnostic_id,
|
||||
session_id=created["session_id"],
|
||||
request=L34FSaveRequest(
|
||||
expected_revision=0,
|
||||
idempotency_key="save-l34f-1",
|
||||
title=created["title"],
|
||||
frames=frames,
|
||||
),
|
||||
)
|
||||
assert saved["revision"] == 1
|
||||
assert saved["progress"]["reviewed_frame_count"] == 31
|
||||
|
||||
freeze = _endpoint(
|
||||
router,
|
||||
("/api/v1/laboratory/l34f/diagnostics/{diagnostic_id}/sessions/{session_id}/freeze"),
|
||||
"POST",
|
||||
)
|
||||
with pytest.raises(HTTPException) as caught:
|
||||
freeze( # type: ignore[operator]
|
||||
diagnostic_id=diagnostic_id,
|
||||
session_id=created["session_id"],
|
||||
request=L34FFreezeRequest(expected_revision=1),
|
||||
)
|
||||
assert caught.value.status_code == 422
|
||||
|
||||
frames[-1].reviewed = True
|
||||
completed = save( # type: ignore[operator]
|
||||
diagnostic_id=diagnostic_id,
|
||||
session_id=created["session_id"],
|
||||
request=L34FSaveRequest(
|
||||
expected_revision=1,
|
||||
idempotency_key="save-l34f-2",
|
||||
title=created["title"],
|
||||
frames=frames,
|
||||
),
|
||||
)
|
||||
assert completed["progress"]["complete"] is True
|
||||
|
||||
def build(**kwargs: object):
|
||||
assert Path(kwargs["adjudication_session_path"]).is_file()
|
||||
return {
|
||||
"result_id": f"l34f-adjudicated-reference-{'f' * 64}",
|
||||
"manifest": {
|
||||
"identity": {
|
||||
"l34e_diagnostic": {"result_id": diagnostic_id},
|
||||
"adjudication_session": {"session_id": created["session_id"]},
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"created_at_utc": "2026-08-03T00:00:00.000Z",
|
||||
"status": "completed-candidate-visible-adjudication-not-truth",
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"metrics": {"frame_count": 32},
|
||||
"decision": {"candidate_accepted": False},
|
||||
"limitations": [],
|
||||
"authority": completed["authority"],
|
||||
},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
l34f_adjudication_api,
|
||||
"build_l34f_adjudicated_reference",
|
||||
build,
|
||||
)
|
||||
frozen = freeze( # type: ignore[operator]
|
||||
diagnostic_id=diagnostic_id,
|
||||
session_id=created["session_id"],
|
||||
request=L34FFreezeRequest(expected_revision=2),
|
||||
)
|
||||
assert frozen["ground_truth"] is False
|
||||
assert frozen["decision"]["candidate_accepted"] is False
|
||||
Reference in New Issue
Block a user