world-2026-hub/how-update.md

12 KiB
Raw Blame History

How to Update — Real Data Migration Guide

This document is a self-contained runbook (and can be pasted as a prompt to an AI agent) for replacing the mock data currently in data/*.json with real FIFA World Cup 2026 data. The app reads these files at runtime — if the JSON keeps the exact same shape, no code changes are needed.


Ground rules — do not break these

  1. Schema is frozen. Every object key, every id format, every enum value listed below must stay exactly as documented. The JS reads by exact field name/value (e.g. phase === "Group A", status === "finished").
  2. Team ids are 3-letter uppercase codes (FIFA/IOC style: MEX, BRA, ARG). Whatever code you pick for a team must be used identically across teams.json, groups.json, and every homeTeam/awayTeam in matches.json.
  3. All match date/time are UTC, format "YYYY-MM-DD" / "HH:MM" (24h). The UI converts to local/stadium time at render time — do not pre-convert.
  4. Paths stay relative (flags/xxx.svg, stadiums/xxx.svg) — required for GitHub Pages.
  5. Code files, localStorage keys, and i18n keys are out of scope. This task only touches files inside data/ (plus optionally assets/images/*).
  6. Work inside the running preview (python -m http.server / Claude Preview, port 8126) and hard-reload after each file edit — fetch() doesn't work on file:// and JSON is aggressively cached by the browser (see gotcha #5 in .agents/project-memory.md).

Where to find real data

Use web search/fetch against authoritative sources, e.g.:

  • https://www.fifa.com — official site: confirmed teams, groups, fixtures, results, venues.
  • https://en.wikipedia.org/wiki/2026_FIFA_World_Cup and its sub-articles (groups, schedule, venues, squads) — usually the fastest single reference, kept current during the tournament.

Cross-check at least two sources for the schedule and current results, since the tournament is live and Wikipedia/FIFA update at slightly different speeds.


Order of operations

Each step depends on ids/names introduced by the previous one. Don't skip ahead.

  1. stadiums.json — confirm the host venues (names, cities, capacities, timezones).
  2. teams.json — confirm the final 48 qualified teams and their codes.
  3. groups.json — confirm the official group draw (AL, 4 teams each), using ids from step 2.
  4. bracket-config.jsonround32 — confirm the official Round-of-32 draw structure (fixed pre-tournament, rarely changes).
  5. matches.json — full 104-match real schedule (dates/times/venues, homeTeam/awayTeam or bracketRef).
  6. results.json — real scores/status as of today; future matches stay scheduled.
  7. bracket-config.jsonthirdPlaceAssignment — fill in once group-stage standings make it determinable (see step 7 detail below); leave null for slots not yet known.
  8. assets/images/ (optional) — swap placeholder SVGs for real flags/stadium art, same filenames or update the JSON path.

File-by-file reference

1. data/stadiums.json — array, currently 30 entries

{ "id": 1, "name": "Estadio Azteca", "city": "Mexico City", "capacity": 87000,
  "image": "stadiums/azteca.svg", "timezone": "America/Mexico_City" }
  • id: integer, unique, referenced by nothing else (informational only — matches.json links by name/city, not id).
  • timezone: valid IANA name — drives "stadium time" mode and must stay DST-correct.
  • image: path relative to assets/images/.

Decision point: the official 2026 tournament uses 16 venues (3 in Mexico, 11 in USA, 2 in Canada), but this file currently lists 30 (the original bid shortlist). Recommendation: trim to the 16 confirmed match venues so every row in stadiums.json is actually used by matches.json — but confirm with the user first if they'd rather keep the extra 14 as a broader "host city guide".

2. data/teams.json — array, 48 entries

{ "id": "MEX", "name": "Mexico", "flag": "flags/mex.svg" }
  • id: 3-letter uppercase code, unique, used as the cross-file key everywhere.
  • flag: path relative to assets/images/, convention flags/<lowercase id>.svg.
  • name: display name, shown as-is (not translated — i18n covers UI strings only).

3. data/groups.json — object, keys "A""L", each an array of exactly 4 team ids

{ "A": ["MEX", "SUI", "KOR", "JAM"], "B": [...], ..., "L": [...] }
  • 12 groups × 4 teams = 48 — must cover every id in teams.json exactly once.
  • Order within the array doesn't affect standings (sorted by points/GD/GF), but conventionally keep host nation first where applicable.

4. data/bracket-config.json

{
  "round32": [
    { "id": "R32-1", "home": { "type": "group", "ref": "A", "pos": 1 },
                      "away": { "type": "third", "slot": 1 } },
    ...
  ],
  "thirdPlaceAssignment": { "1": null, "2": null, ..., "8": null }
}
  • round32: 16 entries, id = "R32-1".."R32-16" (order = bracket position, feeds R16-1..R16-8 by sequential pairing 0-1→0, 2-3→1, …, then QF, SF, FINAL, with THIRD-PLACE from the two SF losers).
    • home/away each are either:
      • { "type": "group", "ref": "<A-L>", "pos": 1 | 2 } → group winner/runner-up, or
      • { "type": "third", "slot": 1-8 } → one of the 8 best third-placed teams.
    • Only edit ref/pos/slot values if the official Round-of-32 draw structure differs from what's encoded here — don't change id values or array order (that's the bracket's visual/geometric layout, tied to the CSS).
  • thirdPlaceAssignment: maps slot "1""8" → group letter "A""L" or null. Fill in step 7, after the group stage actually finishes, following FIFA's official "ranking of third-placed teams" rules. Until a slot is filled, resolveBracketTeams() shows a "Best 3rd #N" placeholder — that's expected and correct for slots not yet determined.

5. data/matches.json — array, 104 entries, ids 1104

ID ranges (keep this layout — resultByMatchId is keyed by id, not position, but contiguous chronological ids match the existing convention):

ids phase count
172 "Group A""Group L" (6 matches each) 72
7388 "Round of 32" 16
8996 "Round of 16" 8
97100 "Quarterfinals" 4
101102 "Semifinals" 2
103 "Third Place" 1
104 "Final" 1

Group-stage match:

{ "id": 1, "phase": "Group A", "date": "2026-06-11", "time": "16:00",
  "stadium": "Estadio Azteca", "city": "Mexico City",
  "homeTeam": "MEX", "awayTeam": "SUI" }
  • homeTeam/awayTeam must be 2 of the 4 ids listed for that group in groups.json (see integrity rule #1 below — violating this crashes the standings table).
  • stadium/city must match a name/city pair in stadiums.json exactly (case-sensitive string match used for cross-links and the stadiums view).

Knockout match:

{ "id": 74, "phase": "Round of 32", "date": "2026-06-29", "time": "16:00",
  "stadium": "Gillette Stadium", "city": "Boston", "bracketRef": "R32-2" }
  • No homeTeam/awayTeam — teams are resolved at runtime from standings + bracket-config.json.
  • bracketRef must be exactly one of: R32-1..R32-16, R16-1..R16-8, QF-1..QF-4, SF-1, SF-2, THIRD-PLACE, FINAL — each value used once.

6. data/results.json — array, 104 entries, one per match id

{ "matchId": 1, "homeScore": 1, "awayScore": 1, "status": "finished" }
{ "matchId": 61, "homeScore": 1, "awayScore": 0, "status": "live" }
{ "matchId": 62, "homeScore": null, "awayScore": null, "status": "scheduled" }
{ "matchId": 74, "homeScore": 1, "awayScore": 1, "status": "finished",
  "penalties": { "home": 4, "away": 3 } }
  • status: "scheduled" (not started, scores null) → "live" (in progress, real scores) → "finished" (full-time).
  • penalties: optional, only on knockout matches finished level after extra time — homeScore/awayScore stay the 90+30-min score (can be equal).
  • Set this from real, up-to-date results as of "today". Anything not yet played stays "scheduled" with null/null — this is the normal/expected state for future fixtures, not a placeholder to "fix".
  • Only "finished" matches feed standings (groups.js) and bracket resolution (bracket.js) — a "live" match's score does not affect either.

Cross-file integrity checklist

Run through this after editing, before trusting the preview:

  1. Every Group-stage homeTeam/awayTeam in matches.json is one of the 4 ids in groups.json[<that group letter>]. If not, computeStandings() throws (rows.get(...) is undefined) and the Groups tab breaks entirely.
  2. groups.json covers all 48 teams.json ids exactly once, 4 per group, AL.
  3. results.json has exactly one entry per matches.json id, 1104, no gaps/dupes.
  4. Every bracketRef value (R32-1..R32-16, R16-1..R16-8, QF-1..QF-4, SF-1, SF-2, THIRD-PLACE, FINAL) appears exactly once across the 32 knockout entries in matches.json.
  5. matches.json's stadium/city strings exist in stadiums.json (exact string match — used by setStadiumFilter cross-links).
  6. bracket-config.json.round32[i].home/away group ref values are valid letters AL and pos/slot are in range (pos: 1-2, slot: 1-8), each slot 1-8 used exactly once across the 16 entries.
  7. No team id appears in two different groups.

Optional: real artwork

assets/images/flags/*.svg and assets/images/stadiums/*.svg are currently placeholders. To replace:

  • Keep the same filenames referenced by teams.json/stadiums.json (e.g. flags/mex.svg), or update the flag/image JSON value if you use different names.
  • SVG is not required by the code — any image format works as long as the path in JSON matches the actual file extension.

Verification steps

  1. preview_start (or python -m http.server from the project root), then hard reload (location.reload() after fetch(..., { cache: 'reload' }), or DevTools "Empty Cache and Hard Reload").
  2. Home — hero shows the right live/next match; dashboard counts look sane.
  3. Matches — spot-check a few real fixtures (date/time/venue), filters (group/phase/team/stadium) still populate correctly.
  4. Groups — all 12 tables show real teams, standings order matches a real-world source for finished matchdays.
  5. Knockout — R32 slots show real teams where standings allow, placeholders ("Group X Winner", "Best 3rd #N") elsewhere; hover-path highlight and zoom still work.
  6. Stadiums — cards match the trimmed/confirmed venue list; "view matches" cross-link filters correctly.
  7. Toggle EN/PT and local/stadium time — no console errors, times shift correctly across at least two different stadium timezones.

After finishing

Per this project's conventions, update .agents/:

  • Check off the relevant items in .agents/TODO.md section 6 (Post-launch).
  • Append a dated entry to .agents/project-memory.md documenting: data source used, the stadiums 30→16 decision (and final list if trimmed), and the thirdPlaceAssignment status (filled / partially filled / still null).

Commit convention

This migration is a one-off; commit it descriptively (e.g. data: migrate mock → real WC2026 data). For day-to-day result updates during the tournament, the standardized commit format lives in how-refresh-data.md → "Commit convention (standardized)": a data: commit per refresh (data: update DD/MM/YYYY HH:MM HOMExAWAY HxA, or — N jogos with one body line per match) plus a docs: log daily refresh DD/MM/YYYY commit. Use those verbatim, not a freshly worded subject each run.