rm logs + caching
This commit is contained in:
@@ -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);
|
||||
|
||||
123
lib/feeds.ts
123
lib/feeds.ts
@@ -267,7 +267,7 @@ function safeParse<T>(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<ParsedOdds[]> {
|
||||
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<ParsedOdds[]> {
|
||||
}
|
||||
|
||||
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[]): Map<number, { home
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[R32-PAIRS] aufgelöste Paarungen:", pairings.size);
|
||||
for (const [num, p] of pairings) {
|
||||
const h = teams.find(t=>t.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<LiveScore[]> {
|
||||
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[] };
|
||||
|
||||
@@ -132,13 +132,8 @@ function classifyGroups(states: Map<GroupId, GroupThirdState>): {
|
||||
// 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<string> {
|
||||
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<string>();
|
||||
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user