mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 11:51:14 +00:00
[AZ-220] Add shared runtime contract models
Implement the shared DTO contract surface with validation so runtime components consume one public model set instead of duplicating shapes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from shared.contracts import (
|
||||
AnchorDecision,
|
||||
CacheTileRecord,
|
||||
FdrEvent,
|
||||
FramePacket,
|
||||
PositionEstimate,
|
||||
TelemetrySample,
|
||||
VioStatePacket,
|
||||
VprCandidate,
|
||||
)
|
||||
|
||||
|
||||
def test_runtime_dtos_accept_valid_minimal_values() -> None:
|
||||
# Arrange
|
||||
timestamp_ns = 1_000_000
|
||||
|
||||
# Act
|
||||
contracts = [
|
||||
FramePacket(
|
||||
frame_id="frame-1",
|
||||
timestamp_ns=timestamp_ns,
|
||||
image_ref="frames/frame-1",
|
||||
calibration_id="calib-1",
|
||||
occlusion="clear",
|
||||
quality=0.95,
|
||||
),
|
||||
TelemetrySample(
|
||||
timestamp_ns=timestamp_ns,
|
||||
imu={"accel_x": 0.1},
|
||||
attitude={"roll": 0.0},
|
||||
altitude_m=950.0,
|
||||
airspeed_mps=16.0,
|
||||
gps_health="lost",
|
||||
),
|
||||
VioStatePacket(
|
||||
timestamp_ns=timestamp_ns,
|
||||
relative_pose={"x": 1.0},
|
||||
velocity_mps=(1.0, 0.0, 0.0),
|
||||
tracking_quality=0.8,
|
||||
),
|
||||
PositionEstimate(
|
||||
timestamp_ns=timestamp_ns,
|
||||
latitude_deg=49.9,
|
||||
longitude_deg=36.2,
|
||||
altitude_m=950.0,
|
||||
covariance_semimajor_m=12.0,
|
||||
source_label="satellite_anchored",
|
||||
fix_type=3,
|
||||
horizontal_accuracy_m=12.0,
|
||||
anchor_age_ms=200,
|
||||
),
|
||||
VprCandidate(
|
||||
chunk_id="chunk-1",
|
||||
tile_id="tile-1",
|
||||
score=0.87,
|
||||
footprint={"min_lat": 49.0},
|
||||
freshness_status="fresh",
|
||||
),
|
||||
AnchorDecision(
|
||||
candidate_id="candidate-1",
|
||||
accepted=True,
|
||||
estimated_pose={"x": 1.0},
|
||||
inliers=42,
|
||||
mean_reprojection_error_px=0.8,
|
||||
),
|
||||
CacheTileRecord(
|
||||
tile_id="tile-1",
|
||||
crs="EPSG:3857",
|
||||
meters_per_pixel=0.3,
|
||||
capture_date="2026-05-03",
|
||||
signature_hash="sha256:abc",
|
||||
trust_level="trusted",
|
||||
freshness_status="fresh",
|
||||
provenance="suite-satellite-service",
|
||||
),
|
||||
FdrEvent(
|
||||
event_type="health",
|
||||
timestamp_ns=timestamp_ns,
|
||||
component="shared.contracts",
|
||||
severity="info",
|
||||
payload_ref="fdr://segment/1",
|
||||
mission_id="mission-1",
|
||||
run_id="run-1",
|
||||
),
|
||||
]
|
||||
|
||||
# Assert
|
||||
assert len(contracts) == 8
|
||||
|
||||
|
||||
def test_missing_required_timestamp_is_rejected_with_structured_error() -> None:
|
||||
# Act
|
||||
with pytest.raises(ValidationError) as error:
|
||||
FramePacket(
|
||||
frame_id="frame-1",
|
||||
image_ref="frames/frame-1",
|
||||
calibration_id="calib-1",
|
||||
occlusion="clear",
|
||||
quality=0.95,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert error.value.errors()[0]["loc"] == ("timestamp_ns",)
|
||||
assert error.value.errors()[0]["type"] == "missing"
|
||||
|
||||
|
||||
def test_raw_frame_retention_is_rejected() -> None:
|
||||
# Act
|
||||
with pytest.raises(ValidationError) as error:
|
||||
FramePacket(
|
||||
frame_id="frame-1",
|
||||
timestamp_ns=1,
|
||||
image_ref="frames/frame-1",
|
||||
calibration_id="calib-1",
|
||||
occlusion="clear",
|
||||
quality=0.95,
|
||||
raw_frame_retained=True,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert "raw frame payloads must be referenced" in str(error.value)
|
||||
|
||||
|
||||
def test_position_accuracy_cannot_under_report_covariance() -> None:
|
||||
# Act
|
||||
with pytest.raises(ValidationError) as error:
|
||||
PositionEstimate(
|
||||
timestamp_ns=1,
|
||||
latitude_deg=49.9,
|
||||
longitude_deg=36.2,
|
||||
altitude_m=950.0,
|
||||
covariance_semimajor_m=50.0,
|
||||
source_label="satellite_anchored",
|
||||
fix_type=3,
|
||||
horizontal_accuracy_m=10.0,
|
||||
anchor_age_ms=200,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert "must not under-report" in str(error.value)
|
||||
|
||||
|
||||
def test_accepted_anchor_requires_estimated_pose() -> None:
|
||||
# Act
|
||||
with pytest.raises(ValidationError) as error:
|
||||
AnchorDecision(
|
||||
candidate_id="candidate-1",
|
||||
accepted=True,
|
||||
inliers=42,
|
||||
mean_reprojection_error_px=0.8,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert "accepted anchor decisions require estimated_pose" in str(error.value)
|
||||
Reference in New Issue
Block a user