Files
NODEDC_MISSION_CORE/tests/test_m48_ravnoves00_pack.py

356 lines
13 KiB
Python

from __future__ import annotations
import hashlib
import json
from itertools import pairwise
from pathlib import Path
from types import SimpleNamespace
from k1link.laboratory.m47_reference_graph import M47_REFERENCE_GRAPH_LAB_SCHEMA
from k1link.laboratory.m48_object_quality import read_m48_object_quality_pack
from k1link.laboratory.m48_ravnoves00_pack import (
M48_FRAME_COUNT,
M48_SELECTION_SCHEMA,
_prediction_objects,
prepare_m48_ravnoves00_pack,
)
def _canonical_json(value: object) -> str:
return json.dumps(value, sort_keys=True, separators=(",", ":"))
def _write_json(path: Path, value: object) -> None:
path.write_text(_canonical_json(value) + "\n", encoding="utf-8")
def _write_jsonl(path: Path, rows: list[dict[str, object]]) -> str:
raw = "".join(_canonical_json(row) + "\n" for row in rows).encode()
path.write_bytes(raw)
return hashlib.sha256(raw).hexdigest()
def _recursive_keys(value: object) -> set[str]:
if isinstance(value, dict):
return set(value) | {key for item in value.values() for key in _recursive_keys(item)}
if isinstance(value, list):
return {key for item in value for key in _recursive_keys(item)}
return set()
def test_prediction_projection_is_class_free_and_conservative() -> None:
rows = _prediction_objects(
[
{
"proposal_id": "proposal-0-1",
"bbox_xyxy": [80.0, 60.0, 400.0, 300.0],
"occupied_support": False,
"threat_decision": "unknown",
"semantic_hint": "person",
"objectness": 0.99,
},
{
"proposal_id": "proposal-0-2",
"bbox_xyxy": [400.0, 300.0, 720.0, 540.0],
"occupied_support": True,
"threat_decision": "threat",
"semantic_hint": "car",
"objectness": 0.98,
},
],
geometry_observations=[
{
"proposal_ids": ["proposal-0-1"],
"currentness": "current",
"metric_geometry": None,
},
{
"proposal_ids": ["proposal-0-2"],
"currentness": "current",
"metric_geometry": {"centroid_xyz_m": [1.0, 2.0, 3.0]},
},
],
metric_obstacles=[
{
"centroid_map_xyz_m": [1.0, 2.0, 3.0],
"motion": "moving",
"assessment": {"decision": "threat"},
}
],
)
assert rows == [
{
"prediction_id": "proposal-0-1",
"extent_xyxy": [0.1, 0.1, 0.5, 0.5],
"geometry_association": "unknown",
"freshness": "current",
"motion": "unsupported",
"threat": "unknown",
"unknown_causes": [
"insufficient-geometry-support",
"threat-evidence-insufficient",
],
},
{
"prediction_id": "proposal-0-2",
"extent_xyxy": [0.5, 0.5, 0.9, 0.9],
"geometry_association": "associated",
"freshness": "current",
"motion": "moving",
"threat": "threat",
"unknown_causes": [],
},
]
assert "semantic_hint" not in _canonical_json(rows)
assert "objectness" not in _canonical_json(rows)
def test_real_selection_contract_is_balanced_and_prediction_blind() -> None:
repository_root = Path(__file__).resolve().parents[1]
document = json.loads(
(repository_root / "config/perception/m48-object-quality-selection-v1.json").read_text()
)
clips = document["clips"]
assert document["schema_version"] == M48_SELECTION_SCHEMA
assert len(clips) == 24
assert {clip["split"] for clip in clips} == {"development", "validation"}
assert all("strata" not in clip and "hypotheses" not in clip for clip in clips)
assert document["selection_hypothesis_profile"] == {
"derivation": "exact-frozen-prediction-rows-before-independent-truth",
"small_obstacle_max_normalized_area": 0.001,
"fisheye_edge_margin_normalized": 0.08,
"sparse_scene_max_median_prediction_count": 2.0,
}
for field in ("component_id", "route_block", "time_block"):
group_splits: dict[str, set[str]] = {}
for clip in clips:
group_splits.setdefault(clip[field], set()).add(clip["split"])
assert all(len(splits) == 1 for splits in group_splits.values())
assert len(group_splits) < len(clips)
assert all(left["end_sequence"] < right["start_sequence"] for left, right in pairwise(clips))
forbidden = {"label", "labels", "truth", "review", "adjudication"}
assert forbidden.isdisjoint(document)
def test_prepare_pack_binds_all_source_ledgers_and_freezes_selected_frames(
tmp_path: Path,
monkeypatch,
) -> None:
repository_root = Path(__file__).resolve().parents[1]
graph_root = tmp_path / ("m47-reference-graph-" + "a" * 64)
threat_root = tmp_path / ("m4-threat-replay-" + "b" * 64)
geometry_root = tmp_path / ("m4-geometry-replay-" + "e" * 64)
lab_root = tmp_path / ("m47-reference-graph-lab-" + "c" * 64)
graph_root.mkdir()
threat_root.mkdir()
geometry_root.mkdir()
lab_root.mkdir()
_write_json(lab_root / "manifest.json", {"fixture": True})
graph_rows: list[dict[str, object]] = []
threat_rows: list[dict[str, object]] = []
geometry_rows: list[dict[str, object]] = []
camera_rows: list[dict[str, object]] = []
for frame_index in range(M48_FRAME_COUNT):
source_time_ns = 35_421_857_292 + frame_index * 100_000_000
graph_rows.append(
{
"sequence": frame_index,
"obstacle_map": {
"schema_version": "missioncore.local-obstacle-map/v1",
"frame_id": f"frame-{frame_index:06d}",
"free_space_claimed": False,
},
"threats": [],
}
)
threat_rows.append(
{
"schema_version": "missioncore.perception-threat-replay-frame/v2",
"sequence": frame_index,
"frame_id": f"frame-{frame_index:06d}",
"source_time_ns": source_time_ns,
"source_available": True,
"camera_proposals": [
{
"proposal_id": f"proposal-{frame_index}-0",
"bbox_xyxy": [0.0, 0.0, 20.0, 20.0],
"occupied_support": True,
"threat_decision": "threat" if frame_index % 2 == 0 else "not-threat",
},
{
"proposal_id": f"proposal-{frame_index}-1",
"bbox_xyxy": [80.0, 60.0, 400.0, 300.0],
"occupied_support": False,
"threat_decision": "unknown",
},
],
"metric_obstacles": [
{
"centroid_map_xyz_m": [1.0, 2.0, 3.0],
"motion": "moving" if frame_index % 2 == 0 else "stationary",
"assessment": {
"decision": "threat" if frame_index % 2 == 0 else "not-threat"
},
}
],
}
)
geometry_rows.append(
{
"schema_version": "missioncore.perception-geometry-replay-frame/v1",
"sequence": frame_index,
"frame_id": f"frame-{frame_index:06d}",
"source_available": True,
"observations": [
{
"proposal_ids": [f"proposal-{frame_index}-0"],
"currentness": "current",
"metric_geometry": {"centroid_xyz_m": [1.0, 2.0, 3.0]},
},
{
"proposal_ids": [f"proposal-{frame_index}-1"],
"currentness": "current",
"metric_geometry": None,
},
],
}
)
camera_rows.append(
{
"schema_version": "missioncore.camera-recording-index/v1",
"sequence": frame_index + 1,
"kind": "media",
"session_monotonic_ns": frame_index + 1,
"sha256": hashlib.sha256(f"camera-{frame_index}".encode()).hexdigest(),
}
)
graph_sha256 = _write_jsonl(graph_root / "frames.jsonl", graph_rows)
threat_sha256 = _write_jsonl(threat_root / "frames.jsonl", threat_rows)
geometry_sha256 = _write_jsonl(geometry_root / "frames.jsonl", geometry_rows)
camera_index = tmp_path / "index.jsonl"
_write_jsonl(camera_index, camera_rows)
_write_json(
graph_root / "manifest.json",
{
"schema_version": "missioncore.reference-perception-graph-manifest/v1",
"result_id": graph_root.name,
"accepted": True,
"graph_id": "reference-perception-graph/v2",
"run_mode": "lossless-replay",
"files": {
"frames.jsonl": {
"bytes": (graph_root / "frames.jsonl").stat().st_size,
"sha256": graph_sha256,
}
},
},
)
_write_json(
threat_root / "manifest.json",
{
"schema_version": "missioncore.perception-threat-replay-result/v2",
"result_id": threat_root.name,
"accepted": True,
"identity": {
"source_session_id": "20260720T065719Z_viewer_live",
"frames_sha256": threat_sha256,
"geometry_result_id": geometry_root.name,
"geometry_frames_sha256": geometry_sha256,
},
},
)
_write_json(
geometry_root / "manifest.json",
{
"schema_version": "missioncore.perception-geometry-replay-result/v1",
"identity": {
"accepted": True,
"source_pack_id": (
"e10-lidar-pack-"
"576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b"
),
"frames_sha256": geometry_sha256,
},
},
)
authority = {
"mode": "replay-simulated",
"physical_live": False,
"commands_enabled": False,
"actuation_allowed": False,
"navigation_or_safety_accepted": False,
}
lab = SimpleNamespace(
result_id=lab_root.name,
result_root=lab_root,
manifest={
"schema_version": M47_REFERENCE_GRAPH_LAB_SCHEMA,
"accepted": True,
"ground_truth": False,
},
report={
"source": {
"graph_result_id": graph_root.name,
"visual_result_id": threat_root.name,
"threat_frames_sha256": threat_sha256,
"source_id": "RAVNOVES00",
"source_session_id": "20260720T065719Z_viewer_live",
},
"method": {
"graph_id": "reference-perception-graph/v2",
"run_mode": "lossless-replay",
"canonical_payload_sha256": "d" * 64,
},
"decision": {
"state": "accepted-reference-graph-replay",
"next_gate": "independent-object-centric-detection-quality",
},
"acceptance": {"accepted": True},
"authority": authority,
},
)
monkeypatch.setattr(
"k1link.laboratory.m48_ravnoves00_pack.read_m47_reference_graph_lab",
lambda _: lab,
)
monkeypatch.setattr(
"k1link.laboratory.m48_object_quality.read_m47_reference_graph_lab",
lambda _: lab,
)
result = prepare_m48_ravnoves00_pack(
m47_lab_root=lab_root,
graph_result_root=graph_root,
threat_result_root=threat_root,
geometry_result_root=geometry_root,
camera_index_path=camera_index,
selection_path=(repository_root / "config/perception/m48-object-quality-selection-v1.json"),
frozen_at_utc="2026-08-24T00:00:00Z",
output_root=tmp_path / "runtime/m48/object-quality-packs",
)
assert read_m48_object_quality_pack(result.result_root) == result
assert result.report["metrics"]["clip_count"] == 24
assert result.report["metrics"]["frame_count"] == 24 * 61
assert len(result.predictions) == 24 * 61
assert result.manifest["identity"]["preparation"]["adapter"]["sha256"] == (
hashlib.sha256(
(repository_root / "src/k1link/laboratory/m48_ravnoves00_pack.py").read_bytes()
).hexdigest()
)
assert result.manifest["identity"]["preparation"]["selection"]["sha256"] == (
hashlib.sha256(
(
repository_root / "config/perception/m48-object-quality-selection-v1.json"
).read_bytes()
).hexdigest()
)
reviewer_package = json.loads((result.result_root / "reviewer-package.json").read_text())
reviewer_keys = _recursive_keys(reviewer_package)
assert "strata" not in reviewer_keys
assert "prediction_id" not in reviewer_keys
assert "semantic_hint" not in reviewer_keys