mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
126 lines
4.9 KiB
Python
126 lines
4.9 KiB
Python
"""Tests for recency-only fire ordering in build_fires_detail.
|
|
|
|
Validates that the env_reporter surfaces fires ordered strictly by
|
|
last_event_at descending, excludes 100%-contained and tombstoned fires,
|
|
and respects the block character cap.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from meshai.notifications.env_reporter import EnvReporter
|
|
from meshai.persistence import get_db
|
|
|
|
|
|
@pytest.fixture
|
|
def reporter():
|
|
return EnvReporter()
|
|
|
|
|
|
def _seed_fire(conn, *, irwin_id, name, acres, contained=None, lat=43.6, lon=-116.2,
|
|
county="Ada", state="ID", declared_at=None, last_event_at=None,
|
|
tombstoned_at=None):
|
|
now = int(time.time())
|
|
conn.execute(
|
|
"INSERT OR REPLACE INTO fires(irwin_id, incident_name, incident_type, "
|
|
"current_acres, current_contained_pct, lat, lon, county, state, "
|
|
"declared_at, last_event_at, tombstoned_at) "
|
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
(irwin_id, name, "WF", acres, contained, lat, lon, county, state,
|
|
declared_at or now, last_event_at or now, tombstoned_at),
|
|
)
|
|
|
|
|
|
def _seed_scenario(conn):
|
|
"""Seed 14 fires: 11 older large, 2 fresh tiny, 1 large tombstoned."""
|
|
now = int(time.time())
|
|
day = 86400
|
|
|
|
# 11 older large fires (3-6 days old, 50-15000 acres)
|
|
for i in range(11):
|
|
_seed_fire(conn, irwin_id=f"OLD-{i:02d}", name=f"Old Fire {i}",
|
|
acres=50 + i * 1400, # 50, 1450, 2850 ... 14050
|
|
contained=100 if i < 5 else None,
|
|
last_event_at=now - (3 + i % 4) * day,
|
|
county="Owyhee", state="ID")
|
|
|
|
# 2 fresh tiny fires (< 1 day old, small acres)
|
|
_seed_fire(conn, irwin_id="FRESH-01", name="Bingham Co. Assist 3",
|
|
acres=0.3, contained=None,
|
|
last_event_at=now - 3600, # 1 hour ago
|
|
county="Bingham", state="ID")
|
|
_seed_fire(conn, irwin_id="FRESH-02", name="IA 1",
|
|
acres=0.1, contained=None,
|
|
last_event_at=now - 7200, # 2 hours ago
|
|
county="Twin Falls", state="ID")
|
|
|
|
# 1 large fire that is tombstoned
|
|
_seed_fire(conn, irwin_id="TOMB-01", name="Tombstoned Blaze",
|
|
acres=9999, contained=100,
|
|
last_event_at=now - 2 * day,
|
|
tombstoned_at=now - day,
|
|
county="Lincoln", state="ID")
|
|
|
|
|
|
class TestFireRecencyOnly:
|
|
"""T1-T5: recency-only ordering + contained exclusion."""
|
|
|
|
def test_t1_output_strictly_recency_ordered(self, reporter):
|
|
"""All listed fires appear in last_event_at descending order."""
|
|
conn = get_db()
|
|
_seed_scenario(conn)
|
|
text = reporter.build_fires_detail()
|
|
# Fresh fires (1h, 2h ago) must appear before older fires (3-6d)
|
|
pos_fresh1 = text.index("Bingham Co. Assist 3")
|
|
pos_fresh2 = text.index("IA 1")
|
|
assert pos_fresh1 < pos_fresh2, (
|
|
"Most recent fire should appear before second most recent")
|
|
# Any old fire that appears must come after both fresh fires
|
|
for i in range(5, 11): # only non-contained old fires
|
|
name = f"Old Fire {i}"
|
|
if name in text:
|
|
pos_old = text.index(name)
|
|
assert pos_fresh2 < pos_old, (
|
|
f"Fresh fires should appear before '{name}'")
|
|
|
|
def test_t2_contained_100_excluded(self, reporter):
|
|
"""Fires with current_contained_pct == 100 are excluded."""
|
|
conn = get_db()
|
|
_seed_scenario(conn)
|
|
text = reporter.build_fires_detail()
|
|
# Old fires 0-4 have contained=100
|
|
for i in range(5):
|
|
assert f"Old Fire {i}:" not in text, (
|
|
f"100%-contained 'Old Fire {i}' should be excluded")
|
|
|
|
def test_t2b_null_containment_included(self, reporter):
|
|
"""Fires with NULL containment (uncontained) are included."""
|
|
conn = get_db()
|
|
_seed_scenario(conn)
|
|
text = reporter.build_fires_detail()
|
|
assert "Bingham Co. Assist 3" in text
|
|
assert "IA 1" in text
|
|
|
|
def test_t3_tombstoned_excluded(self, reporter):
|
|
"""Tombstoned fires do not appear."""
|
|
conn = get_db()
|
|
_seed_scenario(conn)
|
|
text = reporter.build_fires_detail()
|
|
assert "Tombstoned Blaze" not in text
|
|
|
|
def test_t4_respects_block_cap(self, reporter):
|
|
"""Output respects _block_cap() even with long names."""
|
|
conn = get_db()
|
|
now = int(time.time())
|
|
for i in range(12):
|
|
long_name = f"Extremely Long Incident Name For Testing Purposes Number {i:02d} " + "X" * 100
|
|
_seed_fire(conn, irwin_id=f"LONG-{i:02d}", name=long_name,
|
|
acres=100 + i * 500,
|
|
last_event_at=now - 3600 * (i + 1),
|
|
county="Test", state="ID")
|
|
text = reporter.build_fires_detail()
|
|
from meshai.notifications.env_reporter import _block_cap
|
|
assert len(text) <= _block_cap(), (
|
|
f"Output length {len(text)} exceeds cap {_block_cap()}")
|