fix(metrics): make the last-24h classifier window a real 24h, and a plan-pace test time bomb #114
Reference in New Issue
Block a user
Delete Branch "fix/julianday-24h-window"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
The "last 24 hours" window in the classifier-degradation warning is really 24 to 48 hours, depending on the hour of day.
route_decisions.observed_atis written bydatetime.now(timezone.utc).isoformat(), so it has aTseparator:2026-10-05T05:31:12.306322+00:00. SQLite'sdatetime('now', ...)returns a space:2026-10-06 00:54:20.'T'sorts after' ', so once the two dates match the time of day never participates, and the comparison admits every row on the cutoff's calendar date.Two queries in
src/metrics.pydid this:classifier_degradation_warningand_declined_for. Both now usejulianday(observed_at) >= julianday('now', '-24 hours'), the formpoller.mark_staleand the outcome-attribution query indispatcher.pyalready use. The project hit this exact trap once before (spurious 409s); this closes the last two sites and adds a tripwire so a third cannot appear quietly.Measured on synthetic rows at 00:54 UTC: a row aged 24.5h was admitted by the old comparison and rejected by the new one. On the live DB today the shipped window admitted 0 extra rows, only because no traffic fell in that gap.
Commits
7777002the fix, three window tests, and the tripwiretests/test_sql_time_windows.py. The tripwire scanssrc/for an ISO-Tcolumn compared withdatetime('now'in either order, and carries positive controls so its regexes cannot silently stop matching.2c358cda separate, pre-existing bug (below).6b0c3e3tidy: SQL indent, a duplicate scan test removed from the tripwire, one em dash.Separate: a test time bomb this branch ran into
test_quota_accounts_alarm_plan_pace_warningand_criticalhardcodedbilling_reset_day=6and claimed to hold "on any day".quota_accountsdeliberately setselapsed_fractiontoNonebelow 0.02 (day 0 of a period) and skips the pace rule there, so on the 6th UTC both saw the usage-floor alarm instead ofplan_paceand failed. They went red on 2026-10-06 UTC, onmainatd97588cas well, 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. 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_startand_next_reset_date. Product behavior is unchanged; the guard is deliberate.Verification
scripts/verify_commit.py --full HEADfrom the worktree: lint clean, 2678 tests pass. It was run on the 6th UTC, the day the pace tests fail without commit 2.src/metrics.pyreverted toorigin/main,test_degradation_window_excludes_rows_older_than_24h_on_the_cutoff_date,test_declined_for_ignores_reasons_older_than_24hand the tripwire fail; the in-window test passes either way. The fix turns all of them green.now, using midnight at the start of the cutoff's own date as the "just outside" row, so they are deterministic at every hour of the day.src/before the fix: exactly the two sites, no false positives. The otherdatetime('now'uses write space-format columns (applied_at,added_at) and are fine.Not in this PR
isoformat()cutoffs) were not audited here.🤖 Generated with Claude Code
https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
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.