
Dennis opened the SOMBA members area and asked a simple question: why does this page say 74 when we know there are more? The answer was worse than one stale number. Three pages were each reporting a different member count on the same day, and the benchmark stat was quoting a score Sigrun had not held in weeks. Here is the whole repair, and the guard that stops it recurring.
Start With What a Member Actually Sees
Sigrun’s SOMBA members area is three WordPress pages: a hub, a scoreboard, and one page that renders every member’s private dashboard. A member moves between all three in about ten seconds. On 19 July 2026 those three pages told them three different things.
The hub said “all 74 of you.” The scoreboard said “all 100 of you” and listed 100 rows. The dashboards said “the other 76 members.” The roster file behind all of it holds 103 records: 100 clients, Sigrun herself as the benchmark, and 2 team accounts. The scoreboard was right. Nothing else was.

Trace It to the Generator, Never the Page
Every one of these pages is generated by a Python script and published to WordPress. So the fix is never to edit the published page — that lasts exactly until the next rebuild. The fix is to find the literal in the generator.
The scoreboard was correct because its generator already computed n = len(clients) from the roster. The hub was wrong because its generator opened with n=74 typed by hand. The dashboards were wrong for the same reason. Both now derive from the same roster file the scoreboard reads, so the three pages cannot disagree.
The real root cause sat one level up. The daily pipeline that scores new members rebuilt and republished the scoreboard and the dashboards — and never touched the hub. Adding a member changes the count on all three pages, so publishing two of them was guaranteed to drift. It froze at 74 while the board climbed to 100.
Read the Benchmark From the Record, Not From Memory
Dennis’s second question was blunter: what is “100 out of 87”? The scoreboard showed Sigrun’s benchmark as a large 100 / 87. It reads as a fraction, which is meaningless — you cannot score 100 out of 87.
It was meant to be two separate scores: brand 100, business 87. But checking her actual record turned up the bigger problem. Her business score is 81. The 87 was hardcoded months ago and never moved when the score did. A confusing label was hiding a wrong number. It now renders as 100 brand · 81 business, read live from her record every build.
Refuse to Publish a Regression
Rebuilding the dashboards produced a file 186KB smaller than the one it replaced. That is the kind of thing you notice only if you look. Every one of the 39 member headshots was gone.
The photo loader reads each member’s image from disk, and it wrapped that read in a bare except: return None. Running against a mounted folder, those reads fail with errno 35. So the build caught 39 failures, said nothing, and produced a technically valid page with every member’s face stripped off it. Published, that would have been a visible insult to 39 people.
Two changes. The loader now records which photos exist but failed to read, and the build refuses to write at all if the headshot count would drop — it aborts and leaves the good file untouched. While testing that guard it surfaced a genuinely corrupt source file, a truncated JPEG for one member, which a scoped retry now recovers. Photos went from 39 to 40.
Proof ledger. Verified live after publishing, through the REST render layer with a cache-buster: hub and scoreboard both read “all 100 of you”; the benchmark reads “100 brand · 81 business” with no “/ 87” anywhere; the dashboards read “the other 99 members”, carry 40 embedded headshots, and contain zero unresolved audit-link tokens. The 100 audit-PDF links were compared before and after — identical sets, so no member lost their report. Dated history entries that mention older counts were left alone on purpose: they were accurate on the day they were written.
Publish the Set, Never a Subset
The lasting fix is a single script that publishes all three pages together and checks them against the roster before it writes anything. If the hub does not say “all N of you”, if the dashboards do not say “the other N-1 members”, if the benchmark is not the live pair, or if the headshots look thin, it aborts and publishes nothing. Run it bare and it verifies without touching the site.
That script is now wired into two scheduled agents: the Friday weekly pass, and the daily new-member run — because the daily run is where counts change. A member added on Tuesday no longer waits until Friday for the rest of the site to agree with itself.
It also corrected a stale instruction. The weekly agent’s runbook still insisted the dashboard page was an Elementor page and prescribed a ten-step browser routine to update it. That stopped being true on 17 July. It is now one script call.
Count the Cost
One session, one model, no subagent fan-out — the work was diagnosis and judgment, which does not parallelize well. Token figures are approximate.
Take the Rule, Not the Fix
Three things here generalize past Sigrun. Derive every count — any number describing your data should be computed from that data at build time, never typed. Publish related pages as a set — if one number lives on three pages, one script owns all three. Make silent failure loud — a bare except that returns a default will eventually ship something embarrassing, and the only reason this one was caught is that somebody looked at a byte count.
This is the pattern behind The System: agents that run the work also write down what broke, so the next run starts smarter. The scoring model these pages report on is our Personal Brand Score, and the format of this write-up follows our meta article standard.
100 members ranked on brand and business, each row opening that member’s own dashboard. Members only — Sigrun’s cohort has the password.

