"""Tests for the regime quadrant classification + hysteresis (anti-flicker).""" from __future__ import annotations from app.services.alert_service import _classify_quadrant # Quadrant ids: 1=① hot&brittle (regime low, warning high), 2=② transition # (both high), 3=③ healthy (both low), 4=④ real downturn (regime high, warning low). # Dividers: regime 40, early-warning 60; margin 5. def test_fresh_classification(): assert _classify_quadrant(20, 90, None) == "1" # low regime, high warning assert _classify_quadrant(70, 90, None) == "2" # both high assert _classify_quadrant(20, 30, None) == "3" # both low assert _classify_quadrant(70, 30, None) == "4" # high regime, low warning def test_hysteresis_holds_inside_deadband(): # From ③ (both low): early-warning nudging just past 60 stays ③ until it # clears 60 + margin (65). assert _classify_quadrant(20, 62, prev="3") == "3" # within deadband → no flip assert _classify_quadrant(20, 66, prev="3") == "1" # clears 65 → flips to ① def test_hysteresis_sticky_when_already_high(): # From ① (warning high): a dip below 60 keeps ① until it drops past 60 - margin (55). assert _classify_quadrant(20, 58, prev="1") == "1" # still high (deadband) assert _classify_quadrant(20, 54, prev="1") == "3" # drops past 55 → back to ③ def test_hysteresis_on_regime_axis(): # From ③: regime rising past 40 stays ③ until it clears 45. assert _classify_quadrant(43, 30, prev="3") == "3" assert _classify_quadrant(46, 30, prev="3") == "4" # From ④: regime easing keeps ④ until below 35. assert _classify_quadrant(37, 30, prev="4") == "4" assert _classify_quadrant(34, 30, prev="4") == "3" def test_boundary_sitting_does_not_flip(): # A point parked exactly on both dividers keeps whatever quadrant it had. for q in ("1", "2", "3", "4"): assert _classify_quadrant(40, 60, prev=q) == q