feat(perception): split vegetation evidence layers

This commit is contained in:
DCCONSTRUCTIONS
2026-08-28 15:37:10 +03:00
parent a2c3385062
commit e10b96b546
26 changed files with 868 additions and 233 deletions
+15 -1
View File
@@ -138,7 +138,6 @@ from k1link.web.m49_physical_safety_playback_api import (
)
from k1link.web.m49_tgs_fail_closed_api import build_m49_tgs_fail_closed_router
from k1link.web.m49_tgs_full_shadow_api import build_m49_tgs_full_shadow_router
from k1link.web.vegetation_shadow_lab_api import build_vegetation_shadow_lab_router
from k1link.web.map_api import (
MapGatewayConfiguration,
MapGatewayProxy,
@@ -165,6 +164,10 @@ from k1link.web.session_api import build_session_router
from k1link.web.simulation_projects_api import build_simulation_projects_router
from k1link.web.simulation_world_provider_api import build_simulation_world_provider_router
from k1link.web.system_telemetry_api import build_system_telemetry_router
from k1link.web.vegetation_shadow_lab_api import (
build_vegetation_benchmark_lab_router,
build_vegetation_shadow_lab_router,
)
from k1link.web.viewer_diagnostics_api import build_viewer_diagnostics_router
REPOSITORY_ROOT = Path(__file__).resolve().parents[3]
@@ -1031,6 +1034,17 @@ app.include_router(
),
)
)
app.include_router(
build_vegetation_benchmark_lab_router(
root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "lab-v1-vegetation-benchmark"
/ "results"
),
)
)
app.include_router(
build_m49_physical_safety_playback_router(
root_provider=lambda: (
+75 -17
View File
@@ -5,7 +5,6 @@ from __future__ import annotations
import copy
import hashlib
import json
import re
import zipfile
from collections.abc import Callable
from functools import lru_cache
@@ -20,10 +19,9 @@ from k1link.laboratory.evidence_report import (
LaboratoryEvidenceReportError,
verify_laboratory_evidence_result,
)
from k1link.laboratory.vegetation_shadow_lab import LAB_SCHEMA, RESULT_PREFIX
from k1link.laboratory.vegetation_shadow_lab import LAB_SCHEMA
RootProvider = Callable[[], Path | None]
RESULT_ID: Final = re.compile(rf"^{re.escape(RESULT_PREFIX)}[a-f0-9]{{64}}$")
_MAX_DOCUMENT_BYTES: Final = 1024 * 1024
_DEFINITION: Final = LaboratoryEvidenceDefinition(
work_id="lab-v1-vegetation-shadow",
@@ -32,25 +30,55 @@ _DEFINITION: Final = LaboratoryEvidenceDefinition(
document_name="result.json",
result_schema_version=LAB_SCHEMA,
)
_BENCHMARK_DEFINITION: Final = LaboratoryEvidenceDefinition(
work_id="lab-v1-vegetation-benchmark",
runtime_relative_root=PurePosixPath("lab-v1-vegetation-benchmark/results"),
result_id_prefix="lab-v1-vegetation-benchmark",
document_name="result.json",
result_schema_version=LAB_SCHEMA,
)
def build_vegetation_shadow_lab_router(
*, root_provider: RootProvider = lambda: None,
) -> APIRouter:
router = APIRouter(
return _build_vegetation_lab_router(
prefix="/api/v1/laboratory/vegetation-shadow",
definition=_DEFINITION,
root_provider=root_provider,
)
def build_vegetation_benchmark_lab_router(
*, root_provider: RootProvider = lambda: None,
) -> APIRouter:
return _build_vegetation_lab_router(
prefix="/api/v1/laboratory/vegetation-benchmark",
definition=_BENCHMARK_DEFINITION,
root_provider=root_provider,
)
def _build_vegetation_lab_router(
*,
prefix: str,
definition: LaboratoryEvidenceDefinition,
root_provider: RootProvider,
) -> APIRouter:
router = APIRouter(
prefix=prefix,
tags=["laboratory"],
)
@router.get("/{result_id}")
def get_result(result_id: str) -> dict[str, object]:
candidate = _resolve_candidate(root_provider, result_id)
return {**copy.deepcopy(_read_verified(candidate)), "access": "read-only"}
candidate = _resolve_candidate(root_provider, definition, result_id)
return {**copy.deepcopy(_read_verified(candidate, definition)), "access": "read-only"}
@router.get("/{result_id}/assets/{asset_path:path}")
def get_asset(result_id: str, asset_path: str) -> FileResponse:
candidate = _resolve_candidate(root_provider, result_id)
manifest = _read_verified(candidate)
candidate = _resolve_candidate(root_provider, definition, result_id)
manifest = _read_verified(candidate, definition)
artifacts = manifest.get("artifacts")
if not isinstance(artifacts, list):
raise HTTPException(status_code=404, detail="Vegetation LAB asset not found")
@@ -90,8 +118,8 @@ def build_vegetation_shadow_lab_router(
@router.get("/{result_id}/masks/{sequence}")
def get_video_mask(result_id: str, sequence: int) -> Response:
candidate = _resolve_candidate(root_provider, result_id)
manifest = _read_verified(candidate)
candidate = _resolve_candidate(root_provider, definition, result_id)
manifest = _read_verified(candidate, definition)
route_video = manifest.get("route_video")
if (
not isinstance(route_video, dict)
@@ -168,9 +196,13 @@ def _configured_root(provider: RootProvider) -> Path | None:
return root if root.is_dir() else None
def _resolve_candidate(provider: RootProvider, result_id: str) -> Path:
def _resolve_candidate(
provider: RootProvider,
definition: LaboratoryEvidenceDefinition,
result_id: str,
) -> Path:
root = _configured_root(provider)
if root is None or RESULT_ID.fullmatch(result_id) is None:
if root is None or definition.result_id_pattern.fullmatch(result_id) is None:
raise HTTPException(status_code=404, detail="Vegetation LAB result not found")
candidate = root / result_id
if candidate.is_symlink():
@@ -184,7 +216,10 @@ def _resolve_candidate(provider: RootProvider, result_id: str) -> Path:
return resolved
def _read_verified(candidate: Path) -> dict[str, Any]:
def _read_verified(
candidate: Path,
definition: LaboratoryEvidenceDefinition,
) -> dict[str, Any]:
try:
rows: list[tuple[str, int, int, int, int]] = []
for path in sorted(candidate.rglob("*"), key=lambda item: item.as_posix()):
@@ -202,19 +237,39 @@ def _read_verified(candidate: Path) -> dict[str, Any]:
status_code=503,
detail="Vegetation LAB evidence failed verification",
) from None
return _read_verified_cached(str(candidate), signature)
return _read_verified_cached(
str(candidate),
signature,
definition.work_id,
str(definition.runtime_relative_root),
definition.result_id_prefix,
definition.document_name,
definition.result_schema_version,
)
@lru_cache(maxsize=16)
def _read_verified_cached(
candidate_text: str,
signature: tuple[tuple[str, int, int, int, int], ...],
work_id: str,
runtime_relative_root: str,
result_id_prefix: str,
document_name: str,
result_schema_version: str,
) -> dict[str, Any]:
del signature
candidate = Path(candidate_text)
definition = LaboratoryEvidenceDefinition(
work_id=work_id,
runtime_relative_root=PurePosixPath(runtime_relative_root),
result_id_prefix=result_id_prefix,
document_name=document_name,
result_schema_version=result_schema_version,
)
try:
verify_laboratory_evidence_result(_DEFINITION, candidate)
path = candidate / "result.json"
verify_laboratory_evidence_result(definition, candidate)
path = candidate / definition.document_name
if path.stat().st_size > _MAX_DOCUMENT_BYTES:
raise LaboratoryEvidenceReportError("Vegetation LAB document is too large")
payload = json.loads(path.read_text("utf-8"))
@@ -228,4 +283,7 @@ def _read_verified_cached(
return payload
__all__ = ["build_vegetation_shadow_lab_router"]
__all__ = [
"build_vegetation_benchmark_lab_router",
"build_vegetation_shadow_lab_router",
]