feat: replace regime monitor with v2 methodology
This commit is contained in:
@@ -1,52 +1,40 @@
|
||||
"""Tests for the regime quadrant classification + hysteresis (anti-flicker)."""
|
||||
"""Tests for v2 State/Warning quadrant hysteresis and basket reseeding keys."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.services.alert_service import _classify_quadrant, _parse_quadrant_log_key, _quadrant_log_key
|
||||
from app.services.alert_service import (
|
||||
_classify_quadrant,
|
||||
_parse_quadrant_log_key,
|
||||
_quadrant_log_key,
|
||||
)
|
||||
|
||||
|
||||
# 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_uses_60_60_boundaries():
|
||||
assert _classify_quadrant(20, 90, None) == "1"
|
||||
assert _classify_quadrant(70, 90, None) == "2"
|
||||
assert _classify_quadrant(20, 30, None) == "3"
|
||||
assert _classify_quadrant(70, 30, None) == "4"
|
||||
|
||||
|
||||
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_warning_axis_hysteresis():
|
||||
assert _classify_quadrant(20, 62, prev="3") == "3"
|
||||
assert _classify_quadrant(20, 66, prev="3") == "1"
|
||||
assert _classify_quadrant(20, 58, prev="1") == "1"
|
||||
assert _classify_quadrant(20, 54, prev="1") == "3"
|
||||
|
||||
|
||||
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_state_axis_hysteresis():
|
||||
assert _classify_quadrant(63, 30, prev="3") == "3"
|
||||
assert _classify_quadrant(66, 30, prev="3") == "4"
|
||||
assert _classify_quadrant(57, 30, prev="4") == "4"
|
||||
assert _classify_quadrant(54, 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
|
||||
for quadrant in ("1", "2", "3", "4"):
|
||||
assert _classify_quadrant(60, 60, prev=quadrant) == quadrant
|
||||
|
||||
|
||||
def test_quadrant_log_key_keeps_previous_values():
|
||||
key = _quadrant_log_key("3", 32.4, 54.6)
|
||||
assert _parse_quadrant_log_key(key) == ("3", 32.4, 54.6)
|
||||
# Existing pre-value keys still parse so old installs do not need migration.
|
||||
assert _parse_quadrant_log_key("3") == ("3", None, None)
|
||||
def test_quadrant_key_carries_basket_hash_and_parses_legacy_keys():
|
||||
key = _quadrant_log_key("3", 32.4, 54.6, "abc123")
|
||||
assert _parse_quadrant_log_key(key) == ("abc123", "3", 32.4, 54.6)
|
||||
assert _parse_quadrant_log_key("3:32.4:54.6") == (None, "3", 32.4, 54.6)
|
||||
assert _parse_quadrant_log_key("3") == (None, "3", None, None)
|
||||
|
||||
Reference in New Issue
Block a user