81 lines
2.9 KiB
Python
81 lines
2.9 KiB
Python
|
|
"""Unit tests for the subscription evaluation engine's pure logic (no DB, no
|
||
|
|
network): trigger detection against a synthetic climatology and message wording."""
|
||
|
|
import types
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
import pandas as pd
|
||
|
|
|
||
|
|
import notify
|
||
|
|
|
||
|
|
|
||
|
|
def _history() -> pd.DataFrame:
|
||
|
|
dates = pd.date_range("1990-01-01", "2020-12-31", freq="D")
|
||
|
|
rng = np.random.default_rng(1)
|
||
|
|
n = len(dates)
|
||
|
|
df = pd.DataFrame({"date": dates})
|
||
|
|
df["doy"] = df["date"].dt.dayofyear
|
||
|
|
df["tmax"] = 70 + rng.normal(0, 8, n)
|
||
|
|
df["tmin"] = 48 + rng.normal(0, 7, n)
|
||
|
|
df["precip"] = rng.exponential(0.05, n)
|
||
|
|
return df
|
||
|
|
|
||
|
|
|
||
|
|
def _sub(**kw):
|
||
|
|
d = dict(metrics=["tmax"], threshold=95, two_sided=False, kind="observed",
|
||
|
|
label="Testville", lat=1.0, lon=2.0)
|
||
|
|
d.update(kw)
|
||
|
|
return types.SimpleNamespace(**d)
|
||
|
|
|
||
|
|
|
||
|
|
def _row(date, **vals):
|
||
|
|
r = {"date": pd.Timestamp(date)}
|
||
|
|
r.update(vals)
|
||
|
|
return r
|
||
|
|
|
||
|
|
|
||
|
|
def test_high_trigger():
|
||
|
|
row = _row("2021-07-15", tmax=115.0, tmin=50.0, precip=0.0)
|
||
|
|
hit = notify._first_trigger(_sub(metrics=["tmax"], threshold=95), [row], _history(), {})
|
||
|
|
assert hit is not None
|
||
|
|
_, metric, direction, g = hit
|
||
|
|
assert metric == "tmax" and direction == "high" and g["percentile"] >= 95
|
||
|
|
|
||
|
|
|
||
|
|
def test_no_trigger_when_normal():
|
||
|
|
row = _row("2021-07-15", tmax=70.0, tmin=48.0, precip=0.0)
|
||
|
|
assert notify._first_trigger(_sub(metrics=["tmax"], threshold=95), [row], _history(), {}) is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_low_trigger_only_when_two_sided():
|
||
|
|
row = _row("2021-07-15", tmax=70.0, tmin=10.0, precip=0.0) # extreme low overnight
|
||
|
|
assert notify._first_trigger(
|
||
|
|
_sub(metrics=["tmin"], threshold=95, two_sided=False), [row], _history(), {}) is None
|
||
|
|
hit = notify._first_trigger(
|
||
|
|
_sub(metrics=["tmin"], threshold=95, two_sided=True), [row], _history(), {})
|
||
|
|
assert hit is not None and hit[1] == "tmin" and hit[2] == "low"
|
||
|
|
|
||
|
|
|
||
|
|
def test_precip_never_low_side():
|
||
|
|
# A dry day has no rain-percentile, so a precip subscription never fires low
|
||
|
|
# even when two-sided (rain is one-directional).
|
||
|
|
row = _row("2021-07-15", tmax=70.0, precip=0.0)
|
||
|
|
assert notify._first_trigger(
|
||
|
|
_sub(metrics=["precip"], threshold=95, two_sided=True), [row], _history(), {}) is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_compose_observed_wording():
|
||
|
|
g = {"percentile": 99.0, "value": 115.0, "grade": "Near Record"}
|
||
|
|
title, body = notify._compose(_sub(kind="observed", label="Phoenix"),
|
||
|
|
"2026-07-14", "tmax", "high", g)
|
||
|
|
assert title == "Phoenix: unusually hot day"
|
||
|
|
assert body.startswith("On 2026-07-14")
|
||
|
|
assert "99th percentile" in body and "Near Record" in body
|
||
|
|
|
||
|
|
|
||
|
|
def test_compose_forecast_wording():
|
||
|
|
g = {"percentile": 2.0, "value": 5.0, "grade": "Near Record"}
|
||
|
|
title, body = notify._compose(_sub(kind="forecast", label="Nome"),
|
||
|
|
"2026-01-02", "tmin", "low", g)
|
||
|
|
assert title == "Nome: unusually cold night"
|
||
|
|
assert body.startswith("Forecast for 2026-01-02")
|