from __future__ import annotations import math import pytest from k1link.compute.e45_binding_sensitivity import ( E45_ROW_SCHEMA, E45BindingSensitivityError, E45BindingSensitivityProfile, analyze_binding_sensitivity, ) def _row( item_id: str, *, radius: float, speed: float, angular: float, lidar_age: float, pose_age: float, residual: float, points: int, ) -> dict[str, object]: age_seconds = (lidar_age + pose_age) / 1000.0 return { "schema_version": E45_ROW_SCHEMA, "item_id": item_id, "image_radius_normalized": radius, "translation_speed_mps": speed, "angular_speed_deg_s": angular, "lidar_camera_age_ms": lidar_age, "pose_point_age_ms": pose_age, "motion_exposure_translation_m": speed * age_seconds, "motion_exposure_rotation_deg": angular * age_seconds, "centroid_residual_bbox_diagonal": residual, "occupied_points_in_bbox": points, "supported": points >= 2, } def test_e45_stratifies_existing_diagnostic_residual_without_target_claim() -> None: rows = [ _row( "one", radius=0.2, speed=0.0, angular=0.0, lidar_age=5.0, pose_age=2.0, residual=0.1, points=5, ), _row( "two", radius=0.7, speed=0.5, angular=6.0, lidar_age=40.0, pose_age=15.0, residual=0.2, points=8, ), _row( "three", radius=1.0, speed=2.0, angular=20.0, lidar_age=80.0, pose_age=35.0, residual=0.4, points=12, ), ] analysis = analyze_binding_sensitivity(rows) assert analysis["correspondence_count"] == 3 assert analysis["supported_fraction"] == 1.0 assert [ item["count"] for item in analysis["strata"]["image_radius"] ] == [1, 1, 1] assert math.isclose( analysis["spearman_residual_correlation"][ "image_radius_normalized" ], 1.0, ) assert ( analysis["measured_calibration_target_residual_available"] is False ) assert analysis["physical_mount_inferred"] is False def test_e45_rejects_missing_support_in_accepted_correspondence() -> None: rows = [ _row( "one", radius=0.2, speed=0.0, angular=0.0, lidar_age=5.0, pose_age=2.0, residual=0.1, points=1, ) ] with pytest.raises(E45BindingSensitivityError, match="supported"): analyze_binding_sensitivity(rows) def test_e45_profile_rejects_overlapping_or_reversed_edges() -> None: with pytest.raises(E45BindingSensitivityError): E45BindingSensitivityProfile(image_radius_edges=(0.8, 0.4))