import { NextResponse } from "next/server"; import { fetchMatchesAndTeams, fetchOdds, attachOdds, attachKOOdds, fetchLiveScores, applyLiveScores, assignKONumbersBySlots } from "@/lib/feeds"; import { computeGroupTables, computeThirdPlaceTable } from "@/lib/standings"; import { qualifiedThirdGroups, resolveAnnexC } from "@/lib/bracket"; import { securelyQualifiedThirdTeams } from "@/lib/third-place-security"; // Diese Route wird vom Frontend gepollt. Sie ist der einzige Ort, der die // Upstream-Feeds berührt — gecacht, damit Rate Limits eingehalten werden. export const dynamic = "force-dynamic"; export async function GET() { try { const { matches: rawMatches, teams } = await fetchMatchesAndTeams(); // Odds sind optional: fällt der Polymarket-Call aus, liefern wir trotzdem. let matches = rawMatches; let odds: Awaited> | null = null; try { odds = await fetchOdds(); matches = attachOdds(rawMatches, teams, odds); } catch (err) { console.error("[polymarket] fetchOdds fehlgeschlagen:", err); } // Live-Scores von worldcup26.ir (additiv, Fallback auf football-data) try { const liveScores = await fetchLiveScores(); matches = applyLiveScores(matches, teams, liveScores); } catch (err) { console.error("[worldcup26] fetchLiveScores fehlgeschlagen:", 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); } } 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); const qGroups = qualifiedThirdGroups(thirdTable); const annex = qGroups.length === 8 ? resolveAnnexC(qGroups) : null; // Sichere Drittplatzierte (Team-IDs, deren Top-8-Platz mathematisch feststeht) const secureThirdTeams = securelyQualifiedThirdTeams(matches, teams); return NextResponse.json({ updatedAt: new Date().toISOString(), teams, matches: normalizedMatches, groupTables, groupTablesLive, thirdTable, secureThirdTeams: [...secureThirdTeams], annexAssignment: annex, annexResolved: annex != null, }); } catch (err) { const message = err instanceof Error ? err.message : "unbekannter Fehler"; return NextResponse.json( { error: "Feed nicht erreichbar", detail: message }, { status: 502 }, ); } }