world-2026-hub/how-refresh-data.md

8 KiB
Raw Permalink Blame History

How to Refresh Data — Daily Tournament Updates

Self-contained runbook (can be pasted as a prompt to an AI agent) for keeping the hub current during the World Cup (2026-06-11 → 2026-07-19). The full mock→real migration is done (2026-06-12, see .agents/project-memory.md) — this guide covers only what changes day after day from here on.


TL;DR — what changes vs what never changes

File Changes? When
data/results.json Daily After every matchday — scores + status
data/bracket-config.jsonthirdPlaceAssignment Once After the group stage ends (last matches ~2026-06-27, ids 6172)
data/matches.json ⚠️ Rare Only if FIFA officially reschedules a kickoff/venue
data/stadiums.json Never Venues are fixed for the whole tournament
data/teams.json Never The 48 qualifiers are final
data/groups.json Never The draw is final
data/bracket-config.jsonround32 Never Official bracket structure, already encoded
assets/, code, index.html Never Data-only updates by design

If you find yourself editing anything in the rows, stop — something is wrong.


Daily routine: update results.json

1. Find which matches happened since the last update

Match ids in matches.json: 172 = group stage, chronological by UTC kickoff; 73104 = knockout, FIFA official match numbers. Look up matches by date (UTC — a 9 p.m. PDT game lands on the next UTC day) and team ids, never assume id ranges by group.

Group stage runs Jun 1128 (UTC dates) · R32 Jun 28Jul 4 · R16 Jul 47
QF Jul 912 · SF Jul 1415 · 3rd place Jul 18 · Final Jul 19   (UTC dates)

2. Get real scores — never invent

Web-search each played match and cross-check two sources (Wikipedia group/ knockout articles update fast; ESPN/FOX/olympics.com as second source). If a result can't be confirmed in two sources, leave it scheduled and tell the user.

3. Edit the entries

Keep the exact schema — only flip these fields:

{ "matchId": 4, "homeScore": null, "awayScore": null, "status": "scheduled" }    before
{ "matchId": 4, "homeScore": 2, "awayScore": 1, "status": "finished" }           after full-time
{ "matchId": 5, "homeScore": 1, "awayScore": 0, "status": "live" }               only if in progress right now

Rules:

  • status enum is exactly "scheduled" | "live" | "finished" — nothing else.
  • Only finished feeds standings and bracket resolution; live is display-only.
  • Knockout matches only: if decided on penalties, keep homeScore/awayScore as the 90+30-min score (can be equal) and add "penalties": { "home": 4, "away": 3 }. Never add penalties to group matches.
  • Never delete/reorder entries; never change a matchId.

⚠️ First pending item: match id 4 (USA vs Paraguay) kicked off 2026-06-13 01:00 UTC and is still scheduled in the data.

4. Cache-busting (no longer a manual step)

There is no DATA_VERSION to bump. loadData() appends ?t=${Date.now()} to every data/*.json fetch, so each page load gets a fresh URL and Hostinger can never serve a stale results.json. Just edit the data and ship — nothing to bump.


One-time: thirdPlaceAssignment (after ~Jun 2728)

When all 72 group matches are finished, FIFA publishes the ranking of third-placed teams and which group's 3rd goes to which R32 slot. Fill data/bracket-config.json:

"thirdPlaceAssignment": { "1": "C", "2": "G", ... }   // slot → group LETTER, not team id

Each slot only accepts certain groups (official draw constraint — use FIFA's published allocation, it will respect this):

Slot Feeds (FIFA match) Allowed groups
1 M74 (vs Winner E) A/B/C/D/F
2 M77 (vs Winner I) C/D/F/G/H
3 M81 (vs Winner D) B/E/F/I/J
4 M82 (vs Winner G) A/E/H/I/J
5 M79 (vs Winner A) C/E/F/H/I
6 M80 (vs Winner L) E/H/I/J/K
7 M85 (vs Winner B) E/F/G/I/J
8 M87 (vs Winner K) D/E/I/J/L

Each group letter appears in at most one slot. Slots without a published assignment stay null (the UI shows "Best 3rd #N" — that's correct).

Partially-finished group stage: you can fill slots early only if FIFA has; do not derive assignments yourself.


Rare: schedule changes in matches.json

Only if FIFA officially changes a kickoff time or venue:

  • date/time are UTC — convert from the announced local time yourself.
  • stadium/city must exactly match a name/city pair in stadiums.json.
  • Never touch id, phase, homeTeam/awayTeam, bracketRef.

Known caveat to re-verify near Jul 6: match 94 (R16, Lumen Field) kickoff was single-source (Wikipedia 17:00 PDT vs one ESPN summary implying 14:00 PDT).


Verify after every refresh

  1. Quick integrity scan of results.json: exactly 104 entries, matchId 1104 unique, every entry has a valid status, penalties only on ids 73104.
  2. Serve the app (Claude Preview worldcup2026, port 8126 — never file://), hard-reload with cache bypass (browser caches JSON aggressively):
    Promise.all(['data/results.json','data/bracket-config.json']
      .map(f => fetch(f, { cache: 'reload' }))).then(() => location.reload())
    
  3. Spot-check: Groups standings of the groups that played vs a real-world table; Home hero shows the right live/next match; after thirdPlaceAssignment is filled, Knockout R32 shows real team names.
  4. Console must stay free of errors.

Commit convention (standardized)

Every /update-worldcup run produces two commits, in this order, with these exact subject formats — do not improvise per run.

1. Data commit — results.json (+ bracket-config.json when the third-place fill happens)

  • One match:
    data: update DD/MM/YYYY HH:MM HOMExAWAY HxA
    
    e.g. data: update 15/06/2026 18:00 BELxEGY 1x1
  • Multiple matches: short subject, one body line per match:
    data: update DD/MM/YYYY — N jogos
    
    HH:MM HOMExAWAY HxA
    HH:MM HOMExAWAY HxA
    

Rules:

  • DD/MM/YYYY and HH:MM are the match's UTC date/kickoff (same UTC used in matches.json — a 9 p.m. PDT game is the next UTC day).
  • Team codes are the 3-letter uppercase ids; separators are lowercase x (HOMExAWAY and HxA).
  • Penalties (knockout only): append (pen HxA), e.g. data: update 04/07/2026 20:00 BRAxARG 1x1 (pen 4x3).
  • (No DATA_VERSION bump rides along anymore — cache-busting is automatic via ?t=Date.now().)
  • The one-time thirdPlaceAssignment fill rides in the same data commit; note it in the body line (+ thirdPlaceAssignment filled).

2. Docs commit — .agents/ memory + TODO log

docs: log daily refresh DD/MM/YYYY

.agents/ is excluded from the FTP deploy, so keeping it a separate commit keeps the data commit (the one that actually changes the live site) a clean diff.

The previous habit of letting /git-semantic-commit invent a fresh subject each run (data: update match 13 result and stats, data: update match 12 …) is retired — use the formats above verbatim.

After finishing

Update .agents/project-memory.mdCurrent State section (do not append a new dated section — that habit bloated the file and duplicated git):

  1. Refresh the header line: data through match id N, finished count, APP_VERSION.
  2. Update the "Recent refreshes (rolling — keep the last 3)" list: add today's entry at the top and delete the oldest so only the last 3 dated entries remain. Each entry is one compact line (DD/MM or YYYY-MM-DD (revN) — matches XY: HOME HA AWAY, …); per-match sources live in the git commit, not here.
  3. Adjust Pending / next if a milestone was reached (e.g. thirdPlaceAssignment filled).
  4. Tick anything completed in .agents/TODO.md §6.

Only durable new facts (a new gotcha, a decision) go into the other sections of project-memory.md — never a daily refresh log.