From 27c32fc75f9dfe8fdb923c0fd88169f1180d8420 Mon Sep 17 00:00:00 2001 From: Inferencebot Date: Wed, 28 Oct 2026 03:54:42 +0000 Subject: [PATCH] metrics: count LIST membership status, not the subscriber's own status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first live run recorded newsletter_subs = 0 — a wrong zero, the exact fail-open this module documents against. Cause: Listmonk's subscriber record carries status enabled/blocked, while the LIST membership carries subscription_status confirmed/unconfirmed. Filtering on the subscriber's own status matched nothing and returned a silent 0. Now counts memberships whose subscription_status is confirmed (verified: 71, matching the list), and returns NULL when the endpoint yields no rows at all — "not measured" rather than "nobody is subscribed". The daily job is the regression test: it must record 71, not 0. --- metrics_snapshot.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/metrics_snapshot.py b/metrics_snapshot.py index 0cb79f6..ee216a9 100644 --- a/metrics_snapshot.py +++ b/metrics_snapshot.py @@ -99,7 +99,7 @@ def from_listmonk() -> int | None: print("metrics: LISTMONK_API_TOKEN not set — newsletter_subs left NULL") return None try: - total, page = 0, 1 + total, page, seen = 0, 1, 0 while True: data = _get_json( f"{LISTMONK_BASE}/api/subscribers?list_id={LISTMONK_LIST_ID}&per_page=100&page={page}", @@ -107,10 +107,25 @@ def from_listmonk() -> int | None: ) or {} payload = data.get("data") or {} rows = payload.get("results") or [] - total += sum(1 for s in rows if (s.get("status") or "") == "confirmed") + # The subscriber's own `status` is enabled/blocked; the LIST + # membership status is what means "on this list" — counting the + # former returned a silent, wrong 0. + for s in rows: + for membership in s.get("lists") or []: + if membership.get("id") == LISTMONK_LIST_ID and ( + membership.get("subscription_status") or "" + ) == "confirmed": + total += 1 + seen += len(rows) if not rows or len(rows) < 100 or page >= 20: break page += 1 + if seen == 0: + # Nothing came back at all: the endpoint or payload shape changed. + # Record "not measured" (NULL) rather than a zero that reads as + # "nobody is subscribed". + print("metrics: listmonk returned no rows — newsletter_subs left NULL") + return None return total except Exception as exc: # noqa: BLE001 - report, never guess print(f"metrics: listmonk read failed ({exc}) — newsletter_subs left NULL")