TL;DR: Full Court Press is a zine for every WNBA game, live on AWS at fullcourtpress.lol. Code counts every stat from the play-by-play, the AI only writes the voice, and every number and name it writes is checked against the facts. The checks caught a lot, and what they missed taught me more. When the AI's writing fails, the page doesn't go blank. Code writes a plain version and says so.
Fanzines were self-published, non-commercial, and gloriously niche: photocopied, stapled, made by fans who cared more than anyone was paying them to. I wanted one for the WNBA. The morning after a game, I wanted more than a final score and a line in someone else's roundup, and I wanted it for every team, not only the matchup everyone was already talking about.
Full Court Press publishes the morning after every game day. Every finished game gets an issue, written in your team's voice, not the winner's. There's a ten-second audio recap, a share card, and a print button that lays the whole issue out as an eight-panel sheet you fold into a pocket zine.
Old zines had no fact-checker. Most were one fan, a typewriter and a copy machine, and the mistakes were part of the charm. This one has a fact-checker, and it's code. The stats are counted from the play-by-play by code, never by the AI. Every team's points have to add up to the final score before anything publishes. The AI only writes the voice, and every number and name it writes gets checked against those same facts. Anything that fails gets cut, and the page says so.
What you get
- Every game, both sides. A fan of the losing team gets their own edition, honest about the loss.
- Two voices. The Call is warm, like a late-night radio call you'd stay up to hear. The Film Room is whiteboard energy: here's how the game turned, here's the evidence.
- A 10-second audio recap read by Amazon Polly, from a script code builds.
- A share card for every edition, with a Save button. Paste the link anywhere and the same card shows up.
- A print button. Fold the sheet on the dotted lines and it's a pocket zine.
- An archive. Every game day stays up, listed by date in the archive, and every team gets its own page with all of its issues, newest first (the Aces, for one). The next morning's run adds to the zine. It never writes over the last one.
-
A golden set of five saved games that never changes: the golden set. More on why below.
Code writes the numbers
The data tier I pay for, BALLDONTLIE's ALL-STAR plan, doesn't include WNBA box scores. It does include play-by-play. So the zine counts its own stats from the play text: points, rebounds, assists, steals, blocks.
A counted stat needs a check, and points have one. The final score comes from a separate record, so every team's counted points must add up to it. If they don't, every derived stat for that game is dropped. The other categories get their own sanity checks (assists can't outnumber made baskets, for example) and drop one at a time, with a line on the page saying so.
In the five test games, points reconciled 5 of 5, overtime included. Then I did the part no test can: I checked three counted stat lines by eye against Basketball Reference, including an Alyssa Thomas triple-double built entirely on counted rebounds and assists. All three matched.
That proves the counting on those three lines. It doesn't prove rebounds and assists on any given night, and the About page says so.
The fact lock caught a lot. What it missed taught me more.
Claude Haiku 4.5 on Amazon Bedrock writes each page's voice: a headline, a recap, a player spotlight, and a pick for The Number. Before anything publishes, a fact lock checks every section:
- every number must be in that game's facts sheet
- spelled-out numbers are rejected, so "twenty-six" can't sneak past
- every capitalized name must be a player or team in the game
- a banned list blocks betting, injuries, rumors, and claims the facts can't back A section that fails gets one retry with a note naming what failed. If it fails again, it's cut. Logs carry the rule that fired, never the model's text.
Then I read what got through. On the five test games the lock had caught 9 sections, and I found 7 more that were wrong. The lock passed all 7 because every number in them was correct.
"The fourth quarter belonged to Atlanta."
Indiana won the fourth quarter 17 to 9. Every miss was the model doing its own math on correct numbers. A stricter prompt didn't fix that. Moving the claim into code did: the facts sheet now includes plain sentences built by code (who won each quarter, the score at halftime, each team's run, who led each team, where the standings sit), and the model quotes them instead of working them out.
Then it went live, and I audited again: 14 live games, 151 kept sections, 13 with a claim that wasn't true. Some invented records ("the Dream stay perfect in the East" at 30-14). Some put a correct number on the wrong claim. New rules caught 6 of the 13, and they re-check the saved writing every time a page is rebuilt, so the fix reached pages already published without a single new AI call.
The other 7 are the honest limit. The lock proves every number and name came from the facts. It doesn't prove the sentence uses them right.
Then a third of the pages had no story
Stricter rules have a cost, and I found it the day I was supposed to be polishing. The re-check can only cut. It never rewrites. So when I tightened the rules, it went back over writing that had passed the old ones and cut recaps.
I measured the live site: 20 of 56 pages had no recap, just "The writers' room passed on this one." On one night it was 12 of 20. And the featured game on the front page, the one the site tells you to read, had no recap on the winner's page.
The page wasn't empty. The score, The Number, the audio, the stat line and the quarters were all there, because code makes those. But the story was gone.
I could have asked the model to try again. I went the other way. When a recap gets cut now, code writes a plain one from the same facts, told from that edition's side:
The Valkyries beat the Sparks 76-75. The Valkyries trailed by as many as 16 and still won. The Valkyries won the 4th quarter 30-20. The Valkyries went on a 14-0 run in the 4th quarter.
It's labeled "written by code, from the counted facts . no AI," and a line explains why the AI's version was cut. A test runs the code-written recap for every page in the golden set through the same fact lock the AI faces, and every one passes. The featured game now has to come from a game whose winner's page kept its AI recap, and every card opens the voice that kept one.
After the redeploy: 56 of 56 pages have a story. 36 written by the AI, 20 by code, 0 empty.
I think this is the part of the build I'd keep for everything else I make. A cut is honest, but a hole isn't a product. A floor built only from checked facts, and labeled as a floor, is both.
Why five old games stay on the site
The live site only has something new when the WNBA plays, and the WNBA doesn't play every night. In the playoffs, games are days apart. Judging runs into October, and the season can end before a judge ever opens the link. A zine that only exists when there's a game felt risky to hand to a judge.
So the site keeps a golden set: five games from August and September, saved and published once. I picked them to be hard. One went to overtime (Indiana at Atlanta, 95 to 91), because an extra period breaks things a regulation game never touches. Others had a comeback or a triple-double, the kind of line the stat counter and the fact lock have to get right.
The golden set is also where the testing happened. Points reconciled on all five. The first audit of the AI's writing ran on these games, and the test that proves every code-written recap passes the fact lock runs on them on every push.
It is not a mockup. The same code that builds the live pages builds the golden ones. It's saved, not live, so the page says so at the top, and it has no audio. For the newest games, the front page is the place to go.
How it's built on AWS
One SAM stack, one scheduled Lambda, and everything a visitor sees is a static file.
| Piece | AWS |
|---|---|
| Morning run, 6:15am Pacific | EventBridge Scheduler to Lambda (Python 3.13) |
| The voices | Amazon Bedrock, Claude Haiku 4.5 through a tagged application inference profile |
| Audio | Amazon Polly, neural engine only |
| The site | Private S3 behind CloudFront with Origin Access Control, a strict Content Security Policy and HSTS, on a custom domain with an ACM certificate |
| The API key | SSM Parameter Store SecureString, read at runtime |
| Guardrails | AWS Budgets on the project tag ($10) and on Polly ($2), CloudWatch logs with ids and counts only |
Polly is neural on purpose. I wanted the most predictable voice reading the scores, and every word it reads comes from a script code built, not from the AI.
Two agents, one build log
I direct, validate and decide. Kiro, AWS's spec-driven coding agent, wrote the spec, ran the spikes and did everything that touches AWS. Halfway through the sprint I ran low on Kiro credits, so the work split. Kiro kept the AWS side: the connection, the API key, the deploy, Bedrock, Polly. Claude took everything local: the stat counter, the fact lock, the templates, the tests, the design passes. By the last week I was running the deploys myself, from one script Claude wrote so a redeploy is a single command.
Two agents in one repo needed one rule: each logs its own work and never the other's. That build log is the proof of agent use for this hackathon. It also caught one agent's wrong conclusion before it changed the plan, which is the next story.
The file said otherwise
I needed five games from this season to test against, and one had to go to overtime, because overtime breaks things a regulation game never touches. Kiro wrote a script to scan a month of games, then reported: forty-four games, zero overtime. It recommended dropping the requirement.
It was a reasonable report. It was also wrong. Several commands had come back with empty or cut-off output, so the report was built from whatever made it back to the screen. The script had kept writing to a file the whole time. Claude opened the file instead of the summary: sixty-two games, one overtime, Indiana at Atlanta, 95 to 91.
Nobody was careless. The agent wrote its evidence down, and the evidence was right. What failed was the layer between the evidence and me. The rule that came out of it: read long results from the file, never from what scrolled past.
The rules were too tight
Deploy day, Kiro stopped four times. Every stop followed the rule I had signed off on: if a run doesn't match what we expect, report and don't guess a fix.
The rule was fine. The expectations inside it weren't. Claude wrote expected game counts into Kiro's prompts from reasoning instead of data, and twice changed date logic that was already right. Kiro's read-only probe of the timestamps the API sends settled it: the API files games by UTC date, so an 8pm Eastern game shows up under tomorrow. Only one of the four stops caught a bug. The other three caught wrong expectations.
A stop rule is only as good as the numbers you hand it. An expected value in a prompt is a claim like any other, and it needs a source.
Proof of connection
Kiro connected to AWS through the Agent Toolkit for AWS (AWS CLI v2 and the AWS MCP server, browser sign-in, one named profile, us-east-1). No IAM users or access keys were created for the agent, and the data API key never passed through an agent: I put it in Parameter Store myself. Every task, which agent did it, and what it touched is in BUILD-LOG.md, and the prompts I handed Kiro are in docs/agent-prompts.
What it can't do
- One data provider, and only points are verified against an independent number.
- The lock checks numbers and names, not meaning. Code-written sentences make wrong claims rarer, not impossible.
- The code-written recap is plain. It's true, and it reads like a box score with verbs.
- Two voices, WNBA only. The NBA is stubbed until its season starts.
- The golden set has no audio. The live issues do.
- No audience yet. That's the next part.
Try it
Then I went on vacation. The 6:15 run kept going without me: three more nights, eight more games, every issue written, checked and published with nobody touching it. They are all in the archive That is the part I wanted most from a zine that comes out the morning after.
fullcourtpress.lol. Pick your team, read your edition, play the recap, save the card, hit print, fold it. The code, the spikes, the build log and every wrong turn are in the repo.
Quick context if you are new here. I work in the California courts, running court operations for the county. I started building with AI in July 2025 and I have been learning in public ever since. I do not write the code. I direct, the agents generate, I validate and decide.
Data from BALLDONTLIE. Unofficial fan zine, not affiliated with the WNBA or any team.
Built with AI assistance in September 2026. These tools move fast, so check the source before you quote me.
AI Assisted. Human Approved. Powered by NLP.





Top comments (0)