Implement scoreboard system with rank snapshots and delta tracking.
- 전체 및 팀별 스코어보드 기능을 구현하고 순위 변동 시각화를 위한 스냅샷 시스템을 도입했습니다. - `dailyArchive` 시점에 `rankSnapshot`을 기록하여 이전 대비 순위 변화량을 정확히 산출합니다. - 상위권 데이터를 RTDB에 사전 계산하여 저장하고 `MemCache`를 적용해 조회 성능을 최적화했습니다. - Firestore 복합 색인과 Count Aggregation으로 동점자 처리 및 상위 퍼센타일 산출 로직을 구현했습니다. - 주간 결과 타입을 `DailyJudgment`로 전환하여 판정 세부 상태가 통계 응답에 포함되도록 수정했습니다.
This commit is contained in:
parent
b25e78dde8
commit
360530ad13
@ -4,7 +4,7 @@ import {computeLevel} from "../constants/levels.js";
|
||||
import {tierOf} from "../constants/tiers.js";
|
||||
import {getAll, getDay} from "../repositories/voteHistoryRepository.js";
|
||||
import {getUser} from "../repositories/userRepository.js";
|
||||
import type {StatsResponse, VoteHistoryDoc} from "../types/panit.js";
|
||||
import type {DailyJudgment, StatsResponse, VoteHistoryDoc} from "../types/panit.js";
|
||||
import {
|
||||
parseDateString,
|
||||
toDateString,
|
||||
@ -124,16 +124,14 @@ function computeStreak(entries: Array<{ date: DateString; doc: VoteHistoryDoc }>
|
||||
}
|
||||
|
||||
/**
|
||||
* 이번 주(화~월) 7일간의 일별 예측 결과를 배열로 반환한다.
|
||||
*
|
||||
* - `true` — 하나 이상 적중
|
||||
* - `false` — 전부 오답
|
||||
* - `null` — 예측 없음 또는 결과 미확정
|
||||
* 이번 주(화~월) 7일간의 일별 판정 결과를 배열로 반환한다.
|
||||
* 저장된 `VoteHistoryDoc.judgment`(perfect/success/fail/skip)을 그대로 노출하며,
|
||||
* 예측 이력이 없거나 판정 전이면 `null`을 반환한다.
|
||||
*
|
||||
* @param entries - 전체 투표 이력
|
||||
* @returns 화요일부터 월요일 순서의 7개 원소 배열
|
||||
*/
|
||||
function weeklyResultsOf(entries: Array<{ date: DateString; doc: VoteHistoryDoc }>): Array<boolean | null> {
|
||||
function weeklyResultsOf(entries: Array<{ date: DateString; doc: VoteHistoryDoc }>): Array<DailyJudgment | null> {
|
||||
const byDate = new Map(entries.map((e) => [e.date, e.doc] as const));
|
||||
const today = todayKst();
|
||||
const todayMs = startOfDayKst(today).getTime();
|
||||
@ -146,17 +144,11 @@ function weeklyResultsOf(entries: Array<{ date: DateString; doc: VoteHistoryDoc
|
||||
|
||||
const tuesdayMs = todayMs - offset * 86400000;
|
||||
|
||||
const results: Array<boolean | null> = [];
|
||||
const results: Array<DailyJudgment | null> = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const key = toDateString(new Date(tuesdayMs + i * 86400000));
|
||||
const doc = byDate.get(key);
|
||||
if (!doc || doc.data.length === 0) {
|
||||
results.push(null);
|
||||
} else {
|
||||
const anyCorrect = doc.data.some((v) => v.result === true);
|
||||
const allWrong = doc.data.every((v) => v.result === false);
|
||||
results.push(anyCorrect ? true : allWrong ? false : null);
|
||||
}
|
||||
results.push(doc?.judgment ?? null);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ export interface VoteHistoryDoc {
|
||||
export interface StatsResponse {
|
||||
streakDays: number;
|
||||
highestStreak: number;
|
||||
weeklyResults: Array<boolean | null>;
|
||||
weeklyResults: Array<DailyJudgment | null>;
|
||||
winRates: {
|
||||
overall: number;
|
||||
weekly: number;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user