← All articles

This article is part of the SaskPoly story. Read how it all started.

The Loop, Part 1: The Clock — One Machine Instead of Six Alarms

Every prediction this site makes flows through one daemon that ticks every six hours, walks nineteen stations on an assembly line, grades its own homework, and leaves a heartbeat where anyone can see it — this is the machine behind the picks, explained for people who don't gamble.

At 8:28 this morning the machine woke up, walked nineteen stations on an assembly line, graded its own homework, filed a report on itself, and went back to sleep. It does this four times a day. It has done it 81 times since September. This post is about the machine — what it is, why it is one machine instead of many, and what it does when something breaks. No team names are required. The machine is indifferent to all of them, which is the entire point.

The problem: six alarms nobody hears

Most prediction setups are not systems. They are collections. A cron job that fetches odds at 9 AM. A script someone runs by hand when they remember. A spreadsheet with a formula nobody dares touch. Each piece works alone, fails alone, and — this is the expensive part — fails silently. The odds script breaks on a Tuesday and nobody finds out until Saturday's pick comes from stale numbers.

We had the scattered version. Replacing it took one decision, made on 2026-09-07: one clock owns everything. Not a pile of independent cron entries — a single daemon, clock.py, that walks a fixed chain of phases every tick. If you have ever worked a conveyor line, you already understand the architecture. One belt. Stations in order. A part that fails at station four does not stop the belt — it gets logged, and the belt carries on to station five.

What one tick looks like

A tick runs every six hours (21,600 seconds — precise timing matters less than never being down for long). Each station does one job:

StationWhat it fetches or doesSource
fetch_mlb / fetch_cfl / fetch_nfl / fetch_nflversetoday's schedules, standings, playoff stakes, NFL historyMLB Stats API, cfl-sdk, nflverse, ESPN
fetch_linesthe betting board, scraped in one browser passSportSelect PRO•LINE via Playwright
fetch_injuriesthe daily per-team injury digestnflverse injuries release + scraped CFL reports
fetch_news / fetch_odds_espn / fetch_outcomes / fetch_fundingnews signals, US-book consensus, prediction-market prices, funding ratesNewsAPI, ESPN, Hyperliquid, Hyperliquid
teaser / predictbuilds teaser cards, logs prediction rowsthe loop's own engines
auditre-derives from raw data what predict should have writtenread-only verifier
settlegrades finished games, computes scoressame snapshots, finals included
recap / render / contentthe nightly write-up, the daily sheet, draft articlesthe ledger

Nineteen stations, one belt. The slowest station is the odds scrape at 30 to 60 seconds of browser automation. The whole tick finishes in about two minutes.

Failures are data

Here is the rule that makes the machine trustworthy: a phase that fails returns failed with the error string, and the chain continues. When the odds site changes its layout and the scrape dies — and scrapes die; that is what scrapes do — the tick records the failure and predicts anyway, with the market columns honestly blank rather than quietly stale. When the news API rate-limits, the phase waits for the next tick rather than hammering it. (We learned that one empirically: six rapid requests got this IP address banned for an hour. Now the phase makes one polite request per tick. Failed is data, and the next tick picks it up.)

After every tick the clock rewrites a heartbeat — last run, status, tick count, every station's result — into the living state file, and appends a row to an append-only ledger of ticks going back to day one. Eighty-one ticks. Every one accounted for. A machine that files a report on itself every six hours has nowhere to hide, which is exactly the design intent.

The confession: the night the clock ate the ledger

Honesty is cheaper than discovery, so here is the system's worst night. On 2026-09-08 the long-running daemon was holding old code in memory while the ledger's schema had changed on disk. The settlement phase crashed on the new column — and the crash came after the file-open had already truncated the prediction ledger. Twenty-three rows, gone, including a graded win.

The rows were reconstructed from a verbatim session dump, every one marked "reconstructed" in its own lesson column, and the lesson was mechanized: any long-running process gets restarted after a schema change, every time, no exceptions. The rebuilt ledger now carries 199 rows and the audit phase — added the following week — re-derives from the raw snapshots what predict should have written. Its first live run flagged 31 malformed row IDs the same night. Caught, fixed, backed up, green since. The belt did not stop, and the fix is now part of the belt.

Nothing publishes itself

One boundary deserves its own paragraph, because it is the one we will not relax: the loop never publishes and never wagers. The daily sheet is a local file. The article drafts it generates go to a review folder; a human edits them before anything reaches this site. Stakes live on the sheet as accounting, not instructions — the machine prices tickets, it does not buy them. The deploy button is a human's hand on it, the same way the pencil at the pool is. Anyone selling you a system that skips that step is selling a story.

The series

This was the clock. The rest of the machine, one concept at a time:

One belt, nineteen stations, a report card every six hours. The picks get the byline. The clock does the work.