feat(v1): relax fall sensitivity for low overhead cameras
Adds configurable event.require_rapid_drop (default off), require_lower_body (default off) and horizontal_angle_threshold_degrees (default 45). With the lenient defaults a sustained horizontal pose alone enters SUSPECT and confirms after the confirm window, which is now the main false-positive guard; missing knees/ankles no longer reject the pose. Thresholds flow into config_version. End-to-end smoke confirms a lying pose; 70 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -140,6 +140,20 @@ def test_public_example_config_has_no_embedded_credentials():
|
||||
assert "rtsp_url_env" in source
|
||||
|
||||
|
||||
def test_sensitivity_fields_default_to_lenient(tmp_path):
|
||||
config_file = tmp_path / "config.json"
|
||||
_write_config(
|
||||
config_file,
|
||||
{"id": "c", "host": "192.0.2.10", "username": "u", "password": "p"},
|
||||
)
|
||||
|
||||
config = load_config(config_file)
|
||||
|
||||
assert config.event.require_rapid_drop is False
|
||||
assert config.event.require_lower_body is False
|
||||
assert config.event.horizontal_angle_threshold_degrees == 45.0
|
||||
|
||||
|
||||
def test_build_ffmpeg_options_variants():
|
||||
assert build_ffmpeg_options() == "rtsp_transport;tcp|stimeout;5000000|fflags;nobuffer|flags;low_delay"
|
||||
assert build_ffmpeg_options("udp", 3, False) == "rtsp_transport;udp|stimeout;3000000"
|
||||
|
||||
@@ -36,6 +36,14 @@ def test_missing_ankles_rejects_pose():
|
||||
assert quality.reason == "required_joint_low_confidence"
|
||||
|
||||
|
||||
def test_missing_ankles_accepted_when_lower_body_not_required():
|
||||
quality = assess_pose_quality(
|
||||
_pose(missing_ankles=True), threshold=0.4, require_lower_body=False
|
||||
)
|
||||
|
||||
assert quality.accepted is True
|
||||
|
||||
|
||||
def test_horizontal_body_is_evidence_not_event():
|
||||
pose = _pose(horizontal=True)
|
||||
quality = assess_pose_quality(pose, threshold=0.4)
|
||||
|
||||
@@ -42,6 +42,20 @@ def test_rejected_pose_clears_pending_drop_before_the_next_horizontal_pose():
|
||||
assert candidate.is_fall_candidate is False
|
||||
|
||||
|
||||
def test_horizontal_pose_alone_starts_candidate_when_rapid_drop_not_required():
|
||||
policy = FallEvidencePolicy(suspect_window_seconds=0.5, require_rapid_drop=False)
|
||||
|
||||
candidate = policy.evaluate(
|
||||
"P-0001", _evidence(horizontal=True), now=0.0, state=FallState.NORMAL
|
||||
)
|
||||
upright = policy.evaluate(
|
||||
"P-0002", _evidence(horizontal=False), now=0.0, state=FallState.NORMAL
|
||||
)
|
||||
|
||||
assert candidate.is_fall_candidate is True
|
||||
assert upright.is_fall_candidate is False
|
||||
|
||||
|
||||
def test_upright_pose_is_recovery_evidence_only_after_confirmation():
|
||||
policy = FallEvidencePolicy(suspect_window_seconds=0.5)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user