If August was the month Edge Model grew a social layer and a memory, September was the month it learned to be honest about what it actually knows. The headline feature count is modest. The version numbers ran from 0.6.30 through 0.6.36 โ€” fewer releases than August, smaller in surface area. But the work underneath was more consequential than the numbers suggest, because it addressed a problem that had been silently compounding since the first match result was ever ingested: we did not have a reliable way to tell teams apart.

The team identity problem, and why it took until September

Football-data.org and The Odds API โ€” the two external sources Edge Model depends on for results and market odds โ€” name teams inconsistently. The same club might appear as "Manchester City" in one response and "Man City" in another. Through July and August, we resolved this with fuzzy string matching: normalise both names, check whether one is close enough to the other. It worked for the vast majority of teams. It failed, sometimes silently, for the exceptions.

The September fix โ€” H35, the team identity registry โ€” replaced fuzzy matching with a stable integer ID lookup. Every team in every preset now has a row in team_registry.json mapping it to its unique football-data.org integer identifier. When an incoming result arrives, the server looks up the team by that integer before it tries any string comparison. If the integer matches, the attribution is exact. If it misses, the fuzzy fallback runs as before โ€” but the cases where the fallback runs are now exceptions, not the norm.

The deeper story of what went wrong when we deployed the registry โ€” a null ID for Paris FC that silently re-introduced the substring collision it was built to prevent, and a Bundesliga club's ID overwriting a Ligue 1 club's entry in a Python dictionary โ€” is told in full in a separate post. It is worth reading if you care about how quietly data pipelines can fail.

La Liga data quality, and the rolling ingestion window

The registry work was preceded by a different data quality investigation that targeted La Liga specifically. The word "Deportivo" had been listed in the server's name-normalisation function as a noise word โ€” a prefix to strip before matching, like "FC" or "SC". Unlike those prefixes, "Deportivo" is a meaningful part of actual club names. Stripping it reduced "Deportivo" to an empty string, and an empty string is a substring of every string, so the matcher returned a positive result for every comparison involving a club whose API name started with "Deportivo". Results were being attributed to the wrong team.

The same investigation surfaced a fragility in how the daily scheduler ingested results. The scheduler was fetching only a single day's matches on each run. If it missed a day โ€” a deployment, a restart, a transient API error โ€” those results were permanently lost. The fix widened the default fetch window to seven days, so each run self-heals any gap from the preceding week. Combined with the full-season sync endpoint added at the same time, there is now a path to recover from any ingestion gap back to the start of the season.

A public API for anyone who wants to build on the model

H30 shipped a read-only Bearer-authenticated API: three endpoints that expose standings, championship probabilities, and model-vs-market edge data for any loaded league. The intended audience is builders โ€” people who want to pull Edge Model's output into their own tools, dashboards, or analysis pipelines without scraping the app. The full documentation lives at /api-docs. Keys are issued on request. Rate limiting is 60 requests per minute per key.

We also added /llms.txt โ€” a discovery file that describes the app, lists key public URLs, and explains which endpoints are accessible without authentication. This is part of a broader effort to make Edge Model legible to AI agents that want to interact with it programmatically rather than through a browser. A third-party readiness audit scored the site at 44 out of 100 before these changes; the structured JSON errors for unknown API paths and the discovery file together address several of the gaps it identified.

The Full Season Sync stopped timing out

One operational improvement worth calling out: the Full Season Sync โ€” the operator-facing trigger that replays an entire season's worth of results through the ingestion pipeline โ€” was running synchronously on the HTTP request thread. A full replay across five leagues with rate-limited API calls takes several minutes. The platform's reverse proxy kills connections after around 30 seconds. The sync was completing on the server but the client was receiving a 502 error and a red toast before it finished. The fix moves the sync to a background daemon thread, returns an immediate confirmation, and lets the standings update in real time via Server-Sent Events as the background run progresses.

The accuracy question

Here is the thing we have been avoiding saying plainly: the model's early-season accuracy against the market is not where it needs to be for Edge Model to deliver a genuine edge to value hunters. The metrics page is public and you can see this for yourself โ€” the Brier scores and calibration data for each league are updated live as results come in.

A Bayesian model built on pre-season attack and defense priors is working from a thin information base at the start of a new campaign. It does not yet know that a team has conceded in their last seven matches, or that a striker has been injured for three weeks, or that a club promoted last season is outperforming their preseason expectations. The market, which aggregates that information continuously from thousands of sources, tends to price these things in faster than a model that updates only on recorded results.

This is not a surprise โ€” it is an expected property of this class of model at this point in the season. What it means in practice is that using Edge Model's probability estimates to find market mispricings requires either waiting long enough into the season for the posteriors to have absorbed enough match evidence, or supplementing the model's priors with information it currently does not have.

The September data quality work was a prerequisite for investigating this properly. You cannot reason clearly about model accuracy if some of the results feeding into it are attributed to the wrong teams. With the registry in place and the ingestion pipeline verified against the official standings, we now have clean data to work from. October's agenda is to use it: to look carefully at where the model diverges most from the market, understand why, and think through what changes to the model's inputs or update logic could close that gap. The goal is not a better-looking accuracy number โ€” it is a model that can consistently identify fixtures where the Bayesian probability and the market price are far enough apart that one of them has to be wrong. That is what a real edge looks like.