152 lines
6.0 KiB
Markdown
152 lines
6.0 KiB
Markdown
# WM 2026 Board
|
||
|
||
Live-Gruppentabellen, Drittplatzierten-Wertung und vollständiger K.o.-Baum der
|
||
FIFA WM 2026 — mit korrekter **Annex-C-Zuordnung** der acht besten Gruppendritten
|
||
und optionalen **Polymarket-Wahrscheinlichkeiten** pro Spiel.
|
||
|
||
Gebaut als Next.js 15 (App Router) mit serverseitigem Feed-Proxy und Caching,
|
||
Docker-Standalone-Image, ausgelegt für Caddy als Reverse Proxy.
|
||
|
||
## Warum ein Backend-Proxy?
|
||
|
||
Das Frontend kann die Feeds **nicht direkt** aufrufen:
|
||
|
||
- **football-data.org** erlaubt im Free-Tier nur 10 Anfragen/Minute und verlangt
|
||
einen `X-Auth-Token` (im Browser nicht sicher unterzubringen).
|
||
- **Polymarket Gamma** liefert `outcomePrices` als JSON-String und hat Rate Limits.
|
||
- Beide setzen kein CORS für beliebige Browser-Origins.
|
||
|
||
Deshalb holt die Route `app/api/matches` beide Feeds **serverseitig**, cached sie
|
||
in-memory (Spiele 60 s, Odds 120 s) und liefert ein fertiges JSON. Das Frontend
|
||
pollt nur diese eine eigene Route (alle 30 s).
|
||
|
||
## Architektur
|
||
|
||
```
|
||
Browser ──poll 30s──> /api/matches (Next.js, gecacht)
|
||
├── football-data.org/v4/competitions/WC/matches
|
||
└── gamma-api.polymarket.com/events?slug=...
|
||
│
|
||
├── computeGroupTables() (FIFA-Tiebreaker)
|
||
├── computeThirdPlaceTable() (beste 8 Dritte)
|
||
└── resolveAnnexC() (495 Szenarien, lib/annexc-data.ts)
|
||
```
|
||
|
||
Die Annex-C-Tabelle (`lib/annexc-data.ts`) enthält alle 495 möglichen
|
||
Kombinationen aus den FIFA-Wettbewerbsregeln. Sobald genau 8 Gruppendritte
|
||
feststehen, ordnet `resolveAnnexC()` jedem der acht betroffenen Gruppensieger
|
||
(A, B, D, E, G, I, K, L) seinen Drittplatzierten zu, und der Bracket „verbindet"
|
||
sich automatisch.
|
||
|
||
## Schnellstart (lokal)
|
||
|
||
```bash
|
||
cp .env.example .env # FOOTBALL_DATA_TOKEN eintragen
|
||
npm install
|
||
npm run dev # http://localhost:3000
|
||
```
|
||
|
||
Token kostenlos registrieren: https://www.football-data.org/client/register
|
||
|
||
## Deployment auf dem VPS (Docker + Caddy)
|
||
|
||
Das Projekt geht davon aus, dass Caddy bereits als Reverse Proxy in einem
|
||
externen Docker-Netz läuft. Heißt dein Netz anders, in `docker-compose.yml`
|
||
und im Caddy-Snippet anpassen.
|
||
|
||
```bash
|
||
# 1. Netz prüfen/anlegen (falls noch nicht vorhanden)
|
||
docker network ls | grep caddy_net || docker network create caddy_net
|
||
|
||
# 2. .env anlegen
|
||
cp .env.example .env && nano .env
|
||
|
||
# 3. Bauen und starten
|
||
docker compose up -d --build
|
||
|
||
# 4. Caddy-Route einbinden
|
||
# Inhalt von Caddyfile.snippet in deinen Caddyfile übernehmen,
|
||
# Domain anpassen, dann Caddy neu laden:
|
||
docker exec -w /etc/caddy caddy caddy reload
|
||
```
|
||
|
||
Danach ist die Seite unter `https://wm.deine-domain.de` erreichbar. Caddy
|
||
besorgt TLS automatisch.
|
||
|
||
## Konfiguration (.env)
|
||
|
||
| Variable | Pflicht | Beschreibung |
|
||
|-----------------------|---------|--------------|
|
||
| `FOOTBALL_DATA_TOKEN` | ja | Token von football-data.org. Ohne ihn liefert die API nur die Wettbewerbsliste. |
|
||
| `POLYMARKET_WC_SLUG` | nein | Slug des WM-Events auf Polymarket (Standard `world-cup-2026`). Den exakten Slug aus der URL nach `/event/` ablesen. |
|
||
|
||
## Feeds verifizieren
|
||
|
||
Beide Upstream-Strukturen vor dem Turnierstart kurz gegenchecken (Felder können
|
||
sich ändern):
|
||
|
||
```bash
|
||
# football-data: Gruppenfeld "GROUP_X", score.fullTime, stage
|
||
curl -H "X-Auth-Token: $FOOTBALL_DATA_TOKEN" \
|
||
"https://api.football-data.org/v4/competitions/WC/matches" | jq '.matches[0]'
|
||
|
||
# Polymarket: markets[].outcomes / outcomePrices (JSON-Strings!)
|
||
curl "https://gamma-api.polymarket.com/events?slug=world-cup-2026" | jq '.[0].markets[0]'
|
||
```
|
||
|
||
Stimmen Feldnamen nicht, nur die Adapter in `lib/feeds.ts` anpassen — der Rest
|
||
(Tabellen, Annex C, Bracket) ist davon entkoppelt.
|
||
|
||
## Was anpassbar ist
|
||
|
||
- **Anderer Ergebnis-Feed** (API-Football, TheStatsAPI): nur `fetchMatchesAndTeams`
|
||
in `lib/feeds.ts` neu schreiben, sodass es `Match[]` + `Team[]` liefert.
|
||
- **Odds-Matching**: `attachOdds` verknüpft per Teamname im Markttitel. Bei
|
||
abweichenden Namen (z. B. „USA" vs. „United States") dort eine Alias-Tabelle ergänzen.
|
||
- **Poll-Intervall**: in `app/page.tsx` (`setInterval`, Standard 30 s) und den
|
||
Cache-TTLs in `lib/feeds.ts`.
|
||
|
||
## Korrektheit der Annex-C-Logik
|
||
|
||
`lib/annexc-data.ts` wurde gegen die veröffentlichte FIFA-/Wikipedia-Tabelle
|
||
geprüft. Beispiel Szenario 415 (Dritte aus A,B,C,E,F,H,I,J) ergibt die Zuordnung
|
||
`H J B C A F E I` für die Gruppensieger A,B,D,E,G,I,K,L — exakt wie in den
|
||
offiziellen Regeln.
|
||
|
||
## Lizenz / Hinweise
|
||
|
||
Kein offizielles FIFA-Produkt. football-data.org ist für nicht-kommerzielle
|
||
Nutzung kostenlos; bei kommerzieller Nutzung dort anfragen. Polymarket-Zugang
|
||
und -Daten unterliegen den jeweiligen lokalen Bestimmungen.
|
||
|
||
## Lokal testen (ohne VPS/Caddy)
|
||
|
||
Für den lokalen Docker-Test gibt es eine separate Compose-Datei ohne externes
|
||
Caddy-Netz und mit direktem Port-Mapping:
|
||
|
||
```bash
|
||
cp .env.example .env # FOOTBALL_DATA_TOKEN eintragen
|
||
docker compose -f docker-compose.local.yml up --build
|
||
# -> http://localhost:3000
|
||
```
|
||
|
||
Die VPS-Variante (`docker compose up --build`) bleibt davon unberührt.
|
||
|
||
## Tabs & Bedienung
|
||
|
||
- **Gruppen** — alle 12 Tabellen mit Flaggen. Klick auf einen Gruppen-Header
|
||
öffnet die **Spiele** dieser Gruppe.
|
||
- **Spiele** — Anstoßzeit/Ergebnis/Live-Minute, Flaggen, Austragungsort und
|
||
(falls im Feed) Zuschauerzahl. Der Tab merkt sich die zuletzt gewählte Gruppe
|
||
und zeigt sie im Label an („Spiele – Gruppe H"). Oben ein Chip-Wechsler A–L.
|
||
- **Drittplatzierte** — gruppenübergreifende Wertung, Schnittlinie bei Platz 8/9.
|
||
- **K.o.-Baum** — fixe Teams stehen voll ausgeschrieben mit grünem Marker;
|
||
vorläufige (nur nach aktueller Tabelle, Gruppe noch nicht durchgespielt oder
|
||
Annex C noch offen) erscheinen kursiv mit „≈"-Zeichen. Flaggen inklusive.
|
||
|
||
## Flaggen
|
||
|
||
Die Flaggen kommen aus dem `crest`-Feld von football-data.org (SVG-URLs auf
|
||
`crests.football-data.org`). Fehlt eine URL, zeigt die App einen neutralen Kreis
|
||
mit Ländercode als Fallback.
|