from v1.evidence import PoseEvidence from v1.fall_policy import FallEvidencePolicy from v1.fall_state import FallState def _evidence(horizontal=False, rapid_drop=False, accepted=True): return PoseEvidence( accepted=accepted, horizontal_pose=horizontal, rapid_vertical_change=rapid_drop, horizontal_angle_degrees=10.0 if horizontal else 80.0, hip_center_y=100.0, torso_length=50.0, reason="accepted" if accepted else "missing_pose", ) def test_rapid_drop_followed_by_horizontal_pose_within_suspect_window_starts_candidate(): policy = FallEvidencePolicy(suspect_window_seconds=0.5) onset = policy.evaluate( "P-0001", _evidence(rapid_drop=True), now=0.0, state=FallState.NORMAL ) candidate = policy.evaluate( "P-0001", _evidence(horizontal=True), now=0.5, state=FallState.NORMAL ) assert onset.is_fall_candidate is False assert candidate.is_fall_candidate is True def test_rejected_pose_clears_pending_drop_before_the_next_horizontal_pose(): policy = FallEvidencePolicy(suspect_window_seconds=0.5) policy.evaluate("P-0001", _evidence(rapid_drop=True), now=0.0, state=FallState.NORMAL) policy.evaluate("P-0001", _evidence(accepted=False), now=0.1, state=FallState.NORMAL) candidate = policy.evaluate( "P-0001", _evidence(horizontal=True), now=0.2, state=FallState.NORMAL ) assert candidate.accepted is True assert candidate.is_fall_candidate is False def test_upright_pose_is_recovery_evidence_only_after_confirmation(): policy = FallEvidencePolicy(suspect_window_seconds=0.5) recovery = policy.evaluate( "P-0001", _evidence(horizontal=False), now=4.0, state=FallState.CONFIRMED ) assert recovery.accepted is True assert recovery.is_recovery_candidate is True