Skip to content

Let evaluation.py own the reputation predicates and window #293

Open

Description

@codingjoe

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.

Activity

  1. added
    side questOut-of-scope finding, deferred to its own issue
    on Sep 19, 2026
  2. codingjoe commented on Sep 29, 2026

    @codingjoe
    OwnerAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bloatOver-engineering and dead codeside questOut-of-scope finding, deferred to its own issue

    Projects

    • Status
      Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions