Merge live game data into KBO schedule results for today.
- KBO 일정 조회 시 오늘 날짜의 경기에 대해 실시간 점수와 경기 상태를 병합하는 기능을 추가했습니다. - `GameStatus` 타입에 'live'를 추가하고, 게임센터 API 데이터를 기반으로 상태를 변환하는 로직을 구현했습니다. - 외부 API 연동 시 발생할 수 있는 일시적인 오류(503 등)에 대비하여 예외 발생 시 기존 캐시 데이터를 반환하도록 예외 처리를 적용했습니다. - 경기 ID 또는 팀 정보와 시간을 조합한 복합 키를 사용하여 스케줄 데이터와 실시간 데이터를 정확하게 매칭합니다.
This commit is contained in:
parent
98502916de
commit
a7ce2799ef
@ -14,7 +14,7 @@ const USER_AGENT =
|
||||
|
||||
// ── Types ──
|
||||
|
||||
export type GameStatus = "completed" | "cancelled" | "scheduled";
|
||||
export type GameStatus = "completed" | "cancelled" | "scheduled" | "live";
|
||||
|
||||
export interface ScheduleGame {
|
||||
date: string;
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
type ScheduleFilters,
|
||||
type ScheduleResult,
|
||||
type ScheduleGame,
|
||||
type GameStatus,
|
||||
} from "../kbo/schedule.js";
|
||||
import {
|
||||
fetchPlayerStatsInitial,
|
||||
@ -25,6 +26,8 @@ import {
|
||||
releaseLock,
|
||||
} from "./kboCacheRepository.js";
|
||||
import { firestore } from "../firebase.js";
|
||||
import { getGameList } from "../services/gameListService.js";
|
||||
import type { GameListRecord } from "../kbo/game-list.js";
|
||||
|
||||
const TTL_MS = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
@ -164,6 +167,67 @@ function dayTtlMs(yyyymmdd: string, games: ScheduleGame[]): number {
|
||||
return THIRTY_SEC_MS;
|
||||
}
|
||||
|
||||
function statusFromRecord(rec: GameListRecord): GameStatus | null {
|
||||
// cancelCode: "0" = 정상경기, 그 외 = 취소/노게임
|
||||
if (rec.status.cancelCode && rec.status.cancelCode !== "0") return "cancelled";
|
||||
// stateCode: "1" = 예정, "2" = 진행 중, "3" = 종료
|
||||
switch (rec.status.stateCode) {
|
||||
case "1": return "scheduled";
|
||||
case "2": return "live";
|
||||
case "3": return "completed";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
function liveKey(
|
||||
away: string,
|
||||
home: string,
|
||||
time: string
|
||||
): string {
|
||||
return `${away}|${home}|${time}`;
|
||||
}
|
||||
|
||||
async function mergeLiveIntoSchedule(
|
||||
ymd: string,
|
||||
games: ScheduleGame[],
|
||||
series: string | undefined
|
||||
): Promise<ScheduleGame[]> {
|
||||
const today = formatYmd(new Date());
|
||||
if (ymd !== today) return games;
|
||||
|
||||
let live: GameListRecord[];
|
||||
try {
|
||||
const result = await getGameList(ymd, series);
|
||||
live = result.games;
|
||||
} catch {
|
||||
return games;
|
||||
}
|
||||
|
||||
const byId = new Map<string, GameListRecord>();
|
||||
const byComposite = new Map<string, GameListRecord>();
|
||||
for (const rec of live) {
|
||||
if (rec.gameId) byId.set(rec.gameId, rec);
|
||||
byComposite.set(
|
||||
liveKey(rec.awayTeamCode, rec.homeTeamCode, rec.time),
|
||||
rec
|
||||
);
|
||||
}
|
||||
|
||||
return games.map((g) => {
|
||||
const rec =
|
||||
(g.gameId && byId.get(g.gameId)) ||
|
||||
byComposite.get(liveKey(g.awayTeamCode, g.homeTeamCode, g.time));
|
||||
if (!rec) return g;
|
||||
|
||||
const merged: ScheduleGame = { ...g };
|
||||
if (rec.score.away != null) merged.awayScore = rec.score.away;
|
||||
if (rec.score.home != null) merged.homeScore = rec.score.home;
|
||||
const nextStatus = statusFromRecord(rec);
|
||||
if (nextStatus) merged.status = nextStatus;
|
||||
return merged;
|
||||
});
|
||||
}
|
||||
|
||||
async function readDayDocs(keys: string[]): Promise<Map<string, ScheduleGame[] | null>> {
|
||||
if (keys.length === 0) return new Map();
|
||||
const refs = keys.map((k) => firestore.collection(CACHE_COLLECTION).doc(k));
|
||||
@ -224,11 +288,13 @@ async function fetchScheduleSingleDay(
|
||||
cached = (await readDayDocs([key])).get(key) ?? [];
|
||||
}
|
||||
|
||||
const merged = await mergeLiveIntoSchedule(ymd, cached, filters.series);
|
||||
|
||||
return {
|
||||
year: filters.year,
|
||||
month: filters.month,
|
||||
day: filters.day,
|
||||
games: cached,
|
||||
games: merged,
|
||||
};
|
||||
}
|
||||
|
||||
@ -293,9 +359,15 @@ async function fetchScheduleMonth(
|
||||
}
|
||||
|
||||
const games: ScheduleGame[] = [];
|
||||
const today = formatYmd(new Date());
|
||||
for (const ymd of allDays) {
|
||||
const list = cached.get(dayKey(ymd, filters.team, filters.series)) ?? [];
|
||||
games.push(...list);
|
||||
if (ymd === today) {
|
||||
const merged = await mergeLiveIntoSchedule(ymd, list, filters.series);
|
||||
games.push(...merged);
|
||||
} else {
|
||||
games.push(...list);
|
||||
}
|
||||
}
|
||||
|
||||
return { year: filters.year, month: filters.month, games };
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user