162 lines
4.7 KiB
Python
162 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from k1link.perception.contracts import LocalObstacleMap, SourceAccounting, ThreatAssessment
|
|
from k1link.perception.graph_contracts import (
|
|
REFERENCE_GRAPH_ID_V2,
|
|
DeliveredFrame,
|
|
GraphRunMode,
|
|
GraphState,
|
|
TerminalOutcome,
|
|
TerminalOutcomeType,
|
|
build_graph_run_result_v2,
|
|
)
|
|
from k1link.perception.reference_graph_parity import (
|
|
compare_reference_graph_to_accepted_ledgers,
|
|
)
|
|
|
|
|
|
def _result(threats: tuple[ThreatAssessment, ...] = ()):
|
|
obstacle_map = LocalObstacleMap(
|
|
source_id="RAVNOVES00",
|
|
session_id="20260720T065719Z_viewer_live",
|
|
frame_id="frame-000000",
|
|
graph_id=REFERENCE_GRAPH_ID_V2,
|
|
generated_monotonic_ns=1,
|
|
output_age_ns=0,
|
|
occupied=(),
|
|
unknown=(),
|
|
camera_uncertainty=(),
|
|
accounting=SourceAccounting(1, 1, 0, 0),
|
|
)
|
|
return build_graph_run_result_v2(
|
|
graph_id=REFERENCE_GRAPH_ID_V2,
|
|
source_profile_id="m4-ravnoves00-recorded-realtime/v1",
|
|
run_mode=GraphRunMode.LOSSLESS_REPLAY,
|
|
state=GraphState.STOPPED,
|
|
admitted_count=1,
|
|
outcomes=(
|
|
TerminalOutcome(
|
|
source_id="RAVNOVES00",
|
|
session_id="20260720T065719Z_viewer_live",
|
|
frame_id="frame-000000",
|
|
sequence=0,
|
|
outcome=TerminalOutcomeType.DELIVERED,
|
|
stage_id="threat",
|
|
reason="object-payload-delivered",
|
|
),
|
|
),
|
|
deliveries=(DeliveredFrame(0, obstacle_map, threats),),
|
|
queue_high_watermarks=(("detector", 1),),
|
|
)
|
|
|
|
|
|
def _write_ledgers(root: Path, *, threat_assessments: list[dict[str, object]]) -> tuple[Path, Path]:
|
|
temporal = root / "temporal.jsonl"
|
|
threat = root / "threat.jsonl"
|
|
temporal.write_text(
|
|
json.dumps(
|
|
{
|
|
"sequence": 0,
|
|
"frame_id": "frame-000000",
|
|
"current": [],
|
|
"rolling_retained": [],
|
|
"held": [],
|
|
"expired": [],
|
|
}
|
|
)
|
|
+ "\n",
|
|
"utf-8",
|
|
)
|
|
threat.write_text(
|
|
json.dumps(
|
|
{
|
|
"sequence": 0,
|
|
"frame_id": "frame-000000",
|
|
"camera_proposals": [],
|
|
"assessments": threat_assessments,
|
|
}
|
|
)
|
|
+ "\n",
|
|
"utf-8",
|
|
)
|
|
return temporal, threat
|
|
|
|
|
|
def test_reference_graph_parity_accepts_exact_frame_semantics(tmp_path: Path) -> None:
|
|
temporal, threat = _write_ledgers(tmp_path, threat_assessments=[])
|
|
|
|
report = compare_reference_graph_to_accepted_ledgers(
|
|
_result(),
|
|
temporal_frames_path=temporal,
|
|
threat_frames_path=threat,
|
|
expected_frames=1,
|
|
)
|
|
|
|
assert report.accepted is True
|
|
assert dict(report.mismatch_counts) == {
|
|
"camera_uncertainty": 0,
|
|
"current": 0,
|
|
"expired": 0,
|
|
"held": 0,
|
|
"rolling_retained": 0,
|
|
"source_binding": 0,
|
|
"threat_assessments": 0,
|
|
}
|
|
|
|
|
|
def test_reference_graph_parity_reports_threat_drift(tmp_path: Path) -> None:
|
|
temporal, threat = _write_ledgers(tmp_path, threat_assessments=[{"component_id": "x"}])
|
|
|
|
report = compare_reference_graph_to_accepted_ledgers(
|
|
_result(),
|
|
temporal_frames_path=temporal,
|
|
threat_frames_path=threat,
|
|
expected_frames=1,
|
|
)
|
|
|
|
assert report.accepted is False
|
|
assert dict(report.mismatch_counts)["threat_assessments"] == 1
|
|
|
|
|
|
def test_reference_graph_parity_treats_assessment_order_as_nonsemantic(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
first = {
|
|
"schema_version": "missioncore.threat-assessment/v1",
|
|
"assessment_id": "threat-first",
|
|
"component_id": "component-first",
|
|
"rig_profile_id": "rig-test",
|
|
"corridor_profile_id": "corridor-test",
|
|
"qualification": "unqualified",
|
|
"relative_speed_mps": None,
|
|
"closest_approach_m": None,
|
|
"ttc_seconds": None,
|
|
"corridor_intersection": "unknown",
|
|
"decision": "unknown",
|
|
"reason_codes": ["test-unavailable"],
|
|
"authority": "replay-simulated",
|
|
"physical_collision_accepted": False,
|
|
"actuation_allowed": False,
|
|
}
|
|
second = {
|
|
**first,
|
|
"assessment_id": "threat-second",
|
|
"component_id": "component-second",
|
|
}
|
|
temporal, threat = _write_ledgers(
|
|
tmp_path,
|
|
threat_assessments=[second, first],
|
|
)
|
|
|
|
report = compare_reference_graph_to_accepted_ledgers(
|
|
_result((ThreatAssessment.from_dict(first), ThreatAssessment.from_dict(second))),
|
|
temporal_frames_path=temporal,
|
|
threat_frames_path=threat,
|
|
expected_frames=1,
|
|
)
|
|
|
|
assert report.accepted is True
|