feat(lab): publish full TGS shadow evidence

This commit is contained in:
DCCONSTRUCTIONS
2026-08-26 23:07:06 +03:00
parent 40c850b167
commit d722b0f82b
22 changed files with 1508 additions and 28 deletions
+12
View File
@@ -134,6 +134,7 @@ from k1link.web.m48s_fixed_class_detector_lab_api import (
)
from k1link.web.m48t_risk_quality_lab_api import build_m48t_risk_quality_lab_router
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.map_api import (
MapGatewayConfiguration,
MapGatewayProxy,
@@ -1004,6 +1005,17 @@ app.include_router(
),
)
)
app.include_router(
build_m49_tgs_full_shadow_router(
root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "m49"
/ "tgs-full-shadow-results"
),
)
)
app.include_router(
build_m48s_fixed_class_detector_lab_router(
root_provider=lambda: (
+314
View File
@@ -0,0 +1,314 @@
"""Read-only API for the sealed complete TGS shadow."""
from __future__ import annotations
import copy
import json
import math
import re
from collections.abc import Callable
from functools import lru_cache
from pathlib import Path
from typing import Final
import numpy as np
from fastapi import APIRouter, HTTPException, Query, Response
from k1link.laboratory.m49_tgs_full_shadow import (
M49TgsFullShadowError,
M49TgsFullShadowResult,
PREFIX,
read_m49_tgs_full_shadow,
)
RootProvider = Callable[[], Path | None]
RESULT_ID: Final = re.compile(rf"^{re.escape(PREFIX)}[a-f0-9]{{64}}$")
ENDPOINT_ROOT: Final = "/api/v1/laboratory/m49/tgs-full-shadow"
def build_m49_tgs_full_shadow_router(*, root_provider: RootProvider = lambda: None) -> APIRouter:
router = APIRouter(prefix=ENDPOINT_ROOT, tags=["laboratory"])
def sealed(result_id: str) -> M49TgsFullShadowResult:
root = _configured_root(root_provider)
if root is None or RESULT_ID.fullmatch(result_id) is None:
raise HTTPException(status_code=404, detail="M49 TGS full shadow not found")
candidate = root / result_id
if candidate.is_symlink() or not candidate.is_dir():
raise HTTPException(status_code=404, detail="M49 TGS full shadow not found")
try:
resolved = candidate.resolve(strict=True)
if resolved.parent != root:
raise ValueError("result escaped configured root")
return _read_cached(str(resolved), _signature(resolved))
except (M49TgsFullShadowError, OSError, ValueError):
raise HTTPException(status_code=404, detail="M49 TGS full shadow not found") from None
@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 _catalog([], configured=False, invalid_total=0)
results: list[dict[str, object]] = []
invalid = 0
for candidate in sorted(root.iterdir()):
if not candidate.is_dir() or RESULT_ID.fullmatch(candidate.name) is None:
continue
try:
results.append(_project(_read_cached(str(candidate.resolve()), _signature(candidate))))
except (M49TgsFullShadowError, OSError, ValueError):
invalid += 1
results.sort(key=lambda row: (str(row["created_at_utc"]), str(row["result_id"])), reverse=True)
return _catalog(results[:limit], configured=True, invalid_total=invalid)
@router.get("/{result_id}")
def get_result(result_id: str) -> dict[str, object]:
return _project(sealed(result_id))
@router.get("/{result_id}/frames/{source_sequence}/spatial")
def get_spatial(result_id: str, source_sequence: int) -> Response:
if source_sequence < 0 or source_sequence >= 4489:
raise HTTPException(status_code=404, detail="M49 TGS full-shadow frame not found")
result = sealed(result_id)
try:
content = _frame_json_cached(
str(result.root), result_id, source_sequence, _evidence_signature(result.root)
)
except (OSError, ValueError, KeyError, json.JSONDecodeError):
raise HTTPException(status_code=503, detail="M49 TGS full-shadow spatial evidence failed verification") from None
return Response(
content=content,
media_type="application/json",
headers={"Cache-Control": "private, max-age=31536000, immutable", "X-Content-Type-Options": "nosniff"},
)
@router.get("/{result_id}/spatial/chunk")
def get_spatial_chunk(
result_id: str,
start: int = Query(ge=0, lt=4489),
count: int = Query(default=24, ge=1, le=24),
) -> Response:
result = sealed(result_id)
bounded_count = min(count, 4489 - start)
try:
content = _chunk_json_cached(
str(result.root), result_id, start, bounded_count, _evidence_signature(result.root)
)
except (OSError, ValueError, KeyError, json.JSONDecodeError):
raise HTTPException(
status_code=503,
detail="M49 TGS full-shadow spatial chunk failed verification",
) from None
return Response(
content=content,
media_type="application/json",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"X-Content-Type-Options": "nosniff",
},
)
return router
@lru_cache(maxsize=4)
def _read_cached(root: str, signature: tuple[int, ...]) -> M49TgsFullShadowResult:
del signature
return read_m49_tgs_full_shadow(Path(root))
@lru_cache(maxsize=4)
def _frames(root: str, signature: tuple[int, int]) -> tuple[dict[str, object], ...]:
del signature
path = Path(root) / "frames.ndjson"
rows = tuple(json.loads(line) for line in path.read_text(encoding="utf-8").splitlines())
if len(rows) != 4489:
raise ValueError("full-shadow frame catalog changed")
return rows
@lru_cache(maxsize=96)
def _frame_json_cached(
root: str, result_id: str, source_sequence: int, signature: tuple[int, ...]
) -> bytes:
root_path = Path(root)
frame_signature = (signature[-2], signature[-1])
frame = _frames(root, frame_signature)[source_sequence]
centers = np.load(root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False)
states_all = np.load(root_path / "costmap-states.npy", mmap_mode="r", allow_pickle=False)
z_all = np.load(root_path / "costmap-z-bounds-m.npy", mmap_mode="r", allow_pickle=False)
states = states_all[source_sequence]
z_bounds = z_all[source_sequence]
if (
centers.shape != (2244, 2)
or states_all.shape != (4489, 2244)
or z_all.shape != (4489, 2244, 2)
or not np.isfinite(centers).all()
or not np.isin(states, np.asarray([0, 1, 2, 3], dtype=np.uint8)).all()
):
raise ValueError("full-shadow spatial shape changed")
payload = {
"schema_version": "missioncore.m49-tgs-full-shadow-spatial/v1",
"result_id": result_id,
"source_sequence": source_sequence,
"source_frame_index": frame["source_frame_index"],
"session_seconds": frame["session_seconds"],
"sample_available": frame["sample_available"],
"coordinate_frame": "map-gravity-local",
"costmap": {
"cell_size_m": 0.45,
"radius_m": 12.0,
"centers_xy_m": centers.astype(float).tolist(),
"states": states.astype(int).tolist(),
"z_bounds_m": [
[
float(row[0]) if math.isfinite(float(row[0])) else None,
float(row[1]) if math.isfinite(float(row[1])) else None,
]
for row in z_bounds
],
},
"metrics": copy.deepcopy(frame),
"state_codes": {"UNOBSERVED": 0, "GROUND_SUPPORT": 1, "NONGROUND_OCCUPIED": 2, "UNKNOWN_REJECTED": 3},
"aos_used": False,
"gpu_used": False,
"authority": {"navigation_or_safety_accepted": False, "visual_quality_accepted": False},
"access": "read-only",
}
return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("utf-8")
@lru_cache(maxsize=16)
def _chunk_json_cached(
root: str,
result_id: str,
start: int,
count: int,
signature: tuple[int, ...],
) -> bytes:
root_path = Path(root)
frame_signature = (signature[-2], signature[-1])
frames = _frames(root, frame_signature)
centers = np.load(root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False)
states_all = np.load(root_path / "costmap-states.npy", mmap_mode="r", allow_pickle=False)
z_all = np.load(root_path / "costmap-z-bounds-m.npy", mmap_mode="r", allow_pickle=False)
if (
centers.shape != (2244, 2)
or states_all.shape != (4489, 2244)
or z_all.shape != (4489, 2244, 2)
or not np.isfinite(centers).all()
):
raise ValueError("full-shadow spatial chunk shape changed")
rows: list[dict[str, object]] = []
for source_sequence in range(start, start + count):
frame = frames[source_sequence]
states = states_all[source_sequence]
z_bounds = z_all[source_sequence]
if not np.isin(states, np.asarray([0, 1, 2, 3], dtype=np.uint8)).all():
raise ValueError("full-shadow spatial state changed")
rows.append(
{
"source_sequence": source_sequence,
"source_frame_index": frame["source_frame_index"],
"session_seconds": frame["session_seconds"],
"sample_available": frame["sample_available"],
"states": states.astype(int).tolist(),
"z_bounds_m": [
[
float(row[0]) if math.isfinite(float(row[0])) else None,
float(row[1]) if math.isfinite(float(row[1])) else None,
]
for row in z_bounds
],
"metrics": copy.deepcopy(frame),
}
)
payload = {
"schema_version": "missioncore.m49-tgs-full-shadow-spatial-chunk/v1",
"result_id": result_id,
"start": start,
"count": count,
"coordinate_frame": "map-gravity-local",
"costmap": {
"cell_size_m": 0.45,
"radius_m": 12.0,
"centers_xy_m": centers.astype(float).tolist(),
},
"frames": rows,
"state_codes": {
"UNOBSERVED": 0,
"GROUND_SUPPORT": 1,
"NONGROUND_OCCUPIED": 2,
"UNKNOWN_REJECTED": 3,
},
"aos_used": False,
"gpu_used": False,
"authority": {
"navigation_or_safety_accepted": False,
"visual_quality_accepted": False,
},
"access": "read-only",
}
return json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
def _project(result: M49TgsFullShadowResult) -> dict[str, object]:
return {
**copy.deepcopy(result.report),
"schema_version": "missioncore.m49-tgs-full-shadow-view/v1",
"result_id": result.result_id,
"created_at_utc": result.manifest["created_at_utc"],
"ground_truth": False,
"access": "read-only",
}
def _catalog(items: list[dict[str, object]], *, configured: bool, invalid_total: int) -> dict[str, object]:
return {
"schema_version": "missioncore.m49-tgs-full-shadow-catalog/v1",
"configured": configured,
"items": items,
"candidate_total": len(items) + invalid_total,
"invalid_total": invalid_total,
"access": "read-only",
}
def _configured_root(provider: RootProvider) -> Path | None:
value = provider()
if value is None or value.is_symlink() or not value.is_dir():
return None
return value.resolve(strict=True)
def _signature(root: Path) -> tuple[int, ...]:
values: list[int] = []
for name in ("manifest.json", "report.json", "worker-summary.json", *EVIDENCE_FILES):
path = root / name
if path.is_symlink() or not path.is_file():
raise ValueError("full-shadow artifact unavailable")
stat = path.stat()
values.extend((stat.st_size, stat.st_mtime_ns))
return tuple(values)
def _evidence_signature(root: Path) -> tuple[int, ...]:
return _signature(root)
EVIDENCE_FILES: Final = (
"costmap-cell-centers-xy-m.npy",
"costmap-cell-indices-xy.npy",
"costmap-states.npy",
"costmap-z-bounds-m.npy",
"frames.ndjson",
)
__all__ = ["build_m49_tgs_full_shadow_router"]