제 5 장
예측·랭킹 기능 개발
1) PLE 이벤트와 라이브
대진표의 원본은 DB가 아니다
대진의 정본은 프론트 픽스처(www/lib/wwe-ple-matches.ts)다. 이 방향이 뒤집히면 데이터가 조용히 사라지기 때문이다 — 대진표 컴포넌트는 화면을 열 때 경기 id 집합을 DB와 비교해서 다르면 정적 카드를 동기화 API로 밀어 넣는데, 그 API는 페이로드에 없는 경기를 지운다. DB에만 경기를 더하면 다음 방문자가 페이지를 여는 순간 되돌아가고, 그 경기에 달린 예측이 CASCADE로 함께 사라진다.
그래서 대진이 살아남는 경로는 하나다.
위키 → [동기화 도구] → wwe-ple-matches.ts → 사람이 diff 확인 → 커밋
→ Vercel 배포 → 방문자 접속 → sync-from-client → DB
커밋과 배포는 사람이 한다. 위키에서 대진을 읽는 도구는 파일을 고치는 데까지만 가고, 기본이 드라이런이다.
이 구조에서 id 보존이 핵심이다. 경기 id(mitb26-whc)는 사람이 지은 의미 슬러그이고 DB 행과 사용자 예측이 거기 걸려 있다. 접두사에는 규칙이 없다 — 실측하면 wm42(연도가 아니라 회차) · sad26 · italy26 · bl26처럼 제각각이다. 그래서 도구는 접두사를 슬러그에서 유도하지 않고 기존 id에서 읽고, 이미 있는 경기의 id는 글자 하나 바꾸지 않는다. 새 경기에만 id를 제안하며 그 제안은 사람이 고칠 수 있는 초안이다.
라이브
진행 중인 대회는 서버가 밀어 준다. text/event-stream으로 여는 SSE 엔드포인트가 3초 간격으로 보드 전체를 직렬화해 보내고, 프론트는 EventSource로 받는다. 폴링과 달리 연결이 유지되므로 투표 수가 올라가는 것이 화면에 그대로 반영된다.
2) 경기 예측 제출
픽은 경기 단위로 제출되고 제출 즉시 잠긴다(locked = 내 픽이 있는가). 사후 수정이 가능하면 순위가 의미를 잃기 때문이다. 결과가 들어오면 채점은 자동으로 붙는다 — 사용자가 다시 무엇을 할 필요가 없다.
대진이 아직 확정되지 않은 자리는 미정으로 표시한다. 6인 래더의 빈 칸 하나가 그런 자리이고, 참가자가 발표되면 동기화가 채운다. 확정되지 않은 자리에 이름을 지어 넣지 않는다.
3) 랭킹 산출
배점
경기 형식과 타이틀 여부로 점수가 정해진다.
| 형식 | 점수 |
|---|---|
| 싱글 · 태그 | 10 |
| 트리플 스렛 | 20 |
| 페이탈 4웨이 | 25 |
| 엘리미네이션 체임버 · MITB 래더 | 30 |
| 로열럼블 | 50 |
| 타이틀 가산 | +5 |
순수 역배당을 쓰지 않았다. 맞힐 확률의 역수를 그대로 점수로 삼으면(점수 = 참가자 수) 로열럼블 한 경기가 싱글 15경기와 맞먹어 운이 실력을 덮는다. 그래서 log2(참가자 수)로 꼬리를 눌러 럼블을 싱글의 5배로 제한했다.
타이틀 가산이 형식과 독립인 이유는 그것이 난이도가 아니라 스테이크에 대한 보너스이기 때문이다. 챔피언십 트리플 스렛은 20+5=25가 된다.
값이 전부 5의 배수인 것은 상점 가격과 보너스를 소수점 없이 다루기 위해서다. 채점 결과는 포인트 원장(point_ledger_entries)에 쌓이고 상점에서 쓰인다.
채점
픽이 방송 승자와 일치하면 적중이고, 결과가 아직 없으면 미채점(None)이다. 0으로 접지 않는다 — 틀린 것과 아직 모르는 것은 다른 상태이고, 둘을 합치면 순위가 결과 입력 속도를 따라 흔들린다.
4) 기록과 타이틀 히스토리
선수 전적
전적은 ple_matches.card_json을 집계해서 만든다. 규칙은 records_scoring 한 모듈에만 있고, 데이터 센터도 같은 것을 부른다 — 화면마다 다른 규칙으로 세면 같은 선수의 승률이 두 개가 된다.
그 규칙이 실제로 하는 일은 셋이다. 태그팀·WarGames의 팀 이름을 개인으로 펼치고, winner_pick에서 승자를 되짚고, 승자를 정할 수 없으면 무효로 접는다.
승률 순위에는 3경기 이상만 오른다. 명부에는 한 경기만 뛴 선수가 수두룩해서, 그들을 섞으면 순위표 위쪽이 전부 1승 0패 100%가 되어 순위가 아무것도 말하지 않는다. 화면은 그 조건(“3경기 이상”)을 함께 적는다.
타이틀 히스토리
챔피언 이력은 PostgreSQL에 있다(championship_titles · title_acquisitions). 저장소에 Neo4j가 있지만 이 축은 쓰지 않는다 — 그래프 DB는 admin 앱의 PDF·LangChain 검색 경로에 붙어 있다.
보드를 고치는 재료는 둘이고 범위가 다르다.
| 경로 | 재료 | 성격 |
|---|---|---|
apply_results |
PLE 경기 결과 | 좁고 빠르다 — 결과가 들어오면 즉시 따라온다 |
plan_wiki_sync |
위키 현 챔피언 표 | 넓고 늦다 — Raw·하우스쇼까지 메운다 |
둘의 순서가 중요하다. 위키 동기화가 기준일을 읽은 날로 올리므로 그 이전 PLE는 결과 반영의 기준일 관문이 건너뛴다 — 위키가 이미 그 결과를 반영한 표를 주기 때문이다. 같은 변경을 두 번 얹어 방어를 새 획득으로 둔갑시키는 일이 그래서 없다.
동기화가 하지 않는 일이 규칙의 절반이다.
- 챔피언·획득일·팀 이름이 같으면 그 줄은 손대지 않는다. 표기가 조금 달라졌다고 갈아 끼우면 보드 순서와 획득 대회가 이유 없이 흔들린다.
- 위키에 없는 벨트를 지우지 않는다. 폐지인지 문서 누락인지 표가 말해 주지 않으므로 “위키에 없음”으로 알리고 사람이 정한다. 실제로 Speed 벨트 둘이 그렇게 걸려 폐지로 확인된 뒤 카탈로그에서 빠졌다(2026-09-29).
- 보드에 없는 위키 행도 추가하지 않는다.