For the World Cup 2026 we are doing a classic porra with friends. Everyone fills an Excel file with their predictions: match result, 1X2 sign, group stage, knockout rounds, all the usual stuff.

At the beginning this was just a collection of spreadsheets. Useful, but not very fun. If you want to know who is leading, you need to open files, compare results, calculate points, and try not to make mistakes. That is exactly the kind of small personal project where I wanted to try something different: build the whole thing with an AI assistant, step by step, and see how far we could go.

The result is here:

https://jfdzar.github.io/porra-mundial-els-peseges/

It is a static website. No backend, no database, no login. Just files in GitHub Pages.

One detail that makes the project more interesting for me: I did not build it sitting in front of a normal coding IDE. The whole thing was done through Hermes AI Agent, running in a Docker container on my homelab server. I was talking to Hermes from Telegram, usually from my phone, and Hermes was doing the coding work on the server: editing files, starting the local preview, checking the browser, committing to GitHub, and verifying the published page.

The model behind it was openai-codex, used through the 20 euro subscription. So the setup was basically: Telegram as the interface, Hermes as the agent, Docker in my homelab as the runtime, and GitHub Pages as the final hosting.

That combination felt surprisingly natural.

Starting point: many Excel files

The first problem was the data.

Each participant had an Excel file with predictions. The files were not the final product; they were the input. What I needed was a normalized table where every row meant one prediction from one person for one match.

So the first step was to extract the Excel information into something easier to work with. The group stage ended up in a CSV with columns like:

persona
archivo
jornada
grupo
codigo
partido
local
visitante
signo
resultado
goles_local
goles_visitante

For example, one row looks like this:

Alex ml, Excel-Mundial-2026-AlexML.xlsx, J1, A, A1, México-Sudáfrica, México, Sudáfrica, 1, 2-1, 2, 1

That already made the project much easier. Once the predictions are in a clean table, everything else becomes possible.

Turning the spreadsheet into data.json

The website reads a single main file: data.json.

That file contains:

  • the list of participants
  • the list of matches
  • all predictions
  • the current standings
  • real results when a match has finished

At the moment of writing this, the site has:

  • 15 participants
  • 104 matches
  • 1560 predictions
  • group stage and knockout predictions
  • 12 matches with real results already loaded

This is the part where using AI helped a lot. I did not have to design everything perfectly from the beginning. I could ask for a first version, test it, see what was wrong, and then correct the model with very concrete feedback.

For example: “this match is duplicated”, “the ranking should only count group stage for now”, “the date is in the wrong timezone”, “do not count live scores until the match is finished”.

This way of working is quite natural for me. It feels less like writing a full specification and more like working with someone who is fast but needs supervision.

The scoring rules

For now the scoring is simple:

  • 4 points for the correct 1X2 sign
  • 2 extra points for the exact result
  • knockout predictions are visible, but they are not counted in the ranking yet

So if I predicted México 2-0 Sudáfrica and the real result was 2-0, I get 6 points. If I only got the winner right, I get 4.

The generated data.json stores the calculation for every prediction:

{
  "played": true,
  "actual_resultado": "2-0",
  "actual_signo": "1",
  "hit_1x2": true,
  "hit_exact": false,
  "points_1x2": 4,
  "points_exact": 0,
  "points_total": 4
}

This is important because the website does not calculate everything from scratch in the browser. The heavy work is done when the data file is generated.

Building the web interface

The frontend is deliberately boring in a good way:

  • index.html
  • styles.css
  • app.js
  • data.json
  • frases.json

That is it.

The page lets you choose a participant, filter by round, search for a team, and click on a match to compare what everyone predicted. The ranking is at the top because that is the first thing everyone wants to see.

We also added small details later:

  • match cards are clickable
  • finished matches have a grey background
  • pending matches stay white
  • the date column was widened because 11/06/2026 was being clipped on mobile
  • a “frase del día” comes from frases.json, so I can edit the phrases without touching the JavaScript

This is one thing I liked about the process. The first version did not need to be perfect. Once the site existed, improvements were easy to describe: “make finished matches grey”, “show all predictions when I click a match”, “make the daily phrase rotate”.

Local preview before publishing

I wanted to test changes locally before pushing them to GitHub Pages.

The local version runs with a very simple server:

python3 -m http.server 8765 --bind 0.0.0.0

Then I can open:

http://127.0.0.1:8765/

This was useful because static sites can behave differently if you open index.html directly. The browser needs to fetch data.json, and that works properly through a local HTTP server.

The workflow became:

  1. change the local files
  2. start or refresh the preview on port 8765
  3. check the page in the browser
  4. inspect the console for JavaScript errors
  5. only then publish to GitHub Pages

That saved me from pushing broken versions several times.

Publishing to GitHub Pages

The published copy lives in a GitHub repository and GitHub Pages serves it directly.

The nice part is that the whole site is static. Publishing is just:

git add .
git commit -m "Update something"
git push origin main

After that, GitHub Pages updates the public site.

There is sometimes a small cache delay, so we also tested files with a query string like:

styles.css?v=commit_hash

This helped to confirm that the new version was really online and not just my browser showing an old cached file.

Automatic result updates

The next step was more interesting: updating real match results automatically.

For that, we added a Python script that reads results from the Zafronix live feed and updates:

  • actual_results.json
  • data.json
  • the standings

One important bug we had to avoid: live APIs sometimes show scores for matches that are still being played. I do not want the ranking to change with temporary scores. So the script only imports matches that are explicitly finished.

Then we added a cron job in Hermes. It runs every hour, but the script only does real work at selected Madrid times:

18, 20, 22, 00, 02, 04, 06, 08

The timezone detail mattered. The server shows cron times in UTC, but the script checks Europe/Madrid, so it knows whether it is really 22:00 CEST or not.

If there are no new final results, the script stays silent. If there are new results, it regenerates the data, commits, pushes to GitHub, and sends me a message.

This is exactly the kind of automation I like: quiet when nothing happens, noisy only when something actually changed.

What worked well with AI

The best part was not that the AI wrote code. That is useful, but not the main point.

The useful part was iteration.

I could say things like:

  • “the dates are wrong”
  • “this should be CEST”
  • “do not publish yet, only local preview”
  • “now publish it”
  • “finished matches should be grey”
  • “the year 2026 is clipped”

And the assistant could inspect the files, edit the code, run the local server, check the browser, commit, push, and verify GitHub Pages.

This is a different feeling from using ChatGPT just to generate snippets. It is more like having a very fast junior developer who can also run commands, but still needs you to decide what looks right.

What I learned

A few notes for the future:

  • Excel is fine as an input format, but convert it to clean CSV/JSON as soon as possible.
  • Static sites are underrated. If the data can be generated beforehand, GitHub Pages is enough.
  • Always test locally with a real HTTP server.
  • Timezones will always find a way to be annoying.
  • Live sports data needs a clear “finished” status. A score alone is not enough.
  • AI is very good for small UI iterations when you can immediately verify the result.

I also liked that the project stayed personal. It is not a startup, not a SaaS, not something that needs users or metrics. It is just a web page for our porra, built from the files we already had, with a bit of automation so nobody has to calculate the ranking by hand.

And that is probably my favorite use of AI so far: removing boring friction from small real problems.

⤧  Previous post How AI Helped Me Navigate Munich’s Mietspiegel and Challenge My Rent