refactor(lab): объединить RAV004 в единый Rerun replay

This commit is contained in:
DCCONSTRUCTIONS
2026-08-30 16:40:12 +03:00
parent 9c5259dbc9
commit 81fdf6904a
18 changed files with 750 additions and 186 deletions
+103
View File
@@ -24,8 +24,10 @@ from pydantic import BaseModel, ConfigDict, Field
from k1link.laboratory.canonical_rerun_overlay import (
CanonicalLabOverlayArtifact,
CanonicalLabOverlayError,
CanonicalLabReplayArtifact,
_mask_component_boxes,
canonical_lab_overlay,
canonical_lab_replay,
canonical_recording_id,
)
from k1link.laboratory.evidence_registry import LaboratoryEvidenceDefinition
@@ -352,6 +354,59 @@ def _build_vegetation_lab_router(
) from exc
return artifact
async def canonical_rerun_replay_artifact(
result_id: str,
*,
expected_base_generation_sha256: str | None = None,
) -> CanonicalLabReplayArtifact:
"""Return one immutable RRD containing base geometry and LAB perception."""
if (
canonical_recording_provider is None
or jobs_root is None
or rerun_overlay_cache_root is None
or ffmpeg_path is None
):
raise HTTPException(status_code=503, detail="Canonical LAB Rerun replay unavailable")
candidate = _resolve_candidate(root_provider, definition, result_id)
manifest = _read_verified(candidate, definition)
route, _ = _full_route_context(candidate, manifest)
recording = canonical_recording_provider(str(route["session_id"]))
if recording is None:
raise HTTPException(status_code=409, detail="Canonical spatial recording is not ready")
recording_path, generation_sha256 = recording
if (
expected_base_generation_sha256 is not None
and expected_base_generation_sha256 != generation_sha256
):
raise HTTPException(status_code=412, detail="Canonical recording generation changed")
try:
recording_id = await run_in_threadpool(canonical_recording_id, recording_path)
overlay = await run_in_threadpool(
canonical_lab_overlay,
candidate,
manifest,
recording_id=recording_id,
base_generation_sha256=generation_sha256,
jobs_root=jobs_root,
cache_root=rerun_overlay_cache_root,
ffmpeg_path=ffmpeg_path,
)
return await run_in_threadpool(
canonical_lab_replay,
recording_path,
base_generation_sha256=generation_sha256,
overlay=overlay,
result_id=result_id,
recording_id=recording_id,
cache_root=rerun_overlay_cache_root,
)
except CanonicalLabOverlayError as exc:
raise HTTPException(
status_code=503,
detail="Canonical LAB Rerun replay failed verification",
) from exc
def canonical_rerun_overlay_file_response(
artifact: CanonicalLabOverlayArtifact,
) -> FileResponse:
@@ -365,6 +420,19 @@ def _build_vegetation_lab_router(
},
)
def canonical_rerun_replay_file_response(
artifact: CanonicalLabReplayArtifact,
) -> FileResponse:
return FileResponse(
artifact.path,
media_type="application/vnd.rerun.rrd",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"ETag": f'"{artifact.sha256}"',
"X-Content-Type-Options": "nosniff",
},
)
@router.post("/{result_id}/canonical-overlay.rrd")
async def get_canonical_rerun_overlay(
result_id: str,
@@ -446,6 +514,41 @@ def _build_vegetation_lab_router(
raise HTTPException(status_code=412, detail="Canonical overlay generation changed")
return canonical_rerun_overlay_file_response(artifact)
@router.head("/{result_id}/canonical-replay.rrd")
async def describe_canonical_rerun_replay(
result_id: str,
base_generation: Annotated[str, Query(pattern=r"^[a-f0-9]{64}$")],
) -> Response:
"""Build once and describe the single RRD consumed by the LAB viewer."""
artifact = await canonical_rerun_replay_artifact(
result_id,
expected_base_generation_sha256=base_generation,
)
return Response(
status_code=200,
media_type="application/vnd.rerun.rrd",
headers={
"Cache-Control": "private, no-store",
"Content-Length": str(artifact.byte_length),
"ETag": f'"{artifact.sha256}"',
"X-Content-Type-Options": "nosniff",
"X-Rerun-Format": "RRF2",
},
)
@router.get("/{result_id}/canonical-replay.rrd")
async def stream_canonical_rerun_replay(
result_id: str,
generation: Annotated[str, Query(pattern=r"^[a-f0-9]{64}$")],
) -> FileResponse:
"""Stream the digest-bound merged replay through one native receiver."""
artifact = await canonical_rerun_replay_artifact(result_id)
if generation != artifact.sha256:
raise HTTPException(status_code=412, detail="Canonical replay generation changed")
return canonical_rerun_replay_file_response(artifact)
@router.get("/{result_id}/timeline")
def get_canonical_route_timeline(result_id: str) -> dict[str, object]:
candidate = _resolve_candidate(root_provider, definition, result_id)