57 lines
2.6 KiB
TypeScript
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);
|
|
}
|