mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
fix(reminders): resolve Active: fire location via _fire_anchor, not county/state
ReminderScheduler._render built the wfigs "Active:" reminder location as a
bare " / ".join(county, state), skipping the anchor resolution (geocoder_city
-> curated town_anchors -> Photon -> landclass -> county -> state) that the
New/Update fire formatter (formatters/fire.py::_fire_anchor) already uses.
Reminders now call the same _fire_anchor helper on dict(row) (sqlite3.Row has
no .get()) and fit the result to fit_to_budget(..., budget_for("wfigs")),
matching how the other renderers guard the mesh packet budget.
This commit is contained in:
parent
522458f194
commit
2eddd9b572
2 changed files with 97 additions and 2 deletions
|
|
@ -59,6 +59,9 @@ import time
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from meshai.notifications.formatters._budget import budget_for, fit_to_budget
|
||||||
|
from meshai.notifications.formatters.fire import _fire_anchor
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -370,8 +373,9 @@ class ReminderScheduler:
|
||||||
cont = row["current_contained_pct"]
|
cont = row["current_contained_pct"]
|
||||||
acres_s = "N/A" if acres is None else f"{int(acres):,} ac"
|
acres_s = "N/A" if acres is None else f"{int(acres):,} ac"
|
||||||
cont_s = "?" if cont is None else f"{int(cont)}%"
|
cont_s = "?" if cont is None else f"{int(cont)}%"
|
||||||
anchor = " / ".join(p for p in (row["county"], row["state"]) if p) or "?"
|
anchor = _fire_anchor(dict(row))
|
||||||
return f"🔥 {prefix}: {name}, {anchor}: {acres_s}, {cont_s} contained"
|
wire = f"🔥 {prefix}: {name}, {anchor}: {acres_s}, {cont_s} contained"
|
||||||
|
return fit_to_budget(wire, budget_for("wfigs"))
|
||||||
if adapter == "swpc":
|
if adapter == "swpc":
|
||||||
return f"🌌 {prefix}: ongoing space weather event ({row['event_type']})"
|
return f"🌌 {prefix}: ongoing space weather event ({row['event_type']})"
|
||||||
if adapter == "itd_511_work_zone":
|
if adapter == "itd_511_work_zone":
|
||||||
|
|
|
||||||
|
|
@ -229,3 +229,94 @@ def test_work_zone_reminder_skipped_outside_slot(mock_dispatcher):
|
||||||
sch = ReminderScheduler(mock_dispatcher, clock=lambda: now, tick_seconds=60)
|
sch = ReminderScheduler(mock_dispatcher, clock=lambda: now, tick_seconds=60)
|
||||||
fired = asyncio.run(sch.tick_once())
|
fired = asyncio.run(sch.tick_once())
|
||||||
assert fired == 0
|
assert fired == 0
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Anchor resolution in the wfigs "Active:" reminder wire (_fire_anchor)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
def test_wfigs_reminder_uses_anchor_not_county(mock_dispatcher):
|
||||||
|
"""A fire near a known curated town_anchors entry (Bliss, ID) renders the
|
||||||
|
anchor phrase ('mi <bearing> of Bliss'), not the bare county/state join
|
||||||
|
the old renderer produced."""
|
||||||
|
now = 1_780_000_000
|
||||||
|
conn = get_db()
|
||||||
|
_enable_wfigs_reminders()
|
||||||
|
# Coordinates ~4 mi NW of Bliss, ID (see _TOWN_ANCHORS_SEED in
|
||||||
|
# meshai/persistence/curation.py) -- county/state deliberately different
|
||||||
|
# from "Bliss" so the assertion can't pass by accident via the county.
|
||||||
|
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, first_broadcast_at, last_broadcast_at) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
("F-ANCHOR", "Jackalope", "WF", 15, 0,
|
||||||
|
42.98, -114.99, "Gooding", "ID",
|
||||||
|
now - 9 * 3600, now, now - 9 * 3600, now - 9 * 3600),
|
||||||
|
)
|
||||||
|
|
||||||
|
sch = ReminderScheduler(mock_dispatcher, clock=lambda: now)
|
||||||
|
fired = asyncio.run(sch.tick_once())
|
||||||
|
assert fired == 1
|
||||||
|
args = mock_dispatcher.dispatch_scheduled_fire_broadcast.call_args.kwargs
|
||||||
|
text = args["text"]
|
||||||
|
assert "Bliss" in text
|
||||||
|
assert "of Bliss" in text
|
||||||
|
# Old format ("Gooding / ID") must be gone.
|
||||||
|
assert "Gooding / ID" not in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_wfigs_reminder_falls_back_when_no_anchor_nearby(mock_dispatcher):
|
||||||
|
"""A fire with no lat/lon at all still renders sensibly via
|
||||||
|
_fire_anchor's fallback chain (county -> state -> "location unknown"),
|
||||||
|
and never raises."""
|
||||||
|
now = 1_780_000_000
|
||||||
|
conn = get_db()
|
||||||
|
_enable_wfigs_reminders()
|
||||||
|
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, first_broadcast_at, last_broadcast_at) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
("F-NOANCHOR", "Remote Fire", "WF", 42, 5,
|
||||||
|
None, None, "Owyhee", "ID",
|
||||||
|
now - 9 * 3600, now, now - 9 * 3600, now - 9 * 3600),
|
||||||
|
)
|
||||||
|
|
||||||
|
sch = ReminderScheduler(mock_dispatcher, clock=lambda: now)
|
||||||
|
fired = asyncio.run(sch.tick_once())
|
||||||
|
assert fired == 1
|
||||||
|
args = mock_dispatcher.dispatch_scheduled_fire_broadcast.call_args.kwargs
|
||||||
|
text = args["text"]
|
||||||
|
# Falls through landclass (absent) to the "{county} Co {state}" tier.
|
||||||
|
assert "Owyhee Co ID" in text
|
||||||
|
assert "Active" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_wfigs_reminder_truncates_to_budget(mock_dispatcher):
|
||||||
|
"""A very long incident name + anchor is truncated to the wfigs budget
|
||||||
|
(fit_to_budget) rather than exceeding the mesh packet limit."""
|
||||||
|
now = 1_780_000_000
|
||||||
|
conn = get_db()
|
||||||
|
_enable_wfigs_reminders()
|
||||||
|
long_name = ("The Extremely Long Wildfire Incident Name That Keeps Going "
|
||||||
|
"And Going And Going Well Past Any Reasonable Mesh Packet Budget")
|
||||||
|
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, first_broadcast_at, last_broadcast_at) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
("F-LONG", long_name, "WF", 123456, 3,
|
||||||
|
42.98, -114.99, "Gooding", "ID",
|
||||||
|
now - 9 * 3600, now, now - 9 * 3600, now - 9 * 3600),
|
||||||
|
)
|
||||||
|
|
||||||
|
from meshai.notifications.formatters._budget import budget_for
|
||||||
|
sch = ReminderScheduler(mock_dispatcher, clock=lambda: now)
|
||||||
|
fired = asyncio.run(sch.tick_once())
|
||||||
|
assert fired == 1
|
||||||
|
args = mock_dispatcher.dispatch_scheduled_fire_broadcast.call_args.kwargs
|
||||||
|
text = args["text"]
|
||||||
|
assert len(text) <= budget_for("wfigs")
|
||||||
|
assert text.endswith("…")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue