compare M4.7 threat parity by component identity
This commit is contained in:
@@ -134,10 +134,15 @@ def compare_reference_graph_to_accepted_ledgers(
|
|||||||
]
|
]
|
||||||
if observed_camera_uncertainty != expected_camera_uncertainty:
|
if observed_camera_uncertainty != expected_camera_uncertainty:
|
||||||
mismatch["camera_uncertainty"] += 1
|
mismatch["camera_uncertainty"] += 1
|
||||||
if [item.to_dict() for item in delivery.threats] != _array(
|
observed_assessments = _assessment_index(
|
||||||
threat,
|
[item.to_dict() for item in delivery.threats],
|
||||||
"assessments",
|
"graph threat assessments",
|
||||||
):
|
)
|
||||||
|
expected_assessments = _assessment_index(
|
||||||
|
_array(threat, "assessments"),
|
||||||
|
"accepted threat assessments",
|
||||||
|
)
|
||||||
|
if observed_assessments != expected_assessments:
|
||||||
mismatch["threat_assessments"] += 1
|
mismatch["threat_assessments"] += 1
|
||||||
compared += 1
|
compared += 1
|
||||||
if temporal_stream.readline() or threat_stream.readline():
|
if temporal_stream.readline() or threat_stream.readline():
|
||||||
@@ -204,6 +209,19 @@ def _boolean(document: dict[str, object], key: str) -> bool:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _assessment_index(
|
||||||
|
rows: list[dict[str, object]],
|
||||||
|
label: str,
|
||||||
|
) -> dict[str, dict[str, object]]:
|
||||||
|
indexed: dict[str, dict[str, object]] = {}
|
||||||
|
for row in rows:
|
||||||
|
component_id = _string(row, "component_id")
|
||||||
|
if component_id in indexed:
|
||||||
|
raise ReferenceGraphParityError(f"{label} contains a duplicate component")
|
||||||
|
indexed[component_id] = row
|
||||||
|
return indexed
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"REFERENCE_GRAPH_PARITY_SCHEMA",
|
"REFERENCE_GRAPH_PARITY_SCHEMA",
|
||||||
"ReferenceGraphParityError",
|
"ReferenceGraphParityError",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from k1link.perception.contracts import LocalObstacleMap, SourceAccounting
|
from k1link.perception.contracts import LocalObstacleMap, SourceAccounting, ThreatAssessment
|
||||||
from k1link.perception.graph_contracts import (
|
from k1link.perception.graph_contracts import (
|
||||||
REFERENCE_GRAPH_ID_V2,
|
REFERENCE_GRAPH_ID_V2,
|
||||||
DeliveredFrame,
|
DeliveredFrame,
|
||||||
@@ -18,7 +18,7 @@ from k1link.perception.reference_graph_parity import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _result():
|
def _result(threats: tuple[ThreatAssessment, ...] = ()):
|
||||||
obstacle_map = LocalObstacleMap(
|
obstacle_map = LocalObstacleMap(
|
||||||
source_id="RAVNOVES00",
|
source_id="RAVNOVES00",
|
||||||
session_id="20260720T065719Z_viewer_live",
|
session_id="20260720T065719Z_viewer_live",
|
||||||
@@ -48,7 +48,7 @@ def _result():
|
|||||||
reason="object-payload-delivered",
|
reason="object-payload-delivered",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
deliveries=(DeliveredFrame(0, obstacle_map, ()),),
|
deliveries=(DeliveredFrame(0, obstacle_map, threats),),
|
||||||
queue_high_watermarks=(("detector", 1),),
|
queue_high_watermarks=(("detector", 1),),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -119,3 +119,43 @@ def test_reference_graph_parity_reports_threat_drift(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
assert report.accepted is False
|
assert report.accepted is False
|
||||||
assert dict(report.mismatch_counts)["threat_assessments"] == 1
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user