feat(polygon): add full-frame operator review

This commit is contained in:
DCCONSTRUCTIONS
2026-07-25 16:45:10 +03:00
parent cea63f9a1e
commit b037076506
10 changed files with 2080 additions and 330 deletions
+281
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
import hashlib
import json
import math
import os
import re
import secrets
@@ -10,6 +11,7 @@ from datetime import UTC, datetime
from pathlib import Path
from typing import Annotated, Any, Final
import numpy as np
from fastapi import APIRouter, Header, HTTPException, Query
from fastapi import Path as PathParameter
from pydantic import BaseModel, ConfigDict, Field
@@ -30,6 +32,7 @@ from k1link.simulation.worker_gateway import (
)
POLYGON_RUNS_ROOT_ENV: Final = "MISSIONCORE_POLYGON_RUNS_ROOT"
POLYGON_REVIEW_ROOT_ENV: Final = "MISSIONCORE_POLYGON_REVIEW_ROOT"
POLYGON_WORKER_SOCKET_ENV: Final = "MISSIONCORE_POLYGON_WORKER_SOCKET"
POLYGON_WORKER_CONTROL_ENV: Final = "MISSIONCORE_POLYGON_WORKER_CONTROL"
MISSION_CORE_COMMIT_ENV: Final = "MISSIONCORE_COMMIT"
@@ -41,7 +44,15 @@ GROUND_REPORT_ARTIFACT_KIND: Final = "goose-ground-qualification-report"
GROUND_FAILURE_ARTIFACT_KIND: Final = "goose-ground-qualification-failure-preview"
GROUND_REPORT_SCHEMA: Final = "missioncore.goose-ground-qualification-report/v1"
GROUND_FAILURE_SCHEMA: Final = "missioncore.goose-ground-qualification-failure-preview/v1"
GROUND_REVIEW_SCHEMA: Final = "missioncore.polygon-ground-review/v1"
GROUND_REVIEW_FRAME_SCHEMA: Final = "missioncore.polygon-ground-review-frame/v1"
GOOSE_REVIEW_PACK_SCHEMA: Final = "missioncore.goose-ground-review-pack/v1"
GOOSE_REVIEW_FRAME_SCHEMA: Final = "missioncore.goose-ground-review-frame/v1"
MAX_QUALIFICATION_ARTIFACT_BYTES: Final = 32 * 1024**2
MAX_REVIEW_MANIFEST_BYTES: Final = 8 * 1024**2
MAX_REVIEW_FRAME_BYTES: Final = 2 * 1024**2
MAX_REVIEW_FRAMES: Final = 2_000
MAX_REVIEW_POINTS: Final = 20_000
COMMIT_PATTERN: Final = re.compile(r"^[a-f0-9]{40}$")
READ_ONLY_LIMITATIONS: Final[tuple[str, ...]] = (
"Архивный UI-0 публикует только квалификационные доказательства; "
@@ -258,6 +269,121 @@ def build_polygon_router(
**{key: preview[key] for key in required},
}
@router.get("/runs/{run_id}/qualification/review")
def get_polygon_ground_review(
run_id: Annotated[
str,
PathParameter(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"),
],
) -> dict[str, Any]:
_, run = _load_run(root_provider, run_id)
_, manifest = _read_ground_review_manifest(root_provider, run.run_id)
return {
"schema_version": GROUND_REVIEW_SCHEMA,
"access": "read-only",
"run_id": run.run_id,
"identity_sha256": manifest["identity_sha256"],
"source_id": manifest["source_id"],
"frame_count": manifest["frame_count"],
"preview_points": manifest["preview_points"],
"frames": [
{
key: frame[key]
for key in (
"sequence",
"frame_id",
"source_point_count",
"point_count",
"current",
"patchworkpp",
"ground_iou_delta",
)
}
for frame in manifest["frames"]
],
"safety": manifest["safety"],
}
@router.get("/runs/{run_id}/qualification/review/frames/{frame_id}")
def get_polygon_ground_review_frame(
run_id: Annotated[
str,
PathParameter(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"),
],
frame_id: Annotated[
str,
PathParameter(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"),
],
) -> dict[str, Any]:
_, run = _load_run(root_provider, run_id)
pack_root, manifest = _read_ground_review_manifest(root_provider, run.run_id)
matches = [frame for frame in manifest["frames"] if frame.get("frame_id") == frame_id]
if len(matches) != 1:
raise HTTPException(
status_code=404,
detail="Кадр визуального прогона не найден.",
)
frame = matches[0]
path = _verified_review_frame_path(pack_root, frame)
try:
with np.load(path, allow_pickle=False) as source:
schema = str(source["schema"][0])
stored_frame_id = str(source["frame_id"][0])
source_point_count = int(source["source_point_count"][0])
xyz_cm = np.asarray(source["xyz_cm"])
remission = np.asarray(source["remission_u8"])
flags = np.asarray(source["flags"])
except (OSError, ValueError, KeyError, IndexError) as exc:
raise HTTPException(
status_code=500,
detail="Кадр визуального прогона повреждён.",
) from exc
point_count = int(frame["point_count"])
if (
schema != GOOSE_REVIEW_FRAME_SCHEMA
or stored_frame_id != frame_id
or source_point_count != frame["source_point_count"]
or xyz_cm.dtype != np.dtype("<i2")
or xyz_cm.shape != (point_count, 3)
or remission.dtype != np.uint8
or remission.shape != (point_count,)
or flags.dtype != np.uint8
or flags.shape != (point_count,)
or not 1 <= point_count <= MAX_REVIEW_POINTS
or np.any(flags > 15)
):
raise HTTPException(
status_code=500,
detail="Кадр визуального прогона нарушает контракт.",
)
evaluated = (flags & 1) != 0
ground_truth = (flags & 2) != 0
current = (flags & 4) != 0
patchwork = (flags & 8) != 0
return {
"schema_version": GROUND_REVIEW_FRAME_SCHEMA,
"access": "read-only",
"run_id": run.run_id,
"source_id": manifest["source_id"],
"frame_id": frame_id,
"source_point_count": source_point_count,
"point_count": point_count,
"sampling": "deterministic-even-index",
"points_xyz_m": (xyz_cm.astype(np.float32) / 100.0).tolist(),
"intensity_0_255": remission.tolist(),
"ground_truth_ground": ground_truth.astype(np.uint8).tolist(),
"evaluated": evaluated.astype(np.uint8).tolist(),
"current_ground": current.astype(np.uint8).tolist(),
"patchwork_ground": patchwork.astype(np.uint8).tolist(),
"current_disagreement": (evaluated & (current != ground_truth))
.astype(np.uint8)
.tolist(),
"patchwork_disagreement": (evaluated & (patchwork != ground_truth))
.astype(np.uint8)
.tolist(),
"safety": manifest["safety"],
}
@router.get("/worker")
def get_polygon_worker() -> dict[str, Any]:
gateway = worker_provider()
@@ -485,6 +611,161 @@ def _read_registered_json(
return value
def _read_ground_review_manifest(
root_provider: RootProvider,
run_id: str,
) -> tuple[Path, dict[str, Any]]:
runs_root = root_provider()
if runs_root is None or not runs_root.is_absolute():
raise HTTPException(
status_code=503,
detail="Источник визуальных прогонов не настроен.",
)
configured = os.environ.get(POLYGON_REVIEW_ROOT_ENV, "").strip()
review_root = (
Path(configured).expanduser() if configured else runs_root.parent / "polygon-review-packs"
)
if not review_root.is_absolute():
raise HTTPException(
status_code=503,
detail="Источник визуальных прогонов настроен некорректно.",
)
run_review_root = review_root / run_id
try:
resolved_review_root = review_root.resolve()
resolved_run_root = run_review_root.resolve(strict=True)
resolved_run_root.relative_to(resolved_review_root)
except (OSError, ValueError) as exc:
raise HTTPException(
status_code=404,
detail="Покадровый просмотр для прогона ещё не опубликован.",
) from exc
candidates = sorted(
path
for path in resolved_run_root.iterdir()
if path.is_dir()
and not path.is_symlink()
and re.fullmatch(r"review-[a-f0-9]{64}", path.name)
and (path / "manifest.json").is_file()
)
if not candidates:
raise HTTPException(
status_code=404,
detail="Покадровый просмотр для прогона ещё не опубликован.",
)
pack_root = candidates[-1]
manifest_path = pack_root / "manifest.json"
try:
if manifest_path.is_symlink() or manifest_path.stat().st_size > MAX_REVIEW_MANIFEST_BYTES:
raise OSError
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise HTTPException(
status_code=500,
detail="Манифест визуального прогона повреждён.",
) from exc
frames = manifest.get("frames") if isinstance(manifest, dict) else None
safety = manifest.get("safety") if isinstance(manifest, dict) else None
if (
not isinstance(manifest, dict)
or manifest.get("schema_version") != GOOSE_REVIEW_PACK_SCHEMA
or manifest.get("source_run_id") != run_id
or not isinstance(manifest.get("identity_sha256"), str)
or not re.fullmatch(r"[a-f0-9]{64}", manifest["identity_sha256"])
or pack_root.name != f"review-{manifest['identity_sha256']}"
or not isinstance(manifest.get("source_id"), str)
or not isinstance(manifest.get("preview_points"), int)
or not isinstance(frames, list)
or not 1 <= len(frames) <= MAX_REVIEW_FRAMES
or manifest.get("frame_count") != len(frames)
or not isinstance(safety, dict)
or safety.get("visualization_only") is not True
or safety.get("navigation_or_safety_accepted") is not False
):
raise HTTPException(
status_code=500,
detail="Манифест визуального прогона нарушает контракт.",
)
seen: set[str] = set()
for expected_sequence, frame in enumerate(frames):
if (
not isinstance(frame, dict)
or frame.get("sequence") != expected_sequence
or not isinstance(frame.get("frame_id"), str)
or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}", frame["frame_id"])
or frame["frame_id"] in seen
or not isinstance(frame.get("source_point_count"), int)
or not isinstance(frame.get("point_count"), int)
or not 1 <= frame["point_count"] <= MAX_REVIEW_POINTS
or frame["source_point_count"] < frame["point_count"]
or not isinstance(frame.get("relative_path"), str)
or not isinstance(frame.get("sha256"), str)
or not re.fullmatch(r"[a-f0-9]{64}", frame["sha256"])
or not isinstance(frame.get("byte_length"), int)
or not 1 <= frame["byte_length"] <= MAX_REVIEW_FRAME_BYTES
or not _valid_review_metrics(frame.get("current"))
or not _valid_review_metrics(frame.get("patchworkpp"))
or not isinstance(frame.get("ground_iou_delta"), (int, float))
or not math.isfinite(frame["ground_iou_delta"])
or not -1 <= frame["ground_iou_delta"] <= 1
):
raise HTTPException(
status_code=500,
detail="Индекс кадров визуального прогона нарушает контракт.",
)
seen.add(frame["frame_id"])
return pack_root, manifest
def _valid_review_metrics(value: object) -> bool:
if not isinstance(value, dict):
return False
for key in (
"ground_iou",
"natural_ground_recall",
"obstacle_non_ground_recall",
):
metric = value.get(key)
if (
not isinstance(metric, (int, float))
or not math.isfinite(metric)
or not 0 <= metric <= 1
):
return False
latency = value.get("latency_ms")
return isinstance(latency, (int, float)) and math.isfinite(latency) and latency >= 0
def _verified_review_frame_path(pack_root: Path, frame: dict[str, Any]) -> Path:
relative = Path(frame["relative_path"])
if relative.is_absolute() or relative.parts[:1] != ("frames",):
raise HTTPException(
status_code=500,
detail="Кадр визуального прогона нарушает границу.",
)
path = pack_root / relative
try:
resolved = path.resolve(strict=True)
resolved.relative_to(pack_root)
if (
path.is_symlink()
or not resolved.is_file()
or resolved.stat().st_size != frame["byte_length"]
):
raise OSError
except (OSError, ValueError) as exc:
raise HTTPException(
status_code=500,
detail="Файл визуального прогона нарушает границу.",
) from exc
if hashlib.sha256(resolved.read_bytes()).hexdigest() != frame["sha256"]:
raise HTTPException(
status_code=500,
detail="Кадр визуального прогона не прошёл SHA-256.",
)
return resolved
def _run_summary(run: QualificationRun) -> dict[str, Any]:
return {
"run_id": run.run_id,