fix(metrics): make the last-24h classifier window a real 24h, and a plan-pace test time bomb #114

Merged
alee merged 3 commits from fix/julianday-24h-window into main 2026-10-08 00:11:29 +00:00

3 Commits

Author SHA1 Message Date
adlee-was-taken
6b0c3e3e85 test: tidy the julianday window fix
Match the SQL indent of the two edited lines in metrics.py to their
neighbours (one space too deep, which made the diff noisier than the change),
drop test_src_has_no_unexpected_hits from the tripwire (an identical copy of
test_no_iso_t_datetime_comparison misfiled under the positive controls), and
replace an em dash in a comment with ASCII.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
2026-10-05 21:32:12 -04:00
adlee-was-taken
2c358cd40e test(metrics): stop the plan-pace tests failing on the billing reset day
test_quota_accounts_alarm_plan_pace_warning and _critical hardcoded
billing_reset_day=6 and claimed to hold "on any day". quota_accounts sets
elapsed_fraction to None below 0.02 (day 0 of a period) and skips the pace
rule there on purpose, so on the 6th UTC both tests saw the usage-floor alarm
instead of plan_pace and failed. They went red on 2026-10-06 UTC, on main at
d97588c as well as on this branch, so every gate that day was red.

The tests now take a reset day from _pace_reset_day(today), which always puts
today at least one day into the period (valid days are 1..28). A sweep test
checks every calendar day of a leap and a common year against the same 0.02
guard, using metrics' own _billing_period_start and _next_reset_date.

Product behavior is unchanged: the guard is deliberate.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
2026-10-05 21:32:02 -04:00
adlee-was-taken
7777002981 fix: replace raw datetime('now') comparisons with julianday() for ISO-T column 24h windows
Two sites in src/metrics.py (classifier_degradation_warning and
_declined_for) compared observed_at against datetime('now', ...) using
string comparison. Since observed_at is stored as ISO-8601 ('T'
separator) and datetime('now') returns a space separator, the 'T' > ' '
sorting made the time-of-day component irrelevant — a 24h window
silently admitted all rows on the cutoff's calendar date.

Replace both with julianday() on both sides, matching the pattern
already used in dispatcher.py and poller.py.

Add three tests in test_classifier_cascade.py using a just_outside
timestamp (midnight of the cutoff's calendar date — the exact shape
the old comparison wrongly admitted) and an inside timestamp, proving
the fix works.

Add tests/test_sql_time_windows.py as a tripwire: scans src/ for
any ISO-T column compared against datetime('now') with an operator,
with positive controls that verify the regexes match the bad pattern
and reject the safe julianday pattern.
2026-10-05 21:21:14 -04:00