mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
refactor: move source tree into work/, multi-stage Docker build, fix satpass
- Move all application source (meshai/, dashboard-frontend/, tests/, config/, docs/, Dockerfile, etc.) into work/ directory - Add Node.js multi-stage build to Dockerfile for frontend compilation; remove compiled static assets from git tracking - Fix satpass missing time windows: consolidation was splitting wire on newline and only putting line 1 in event.title, dropping the time window line that the composer uses for precomposed broadcasts - Fix satpass burst flooding: stagger consolidation timers (+60s per pending pass) so Central batch publishes don't blast the mesh - Update CI workflow build context to work/ - Anchor lib/ and data/ gitignore patterns to repo root to prevent false matches on nested directories - Add dashboard-frontend/node_modules/ to .dockerignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7128b432ee
commit
2e1fb325f7
283 changed files with 109402 additions and 483 deletions
126
work/tests/test_env_reporter_fire_recency.py
Normal file
126
work/tests/test_env_reporter_fire_recency.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
"""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()}")
|
||||
Loading…
Add table
Add a link
Reference in a new issue