This commit is contained in:
2026-07-01 16:36:11 -05:00
parent 4e4c72da7f
commit b2e4b63729
3 changed files with 166 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ export const TLA_TO_ISO2: Record<string, string> = {
COD: "cd", COD: "cd",
COL: "co", COL: "co",
CPV: "cv", CPV: "cv",
CUW: "cw",
CZE: "cz", CZE: "cz",
ECU: "ec", ECU: "ec",
EGY: "eg", EGY: "eg",
@@ -48,5 +49,4 @@ export const TLA_TO_ISO2: Record<string, string> = {
URU: "uy", URU: "uy",
USA: "us", USA: "us",
UZB: "uz", UZB: "uz",
CUW: "cw",
}; };

View File

@@ -18,6 +18,7 @@ export const TEAM_LOCALIZATION: Record<string, { de: string; en: string }> = {
COD: { de: "DR Kongo", en: "Congo DR" }, COD: { de: "DR Kongo", en: "Congo DR" },
COL: { de: "Kolumbien", en: "Colombia" }, COL: { de: "Kolumbien", en: "Colombia" },
CPV: { de: "Kap Verde", en: "Cape Verde" }, CPV: { de: "Kap Verde", en: "Cape Verde" },
CUW: { de: "Curaçao", en: "Curaçao" },
CZE: { de: "Tschechien", en: "Czech Republic" }, CZE: { de: "Tschechien", en: "Czech Republic" },
ECU: { de: "Ecuador", en: "Ecuador" }, ECU: { de: "Ecuador", en: "Ecuador" },
EGY: { de: "Ägypten", en: "Egypt" }, EGY: { de: "Ägypten", en: "Egypt" },

164
scripts/verify-migration.ts Normal file
View File

@@ -0,0 +1,164 @@
// verify-migration.ts — Automatisierte FIFA-Migrations-Checks
// Ausführung: npx tsx scripts/verify-migration.ts
import { TEAM_LOCALIZATION } from "../lib/team-mappings";
import { TLA_TO_ISO2 } from "../lib/flags";
import { FIFA_GROUP_MAP } from "../lib/fifa-constants";
import * as fs from "fs";
import * as path from "path";
const FIFA_BASE = "https://api.fifa.com/api/v3";
const FIFA_SEASON = "285023";
const FLAGS_DIR = path.join(__dirname, "..", "public", "flags");
interface RawMatch {
MatchNumber: number;
IdGroup: string | null;
IdStage: string;
Home: { IdTeam: string; Abbreviation: string; TeamName?: Array<{ Locale: string; Description: string }> } | null;
Away: { IdTeam: string; Abbreviation: string; TeamName?: Array<{ Locale: string; Description: string }> } | null;
HomeTeamScore: number | null;
AwayTeamScore: number | null;
}
let failures = 0;
function pass(msg: string) { console.log(" \x1b[32m✓\x1b[0m " + msg); }
function fail(msg: string) { console.log(" \x1b[31m✗\x1b[0m " + msg); failures++; }
function warn(msg: string) { console.log(" \x1b[33m!\x1b[0m " + msg); }
async function main() {
console.log("\n═══ FIFA-Migration Verifikation ═══\n");
// ── 1. Flaggen-Vollständigkeit ──
console.log("1. Flaggen-Vollständigkeit");
const missingFlags: string[] = [];
const emptyFlags: string[] = [];
for (const code of Object.keys(TEAM_LOCALIZATION)) {
const iso2 = TLA_TO_ISO2[code];
if (!iso2) { missingFlags.push(`${code} (kein ISO-2)`); continue; }
const filePath = path.join(FLAGS_DIR, `${iso2}.svg`);
if (!fs.existsSync(filePath)) { missingFlags.push(`${code}${iso2}.svg`); continue; }
const content = fs.readFileSync(filePath, "utf8");
if (content.trim().length === 0) { emptyFlags.push(`${code}${iso2}.svg (leer)`); continue; }
if (!content.includes("<svg")) { emptyFlags.push(`${code}${iso2}.svg (kein <svg>, ${content.length} bytes)`); continue; }
}
if (missingFlags.length === 0 && emptyFlags.length === 0) {
pass(`${Object.keys(TEAM_LOCALIZATION).length} Teams, alle Flaggen ok`);
} else {
for (const f of missingFlags) fail(`Fehlende Flagge: ${f}`);
for (const f of emptyFlags) fail(`Leere/defekte Flagge: ${f}`);
}
// ── 2. FIFA-Feed abrufen ──
console.log("\n2. FIFA-Feed-Daten abrufen");
let rawMatches: RawMatch[] = [];
try {
const url = `${FIFA_BASE}/calendar/matches?language=de&count=500&idSeason=${FIFA_SEASON}`;
const res = await fetch(url, { headers: { "User-Agent": "wm2026-board/1.0" }, signal: AbortSignal.timeout(10000) });
if (!res.ok) { fail(`HTTP ${res.status}`); } else {
const data = (await res.json()) as { Results: RawMatch[] };
rawMatches = data.Results ?? [];
pass(`${rawMatches.length} Matches geladen`);
}
} catch (err) {
fail(`Feed nicht erreichbar: ${err instanceof Error ? err.message : err}`);
}
if (rawMatches.length === 0) {
console.log(`\n\x1b[31m${failures} Fehler\x1b[0m (Feed-Fehler, Rest übersprungen)`);
process.exit(failures > 0 ? 1 : 0);
}
// ── 3. Gruppenzuordnung ──
console.log("\n3. Gruppenzuordnung");
const perGroup = new Map<string, RawMatch[]>();
const teamsPerGroup = new Map<string, Set<string>>();
for (const m of rawMatches) {
const gid = m.IdGroup ? FIFA_GROUP_MAP[m.IdGroup] : null;
if (gid) {
if (!perGroup.has(gid)) perGroup.set(gid, []);
perGroup.get(gid)!.push(m);
if (!teamsPerGroup.has(gid)) teamsPerGroup.set(gid, new Set());
if (m.Home?.IdTeam) teamsPerGroup.get(gid)!.add(m.Home.IdTeam);
if (m.Away?.IdTeam) teamsPerGroup.get(gid)!.add(m.Away.IdTeam);
}
}
const groupIds = ["A","B","C","D","E","F","G","H","I","J","K","L"];
let groupOk = true;
for (const g of groupIds) {
const matches = perGroup.get(g)?.length ?? 0;
const teams = teamsPerGroup.get(g)?.size ?? 0;
if (matches !== 6) { warn(`Gruppe ${g}: ${matches} Spiele (erwartet 6)`); groupOk = false; }
if (teams !== 4) { warn(`Gruppe ${g}: ${teams} Teams (erwartet 4)`); groupOk = false; }
}
if (groupOk) {
const total = [...perGroup.values()].reduce((s, a) => s + a.length, 0);
pass(`${total} Gruppenspiele, 12 Gruppen mit je 6 Spielen + 4 Teams`);
}
// ── 4. MatchNumber-Vollständigkeit ──
console.log("\n4. MatchNumber-Vollständigkeit (1104)");
const byNum = new Map<number, RawMatch[]>();
for (const m of rawMatches) {
if (!byNum.has(m.MatchNumber)) byNum.set(m.MatchNumber, []);
byNum.get(m.MatchNumber)!.push(m);
}
const missing: number[] = [];
const dupes: number[] = [];
for (let n = 1; n <= 104; n++) {
const entries = byNum.get(n);
if (!entries || entries.length === 0) missing.push(n);
else if (entries.length > 1) dupes.push(n);
}
if (missing.length === 0 && dupes.length === 0) {
pass("Alle 104 MatchNumbers genau einmal vorhanden");
} else {
if (missing.length > 0) fail(`Fehlend: ${missing.join(", ")}`);
if (dupes.length > 0) fail(`Duplikate: ${dupes.join(", ")}`);
}
// ── 5. Team-Code-Auflösung ──
console.log("\n5. Team-Code-Auflösung");
const teamCodes = new Set<string>();
for (const m of rawMatches) {
for (const tb of [m.Home, m.Away]) {
if (tb?.Abbreviation) teamCodes.add(tb.Abbreviation);
}
}
const unresolved: string[] = [];
for (const code of teamCodes) {
const appCode = code; // Rohwert aus FIFA
// FIFA_CODE_OVERRIDE: CRO→HRV, POR→PRT, SUI→CHE
const overrides: Record<string, string> = { CRO: "HRV", POR: "PRT", SUI: "CHE" };
const resolved = overrides[code] ?? code;
if (!TEAM_LOCALIZATION[resolved]) unresolved.push(`${code}${resolved}`);
if (!TLA_TO_ISO2[resolved]) unresolved.push(`${code}${resolved} (kein ISO-2)`);
}
if (unresolved.length === 0) {
pass(`${teamCodes.size} eindeutige Team-Codes, alle in TEAM_LOCALIZATION + TLA_TO_ISO2`);
} else {
for (const u of unresolved) fail(`Nicht auflösbar: ${u}`);
}
// ── 6. K.o.-Paarung 89/90 ──
console.log("\n6. K.o.-Paarung 89/90");
const m89 = rawMatches.find(m => m.MatchNumber === 89);
const m90 = rawMatches.find(m => m.MatchNumber === 90);
if (m89) {
const h = m89.Home?.Abbreviation ?? "?";
const a = m89.Away?.Abbreviation ?? "?";
const ok = h === "PAR" && a === "FRA";
(ok ? pass : fail)(`Spiel 89: ${h}/${a} ${ok ? "(korrekt PAR/FRA)" : "(erwartet PAR/FRA)"}`);
} else fail("Spiel 89 nicht gefunden");
if (m90) {
const h = m90.Home?.Abbreviation ?? "?";
const a = m90.Away?.Abbreviation ?? "?";
const ok = h === "CAN" && a === "MAR";
(ok ? pass : fail)(`Spiel 90: ${h}/${a} ${ok ? "(korrekt CAN/MAR)" : "(erwartet CAN/MAR)"}`);
} else fail("Spiel 90 nicht gefunden");
console.log(`\n═══ Ergebnis: ${failures} Fehler ═══\n`);
process.exit(failures > 0 ? 1 : 0);
}
main().catch(err => { console.error(err); process.exit(1); });