You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Let evaluation.py own the reputation predicates and window #293
The reputation chart and the reputation check compute the same predicates twice. The rules can drift apart.
Work reference: PR #289 (branch codingjoe-move-graphs-to-message-lists), reputation dashboard rework.
Finding (lazyJoe, superJoe crew, out of scope for that PR):
side quest: charts.py re-implements evaluation.py's complaint and bounce predicates (provider FBL + held spam; 5xx vs 4xx) and even the window differs, so the chart's "equals the reputation check" promise drifts; give evaluation.py the rule. [services/email/reputation/evaluation.py]
Why it matters:charts.py computes hard bounces with status=BOUNCED, code >= 500. It computes complaints as provider FBL reports plus held outgoing messages. This mirrors evaluation.py::compute_org_reputation. The two windows differ on top of that. The chart filters on created_at__date__gte = localdate - (window - 1), and the evaluation uses a rolling created_at__gte = now - window_days. A rate can therefore read slightly differently in the card than the check that suspends an organization. Any future rule change must be made twice.
Suggested direction: let evaluation.py own the predicates and the window. The chart then consumes them per day.
Work reference: branch codingjoe-weekly-digest, commits 2ace45d and 904e572, weekly sending digest (superJoe crew, out of scope for this change).
The same window mismatch surfaced again from a different angle, so this issue covers one more code path:
side quest: the chart's window is day-aligned (created_at__date >= start) while the suspension check uses an exact now - N days cutoff, so the chart's last point and the threshold check have always disagreed at the boundary; this patch only moves the dashboard cards from one to the other. [services/email/reputation/charts.py]:L106
The dashboard cards started reading build_reputation_stats (the rolling, exact-cutoff window) and that turned out to be a visible change: the cards no longer matched the chart directly beneath them. The cards are back on chart["rows"][-1], and the new digest mail reports the rolling window because that is the window the suspension check uses. Two sources, one screen, which is the drift this issue already asks to remove.
While there: the new mail carries the second copy of the max(RELAY_REPUTATION_WINDOW_DAYS, 1) guard that charts.py already had, so the window guard now exists in three places. Worth folding into the same fix.
Activity
codingjoe commentedon Sep 29, 2026
Work reference: branch
codingjoe-weekly-digest, commits 2ace45d and 904e572, weekly sending digest (superJoe crew, out of scope for this change).The same window mismatch surfaced again from a different angle, so this issue covers one more code path:
The dashboard cards started reading
build_reputation_stats(the rolling, exact-cutoff window) and that turned out to be a visible change: the cards no longer matched the chart directly beneath them. The cards are back onchart["rows"][-1], and the new digest mail reports the rolling window because that is the window the suspension check uses. Two sources, one screen, which is the drift this issue already asks to remove.While there: the new mail carries the second copy of the
max(RELAY_REPUTATION_WINDOW_DAYS, 1)guard thatcharts.pyalready had, so the window guard now exists in three places. Worth folding into the same fix.