feat(api): publish immutable perception lab evidence
This commit is contained in:
@@ -56,14 +56,11 @@ from k1link.compute.e40_perception_product_gate import (
|
||||
read_e40_perception_product_gate,
|
||||
)
|
||||
from k1link.web.l3_pointpillars_visual_api import latest_l3_visual_identity
|
||||
from k1link.web.l31_pointpillars_ravnoves_api import latest_l31_identity
|
||||
from k1link.web.l32_pointpillars_camera_review_api import latest_l32_identity
|
||||
from k1link.web.l33_camera_first_detector_review_api import latest_l33_identity
|
||||
|
||||
LABORATORY_ADVANCED_CATALOG_SCHEMA: Final = (
|
||||
"missioncore.laboratory-advanced-catalog/v1"
|
||||
)
|
||||
LABORATORY_ADVANCED_INDEX_SCHEMA: Final = (
|
||||
"missioncore.laboratory-advanced-index/v1"
|
||||
)
|
||||
LABORATORY_ADVANCED_CATALOG_SCHEMA: Final = "missioncore.laboratory-advanced-catalog/v1"
|
||||
LABORATORY_ADVANCED_INDEX_SCHEMA: Final = "missioncore.laboratory-advanced-index/v1"
|
||||
_INDEX_DOCUMENT_MAX_BYTES: Final = 64 * 1024
|
||||
|
||||
_E31_RESULT_ID = re.compile(r"^e31-source-qualification-[a-f0-9]{64}$")
|
||||
@@ -75,6 +72,24 @@ _E37_RESULT_ID = re.compile(r"^e37-ravnoves-acceptance-[a-f0-9]{64}$")
|
||||
_E38_RESULT_ID = re.compile(r"^e38-perception-baseline-[a-f0-9]{64}$")
|
||||
_E39_RESULT_ID = re.compile(r"^e39-perception-refinement-[a-f0-9]{64}$")
|
||||
_E40_RESULT_ID = re.compile(r"^e40-perception-product-gate-[a-f0-9]{64}$")
|
||||
_E46_RESULT_ID = re.compile(r"^e46-detector-truth-island-[a-f0-9]{64}$")
|
||||
_E46A_RESULT_ID = re.compile(r"^e46a-ai-engineering-preannotation-[a-f0-9]{64}$")
|
||||
_E46B_RESULT_ID = re.compile(r"^e46b-temporal-motion-[a-f0-9]{64}$")
|
||||
_E46C_RESULT_ID = re.compile(r"^e46c-full-replay-world-tracks-[a-f0-9]{64}$")
|
||||
_E46D_RESULT_ID = re.compile(r"^e46d-temporal-failure-audit-[a-f0-9]{64}$")
|
||||
_E46E_RESULT_ID = re.compile(r"^e46e-ready-stack-[a-f0-9]{64}$")
|
||||
_E46F_RESULT_ID = re.compile(r"^e46f-dashcam-bakeoff-[a-f0-9]{64}$")
|
||||
_E46G_RESULT_ID = re.compile(r"^e46g-rectified-detector-bakeoff-[a-f0-9]{64}$")
|
||||
_E46H_RESULT_ID = re.compile(r"^e46h-full-rectified-front-replay-[a-f0-9]{64}$")
|
||||
_E46I_RESULT_ID = re.compile(r"^e46i-grounding-dino-full-replay-[a-f0-9]{64}$")
|
||||
_E46J_RESULT_ID = re.compile(r"^e46j-raw-fisheye-realtime-[a-f0-9]{64}$")
|
||||
_L34_RESULT_ID = re.compile(r"^l34-right-yolox-truth-island-freeze-[a-f0-9]{64}$")
|
||||
_L34A_RESULT_ID = re.compile(r"^l34a-assisted-yolox-error-audit-[a-f0-9]{64}$")
|
||||
_L34B_RESULT_ID = re.compile(r"^l34b-nested-box-consolidation-shadow-[a-f0-9]{64}$")
|
||||
_L34C_RESULT_ID = re.compile(r"^l34c-tile-seam-stitch-shadow-[a-f0-9]{64}$")
|
||||
_L34D_RESULT_ID = re.compile(r"^l34d-cumulative-postprocessing-candidate-[a-f0-9]{64}$")
|
||||
_L34E_RESULT_ID = re.compile(r"^l34e-self-review-diagnostic-[a-f0-9]{64}$")
|
||||
_L34F_RESULT_ID = re.compile(r"^l34f-adjudicated-reference-[a-f0-9]{64}$")
|
||||
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
@@ -328,12 +343,8 @@ def _project_e31(result: E31SourceQualification) -> dict[str, object]:
|
||||
"correspondence_count": offset.get("correspondence_count"),
|
||||
"supported_fraction": offset.get("baseline_supported_fraction"),
|
||||
"semantic_self_sample_count": semantic_mask.get("source_sample_count"),
|
||||
"semantic_self_collateral_count": semantic_mask.get(
|
||||
"collateral_item_count"
|
||||
),
|
||||
"exact_geometry_correction_count": len(
|
||||
self_mask.get("exact_correction_item_ids", [])
|
||||
),
|
||||
"semantic_self_collateral_count": semantic_mask.get("collateral_item_count"),
|
||||
"exact_geometry_correction_count": len(self_mask.get("exact_correction_item_ids", [])),
|
||||
"geometry_point_mask_status": geometry_mask.get("status"),
|
||||
},
|
||||
"limitations": copy.deepcopy(result.report.get("limitations")),
|
||||
@@ -375,13 +386,9 @@ def _project_e32(result: E32TrackGeometryReplay) -> dict[str, object]:
|
||||
"semantic_masked": objects.get("semantic_masked"),
|
||||
"geometry_published": objects.get("geometry_published"),
|
||||
"observations_arbitrated": ownership.get("observations_arbitrated"),
|
||||
"overlapping_claims_removed": ownership.get(
|
||||
"overlapping_claims_removed"
|
||||
),
|
||||
"overlapping_claims_removed": ownership.get("overlapping_claims_removed"),
|
||||
"qualified_points_published": point_rows.get("e32_published"),
|
||||
"qualified_points_withheld": point_rows.get(
|
||||
"unqualified_ranges_withheld"
|
||||
),
|
||||
"qualified_points_withheld": point_rows.get("unqualified_ranges_withheld"),
|
||||
"frame_processing_p95_ms": frame_processing.get("p95"),
|
||||
"build_elapsed_ms": runtime.get("build_elapsed_ms"),
|
||||
},
|
||||
@@ -444,8 +451,7 @@ def _project_e33(
|
||||
"source_frames": accounting.get("source_frames"),
|
||||
"source_available_frames": linked_frames.get("source_available"),
|
||||
"source_unavailable_frames": (
|
||||
int(accounting["source_frames"])
|
||||
- int(linked_frames["source_available"])
|
||||
int(accounting["source_frames"]) - int(linked_frames["source_available"])
|
||||
),
|
||||
"delivered_frames": accounting.get("delivered"),
|
||||
"input_superseded": accounting.get("input_superseded"),
|
||||
@@ -460,9 +466,7 @@ def _project_e33(
|
||||
"result_deadline_ms": deadlines.get("result_ms"),
|
||||
"process_rss_p95_mib": process_rss.get("p95"),
|
||||
"process_rss_max_mib": process_rss.get("maximum"),
|
||||
"process_rss_limit_mib": profile_acceptance.get(
|
||||
"maximum_process_rss_mib"
|
||||
),
|
||||
"process_rss_limit_mib": profile_acceptance.get("maximum_process_rss_mib"),
|
||||
"gpu_utilization_p95_percent": gpu_utilization.get("p95"),
|
||||
"gpu_visible": resources.get("gpu_visible"),
|
||||
"work_queue_capacity": work_queue.get("capacity"),
|
||||
@@ -507,15 +511,9 @@ def _project_e34(result: E34TemporalOccupiedReplay) -> dict[str, object]:
|
||||
"configuration": {
|
||||
"voxel_size_m": layer.get("voxel_size_m"),
|
||||
"occupied_ttl_seconds": layer.get("occupied_ttl_seconds"),
|
||||
"maximum_active_components": layer.get(
|
||||
"maximum_active_components"
|
||||
),
|
||||
"maximum_cells_per_component": layer.get(
|
||||
"maximum_cells_per_component"
|
||||
),
|
||||
"geometry_maximum_gap_seconds": association.get(
|
||||
"geometry_maximum_gap_seconds"
|
||||
),
|
||||
"maximum_active_components": layer.get("maximum_active_components"),
|
||||
"maximum_cells_per_component": layer.get("maximum_cells_per_component"),
|
||||
"geometry_maximum_gap_seconds": association.get("geometry_maximum_gap_seconds"),
|
||||
"geometry_maximum_centroid_distance_m": association.get(
|
||||
"geometry_maximum_centroid_distance_m"
|
||||
),
|
||||
@@ -529,44 +527,26 @@ def _project_e34(result: E34TemporalOccupiedReplay) -> dict[str, object]:
|
||||
"created_components": components.get("created"),
|
||||
"camera_created_components": components.get("camera_created"),
|
||||
"geometry_created_components": components.get("geometry_created"),
|
||||
"geometry_reassociated_components": components.get(
|
||||
"geometry_reassociated_components"
|
||||
),
|
||||
"exact_camera_associations": components.get(
|
||||
"exact_camera_associations"
|
||||
),
|
||||
"geometry_spatial_reassociations": components.get(
|
||||
"geometry_spatial_reassociations"
|
||||
),
|
||||
"geometry_reassociated_components": components.get("geometry_reassociated_components"),
|
||||
"exact_camera_associations": components.get("exact_camera_associations"),
|
||||
"geometry_spatial_reassociations": components.get("geometry_spatial_reassociations"),
|
||||
"held_publications": components.get("held_publications"),
|
||||
"expired_components": components.get("expired"),
|
||||
"peak_active_components": components.get("peak_active"),
|
||||
"maximum_observed_span_seconds": components.get(
|
||||
"maximum_observed_span_seconds"
|
||||
),
|
||||
"maximum_observed_span_seconds": components.get("maximum_observed_span_seconds"),
|
||||
"continuity_fraction": components.get("continuity_fraction"),
|
||||
"geometry_only_reassociation_fraction": components.get(
|
||||
"geometry_only_reassociation_fraction"
|
||||
),
|
||||
"e32_current_point_rows": occupancy.get(
|
||||
"e32_current_point_rows"
|
||||
),
|
||||
"e34_consumed_current_point_rows": occupancy.get(
|
||||
"e34_consumed_current_point_rows"
|
||||
),
|
||||
"e32_current_point_rows": occupancy.get("e32_current_point_rows"),
|
||||
"e34_consumed_current_point_rows": occupancy.get("e34_consumed_current_point_rows"),
|
||||
"current_cell_rows": occupancy.get("current_cell_rows"),
|
||||
"held_cell_rows": occupancy.get("held_cell_rows"),
|
||||
"stored_cell_rows": occupancy.get("stored_cell_rows"),
|
||||
"free_cell_rows": occupancy.get("free_cell_rows"),
|
||||
"peak_cells_per_component": occupancy.get(
|
||||
"peak_cells_per_component"
|
||||
),
|
||||
"maximum_held_age_seconds": aging.get(
|
||||
"maximum_held_age_seconds"
|
||||
),
|
||||
"maximum_expiry_delay_seconds": aging.get(
|
||||
"maximum_expiry_delay_seconds"
|
||||
),
|
||||
"peak_cells_per_component": occupancy.get("peak_cells_per_component"),
|
||||
"maximum_held_age_seconds": aging.get("maximum_held_age_seconds"),
|
||||
"maximum_expiry_delay_seconds": aging.get("maximum_expiry_delay_seconds"),
|
||||
"maximum_replay_materialization_delay_seconds": aging.get(
|
||||
"maximum_replay_materialization_delay_seconds"
|
||||
),
|
||||
@@ -611,19 +591,13 @@ def _project_e35(result: E35DegradationReplay) -> dict[str, object]:
|
||||
"pipeline_id": identity.get("pipeline"),
|
||||
"coordinate_frame": identity.get("coordinate_frame"),
|
||||
"configuration": {
|
||||
"maximum_recovery_seconds": acceptance_profile.get(
|
||||
"maximum_recovery_seconds"
|
||||
),
|
||||
"scenario_count": acceptance_profile.get(
|
||||
"expected_scenario_count"
|
||||
),
|
||||
"maximum_recovery_seconds": acceptance_profile.get("maximum_recovery_seconds"),
|
||||
"scenario_count": acceptance_profile.get("expected_scenario_count"),
|
||||
},
|
||||
"metrics": {
|
||||
"source_frames": accounting.get("source_frames"),
|
||||
"variant_count": accounting.get("variant_count"),
|
||||
"variant_frame_outcomes": accounting.get(
|
||||
"variant_frame_outcomes"
|
||||
),
|
||||
"variant_frame_outcomes": accounting.get("variant_frame_outcomes"),
|
||||
"injection_records": accounting.get("injection_records"),
|
||||
"variant_frame_processing_p95_ms": processing.get("p95"),
|
||||
"build_elapsed_ms": runtime.get("build_elapsed_ms"),
|
||||
@@ -669,18 +643,12 @@ def _project_e37(result: E37AcceptanceContract) -> dict[str, object]:
|
||||
"accounting_fraction": metrics.get("accounting_fraction"),
|
||||
"false_free_claims": metrics.get("false_free_claims"),
|
||||
},
|
||||
"dimension_distributions": copy.deepcopy(
|
||||
result.contract.get("dimension_distributions")
|
||||
),
|
||||
"severity_distribution": copy.deepcopy(
|
||||
result.contract.get("severity_distribution")
|
||||
),
|
||||
"dimension_distributions": copy.deepcopy(result.contract.get("dimension_distributions")),
|
||||
"severity_distribution": copy.deepcopy(result.contract.get("severity_distribution")),
|
||||
"label_provenance": {
|
||||
"engineering_items": provenance.get("engineering_items"),
|
||||
"human_exception_items": provenance.get("human_exception_items"),
|
||||
"independent_ground_truth": provenance.get(
|
||||
"independent_ground_truth"
|
||||
),
|
||||
"independent_ground_truth": provenance.get("independent_ground_truth"),
|
||||
},
|
||||
"split": copy.deepcopy(result.contract.get("split")),
|
||||
"targets": copy.deepcopy(result.contract.get("targets")),
|
||||
@@ -827,8 +795,28 @@ def build_advanced_laboratory_router(
|
||||
e38_root_provider: RootProvider = lambda: None,
|
||||
e39_root_provider: RootProvider = lambda: None,
|
||||
e40_root_provider: RootProvider = lambda: None,
|
||||
e46_root_provider: RootProvider = lambda: None,
|
||||
e46a_root_provider: RootProvider = lambda: None,
|
||||
e46b_root_provider: RootProvider = lambda: None,
|
||||
e46c_root_provider: RootProvider = lambda: None,
|
||||
e46d_root_provider: RootProvider = lambda: None,
|
||||
e46e_root_provider: RootProvider = lambda: None,
|
||||
e46f_root_provider: RootProvider = lambda: None,
|
||||
e46g_root_provider: RootProvider = lambda: None,
|
||||
e46h_root_provider: RootProvider = lambda: None,
|
||||
e46i_root_provider: RootProvider = lambda: None,
|
||||
e46j_root_provider: RootProvider = lambda: None,
|
||||
l34_root_provider: RootProvider = lambda: None,
|
||||
l34a_root_provider: RootProvider = lambda: None,
|
||||
l34b_root_provider: RootProvider = lambda: None,
|
||||
l34c_root_provider: RootProvider = lambda: None,
|
||||
l34d_root_provider: RootProvider = lambda: None,
|
||||
l34e_root_provider: RootProvider = lambda: None,
|
||||
l34f_root_provider: RootProvider = lambda: None,
|
||||
l3_visual_root_provider: RootProvider = lambda: None,
|
||||
l31_ravnoves_root_provider: RootProvider = lambda: None,
|
||||
l32_camera_review_root_provider: RootProvider = lambda: None,
|
||||
l33_camera_first_review_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
|
||||
|
||||
@@ -899,23 +887,161 @@ def build_advanced_laboratory_router(
|
||||
"manifest.json",
|
||||
"missioncore.e40-perception-product-gate/v1",
|
||||
),
|
||||
(
|
||||
"e46-detector-truth-island",
|
||||
e46_root_provider,
|
||||
_E46_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46-detector-truth-island/v1",
|
||||
),
|
||||
(
|
||||
"e46a-ai-engineering-preannotation",
|
||||
e46a_root_provider,
|
||||
_E46A_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46a-ai-engineering-preannotation/v1",
|
||||
),
|
||||
(
|
||||
"e46b-temporal-motion",
|
||||
e46b_root_provider,
|
||||
_E46B_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46b-temporal-motion/v1",
|
||||
),
|
||||
(
|
||||
"e46c-full-replay-world-tracks",
|
||||
e46c_root_provider,
|
||||
_E46C_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46c-full-replay-world-tracks/v1",
|
||||
),
|
||||
(
|
||||
"e46d-temporal-failure-audit",
|
||||
e46d_root_provider,
|
||||
_E46D_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46d-temporal-failure-audit/v1",
|
||||
),
|
||||
(
|
||||
"e46e-ready-stack",
|
||||
e46e_root_provider,
|
||||
_E46E_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46e-ready-stack-result/v1",
|
||||
),
|
||||
(
|
||||
"e46f-dashcam-bakeoff",
|
||||
e46f_root_provider,
|
||||
_E46F_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46f-dashcam-bakeoff-result/v1",
|
||||
),
|
||||
(
|
||||
"e46g-rectified-detector-bakeoff",
|
||||
e46g_root_provider,
|
||||
_E46G_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46g-rectified-detector-bakeoff-result/v1",
|
||||
),
|
||||
(
|
||||
"e46h-full-rectified-front-replay",
|
||||
e46h_root_provider,
|
||||
_E46H_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46h-full-rectified-front-replay-result/v1",
|
||||
),
|
||||
(
|
||||
"e46i-grounding-dino-full-replay",
|
||||
e46i_root_provider,
|
||||
_E46I_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46i-grounding-dino-full-replay-result/v1",
|
||||
),
|
||||
(
|
||||
"e46j-raw-fisheye-realtime",
|
||||
e46j_root_provider,
|
||||
_E46J_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.e46j-raw-fisheye-realtime-result/v1",
|
||||
),
|
||||
(
|
||||
"l34-right-yolox-truth-island-freeze",
|
||||
l34_root_provider,
|
||||
_L34_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.l34-right-yolox-truth-island-freeze/v1",
|
||||
),
|
||||
(
|
||||
"l34a-assisted-yolox-error-audit",
|
||||
l34a_root_provider,
|
||||
_L34A_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.l34a-assisted-yolox-error-audit/v1",
|
||||
),
|
||||
(
|
||||
"l34b-nested-box-consolidation-shadow",
|
||||
l34b_root_provider,
|
||||
_L34B_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.l34b-nested-box-consolidation-shadow/v1",
|
||||
),
|
||||
(
|
||||
"l34c-tile-seam-stitch-shadow",
|
||||
l34c_root_provider,
|
||||
_L34C_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.l34c-tile-seam-stitch-shadow/v1",
|
||||
),
|
||||
(
|
||||
"l34d-cumulative-postprocessing-candidate",
|
||||
l34d_root_provider,
|
||||
_L34D_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.l34d-cumulative-postprocessing-candidate/v1",
|
||||
),
|
||||
(
|
||||
"l34e-self-review-diagnostic",
|
||||
l34e_root_provider,
|
||||
_L34E_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.l34e-self-review-diagnostic/v1",
|
||||
),
|
||||
(
|
||||
"l34f-adjudicated-reference",
|
||||
l34f_root_provider,
|
||||
_L34F_RESULT_ID,
|
||||
"manifest.json",
|
||||
"missioncore.l34f-adjudicated-reference/v1",
|
||||
),
|
||||
)
|
||||
)
|
||||
items = result["items"]
|
||||
if not isinstance(items, list):
|
||||
raise RuntimeError("advanced LAB index items are invalid")
|
||||
l3_identity = latest_l3_visual_identity(l3_visual_root_provider)
|
||||
if l3_identity is not None:
|
||||
result["items"].append(
|
||||
items.append(
|
||||
{
|
||||
"work_id": "l3-pointpillars-visual-audit",
|
||||
**l3_identity,
|
||||
"access": "read-only",
|
||||
}
|
||||
)
|
||||
l31_identity = latest_l31_identity(l31_ravnoves_root_provider)
|
||||
if l31_identity is not None:
|
||||
result["items"].append(
|
||||
l32_identity = latest_l32_identity(l32_camera_review_root_provider)
|
||||
if l32_identity is not None:
|
||||
items.append(
|
||||
{
|
||||
"work_id": "l31-pointpillars-ravnoves",
|
||||
**l31_identity,
|
||||
"work_id": "l32-pointpillars-camera-review",
|
||||
**l32_identity,
|
||||
"access": "read-only",
|
||||
}
|
||||
)
|
||||
l33_identity = latest_l33_identity(l33_camera_first_review_root_provider)
|
||||
if l33_identity is not None:
|
||||
items.append(
|
||||
{
|
||||
"work_id": "l33-camera-first-detector-review",
|
||||
**l33_identity,
|
||||
"access": "read-only",
|
||||
}
|
||||
)
|
||||
@@ -972,10 +1098,7 @@ def build_advanced_laboratory_router(
|
||||
str(candidate.resolve()),
|
||||
_result_signature(candidate),
|
||||
)
|
||||
if (
|
||||
result.report.get("status")
|
||||
!= "accepted-diagnostic-track-geometry-replay"
|
||||
):
|
||||
if result.report.get("status") != "accepted-diagnostic-track-geometry-replay":
|
||||
raise ValueError("E32 result is not accepted")
|
||||
if len(items) < limit:
|
||||
items.append(_project_e32(result))
|
||||
|
||||
+544
-172
@@ -41,6 +41,31 @@ from k1link.web.e30_engineering_api import build_e30_engineering_router
|
||||
from k1link.web.e30_human_review_api import build_e30_human_review_router
|
||||
from k1link.web.e30_review_api import build_e30_review_router
|
||||
from k1link.web.e40_case_review_api import build_e40_case_review_router
|
||||
from k1link.web.e46_blind_review_api import build_e46_blind_review_router
|
||||
from k1link.web.e46a_ai_engineering_preannotation_api import (
|
||||
build_e46a_ai_engineering_preannotation_router,
|
||||
)
|
||||
from k1link.web.e46b_temporal_motion_api import build_e46b_temporal_motion_router
|
||||
from k1link.web.e46c_full_replay_world_tracks_api import (
|
||||
build_e46c_full_replay_world_tracks_router,
|
||||
)
|
||||
from k1link.web.e46d_temporal_failure_audit_api import (
|
||||
build_e46d_temporal_failure_audit_router,
|
||||
)
|
||||
from k1link.web.e46e_ready_stack_api import build_e46e_ready_stack_router
|
||||
from k1link.web.e46f_dashcam_bakeoff_api import build_e46f_dashcam_bakeoff_router
|
||||
from k1link.web.e46g_rectified_detector_bakeoff_api import (
|
||||
build_e46g_rectified_detector_bakeoff_router,
|
||||
)
|
||||
from k1link.web.e46h_full_rectified_front_replay_api import (
|
||||
build_e46h_full_rectified_front_replay_router,
|
||||
)
|
||||
from k1link.web.e46i_grounding_dino_full_replay_api import (
|
||||
build_e46i_grounding_dino_full_replay_router,
|
||||
)
|
||||
from k1link.web.e46j_raw_fisheye_realtime_api import (
|
||||
build_e46j_raw_fisheye_realtime_router,
|
||||
)
|
||||
from k1link.web.environment_api import build_environment_router
|
||||
from k1link.web.l3_pointpillars_visual_api import (
|
||||
build_l3_pointpillars_visual_router,
|
||||
@@ -48,6 +73,35 @@ from k1link.web.l3_pointpillars_visual_api import (
|
||||
from k1link.web.l31_pointpillars_ravnoves_api import (
|
||||
build_l31_pointpillars_ravnoves_router,
|
||||
)
|
||||
from k1link.web.l32_pointpillars_camera_review_api import (
|
||||
build_l32_pointpillars_camera_review_router,
|
||||
)
|
||||
from k1link.web.l33_camera_first_detector_review_api import (
|
||||
build_l33_camera_first_detector_review_router,
|
||||
)
|
||||
from k1link.web.l34_annotation_api import (
|
||||
build_l34_annotation_router,
|
||||
build_l34d_blind_annotation_router,
|
||||
)
|
||||
from k1link.web.l34_right_yolox_truth_island_api import (
|
||||
build_l34_right_yolox_truth_island_freeze_router,
|
||||
)
|
||||
from k1link.web.l34a_assisted_yolox_error_audit_api import (
|
||||
build_l34a_assisted_yolox_error_audit_router,
|
||||
)
|
||||
from k1link.web.l34b_nested_box_consolidation_api import (
|
||||
build_l34b_nested_box_consolidation_router,
|
||||
)
|
||||
from k1link.web.l34c_tile_seam_stitch_api import (
|
||||
build_l34c_tile_seam_stitch_router,
|
||||
)
|
||||
from k1link.web.l34d_cumulative_postprocessing_api import (
|
||||
build_l34d_cumulative_postprocessing_router,
|
||||
)
|
||||
from k1link.web.l34e_self_review_diagnostic_api import (
|
||||
build_l34e_self_review_diagnostic_router,
|
||||
)
|
||||
from k1link.web.l34f_adjudication_api import build_l34f_adjudication_router
|
||||
from k1link.web.laboratory_api import build_laboratory_router
|
||||
from k1link.web.lidar_api import build_lidar_router
|
||||
from k1link.web.lidar_local_surface_service import K1LocalSurfaceReadService
|
||||
@@ -139,18 +193,10 @@ session_integrated_perception_store = (
|
||||
IntegratedPerceptionOverlayStore(
|
||||
jobs_root=REPOSITORY_ROOT / ".runtime" / "compute-jobs",
|
||||
results_root=(
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e10"
|
||||
/ "worker-results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e10" / "worker-results"
|
||||
),
|
||||
lidar_packs_root=(
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e10"
|
||||
/ "lidar-packs"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e10" / "lidar-packs"
|
||||
),
|
||||
cache_root=session_store.data_dir / "integrated-perception-overlays",
|
||||
ffmpeg_path=_ffmpeg,
|
||||
@@ -304,9 +350,7 @@ async def _recording_preparation_reconciler() -> None:
|
||||
async def app_lifespan(_: FastAPI) -> AsyncIterator[None]:
|
||||
reconciler: asyncio.Task[None] | None = None
|
||||
try:
|
||||
configure_scanner_diagnostics(
|
||||
REPOSITORY_ROOT / ".runtime" / "mission-core" / "logs"
|
||||
)
|
||||
configure_scanner_diagnostics(REPOSITORY_ROOT / ".runtime" / "mission-core" / "logs")
|
||||
session_recording_preparation_manager.start()
|
||||
# Recovery is intentionally a one-shot startup phase. The archive
|
||||
# helper owns a cross-process lease, while ordinary catalog requests
|
||||
@@ -441,9 +485,7 @@ app.include_router(
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_environment_router(
|
||||
root_provider=lambda: session_store.data_dir / "ui-environment"
|
||||
)
|
||||
build_environment_router(root_provider=lambda: session_store.data_dir / "ui-environment")
|
||||
)
|
||||
app.include_router(build_map_router(map_gateway_proxy))
|
||||
app.include_router(
|
||||
@@ -453,25 +495,18 @@ app.include_router(
|
||||
)
|
||||
app.include_router(
|
||||
build_polygon_router(
|
||||
root_provider=lambda: configured_polygon_runs_root()
|
||||
or REPOSITORY_ROOT / ".runtime" / "polygon-runs"
|
||||
root_provider=lambda: (
|
||||
configured_polygon_runs_root() or REPOSITORY_ROOT / ".runtime" / "polygon-runs"
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_lidar_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "lidar-replay-v2"
|
||||
/ "packs"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "lidar-replay-v2" / "packs"
|
||||
),
|
||||
ground_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "lidar-ground-v1"
|
||||
/ "benchmarks"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "lidar-ground-v1" / "benchmarks"
|
||||
),
|
||||
field_review_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
@@ -481,18 +516,10 @@ app.include_router(
|
||||
/ "reviews"
|
||||
),
|
||||
local_surface_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "k1-local-surface-v1"
|
||||
/ "models"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "k1-local-surface-v1" / "models"
|
||||
),
|
||||
e10_source_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e10"
|
||||
/ "lidar-packs"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e10" / "lidar-packs"
|
||||
),
|
||||
dataset_admission_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "dataset-gateway" / "admission.json"
|
||||
@@ -518,221 +545,574 @@ app.include_router(
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e29" / "results"
|
||||
),
|
||||
local_surface_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "k1-local-surface-v1"
|
||||
/ "models"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "k1-local-surface-v1" / "models"
|
||||
),
|
||||
source_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e10"
|
||||
/ "lidar-packs"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e10" / "lidar-packs"
|
||||
),
|
||||
source_result_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e10"
|
||||
/ "worker-results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e10" / "worker-results"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_advanced_laboratory_router(
|
||||
e31_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e31"
|
||||
/ "source-qualifications"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e31" / "source-qualifications"
|
||||
),
|
||||
e32_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e32"
|
||||
/ "results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e32" / "results"
|
||||
),
|
||||
e33_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e33"
|
||||
/ "results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e33" / "results"
|
||||
),
|
||||
e34_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e34"
|
||||
/ "results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e34" / "results"
|
||||
),
|
||||
e35_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e35"
|
||||
/ "results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e35" / "results"
|
||||
),
|
||||
e37_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e37"
|
||||
/ "results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e37" / "results"
|
||||
),
|
||||
e38_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e38"
|
||||
/ "results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e38" / "results"
|
||||
),
|
||||
e39_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e39"
|
||||
/ "results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e39" / "results"
|
||||
),
|
||||
e40_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e40" / "results"
|
||||
),
|
||||
e46_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
e46a_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e40"
|
||||
/ "results"
|
||||
/ "e46a"
|
||||
/ "ai-engineering-preannotations"
|
||||
),
|
||||
e46b_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46b" / "temporal-motion"
|
||||
),
|
||||
e46c_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46c"
|
||||
/ "full-replay-world-tracks"
|
||||
),
|
||||
e46d_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46d"
|
||||
/ "temporal-failure-audits"
|
||||
),
|
||||
e46e_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46e"
|
||||
/ "ready-stack-results"
|
||||
),
|
||||
e46f_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46f"
|
||||
/ "dashcam-bakeoff-results"
|
||||
),
|
||||
e46g_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46g" / "results"
|
||||
),
|
||||
e46h_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46h" / "results"
|
||||
),
|
||||
e46i_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46i" / "results"
|
||||
),
|
||||
e46j_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46j" / "results"
|
||||
),
|
||||
l34_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "right-yolox-truth-island-freeze"
|
||||
),
|
||||
l34a_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "assisted-yolox-error-audits"
|
||||
),
|
||||
l34b_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "nested-box-consolidation-shadows"
|
||||
),
|
||||
l34c_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "tile-seam-stitch-shadows"
|
||||
),
|
||||
l34d_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "cumulative-postprocessing-candidates"
|
||||
),
|
||||
l34e_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "self-review-diagnostics"
|
||||
),
|
||||
l34f_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "adjudicated-references"
|
||||
),
|
||||
l3_visual_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "visual-audits"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "visual-audits"
|
||||
),
|
||||
l31_ravnoves_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "pointpillars-ravnoves"
|
||||
),
|
||||
l32_camera_review_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "pointpillars-ravnoves"
|
||||
/ "pointpillars-camera-review"
|
||||
),
|
||||
l33_camera_first_review_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "camera-first-detector-review"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l3_pointpillars_visual_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "visual-audits"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "visual-audits"
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l31_pointpillars_ravnoves_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "pointpillars-ravnoves"
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l32_pointpillars_camera_review_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "pointpillars-ravnoves"
|
||||
/ "pointpillars-camera-review"
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l33_camera_first_detector_review_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "camera-first-detector-review"
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l34_right_yolox_truth_island_freeze_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "right-yolox-truth-island-freeze"
|
||||
),
|
||||
truth_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
evaluation_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e2" / "evaluation-packs"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46_blind_review_router(
|
||||
truth_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
evaluation_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e2" / "evaluation-packs"
|
||||
),
|
||||
annotation_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "laboratory-annotations" / "e46-blind"
|
||||
),
|
||||
submission_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e48" / "review-submissions"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46a_ai_engineering_preannotation_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46a"
|
||||
/ "ai-engineering-preannotations"
|
||||
),
|
||||
annotation_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "laboratory-annotations" / "e46a-correction"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46b_temporal_motion_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46b" / "temporal-motion"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46c_full_replay_world_tracks_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46c"
|
||||
/ "full-replay-world-tracks"
|
||||
),
|
||||
e26_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e10" / "worker-results"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46d_temporal_failure_audit_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46d"
|
||||
/ "temporal-failure-audits"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46e_ready_stack_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46e"
|
||||
/ "ready-stack-results"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46f_dashcam_bakeoff_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46f"
|
||||
/ "dashcam-bakeoff-results"
|
||||
),
|
||||
e46e_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46e"
|
||||
/ "ready-stack-results"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46g_rectified_detector_bakeoff_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46g" / "results"
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46h_full_rectified_front_replay_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46h" / "results"
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46i_grounding_dino_full_replay_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46i" / "results"
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46j_raw_fisheye_realtime_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46j" / "results"
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l34_annotation_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "right-yolox-truth-island-freeze"
|
||||
),
|
||||
truth_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
evaluation_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e2" / "evaluation-packs"
|
||||
),
|
||||
annotation_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "laboratory-annotations" / "l34"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l34a_assisted_yolox_error_audit_router(
|
||||
audit_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "assisted-yolox-error-audits"
|
||||
),
|
||||
l34_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "right-yolox-truth-island-freeze"
|
||||
),
|
||||
truth_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
evaluation_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e2" / "evaluation-packs"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l34b_nested_box_consolidation_router(
|
||||
shadow_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "nested-box-consolidation-shadows"
|
||||
),
|
||||
l34_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "right-yolox-truth-island-freeze"
|
||||
),
|
||||
truth_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
evaluation_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e2" / "evaluation-packs"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l34c_tile_seam_stitch_router(
|
||||
shadow_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "tile-seam-stitch-shadows"
|
||||
),
|
||||
l34_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "right-yolox-truth-island-freeze"
|
||||
),
|
||||
truth_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
evaluation_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e2" / "evaluation-packs"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l34d_cumulative_postprocessing_router(
|
||||
candidate_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "cumulative-postprocessing-candidates"
|
||||
),
|
||||
l34_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "right-yolox-truth-island-freeze"
|
||||
),
|
||||
truth_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
evaluation_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e2" / "evaluation-packs"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l34d_blind_annotation_router(
|
||||
candidate_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "cumulative-postprocessing-candidates"
|
||||
),
|
||||
l34_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "right-yolox-truth-island-freeze"
|
||||
),
|
||||
truth_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
evaluation_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e2" / "evaluation-packs"
|
||||
),
|
||||
annotation_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "laboratory-annotations" / "l34d-blind"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l34e_self_review_diagnostic_router(
|
||||
diagnostic_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "self-review-diagnostics"
|
||||
),
|
||||
candidate_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "cumulative-postprocessing-candidates"
|
||||
),
|
||||
l34_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "right-yolox-truth-island-freeze"
|
||||
),
|
||||
truth_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
evaluation_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e2" / "evaluation-packs"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l34f_adjudication_router(
|
||||
diagnostic_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "self-review-diagnostics"
|
||||
),
|
||||
candidate_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "cumulative-postprocessing-candidates"
|
||||
),
|
||||
l34_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "right-yolox-truth-island-freeze"
|
||||
),
|
||||
truth_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46" / "results"
|
||||
),
|
||||
evaluation_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e2" / "evaluation-packs"
|
||||
),
|
||||
adjudication_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "laboratory-annotations" / "l34f-adjudication"
|
||||
),
|
||||
result_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "l3" / "adjudicated-references"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e30_review_router(
|
||||
materialization_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e30"
|
||||
/ "materializations"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e30" / "materializations"
|
||||
),
|
||||
review_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e30"
|
||||
/ "review-packs"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e30" / "review-packs"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e40_case_review_router(
|
||||
e40_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e40"
|
||||
/ "results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e40" / "results"
|
||||
),
|
||||
operator_review_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e40"
|
||||
/ "operator-reviews"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e40" / "operator-reviews"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e30_engineering_router(
|
||||
generation_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e30"
|
||||
/ "engineering-generations"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e30" / "engineering-generations"
|
||||
),
|
||||
materialization_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e30"
|
||||
/ "materializations"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e30" / "materializations"
|
||||
),
|
||||
review_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e30"
|
||||
/ "review-packs"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e30" / "review-packs"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e30_human_review_router(
|
||||
materialization_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e30"
|
||||
/ "materializations"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e30" / "materializations"
|
||||
),
|
||||
review_pack_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e30"
|
||||
/ "review-packs"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e30" / "review-packs"
|
||||
),
|
||||
engineering_generation_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e30"
|
||||
/ "engineering-generations"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e30" / "engineering-generations"
|
||||
),
|
||||
draft_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e30"
|
||||
/ "human-review-drafts"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e30" / "human-review-drafts"
|
||||
),
|
||||
generation_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
@@ -752,18 +1132,10 @@ app.include_router(
|
||||
build_artifact_health_router(
|
||||
runtime_root_provider=lambda: REPOSITORY_ROOT / ".runtime",
|
||||
e44_results_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e44"
|
||||
/ "results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e44" / "results"
|
||||
),
|
||||
e50_results_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e50"
|
||||
/ "results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e50" / "results"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,610 @@
|
||||
"""Source-only E46 review collection for two independent human slots."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import secrets
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Annotated, Any, Final, Literal
|
||||
|
||||
from fastapi import APIRouter, Header, HTTPException, Query
|
||||
from fastapi import Path as ApiPath
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from k1link.artifacts import write_json_atomic
|
||||
from k1link.compute.e46_detector_truth_island import (
|
||||
E46_REFERENCES_NAME,
|
||||
E46DetectorTruthIsland,
|
||||
E46DetectorTruthIslandError,
|
||||
read_e46_detector_truth_island,
|
||||
)
|
||||
from k1link.compute.e46_lab_review_submission import (
|
||||
E46LabReviewSubmissionError,
|
||||
build_e46_lab_review_submission,
|
||||
read_e46_lab_review_submission,
|
||||
)
|
||||
from k1link.web.l34_annotation_api import (
|
||||
L34_ANNOTATION_LABEL_CATALOG_SCHEMA,
|
||||
L34_ANNOTATION_SESSION_CATALOG_SCHEMA,
|
||||
L34_ANNOTATION_SOURCE_CATALOG_SCHEMA,
|
||||
L34_ANNOTATION_SOURCE_SCHEMA,
|
||||
L34AnnotationCreateRequest,
|
||||
L34AnnotationSaveRequest,
|
||||
_L34AnnotationSessionStore,
|
||||
_normalize_object,
|
||||
_png_dimensions,
|
||||
_source_projection,
|
||||
)
|
||||
|
||||
_TRUTH_ID = re.compile(r"^e46-detector-truth-island-[a-f0-9]{64}$")
|
||||
_SESSION_ID = re.compile(r"^l34-annotation-session-[a-f0-9]{64}$")
|
||||
_SUBMISSION_ID = re.compile(r"^e46-lab-review-submission-[a-f0-9]{64}$")
|
||||
_PACK_ID = re.compile(r"^evaluation-pack-[a-f0-9]{64}$")
|
||||
_REVIEWER_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{1,63}$")
|
||||
_TARGET_CLASSES: Final = (
|
||||
"person",
|
||||
"bicycle",
|
||||
"motorcycle",
|
||||
"car",
|
||||
"heavy_vehicle",
|
||||
"static_obstacle",
|
||||
"animal",
|
||||
)
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
class E46ReviewFreezeRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
expected_revision: int = Field(ge=1)
|
||||
reviewer_id: str = Field(min_length=2, max_length=64)
|
||||
independent_attestation: Literal[True]
|
||||
candidate_identity_not_seen: Literal[True]
|
||||
model_material_not_seen: Literal[True]
|
||||
|
||||
|
||||
def build_e46_blind_review_router(
|
||||
*,
|
||||
truth_root_provider: RootProvider = lambda: None,
|
||||
evaluation_pack_root_provider: RootProvider = lambda: None,
|
||||
annotation_root_provider: RootProvider = lambda: None,
|
||||
submission_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46", tags=["laboratory"])
|
||||
store = _L34AnnotationSessionStore(
|
||||
annotation_root_provider,
|
||||
result_id_pattern=_TRUTH_ID,
|
||||
lab_label="E46",
|
||||
initial_assistance_mode="prediction-free-manual",
|
||||
allowed_assistance_modes=frozenset({"prediction-free-manual"}),
|
||||
)
|
||||
|
||||
def require_capability(
|
||||
result_id: str,
|
||||
session_id: str,
|
||||
capability: str | None,
|
||||
) -> None:
|
||||
sidecar = store.session_path(result_id, session_id).with_suffix(
|
||||
".capability.json"
|
||||
)
|
||||
try:
|
||||
document = json.loads(sidecar.read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError):
|
||||
raise HTTPException(status_code=403, detail="E46 reviewer slot недоступен") from None
|
||||
expected = document.get("capability") if isinstance(document, dict) else None
|
||||
if (
|
||||
not isinstance(capability, str)
|
||||
or not isinstance(expected, str)
|
||||
or not secrets.compare_digest(capability, expected)
|
||||
):
|
||||
raise HTTPException(status_code=403, detail="E46 reviewer slot недоступен")
|
||||
|
||||
def truth(result_id: str) -> tuple[E46DetectorTruthIsland, tuple[dict[str, Any], ...]]:
|
||||
if _TRUTH_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46 truth island не найден")
|
||||
root = _configured_root(truth_root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46 truth island не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46 truth island не найден")
|
||||
try:
|
||||
result = read_e46_detector_truth_island(path)
|
||||
references = tuple(_read_jsonl(result.result_root / E46_REFERENCES_NAME))
|
||||
except (E46DetectorTruthIslandError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46 truth island не найден") from None
|
||||
return result, references
|
||||
|
||||
def source(
|
||||
result_id: str,
|
||||
sequence: int,
|
||||
) -> tuple[E46DetectorTruthIsland, dict[str, Any], Path]:
|
||||
result, references = truth(result_id)
|
||||
reference = next(
|
||||
(item for item in references if item.get("truth_island_sequence") == sequence),
|
||||
None,
|
||||
)
|
||||
if reference is None:
|
||||
raise HTTPException(status_code=404, detail="E46 source frame не найден")
|
||||
identity = result.manifest.get("identity")
|
||||
source_identity = identity.get("source") if isinstance(identity, dict) else None
|
||||
pack_id = (
|
||||
source_identity.get("evaluation_pack_id")
|
||||
if isinstance(source_identity, dict)
|
||||
else None
|
||||
)
|
||||
pack_root = _configured_root(evaluation_pack_root_provider)
|
||||
if (
|
||||
pack_root is None
|
||||
or not isinstance(pack_id, str)
|
||||
or _PACK_ID.fullmatch(pack_id) is None
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="E46 image source не найден")
|
||||
pack = (pack_root / pack_id).resolve()
|
||||
relative = reference.get("source_path")
|
||||
if not isinstance(relative, str):
|
||||
raise HTTPException(status_code=404, detail="E46 image source не найден")
|
||||
pure = PurePosixPath(relative)
|
||||
if (
|
||||
pack.parent != pack_root
|
||||
or pack.is_symlink()
|
||||
or not pack.is_dir()
|
||||
or pure.is_absolute()
|
||||
or ".." in pure.parts
|
||||
or pure.parts[:2] != ("images", "valid-fov-fill")
|
||||
or pure.suffix.lower() != ".png"
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="E46 image source не найден")
|
||||
image = (pack / Path(*pure.parts)).resolve()
|
||||
if (
|
||||
not image.is_relative_to(pack)
|
||||
or image.is_symlink()
|
||||
or not image.is_file()
|
||||
or image.stat().st_size != reference.get("byte_length")
|
||||
or _sha256(image) != reference.get("sha256")
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="E46 image source изменён")
|
||||
return result, reference, image
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _truth_candidates(truth_root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
result, references = truth(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(
|
||||
_project_result(
|
||||
result,
|
||||
references,
|
||||
sessions=store.list(result.result_id),
|
||||
submissions=_submissions_for_truth(
|
||||
submission_root_provider,
|
||||
result.result_root,
|
||||
result.result_id,
|
||||
),
|
||||
)
|
||||
)
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46-detector-truth-island-catalog/v1",
|
||||
"configured": _configured_root(truth_root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/annotation-source")
|
||||
def get_source_catalog(result_id: str) -> dict[str, object]:
|
||||
result, references = truth(result_id)
|
||||
frames: list[dict[str, object]] = []
|
||||
for reference in references:
|
||||
_, _, image = source(result_id, int(reference["truth_island_sequence"]))
|
||||
width, height = _png_dimensions(image)
|
||||
frames.append(_source_projection(reference, width=width, height=height))
|
||||
return {
|
||||
"schema_version": L34_ANNOTATION_SOURCE_CATALOG_SCHEMA,
|
||||
"result_id": result.result_id,
|
||||
"truth_island_id": result.result_id,
|
||||
"contract": _review_contract(),
|
||||
"frames": frames,
|
||||
"frame_count": len(frames),
|
||||
"model_material_included": False,
|
||||
"candidate_identity_included": False,
|
||||
"candidate_predictions_included": False,
|
||||
"prelabels_included": False,
|
||||
"reviewer_slots": ["A", "B"],
|
||||
"access": "prediction-free-independent-review-source-read-only",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/annotation-source/frames/{sequence}")
|
||||
def get_source_frame(result_id: str, sequence: int) -> dict[str, object]:
|
||||
_, reference, image = source(result_id, sequence)
|
||||
width, height = _png_dimensions(image)
|
||||
return {
|
||||
"schema_version": L34_ANNOTATION_SOURCE_SCHEMA,
|
||||
"result_id": result_id,
|
||||
**_source_projection(reference, width=width, height=height),
|
||||
"camera_url": (
|
||||
f"/api/v1/laboratory/e46/results/{result_id}"
|
||||
f"/annotation-source/frames/{sequence}/camera"
|
||||
),
|
||||
"model_material_included": False,
|
||||
"candidate_identity_included": False,
|
||||
"candidate_predictions_included": False,
|
||||
"prelabels_included": False,
|
||||
"access": "prediction-free-independent-review-source-read-only",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/annotation-source/frames/{sequence}/camera")
|
||||
def get_source_camera(result_id: str, sequence: int) -> FileResponse:
|
||||
_, _, image = source(result_id, sequence)
|
||||
return FileResponse(image, media_type="image/png")
|
||||
|
||||
@router.get("/results/{result_id}/annotation-sessions")
|
||||
def list_sessions(result_id: str) -> dict[str, object]:
|
||||
truth(result_id)
|
||||
items = store.list(result_id)
|
||||
return {
|
||||
"schema_version": L34_ANNOTATION_SESSION_CATALOG_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"items": items,
|
||||
"total": len(items),
|
||||
"maximum_sessions": 2,
|
||||
"access": "prediction-free-independent-review-summaries",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/annotation-labels")
|
||||
def get_labels(result_id: str) -> dict[str, object]:
|
||||
truth(result_id)
|
||||
return {
|
||||
"schema_version": L34_ANNOTATION_LABEL_CATALOG_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"items": [],
|
||||
"total": 0,
|
||||
"access": "prediction-free-fixed-taxonomy",
|
||||
}
|
||||
|
||||
@router.post("/results/{result_id}/annotation-sessions")
|
||||
def create_session(
|
||||
result_id: str,
|
||||
request: L34AnnotationCreateRequest,
|
||||
) -> dict[str, object]:
|
||||
result, _ = truth(result_id)
|
||||
expected = "l34-annotation-session-" + hashlib.sha256(
|
||||
f"{result_id}\n{request.idempotency_key}".encode()
|
||||
).hexdigest()
|
||||
existing = store.list(result_id)
|
||||
if len(existing) >= 2 and all(item["session_id"] != expected for item in existing):
|
||||
raise HTTPException(status_code=409, detail="E46 допускает только Reviewer A и B")
|
||||
created = store.create(
|
||||
result_id=result_id,
|
||||
truth_island_id=result.result_id,
|
||||
idempotency_key=request.idempotency_key,
|
||||
)
|
||||
capability_path = store.session_path(
|
||||
result_id,
|
||||
str(created["session_id"]),
|
||||
).with_suffix(".capability.json")
|
||||
if capability_path.exists():
|
||||
try:
|
||||
capability_document = json.loads(
|
||||
capability_path.read_text(encoding="utf-8")
|
||||
)
|
||||
capability = capability_document["capability"]
|
||||
except (KeyError, OSError, TypeError, ValueError):
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="E46 reviewer slot не прошёл проверку",
|
||||
) from None
|
||||
else:
|
||||
capability = secrets.token_urlsafe(32)
|
||||
write_json_atomic(
|
||||
capability_path,
|
||||
{
|
||||
"schema_version": "missioncore.e46-review-capability/v1",
|
||||
"result_id": result_id,
|
||||
"session_id": created["session_id"],
|
||||
"capability": capability,
|
||||
},
|
||||
)
|
||||
capability_path.chmod(0o600)
|
||||
return {**created, "review_capability": capability}
|
||||
|
||||
@router.get("/results/{result_id}/annotation-sessions/{session_id}")
|
||||
def get_session(
|
||||
result_id: str,
|
||||
session_id: Annotated[str, ApiPath(pattern=_SESSION_ID.pattern)],
|
||||
x_e46_review_capability: Annotated[
|
||||
str | None,
|
||||
Header(alias="X-E46-Review-Capability"),
|
||||
] = None,
|
||||
) -> dict[str, object]:
|
||||
truth(result_id)
|
||||
require_capability(result_id, session_id, x_e46_review_capability)
|
||||
return store.get(result_id, session_id)
|
||||
|
||||
@router.put("/results/{result_id}/annotation-sessions/{session_id}")
|
||||
def save_session(
|
||||
result_id: str,
|
||||
session_id: Annotated[str, ApiPath(pattern=_SESSION_ID.pattern)],
|
||||
request: L34AnnotationSaveRequest,
|
||||
x_e46_review_capability: Annotated[
|
||||
str | None,
|
||||
Header(alias="X-E46-Review-Capability"),
|
||||
] = None,
|
||||
) -> dict[str, object]:
|
||||
require_capability(result_id, session_id, x_e46_review_capability)
|
||||
result, _ = truth(result_id)
|
||||
if any(
|
||||
item["annotation_session_id"] == session_id
|
||||
for item in _submissions_for_truth(
|
||||
submission_root_provider,
|
||||
result.result_root,
|
||||
result.result_id,
|
||||
)
|
||||
):
|
||||
raise HTTPException(status_code=409, detail="E46 reviewer slot уже заморожен")
|
||||
if request.assistance_mode != "prediction-free-manual":
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="E46 принимает только prediction-free разметку",
|
||||
)
|
||||
sequences: set[int] = set()
|
||||
normalized: list[dict[str, object]] = []
|
||||
for frame in request.frames:
|
||||
sequence = frame.truth_island_sequence
|
||||
if sequence in sequences:
|
||||
raise HTTPException(status_code=422, detail="Кадр размечен дважды")
|
||||
sequences.add(sequence)
|
||||
_, reference, image = source(result_id, sequence)
|
||||
width, height = _png_dimensions(image)
|
||||
if any(
|
||||
item.origin != "manual" or item.category == "unmapped"
|
||||
for item in frame.objects
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="E46 принимает только ручные canonical-классы",
|
||||
)
|
||||
objects = [
|
||||
_normalize_object(item, width=width, height=height)
|
||||
for item in frame.objects
|
||||
]
|
||||
if frame.hard_negative != (len(objects) == 0):
|
||||
raise HTTPException(status_code=422, detail="Пустой кадр должен быть явно отмечен")
|
||||
normalized.append(
|
||||
{
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": int(reference["image_id"]),
|
||||
"frame_index": int(reference["frame_index"]),
|
||||
"source_sha256": str(reference["sha256"]),
|
||||
"reviewed": True,
|
||||
"hard_negative": frame.hard_negative,
|
||||
"objects": objects,
|
||||
}
|
||||
)
|
||||
normalized.sort(key=lambda item: int(item["truth_island_sequence"]))
|
||||
return store.save(
|
||||
result_id=result_id,
|
||||
session_id=session_id,
|
||||
request=request,
|
||||
frames=normalized,
|
||||
)
|
||||
|
||||
@router.post("/results/{result_id}/annotation-sessions/{session_id}/freeze")
|
||||
def freeze_session(
|
||||
result_id: str,
|
||||
session_id: Annotated[str, ApiPath(pattern=_SESSION_ID.pattern)],
|
||||
request: E46ReviewFreezeRequest,
|
||||
x_e46_review_capability: Annotated[
|
||||
str | None,
|
||||
Header(alias="X-E46-Review-Capability"),
|
||||
] = None,
|
||||
) -> dict[str, object]:
|
||||
result, _ = truth(result_id)
|
||||
require_capability(result_id, session_id, x_e46_review_capability)
|
||||
session = store.get(result_id, session_id)
|
||||
if session["revision"] != request.expected_revision:
|
||||
raise HTTPException(status_code=409, detail="E46-сессия уже изменена")
|
||||
if not session["progress"]["complete"]:
|
||||
raise HTTPException(status_code=422, detail="Для freeze нужны 32/32 кадров")
|
||||
reviewer_id = request.reviewer_id.strip()
|
||||
if _REVIEWER_ID.fullmatch(reviewer_id) is None:
|
||||
raise HTTPException(status_code=422, detail="Некорректный reviewer id")
|
||||
root = submission_root_provider()
|
||||
if root is None:
|
||||
raise HTTPException(status_code=503, detail="Хранилище E46 review не настроено")
|
||||
existing = _submissions_for_truth(
|
||||
submission_root_provider,
|
||||
result.result_root,
|
||||
result.result_id,
|
||||
)
|
||||
for item in existing:
|
||||
if item["annotation_session_id"] == session_id:
|
||||
if item["reviewer_id"] == reviewer_id:
|
||||
return item
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="E46 reviewer slot уже заморожен другим reviewer id",
|
||||
)
|
||||
if item["reviewer_id"] == reviewer_id and item["annotation_session_id"] != session_id:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="Reviewer id уже использован другим слотом",
|
||||
)
|
||||
try:
|
||||
frozen = build_e46_lab_review_submission(
|
||||
truth_island_root=result.result_root,
|
||||
annotation_session_path=store.session_path(result_id, session_id),
|
||||
reviewer_id=reviewer_id,
|
||||
output_root=root,
|
||||
)
|
||||
except (E46LabReviewSubmissionError, OSError) as exc:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="E46 review freeze не прошёл проверку",
|
||||
) from exc
|
||||
return _project_submission(frozen)
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(
|
||||
result: E46DetectorTruthIsland,
|
||||
references: tuple[dict[str, Any], ...],
|
||||
*,
|
||||
sessions: list[dict[str, object]],
|
||||
submissions: list[dict[str, object]],
|
||||
) -> dict[str, object]:
|
||||
selection = result.report["selection"]
|
||||
source = result.manifest["identity"]["source"]
|
||||
return {
|
||||
"result_id": result.result_id,
|
||||
"created_at_utc": result.manifest["created_at_utc"],
|
||||
"status": result.report["status"],
|
||||
"source_session_id": source["session_id"],
|
||||
"camera_source_id": source["source_id"],
|
||||
"metrics": {
|
||||
"frame_count": len(references),
|
||||
"anchor_count": selection["anchor_count"],
|
||||
"temporal_frame_count": selection["temporal_frame_count"],
|
||||
"temporal_group_count": selection["temporal_group_count"],
|
||||
"review_session_count": len(sessions),
|
||||
"completed_submission_count": len(submissions),
|
||||
"required_submission_count": 2,
|
||||
},
|
||||
"review_sessions": copy.deepcopy(sessions),
|
||||
"submissions": copy.deepcopy(submissions),
|
||||
"decision": {
|
||||
"collection_ready": True,
|
||||
"two_independent_reviews_complete": len(submissions) == 2,
|
||||
"adjudication_ready": len(submissions) == 2,
|
||||
"e48_sealed": False,
|
||||
"l35_open": False,
|
||||
"next_action": (
|
||||
"complete two distinct prediction-free reviewer submissions"
|
||||
if len(submissions) < 2
|
||||
else "adjudicate both frozen reviews before E48 seal"
|
||||
),
|
||||
},
|
||||
"blindness": {
|
||||
"candidate_identity_included": False,
|
||||
"model_prelabels_included": False,
|
||||
"model_predictions_included": False,
|
||||
"model_scores_included": False,
|
||||
},
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
"access": "review-collection-read-only-summary",
|
||||
}
|
||||
|
||||
|
||||
def _project_submission(result: dict[str, Any]) -> dict[str, object]:
|
||||
identity = result["manifest"]["identity"]
|
||||
return {
|
||||
"result_id": result["result_id"],
|
||||
"created_at_utc": result["manifest"]["created_at_utc"],
|
||||
"truth_island_id": identity["truth_island_id"],
|
||||
"annotation_session_id": identity["annotation_session_id"],
|
||||
"reviewer_id": identity["reviewer_id"],
|
||||
"state": result["manifest"]["state"],
|
||||
"frame_count": len(result["review"]["images"]),
|
||||
"object_count": sum(len(item["objects"]) for item in result["review"]["images"]),
|
||||
"blindness": copy.deepcopy(identity["blindness"]),
|
||||
"authority": copy.deepcopy(identity["authority"]),
|
||||
"ground_truth": False,
|
||||
"access": "immutable-e48-review-input",
|
||||
}
|
||||
|
||||
|
||||
def _submissions_for_truth(
|
||||
provider: RootProvider,
|
||||
truth_root: Path,
|
||||
truth_id: str,
|
||||
) -> list[dict[str, object]]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
items: list[dict[str, object]] = []
|
||||
for path in sorted(root.iterdir(), key=lambda item: item.stat().st_mtime_ns):
|
||||
if not path.is_dir() or path.is_symlink() or _SUBMISSION_ID.fullmatch(path.name) is None:
|
||||
continue
|
||||
try:
|
||||
result = read_e46_lab_review_submission(path, truth_island_root=truth_root)
|
||||
if result["manifest"]["identity"]["truth_island_id"] == truth_id:
|
||||
items.append(_project_submission(result))
|
||||
except (E46LabReviewSubmissionError, OSError):
|
||||
continue
|
||||
return items
|
||||
|
||||
|
||||
def _review_contract() -> dict[str, object]:
|
||||
return {
|
||||
"contract_id": "e46-detector-blind-review/v1",
|
||||
"classes": list(_TARGET_CLASSES),
|
||||
"unmapped_class": None,
|
||||
"box_format": "xyxy-pixels-800x600",
|
||||
"all_identifiable_instances_required": True,
|
||||
"inside_valid_fov_only": True,
|
||||
"hard_negative_frame_flag_required": True,
|
||||
"occluded_and_truncated_flags_required": True,
|
||||
}
|
||||
|
||||
|
||||
def _truth_candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
path
|
||||
for path in root.iterdir()
|
||||
if path.is_dir() and not path.is_symlink() and _TRUTH_ID.fullmatch(path.name)
|
||||
),
|
||||
key=lambda path: path.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
|
||||
if any(not isinstance(row, dict) for row in rows):
|
||||
raise ValueError("invalid E46 references")
|
||||
return rows
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
@@ -0,0 +1,383 @@
|
||||
"""Read-only LAB projection for E46A AI engineering preannotations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi import Path as ApiPath
|
||||
|
||||
from k1link.compute.e46a_ai_engineering_preannotation import (
|
||||
E46AAiEngineeringPreannotationError,
|
||||
read_e46a_ai_engineering_preannotation,
|
||||
)
|
||||
from k1link.web.l34_annotation_api import (
|
||||
L34_ANNOTATION_LABEL_CATALOG_SCHEMA,
|
||||
L34_ANNOTATION_SEED_SCHEMA,
|
||||
L34_ANNOTATION_SESSION_CATALOG_SCHEMA,
|
||||
L34_ANNOTATION_SOURCE_CATALOG_SCHEMA,
|
||||
L34_ANNOTATION_SOURCE_SCHEMA,
|
||||
L34AnnotationCreateRequest,
|
||||
L34AnnotationSaveRequest,
|
||||
_annotation_contract,
|
||||
_L34AnnotationSessionStore,
|
||||
_normalize_object,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46a-ai-engineering-preannotation-[a-f0-9]{64}$")
|
||||
_SESSION_ID = re.compile(r"^l34-annotation-session-[a-f0-9]{64}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_e46a_ai_engineering_preannotation_router(
|
||||
*,
|
||||
root_provider: RootProvider = lambda: None,
|
||||
annotation_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46a", tags=["laboratory"])
|
||||
store = _L34AnnotationSessionStore(
|
||||
annotation_root_provider,
|
||||
result_id_pattern=_RESULT_ID,
|
||||
lab_label="E46A correction",
|
||||
initial_assistance_mode="frozen-candidate-seeded",
|
||||
allowed_assistance_modes=frozenset({"frozen-candidate-seeded"}),
|
||||
contract_id_overrides={
|
||||
"frozen-candidate-seeded": "e46a-ai-engineering-correction/v1"
|
||||
},
|
||||
)
|
||||
|
||||
def result(result_id: str) -> dict[str, Any]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46A result не найден")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46A result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46A result не найден")
|
||||
try:
|
||||
return read_e46a_ai_engineering_preannotation(path)
|
||||
except (E46AAiEngineeringPreannotationError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46A result не найден") from None
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _candidates(root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
frozen = result(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(frozen))
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46a-ai-engineering-preannotation-catalog/v1",
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only-non-truth",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/cases/{sequence}")
|
||||
def get_case(result_id: str, sequence: int) -> dict[str, object]:
|
||||
if sequence < 1 or sequence > 32:
|
||||
raise HTTPException(status_code=404, detail="E46A frame не найден")
|
||||
frozen = result(result_id)
|
||||
case = next(
|
||||
(
|
||||
item
|
||||
for item in frozen["cases"]
|
||||
if item.get("truth_island_sequence") == sequence
|
||||
),
|
||||
None,
|
||||
)
|
||||
if case is None:
|
||||
raise HTTPException(status_code=404, detail="E46A frame не найден")
|
||||
identity = frozen["manifest"].get("identity")
|
||||
e46_source = identity.get("e46_source") if isinstance(identity, dict) else None
|
||||
e46_result_id = (
|
||||
e46_source.get("result_id") if isinstance(e46_source, dict) else None
|
||||
)
|
||||
if not isinstance(e46_result_id, str):
|
||||
raise HTTPException(status_code=404, detail="E46A source не найден")
|
||||
return {
|
||||
**copy.deepcopy(case),
|
||||
"result_id": result_id,
|
||||
"camera_url": (
|
||||
f"/api/v1/laboratory/e46/results/{e46_result_id}"
|
||||
f"/annotation-source/frames/{sequence}/camera"
|
||||
),
|
||||
"candidate_identity_included": True,
|
||||
"model_predictions_seen_during_source_adjudication": True,
|
||||
"model_scores_included": False,
|
||||
"independent_review": False,
|
||||
"ground_truth": False,
|
||||
"access": "read-only-ai-engineering-preannotation-not-truth",
|
||||
}
|
||||
|
||||
def annotation_case(result_id: str, sequence: int) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
frozen = result(result_id)
|
||||
case = next(
|
||||
(
|
||||
item
|
||||
for item in frozen["cases"]
|
||||
if item.get("truth_island_sequence") == sequence
|
||||
),
|
||||
None,
|
||||
)
|
||||
if case is None:
|
||||
raise HTTPException(status_code=404, detail="E46A frame не найден")
|
||||
return frozen, case
|
||||
|
||||
def source_projection(case: dict[str, Any]) -> dict[str, object]:
|
||||
group_id = str(case["group_id"])
|
||||
return {
|
||||
"truth_island_sequence": int(case["truth_island_sequence"]),
|
||||
"image_id": int(case["image_id"]),
|
||||
"frame_index": int(case["frame_index"]),
|
||||
"group_id": group_id,
|
||||
"role": "temporal" if group_id.startswith("clip-") else "anchor",
|
||||
"session_seconds": float(case["session_seconds"]),
|
||||
"camera": {
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"sha256": str(case["source_image_sha256"]),
|
||||
},
|
||||
}
|
||||
|
||||
def e46_source_id(frozen: dict[str, Any]) -> str:
|
||||
identity = frozen["manifest"].get("identity")
|
||||
source = identity.get("e46_source") if isinstance(identity, dict) else None
|
||||
source_id = source.get("result_id") if isinstance(source, dict) else None
|
||||
if not isinstance(source_id, str):
|
||||
raise HTTPException(status_code=404, detail="E46A source не найден")
|
||||
return source_id
|
||||
|
||||
@router.get("/results/{result_id}/annotation-source")
|
||||
def get_annotation_source_catalog(result_id: str) -> dict[str, object]:
|
||||
frozen = result(result_id)
|
||||
frames = [source_projection(case) for case in frozen["cases"]]
|
||||
return {
|
||||
"schema_version": L34_ANNOTATION_SOURCE_CATALOG_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"truth_island_id": e46_source_id(frozen),
|
||||
"contract": _annotation_contract(
|
||||
contract_id="e46a-ai-engineering-correction/v1"
|
||||
),
|
||||
"frames": frames,
|
||||
"frame_count": len(frames),
|
||||
"model_material_included": False,
|
||||
"candidate_identity_included": True,
|
||||
"candidate_predictions_included": False,
|
||||
"prelabels_included": False,
|
||||
"access": "ai-engineering-correction-source-read-only",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/annotation-source/frames/{sequence}")
|
||||
def get_annotation_source_frame(result_id: str, sequence: int) -> dict[str, object]:
|
||||
frozen, case = annotation_case(result_id, sequence)
|
||||
source_id = e46_source_id(frozen)
|
||||
return {
|
||||
"schema_version": L34_ANNOTATION_SOURCE_SCHEMA,
|
||||
"result_id": result_id,
|
||||
**source_projection(case),
|
||||
"camera_url": (
|
||||
f"/api/v1/laboratory/e46/results/{source_id}"
|
||||
f"/annotation-source/frames/{sequence}/camera"
|
||||
),
|
||||
"model_material_included": False,
|
||||
"candidate_identity_included": True,
|
||||
"candidate_predictions_included": False,
|
||||
"prelabels_included": False,
|
||||
"access": "ai-engineering-correction-source-read-only",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/annotation-seed/frames/{sequence}")
|
||||
def get_annotation_seed_frame(result_id: str, sequence: int) -> dict[str, object]:
|
||||
_, case = annotation_case(result_id, sequence)
|
||||
objects: list[dict[str, object]] = []
|
||||
for raw in case["objects"]:
|
||||
category = str(raw["category"])
|
||||
proposed_label: str | None = None
|
||||
if category == "stroller":
|
||||
category = "unmapped"
|
||||
proposed_label = "Коляска"
|
||||
elif category == "laptop":
|
||||
category = "unmapped"
|
||||
proposed_label = "Ноутбук"
|
||||
objects.append(
|
||||
{
|
||||
"object_id": str(raw["object_id"]),
|
||||
"category": category,
|
||||
"proposed_label": proposed_label,
|
||||
"origin": "frozen_candidate_seed",
|
||||
"box_xyxy": copy.deepcopy(raw["box_xyxy"]),
|
||||
"occluded": bool(raw["occluded"]),
|
||||
"truncated": bool(raw["truncated"]),
|
||||
}
|
||||
)
|
||||
return {
|
||||
"schema_version": L34_ANNOTATION_SEED_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"truth_island_sequence": sequence,
|
||||
"source_sha256": str(case["source_image_sha256"]),
|
||||
"objects": objects,
|
||||
"assistance": {
|
||||
"mode": "frozen-candidate-seeded",
|
||||
"independent_truth_eligible": False,
|
||||
},
|
||||
"model_material_included": True,
|
||||
"access": "ai-engineering-preannotation-seed-read-only",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/annotation-sessions")
|
||||
def list_annotation_sessions(result_id: str) -> dict[str, object]:
|
||||
annotation_case(result_id, 1)
|
||||
items = store.list(result_id)
|
||||
return {
|
||||
"schema_version": L34_ANNOTATION_SESSION_CATALOG_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"items": items,
|
||||
"total": len(items),
|
||||
"access": "ai-engineering-human-correction",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/annotation-labels")
|
||||
def get_annotation_labels(result_id: str) -> dict[str, object]:
|
||||
annotation_case(result_id, 1)
|
||||
values = {"коляска": "Коляска", "ноутбук": "Ноутбук"}
|
||||
for item in store.labels(result_id):
|
||||
label = str(item["value"])
|
||||
values[label.casefold()] = label
|
||||
items = [
|
||||
{"value": value, "normalization_state": "pending-adjudication"}
|
||||
for value in sorted(values.values(), key=str.casefold)
|
||||
]
|
||||
return {
|
||||
"schema_version": L34_ANNOTATION_LABEL_CATALOG_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"items": items,
|
||||
"total": len(items),
|
||||
"access": "ai-engineering-correction-taxonomy",
|
||||
}
|
||||
|
||||
@router.post("/results/{result_id}/annotation-sessions")
|
||||
def create_annotation_session(
|
||||
result_id: str,
|
||||
request: L34AnnotationCreateRequest,
|
||||
) -> dict[str, object]:
|
||||
frozen, _ = annotation_case(result_id, 1)
|
||||
return store.create(
|
||||
result_id=result_id,
|
||||
truth_island_id=e46_source_id(frozen),
|
||||
idempotency_key=request.idempotency_key,
|
||||
)
|
||||
|
||||
@router.get("/results/{result_id}/annotation-sessions/{session_id}")
|
||||
def get_annotation_session(
|
||||
result_id: str,
|
||||
session_id: Annotated[str, ApiPath(pattern=_SESSION_ID.pattern)],
|
||||
) -> dict[str, object]:
|
||||
annotation_case(result_id, 1)
|
||||
return store.get(result_id, session_id)
|
||||
|
||||
@router.put("/results/{result_id}/annotation-sessions/{session_id}")
|
||||
def save_annotation_session(
|
||||
result_id: str,
|
||||
session_id: Annotated[str, ApiPath(pattern=_SESSION_ID.pattern)],
|
||||
request: L34AnnotationSaveRequest,
|
||||
) -> dict[str, object]:
|
||||
if request.assistance_mode != "frozen-candidate-seeded":
|
||||
raise HTTPException(status_code=422, detail="Режим E46A correction запрещён")
|
||||
sequences: set[int] = set()
|
||||
frames: list[dict[str, object]] = []
|
||||
for frame in request.frames:
|
||||
sequence = frame.truth_island_sequence
|
||||
if sequence in sequences:
|
||||
raise HTTPException(status_code=422, detail="Кадр размечен дважды")
|
||||
sequences.add(sequence)
|
||||
_, case = annotation_case(result_id, sequence)
|
||||
objects = [
|
||||
_normalize_object(item, width=800, height=600) for item in frame.objects
|
||||
]
|
||||
if frame.hard_negative != (len(objects) == 0):
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="Пустой кадр должен быть явно отмечен",
|
||||
)
|
||||
frames.append(
|
||||
{
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": int(case["image_id"]),
|
||||
"frame_index": int(case["frame_index"]),
|
||||
"source_sha256": str(case["source_image_sha256"]),
|
||||
"reviewed": True,
|
||||
"hard_negative": frame.hard_negative,
|
||||
"objects": objects,
|
||||
}
|
||||
)
|
||||
frames.sort(key=lambda item: int(item["truth_island_sequence"]))
|
||||
return store.save(
|
||||
result_id=result_id,
|
||||
session_id=session_id,
|
||||
request=request,
|
||||
frames=frames,
|
||||
)
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(frozen: dict[str, Any]) -> dict[str, object]:
|
||||
manifest = frozen["manifest"]
|
||||
report = frozen["report"]
|
||||
identity = manifest.get("identity")
|
||||
e46_source = identity.get("e46_source") if isinstance(identity, dict) else None
|
||||
return {
|
||||
"schema_version": "missioncore.e46a-ai-engineering-preannotation-view/v1",
|
||||
"result_id": frozen["result_id"],
|
||||
"created_at_utc": report.get("created_at_utc"),
|
||||
"source_session_id": report.get("source_session_id"),
|
||||
"camera_source_id": report.get("camera_source_id"),
|
||||
"source_e46_result_id": (
|
||||
e46_source.get("result_id") if isinstance(e46_source, dict) else None
|
||||
),
|
||||
"metrics": copy.deepcopy(report.get("metrics")),
|
||||
"assistance": copy.deepcopy(report.get("assistance")),
|
||||
"taxonomy": copy.deepcopy(report.get("taxonomy")),
|
||||
"decision": copy.deepcopy(report.get("decision")),
|
||||
"limitations": copy.deepcopy(report.get("limitations")),
|
||||
"authority": copy.deepcopy(report.get("authority")),
|
||||
"ground_truth": False,
|
||||
"access": "read-only-ai-engineering-preannotation-not-truth",
|
||||
}
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
configured = provider()
|
||||
if configured is None:
|
||||
return None
|
||||
return configured.expanduser().absolute()
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None or not root.is_dir() or root.is_symlink():
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
path
|
||||
for path in root.iterdir()
|
||||
if path.is_dir()
|
||||
and not path.is_symlink()
|
||||
and _RESULT_ID.fullmatch(path.name) is not None
|
||||
),
|
||||
key=lambda path: (path.stat().st_mtime_ns, path.name),
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,133 @@
|
||||
"""Read-only LAB projection for E46B temporal tracks and motion state."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
|
||||
from k1link.compute.e46b_temporal_motion import (
|
||||
E46BTemporalMotionError,
|
||||
read_e46b_temporal_motion,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46b-temporal-motion-[a-f0-9]{64}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_e46b_temporal_motion_router(
|
||||
*, root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46b", tags=["laboratory"])
|
||||
|
||||
def result(result_id: str) -> dict[str, Any]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46B result не найден")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46B result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46B result не найден")
|
||||
try:
|
||||
return read_e46b_temporal_motion(path)
|
||||
except (E46BTemporalMotionError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46B result не найден") from None
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _candidates(root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
frozen = result(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(frozen))
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46b-temporal-motion-catalog/v1",
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only-engineering-evidence",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/cases/{sequence}")
|
||||
def get_case(result_id: str, sequence: int) -> dict[str, object]:
|
||||
frozen = result(result_id)
|
||||
case = next(
|
||||
(item for item in frozen["cases"] if item.get("truth_island_sequence") == sequence),
|
||||
None,
|
||||
)
|
||||
if case is None:
|
||||
raise HTTPException(status_code=404, detail="E46B frame не найден")
|
||||
identity = frozen["manifest"]["identity"]
|
||||
e46_result_id = identity["e46_source"]["result_id"]
|
||||
return {
|
||||
**copy.deepcopy(case),
|
||||
"result_id": result_id,
|
||||
"camera_url": (
|
||||
f"/api/v1/laboratory/e46/results/{e46_result_id}"
|
||||
f"/annotation-source/frames/{sequence}/camera"
|
||||
),
|
||||
"ground_truth": False,
|
||||
"access": "read-only-source-scoped-temporal-motion",
|
||||
}
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(frozen: dict[str, Any]) -> dict[str, object]:
|
||||
report = frozen["report"]
|
||||
identity = frozen["manifest"]["identity"]
|
||||
return {
|
||||
"schema_version": "missioncore.e46b-temporal-motion-view/v1",
|
||||
"result_id": frozen["result_id"],
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"source_e46a_result_id": identity["e46a_source"]["result_id"],
|
||||
"source_e26_result_id": identity["e26_motion_source"]["result_id"],
|
||||
"status": report["status"],
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"tracks": copy.deepcopy(report["tracks"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"ground_truth": False,
|
||||
"access": "read-only-engineering-evidence",
|
||||
}
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
item for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and _RESULT_ID.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,396 @@
|
||||
"""Read-only LAB projection for E46C full-replay world tracks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
|
||||
from k1link.compute.e46c_full_replay_world_tracks import (
|
||||
E46CFullReplayWorldTracksError,
|
||||
read_e46c_full_replay_world_tracks,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46c-full-replay-world-tracks-[a-f0-9]{64}$")
|
||||
_E26_RESULT_ID = re.compile(r"^e10-integrated-perception-[a-f0-9]{64}$")
|
||||
_SAFE_SESSION_ID = re.compile(r"^[A-Za-z0-9._:-]{1,128}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_e46c_full_replay_world_tracks_router(
|
||||
*,
|
||||
root_provider: RootProvider = lambda: None,
|
||||
e26_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46c", tags=["laboratory"])
|
||||
|
||||
def result(result_id: str) -> dict[str, Any]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46C result не найден")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46C result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46C result не найден")
|
||||
try:
|
||||
return read_e46c_full_replay_world_tracks(path)
|
||||
except (E46CFullReplayWorldTracksError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46C result не найден") from None
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _candidates(root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
frozen = result(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(frozen))
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46c-full-replay-world-tracks-catalog/v1",
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only-diagnostic",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/cases/{sequence}")
|
||||
def get_case(result_id: str, sequence: int) -> dict[str, object]:
|
||||
frozen = result(result_id)
|
||||
case = next(
|
||||
(item for item in frozen["cases"] if item.get("truth_island_sequence") == sequence),
|
||||
None,
|
||||
)
|
||||
if case is None:
|
||||
raise HTTPException(status_code=404, detail="E46C frame не найден")
|
||||
e46_result_id = frozen["manifest"]["identity"]["e46_source"]["result_id"]
|
||||
return {
|
||||
**copy.deepcopy(case),
|
||||
"result_id": result_id,
|
||||
"camera_url": (
|
||||
f"/api/v1/laboratory/e46/results/{e46_result_id}"
|
||||
f"/annotation-source/frames/{sequence}/camera"
|
||||
),
|
||||
"ground_truth": False,
|
||||
"access": "read-only-full-route-diagnostic-sample",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/video-overlay")
|
||||
def get_video_overlay(result_id: str) -> dict[str, object]:
|
||||
frozen = result(result_id)
|
||||
e26_root = _configured_root(e26_root_provider)
|
||||
if e26_root is None:
|
||||
raise HTTPException(status_code=404, detail="E46C video overlay не найден")
|
||||
identity = frozen["manifest"]["identity"]["e26_full_replay"]
|
||||
source_result_id = identity.get("result_id")
|
||||
expected_result_sha256 = identity.get("result_sha256")
|
||||
expected_fusion_sha256 = identity.get("fusion_frames_sha256")
|
||||
if (
|
||||
not isinstance(source_result_id, str)
|
||||
or _E26_RESULT_ID.fullmatch(source_result_id) is None
|
||||
or not isinstance(expected_result_sha256, str)
|
||||
or not isinstance(expected_fusion_sha256, str)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="E46C video overlay не найден")
|
||||
source_path = (e26_root / source_result_id).resolve()
|
||||
if source_path.parent != e26_root or source_path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46C video overlay не найден")
|
||||
try:
|
||||
return _cached_video_overlay(
|
||||
result_id,
|
||||
str(source_path),
|
||||
expected_result_sha256,
|
||||
expected_fusion_sha256,
|
||||
)
|
||||
except (OSError, ValueError, json.JSONDecodeError):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="E46C video overlay не найден",
|
||||
) from None
|
||||
|
||||
return router
|
||||
|
||||
|
||||
@lru_cache(maxsize=4)
|
||||
def _cached_video_overlay(
|
||||
e46c_result_id: str,
|
||||
source_root_value: str,
|
||||
expected_result_sha256: str,
|
||||
expected_fusion_sha256: str,
|
||||
) -> dict[str, object]:
|
||||
source_root = Path(source_root_value).resolve(strict=True)
|
||||
if source_root.is_symlink() or not source_root.is_dir():
|
||||
raise ValueError("E26 source root is invalid")
|
||||
result_path = source_root / "result.json"
|
||||
if _sha256(result_path) != expected_result_sha256:
|
||||
raise ValueError("E26 result identity changed")
|
||||
result = _read_json(result_path)
|
||||
identity = result.get("identity")
|
||||
artifacts = result.get("artifacts")
|
||||
if (
|
||||
result.get("schema_version")
|
||||
!= "missioncore.e10-integrated-perception-result/v1"
|
||||
or result.get("acceptance_state") != "accepted"
|
||||
or not isinstance(identity, dict)
|
||||
or not isinstance(artifacts, list)
|
||||
):
|
||||
raise ValueError("E26 result contract is invalid")
|
||||
selection = identity.get("selection")
|
||||
configuration = identity.get("configuration")
|
||||
if not isinstance(selection, dict) or not isinstance(configuration, dict):
|
||||
raise ValueError("E26 timeline contract is invalid")
|
||||
recorded_session_id = configuration.get("source_session_id")
|
||||
if (
|
||||
not isinstance(recorded_session_id, str)
|
||||
or _SAFE_SESSION_ID.fullmatch(recorded_session_id) is None
|
||||
or identity.get("source_id") != "sensor.camera.right"
|
||||
or selection.get("frame_count") != 4489
|
||||
):
|
||||
raise ValueError("E26 recorded source binding is invalid")
|
||||
fusion_artifact = next(
|
||||
(
|
||||
item
|
||||
for item in artifacts
|
||||
if isinstance(item, dict) and item.get("path") == "fusion-frames.jsonl"
|
||||
),
|
||||
None,
|
||||
)
|
||||
fusion_path = _validated_source_artifact(
|
||||
source_root,
|
||||
fusion_artifact,
|
||||
expected_fusion_sha256,
|
||||
)
|
||||
|
||||
frames: list[dict[str, object]] = []
|
||||
previous_seconds = -math.inf
|
||||
for expected_frame_index, row in enumerate(_read_jsonl(fusion_path)):
|
||||
frame_index = row.get("source_frame_index")
|
||||
session_seconds = row.get("session_seconds")
|
||||
raw_objects = row.get("objects")
|
||||
if (
|
||||
frame_index != expected_frame_index
|
||||
or not isinstance(session_seconds, int | float)
|
||||
or isinstance(session_seconds, bool)
|
||||
or not math.isfinite(float(session_seconds))
|
||||
or float(session_seconds) <= previous_seconds
|
||||
or not isinstance(raw_objects, list)
|
||||
):
|
||||
raise ValueError("E26 video frame sequence is invalid")
|
||||
previous_seconds = float(session_seconds)
|
||||
frames.append(
|
||||
{
|
||||
"frame_index": frame_index,
|
||||
"session_seconds": float(session_seconds),
|
||||
"fusion_state": str(row.get("fusion_state", "unknown")),
|
||||
"objects": [
|
||||
_video_object(item)
|
||||
for item in raw_objects
|
||||
if isinstance(item, dict)
|
||||
],
|
||||
}
|
||||
)
|
||||
timeline_start = float(selection.get("timeline_start_seconds"))
|
||||
timeline_end = float(selection.get("timeline_end_seconds"))
|
||||
if (
|
||||
len(frames) != 4489
|
||||
or frames[0]["session_seconds"] != timeline_start
|
||||
or frames[-1]["session_seconds"] != timeline_end
|
||||
):
|
||||
raise ValueError("E26 video timeline changed")
|
||||
|
||||
benchmark = configuration.get("benchmark")
|
||||
benchmark_events = benchmark.get("events") if isinstance(benchmark, dict) else None
|
||||
review_windows = []
|
||||
if isinstance(benchmark_events, list):
|
||||
for event in benchmark_events:
|
||||
if not isinstance(event, dict):
|
||||
continue
|
||||
window = event.get("window_seconds")
|
||||
if (
|
||||
not isinstance(window, list)
|
||||
or len(window) != 2
|
||||
or not all(isinstance(item, int | float) for item in window)
|
||||
):
|
||||
continue
|
||||
review_windows.append(
|
||||
{
|
||||
"id": str(event.get("id", "review-window")),
|
||||
"kind": str(event.get("kind", "diagnostic")),
|
||||
"class_group": str(event.get("class_group", "all")),
|
||||
"start_seconds": float(window[0]),
|
||||
"end_seconds": float(window[1]),
|
||||
"target_source_track_ids": [
|
||||
int(value)
|
||||
for value in event.get("target_source_track_ids", [])
|
||||
if isinstance(value, int) and not isinstance(value, bool)
|
||||
],
|
||||
}
|
||||
)
|
||||
return {
|
||||
"schema_version": "missioncore.e46c-recorded-video-overlay/v1",
|
||||
"result_id": e46c_result_id,
|
||||
"recorded_source": {
|
||||
"session_id": recorded_session_id,
|
||||
"source_id": "sensor.camera.right",
|
||||
"input_sha256": identity.get("input_sha256"),
|
||||
"synchronization": "host-arrival-best-effort",
|
||||
},
|
||||
"image_width": 800,
|
||||
"image_height": 600,
|
||||
"timeline_start_seconds": timeline_start,
|
||||
"timeline_end_seconds": timeline_end,
|
||||
"frame_count": len(frames),
|
||||
"frames": frames,
|
||||
"review_windows": review_windows,
|
||||
"ground_truth": False,
|
||||
"access": "read-only-full-route-diagnostic-video",
|
||||
}
|
||||
|
||||
|
||||
def _video_object(item: dict[str, Any]) -> dict[str, object]:
|
||||
box = item.get("bbox_xyxy")
|
||||
if (
|
||||
not isinstance(box, list)
|
||||
or len(box) != 4
|
||||
or not all(
|
||||
isinstance(value, int | float)
|
||||
and not isinstance(value, bool)
|
||||
and math.isfinite(float(value))
|
||||
for value in box
|
||||
)
|
||||
):
|
||||
raise ValueError("E26 video object box is invalid")
|
||||
source_track_id = item.get("source_track_id")
|
||||
world_track_id = item.get("track_id")
|
||||
if not isinstance(source_track_id, int) or isinstance(source_track_id, bool):
|
||||
raise ValueError("E26 video object track is invalid")
|
||||
if not isinstance(world_track_id, int) or isinstance(world_track_id, bool):
|
||||
world_track_id = source_track_id
|
||||
return {
|
||||
"bbox_xyxy": [float(value) for value in box],
|
||||
"category": str(item.get("label", "unknown")),
|
||||
"score": float(item.get("score") or 0.0),
|
||||
"route_track_id": source_track_id,
|
||||
"world_track_id": (
|
||||
world_track_id if world_track_id != source_track_id else None
|
||||
),
|
||||
"motion_state": str(item.get("motion_state", "unknown")),
|
||||
"motion_confidence": float(item.get("motion_confidence") or 0.0),
|
||||
"track_hits": int(item.get("track_hits") or 0),
|
||||
"track_age_seconds": float(item.get("track_age_s") or 0.0),
|
||||
"camera_evidence_current": item.get("camera_evidence_current") is True,
|
||||
"world_evidence_current": item.get("occupancy_evidence_current") is True,
|
||||
}
|
||||
|
||||
|
||||
def _validated_source_artifact(
|
||||
root: Path,
|
||||
raw: object,
|
||||
expected_sha256: str,
|
||||
) -> Path:
|
||||
if not isinstance(raw, dict) or raw.get("sha256") != expected_sha256:
|
||||
raise ValueError("E26 fusion artifact identity changed")
|
||||
relative = raw.get("path")
|
||||
if not isinstance(relative, str):
|
||||
raise ValueError("E26 fusion artifact path is invalid")
|
||||
path = (root / relative).resolve(strict=True)
|
||||
expected_size = raw.get("byte_length", raw.get("size_bytes"))
|
||||
if (
|
||||
path.parent != root
|
||||
or path.is_symlink()
|
||||
or not path.is_file()
|
||||
or path.stat().st_size != expected_size
|
||||
or _sha256(path) != expected_sha256
|
||||
):
|
||||
raise ValueError("E26 fusion artifact changed")
|
||||
return path
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError("JSON document is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _read_jsonl(path: Path):
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
if line.strip():
|
||||
value = json.loads(line)
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError("JSONL row is invalid")
|
||||
yield value
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for block in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _project_result(frozen: dict[str, Any]) -> dict[str, object]:
|
||||
report = frozen["report"]
|
||||
identity = frozen["manifest"]["identity"]
|
||||
return {
|
||||
"schema_version": "missioncore.e46c-full-replay-world-tracks-view/v1",
|
||||
"result_id": frozen["result_id"],
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"source_e46a_result_id": identity["e46a_visual_sample"]["result_id"],
|
||||
"source_e26_result_id": identity["e26_full_replay"]["result_id"],
|
||||
"status": report["status"],
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"acceptance": copy.deepcopy(report["acceptance"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"ground_truth": False,
|
||||
"access": "read-only-diagnostic",
|
||||
}
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir()
|
||||
and not item.is_symlink()
|
||||
and _RESULT_ID.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,114 @@
|
||||
"""Read-only LAB projection for the E46D full-route temporal audit."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
|
||||
from k1link.compute.e46d_temporal_failure_audit import (
|
||||
E46DTemporalFailureAuditError,
|
||||
read_e46d_temporal_failure_audit,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46d-temporal-failure-audit-[a-f0-9]{64}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_e46d_temporal_failure_audit_router(
|
||||
*, root_provider: RootProvider = lambda: None
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46d", tags=["laboratory"])
|
||||
|
||||
def result(result_id: str) -> dict[str, Any]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46D result не найден")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46D result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46D result не найден")
|
||||
try:
|
||||
return read_e46d_temporal_failure_audit(path)
|
||||
except (E46DTemporalFailureAuditError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46D result не найден") from None
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _candidates(root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
frozen = result(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(frozen))
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46d-temporal-failure-audit-catalog/v1",
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only-recorded-temporal-diagnostic",
|
||||
}
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(frozen: dict[str, Any]) -> dict[str, object]:
|
||||
report = frozen["report"]
|
||||
identity = frozen["manifest"]["identity"]
|
||||
return {
|
||||
"schema_version": "missioncore.e46d-temporal-failure-audit-view/v1",
|
||||
"result_id": frozen["result_id"],
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"source_e46c_result_id": identity["e46c_source"]["result_id"],
|
||||
"source_e26_result_id": identity["e26_source"]["result_id"],
|
||||
"status": report["status"],
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"acceptance": copy.deepcopy(report["acceptance"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"review_clips": copy.deepcopy(frozen["clips"]),
|
||||
"ground_truth": False,
|
||||
"access": "read-only-recorded-temporal-diagnostic",
|
||||
}
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and _RESULT_ID.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,149 @@
|
||||
"""Read-only LAB projection and video delivery for the E46E NVIDIA ready stack."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.e46e_ready_stack import (
|
||||
E46EReadyStackError,
|
||||
read_e46e_ready_stack,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46e-ready-stack-[a-f0-9]{64}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_e46e_ready_stack_router(
|
||||
*, root_provider: RootProvider = lambda: None
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46e", tags=["laboratory"])
|
||||
|
||||
def result(result_id: str) -> dict[str, Any]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46E result не найден")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46E result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46E result не найден")
|
||||
try:
|
||||
return read_e46e_ready_stack(path)
|
||||
except (E46EReadyStackError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46E result не найден") from None
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _candidates(root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
frozen = result(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(frozen))
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46e-ready-stack-catalog/v1",
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only-stock-nvidia-recorded-diagnostic",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/overlay.mp4")
|
||||
def get_overlay_video(result_id: str) -> FileResponse:
|
||||
frozen = result(result_id)
|
||||
overlay = frozen["overlay_path"]
|
||||
descriptor = next(
|
||||
(
|
||||
row
|
||||
for row in frozen["manifest"]["artifacts"]
|
||||
if row.get("role") == "visual-overlay-video"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not isinstance(descriptor, dict):
|
||||
raise HTTPException(status_code=404, detail="E46E video не найден")
|
||||
return FileResponse(
|
||||
overlay,
|
||||
media_type="video/mp4",
|
||||
headers={
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
"ETag": f'"{descriptor["sha256"]}"',
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(frozen: dict[str, Any]) -> dict[str, object]:
|
||||
report = frozen["report"]
|
||||
overlay = next(
|
||||
row
|
||||
for row in frozen["manifest"]["artifacts"]
|
||||
if row.get("role") == "visual-overlay-video"
|
||||
)
|
||||
result_id = frozen["result_id"]
|
||||
return {
|
||||
"schema_version": "missioncore.e46e-ready-stack-view/v1",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"status": report["status"],
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"acceptance": copy.deepcopy(report["acceptance"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"video": {
|
||||
"url": f"/api/v1/laboratory/e46e/results/{result_id}/overlay.mp4",
|
||||
"media_type": "video/mp4",
|
||||
"byte_length": overlay["byte_length"],
|
||||
"sha256": overlay["sha256"],
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
},
|
||||
"ground_truth": False,
|
||||
"access": "read-only-stock-nvidia-recorded-diagnostic",
|
||||
}
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and _RESULT_ID.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,230 @@
|
||||
"""Read-only LAB projection for the E46F DashCamNet detector bake-off."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections import Counter
|
||||
from collections.abc import Callable, Iterable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.e46e_ready_stack import (
|
||||
E46EReadyStackError,
|
||||
read_e46e_ready_stack,
|
||||
)
|
||||
from k1link.compute.e46f_dashcam_bakeoff import (
|
||||
E46FDashCamBakeoffError,
|
||||
read_e46f_dashcam_bakeoff,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46f-dashcam-bakeoff-[a-f0-9]{64}$")
|
||||
_BASELINE_ID = re.compile(r"^e46e-ready-stack-[a-f0-9]{64}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_e46f_dashcam_bakeoff_router(
|
||||
*,
|
||||
root_provider: RootProvider = lambda: None,
|
||||
e46e_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46f", tags=["laboratory"])
|
||||
|
||||
def result(result_id: str) -> dict[str, Any]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46F result не найден")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46F result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46F result не найден")
|
||||
try:
|
||||
return read_e46f_dashcam_bakeoff(path)
|
||||
except (E46FDashCamBakeoffError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46F result не найден") from None
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _candidates(root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
frozen = result(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(frozen, e46e_root_provider))
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46f-dashcam-bakeoff-catalog/v1",
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only-rejected-detector-bakeoff",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/overlay.mp4")
|
||||
def get_overlay_video(result_id: str) -> FileResponse:
|
||||
frozen = result(result_id)
|
||||
descriptor = next(
|
||||
(
|
||||
row
|
||||
for row in frozen["manifest"]["artifacts"]
|
||||
if row.get("role") == "visual-overlay-video"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not isinstance(descriptor, dict):
|
||||
raise HTTPException(status_code=404, detail="E46F video не найден")
|
||||
return FileResponse(
|
||||
frozen["overlay_path"],
|
||||
media_type="video/mp4",
|
||||
headers={
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
"ETag": f'"{descriptor["sha256"]}"',
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(frozen: dict[str, Any], e46e_root_provider: RootProvider) -> dict[str, object]:
|
||||
report = frozen["report"]
|
||||
overlay = next(
|
||||
row for row in frozen["manifest"]["artifacts"] if row.get("role") == "visual-overlay-video"
|
||||
)
|
||||
baseline_id = report["comparison_contract"]["baseline_result_id"]
|
||||
baseline = _read_baseline(e46e_root_provider, baseline_id)
|
||||
baseline_metrics = baseline["report"]["metrics"]
|
||||
metrics = report["metrics"]
|
||||
candidate_triage = _large_box_triage(frozen["frames"])
|
||||
baseline_triage = _large_box_triage(baseline["frames"])
|
||||
result_id = frozen["result_id"]
|
||||
return {
|
||||
"schema_version": "missioncore.e46f-dashcam-bakeoff-view/v1",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"status": report["status"],
|
||||
"metrics": copy.deepcopy(metrics),
|
||||
"acceptance": copy.deepcopy(report["acceptance"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"comparison": {
|
||||
"controlled_change": "detector-only",
|
||||
"baseline_result_id": baseline_id,
|
||||
"baseline_metrics": copy.deepcopy(baseline_metrics),
|
||||
"delta": {
|
||||
"zero_track_frame_count": metrics["zero_track_frame_count"]
|
||||
- baseline_metrics["zero_track_frame_count"],
|
||||
"full_layer_blackout_event_count": metrics["full_layer_blackout_event_count"]
|
||||
- baseline_metrics["full_layer_blackout_event_count"],
|
||||
"unique_track_count": metrics["unique_track_count"]
|
||||
- baseline_metrics["unique_track_count"],
|
||||
"short_track_fraction": metrics["short_track_fraction"]
|
||||
- baseline_metrics["short_track_fraction"],
|
||||
},
|
||||
"large_box_visual_triage": {
|
||||
"area_ratio_threshold": 0.2,
|
||||
"candidate": candidate_triage,
|
||||
"baseline": baseline_triage,
|
||||
"interpretation": "diagnostic visual triage; not precision/recall",
|
||||
},
|
||||
"visual_review": {
|
||||
"status": "rejected-semantic-regression",
|
||||
"sample_video_seconds": [4.2, 9.0, 22.0, 44.0, 264.0, 418.0],
|
||||
"finding": (
|
||||
"DashCamNet repeatedly turns the fisheye black rim and edge geometry "
|
||||
"into very large person tracks; temporal continuity improves, but the "
|
||||
"semantic object layer is less trustworthy than E46E"
|
||||
),
|
||||
"next_action": (
|
||||
"insert one calibration-derived rectification and valid-FOV projection "
|
||||
"before any perspective ready detector, then repeat the controlled bake-off"
|
||||
),
|
||||
},
|
||||
"verdict": "reject-dashcamnet-on-unrectified-fisheye",
|
||||
},
|
||||
"video": {
|
||||
"url": f"/api/v1/laboratory/e46f/results/{result_id}/overlay.mp4",
|
||||
"media_type": "video/mp4",
|
||||
"byte_length": overlay["byte_length"],
|
||||
"sha256": overlay["sha256"],
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
},
|
||||
"ground_truth": False,
|
||||
"access": "read-only-rejected-detector-bakeoff",
|
||||
}
|
||||
|
||||
|
||||
def _read_baseline(provider: RootProvider, result_id: str) -> dict[str, Any]:
|
||||
root = _configured_root(provider)
|
||||
if root is None or _BASELINE_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46E baseline не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46E baseline не найден")
|
||||
try:
|
||||
return read_e46e_ready_stack(path)
|
||||
except (E46EReadyStackError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46E baseline не найден") from None
|
||||
|
||||
|
||||
def _large_box_triage(frames: Iterable[dict[str, Any]]) -> dict[str, object]:
|
||||
observation_count = 0
|
||||
frame_ids: set[int] = set()
|
||||
track_ids: set[int] = set()
|
||||
classes: Counter[str] = Counter()
|
||||
for frame in frames:
|
||||
for item in frame["objects"]:
|
||||
_, _, width, height = item["bbox"]
|
||||
if float(width) * float(height) < 800 * 600 * 0.2:
|
||||
continue
|
||||
observation_count += 1
|
||||
frame_ids.add(int(frame["frame_index"]))
|
||||
track_ids.add(int(item["source_track_id"]))
|
||||
classes.update((str(item["class_name"]),))
|
||||
return {
|
||||
"observation_count": observation_count,
|
||||
"frame_count": len(frame_ids),
|
||||
"track_id_count": len(track_ids),
|
||||
"class_observations": dict(sorted(classes.items())),
|
||||
}
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and _RESULT_ID.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,185 @@
|
||||
"""Read-only LAB projection for the calibrated E46G ready-detector bake-off."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.e46g_rectified_detector_bakeoff import (
|
||||
E46GRectifiedDetectorBakeoffError,
|
||||
read_e46g_rectified_detector_bakeoff,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46g-rectified-detector-bakeoff-[a-f0-9]{64}$")
|
||||
_CANDIDATES = ("trafficcamnet", "dashcamnet")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_e46g_rectified_detector_bakeoff_router(
|
||||
*, root_provider: RootProvider = lambda: None
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46g", tags=["laboratory"])
|
||||
|
||||
def result(result_id: str) -> dict[str, Any]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46G result не найден")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46G result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46G result не найден")
|
||||
try:
|
||||
return read_e46g_rectified_detector_bakeoff(path)
|
||||
except (E46GRectifiedDetectorBakeoffError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46G result не найден") from None
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _candidates(root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
frozen = result(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(frozen))
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46g-rectified-detector-bakeoff-catalog/v1",
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only-diagnostic-selection",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/{candidate}.mp4")
|
||||
def get_comparison_video(result_id: str, candidate: str) -> FileResponse:
|
||||
if candidate not in _CANDIDATES:
|
||||
raise HTTPException(status_code=404, detail="E46G video не найден")
|
||||
frozen = result(result_id)
|
||||
descriptor = _video_descriptor(frozen, candidate)
|
||||
return FileResponse(
|
||||
frozen["comparison_paths"][candidate],
|
||||
media_type="video/mp4",
|
||||
headers={
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
"ETag": f'"{descriptor["sha256"]}"',
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(frozen: dict[str, Any]) -> dict[str, object]:
|
||||
report = frozen["report"]
|
||||
result_id = frozen["result_id"]
|
||||
videos = {
|
||||
candidate: {
|
||||
"url": f"/api/v1/laboratory/e46g/results/{result_id}/{candidate}.mp4",
|
||||
"media_type": "video/mp4",
|
||||
"byte_length": descriptor["byte_length"],
|
||||
"sha256": descriptor["sha256"],
|
||||
"width": 2880,
|
||||
"height": 544,
|
||||
"duration_seconds": 60.0,
|
||||
"view_order": ["left", "front", "right"],
|
||||
}
|
||||
for candidate in _CANDIDATES
|
||||
for descriptor in (_video_descriptor(frozen, candidate),)
|
||||
}
|
||||
return {
|
||||
"schema_version": "missioncore.e46g-rectified-detector-bakeoff-view/v1",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"status": "selected-for-next-diagnostic-full-route",
|
||||
"selection": copy.deepcopy(report["selection"]),
|
||||
"rectification": copy.deepcopy(report["rectification"]),
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"acceptance": copy.deepcopy(report["acceptance"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"comparison": {
|
||||
"controlled_change": "detector-provider-only",
|
||||
"visual_review": {
|
||||
"status": "selected-for-next-diagnostic",
|
||||
"reviewed_video_seconds": [0.0, 10.0, 20.0, 30.0, 40.0, 50.0],
|
||||
"selected_candidate": "trafficcamnet",
|
||||
"selected_view": "front",
|
||||
"excluded_views": ["left", "right"],
|
||||
"finding": (
|
||||
"The calibrated FRONT projection is useful. TrafficCamNet preserves "
|
||||
"substantially more visible vehicles and people than DashCamNet, while "
|
||||
"DashCamNet repeatedly leaves visible road objects undetected."
|
||||
),
|
||||
"risk": (
|
||||
"TrafficCamNet still produces duplicate or stale boxes around some "
|
||||
"vehicles. LEFT and RIGHT projections are dominated by the camera mount "
|
||||
"and trigger large semantic false tracks, so they are excluded."
|
||||
),
|
||||
"next_action": (
|
||||
"run TrafficCamNet with stock NvDCF over the complete retained FRONT "
|
||||
"projection and review the full temporal overlay before motion-state work"
|
||||
),
|
||||
},
|
||||
"verdict": "select-trafficcamnet-front-only-for-e46h",
|
||||
},
|
||||
"videos": videos,
|
||||
"ground_truth": False,
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"access": "read-only-diagnostic-selection",
|
||||
}
|
||||
|
||||
|
||||
def _video_descriptor(frozen: dict[str, Any], candidate: str) -> dict[str, Any]:
|
||||
descriptor = next(
|
||||
(
|
||||
row
|
||||
for row in frozen["manifest"]["artifacts"]
|
||||
if row.get("role") == f"comparison-video-{candidate}"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not isinstance(descriptor, dict):
|
||||
raise HTTPException(status_code=404, detail="E46G video не найден")
|
||||
return descriptor
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and _RESULT_ID.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,224 @@
|
||||
"""Read-only LAB projection and video delivery for E46H."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.e46h_full_rectified_front_replay import (
|
||||
E46HFullRectifiedFrontReplayError,
|
||||
read_e46h_full_rectified_front_replay,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46h-full-rectified-front-replay-[a-f0-9]{64}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
_REVIEW_WINDOWS = [
|
||||
{
|
||||
"id": "large-false-car-wall",
|
||||
"label": "6.0–10.9 с · false car на стене",
|
||||
"start_seconds": 6.0,
|
||||
"end_seconds": 10.9,
|
||||
"source_track_id": 6,
|
||||
"verdict": "semantic-false-positive",
|
||||
},
|
||||
{
|
||||
"id": "large-false-car-shrub",
|
||||
"label": "178.6–180.3 с · false car на кусте",
|
||||
"start_seconds": 178.6,
|
||||
"end_seconds": 180.3,
|
||||
"source_track_id": 371,
|
||||
"verdict": "semantic-false-positive",
|
||||
},
|
||||
{
|
||||
"id": "large-false-car-ground",
|
||||
"label": "250.7–265.6 с · false car на полотне",
|
||||
"start_seconds": 250.7,
|
||||
"end_seconds": 265.6,
|
||||
"source_track_id": 481,
|
||||
"verdict": "semantic-false-positive",
|
||||
},
|
||||
{
|
||||
"id": "large-false-car-road",
|
||||
"label": "392.2–400.6 с · false car на дороге",
|
||||
"start_seconds": 392.2,
|
||||
"end_seconds": 400.6,
|
||||
"source_track_id": 822,
|
||||
"verdict": "semantic-false-positive",
|
||||
},
|
||||
{
|
||||
"id": "empty-scene-not-blackout",
|
||||
"label": "419.4–426.9 с · пустая сцена, не сбой",
|
||||
"start_seconds": 419.4,
|
||||
"end_seconds": 426.9,
|
||||
"source_track_id": None,
|
||||
"verdict": "empty-scene-expected",
|
||||
},
|
||||
{
|
||||
"id": "large-false-car-terrace",
|
||||
"label": "440.8–448.4 с · false car на террасе",
|
||||
"start_seconds": 440.8,
|
||||
"end_seconds": 448.4,
|
||||
"source_track_id": 927,
|
||||
"verdict": "semantic-false-positive",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def build_e46h_full_rectified_front_replay_router(
|
||||
*, root_provider: RootProvider = lambda: None
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46h", tags=["laboratory"])
|
||||
|
||||
def result(result_id: str) -> dict[str, Any]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46H result не найден")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46H result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46H result не найден")
|
||||
try:
|
||||
return read_e46h_full_rectified_front_replay(path)
|
||||
except (E46HFullRectifiedFrontReplayError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46H result не найден") from None
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _candidates(root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
frozen = result(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(frozen))
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46h-full-rectified-front-replay-catalog/v1",
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only-full-route-diagnostic",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/overlay.mp4")
|
||||
def get_overlay_video(result_id: str) -> FileResponse:
|
||||
frozen = result(result_id)
|
||||
descriptor = _video_descriptor(frozen)
|
||||
return FileResponse(
|
||||
frozen["overlay_path"],
|
||||
media_type="video/mp4",
|
||||
headers={
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
"ETag": f'"{descriptor["sha256"]}"',
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(frozen: dict[str, Any]) -> dict[str, object]:
|
||||
report = frozen["report"]
|
||||
descriptor = _video_descriptor(frozen)
|
||||
result_id = frozen["result_id"]
|
||||
return {
|
||||
"schema_version": "missioncore.e46h-full-rectified-front-replay-view/v1",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"status": "diagnostic-regression-large-semantic-false-tracks",
|
||||
"baseline_result_id": report["baseline_result_id"],
|
||||
"selection": copy.deepcopy(report["selection"]),
|
||||
"rectification": copy.deepcopy(report["rectification"]),
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"acceptance": copy.deepcopy(report["acceptance"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"visual_review": {
|
||||
"status": "full-continuous-and-targeted-review-completed",
|
||||
"complete_video_reviewed": True,
|
||||
"reviewed_video_range_seconds": [0.0, 448.8],
|
||||
"verdict": "useful-front-continuity-but-semantic-regression-blocks-promotion",
|
||||
"review_windows": copy.deepcopy(_REVIEW_WINDOWS),
|
||||
"finding": (
|
||||
"FRONT is readable and most route objects are tracked, but five large false "
|
||||
"car tracks attach to a wall, shrub, road surface and terrace."
|
||||
),
|
||||
"blackout_interpretation": (
|
||||
"The two zero-track intervals at 419.4..426.9 video seconds show an actually "
|
||||
"empty scene and are not object-layer failures."
|
||||
),
|
||||
"next_action": (
|
||||
"do not tune the detector to this route; isolate a provider-level semantic "
|
||||
"suppression or stronger ready model on the same immutable FRONT adapter"
|
||||
),
|
||||
},
|
||||
"video": {
|
||||
"url": f"/api/v1/laboratory/e46h/results/{result_id}/overlay.mp4",
|
||||
"media_type": "video/mp4",
|
||||
"byte_length": descriptor["byte_length"],
|
||||
"sha256": descriptor["sha256"],
|
||||
"width": 960,
|
||||
"height": 544,
|
||||
"duration_seconds": 448.8,
|
||||
},
|
||||
"ground_truth": False,
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"access": "read-only-full-route-diagnostic",
|
||||
}
|
||||
|
||||
|
||||
def _video_descriptor(frozen: dict[str, Any]) -> dict[str, Any]:
|
||||
descriptor = next(
|
||||
(
|
||||
row
|
||||
for row in frozen["manifest"]["artifacts"]
|
||||
if row.get("role") == "visual-overlay-video"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not isinstance(descriptor, dict):
|
||||
raise HTTPException(status_code=404, detail="E46H video не найден")
|
||||
return descriptor
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and _RESULT_ID.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,194 @@
|
||||
"""Read-only LAB projection and visual evidence delivery for E46I."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.e46i_grounding_dino_full_replay import (
|
||||
E46IGroundingDinoFullReplayError,
|
||||
read_e46i_grounding_dino_full_replay,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46i-grounding-dino-full-replay-[a-f0-9]{64}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_e46i_grounding_dino_full_replay_router(
|
||||
*, root_provider: RootProvider = lambda: None
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46i", tags=["laboratory"])
|
||||
|
||||
def result(result_id: str) -> dict[str, Any]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46I result не найден")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46I result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46I result не найден")
|
||||
try:
|
||||
return read_e46i_grounding_dino_full_replay(path)
|
||||
except (E46IGroundingDinoFullReplayError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46I result не найден") from None
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _candidates(root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
frozen = result(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(frozen))
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46i-grounding-dino-full-replay-catalog/v1",
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only-full-route-diagnostic",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/overlay.mp4")
|
||||
def get_overlay_video(result_id: str) -> FileResponse:
|
||||
frozen = result(result_id)
|
||||
descriptor = _artifact_descriptor(frozen, "visual-overlay-video")
|
||||
return _file_response(frozen["overlay_path"], descriptor, "video/mp4")
|
||||
|
||||
@router.get("/results/{result_id}/visual/{role}.png")
|
||||
def get_visual(result_id: str, role: str) -> FileResponse:
|
||||
roles = {
|
||||
"shadow-gate": ("shadow-gate-contact-sheet", "shadow_sheet_path"),
|
||||
"full-route": ("full-route-contact-sheet", "route_sheet_path"),
|
||||
"targeted-windows": (
|
||||
"targeted-windows-contact-sheet",
|
||||
"targeted_sheet_path",
|
||||
),
|
||||
}
|
||||
selected = roles.get(role)
|
||||
if selected is None:
|
||||
raise HTTPException(status_code=404, detail="E46I visual не найден")
|
||||
frozen = result(result_id)
|
||||
descriptor = _artifact_descriptor(frozen, selected[0])
|
||||
return _file_response(frozen[selected[1]], descriptor, "image/png")
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(frozen: dict[str, Any]) -> dict[str, object]:
|
||||
report = frozen["report"]
|
||||
result_id = frozen["result_id"]
|
||||
video = _artifact_descriptor(frozen, "visual-overlay-video")
|
||||
visual_roles = {
|
||||
"shadow_gate": "shadow-gate-contact-sheet",
|
||||
"full_route": "full-route-contact-sheet",
|
||||
"targeted_windows": "targeted-windows-contact-sheet",
|
||||
}
|
||||
visuals = {}
|
||||
for key, role in visual_roles.items():
|
||||
descriptor = _artifact_descriptor(frozen, role)
|
||||
slug = role.removesuffix("-contact-sheet")
|
||||
visuals[key] = {
|
||||
"url": f"/api/v1/laboratory/e46i/results/{result_id}/visual/{slug}.png",
|
||||
"media_type": "image/png",
|
||||
"byte_length": descriptor["byte_length"],
|
||||
"sha256": descriptor["sha256"],
|
||||
}
|
||||
return {
|
||||
"schema_version": "missioncore.e46i-grounding-dino-full-replay-view/v1",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"status": report["status"],
|
||||
"baseline_result_id": report["baseline_result_id"],
|
||||
"source": copy.deepcopy(report["source"]),
|
||||
"provider": copy.deepcopy(report["provider"]),
|
||||
"inference": copy.deepcopy(report["inference"]),
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"shadow_gate": copy.deepcopy(report["shadow_gate"]),
|
||||
"visual_review": copy.deepcopy(report["visual_review"]),
|
||||
"acceptance": copy.deepcopy(report["acceptance"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"video": {
|
||||
"url": f"/api/v1/laboratory/e46i/results/{result_id}/overlay.mp4",
|
||||
"media_type": "video/mp4",
|
||||
"byte_length": video["byte_length"],
|
||||
"sha256": video["sha256"],
|
||||
"width": 960,
|
||||
"height": 544,
|
||||
"frame_rate": 10.0,
|
||||
"frame_count": 4488,
|
||||
"duration_seconds": 448.8,
|
||||
},
|
||||
"visuals": visuals,
|
||||
"ground_truth": False,
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"access": "read-only-full-route-diagnostic",
|
||||
}
|
||||
|
||||
|
||||
def _artifact_descriptor(frozen: dict[str, Any], role: str) -> dict[str, Any]:
|
||||
descriptor = next(
|
||||
(
|
||||
row
|
||||
for row in frozen["manifest"]["artifacts"]
|
||||
if isinstance(row, dict) and row.get("role") == role
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not isinstance(descriptor, dict):
|
||||
raise HTTPException(status_code=404, detail="E46I artifact не найден")
|
||||
return descriptor
|
||||
|
||||
|
||||
def _file_response(path: Path, descriptor: dict[str, Any], media_type: str) -> FileResponse:
|
||||
return FileResponse(
|
||||
path,
|
||||
media_type=media_type,
|
||||
headers={
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
"ETag": f'"{descriptor["sha256"]}"',
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and _RESULT_ID.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,196 @@
|
||||
"""Read-only LAB projection and visual evidence delivery for E46J."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.e46j_raw_fisheye_realtime import (
|
||||
E46JRawFisheyeRealtimeError,
|
||||
read_e46j_raw_fisheye_realtime,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46j-raw-fisheye-realtime-[a-f0-9]{64}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_e46j_raw_fisheye_realtime_router(
|
||||
*, root_provider: RootProvider = lambda: None
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/e46j", tags=["laboratory"])
|
||||
|
||||
def result(result_id: str) -> dict[str, Any]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="E46J result не найден")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="E46J result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="E46J result не найден")
|
||||
try:
|
||||
return read_e46j_raw_fisheye_realtime(path)
|
||||
except (E46JRawFisheyeRealtimeError, OSError, ValueError):
|
||||
raise HTTPException(status_code=404, detail="E46J result не найден") from None
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _candidates(root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
frozen = result(path.name)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(frozen))
|
||||
except HTTPException:
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.e46j-raw-fisheye-realtime-catalog/v1",
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only-full-route-diagnostic",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/overlay.mp4")
|
||||
def get_overlay_video(result_id: str) -> FileResponse:
|
||||
frozen = result(result_id)
|
||||
descriptor = _artifact_descriptor(frozen, "visual-overlay-video")
|
||||
return _file_response(frozen["overlay_path"], descriptor, "video/mp4")
|
||||
|
||||
@router.get("/results/{result_id}/visual/{role}.png")
|
||||
def get_visual(result_id: str, role: str) -> FileResponse:
|
||||
roles = {
|
||||
"full-route": ("full-route-contact-sheet", "route_sheet_path"),
|
||||
"targeted-windows": (
|
||||
"targeted-windows-contact-sheet",
|
||||
"targeted_sheet_path",
|
||||
),
|
||||
"operator-shadow": (
|
||||
"operator-shadow-contact-sheet",
|
||||
"shadow_sheet_path",
|
||||
),
|
||||
}
|
||||
selected = roles.get(role)
|
||||
if selected is None:
|
||||
raise HTTPException(status_code=404, detail="E46J visual не найден")
|
||||
frozen = result(result_id)
|
||||
descriptor = _artifact_descriptor(frozen, selected[0])
|
||||
return _file_response(frozen[selected[1]], descriptor, "image/png")
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project_result(frozen: dict[str, Any]) -> dict[str, object]:
|
||||
report = frozen["report"]
|
||||
result_id = frozen["result_id"]
|
||||
video = _artifact_descriptor(frozen, "visual-overlay-video")
|
||||
visual_roles = {
|
||||
"full_route": "full-route-contact-sheet",
|
||||
"targeted_windows": "targeted-windows-contact-sheet",
|
||||
"operator_shadow": "operator-shadow-contact-sheet",
|
||||
}
|
||||
visuals = {}
|
||||
for key, role in visual_roles.items():
|
||||
descriptor = _artifact_descriptor(frozen, role)
|
||||
slug = role.removesuffix("-contact-sheet")
|
||||
visuals[key] = {
|
||||
"url": f"/api/v1/laboratory/e46j/results/{result_id}/visual/{slug}.png",
|
||||
"media_type": "image/png",
|
||||
"byte_length": descriptor["byte_length"],
|
||||
"sha256": descriptor["sha256"],
|
||||
}
|
||||
return {
|
||||
"schema_version": "missioncore.e46j-raw-fisheye-realtime-view/v1",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"status": report["status"],
|
||||
"source": copy.deepcopy(report["source"]),
|
||||
"detector": copy.deepcopy(report["detector"]),
|
||||
"preprocessing": copy.deepcopy(report["preprocessing"]),
|
||||
"detection": copy.deepcopy(report["detection"]),
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"visual_review": copy.deepcopy(report["visual_review"]),
|
||||
"acceptance": copy.deepcopy(report["acceptance"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"video": {
|
||||
"url": f"/api/v1/laboratory/e46j/results/{result_id}/overlay.mp4",
|
||||
"media_type": "video/mp4",
|
||||
"byte_length": video["byte_length"],
|
||||
"sha256": video["sha256"],
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"frame_rate": 10.003944527024467,
|
||||
"frame_count": 4489,
|
||||
"duration_seconds": 448.723,
|
||||
},
|
||||
"visuals": visuals,
|
||||
"ground_truth": False,
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"access": "read-only-full-route-diagnostic",
|
||||
}
|
||||
|
||||
|
||||
def _artifact_descriptor(frozen: dict[str, Any], role: str) -> dict[str, Any]:
|
||||
descriptor = next(
|
||||
(
|
||||
row
|
||||
for row in frozen["manifest"]["artifacts"]
|
||||
if isinstance(row, dict) and row.get("role") == role
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not isinstance(descriptor, dict):
|
||||
raise HTTPException(status_code=404, detail="E46J artifact не найден")
|
||||
return descriptor
|
||||
|
||||
|
||||
def _file_response(path: Path, descriptor: dict[str, Any], media_type: str) -> FileResponse:
|
||||
return FileResponse(
|
||||
path,
|
||||
media_type=media_type,
|
||||
headers={
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
"ETag": f'"{descriptor["sha256"]}"',
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and _RESULT_ID.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,323 @@
|
||||
"""Read-only API for sealed L3.2 camera-bound PointPillars evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
RESULT_SCHEMA: Final = "missioncore.l32-pointpillars-camera-review/v1"
|
||||
CATALOG_SCHEMA: Final = "missioncore.l32-pointpillars-camera-review-catalog/v1"
|
||||
FRAME_SCHEMA: Final = "missioncore.l32-pointpillars-camera-review-frame/v1"
|
||||
RESULT_PROJECTION_SCHEMA: Final = (
|
||||
"missioncore.l32-pointpillars-camera-review-result/v1"
|
||||
)
|
||||
RESULT_CATALOG_SCHEMA: Final = (
|
||||
"missioncore.l32-pointpillars-camera-review-catalog-results/v1"
|
||||
)
|
||||
RESULT_ID: Final = re.compile(
|
||||
r"^l32-pointpillars-camera-review-[a-f0-9]{64}$"
|
||||
)
|
||||
FRAME_ID: Final = re.compile(r"^[0-9]{6}$")
|
||||
SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
|
||||
MAX_JSON_BYTES: Final = 16 * 1024 * 1024
|
||||
MAX_FRAMES: Final = 18
|
||||
|
||||
|
||||
def build_l32_pointpillars_camera_review_router(
|
||||
*,
|
||||
root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(
|
||||
prefix="/api/v1/laboratory/l32/pointpillars-camera-review",
|
||||
tags=["laboratory"],
|
||||
)
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(
|
||||
limit: int = Query(default=1, ge=1, le=10),
|
||||
) -> dict[str, object]:
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
return _empty_catalog(False)
|
||||
candidates = _candidates(root)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for candidate in candidates:
|
||||
try:
|
||||
items.append(_project_result(candidate))
|
||||
except RuntimeError:
|
||||
invalid_total += 1
|
||||
items.sort(
|
||||
key=lambda item: (str(item["created_at_utc"]), str(item["result_id"])),
|
||||
reverse=True,
|
||||
)
|
||||
return {
|
||||
"schema_version": RESULT_CATALOG_SCHEMA,
|
||||
"configured": True,
|
||||
"items": items[:limit],
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/{result_id}/frames/{frame_id}")
|
||||
def get_frame(result_id: str, frame_id: str) -> dict[str, object]:
|
||||
candidate, descriptor = _resolve_frame(
|
||||
root_provider, result_id=result_id, frame_id=frame_id
|
||||
)
|
||||
path = candidate / str(descriptor["detail_path"])
|
||||
payload = _read_json(path)
|
||||
if (
|
||||
payload.get("schema_version") != FRAME_SCHEMA
|
||||
or payload.get("frame_id") != frame_id
|
||||
or descriptor.get("detail_sha256") != _sha256(path)
|
||||
or descriptor.get("detail_byte_length") != path.stat().st_size
|
||||
or not _valid_frame_payload(payload)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L3.2 frame not found")
|
||||
return {**copy.deepcopy(payload), "access": "read-only"}
|
||||
|
||||
@router.get("/{result_id}/frames/{frame_id}/camera")
|
||||
def get_camera(result_id: str, frame_id: str) -> FileResponse:
|
||||
candidate, descriptor = _resolve_frame(
|
||||
root_provider, result_id=result_id, frame_id=frame_id
|
||||
)
|
||||
path = (candidate / str(descriptor["camera_path"])).resolve()
|
||||
if (
|
||||
not path.is_relative_to(candidate)
|
||||
or path.is_symlink()
|
||||
or not path.is_file()
|
||||
or descriptor.get("camera_sha256") != _sha256(path)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L3.2 camera not found")
|
||||
return FileResponse(path, media_type="image/jpeg")
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def latest_l32_identity(root_provider: RootProvider) -> dict[str, str] | None:
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
return None
|
||||
valid: list[dict[str, object]] = []
|
||||
for candidate in _candidates(root):
|
||||
try:
|
||||
valid.append(_project_result(candidate))
|
||||
except RuntimeError:
|
||||
continue
|
||||
if not valid:
|
||||
return None
|
||||
latest = max(
|
||||
valid,
|
||||
key=lambda item: (str(item["created_at_utc"]), str(item["result_id"])),
|
||||
)
|
||||
return {
|
||||
"result_id": str(latest["result_id"]),
|
||||
"created_at_utc": str(latest["created_at_utc"]),
|
||||
}
|
||||
|
||||
|
||||
def _resolve_frame(
|
||||
root_provider: RootProvider,
|
||||
*,
|
||||
result_id: str,
|
||||
frame_id: str,
|
||||
) -> tuple[Path, dict[str, Any]]:
|
||||
if not RESULT_ID.fullmatch(result_id) or not FRAME_ID.fullmatch(frame_id):
|
||||
raise HTTPException(status_code=404, detail="L3.2 frame not found")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="L3.2 result not found")
|
||||
candidate = root / result_id
|
||||
try:
|
||||
result = _load_result(candidate)
|
||||
descriptor = next(
|
||||
item for item in result["catalog"]["frames"]
|
||||
if item["frame_id"] == frame_id
|
||||
)
|
||||
except (RuntimeError, StopIteration):
|
||||
raise HTTPException(status_code=404, detail="L3.2 frame not found") from None
|
||||
return candidate, descriptor
|
||||
|
||||
|
||||
def _project_result(candidate: Path) -> dict[str, object]:
|
||||
result = _load_result(candidate)
|
||||
manifest = result["manifest"]
|
||||
identity = manifest["identity"]
|
||||
return {
|
||||
"schema_version": RESULT_PROJECTION_SCHEMA,
|
||||
"result_id": manifest["result_id"],
|
||||
"created_at_utc": manifest["created_at_utc"],
|
||||
"status": "camera-bound-review-rejects-current-candidate",
|
||||
"source_session_id": identity["source_session_id"],
|
||||
"source_l31_result_id": identity["source_l31_result_id"],
|
||||
"source_e10_pack_id": identity["source_e10_pack_id"],
|
||||
"projection": copy.deepcopy(identity["projection"]),
|
||||
"metrics": copy.deepcopy(manifest["metrics"]),
|
||||
"frames": copy.deepcopy(result["catalog"]["frames"]),
|
||||
"limitations": copy.deepcopy(manifest["limitations"]),
|
||||
"authority": copy.deepcopy(manifest["authority"]),
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _load_result(candidate: Path) -> dict[str, Any]:
|
||||
if (
|
||||
not candidate.is_dir()
|
||||
or candidate.is_symlink()
|
||||
or not RESULT_ID.fullmatch(candidate.name)
|
||||
):
|
||||
raise RuntimeError("L3.2 candidate is invalid")
|
||||
manifest = _read_json(candidate / "manifest.json")
|
||||
identity = manifest.get("identity")
|
||||
authority = manifest.get("authority")
|
||||
catalog_descriptor = manifest.get("catalog")
|
||||
if (
|
||||
manifest.get("schema_version") != RESULT_SCHEMA
|
||||
or manifest.get("result_id") != candidate.name
|
||||
or manifest.get("status") != "camera-bound-review-rejects-current-candidate"
|
||||
or not isinstance(manifest.get("created_at_utc"), str)
|
||||
or not isinstance(identity, dict)
|
||||
or identity.get("schema_version") != RESULT_SCHEMA
|
||||
or identity.get("source_session_id") != "20260720T065719Z_viewer_live"
|
||||
or manifest.get("identity_sha256")
|
||||
!= hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
or candidate.name
|
||||
!= f"l32-pointpillars-camera-review-{manifest.get('identity_sha256')}"
|
||||
or not isinstance(authority, dict)
|
||||
or authority.get("shadow_only") is not True
|
||||
or authority.get("accuracy_accepted") is not False
|
||||
or authority.get("commands_enabled") is not False
|
||||
or not isinstance(manifest.get("metrics"), dict)
|
||||
or not isinstance(manifest.get("limitations"), list)
|
||||
or not isinstance(catalog_descriptor, dict)
|
||||
or catalog_descriptor.get("path") != "catalog.json"
|
||||
):
|
||||
raise RuntimeError("L3.2 manifest is invalid")
|
||||
catalog_path = candidate / "catalog.json"
|
||||
if (
|
||||
catalog_descriptor.get("sha256") != _sha256(catalog_path)
|
||||
or catalog_descriptor.get("byte_length") != catalog_path.stat().st_size
|
||||
):
|
||||
raise RuntimeError("L3.2 catalog changed")
|
||||
catalog = _read_json(catalog_path)
|
||||
frames = catalog.get("frames")
|
||||
if (
|
||||
catalog.get("schema_version") != CATALOG_SCHEMA
|
||||
or catalog.get("result_id") != candidate.name
|
||||
or not isinstance(frames, list)
|
||||
or not 1 <= len(frames) <= MAX_FRAMES
|
||||
or catalog.get("frame_count") != len(frames)
|
||||
or len({item.get("frame_id") for item in frames if isinstance(item, dict)})
|
||||
!= len(frames)
|
||||
or any(not _valid_descriptor(item) for item in frames)
|
||||
):
|
||||
raise RuntimeError("L3.2 catalog is invalid")
|
||||
return {"manifest": manifest, "catalog": catalog}
|
||||
|
||||
|
||||
def _valid_descriptor(value: object) -> bool:
|
||||
if not isinstance(value, dict):
|
||||
return False
|
||||
frame_id = value.get("frame_id")
|
||||
return (
|
||||
isinstance(frame_id, str)
|
||||
and FRAME_ID.fullmatch(frame_id) is not None
|
||||
and value.get("detail_path") == f"frame-{frame_id}.json"
|
||||
and isinstance(value.get("detail_sha256"), str)
|
||||
and SHA256.fullmatch(value["detail_sha256"]) is not None
|
||||
and isinstance(value.get("detail_byte_length"), int)
|
||||
and 0 < value["detail_byte_length"] <= MAX_JSON_BYTES
|
||||
and isinstance(value.get("camera_path"), str)
|
||||
and value["camera_path"].startswith("frames/frame-")
|
||||
and isinstance(value.get("camera_sha256"), str)
|
||||
and SHA256.fullmatch(value["camera_sha256"]) is not None
|
||||
and isinstance(value.get("camera_delta_ms"), (int, float))
|
||||
and abs(float(value["camera_delta_ms"])) <= 100.0
|
||||
)
|
||||
|
||||
|
||||
def _valid_frame_payload(value: dict[str, Any]) -> bool:
|
||||
projection = value.get("camera_projection")
|
||||
camera = value.get("camera")
|
||||
interpretation = value.get("interpretation")
|
||||
return (
|
||||
isinstance(value.get("summary"), dict)
|
||||
and isinstance(value.get("points"), dict)
|
||||
and isinstance(value.get("prediction_boxes"), list)
|
||||
and isinstance(projection, dict)
|
||||
and projection.get("point_layout") == "flat-xy-depth-m"
|
||||
and isinstance(projection.get("point_count"), int)
|
||||
and isinstance(projection.get("point_values"), list)
|
||||
and len(projection["point_values"]) == projection["point_count"] * 3
|
||||
and isinstance(projection.get("boxes"), list)
|
||||
and isinstance(camera, dict)
|
||||
and camera.get("media_type") == "image/jpeg"
|
||||
and camera.get("exact_source_frame") is True
|
||||
and isinstance(interpretation, dict)
|
||||
and interpretation.get("camera_is_semantic_reference") is True
|
||||
and interpretation.get("boxes_are_model_hypotheses") is True
|
||||
and interpretation.get("accuracy_claim_allowed") is False
|
||||
)
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
configured = provider()
|
||||
if configured is None:
|
||||
return None
|
||||
root = configured.expanduser().resolve()
|
||||
return root if root.is_dir() and not root.is_symlink() else None
|
||||
|
||||
|
||||
def _candidates(root: Path) -> list[Path]:
|
||||
return [
|
||||
item for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and RESULT_ID.fullmatch(item.name)
|
||||
]
|
||||
|
||||
|
||||
def _empty_catalog(configured: bool) -> dict[str, object]:
|
||||
return {
|
||||
"schema_version": RESULT_CATALOG_SCHEMA,
|
||||
"configured": configured,
|
||||
"items": [],
|
||||
"candidate_total": 0,
|
||||
"invalid_total": 0,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
if path.is_symlink() or not path.is_file() or path.stat().st_size > MAX_JSON_BYTES:
|
||||
raise RuntimeError("L3.2 JSON artifact is invalid")
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise RuntimeError("L3.2 JSON artifact is invalid") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise RuntimeError("L3.2 JSON artifact is invalid")
|
||||
return value
|
||||
|
||||
|
||||
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:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
while chunk := stream.read(1024 * 1024):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
@@ -0,0 +1,394 @@
|
||||
"""Read-only API for sealed L3.3 camera-first detector evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.l33_camera_first_admission import is_admitted_l33_identity
|
||||
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
RESULT_SCHEMA: Final = "missioncore.l33-camera-first-detector-review/v1"
|
||||
CATALOG_SCHEMA: Final = "missioncore.l33-camera-first-detector-review-catalog/v1"
|
||||
FRAME_SCHEMA: Final = "missioncore.l33-camera-first-detector-review-frame/v1"
|
||||
RESULT_PROJECTION_SCHEMA: Final = "missioncore.l33-camera-first-detector-review-result/v1"
|
||||
RESULT_CATALOG_SCHEMA: Final = "missioncore.l33-camera-first-detector-review-catalog-results/v1"
|
||||
RESULT_ID: Final = re.compile(r"^l33-camera-first-detector-review-[a-f0-9]{64}$")
|
||||
FRAME_ID: Final = re.compile(r"^[0-9]{6}$")
|
||||
SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
|
||||
MAX_JSON_BYTES: Final = 16 * 1024 * 1024
|
||||
MAX_FRAMES: Final = 18
|
||||
|
||||
|
||||
def build_l33_camera_first_detector_review_router(
|
||||
*,
|
||||
root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(
|
||||
prefix="/api/v1/laboratory/l33/camera-first-detector-review",
|
||||
tags=["laboratory"],
|
||||
)
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
return _empty_catalog(False)
|
||||
candidates = _candidates(root)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for candidate in candidates:
|
||||
try:
|
||||
items.append(_project_result(candidate))
|
||||
except RuntimeError:
|
||||
invalid_total += 1
|
||||
items.sort(
|
||||
key=lambda item: (str(item["created_at_utc"]), str(item["result_id"])),
|
||||
reverse=True,
|
||||
)
|
||||
return {
|
||||
"schema_version": RESULT_CATALOG_SCHEMA,
|
||||
"configured": True,
|
||||
"items": items[:limit],
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/{result_id}/frames/{frame_id}")
|
||||
def get_frame(result_id: str, frame_id: str) -> dict[str, object]:
|
||||
candidate, descriptor = _resolve_frame(
|
||||
root_provider, result_id=result_id, frame_id=frame_id
|
||||
)
|
||||
path = candidate / str(descriptor["detail_path"])
|
||||
payload = _read_json(path)
|
||||
if (
|
||||
payload.get("schema_version") != FRAME_SCHEMA
|
||||
or payload.get("frame_id") != frame_id
|
||||
or descriptor.get("detail_sha256") != _sha256(path)
|
||||
or descriptor.get("detail_byte_length") != path.stat().st_size
|
||||
or not _valid_frame_payload(payload)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L3.3 frame not found")
|
||||
return {**copy.deepcopy(payload), "access": "read-only"}
|
||||
|
||||
@router.get("/{result_id}/frames/{frame_id}/camera")
|
||||
def get_camera(result_id: str, frame_id: str) -> FileResponse:
|
||||
candidate, descriptor = _resolve_frame(
|
||||
root_provider, result_id=result_id, frame_id=frame_id
|
||||
)
|
||||
path = (candidate / str(descriptor["camera_path"])).resolve()
|
||||
if (
|
||||
not path.is_relative_to(candidate)
|
||||
or path.is_symlink()
|
||||
or not path.is_file()
|
||||
or descriptor.get("camera_sha256") != _sha256(path)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L3.3 camera not found")
|
||||
return FileResponse(path, media_type="image/jpeg")
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def latest_l33_identity(root_provider: RootProvider) -> dict[str, str] | None:
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
return None
|
||||
valid: list[dict[str, object]] = []
|
||||
for candidate in _candidates(root):
|
||||
try:
|
||||
valid.append(_project_result(candidate))
|
||||
except RuntimeError:
|
||||
continue
|
||||
if not valid:
|
||||
return None
|
||||
latest = max(
|
||||
valid,
|
||||
key=lambda item: (str(item["created_at_utc"]), str(item["result_id"])),
|
||||
)
|
||||
return {
|
||||
"result_id": str(latest["result_id"]),
|
||||
"created_at_utc": str(latest["created_at_utc"]),
|
||||
}
|
||||
|
||||
|
||||
def _resolve_frame(
|
||||
root_provider: RootProvider,
|
||||
*,
|
||||
result_id: str,
|
||||
frame_id: str,
|
||||
) -> tuple[Path, dict[str, Any]]:
|
||||
if not RESULT_ID.fullmatch(result_id) or not FRAME_ID.fullmatch(frame_id):
|
||||
raise HTTPException(status_code=404, detail="L3.3 frame not found")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="L3.3 result not found")
|
||||
candidate = root / result_id
|
||||
try:
|
||||
result = _load_result(candidate)
|
||||
descriptor = next(
|
||||
item for item in result["catalog"]["frames"] if item["frame_id"] == frame_id
|
||||
)
|
||||
except (RuntimeError, StopIteration):
|
||||
raise HTTPException(status_code=404, detail="L3.3 frame not found") from None
|
||||
return candidate, descriptor
|
||||
|
||||
|
||||
def _project_result(candidate: Path) -> dict[str, object]:
|
||||
result = _load_result(candidate)
|
||||
manifest = result["manifest"]
|
||||
identity = manifest["identity"]
|
||||
return {
|
||||
"schema_version": RESULT_PROJECTION_SCHEMA,
|
||||
"result_id": manifest["result_id"],
|
||||
"created_at_utc": manifest["created_at_utc"],
|
||||
"status": manifest["status"],
|
||||
"source_session_id": identity["source_session_id"],
|
||||
"source_l32_result_id": identity["source_l32_result_id"],
|
||||
"source_e26_result_id": identity["source_e26_result_id"],
|
||||
"source_e29_result_id": identity["source_e29_result_id"],
|
||||
"source_e10_pack_id": identity["source_e10_pack_id"],
|
||||
"source_world_state_result_id": identity.get("source_world_state_result_id"),
|
||||
"detector": copy.deepcopy(identity["detector"]),
|
||||
"semantic_contract": copy.deepcopy(identity["semantic_contract"]),
|
||||
"geometry_contract": copy.deepcopy(identity["geometry_contract"]),
|
||||
"metrics": copy.deepcopy(manifest["metrics"]),
|
||||
"frames": copy.deepcopy(result["catalog"]["frames"]),
|
||||
"limitations": copy.deepcopy(manifest["limitations"]),
|
||||
"authority": copy.deepcopy(manifest["authority"]),
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _load_result(candidate: Path) -> dict[str, Any]:
|
||||
if (
|
||||
not candidate.is_dir()
|
||||
or candidate.is_symlink()
|
||||
or not RESULT_ID.fullmatch(candidate.name)
|
||||
):
|
||||
raise RuntimeError("L3.3 candidate is invalid")
|
||||
manifest = _read_json(candidate / "manifest.json")
|
||||
identity = manifest.get("identity")
|
||||
authority = manifest.get("authority")
|
||||
catalog_descriptor = manifest.get("catalog")
|
||||
if (
|
||||
manifest.get("schema_version") != RESULT_SCHEMA
|
||||
or manifest.get("result_id") != candidate.name
|
||||
or manifest.get("status") != "camera-first-boundary-restored-shadow-only"
|
||||
or not isinstance(manifest.get("created_at_utc"), str)
|
||||
or manifest.get("ground_truth") is not False
|
||||
or not isinstance(identity, dict)
|
||||
or identity.get("schema_version") != RESULT_SCHEMA
|
||||
or not is_admitted_l33_identity(identity)
|
||||
or manifest.get("identity_sha256")
|
||||
!= hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
or candidate.name
|
||||
!= f"l33-camera-first-detector-review-{manifest.get('identity_sha256')}"
|
||||
or not isinstance(authority, dict)
|
||||
or authority.get("shadow_only") is not True
|
||||
or authority.get("accuracy_accepted") is not False
|
||||
or authority.get("commands_enabled") is not False
|
||||
or authority.get("navigation_or_safety_accepted") is not False
|
||||
or not isinstance(manifest.get("metrics"), dict)
|
||||
or not isinstance(manifest.get("limitations"), list)
|
||||
or not isinstance(catalog_descriptor, dict)
|
||||
or catalog_descriptor.get("path") != "catalog.json"
|
||||
):
|
||||
raise RuntimeError("L3.3 manifest is invalid")
|
||||
catalog_path = candidate / "catalog.json"
|
||||
if (
|
||||
catalog_descriptor.get("sha256") != _sha256(catalog_path)
|
||||
or catalog_descriptor.get("byte_length") != catalog_path.stat().st_size
|
||||
):
|
||||
raise RuntimeError("L3.3 catalog changed")
|
||||
catalog = _read_json(catalog_path)
|
||||
frames = catalog.get("frames")
|
||||
if (
|
||||
catalog.get("schema_version") != CATALOG_SCHEMA
|
||||
or catalog.get("result_id") != candidate.name
|
||||
or not isinstance(frames, list)
|
||||
or not 1 <= len(frames) <= MAX_FRAMES
|
||||
or catalog.get("frame_count") != len(frames)
|
||||
or len({item.get("frame_id") for item in frames if isinstance(item, dict)})
|
||||
!= len(frames)
|
||||
or any(not _valid_descriptor(item) for item in frames)
|
||||
):
|
||||
raise RuntimeError("L3.3 catalog is invalid")
|
||||
return {"manifest": manifest, "catalog": catalog}
|
||||
|
||||
|
||||
def _valid_descriptor(value: object) -> bool:
|
||||
if not isinstance(value, dict):
|
||||
return False
|
||||
frame_id = value.get("frame_id")
|
||||
return (
|
||||
isinstance(frame_id, str)
|
||||
and FRAME_ID.fullmatch(frame_id) is not None
|
||||
and value.get("detail_path") == f"frame-{frame_id}.json"
|
||||
and isinstance(value.get("detail_sha256"), str)
|
||||
and SHA256.fullmatch(value["detail_sha256"]) is not None
|
||||
and isinstance(value.get("detail_byte_length"), int)
|
||||
and 0 < value["detail_byte_length"] <= MAX_JSON_BYTES
|
||||
and isinstance(value.get("camera_path"), str)
|
||||
and value["camera_path"].startswith("frames/frame-")
|
||||
and isinstance(value.get("camera_sha256"), str)
|
||||
and SHA256.fullmatch(value["camera_sha256"]) is not None
|
||||
and value.get("semantic_provenance")
|
||||
in (
|
||||
"recorded-current",
|
||||
"bounded-track-interpolation",
|
||||
"rectified-detector-current",
|
||||
"e23-temporal-hold",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _valid_frame_payload(value: dict[str, Any]) -> bool:
|
||||
projection = value.get("camera_projection")
|
||||
camera = value.get("camera")
|
||||
detections = value.get("detections")
|
||||
prediction_boxes = value.get("prediction_boxes", [])
|
||||
interpretation = value.get("interpretation")
|
||||
return (
|
||||
isinstance(value.get("summary"), dict)
|
||||
and isinstance(value.get("points"), dict)
|
||||
and isinstance(projection, dict)
|
||||
and projection.get("point_layout") == "flat-xy-depth-m"
|
||||
and isinstance(projection.get("point_count"), int)
|
||||
and isinstance(projection.get("point_values"), list)
|
||||
and len(projection["point_values"]) == projection["point_count"] * 3
|
||||
and isinstance(camera, dict)
|
||||
and camera.get("media_type") == "image/jpeg"
|
||||
and camera.get("exact_source_frame") is True
|
||||
and isinstance(detections, list)
|
||||
and all(_valid_detection(item) for item in detections)
|
||||
and isinstance(prediction_boxes, list)
|
||||
and all(_valid_prediction_box(item) for item in prediction_boxes)
|
||||
and isinstance(interpretation, dict)
|
||||
and interpretation.get("camera_owns_semantic_identity") is True
|
||||
and interpretation.get("lidar_owns_metric_range") is True
|
||||
and interpretation.get("missing_lidar_erases_camera_detection") is False
|
||||
and interpretation.get("ground_truth_available") is False
|
||||
and interpretation.get("accuracy_claim_allowed") is False
|
||||
)
|
||||
|
||||
|
||||
def _valid_detection(value: object) -> bool:
|
||||
if not isinstance(value, dict):
|
||||
return False
|
||||
bbox = value.get("bbox_xyxy")
|
||||
camera_ray = value.get("camera_ray_lidar")
|
||||
geometry_anchor = value.get("geometry_anchor_xyz_m")
|
||||
return (
|
||||
isinstance(value.get("source_track_id"), int)
|
||||
and isinstance(value.get("label"), str)
|
||||
and isinstance(value.get("score"), (int, float))
|
||||
and 0.25 <= float(value["score"]) <= 1.0
|
||||
and isinstance(bbox, list)
|
||||
and len(bbox) == 4
|
||||
and all(isinstance(item, (int, float)) for item in bbox)
|
||||
and value.get("semantic_provenance")
|
||||
in (
|
||||
"recorded-current",
|
||||
"bounded-track-interpolation",
|
||||
"rectified-detector-current",
|
||||
"e23-temporal-hold",
|
||||
)
|
||||
and isinstance(value.get("geometry_status"), str)
|
||||
and (value.get("range_m") is None or isinstance(value.get("range_m"), (int, float)))
|
||||
and (
|
||||
value.get("source_e29_optical_depth_m") is None
|
||||
or isinstance(value.get("source_e29_optical_depth_m"), (int, float))
|
||||
)
|
||||
and isinstance(camera_ray, dict)
|
||||
and _valid_vector(camera_ray.get("origin_xyz_m"), 3)
|
||||
and _valid_vector(camera_ray.get("direction_xyz"), 3)
|
||||
and (geometry_anchor is None or _valid_vector(geometry_anchor, 3))
|
||||
and ((value.get("range_m") is None) == (geometry_anchor is None))
|
||||
)
|
||||
|
||||
|
||||
def _valid_prediction_box(value: object) -> bool:
|
||||
if not isinstance(value, dict):
|
||||
return False
|
||||
return (
|
||||
isinstance(value.get("benchmark_class"), str)
|
||||
and _valid_vector(value.get("center_xyz_m"), 3)
|
||||
and _valid_vector(value.get("size_lwh_m"), 3)
|
||||
and all(float(item) > 0 for item in value["size_lwh_m"])
|
||||
and isinstance(value.get("yaw_rad"), (int, float))
|
||||
and value.get("status") == "model-prediction"
|
||||
and isinstance(value.get("score"), (int, float))
|
||||
and 0 <= float(value["score"]) <= 1
|
||||
and isinstance(value.get("track_id"), int)
|
||||
and isinstance(value.get("temporal_status"), str)
|
||||
)
|
||||
|
||||
|
||||
def _valid_vector(value: object, length: int) -> bool:
|
||||
return (
|
||||
isinstance(value, list)
|
||||
and len(value) == length
|
||||
and all(isinstance(item, (int, float)) for item in value)
|
||||
)
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
configured = provider()
|
||||
if configured is None:
|
||||
return None
|
||||
root = configured.expanduser().resolve()
|
||||
return root if root.is_dir() and not root.is_symlink() else None
|
||||
|
||||
|
||||
def _candidates(root: Path) -> list[Path]:
|
||||
return [
|
||||
item for item in root.iterdir()
|
||||
if item.is_dir() and not item.is_symlink() and RESULT_ID.fullmatch(item.name)
|
||||
]
|
||||
|
||||
|
||||
def _empty_catalog(configured: bool) -> dict[str, object]:
|
||||
return {
|
||||
"schema_version": RESULT_CATALOG_SCHEMA,
|
||||
"configured": configured,
|
||||
"items": [],
|
||||
"candidate_total": 0,
|
||||
"invalid_total": 0,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
if path.is_symlink() or not path.is_file() or path.stat().st_size > MAX_JSON_BYTES:
|
||||
raise RuntimeError("L3.3 JSON artifact is invalid")
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise RuntimeError("L3.3 JSON artifact is invalid") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise RuntimeError("L3.3 JSON artifact is invalid")
|
||||
return value
|
||||
|
||||
|
||||
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:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,450 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import struct
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.e46_detector_truth_island import (
|
||||
E46_MANIFEST_NAME,
|
||||
E46_REFERENCES_NAME,
|
||||
E46DetectorTruthIslandError,
|
||||
read_e46_detector_truth_island,
|
||||
)
|
||||
from k1link.compute.l34_right_yolox_truth_island_freeze import (
|
||||
L34_MANIFEST_NAME,
|
||||
L34_PREDICTION_SCHEMA,
|
||||
L34RightYoloxTruthIsland,
|
||||
L34RightYoloxTruthIslandError,
|
||||
read_l34_right_yolox_truth_island_freeze,
|
||||
)
|
||||
|
||||
RootProvider = Callable[[], Path | None]
|
||||
_RESULT_ID = re.compile(r"^l34-right-yolox-truth-island-freeze-[a-f0-9]{64}$")
|
||||
_TRUTH_ID = re.compile(r"^e46-detector-truth-island-[a-f0-9]{64}$")
|
||||
_PACK_ID = re.compile(r"^evaluation-pack-[a-f0-9]{64}$")
|
||||
_PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
|
||||
_MAX_VISUAL_FRAMES = 32
|
||||
|
||||
|
||||
def latest_l34_identity(provider: RootProvider) -> dict[str, object] | None:
|
||||
for candidate in _candidates(provider):
|
||||
try:
|
||||
result = read_l34_right_yolox_truth_island_freeze(candidate)
|
||||
except L34RightYoloxTruthIslandError:
|
||||
continue
|
||||
created_at_utc = result.manifest.get("created_at_utc")
|
||||
if not isinstance(created_at_utc, str) or not created_at_utc.strip():
|
||||
continue
|
||||
return {
|
||||
"result_id": result.result_id,
|
||||
"created_at_utc": created_at_utc,
|
||||
"access": "read-only",
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def build_l34_right_yolox_truth_island_freeze_router(
|
||||
*,
|
||||
root_provider: RootProvider = lambda: None,
|
||||
truth_root_provider: RootProvider = lambda: None,
|
||||
evaluation_pack_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
|
||||
|
||||
@router.get("/l34/results")
|
||||
def list_l34_results(
|
||||
limit: int = Query(default=1, ge=1, le=10),
|
||||
) -> dict[str, object]:
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for candidate in _candidates(root_provider):
|
||||
try:
|
||||
result = read_l34_right_yolox_truth_island_freeze(candidate)
|
||||
items.append(_project(result))
|
||||
except L34RightYoloxTruthIslandError:
|
||||
invalid_total += 1
|
||||
continue
|
||||
if len(items) >= limit:
|
||||
break
|
||||
return {
|
||||
"schema_version": (
|
||||
"missioncore.l34-right-yolox-truth-island-catalog/v1"
|
||||
),
|
||||
"configured": _configured_root(root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(items),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34/results/{result_id}/frames/{sequence}")
|
||||
def get_l34_frame(result_id: str, sequence: int) -> dict[str, object]:
|
||||
result, prediction, reference, image_path = _resolve_visual_frame(
|
||||
root_provider=root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
width, height = _png_dimensions(image_path)
|
||||
predictions = _validated_predictions(prediction, width=width, height=height)
|
||||
identity = result.manifest["identity"]
|
||||
candidate = identity["candidate"]
|
||||
return {
|
||||
"schema_version": "missioncore.l34-right-yolox-truth-island-frame/v1",
|
||||
"result_id": result.result_id,
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": prediction["image_id"],
|
||||
"frame_index": prediction["frame_index"],
|
||||
"group_id": prediction["group_id"],
|
||||
"role": reference["role"],
|
||||
"session_seconds": reference["session_seconds"],
|
||||
"camera": {
|
||||
"width": width,
|
||||
"height": height,
|
||||
"byte_length": image_path.stat().st_size,
|
||||
"sha256": reference["sha256"],
|
||||
},
|
||||
"prediction_rows_sha256": candidate["prediction_rows_sha256"],
|
||||
"predictions": predictions,
|
||||
"truth_labels_read": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34/results/{result_id}/frames/{sequence}/camera")
|
||||
def get_l34_camera(result_id: str, sequence: int) -> FileResponse:
|
||||
_, _, _, image_path = _resolve_visual_frame(
|
||||
root_provider=root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return FileResponse(image_path, media_type="image/png")
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project(result: L34RightYoloxTruthIsland) -> dict[str, object]:
|
||||
identity = result.manifest["identity"]
|
||||
report = result.report
|
||||
return {
|
||||
"result_id": result.result_id,
|
||||
"created_at_utc": result.manifest["created_at_utc"],
|
||||
"status": report["status"],
|
||||
"profile_id": report["profile_id"],
|
||||
"pipeline_id": report["pipeline_id"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"candidate": {
|
||||
**copy.deepcopy(identity["profile"]["candidate"]),
|
||||
"l33_result_id": identity["candidate"]["l33_result_id"],
|
||||
},
|
||||
"truth_island": {
|
||||
"result_id": identity["truth_island"]["result_id"],
|
||||
"truth_state": identity["truth_island"]["truth_state"],
|
||||
},
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"frames": [_frame_summary(row) for row in result.predictions],
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _frame_summary(row: dict[str, Any]) -> dict[str, object]:
|
||||
sequence = _nonnegative_integer(row.get("truth_island_sequence"), "sequence")
|
||||
image_id = _nonnegative_integer(row.get("image_id"), "image id")
|
||||
frame_index = _nonnegative_integer(row.get("frame_index"), "frame index")
|
||||
group_id = row.get("group_id")
|
||||
predictions = row.get("predictions")
|
||||
if (
|
||||
row.get("schema_version") != L34_PREDICTION_SCHEMA
|
||||
or not 1 <= sequence <= _MAX_VISUAL_FRAMES
|
||||
or not isinstance(group_id, str)
|
||||
or not group_id.strip()
|
||||
or not isinstance(predictions, list)
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError("L34 visual frame is invalid")
|
||||
scores = [
|
||||
float(prediction["score"])
|
||||
for prediction in predictions
|
||||
if isinstance(prediction, dict)
|
||||
and isinstance(prediction.get("score"), (int, float))
|
||||
and not isinstance(prediction.get("score"), bool)
|
||||
]
|
||||
if len(scores) != len(predictions) or any(not math.isfinite(score) for score in scores):
|
||||
raise L34RightYoloxTruthIslandError("L34 visual scores are invalid")
|
||||
return {
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": image_id,
|
||||
"frame_index": frame_index,
|
||||
"group_id": group_id,
|
||||
"prediction_count": len(predictions),
|
||||
"maximum_score": max(scores, default=0.0),
|
||||
}
|
||||
|
||||
|
||||
def _resolve_visual_frame(
|
||||
*,
|
||||
root_provider: RootProvider,
|
||||
truth_root_provider: RootProvider,
|
||||
evaluation_pack_root_provider: RootProvider,
|
||||
result_id: str,
|
||||
sequence: int,
|
||||
) -> tuple[L34RightYoloxTruthIsland, dict[str, Any], dict[str, Any], Path]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None or not 1 <= sequence <= _MAX_VISUAL_FRAMES:
|
||||
raise HTTPException(status_code=404, detail="L34 frame not found")
|
||||
result_root = _configured_root(root_provider)
|
||||
if result_root is None:
|
||||
raise HTTPException(status_code=404, detail="L34 result not found")
|
||||
result_path = (result_root / result_id).resolve()
|
||||
if result_path.parent != result_root or result_path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="L34 result not found")
|
||||
try:
|
||||
result = read_l34_right_yolox_truth_island_freeze(result_path)
|
||||
except (L34RightYoloxTruthIslandError, OSError):
|
||||
raise HTTPException(status_code=404, detail="L34 result not found") from None
|
||||
prediction = next(
|
||||
(
|
||||
row
|
||||
for row in result.predictions
|
||||
if row.get("truth_island_sequence") == sequence
|
||||
),
|
||||
None,
|
||||
)
|
||||
if prediction is None:
|
||||
raise HTTPException(status_code=404, detail="L34 frame not found")
|
||||
|
||||
identity = result.manifest["identity"]
|
||||
truth_identity = identity["truth_island"]
|
||||
truth_id = truth_identity.get("result_id")
|
||||
truth_root = _configured_root(truth_root_provider)
|
||||
if (
|
||||
truth_root is None
|
||||
or not isinstance(truth_id, str)
|
||||
or _TRUTH_ID.fullmatch(truth_id) is None
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L34 truth island not found")
|
||||
truth_path = (truth_root / truth_id).resolve()
|
||||
if truth_path.parent != truth_root or truth_path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="L34 truth island not found")
|
||||
try:
|
||||
truth = read_e46_detector_truth_island(truth_path)
|
||||
except (E46DetectorTruthIslandError, OSError):
|
||||
raise HTTPException(status_code=404, detail="L34 truth island not found") from None
|
||||
if _sha256(truth.result_root / E46_MANIFEST_NAME) != truth_identity.get(
|
||||
"manifest_sha256"
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L34 truth island changed")
|
||||
references = _read_jsonl(truth.result_root / E46_REFERENCES_NAME)
|
||||
reference = next(
|
||||
(row for row in references if row.get("truth_island_sequence") == sequence),
|
||||
None,
|
||||
)
|
||||
if (
|
||||
reference is None
|
||||
or prediction.get("candidate_id") != "yolox-s-kb4-core3"
|
||||
or prediction.get("truth_joined") is not False
|
||||
or reference.get("frame_index") != prediction.get("frame_index")
|
||||
or reference.get("image_id") != prediction.get("image_id")
|
||||
or reference.get("group_id") != prediction.get("group_id")
|
||||
or reference.get("session_seconds")
|
||||
!= prediction.get("session_seconds")
|
||||
or reference.get("sha256")
|
||||
!= prediction.get("source_image_sha256")
|
||||
or reference.get("role") not in {"anchor", "temporal"}
|
||||
or not isinstance(reference.get("session_seconds"), (int, float))
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L34 frame binding is invalid")
|
||||
|
||||
truth_source = truth.manifest["identity"]["source"]
|
||||
pack_id = truth_source.get("evaluation_pack_id")
|
||||
pack_root = _configured_root(evaluation_pack_root_provider)
|
||||
if (
|
||||
pack_root is None
|
||||
or not isinstance(pack_id, str)
|
||||
or _PACK_ID.fullmatch(pack_id) is None
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L34 image source not found")
|
||||
pack_path = (pack_root / pack_id).resolve()
|
||||
if pack_path.parent != pack_root or pack_path.is_symlink() or not pack_path.is_dir():
|
||||
raise HTTPException(status_code=404, detail="L34 image source not found")
|
||||
relative = reference.get("source_path")
|
||||
if not isinstance(relative, str):
|
||||
raise HTTPException(status_code=404, detail="L34 image source not found")
|
||||
pure = PurePosixPath(relative)
|
||||
if (
|
||||
pure.is_absolute()
|
||||
or ".." in pure.parts
|
||||
or pure.parts[:2] != ("images", "valid-fov-fill")
|
||||
or pure.suffix.lower() != ".png"
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L34 image source not found")
|
||||
image_path = (pack_path / Path(*pure.parts)).resolve()
|
||||
if (
|
||||
not image_path.is_relative_to(pack_path)
|
||||
or image_path.is_symlink()
|
||||
or not image_path.is_file()
|
||||
or image_path.stat().st_size != reference.get("byte_length")
|
||||
or _sha256(image_path) != reference.get("sha256")
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L34 image source changed")
|
||||
return result, prediction, reference, image_path
|
||||
|
||||
|
||||
def resolve_l34_frame_source(
|
||||
*,
|
||||
root_provider: RootProvider,
|
||||
truth_root_provider: RootProvider,
|
||||
evaluation_pack_root_provider: RootProvider,
|
||||
result_id: str,
|
||||
sequence: int,
|
||||
) -> tuple[L34RightYoloxTruthIsland, dict[str, Any], dict[str, Any], Path]:
|
||||
"""Resolve one immutable L3.4 source frame for bounded sibling APIs.
|
||||
|
||||
The caller receives the server-side binding. Prediction-free source routes
|
||||
project only the source reference; explicitly assisted routes may project a
|
||||
bounded, provenance-marked subset of ``prediction``.
|
||||
"""
|
||||
|
||||
return _resolve_visual_frame(
|
||||
root_provider=root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
|
||||
|
||||
def _validated_predictions(
|
||||
row: dict[str, Any],
|
||||
*,
|
||||
width: int,
|
||||
height: int,
|
||||
) -> list[dict[str, object]]:
|
||||
predictions = row.get("predictions")
|
||||
if not isinstance(predictions, list):
|
||||
raise HTTPException(status_code=404, detail="L34 predictions not found")
|
||||
projected: list[dict[str, object]] = []
|
||||
for prediction in predictions:
|
||||
if not isinstance(prediction, dict):
|
||||
raise HTTPException(status_code=404, detail="L34 prediction is invalid")
|
||||
label = prediction.get("label")
|
||||
score = prediction.get("score")
|
||||
bbox = prediction.get("bbox_xyxy")
|
||||
if (
|
||||
label not in {
|
||||
"person",
|
||||
"bicycle",
|
||||
"motorcycle",
|
||||
"car",
|
||||
"heavy_vehicle",
|
||||
"static_obstacle",
|
||||
"animal",
|
||||
}
|
||||
or not isinstance(score, (int, float))
|
||||
or isinstance(score, bool)
|
||||
or not math.isfinite(float(score))
|
||||
or not 0.25 <= float(score) <= 1.0
|
||||
or not isinstance(bbox, list)
|
||||
or len(bbox) != 4
|
||||
or any(
|
||||
not isinstance(value, (int, float))
|
||||
or isinstance(value, bool)
|
||||
or not math.isfinite(float(value))
|
||||
for value in bbox
|
||||
)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L34 prediction is invalid")
|
||||
left, top, right, bottom = (float(value) for value in bbox)
|
||||
if not (0 <= left < right <= width and 0 <= top < bottom <= height):
|
||||
raise HTTPException(status_code=404, detail="L34 prediction box is invalid")
|
||||
projected.append(
|
||||
{
|
||||
"label": label,
|
||||
"score": float(score),
|
||||
"bbox_xyxy": [left, top, right, bottom],
|
||||
}
|
||||
)
|
||||
return projected
|
||||
|
||||
|
||||
def _png_dimensions(path: Path) -> tuple[int, int]:
|
||||
try:
|
||||
with path.open("rb") as stream:
|
||||
header = stream.read(24)
|
||||
except OSError:
|
||||
raise HTTPException(status_code=404, detail="L34 camera not found") from None
|
||||
if len(header) != 24 or header[:8] != _PNG_SIGNATURE or header[12:16] != b"IHDR":
|
||||
raise HTTPException(status_code=404, detail="L34 camera is invalid")
|
||||
width, height = struct.unpack(">II", header[16:24])
|
||||
if not 1 <= width <= 8192 or not 1 <= height <= 8192:
|
||||
raise HTTPException(status_code=404, detail="L34 camera is invalid")
|
||||
return width, height
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
try:
|
||||
rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
|
||||
except (json.JSONDecodeError, OSError):
|
||||
raise HTTPException(status_code=404, detail="L34 frame index not found") from None
|
||||
if not rows or len(rows) > _MAX_VISUAL_FRAMES or any(not isinstance(row, dict) for row in rows):
|
||||
raise HTTPException(status_code=404, detail="L34 frame index is invalid")
|
||||
return rows
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _nonnegative_integer(value: object, label: str) -> int:
|
||||
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
||||
raise L34RightYoloxTruthIslandError(f"L34 {label} is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
resolved = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return resolved if resolved.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: RootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
candidate
|
||||
for candidate in root.iterdir()
|
||||
if candidate.is_dir()
|
||||
and not candidate.is_symlink()
|
||||
and _RESULT_ID.fullmatch(candidate.name) is not None
|
||||
and (candidate / L34_MANIFEST_NAME).is_file()
|
||||
),
|
||||
key=lambda candidate: candidate.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,207 @@
|
||||
"""Read-only API for the L3.4A assisted YOLOX error audit."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.l34a_assisted_yolox_error_audit import (
|
||||
L34A_MANIFEST_NAME,
|
||||
L34AAssistedYoloxErrorAuditError,
|
||||
read_l34a_assisted_yolox_error_audit,
|
||||
)
|
||||
from k1link.web.l34_right_yolox_truth_island_api import (
|
||||
RootProvider,
|
||||
resolve_l34_frame_source,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^l34a-assisted-yolox-error-audit-[a-f0-9]{64}$")
|
||||
AuditRootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_l34a_assisted_yolox_error_audit_router(
|
||||
*,
|
||||
audit_root_provider: AuditRootProvider = lambda: None,
|
||||
l34_root_provider: RootProvider = lambda: None,
|
||||
truth_root_provider: RootProvider = lambda: None,
|
||||
evaluation_pack_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
|
||||
|
||||
@router.get("/l34a/results")
|
||||
def list_l34a_results(
|
||||
limit: int = Query(default=1, ge=1, le=10),
|
||||
) -> dict[str, object]:
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
candidates = _candidates(audit_root_provider)
|
||||
for candidate in candidates:
|
||||
try:
|
||||
result = read_l34a_assisted_yolox_error_audit(candidate)
|
||||
if len(items) < limit:
|
||||
items.append(_project(result))
|
||||
except (L34AAssistedYoloxErrorAuditError, OSError, ValueError):
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.l34a-assisted-yolox-error-catalog/v1",
|
||||
"configured": _configured_root(audit_root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34a/results/{result_id}/cases/{sequence}")
|
||||
def get_l34a_case(result_id: str, sequence: int) -> dict[str, object]:
|
||||
result, case, _, _ = _resolve_case(
|
||||
audit_root_provider=audit_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return {
|
||||
**copy.deepcopy(case),
|
||||
"result_id": result["result_id"],
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34a/results/{result_id}/cases/{sequence}/camera")
|
||||
def get_l34a_case_camera(result_id: str, sequence: int) -> FileResponse:
|
||||
_, _, _, image_path = _resolve_case(
|
||||
audit_root_provider=audit_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return FileResponse(image_path, media_type="image/png")
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project(result: dict[str, Any]) -> dict[str, object]:
|
||||
report = result["report"]
|
||||
identity = result["manifest"]["identity"]
|
||||
annotation = identity["assisted_annotation"]
|
||||
return {
|
||||
"result_id": result["result_id"],
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"status": report["status"],
|
||||
"profile_id": report["profile"]["profile_id"],
|
||||
"pipeline_id": report["method"]["pipeline_id"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"assisted_annotation": copy.deepcopy(annotation),
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"cases": [_case_summary(case) for case in result["cases"]],
|
||||
"case_order": copy.deepcopy(report["case_order"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _case_summary(case: dict[str, Any]) -> dict[str, object]:
|
||||
return {
|
||||
"truth_island_sequence": case["truth_island_sequence"],
|
||||
"image_id": case["image_id"],
|
||||
"frame_index": case["frame_index"],
|
||||
"group_id": case["group_id"],
|
||||
"session_seconds": case["session_seconds"],
|
||||
"source_image_sha256": case["source_image_sha256"],
|
||||
"summary": copy.deepcopy(case["summary"]),
|
||||
}
|
||||
|
||||
|
||||
def _resolve_case(
|
||||
*,
|
||||
audit_root_provider: AuditRootProvider,
|
||||
l34_root_provider: RootProvider,
|
||||
truth_root_provider: RootProvider,
|
||||
evaluation_pack_root_provider: RootProvider,
|
||||
result_id: str,
|
||||
sequence: int,
|
||||
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any], Path]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None or not 1 <= sequence <= 32:
|
||||
raise HTTPException(status_code=404, detail="L3.4A case not found")
|
||||
root = _configured_root(audit_root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4A result not found")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="L3.4A result not found")
|
||||
try:
|
||||
result = read_l34a_assisted_yolox_error_audit(path)
|
||||
except (L34AAssistedYoloxErrorAuditError, OSError):
|
||||
raise HTTPException(status_code=404, detail="L3.4A result not found") from None
|
||||
case = next(
|
||||
(
|
||||
item
|
||||
for item in result["cases"]
|
||||
if item.get("truth_island_sequence") == sequence
|
||||
),
|
||||
None,
|
||||
)
|
||||
if case is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4A case not found")
|
||||
l34_result_id = result["manifest"]["identity"]["l34_freeze"]["result_id"]
|
||||
_, prediction, reference, image_path = resolve_l34_frame_source(
|
||||
root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=l34_result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
if (
|
||||
prediction.get("source_image_sha256") != case.get("source_image_sha256")
|
||||
or reference.get("sha256") != case.get("source_image_sha256")
|
||||
or prediction.get("image_id") != case.get("image_id")
|
||||
or prediction.get("frame_index") != case.get("frame_index")
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L3.4A case binding changed")
|
||||
return result, case, prediction, image_path
|
||||
|
||||
|
||||
def _configured_root(provider: AuditRootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: AuditRootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
candidate
|
||||
for candidate in root.iterdir()
|
||||
if candidate.is_dir()
|
||||
and not candidate.is_symlink()
|
||||
and _RESULT_ID.fullmatch(candidate.name) is not None
|
||||
and (candidate / L34A_MANIFEST_NAME).is_file()
|
||||
),
|
||||
key=lambda candidate: candidate.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,202 @@
|
||||
"""Read-only API for the L3.4B nested-box consolidation shadow."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.l34b_nested_box_consolidation_shadow import (
|
||||
L34B_MANIFEST_NAME,
|
||||
L34BNestedBoxConsolidationError,
|
||||
read_l34b_nested_box_consolidation_shadow,
|
||||
)
|
||||
from k1link.web.l34_right_yolox_truth_island_api import (
|
||||
RootProvider,
|
||||
resolve_l34_frame_source,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^l34b-nested-box-consolidation-shadow-[a-f0-9]{64}$")
|
||||
ShadowRootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_l34b_nested_box_consolidation_router(
|
||||
*,
|
||||
shadow_root_provider: ShadowRootProvider = lambda: None,
|
||||
l34_root_provider: RootProvider = lambda: None,
|
||||
truth_root_provider: RootProvider = lambda: None,
|
||||
evaluation_pack_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
|
||||
|
||||
@router.get("/l34b/results")
|
||||
def list_l34b_results(
|
||||
limit: int = Query(default=1, ge=1, le=10),
|
||||
) -> dict[str, object]:
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
candidates = _candidates(shadow_root_provider)
|
||||
for candidate in candidates:
|
||||
try:
|
||||
result = read_l34b_nested_box_consolidation_shadow(candidate)
|
||||
if len(items) < limit:
|
||||
items.append(_project(result))
|
||||
except (L34BNestedBoxConsolidationError, OSError, ValueError):
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.l34b-nested-box-consolidation-catalog/v1",
|
||||
"configured": _configured_root(shadow_root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34b/results/{result_id}/cases/{sequence}")
|
||||
def get_l34b_case(result_id: str, sequence: int) -> dict[str, object]:
|
||||
result, case, _, _ = _resolve_case(
|
||||
shadow_root_provider=shadow_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return {
|
||||
**copy.deepcopy(case),
|
||||
"result_id": result["result_id"],
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34b/results/{result_id}/cases/{sequence}/camera")
|
||||
def get_l34b_case_camera(result_id: str, sequence: int) -> FileResponse:
|
||||
_, _, _, image_path = _resolve_case(
|
||||
shadow_root_provider=shadow_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return FileResponse(image_path, media_type="image/png")
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project(result: dict[str, Any]) -> dict[str, object]:
|
||||
report = result["report"]
|
||||
return {
|
||||
"result_id": result["result_id"],
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"status": report["status"],
|
||||
"profile": copy.deepcopy(report["profile"]),
|
||||
"pipeline_id": report["method"]["pipeline_id"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"cases": [_case_summary(case) for case in result["cases"]],
|
||||
"case_order": copy.deepcopy(report["case_order"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _case_summary(case: dict[str, Any]) -> dict[str, object]:
|
||||
return {
|
||||
"truth_island_sequence": case["truth_island_sequence"],
|
||||
"image_id": case["image_id"],
|
||||
"frame_index": case["frame_index"],
|
||||
"group_id": case["group_id"],
|
||||
"session_seconds": case["session_seconds"],
|
||||
"source_image_sha256": case["source_image_sha256"],
|
||||
"before_summary": copy.deepcopy(case["before_summary"]),
|
||||
"after_summary": copy.deepcopy(case["after_summary"]),
|
||||
"consolidation_count": len(case["consolidations"]),
|
||||
}
|
||||
|
||||
|
||||
def _resolve_case(
|
||||
*,
|
||||
shadow_root_provider: ShadowRootProvider,
|
||||
l34_root_provider: RootProvider,
|
||||
truth_root_provider: RootProvider,
|
||||
evaluation_pack_root_provider: RootProvider,
|
||||
result_id: str,
|
||||
sequence: int,
|
||||
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any], Path]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None or not 1 <= sequence <= 32:
|
||||
raise HTTPException(status_code=404, detail="L3.4B case not found")
|
||||
root = _configured_root(shadow_root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4B result not found")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="L3.4B result not found")
|
||||
try:
|
||||
result = read_l34b_nested_box_consolidation_shadow(path)
|
||||
except (L34BNestedBoxConsolidationError, OSError):
|
||||
raise HTTPException(status_code=404, detail="L3.4B result not found") from None
|
||||
case = next(
|
||||
(item for item in result["cases"] if item.get("truth_island_sequence") == sequence),
|
||||
None,
|
||||
)
|
||||
if case is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4B case not found")
|
||||
l34_result_id = result["manifest"]["identity"]["l34_freeze"]["result_id"]
|
||||
_, prediction, reference, image_path = resolve_l34_frame_source(
|
||||
root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=l34_result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
if (
|
||||
prediction.get("source_image_sha256") != case.get("source_image_sha256")
|
||||
or reference.get("sha256") != case.get("source_image_sha256")
|
||||
or prediction.get("image_id") != case.get("image_id")
|
||||
or prediction.get("frame_index") != case.get("frame_index")
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L3.4B case binding changed")
|
||||
return result, case, prediction, image_path
|
||||
|
||||
|
||||
def _configured_root(provider: ShadowRootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: ShadowRootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
candidate
|
||||
for candidate in root.iterdir()
|
||||
if candidate.is_dir()
|
||||
and not candidate.is_symlink()
|
||||
and _RESULT_ID.fullmatch(candidate.name) is not None
|
||||
and (candidate / L34B_MANIFEST_NAME).is_file()
|
||||
),
|
||||
key=lambda candidate: candidate.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,206 @@
|
||||
"""Read-only API for the L3.4C temporal tile-seam stitch shadow."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.l34c_tile_seam_stitch_shadow import (
|
||||
L34C_MANIFEST_NAME,
|
||||
L34CTileSeamStitchError,
|
||||
read_l34c_tile_seam_stitch_shadow,
|
||||
)
|
||||
from k1link.web.l34_right_yolox_truth_island_api import (
|
||||
RootProvider,
|
||||
resolve_l34_frame_source,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^l34c-tile-seam-stitch-shadow-[a-f0-9]{64}$")
|
||||
ShadowRootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_l34c_tile_seam_stitch_router(
|
||||
*,
|
||||
shadow_root_provider: ShadowRootProvider = lambda: None,
|
||||
l34_root_provider: RootProvider = lambda: None,
|
||||
truth_root_provider: RootProvider = lambda: None,
|
||||
evaluation_pack_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
|
||||
|
||||
@router.get("/l34c/results")
|
||||
def list_l34c_results(
|
||||
limit: int = Query(default=1, ge=1, le=10),
|
||||
) -> dict[str, object]:
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
candidates = _candidates(shadow_root_provider)
|
||||
for candidate in candidates:
|
||||
try:
|
||||
result = read_l34c_tile_seam_stitch_shadow(candidate)
|
||||
if len(items) < limit:
|
||||
items.append(_project(result))
|
||||
except (L34CTileSeamStitchError, OSError, ValueError):
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.l34c-tile-seam-stitch-catalog/v1",
|
||||
"configured": _configured_root(shadow_root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34c/results/{result_id}/cases/{sequence}")
|
||||
def get_l34c_case(result_id: str, sequence: int) -> dict[str, object]:
|
||||
result, case, _, _ = _resolve_case(
|
||||
shadow_root_provider=shadow_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return {
|
||||
**copy.deepcopy(case),
|
||||
"result_id": result["result_id"],
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34c/results/{result_id}/cases/{sequence}/camera")
|
||||
def get_l34c_case_camera(result_id: str, sequence: int) -> FileResponse:
|
||||
_, _, _, image_path = _resolve_case(
|
||||
shadow_root_provider=shadow_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return FileResponse(image_path, media_type="image/png")
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project(result: dict[str, Any]) -> dict[str, object]:
|
||||
report = result["report"]
|
||||
return {
|
||||
"result_id": result["result_id"],
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"status": report["status"],
|
||||
"profile": copy.deepcopy(report["profile"]),
|
||||
"pipeline_id": report["method"]["pipeline_id"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"cases": [_case_summary(case) for case in result["cases"]],
|
||||
"case_order": copy.deepcopy(report["case_order"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _case_summary(case: dict[str, Any]) -> dict[str, object]:
|
||||
return {
|
||||
"truth_island_sequence": case["truth_island_sequence"],
|
||||
"image_id": case["image_id"],
|
||||
"frame_index": case["frame_index"],
|
||||
"group_id": case["group_id"],
|
||||
"session_seconds": case["session_seconds"],
|
||||
"source_image_sha256": case["source_image_sha256"],
|
||||
"before_summary": copy.deepcopy(case["before_summary"]),
|
||||
"after_summary": copy.deepcopy(case["after_summary"]),
|
||||
"stitch_count": len(case["stitches"]),
|
||||
}
|
||||
|
||||
|
||||
def _resolve_case(
|
||||
*,
|
||||
shadow_root_provider: ShadowRootProvider,
|
||||
l34_root_provider: RootProvider,
|
||||
truth_root_provider: RootProvider,
|
||||
evaluation_pack_root_provider: RootProvider,
|
||||
result_id: str,
|
||||
sequence: int,
|
||||
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any], Path]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None or not 1 <= sequence <= 32:
|
||||
raise HTTPException(status_code=404, detail="L3.4C case not found")
|
||||
root = _configured_root(shadow_root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4C result not found")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="L3.4C result not found")
|
||||
try:
|
||||
result = read_l34c_tile_seam_stitch_shadow(path)
|
||||
except (L34CTileSeamStitchError, OSError):
|
||||
raise HTTPException(status_code=404, detail="L3.4C result not found") from None
|
||||
case = next(
|
||||
(
|
||||
item
|
||||
for item in result["cases"]
|
||||
if item.get("truth_island_sequence") == sequence
|
||||
),
|
||||
None,
|
||||
)
|
||||
if case is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4C case not found")
|
||||
l34_result_id = result["manifest"]["identity"]["l34_freeze"]["result_id"]
|
||||
_, prediction, reference, image_path = resolve_l34_frame_source(
|
||||
root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=l34_result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
if (
|
||||
prediction.get("source_image_sha256") != case.get("source_image_sha256")
|
||||
or reference.get("sha256") != case.get("source_image_sha256")
|
||||
or prediction.get("image_id") != case.get("image_id")
|
||||
or prediction.get("frame_index") != case.get("frame_index")
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L3.4C case binding changed")
|
||||
return result, case, prediction, image_path
|
||||
|
||||
|
||||
def _configured_root(provider: ShadowRootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: ShadowRootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
candidate
|
||||
for candidate in root.iterdir()
|
||||
if candidate.is_dir()
|
||||
and not candidate.is_symlink()
|
||||
and _RESULT_ID.fullmatch(candidate.name) is not None
|
||||
and (candidate / L34C_MANIFEST_NAME).is_file()
|
||||
),
|
||||
key=lambda candidate: candidate.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,207 @@
|
||||
"""Read-only API for the L3.4D cumulative post-processing candidate."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.l34d_cumulative_postprocessing_candidate import (
|
||||
L34D_MANIFEST_NAME,
|
||||
L34DCumulativeCandidateError,
|
||||
read_l34d_cumulative_postprocessing_candidate,
|
||||
)
|
||||
from k1link.web.l34_right_yolox_truth_island_api import (
|
||||
RootProvider,
|
||||
resolve_l34_frame_source,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^l34d-cumulative-postprocessing-candidate-[a-f0-9]{64}$")
|
||||
CandidateRootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_l34d_cumulative_postprocessing_router(
|
||||
*,
|
||||
candidate_root_provider: CandidateRootProvider = lambda: None,
|
||||
l34_root_provider: RootProvider = lambda: None,
|
||||
truth_root_provider: RootProvider = lambda: None,
|
||||
evaluation_pack_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
|
||||
|
||||
@router.get("/l34d/results")
|
||||
def list_l34d_results(
|
||||
limit: int = Query(default=1, ge=1, le=10),
|
||||
) -> dict[str, object]:
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
candidates = _candidates(candidate_root_provider)
|
||||
for candidate in candidates:
|
||||
try:
|
||||
result = read_l34d_cumulative_postprocessing_candidate(candidate)
|
||||
if len(items) < limit:
|
||||
items.append(_project(result))
|
||||
except (L34DCumulativeCandidateError, OSError, ValueError):
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": ("missioncore.l34d-cumulative-postprocessing-catalog/v1"),
|
||||
"configured": _configured_root(candidate_root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34d/results/{result_id}/cases/{sequence}")
|
||||
def get_l34d_case(result_id: str, sequence: int) -> dict[str, object]:
|
||||
result, case, _, _, _ = resolve_l34d_case_source(
|
||||
candidate_root_provider=candidate_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return {
|
||||
**copy.deepcopy(case),
|
||||
"result_id": result["result_id"],
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34d/results/{result_id}/cases/{sequence}/camera")
|
||||
def get_l34d_case_camera(result_id: str, sequence: int) -> FileResponse:
|
||||
_, _, _, _, image_path = resolve_l34d_case_source(
|
||||
candidate_root_provider=candidate_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return FileResponse(image_path, media_type="image/png")
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _project(result: dict[str, Any]) -> dict[str, object]:
|
||||
report = result["report"]
|
||||
return {
|
||||
"result_id": result["result_id"],
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"status": report["status"],
|
||||
"profile": copy.deepcopy(report["profile"]),
|
||||
"pipeline_id": report["method"]["pipeline_id"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"cases": [_case_summary(case) for case in result["cases"]],
|
||||
"case_order": copy.deepcopy(report["case_order"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _case_summary(case: dict[str, Any]) -> dict[str, object]:
|
||||
operations = case["operations"]
|
||||
return {
|
||||
"truth_island_sequence": case["truth_island_sequence"],
|
||||
"image_id": case["image_id"],
|
||||
"frame_index": case["frame_index"],
|
||||
"group_id": case["group_id"],
|
||||
"session_seconds": case["session_seconds"],
|
||||
"source_image_sha256": case["source_image_sha256"],
|
||||
"before_summary": copy.deepcopy(case["before_summary"]),
|
||||
"after_summary": copy.deepcopy(case["after_summary"]),
|
||||
"operation_count": len(operations),
|
||||
"operation_types": [item["operation_type"] for item in operations],
|
||||
}
|
||||
|
||||
|
||||
def resolve_l34d_case_source(
|
||||
*,
|
||||
candidate_root_provider: CandidateRootProvider,
|
||||
l34_root_provider: RootProvider,
|
||||
truth_root_provider: RootProvider,
|
||||
evaluation_pack_root_provider: RootProvider,
|
||||
result_id: str,
|
||||
sequence: int,
|
||||
) -> tuple[dict[str, Any], dict[str, Any], Any, dict[str, Any], Path]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None or not 1 <= sequence <= 32:
|
||||
raise HTTPException(status_code=404, detail="L3.4D case not found")
|
||||
root = _configured_root(candidate_root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4D result not found")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="L3.4D result not found")
|
||||
try:
|
||||
result = read_l34d_cumulative_postprocessing_candidate(path)
|
||||
except (L34DCumulativeCandidateError, OSError):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="L3.4D result not found",
|
||||
) from None
|
||||
case = next(
|
||||
(item for item in result["cases"] if item.get("truth_island_sequence") == sequence),
|
||||
None,
|
||||
)
|
||||
if case is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4D case not found")
|
||||
l34_result_id = result["manifest"]["identity"]["l34_freeze"]["result_id"]
|
||||
l34_result, prediction, reference, image_path = resolve_l34_frame_source(
|
||||
root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=l34_result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
if (
|
||||
prediction.get("source_image_sha256") != case.get("source_image_sha256")
|
||||
or reference.get("sha256") != case.get("source_image_sha256")
|
||||
or prediction.get("image_id") != case.get("image_id")
|
||||
or prediction.get("frame_index") != case.get("frame_index")
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L3.4D case binding changed")
|
||||
return result, case, l34_result, reference, image_path
|
||||
|
||||
|
||||
def _configured_root(provider: CandidateRootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: CandidateRootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
candidate
|
||||
for candidate in root.iterdir()
|
||||
if candidate.is_dir()
|
||||
and not candidate.is_symlink()
|
||||
and _RESULT_ID.fullmatch(candidate.name) is not None
|
||||
and (candidate / L34D_MANIFEST_NAME).is_file()
|
||||
),
|
||||
key=lambda candidate: candidate.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,219 @@
|
||||
"""Read-only API for the L3.4E manual self-review diagnostic."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.compute.l34e_self_review_diagnostic import (
|
||||
L34E_MANIFEST_NAME,
|
||||
L34ESelfReviewDiagnosticError,
|
||||
read_l34e_self_review_diagnostic,
|
||||
)
|
||||
from k1link.web.l34_right_yolox_truth_island_api import RootProvider
|
||||
from k1link.web.l34d_cumulative_postprocessing_api import (
|
||||
CandidateRootProvider,
|
||||
resolve_l34d_case_source,
|
||||
)
|
||||
|
||||
_RESULT_ID = re.compile(r"^l34e-self-review-diagnostic-[a-f0-9]{64}$")
|
||||
DiagnosticRootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
def build_l34e_self_review_diagnostic_router(
|
||||
*,
|
||||
diagnostic_root_provider: DiagnosticRootProvider = lambda: None,
|
||||
candidate_root_provider: CandidateRootProvider = lambda: None,
|
||||
l34_root_provider: RootProvider = lambda: None,
|
||||
truth_root_provider: RootProvider = lambda: None,
|
||||
evaluation_pack_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
|
||||
|
||||
@router.get("/l34e/results")
|
||||
def list_l34e_results(
|
||||
limit: int = Query(default=1, ge=1, le=10),
|
||||
) -> dict[str, object]:
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
candidates = _candidates(diagnostic_root_provider)
|
||||
for candidate in candidates:
|
||||
try:
|
||||
result = read_l34e_self_review_diagnostic(candidate)
|
||||
if len(items) < limit:
|
||||
items.append(_project(result))
|
||||
except (L34ESelfReviewDiagnosticError, OSError, ValueError):
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.l34e-self-review-diagnostic-catalog/v1",
|
||||
"configured": _configured_root(diagnostic_root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34e/results/{result_id}/cases/{sequence}")
|
||||
def get_l34e_case(result_id: str, sequence: int) -> dict[str, object]:
|
||||
result, case, _ = resolve_l34e_case_source(
|
||||
diagnostic_root_provider=diagnostic_root_provider,
|
||||
candidate_root_provider=candidate_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return {
|
||||
**copy.deepcopy(case),
|
||||
"result_id": result["result_id"],
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/l34e/results/{result_id}/cases/{sequence}/camera")
|
||||
def get_l34e_case_camera(result_id: str, sequence: int) -> FileResponse:
|
||||
_, _, image_path = resolve_l34e_case_source(
|
||||
diagnostic_root_provider=diagnostic_root_provider,
|
||||
candidate_root_provider=candidate_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
return FileResponse(image_path, media_type="image/png")
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def resolve_l34e_case_source(
|
||||
*,
|
||||
diagnostic_root_provider: DiagnosticRootProvider,
|
||||
candidate_root_provider: CandidateRootProvider,
|
||||
l34_root_provider: RootProvider,
|
||||
truth_root_provider: RootProvider,
|
||||
evaluation_pack_root_provider: RootProvider,
|
||||
result_id: str,
|
||||
sequence: int,
|
||||
) -> tuple[dict[str, Any], dict[str, Any], Path]:
|
||||
if _RESULT_ID.fullmatch(result_id) is None or not 1 <= sequence <= 32:
|
||||
raise HTTPException(status_code=404, detail="L3.4E case not found")
|
||||
root = _configured_root(diagnostic_root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4E result not found")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="L3.4E result not found")
|
||||
try:
|
||||
result = read_l34e_self_review_diagnostic(path)
|
||||
except (L34ESelfReviewDiagnosticError, OSError):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="L3.4E result not found",
|
||||
) from None
|
||||
case = next(
|
||||
(
|
||||
item
|
||||
for item in result["cases"]
|
||||
if item.get("truth_island_sequence") == sequence
|
||||
),
|
||||
None,
|
||||
)
|
||||
if case is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4E case not found")
|
||||
l34d_result_id = result["manifest"]["identity"]["l34d_candidate"][
|
||||
"result_id"
|
||||
]
|
||||
_, l34d_case, _, _, image_path = resolve_l34d_case_source(
|
||||
candidate_root_provider=candidate_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=l34d_result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
if any(
|
||||
case.get(key) != l34d_case.get(key)
|
||||
for key in (
|
||||
"truth_island_sequence",
|
||||
"image_id",
|
||||
"frame_index",
|
||||
"source_image_sha256",
|
||||
)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="L3.4E case binding changed")
|
||||
return result, case, image_path
|
||||
|
||||
|
||||
def _project(result: dict[str, Any]) -> dict[str, object]:
|
||||
report = result["report"]
|
||||
return {
|
||||
"result_id": result["result_id"],
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"status": report["status"],
|
||||
"profile": copy.deepcopy(report["profile"]),
|
||||
"pipeline_id": report["method"]["pipeline_id"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"cases": [_case_summary(case) for case in result["cases"]],
|
||||
"case_order": copy.deepcopy(report["case_order"]),
|
||||
"method": copy.deepcopy(report["method"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _case_summary(case: dict[str, Any]) -> dict[str, object]:
|
||||
return {
|
||||
"truth_island_sequence": case["truth_island_sequence"],
|
||||
"image_id": case["image_id"],
|
||||
"frame_index": case["frame_index"],
|
||||
"group_id": case["group_id"],
|
||||
"session_seconds": case["session_seconds"],
|
||||
"source_image_sha256": case["source_image_sha256"],
|
||||
"strict_summary": copy.deepcopy(case["strict_summary"]),
|
||||
"diagnostic_summary": copy.deepcopy(case["diagnostic_summary"]),
|
||||
}
|
||||
|
||||
|
||||
def _configured_root(provider: DiagnosticRootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
candidate = value.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
return None
|
||||
try:
|
||||
root = candidate.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _candidates(provider: DiagnosticRootProvider) -> list[Path]:
|
||||
root = _configured_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
candidate
|
||||
for candidate in root.iterdir()
|
||||
if candidate.is_dir()
|
||||
and not candidate.is_symlink()
|
||||
and _RESULT_ID.fullmatch(candidate.name) is not None
|
||||
and (candidate / L34E_MANIFEST_NAME).is_file()
|
||||
),
|
||||
key=lambda candidate: candidate.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
@@ -0,0 +1,601 @@
|
||||
"""Revisioned L3.4F disagreement adjudication and immutable freeze API."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import threading
|
||||
from collections.abc import Callable
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any, Final, Literal
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi import Path as ApiPath
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from k1link.artifacts import write_json_atomic
|
||||
from k1link.compute.l34e_self_review_diagnostic import (
|
||||
L34ESelfReviewDiagnosticError,
|
||||
read_l34e_self_review_diagnostic,
|
||||
)
|
||||
from k1link.compute.l34f_adjudicated_reference import (
|
||||
L34F_MANIFEST_NAME,
|
||||
L34F_SESSION_SCHEMA,
|
||||
L34FAdjudicatedReferenceError,
|
||||
build_l34f_adjudicated_reference,
|
||||
read_l34f_adjudicated_reference,
|
||||
)
|
||||
from k1link.web.l34_right_yolox_truth_island_api import RootProvider
|
||||
from k1link.web.l34d_cumulative_postprocessing_api import CandidateRootProvider
|
||||
from k1link.web.l34e_self_review_diagnostic_api import (
|
||||
DiagnosticRootProvider,
|
||||
resolve_l34e_case_source,
|
||||
)
|
||||
|
||||
_DIAGNOSTIC_ID = re.compile(r"^l34e-self-review-diagnostic-[a-f0-9]{64}$")
|
||||
_SESSION_ID = re.compile(r"^l34f-adjudication-session-[a-f0-9]{64}$")
|
||||
_RESULT_ID = re.compile(r"^l34f-adjudicated-reference-[a-f0-9]{64}$")
|
||||
_OBJECT_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,95}$")
|
||||
_OPERATION_KEY = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$")
|
||||
_TARGET_CLASSES: Final = (
|
||||
"person",
|
||||
"bicycle",
|
||||
"motorcycle",
|
||||
"car",
|
||||
"heavy_vehicle",
|
||||
"static_obstacle",
|
||||
"animal",
|
||||
)
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
_BLINDNESS: Final = {
|
||||
"candidate_identity_seen": True,
|
||||
"model_prelabels_seen": False,
|
||||
"model_predictions_seen": True,
|
||||
"model_scores_seen": False,
|
||||
"source_self_review_seen": True,
|
||||
}
|
||||
_ASSISTANCE: Final = {
|
||||
"mode": "candidate-visible-human-adjudication",
|
||||
"independent_truth_eligible": False,
|
||||
"metric_grade_reference": False,
|
||||
}
|
||||
|
||||
AdjudicationRootProvider = Callable[[], Path | None]
|
||||
ResultRootProvider = Callable[[], Path | None]
|
||||
|
||||
|
||||
class L34FCreateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
idempotency_key: str = Field(min_length=1, max_length=128)
|
||||
|
||||
|
||||
class L34FObjectRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
object_id: str = Field(min_length=1, max_length=96)
|
||||
category: Literal[
|
||||
"person",
|
||||
"bicycle",
|
||||
"motorcycle",
|
||||
"car",
|
||||
"heavy_vehicle",
|
||||
"static_obstacle",
|
||||
"animal",
|
||||
"unmapped",
|
||||
]
|
||||
proposed_label: str | None = Field(default=None, max_length=80)
|
||||
origin: Literal["self_review_seed", "adjudicated_manual"]
|
||||
box_xyxy: list[float] = Field(min_length=4, max_length=4)
|
||||
occluded: bool
|
||||
truncated: bool
|
||||
|
||||
|
||||
class L34FFrameRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
truth_island_sequence: int = Field(ge=1, le=32)
|
||||
reviewed: bool
|
||||
objects: list[L34FObjectRequest] = Field(max_length=256)
|
||||
|
||||
|
||||
class L34FSaveRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
expected_revision: int = Field(ge=0)
|
||||
idempotency_key: str = Field(min_length=1, max_length=128)
|
||||
title: str = Field(min_length=1, max_length=160)
|
||||
frames: list[L34FFrameRequest] = Field(min_length=32, max_length=32)
|
||||
|
||||
|
||||
class L34FFreezeRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
expected_revision: int = Field(ge=1)
|
||||
|
||||
|
||||
class _Store:
|
||||
def __init__(self, provider: AdjudicationRootProvider) -> None:
|
||||
self.provider = provider
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def list(self, diagnostic_id: str) -> list[dict[str, object]]:
|
||||
root = self._diagnostic_root(diagnostic_id, create=False)
|
||||
if root is None:
|
||||
return []
|
||||
items: list[dict[str, object]] = []
|
||||
for path in sorted(root.glob("l34f-adjudication-session-*.json")):
|
||||
if path.is_symlink() or _SESSION_ID.fullmatch(path.stem) is None:
|
||||
continue
|
||||
try:
|
||||
items.append(_project_session(self._read(path, diagnostic_id), False))
|
||||
except (OSError, ValueError):
|
||||
continue
|
||||
items.sort(key=lambda item: str(item["created_at_utc"]))
|
||||
return items
|
||||
|
||||
def create(self, diagnostic: dict[str, Any], key: str) -> dict[str, object]:
|
||||
diagnostic_id = diagnostic["result_id"]
|
||||
_validate_operation_key(key)
|
||||
digest = hashlib.sha256(f"{diagnostic_id}\n{key}".encode()).hexdigest()
|
||||
session_id = f"l34f-adjudication-session-{digest}"
|
||||
with self.lock:
|
||||
path = self._session_path(diagnostic_id, session_id, create=True)
|
||||
assert path is not None
|
||||
if path.exists():
|
||||
return _project_session(self._read(path, diagnostic_id), True)
|
||||
now = _utc_now()
|
||||
frames = []
|
||||
for case in sorted(diagnostic["cases"], key=lambda row: row["truth_island_sequence"]):
|
||||
frames.append(
|
||||
{
|
||||
"truth_island_sequence": int(case["truth_island_sequence"]),
|
||||
"image_id": int(case["image_id"]),
|
||||
"frame_index": int(case["frame_index"]),
|
||||
"source_sha256": str(case["source_image_sha256"]),
|
||||
"reviewed": False,
|
||||
"objects": [
|
||||
{
|
||||
"object_id": str(item["object_id"]),
|
||||
"category": str(item["category"]),
|
||||
"proposed_label": item.get("proposed_label"),
|
||||
"origin": "self_review_seed",
|
||||
"box_xyxy": [float(value) for value in item["box_xyxy"]],
|
||||
"occluded": bool(item["occluded"]),
|
||||
"truncated": bool(item["truncated"]),
|
||||
}
|
||||
for item in case["references"]
|
||||
],
|
||||
}
|
||||
)
|
||||
reviewer_slot = len(self.list(diagnostic_id)) + 1
|
||||
document: dict[str, object] = {
|
||||
"schema_version": L34F_SESSION_SCHEMA,
|
||||
"session_id": session_id,
|
||||
"diagnostic_result_id": diagnostic_id,
|
||||
"source_session_id": str(diagnostic["report"]["source_session_id"]),
|
||||
"source_review_session_id": str(
|
||||
diagnostic["manifest"]["identity"]["self_review"]["session_id"]
|
||||
),
|
||||
"contract_id": "l34e-candidate-visible-adjudication/v1",
|
||||
"title": f"Разбор конфликтов · LAB L3.4F · Рецензент {reviewer_slot}",
|
||||
"reviewer_slot": reviewer_slot,
|
||||
"revision": 0,
|
||||
"state": "draft",
|
||||
"created_at_utc": now,
|
||||
"updated_at_utc": now,
|
||||
"frames": frames,
|
||||
"blindness": dict(_BLINDNESS),
|
||||
"assistance": dict(_ASSISTANCE),
|
||||
"authority": dict(_AUTHORITY),
|
||||
"last_save_idempotency_key": None,
|
||||
}
|
||||
write_json_atomic(path, document)
|
||||
return _project_session(document, True)
|
||||
|
||||
def get(self, diagnostic_id: str, session_id: str) -> dict[str, object]:
|
||||
path = self._session_path(diagnostic_id, session_id, create=False)
|
||||
if path is None or not path.is_file() or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="Сессия adjudication не найдена")
|
||||
try:
|
||||
return _project_session(self._read(path, diagnostic_id), True)
|
||||
except (OSError, ValueError) as exc:
|
||||
raise HTTPException(status_code=409, detail="Сессия adjudication повреждена") from exc
|
||||
|
||||
def save(
|
||||
self,
|
||||
diagnostic: dict[str, Any],
|
||||
session_id: str,
|
||||
request: L34FSaveRequest,
|
||||
) -> dict[str, object]:
|
||||
diagnostic_id = diagnostic["result_id"]
|
||||
_validate_operation_key(request.idempotency_key)
|
||||
path = self._session_path(diagnostic_id, session_id, create=False)
|
||||
if path is None or not path.is_file() or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="Сессия adjudication не найдена")
|
||||
source_cases = {int(case["truth_island_sequence"]): case for case in diagnostic["cases"]}
|
||||
seen: set[int] = set()
|
||||
frames: list[dict[str, object]] = []
|
||||
for frame in request.frames:
|
||||
if frame.truth_island_sequence in seen:
|
||||
raise HTTPException(status_code=422, detail="Кадр передан дважды")
|
||||
seen.add(frame.truth_island_sequence)
|
||||
source = source_cases[frame.truth_island_sequence]
|
||||
frames.append(
|
||||
{
|
||||
"truth_island_sequence": frame.truth_island_sequence,
|
||||
"image_id": int(source["image_id"]),
|
||||
"frame_index": int(source["frame_index"]),
|
||||
"source_sha256": str(source["source_image_sha256"]),
|
||||
"reviewed": frame.reviewed,
|
||||
"objects": [_normalize_object(item) for item in frame.objects],
|
||||
}
|
||||
)
|
||||
if seen != set(range(1, 33)):
|
||||
raise HTTPException(status_code=422, detail="Требуются все 32 кадра")
|
||||
frames.sort(key=lambda item: int(item["truth_island_sequence"]))
|
||||
with self.lock:
|
||||
current = self._read(path, diagnostic_id)
|
||||
if current.get("last_save_idempotency_key") == request.idempotency_key:
|
||||
return _project_session(current, True)
|
||||
if current["revision"] != request.expected_revision:
|
||||
raise HTTPException(status_code=409, detail="Сессия уже изменена в другом окне")
|
||||
current.update(
|
||||
{
|
||||
"title": request.title.strip(),
|
||||
"revision": request.expected_revision + 1,
|
||||
"state": "saved",
|
||||
"updated_at_utc": _utc_now(),
|
||||
"frames": frames,
|
||||
"last_save_idempotency_key": request.idempotency_key,
|
||||
}
|
||||
)
|
||||
write_json_atomic(path, current)
|
||||
return _project_session(current, True)
|
||||
|
||||
def session_path(self, diagnostic_id: str, session_id: str) -> Path:
|
||||
path = self._session_path(diagnostic_id, session_id, create=False)
|
||||
if path is None or not path.is_file() or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="Сессия adjudication не найдена")
|
||||
return path
|
||||
|
||||
def _diagnostic_root(self, diagnostic_id: str, *, create: bool) -> Path | None:
|
||||
if _DIAGNOSTIC_ID.fullmatch(diagnostic_id) is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4E result не найден")
|
||||
value = self.provider()
|
||||
if value is None:
|
||||
raise HTTPException(status_code=503, detail="Хранилище adjudication не настроено")
|
||||
root = value.expanduser().absolute()
|
||||
if root.is_symlink():
|
||||
raise HTTPException(status_code=503, detail="Хранилище adjudication недоступно")
|
||||
if create:
|
||||
root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
if not root.is_dir():
|
||||
return None
|
||||
root = root.resolve(strict=True)
|
||||
child = root / diagnostic_id
|
||||
if create:
|
||||
child.mkdir(mode=0o700, exist_ok=True)
|
||||
if not child.is_dir() or child.is_symlink():
|
||||
return None
|
||||
resolved = child.resolve(strict=True)
|
||||
if resolved.parent != root:
|
||||
raise HTTPException(status_code=503, detail="Хранилище adjudication недоступно")
|
||||
return resolved
|
||||
|
||||
def _session_path(self, diagnostic_id: str, session_id: str, *, create: bool) -> Path | None:
|
||||
if _SESSION_ID.fullmatch(session_id) is None:
|
||||
raise HTTPException(status_code=404, detail="Сессия adjudication не найдена")
|
||||
root = self._diagnostic_root(diagnostic_id, create=create)
|
||||
return None if root is None else root / f"{session_id}.json"
|
||||
|
||||
def _read(self, path: Path, diagnostic_id: str) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if (
|
||||
not isinstance(value, dict)
|
||||
or value.get("schema_version") != L34F_SESSION_SCHEMA
|
||||
or value.get("session_id") != path.stem
|
||||
or value.get("diagnostic_result_id") != diagnostic_id
|
||||
or value.get("contract_id") != "l34e-candidate-visible-adjudication/v1"
|
||||
or value.get("blindness") != _BLINDNESS
|
||||
or value.get("assistance") != _ASSISTANCE
|
||||
or value.get("authority") != _AUTHORITY
|
||||
or not isinstance(value.get("revision"), int)
|
||||
or not isinstance(value.get("frames"), list)
|
||||
or len(value["frames"]) != 32
|
||||
):
|
||||
raise ValueError("invalid L3.4F session")
|
||||
return value
|
||||
|
||||
|
||||
def build_l34f_adjudication_router(
|
||||
*,
|
||||
diagnostic_root_provider: DiagnosticRootProvider = lambda: None,
|
||||
candidate_root_provider: CandidateRootProvider = lambda: None,
|
||||
l34_root_provider: RootProvider = lambda: None,
|
||||
truth_root_provider: RootProvider = lambda: None,
|
||||
evaluation_pack_root_provider: RootProvider = lambda: None,
|
||||
adjudication_root_provider: AdjudicationRootProvider = lambda: None,
|
||||
result_root_provider: ResultRootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/l34f", tags=["laboratory"])
|
||||
store = _Store(adjudication_root_provider)
|
||||
|
||||
def diagnostic(result_id: str) -> dict[str, Any]:
|
||||
if _DIAGNOSTIC_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4E result не найден")
|
||||
root = _configured_root(diagnostic_root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="L3.4E result не найден")
|
||||
path = (root / result_id).resolve()
|
||||
if path.parent != root or path.is_symlink():
|
||||
raise HTTPException(status_code=404, detail="L3.4E result не найден")
|
||||
try:
|
||||
return read_l34e_self_review_diagnostic(path)
|
||||
except (L34ESelfReviewDiagnosticError, OSError):
|
||||
raise HTTPException(status_code=404, detail="L3.4E result не найден") from None
|
||||
|
||||
def case_source(result_id: str, sequence: int) -> tuple[dict[str, Any], dict[str, Any], Path]:
|
||||
return resolve_l34e_case_source(
|
||||
diagnostic_root_provider=diagnostic_root_provider,
|
||||
candidate_root_provider=candidate_root_provider,
|
||||
l34_root_provider=l34_root_provider,
|
||||
truth_root_provider=truth_root_provider,
|
||||
evaluation_pack_root_provider=evaluation_pack_root_provider,
|
||||
result_id=result_id,
|
||||
sequence=sequence,
|
||||
)
|
||||
|
||||
@router.get("/diagnostics/{diagnostic_id}/cases/{sequence}")
|
||||
def get_case(diagnostic_id: str, sequence: int) -> dict[str, object]:
|
||||
result, case, image_path = case_source(diagnostic_id, sequence)
|
||||
width, height = _png_dimensions(image_path)
|
||||
return {
|
||||
"schema_version": "missioncore.l34f-adjudication-case/v1",
|
||||
"diagnostic_result_id": result["result_id"],
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": case["image_id"],
|
||||
"frame_index": case["frame_index"],
|
||||
"group_id": case["group_id"],
|
||||
"session_seconds": case["session_seconds"],
|
||||
"source_image_sha256": case["source_image_sha256"],
|
||||
"camera": {"width": width, "height": height},
|
||||
"camera_url": (
|
||||
f"/api/v1/laboratory/l34f/diagnostics/{diagnostic_id}/cases/{sequence}/camera"
|
||||
),
|
||||
"candidate_objects": [
|
||||
{
|
||||
"candidate_id": f"candidate-{sequence}-{item['prediction_index']}",
|
||||
"prediction_index": item["prediction_index"],
|
||||
"category": item["category"],
|
||||
"box_xyxy": copy.deepcopy(item["box_xyxy"]),
|
||||
"diagnostic_verdict": item["diagnostic_verdict"],
|
||||
"associated_object_id": item["associated_object_id"],
|
||||
}
|
||||
for item in case["predictions"]
|
||||
],
|
||||
"diagnostic_summary": copy.deepcopy(case["diagnostic_summary"]),
|
||||
"model_scores_included": False,
|
||||
"ground_truth": False,
|
||||
"access": "candidate-visible-adjudication-read-only-source",
|
||||
}
|
||||
|
||||
@router.get("/diagnostics/{diagnostic_id}/cases/{sequence}/camera")
|
||||
def get_camera(diagnostic_id: str, sequence: int) -> FileResponse:
|
||||
_, _, image_path = case_source(diagnostic_id, sequence)
|
||||
return FileResponse(image_path, media_type="image/png")
|
||||
|
||||
@router.get("/diagnostics/{diagnostic_id}/sessions")
|
||||
def list_sessions(diagnostic_id: str) -> dict[str, object]:
|
||||
diagnostic(diagnostic_id)
|
||||
items = store.list(diagnostic_id)
|
||||
return {
|
||||
"schema_version": "missioncore.l34f-adjudication-session-catalog/v1",
|
||||
"diagnostic_result_id": diagnostic_id,
|
||||
"items": items,
|
||||
"total": len(items),
|
||||
"access": "human-adjudication",
|
||||
}
|
||||
|
||||
@router.post("/diagnostics/{diagnostic_id}/sessions")
|
||||
def create_session(diagnostic_id: str, request: L34FCreateRequest) -> dict[str, object]:
|
||||
return store.create(diagnostic(diagnostic_id), request.idempotency_key)
|
||||
|
||||
@router.get("/diagnostics/{diagnostic_id}/sessions/{session_id}")
|
||||
def get_session(
|
||||
diagnostic_id: str, session_id: Annotated[str, ApiPath(pattern=_SESSION_ID.pattern)]
|
||||
) -> dict[str, object]:
|
||||
diagnostic(diagnostic_id)
|
||||
return store.get(diagnostic_id, session_id)
|
||||
|
||||
@router.put("/diagnostics/{diagnostic_id}/sessions/{session_id}")
|
||||
def save_session(
|
||||
diagnostic_id: str,
|
||||
session_id: Annotated[str, ApiPath(pattern=_SESSION_ID.pattern)],
|
||||
request: L34FSaveRequest,
|
||||
) -> dict[str, object]:
|
||||
return store.save(diagnostic(diagnostic_id), session_id, request)
|
||||
|
||||
@router.post("/diagnostics/{diagnostic_id}/sessions/{session_id}/freeze")
|
||||
def freeze_session(
|
||||
diagnostic_id: str,
|
||||
session_id: Annotated[str, ApiPath(pattern=_SESSION_ID.pattern)],
|
||||
request: L34FFreezeRequest,
|
||||
) -> dict[str, object]:
|
||||
source = diagnostic(diagnostic_id)
|
||||
session = store.get(diagnostic_id, session_id)
|
||||
if session["revision"] != request.expected_revision:
|
||||
raise HTTPException(status_code=409, detail="Сессия уже изменена")
|
||||
if not session["progress"]["complete"]:
|
||||
raise HTTPException(status_code=422, detail="Для freeze нужны 32/32 проверенных кадров")
|
||||
output = result_root_provider()
|
||||
if output is None:
|
||||
raise HTTPException(status_code=503, detail="Хранилище L3.4F не настроено")
|
||||
try:
|
||||
frozen = build_l34f_adjudicated_reference(
|
||||
diagnostic_root=source["result_root"],
|
||||
adjudication_session_path=store.session_path(diagnostic_id, session_id),
|
||||
output_root=output,
|
||||
)
|
||||
except (L34FAdjudicatedReferenceError, OSError) as exc:
|
||||
raise HTTPException(status_code=409, detail="L3.4F freeze не прошёл проверку") from exc
|
||||
return _project_result(frozen)
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
|
||||
candidates = _result_candidates(result_root_provider)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for path in candidates:
|
||||
try:
|
||||
result = read_l34f_adjudicated_reference(path)
|
||||
if len(items) < limit:
|
||||
items.append(_project_result(result))
|
||||
except (L34FAdjudicatedReferenceError, OSError):
|
||||
invalid_total += 1
|
||||
return {
|
||||
"schema_version": "missioncore.l34f-adjudicated-reference-catalog/v1",
|
||||
"configured": _configured_result_root(result_root_provider) is not None,
|
||||
"items": items,
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _normalize_object(item: L34FObjectRequest) -> dict[str, object]:
|
||||
if _OBJECT_ID.fullmatch(item.object_id) is None:
|
||||
raise HTTPException(status_code=422, detail="Некорректный id рамки")
|
||||
if any(not math.isfinite(float(value)) for value in item.box_xyxy):
|
||||
raise HTTPException(status_code=422, detail="Некорректная геометрия рамки")
|
||||
left, top, right, bottom = (float(value) for value in item.box_xyxy)
|
||||
if not (0 <= left < right <= 800 and 0 <= top < bottom <= 600):
|
||||
raise HTTPException(status_code=422, detail="Рамка выходит за границы кадра")
|
||||
proposed = item.proposed_label.strip() if isinstance(item.proposed_label, str) else None
|
||||
if item.category == "unmapped" and not proposed:
|
||||
raise HTTPException(status_code=422, detail="Для другого объекта требуется название")
|
||||
if item.category != "unmapped" and proposed is not None:
|
||||
raise HTTPException(status_code=422, detail="Название допустимо только для другого объекта")
|
||||
return {
|
||||
"object_id": item.object_id,
|
||||
"category": item.category,
|
||||
"proposed_label": proposed,
|
||||
"origin": item.origin,
|
||||
"box_xyxy": [left, top, right, bottom],
|
||||
"occluded": item.occluded,
|
||||
"truncated": item.truncated,
|
||||
}
|
||||
|
||||
|
||||
def _project_session(document: dict[str, Any], include_frames: bool) -> dict[str, object]:
|
||||
frames = document["frames"]
|
||||
reviewed = sum(frame.get("reviewed") is True for frame in frames)
|
||||
objects = sum(len(frame.get("objects", [])) for frame in frames)
|
||||
value = {
|
||||
key: copy.deepcopy(document[key])
|
||||
for key in (
|
||||
"schema_version",
|
||||
"session_id",
|
||||
"diagnostic_result_id",
|
||||
"contract_id",
|
||||
"title",
|
||||
"reviewer_slot",
|
||||
"revision",
|
||||
"state",
|
||||
"created_at_utc",
|
||||
"updated_at_utc",
|
||||
"blindness",
|
||||
"assistance",
|
||||
"authority",
|
||||
)
|
||||
}
|
||||
value["progress"] = {
|
||||
"reviewed_frame_count": reviewed,
|
||||
"frame_count": 32,
|
||||
"object_count": objects,
|
||||
"complete": reviewed == 32,
|
||||
}
|
||||
if include_frames:
|
||||
value["frames"] = copy.deepcopy(frames)
|
||||
return value
|
||||
|
||||
|
||||
def _project_result(result: dict[str, Any]) -> dict[str, object]:
|
||||
report = result["report"]
|
||||
identity = result["manifest"]["identity"]
|
||||
return {
|
||||
"result_id": result["result_id"],
|
||||
"created_at_utc": report["created_at_utc"],
|
||||
"status": report["status"],
|
||||
"source_session_id": report["source_session_id"],
|
||||
"camera_source_id": report["camera_source_id"],
|
||||
"diagnostic_result_id": identity["l34e_diagnostic"]["result_id"],
|
||||
"adjudication_session_id": identity["adjudication_session"]["session_id"],
|
||||
"metrics": copy.deepcopy(report["metrics"]),
|
||||
"decision": copy.deepcopy(report["decision"]),
|
||||
"limitations": copy.deepcopy(report["limitations"]),
|
||||
"authority": copy.deepcopy(report["authority"]),
|
||||
"ground_truth": False,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _configured_root(provider: DiagnosticRootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
path = value.expanduser().absolute()
|
||||
if path.is_symlink():
|
||||
return None
|
||||
try:
|
||||
path = path.resolve(strict=True)
|
||||
except OSError:
|
||||
return None
|
||||
return path if path.is_dir() else None
|
||||
|
||||
|
||||
def _configured_result_root(provider: ResultRootProvider) -> Path | None:
|
||||
return _configured_root(provider)
|
||||
|
||||
|
||||
def _result_candidates(provider: ResultRootProvider) -> list[Path]:
|
||||
root = _configured_result_root(provider)
|
||||
if root is None:
|
||||
return []
|
||||
return sorted(
|
||||
(
|
||||
path
|
||||
for path in root.iterdir()
|
||||
if path.is_dir()
|
||||
and not path.is_symlink()
|
||||
and _RESULT_ID.fullmatch(path.name) is not None
|
||||
and (path / L34F_MANIFEST_NAME).is_file()
|
||||
),
|
||||
key=lambda path: path.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
|
||||
def _validate_operation_key(value: str) -> None:
|
||||
if _OPERATION_KEY.fullmatch(value) is None:
|
||||
raise HTTPException(status_code=422, detail="Некорректный ключ операции")
|
||||
|
||||
|
||||
def _png_dimensions(path: Path) -> tuple[int, int]:
|
||||
header = path.read_bytes()[:24]
|
||||
if len(header) != 24 or header[:8] != b"\x89PNG\r\n\x1a\n" or header[12:16] != b"IHDR":
|
||||
raise HTTPException(status_code=404, detail="Кадр повреждён")
|
||||
return int.from_bytes(header[16:20], "big"), int.from_bytes(header[20:24], "big")
|
||||
|
||||
|
||||
def _utc_now() -> str:
|
||||
return datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||
@@ -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