from __future__ import annotations import os import re from collections.abc import Callable from pathlib import Path from typing import Any, Final from fastapi import APIRouter, HTTPException, Query from k1link.compute import ( LidarReplayError, LidarReplayPackV2, lidar_pack_catalog_item, lidar_pack_detail, ) LIDAR_CATALOG_SCHEMA: Final = "missioncore.lidar-replay-pack-catalog/v1" _PACK_ID = re.compile(r"^lidar-replay-pack-[a-f0-9]{64}$") RootProvider = Callable[[], Path | None] def configured_lidar_replay_root() -> Path | None: value = os.environ.get("MISSIONCORE_LIDAR_REPLAY_ROOT", "").strip() return Path(value).expanduser().absolute() if value else None def build_lidar_router( *, root_provider: RootProvider = configured_lidar_replay_root, ) -> APIRouter: router = APIRouter(prefix="/api/v1/lidar", tags=["lidar"]) @router.get("/replay-packs") def list_lidar_replay_packs( limit: int = Query(default=20, ge=1, le=100), ) -> dict[str, Any]: root = root_provider() if root is None or not root.is_dir(): return { "schema_version": LIDAR_CATALOG_SCHEMA, "configured": root is not None, "items": [], "valid_total": 0, "invalid_total": 0, "duplicate_total": 0, "access": "read-only", } items: list[dict[str, object]] = [] invalid_total = 0 duplicate_total = 0 seen_logical_content: set[str] = set() candidates = sorted( ( candidate for candidate in root.iterdir() if candidate.is_dir() and _PACK_ID.fullmatch(candidate.name) is not None ), key=lambda candidate: candidate.stat().st_mtime_ns, reverse=True, ) for candidate in candidates: try: pack = LidarReplayPackV2(candidate) try: item = lidar_pack_catalog_item(pack) logical_content = str(item["logical_content_sha256"]) if logical_content in seen_logical_content: duplicate_total += 1 continue seen_logical_content.add(logical_content) item["created_at_utc"] = pack.manifest.get("created_at_utc") items.append(item) finally: pack.close() except (LidarReplayError, OSError): invalid_total += 1 return { "schema_version": LIDAR_CATALOG_SCHEMA, "configured": True, "items": items[:limit], "valid_total": len(items), "invalid_total": invalid_total, "duplicate_total": duplicate_total, "access": "read-only", } @router.get("/replay-packs/{pack_id}") def get_lidar_replay_pack(pack_id: str) -> dict[str, object]: if _PACK_ID.fullmatch(pack_id) is None: raise HTTPException(status_code=404, detail="LiDAR replay pack не найден") root = root_provider() if root is None or not root.is_dir(): raise HTTPException(status_code=503, detail="LiDAR replay storage не настроен") candidate = root / pack_id if not candidate.is_dir(): raise HTTPException(status_code=404, detail="LiDAR replay pack не найден") try: pack = LidarReplayPackV2(candidate) try: return lidar_pack_detail(pack) finally: pack.close() except (LidarReplayError, OSError) as exc: raise HTTPException( status_code=409, detail="LiDAR replay pack не прошёл проверку целостности", ) from exc return router