Handle tie games in KBO schedule parsing and result processing.
- KBO 경기 결과 파싱 시 무승부(`draw`) 상황의 점수도 추출할 수 있도록 정규표현식을 개선하여 경기 상태가 'scheduled'로 오분류되는 문제를 해결했습니다. - 승리 팀 코드(`winningTeamCode`)가 없는 무승부 경기에서도 결과 처리가 정상적으로 수행되도록 서비스 레이어의 예외 처리를 수정했습니다. - 무승부 시 어느 팀에 투표했더라도 모두 정답(`result: true`)으로 처리되도록 투표 결과 정산 및 자가 치유(reconcile) 로직을 업데이트했습니다.
This commit is contained in:
parent
c94ce69e4f
commit
1a5f6625c0
@ -131,12 +131,14 @@ function parsePlayCell(html: string): {
|
|||||||
const homeTeamCode = toTeamCode(teamMatch[3]);
|
const homeTeamCode = toTeamCode(teamMatch[3]);
|
||||||
const emContent = teamMatch[2];
|
const emContent = teamMatch[2];
|
||||||
|
|
||||||
// Check for scores inside <em>
|
// Check for scores inside <em>. 무승부 시 KBO는 `class="draw"`(또는 win/lose가 아닌 다른 값)을
|
||||||
const scoreRegex = /<span class="(win|lose)">(\d+)<\/span>/g;
|
// 쓰므로 win/lose만 매칭하면 점수가 0건으로 잡혀 scheduled로 분류된다. 클래스 값과 무관하게
|
||||||
const scores: { cls: string; val: number }[] = [];
|
// span 안의 숫자만 추출한다.
|
||||||
|
const scoreRegex = /<span class="[^"]*">(\d+)<\/span>/g;
|
||||||
|
const scores: number[] = [];
|
||||||
let sm: RegExpExecArray | null;
|
let sm: RegExpExecArray | null;
|
||||||
while ((sm = scoreRegex.exec(emContent)) !== null) {
|
while ((sm = scoreRegex.exec(emContent)) !== null) {
|
||||||
scores.push({ cls: sm[1], val: parseInt(sm[2], 10) });
|
scores.push(parseInt(sm[1], 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scores.length === 2) {
|
if (scores.length === 2) {
|
||||||
@ -144,8 +146,8 @@ function parsePlayCell(html: string): {
|
|||||||
return {
|
return {
|
||||||
awayTeamCode,
|
awayTeamCode,
|
||||||
homeTeamCode,
|
homeTeamCode,
|
||||||
awayScore: scores[0].val,
|
awayScore: scores[0],
|
||||||
homeScore: scores[1].val,
|
homeScore: scores[1],
|
||||||
status: "completed",
|
status: "completed",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,12 +50,15 @@ async function reconcileDayVotes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 트리거가 실패/유실되었거나 배포 전 상태 전이가 일어난 경우의 자가치유 경로.
|
// 트리거가 실패/유실되었거나 배포 전 상태 전이가 일어난 경우의 자가치유 경로.
|
||||||
if (game.status === "completed" && game.winningTeamCode) {
|
// `winningTeamCode`가 없으면 무승부 — `processGameEndWithGame`이 양 팀 투표 모두
|
||||||
|
// result=true로 처리한다.
|
||||||
|
if (game.status === "completed") {
|
||||||
try {
|
try {
|
||||||
await processGameEndWithGame(gameId, game);
|
await processGameEndWithGame(gameId, game);
|
||||||
|
const isDraw = !game.winningTeamCode;
|
||||||
result[gameId] = {
|
result[gameId] = {
|
||||||
team: vote.team,
|
team: vote.team,
|
||||||
result: vote.team === game.winningTeamCode,
|
result: isDraw ? true : vote.team === game.winningTeamCode,
|
||||||
};
|
};
|
||||||
logger.info(`reconcile: judged ${gameId} on the fly (uid=${uid})`);
|
logger.info(`reconcile: judged ${gameId} on the fly (uid=${uid})`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -18,17 +18,18 @@ export async function processGameEndWithGame(
|
|||||||
gameId: string,
|
gameId: string,
|
||||||
game: Game
|
game: Game
|
||||||
): Promise<{ processed: number }> {
|
): Promise<{ processed: number }> {
|
||||||
if (!game.winningTeamCode) throw new HttpError(400, "winningTeamCode not set");
|
|
||||||
if (game.status !== "completed") throw new HttpError(409, `game status must be completed, got ${game.status}`);
|
if (game.status !== "completed") throw new HttpError(409, `game status must be completed, got ${game.status}`);
|
||||||
|
|
||||||
const date = fromTimestamp(game.time);
|
const date = fromTimestamp(game.time);
|
||||||
const votes = await getAllUserVotes(gameId);
|
const votes = await getAllUserVotes(gameId);
|
||||||
const uids = Object.keys(votes);
|
const uids = Object.keys(votes);
|
||||||
|
|
||||||
|
// 무승부(`winningTeamCode` 없음): 양 팀 투표 모두 적중(`result: true`)으로 처리.
|
||||||
|
const isDraw = !game.winningTeamCode;
|
||||||
const rtdbUpdates: Record<string, unknown> = {};
|
const rtdbUpdates: Record<string, unknown> = {};
|
||||||
for (const uid of uids) {
|
for (const uid of uids) {
|
||||||
const voted = votes[uid].team;
|
const voted = votes[uid].team;
|
||||||
const result = voted === game.winningTeamCode;
|
const result = isDraw ? true : voted === game.winningTeamCode;
|
||||||
rtdbUpdates[`/userVotes/${uid}/${date}/${gameId}/team`] = voted;
|
rtdbUpdates[`/userVotes/${uid}/${date}/${gameId}/team`] = voted;
|
||||||
rtdbUpdates[`/userVotes/${uid}/${date}/${gameId}/result`] = result;
|
rtdbUpdates[`/userVotes/${uid}/${date}/${gameId}/result`] = result;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user