o2o-triple-pick/lib/leaderboard.ts

57 lines
2.6 KiB
TypeScript

// 누적 랭킹 — 투표 결과가 반영된 포인트 축적 TOP 10
// ─────────────────────────────────────────────────────────────
// UI(Leaderboard.tsx)는 fetchLeaderboard 의 "시그니처(입출력 타입)"에만 의존한다.
// 백엔드 개발자는 fetchLeaderboard 의 *본문만* 콜러블 함수(getLeaderboard)
// 호출로 교체하면 UI 수정 불필요.
// const { fns } = getFirebase();
// const res = await httpsCallable(fns, "getLeaderboard")({ tab });
// return res.data as LeaderEntry[]; // 누적 포인트 내림차순, 최대 10
//
// 포인트 산정(서버 책임): 종료 경기마다 예측 정확도로 가점 → 누적.
// PII: 참가자 label 은 *마스킹된 이메일 아이디*만 반환할 것(전체 이메일 금지).
export type LeaderTab = "voters" | "ai";
/** 랭킹 1행 — 표시용 2개 항목(이름/아이디 + 누적 포인트) */
export type LeaderEntry = {
label: string; // 참가자: 마스킹된 이메일 아이디 / AI: 모델명(GPT·Claude·Gemini)
points: number; // 누적 포인트
};
/** 공개 노출용 이메일 아이디 마스킹: "haewonkam@x.com" → "hae****m" */
export function maskEmailId(email: string): string {
const id = (email.split("@")[0] || email).trim();
if (id.length <= 2) return id[0] + "*";
if (id.length <= 4) return id.slice(0, 2) + "*".repeat(id.length - 2);
return id.slice(0, 3) + "*".repeat(Math.min(4, id.length - 4)) + id.slice(-1);
}
// ── 데모 목업 (결정론적) — 운영에서는 getLeaderboard 콜러블로 대체 ──
const DEMO_VOTERS: LeaderEntry[] = [
{ label: "cha***n", points: 1840 },
{ label: "jun***0", points: 1715 },
{ label: "min***k", points: 1660 },
{ label: "soo***2", points: 1590 },
{ label: "hae***i", points: 1505 },
{ label: "yul***7", points: 1430 },
{ label: "dae***n", points: 1388 },
{ label: "seo***3", points: 1322 },
{ label: "par***k", points: 1260 },
{ label: "won***9", points: 1195 },
];
const DEMO_AI: LeaderEntry[] = [
{ label: "Gemini", points: 1420 },
{ label: "GPT", points: 1305 },
{ label: "Claude", points: 1180 },
];
/* ============================================================
* 백엔드 연동 지점 — 누적 랭킹 TOP 10 조회
* 개발자: 본문을 getLeaderboard 콜러블 호출로 교체.
* ============================================================ */
export async function fetchLeaderboard(tab: LeaderTab): Promise<LeaderEntry[]> {
const list = tab === "ai" ? DEMO_AI : DEMO_VOTERS;
return [...list].sort((a, b) => b.points - a.points).slice(0, 10);
}