v19: Accuracy Audit — Fixing What the Site Was Telling You
A full review of the codebase found that the published methodology had drifted badly from the code that actually runs — the How It Works page was describing v14 while the engine ran v17. Three signal tabs were silently hiding six feeds. Two sources had been dead for months. v19 fixes the scoring, replaces the dead feeds, and rebuilds the methodology page so it reads its numbers directly from the scoring engine and can never drift again.
This release is not a new feature. It is the result of auditing every line of the codebase against what the site claims to do, and finding that a number of those claims had stopped being true. Some were cosmetic. Several were not. This post documents all of them, because a risk meter that misdescribes its own method is not worth much.
The methodology page was describing an older engine
The How It Works page restated every scoring constant by hand. When v17 changed those constants, the page was not updated. The result was a published methodology that described v14 while the meter ran v17 — and a reader who checked the arithmetic against the live score would have concluded the meter was broken.
| Claim | Page said | Engine actually ran |
|---|---|---|
| Geo blend weight | 27% | 40% relative (33% effective) |
| Geo article pool | top 100 | top 30 |
| Geo recency half-life | 6 hours | 24 hours |
| Urgency threshold | 8 keyword matches | 6 matches |
| Sentiment threshold | 6 negative words | 4 words |
| Articles sent to AI per cycle | 50 | 20 |
| Claude model | Claude 3.5 Haiku | claude-haiku-4-5 |
| Market data source | Alpha Vantage | FRED + Yahoo Finance |
| Market score formula | mean of four signals | weighted 35/30/20/15 |
| News poll interval | every 2 minutes | every 5 minutes |
| Market poll interval | every 6 hours | every 2 hours |
| Space weather poll | every 5 minutes | every 30 minutes |
The fix is structural rather than editorial. Every scoring constant now lives in one file that the engine imports, and the How It Works page imports the same file. The blend table, the formulas, the pool sizes, the half-lives, the source list and the severity bands are all generated at render time from the values the scorer is running. Changing a weight now changes the published methodology in the same commit, because there is no second copy to forget.
The blend weights do not sum to 1 — and never did
Writing tests for the regenerated page surfaced something that had been true since v17: the eight blend weights add up to 1.21, not 1.00. When geo was raised from 0.27 to 0.40, the other seven were not reduced to compensate.
The meter itself has always been correct. The scorer divides each weight by the total of whichever signals actually reported, so the normalisation happens at runtime and the arithmetic works out. But the percentages the site published were the raw weights, which meant every one of them was overstated by about a fifth. Geo was never contributing 40% of the meter; its real share is 33%.
published before: geo 40% health 16% market 16% cyber 14% energy 10% climate 9% space 8% AI 8% published now: geo 33% health 13% market 13% cyber 12% energy 8% climate 7% space 7% AI 7%
The constants are deliberately unchanged — they express relative importance, which is how they are tuned. What changed is that the site now publishes the effective share, so the numbers on the page are the numbers a reader can verify against the meter.
Three signal tabs were hiding six feeds
Each signal tab kept its own hardcoded list of which sources belong to it, separate from the registry the ingest pipeline polls. Three of those lists had fallen behind. The Health tab queried a source ID that does not exist, so the PAHO feed never appeared, and it omitted two more. The Cyber tab omitted Bleeping Computer and SecurityWeek — the two highest-volume cyber feeds, both added back in v15. The Energy tab omitted the EIA feed.
The score shown on each tab came from the backend and included every source, so the number never matched the articles listed beneath it. There is now a single source registry, shared by the pipeline and the interface, and every tab derives its query from it.
Two sources had been dead for months
Signal confidence had been sitting at 95% for a long time. That figure is the fraction of feeds fetching without errors, and it was telling the truth: two of the 37 were failing every single day.
Outbreak News Today, added in v15, had never completed a single successful fetch. It serves normally to an ordinary browser but its CDN returns 403 to Google Cloud address ranges, so the scheduled function could never reach it — a failure invisible to any test run from a laptop. Inside Climate News worked until 23 June 2026, then began refusing all automated clients across every feed path.
Both are deliberate decisions by those publishers to refuse bots, and they are entitled to make them. Rather than work around the block, both have been replaced with feeds that serve automated clients willingly.
| Removed | Replacement | Group | Veracity | Why |
|---|---|---|---|---|
| Outbreak News Today | CIDRAP Infectious Disease News | Health | 0.95 | The University of Minnesota Center for Infectious Disease Research and Policy. Institutional reporting rather than aggregation, with daily coverage of outbreaks, avian influenza, antimicrobial resistance and vaccine policy. Higher editorial authority than the feed it replaces. |
| Inside Climate News | Guardian Climate Crisis | Climate | 0.90 | The Guardian’s dedicated climate desk. The Guardian world feed already fetches cleanly from our infrastructure, so the domain is known to serve us. Strong coverage of climate policy, extreme weather attribution and energy transition. |
Signal confidence returned to 100% on deployment. Category counts are unchanged: health keeps 8 sources, climate keeps 4.
Recency now decays from the publication date
This is the one change in v19 that moves scores. Article recency was decaying from the time we fetched an article rather than the time it was published, and those are not the same thing when a feed publishes slowly.
Articles are deleted after 72 hours. A weekly publication — a WHO bulletin, a CISA advisory, Import AI — is often still sitting in its RSS feed when its record is purged, so the next poll writes it back as though it were brand new and its recency score resets to 10 out of 10. Those categories were getting a phantom freshness bump every three days from stories that were already a week old.
Recency is now measured from the publisher’s own date. Feed dates are not trusted blindly: a timestamp in the future, at epoch zero, or more than thirty days before the fetch falls back to the fetch time, because feeds routinely emit all three. The practical effect is that slow institutional sources now score on their real age. The overall meter moved by less than a tenth of a point, which is the expected size — it only suppresses articles that were being artificially refreshed.
What the scored window really is
The site described a 72-hour scoring window. In practice each scoring run reads the 500 most recent articles, and at current publishing volume those 500 span roughly 32 hours. The cap binds long before the time limit does.
This has a consequence worth stating plainly: because the cap sorts by recency across all categories, high-volume feeds occupy more of the pool than slow institutional ones. A weekly outbreak bulletin can fall outside the scored set entirely. The methodology page and the limitations section now say this outright rather than claiming a full 72-hour window. Addressing it properly means querying each category against its own limit, which is planned but not in this release.
Smaller corrections
- The embed widget could never load. Site-wide framing protection blocked every iframe, and the embed route additionally required a sign-in, so the snippet the page handed out was refused by every browser on every host. Framing policy is now set per path, the widget serves unauthenticated, and it renders standalone when embedded.
- The geo heatmap opened two stacked modals on every country click — two separate implementations had accumulated in the same component.
- The confidence explainer described a formula the backend does not use. It claimed confidence was articles-ingested divided by articles-expected; it has always been the fraction of feeds fetching without errors. The explainer now describes what the number measures and links to live feed status.
- Failure backoff never reset. After 24 hours of failure a feed is meant to reset its error count and retry, but the reset was overwritten by a stale value on the same run, so counts climbed without bound — one dead feed had reached 167. The backoff behaved correctly; the number it reported did not.
- Per-source poll intervals were fiction. The registry carried a poll interval for each feed and two pages displayed it, but the pipeline fetches every source on one five-minute schedule and never read the field. It has been removed rather than left to mislead.
- User account records are now write-restricted at the database level, so entitlement and delivery-verification fields can only be set by the backend.
Why publish this
WakeUpNeo is an experimental signal, not a factual prediction, and the only thing that makes an experimental signal useful is that its method is legible. A methodology page that describes a version of the engine retired months ago is worse than no page at all, because it invites a reader to trust arithmetic that will not reproduce. Everything above is now either generated from the running code or stated as a known limitation.