Skip to content

Commit 5eda39b

Browse files
fix(risk): lower low and very_low FWI confidence thresholds (#697)
Temporarily set low to 0.30 and very_low to 0.35 until the Ocorus study gives final values. Adjust risk filter tests to the new thresholds.
1 parent c203470 commit 5eda39b

2 files changed

Lines changed: 20 additions & 19 deletions

File tree

‎src/app/services/risk.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
FwiClass = Literal["very_low", "low", "moderate", "high", "very_high", "extreme"]
2020

2121
# Minimum sequence ``max_conf`` required per FWI class. Zero or absent → no filter.
22+
# very_low and low are temporarily lowered until the Ocorus study gives final values.
2223
# All EFFIS classes are listed even when unused so the table stays explicit and easy to tune.
2324
FWI_MIN_CONF: dict[str, float] = {
24-
"very_low": 0.6,
25-
"low": 0.45,
25+
"very_low": 0.35,
26+
"low": 0.3,
2627
"moderate": 0.0,
2728
"high": 0.0,
2829
"very_high": 0.0,

‎src/tests/endpoints/test_risk_filter.py‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ async def test_unlabeled_latest_drops_low_conf_when_camera_is_low_risk(
5656
):
5757
camera_id = pytest.camera_table[0]["id"]
5858
pose_id = pytest.pose_table[0]["id"]
59-
low_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.40, minutes_ago=30)
59+
low_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.20, minutes_ago=30)
6060
high_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.55, minutes_ago=20)
6161

6262
risk_service._scores = {camera_id: "low"}
@@ -80,9 +80,9 @@ async def test_unlabeled_latest_admin_bypasses_risk_filter(
8080
"""Admins skip the risk filter entirely; the ``risk_score`` override is ignored for them."""
8181
camera_id = pytest.camera_table[0]["id"]
8282
pose_id = pytest.pose_table[0]["id"]
83-
low_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.40, minutes_ago=30)
83+
low_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.20, minutes_ago=30)
8484

85-
risk_service._scores = {camera_id: "low"} # 0.45 threshold would drop the sequence
85+
risk_service._scores = {camera_id: "low"} # 0.30 threshold would drop the sequence
8686

8787
auth = pytest.get_token(
8888
pytest.user_table[0]["id"],
@@ -101,8 +101,8 @@ async def test_unlabeled_latest_drops_below_very_low_threshold(
101101
):
102102
camera_id = pytest.camera_table[0]["id"]
103103
pose_id = pytest.pose_table[0]["id"]
104-
# 0.55 passes the low threshold (0.45) but fails very_low (0.6)
105-
seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.55, minutes_ago=25)
104+
# 0.32 passes the low threshold (0.30) but fails very_low (0.35)
105+
seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.32, minutes_ago=25)
106106

107107
risk_service._scores = {camera_id: "very_low"}
108108

@@ -147,7 +147,7 @@ async def test_unlabeled_latest_keeps_seq_with_null_max_conf_under_filter(
147147
pose_id = pytest.pose_table[0]["id"]
148148
null_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=None, minutes_ago=20) # type: ignore[arg-type]
149149

150-
risk_service._scores = {camera_id: "low"} # 0.45 threshold, would normally drop
150+
risk_service._scores = {camera_id: "low"} # 0.30 threshold, would normally drop
151151

152152
auth = pytest.get_token(
153153
pytest.user_table[1]["id"],
@@ -169,7 +169,7 @@ async def test_unlabeled_latest_keeps_seq_for_camera_unknown_to_risk_api(
169169
known_pose = pytest.pose_table[0]["id"]
170170
unknown_pose = pytest.pose_table[2]["id"]
171171

172-
# Cache only knows about ``known_cam`` and flags it ``low`` (0.45 threshold).
172+
# Cache only knows about ``known_cam`` and flags it ``low`` (0.30 threshold).
173173
# ``unknown_cam`` has no entry -> CASE else_=0.0 -> any max_conf passes.
174174
risk_service._scores = {known_cam: "low"}
175175

@@ -218,7 +218,7 @@ async def test_alerts_unlabeled_latest_drops_alert_when_all_seqs_below_threshold
218218
):
219219
camera_id = pytest.camera_table[1]["id"] # belongs to org 2 (user_idx 2)
220220
pose_id = pytest.pose_table[2]["id"]
221-
seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.30, minutes_ago=20)
221+
seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.20, minutes_ago=20)
222222
alert = await _seed_alert_with_sequence(detection_session, organization_id=2, seq=seq)
223223

224224
risk_service._scores = {camera_id: "low"}
@@ -239,10 +239,10 @@ async def test_alerts_unlabeled_latest_risk_score_override(
239239
):
240240
camera_id = pytest.camera_table[1]["id"]
241241
pose_id = pytest.pose_table[2]["id"]
242-
seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.30, minutes_ago=20)
242+
seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.20, minutes_ago=20)
243243
alert = await _seed_alert_with_sequence(detection_session, organization_id=2, seq=seq)
244244

245-
# Risk-api would say "moderate" (no filter), but the override forces "low" -> 0.45 threshold drops it.
245+
# Risk-api would say "moderate" (no filter), but the override forces "low" -> 0.30 threshold drops it.
246246
risk_service._scores = {camera_id: "moderate"}
247247

248248
auth = pytest.get_token(
@@ -262,7 +262,7 @@ async def test_alerts_unlabeled_latest_count_matches_list_under_risk_filter(
262262
"""The count endpoint must apply the same risk_score filter as the list, so the two stay in sync."""
263263
camera_id = pytest.camera_table[1]["id"]
264264
pose_id = pytest.pose_table[2]["id"]
265-
low_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.30, minutes_ago=20)
265+
low_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.20, minutes_ago=20)
266266
high_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.55, minutes_ago=15)
267267
await _seed_alert_with_sequence(detection_session, organization_id=2, seq=low_seq)
268268
kept_alert = await _seed_alert_with_sequence(detection_session, organization_id=2, seq=high_seq)
@@ -275,7 +275,7 @@ async def test_alerts_unlabeled_latest_count_matches_list_under_risk_filter(
275275
pytest.user_table[2]["organization_id"],
276276
)
277277

278-
# low threshold (0.45) drops the 0.30 alert but keeps the 0.55 one.
278+
# low threshold (0.30) drops the 0.20 alert but keeps the 0.55 one.
279279
count_resp = await async_client.get("/alerts/unlabeled/latest/count?risk_score=low", headers=auth)
280280
assert count_resp.status_code == 200, print(count_resp.__dict__)
281281
assert count_resp.json() == {"count": 1}
@@ -335,7 +335,7 @@ async def test_sequences_unlabeled_latest_risk_score_override_drops_low_conf(
335335
):
336336
camera_id = pytest.camera_table[1]["id"]
337337
pose_id = pytest.pose_table[2]["id"]
338-
low_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.30, minutes_ago=20)
338+
low_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.20, minutes_ago=20)
339339
high_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.55, minutes_ago=15)
340340

341341
auth = pytest.get_token(
@@ -399,7 +399,7 @@ async def test_alerts_unlabeled_latest_keeps_alert_with_mixed_seqs(
399399
"""An alert mixing one passing and one failing sequence stays, with only the passing seq in payload."""
400400
camera_id = pytest.camera_table[1]["id"]
401401
pose_id = pytest.pose_table[2]["id"]
402-
low_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.30, minutes_ago=25)
402+
low_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.20, minutes_ago=25)
403403
high_seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.70, minutes_ago=15)
404404

405405
now = utcnow()
@@ -415,7 +415,7 @@ async def test_alerts_unlabeled_latest_keeps_alert_with_mixed_seqs(
415415
detection_session.add(AlertSequence(alert_id=alert.id, sequence_id=high_seq.id))
416416
await detection_session.commit()
417417

418-
risk_service._scores = {camera_id: "low"} # 0.45 threshold
418+
risk_service._scores = {camera_id: "low"} # 0.30 threshold
419419

420420
auth = pytest.get_token(
421421
pytest.user_table[2]["id"],
@@ -439,7 +439,7 @@ async def test_alerts_fromdate_risk_score_override_drops_low_conf_alert(
439439
camera_id = pytest.camera_table[1]["id"]
440440
pose_id = pytest.pose_table[2]["id"]
441441
target_date = utcnow().date().isoformat()
442-
seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.30, minutes_ago=20)
442+
seq = await _seed_unlabeled_sequence(detection_session, camera_id, pose_id, max_conf=0.20, minutes_ago=20)
443443
alert = await _seed_alert_with_sequence(detection_session, organization_id=2, seq=seq)
444444

445445
auth = pytest.get_token(
@@ -629,4 +629,4 @@ async def test_sequences_fromdate_pagination_filters_before_limit(
629629
assert response.status_code == 200, print(response.__dict__)
630630
page = response.json()
631631
assert len(page) == 3
632-
assert all(seq["max_conf"] >= 0.45 for seq in page)
632+
assert all(seq["max_conf"] >= 0.30 for seq in page)

0 commit comments

Comments
 (0)