diff --git a/app/api/matches/route.ts b/app/api/matches/route.ts index ad060a0..ae7c9fb 100644 --- a/app/api/matches/route.ts +++ b/app/api/matches/route.ts @@ -27,37 +27,18 @@ export async function GET() { const liveScores = await fetchLiveScores(); matches = applyLiveScores(matches, teams, liveScores); } catch (err) { - console.error("[worldcup26] fetchLiveScores fehlgeschlagen:", err); + console.warn("[worldcup26] fetchLiveScores fehlgeschlagen:", err instanceof Error ? err.message : err); } const groupTables = computeGroupTables(teams, matches); const groupTablesLive = computeGroupTables(teams, matches, true); - // K.o.-Match-Nummern per Slot-Auflösung vergeben (FIFA-Topologie) assignKONumbersBySlots(matches, teams); - // Zweiter Durchlauf: Polymarket-Odds auf K.o.-Matches mit aufgelöster R32-Paarung - console.log("[ROUTE] vor attachKOOdds | odds vorhanden:", !!odds, "| odds länge:", odds?.length); if (odds) { - try { - matches = attachKOOdds(matches, teams, odds); - console.log("[ROUTE] nach attachKOOdds | matches länge:", matches?.length); - } catch (err) { - console.error("[ROUTE] attachKOOdds fehlgeschlagen:", err); - } + matches = attachKOOdds(matches, teams, odds); } - console.log("[ROUTE] vor PM-SNAPSHOT | matches länge:", matches?.length); - const withProb = matches.filter(m => m.prob != null); - console.log("[PM-SNAPSHOT]", new Date().toISOString(), - "| anzahl mit prob:", withProb.length, - "| spiele:", withProb.map(m => { - const h = teams.find(t=>t.id===m.homeTeamId)?.code; - const a = teams.find(t=>t.id===m.awayTeamId)?.code; - return `${h}-${a}`; - }).join(", ")); - - // prob-Feld normalisieren: immer null statt undefined, damit JSON konsistent ist const normalizedMatches = matches.map((m) => ({ ...m, prob: m.prob ?? null })); const thirdTable = computeThirdPlaceTable(groupTablesLive); diff --git a/lib/feeds.ts b/lib/feeds.ts index f204043..974dfe4 100644 --- a/lib/feeds.ts +++ b/lib/feeds.ts @@ -267,7 +267,7 @@ function safeParse(s: string, fallback: T): T { // Holt alle WM-Spiele über den Series-Endpoint mit Pagination. // Parst pro Event die drei Moneyline-Märkte (Heim/Draw/Auswärts). export async function fetchOdds(): Promise { - return cached("pm:odds", 120_000, async () => { + return cached("pm:odds", 300_000, async () => { const allEvents: PmEvent[] = []; for (let offset = 0; ; offset += 100) { const url = `${PM_BASE}/events?series_id=${PM_SERIES}&active=true&closed=false&limit=100&offset=${offset}`; @@ -321,22 +321,11 @@ export async function fetchOdds(): Promise { } if (pHome > 0 || pDraw > 0 || pAway > 0) { - const rawStartDate = (ev as any).startDate ?? null; - const rawEndDate = (ev as any).endDate ?? null; - const rawGameStartTime = (ev as any).gameStartTime ?? null; - const startTime = marketGameStartTime ?? rawGameStartTime ?? rawEndDate ?? rawStartDate; - console.log(" [PM-RAW-ML]", ev.slug, - "| active:", (ev as any).active, "| closed:", (ev as any).closed, - "| startDate:", rawStartDate, - "| gameStartTime:", rawGameStartTime, - "| mkGameStartTime:", marketGameStartTime, - "| endDate:", rawEndDate); + const startTime = marketGameStartTime ?? (ev as any).gameStartTime ?? (ev as any).endDate ?? (ev as any).startDate; parsed.push({ slug: ev.slug, homeCode, awayCode, homeName, awayName, pHome, pDraw, pAway, startTime }); } } - console.log("[PM-RAW]", new Date().toISOString(), "| events gesamt:", allEvents.length); - console.log("[PM-LOAD]", new Date().toISOString(), "| moneyline-events:", parsed.length, "| events gesamt:", allEvents.length); console.log("[polymarket] spiele:", parsed.length); return parsed; }); @@ -376,17 +365,6 @@ export function attachOdds(matches: Match[], teams: Team[], odds: ParsedOdds[]): oddsMatch.set(o, { homeId, awayId }); } else { oddsMatch.set(o, null); - const feedMatchesWithTheseTeams = matches.filter(m => - m.homeTeamId && m.awayTeamId && - ((m.homeTeamId === homeId && m.awayTeamId === awayId) || - (m.homeTeamId === awayId && m.awayTeamId === homeId)), - ); - console.log("[PM-NOMATCH]", o.homeName, "vs", o.awayName, "| slug:", o.slug); - console.log("[PM-NOMATCH-WHY]", o.homeName, "vs", o.awayName, - "| homeId gefunden:", !!homeId, - "| awayId gefunden:", !!awayId, - "| homeCode:", o.homeCode, "| awayCode:", o.awayCode, - "| feed-matches mit diesen teams:", feedMatchesWithTheseTeams.length); } } @@ -402,10 +380,6 @@ export function attachOdds(matches: Match[], teams: Team[], odds: ParsedOdds[]): (ids.homeId === m.awayTeamId && ids.awayId === m.homeTeamId)) { const swapped = ids.homeId === m.awayTeamId; attachedOdds.add(o); - console.log("[PM-ATTACH]", o.homeName, "vs", o.awayName, - "| match gefunden:", true, - "| match.status:", m.status, - "| prob gesetzt:", true); return { ...m, prob: { @@ -419,22 +393,6 @@ export function attachOdds(matches: Match[], teams: Team[], odds: ParsedOdds[]): return m; }); - for (const [o, ids] of oddsMatch) { - if (ids && !attachedOdds.has(o)) { - const matchingFeedMatch = matches.find(m => - m.homeTeamId && m.awayTeamId && - ((m.homeTeamId === ids.homeId && m.awayTeamId === ids.awayId) || - (m.homeTeamId === ids.awayId && m.awayTeamId === ids.homeId)), - ); - console.log("[PM-NOMATCH]", o.homeName, "vs", o.awayName, "| slug:", o.slug); - console.log("[PM-NOMATCH-WHY]", o.homeName, "vs", o.awayName, - "| team-ids gematcht:", ids.homeId, "vs", ids.awayId, - "| feed-match mit diesen teams existiert:", !!matchingFeedMatch, - "| feed-match status:", matchingFeedMatch?.status ?? "—", - "| prob bereits gesetzt:", matchingFeedMatch?.prob ? "ja" : "nein"); - } - } - return result; } @@ -484,13 +442,6 @@ function resolveR32Pairings(matches: Match[], teams: Team[]): Mapt.id===p.homeTeamId)?.name ?? p.homeTeamId; - const a = teams.find(t=>t.id===p.awayTeamId)?.name ?? p.awayTeamId; - console.log(" [R32-PAIR]", "matchNumber:", num, "|", h, "vs", a); - } - return pairings; } @@ -515,10 +466,6 @@ function slugToDate(slug: string): string | null { // Slug (Stufe 2) — KEINE chronologische Nummernvergabe. // Mutiert matches in-place, setzt nur bei Matches OHNE bestehende prob. export function attachKOOdds(matches: Match[], teams: Team[], odds: ParsedOdds[]): Match[] { - const koOhneProb = matches.filter(m => m.group == null && m.prob == null).length; - console.log("[KO-ODDS] start | odds-events:", odds?.length ?? "undefined", - "| ko-matches ohne prob:", koOhneProb); - const pairings = resolveR32Pairings(matches, teams); if (pairings.size === 0) return matches; @@ -529,14 +476,9 @@ export function attachKOOdds(matches: Match[], teams: Team[], odds: ParsedOdds[] } const idByCode = new Map(teams.map((t) => [t.code.toLowerCase(), t.id])); - console.log("[KO-LOOP] start | pairings:", pairings.size, "| odds-events:", odds?.length ?? "undef"); - for (const [matchNum, pairing] of pairings) { if (!pairing.homeTeamId || !pairing.awayTeamId) continue; - const hName = teams.find(t => t.id === pairing.homeTeamId)?.name ?? "?"; - const aName = teams.find(t => t.id === pairing.awayTeamId)?.name ?? "?"; - // Finde Polymarket-Event für dieses Team-Paar let matchedEvent: ParsedOdds | null = null; for (const o of odds) { @@ -552,24 +494,15 @@ export function attachKOOdds(matches: Match[], teams: Team[], odds: ParsedOdds[] } } - if (!matchedEvent) { - console.log("[KO-TRY]", "matchNumber:", matchNum, - "| teams:", `${hName} vs ${aName}`, - "| polymarket-event gefunden:", false, - "| ziel-feedmatch gefunden:", false, - "| stufe:", "keine"); - continue; - } + if (!matchedEvent) continue; // Ziel-Feed-Match identifizieren let targetMatch: Match | undefined; - let stufe: string | undefined; // Stufe 1: bereits nummeriertes R32-Match (via assignKONumbersBySlots Pass 1) targetMatch = matches.find(m => m.stage === "R32" && m.group == null && m.matchNumber === matchNum, ); - if (targetMatch) stufe = "1 (nummeriert)"; // Stufe 2: Datums-Lookup aus Polymarket-Slug für unnummerierte Matches if (!targetMatch) { @@ -583,7 +516,6 @@ export function attachKOOdds(matches: Match[], teams: Team[], odds: ParsedOdds[] if (candidates.length === 1) { targetMatch = candidates[0]; targetMatch.matchNumber = matchNum; - stufe = "2 (datum)"; } else if (candidates.length > 1) { // Tie-breaker: falls Polymarket-Event eine Startzeit hat, nächstgelegenes Feed-Match if (matchedEvent.startTime) { @@ -598,48 +530,16 @@ export function attachKOOdds(matches: Match[], teams: Team[], odds: ParsedOdds[] if (best && bestDiff < 4 * 60 * 60 * 1000) { targetMatch = best; targetMatch.matchNumber = matchNum; - stufe = "2 (datum+tie)"; } } - console.log("[KO-TIME]", "slot:", matchNum, - "| teams:", `${hName} vs ${aName}`, - "| pm-gameStartTime(UTC):", matchedEvent.startTime, - "| feed-kandidaten:", candidates.map(c => - `${c.id}(${c.utcDate})`, - ).join(", "), - "| gewählt:", targetMatch?.id ?? "keins"); - if (!targetMatch) { - console.log("[KO-TIE]", "matchNumber:", matchNum, - "| slug:", matchedEvent.slug, - "| slugDate:", slugDate, - "| startTime:", matchedEvent.startTime, - "| candidates:", candidates.map(c => - `${c.id}(${c.utcDate})`, - ).join(", ")); - } } } } - if (!targetMatch) { - console.log("[KO-TRY]", "matchNumber:", matchNum, - "| teams:", `${hName} vs ${aName}`, - "| polymarket-event gefunden:", true, - "| ziel-feedmatch gefunden:", false, - "| stufe:", stufe ?? "keine"); - continue; - } + if (!targetMatch) continue; // prob nicht überschreiben, falls schon gesetzt - if (targetMatch.prob) { - console.log("[KO-TRY]", "matchNumber:", matchNum, - "| teams:", `${hName} vs ${aName}`, - "| polymarket-event gefunden:", true, - "| ziel-feedmatch gefunden:", true, - "| stufe:", stufe, - "| prob bereits gesetzt:", true); - continue; - } + if (targetMatch.prob) continue; const homeIdFromEvent = idByName.get(normName(matchedEvent.homeName)) ?? idByCode.get(matchedEvent.homeCode); @@ -650,17 +550,6 @@ export function attachKOOdds(matches: Match[], teams: Team[], odds: ParsedOdds[] draw: matchedEvent.pDraw, away: swapped ? matchedEvent.pHome : matchedEvent.pAway, }; - - console.log("[PM-ATTACH]", matchedEvent.homeName, "vs", matchedEvent.awayName, - "| match gefunden via R32:", true, - "| match.status:", targetMatch.status, - "| matchNumber:", targetMatch.matchNumber, - "| prob gesetzt:", true); - console.log("[KO-TRY]", "matchNumber:", matchNum, - "| teams:", `${hName} vs ${aName}`, - "| polymarket-event gefunden:", !!matchedEvent, - "| ziel-feedmatch gefunden:", !!targetMatch, - "| stufe:", stufe); } return matches; @@ -696,7 +585,7 @@ export async function fetchLiveScores(): Promise { const res = await fetch(`${WC26_BASE}/get/games`, { cache: "no-store", headers: { "User-Agent": "wm2026-board/1.0" }, - signal: AbortSignal.timeout(8000), + signal: AbortSignal.timeout(5000), }); if (!res.ok) throw new Error(`worldcup26 ${res.status}`); const data = (await res.json()) as { games: Wc26Game[] }; diff --git a/lib/third-place-security.ts b/lib/third-place-security.ts index 6d2f7ab..ec0be41 100644 --- a/lib/third-place-security.ts +++ b/lib/third-place-security.ts @@ -132,13 +132,8 @@ function classifyGroups(states: Map): { // einer FERTIGEN Gruppe (played === 3), ob es in ALLEN noch möglichen // Restspiel-Konstellationen von maximal 7 anderen Dritten überholt werden kann. export function securelyQualifiedThirdTeams(matches: Match[], teams: Team[]): Set { - console.log("[3RD-DISPLAY] ThirdPlace ✓-haekchen = securelyQualifiedThirdTeams (team-level, zaehlt GD)", - "| ThirdPlace GRUENER name = CSS .team-complete (played===3, KEIN Sicherheitsindikator)", - "| Bracket fix = thirdSlotIsSecure -> classifyGroups -> lockedIn (group-level, NUR Punkte, KEIN GD)"); - console.log("[3RD-SOURCE] securelyQualifiedThirdTeams aufgerufen (teams)"); const states = buildThirdStates(matches, teams); const secureTeams = new Set(); - const { lockedIn } = classifyGroups(states); for (const g of GROUP_IDS) { const st = states.get(g)!; @@ -159,30 +154,7 @@ export function securelyQualifiedThirdTeams(matches: Match[], teams: Team[]): Se } } - const team = teams.find(t => t.id === my.teamId); - const isSecure = couldBeBetter <= 7; - if (isSecure) secureTeams.add(my.teamId); - - // Diagnose-Log für Iran und Grenzfälle - const targetTeams = new Set(["IRN", "KOR", "ALG", "CRO"]); // Team-Codes - if (team && targetTeams.has(team.code)) { - console.log("[3RD-TEAM-SECURE]", - "team:", team.name, "| group:", g, - "| punkte:", my.points, "| gd:", my.goalDiff, - "| couldBeBetter:", couldBeBetter, - "| gruppe lockedIn:", lockedIn.has(g), - "| als sicher markiert:", isSecure); - } - } - - const iranTeam = teams.find(t => t.code === "IRN"); - if (iranTeam) { - console.log("[3RD-IRAN-DISPLAY]", - "secureTeams enthaelt Iran:", secureTeams.has(iranTeam.id), - "| secureGroups (lockedIn) enthaelt G:", lockedIn.has("G"), - "| haekchen gezeigt:", secureTeams.has(iranTeam.id) ? "ja" : "nein", - "| name gruen (CSS team-complete, played===3):", "immer bei finished gruppe", - "| im baum fix:", lockedIn.has("G") ? "ja (thirdSlotIsSecure via classifyGroups/POINTS)" : "nein"); + if (couldBeBetter <= 7) secureTeams.add(my.teamId); } return secureTeams; @@ -191,19 +163,14 @@ export function securelyQualifiedThirdTeams(matches: Match[], teams: Team[]): Se // Liefert die Gruppen, deren Dritter mathematisch sicher unter den Top 8 ist. // (Gruppen-Ebene — für Fix-Markierung im Baum, nicht für Team-Häkchen.) export function securelyQualifiedThirdGroups(matches: Match[], teams: Team[]): GroupId[] { - console.log("[3RD-SOURCE] securelyQualifiedThirdGroups aufgerufen (groups)"); const states = buildThirdStates(matches, teams); - const { lockedIn, lockedOut, contested } = classifyGroups(states); - console.log("[3RD-CLASSIFY] lockedIn:", [...lockedIn].join(","), - "| lockedOut:", [...lockedOut].join(","), - "| contested:", [...contested].join(",")); + const { lockedIn } = classifyGroups(states); return [...lockedIn]; } // Prüft, ob ein bestimmter Dritten-Slot (Gegner des Siegers von `winnerGroup`) // in ALLEN noch möglichen Konstellationen identisch bleibt. export function thirdSlotIsSecure(winnerGroup: GroupId, matches: Match[], teams: Team[]): boolean { - console.log("[3RD-SOURCE] thirdSlotIsSecure aufgerufen (slot-fix via classifyGroups)"); const states = buildThirdStates(matches, teams); const { lockedIn, contested } = classifyGroups(states);