Skip to content

Repository files navigation

Ranker (Session-based Bayesian Bradley-Terry Rating System)

Ranker는 1:1 또는 3-way 대결 투표를 통해 실시간으로 순위를 산정하는 범용 랭킹 웹 애플리케이션입니다. 영화, 음식, 게임, 애니메이션 등 어떤 주제든 사용자가 원하는 항목과 평가 기준을 자유롭게 설정하여 나만의 랭킹을 만들 수 있습니다.

Online Bayesian Bradley-Terry 모델을 채택하여 각 항목의 실력과 불확실성(신뢰 구간)을 동시에 추정하고, 계층적 축소(Hierarchical Shrinkage)로 기준 간 정보를 공유합니다.

단일 SQLite DB에 세션 기반으로 데이터를 저장하여, 누구나 독립적인 환경에서 자신만의 데이터를 구축하고 백업(Export/Import)할 수 있습니다.

License Python FastAPI SQLite HTMX TailwindCSS Fly.io


✨ 주요 기능 (Key Features)

1. ⚔️ Battle Arena (다차원 동시 투표 시스템)

  • 다중 기준 동시 평가: 한 번의 대결에서 사용자가 설정한 모든 평가 기준(예: 스토리, 작화, 음악 등)을 동시에 비교하여 투표합니다. 이를 통해 점수 수렴 속도가 대폭 향상됩니다.
  • 페이지 리로드 없는 연속 배틀: HTMX 기반 partial 응답으로 투표 결과 확인 후 다음 대결이 즉시 시작됩니다. 서버가 결과 모달과 다음 배틀 카드를 동시에 반환합니다.
  • 불확실성 기반 매치메이킹: 가장 불확실한 항목을 우선 매칭하여 정보 획득을 극대화하고, 비슷한 복합 점수의 상대와 매칭하여 공정성을 유지합니다.
  • Focus Mode: 특정 항목만 고정해두고 다른 항목들과 연속으로 대결시킬 수 있습니다.

2. 🏆 Ranking Board (동적 순위표)

  • 실시간 랭킹: 투표 즉시 Bayesian BT 사후분포가 갱신되어 순위에 반영됩니다.
  • 불확실성 시각화: 각 항목의 신뢰 구간을 표시하여 순위의 신뢰도를 직관적으로 확인할 수 있습니다. 데이터가 부족한 항목에는 "불확실" 배지가 표시됩니다.
  • 가중치 기반 종합 점수: 각 평가 기준별로 가중치를 부여하여 보다 합리적인 종합 점수를 산출합니다.
  • 차트 시각화: Chart.js를 활용하여 현재 점수 분포(Distribution)를 기준별로 한눈에 파악할 수 있습니다.

3. ⚙️ Management (데이터 및 시스템 설정)

  • 항목(Items) 관리: 평가할 대상을 개별 또는 여러 줄 텍스트로 일괄(Bulk) 등록하고 수정/삭제할 수 있습니다. HTMX로 페이지 이동 없이 즉시 반영됩니다.
  • 평가 기준(Criteria) 편집: 평가할 기준의 이름, 테마 색상, 가중치를 자유롭게 추가하고 편집할 수 있습니다.
  • BT Settings: 사전분포(Prior), 무승부 확률, 계층적 축소(Hierarchical Shrinkage), 표시 스케일 등 랭킹 알고리즘의 모든 파라미터를 UI에서 직접 튜닝할 수 있습니다.
  • 데이터 백업 및 복구 (Data I/O): 현재 세션의 모든 데이터(설정, 기준, 항목)를 단일 JSON 파일로 다운로드(Export)하거나 업로드(Import)하여 이어서 진행할 수 있습니다. 이전 Elo 형식 JSON도 자동 마이그레이션됩니다.

4. 🗂️ 독립적인 멀티 유저 세션 (Multi-Session)

  • 복잡한 설정이나 회원가입 없이, 사이트 접속 시 발급되는 브라우저 쿠키를 기반으로 각 유저마다 독립된 데이터 환경을 제공합니다.

5. ♿ 접근성 & 모션 (Accessibility & Motion)

  • 통일된 Heroicons SVG 아이콘 시스템: nav/액션 아이콘을 templates/partials/_icons.html 매크로로 통일하여 플랫폼 간 일관된 렌더링과 currentColor 기반 다크모드 색상 대응을 보장합니다.
  • prefers-reduced-motion 전역 대응: OS의 "동작 줄이기" 설정을 존중하여 모션 민감 사용자에게도 안전한 경험을 제공합니다 (bounce-in, result-row-in, toast-in/out 등 모든 키프레임 적용).
  • 건너뛰기 링크 (Skip to Main): 키보드 사용자가 매 페이지 nav 를 탭하지 않고 메인 콘텐츠로 바로 이동할 수 있습니다.
  • iOS Safari 동적 뷰포트: min-h-dvh 사용으로 주소창 토글 시 레이아웃 점프를 방지합니다.
  • HTMX 스왑 영역의 aria-live / aria-busy 토글: 동적 콘텐츠 변경을 스크린리더가 실시간으로 안내합니다.

🛠 기술 스택 (Tech Stack)

  • Backend: Python 3.13, FastAPI 0.136, Uvicorn 0.46, aiosqlite, Pydantic v2
  • Data Storage: SQLite (WAL mode, database.py + store.py, 단일 DB 파일에 세션별 데이터 저장)
  • Frontend: Jinja2 Templates, HTMX 2.0.10 (self-hosted), TailwindCSS v4.2 CLI build, Chart.js 4.5 (self-hosted), Heroicons SVG 매크로
  • Tooling: uv (Python 의존성), npm (Tailwind CLI), Ruff 0.15 (lint/format), pytest 9 + pytest-asyncio
  • Deployment: Fly.io (Docker container + mounted volume, 도쿄 리전)

🚀 설치 및 실행 (Installation)

1. 클론

git clone https://github.com/RememberIOm/battle-ranker.git
cd battle-ranker

2. 개발 서버 실행

환경 변수나 DB 초기화 설정 없이 바로 실행 가능합니다. 데이터는 ranker_data Docker 볼륨에 저장됩니다.

docker compose up --build

브라우저에서 http://localhost:8080으로 접속하여 **"새로 시작"**을 클릭하면 즉시 사용할 수 있습니다.

Python 의존성 추가 시: uv add <패키지명>

Tailwind 의존성 변경 시: npm install <패키지명> --save

3. 테스트 실행

가장 간단한 검증 경로는 Docker 이미지 안에서 테스트를 실행하는 것입니다.

docker build -t ranker-test .
docker run --rm ranker-test python -m pytest tests/

📂 프로젝트 구조 (Project Structure)

.
├── main.py              # 앱 진입점 및 세션 쿠키 관리 라우터
├── database.py          # SQLite 스키마 초기화, 커넥션 관리, JSON 마이그레이션
├── deps.py              # FastAPI 의존성 (세션 ID 검증 및 Store 주입)
├── store.py             # 세션별 SQLite 데이터 저장소 (DataStore 클래스)
├── schemas.py           # Vote / Import / Response 검증 스키마
├── services.py          # 순수 비즈니스 로직 (Bayesian BT, 매치메이킹, 계층적 축소)
├── template_env.py      # 공용 Jinja2 템플릿 환경
├── routers/             # API 라우터 모듈
│   ├── battle.py        # 대결 페이지 및 투표 처리
│   ├── ranking.py       # 순위 조회 및 통계 차트
│   └── manage.py        # 데이터 CRUD 및 시스템 파라미터 설정
├── templates/           # Jinja2 HTML 템플릿
│   ├── base.html        # 레이아웃, 다크모드, 네비게이션, HTMX 글로벌 핸들러
│   ├── index.html       # 메인(시작/업로드) 페이지
│   ├── battle.html      # 2-way 배틀 UI
│   ├── battle_3way.html # 3-way 배틀 UI
│   ├── battle_empty.html # 배틀 불가 상태 안내
│   ├── ranking.html     # 랭킹 테이블 및 차트
│   ├── manage.html      # 항목/기준/설정 관리 UI
│   └── partials/        # HTMX partial 응답 템플릿 (배틀 카드, 결과 모달, 항목 목록, _icons.html 아이콘 매크로)
├── Dockerfile           # 프로덕션 이미지 (Fly.io 배포용)
├── Dockerfile.dev       # 개발 이미지 (hot reload, docker compose 전용)
├── docker-compose.yml   # 로컬 개발 환경 오케스트레이션
├── package.json         # Tailwind CLI 스크립트 및 Node 의존성
├── package-lock.json    # Tailwind 의존성 잠금 파일
├── input.css            # TailwindCSS 입력 파일 (safelist 포함)
├── pyproject.toml       # 프로젝트 메타데이터 및 의존성 (uv)
├── uv.lock              # 의존성 잠금 파일
├── static/vendor/       # self-hosted JS (htmx.min.js, chart.umd.min.js)
├── tests/               # 유닛 + 통합 테스트 (pytest-asyncio)
└── fly.toml             # Fly.io 배포 설정

🧠 알고리즘 상세 (Algorithm Logic)

모든 알고리즘 동작 방식은 [Manage] -> [Settings] 탭에서 실시간으로 조정할 수 있습니다.

  1. Online Bayesian Bradley-Terry (Laplace Approximation):
    • 각 항목·기준별로 사후분포 (μ, σ²)를 유지합니다. μ는 실력 추정치, σ²는 불확실성입니다.
    • 투표 시 p = sigmoid(μ_a - μ_b) 기반 예측 확률로 정밀도와 평균을 동시 업데이트합니다.
    • 새 항목은 높은 σ²(높은 불확실성)로 시작하여 초반에 점수가 크게 변동하고, 대결이 누적될수록 σ²가 감소하여 자연 안정화됩니다.
  2. Hierarchical Shrinkage (계층적 축소):
    • 투표 후 기준 간 정밀도 가중 평균을 계산하고 각 기준의 μ를 그 방향으로 축소합니다.
    • 데이터가 부족한 기준에서 다른 기준의 정보를 차용하여 보다 안정적인 추정을 제공합니다.
  3. Draw Probability (무승부 확률 보정):
    • Bayesian Beta prior로 실측 무승부 비율에 자연 수렴하는 드로우 확률 모델을 사용합니다.
    • 점수 차이 기반 가우시안 감쇠로 점수가 비슷할수록 높은 무승부 확률을 표시합니다.
  4. Display Conversion (표시 변환):
    • 내부 logit 스케일 점수를 μ × display_scale + display_center (기본: 173.72 × μ + 1200)로 친숙한 스케일로 변환하여 표시합니다.

☁️ 배포 (Deployment)

이 프로젝트는 Fly.io 배포에 최적화되어 있습니다. (Docker 컨테이너 환경)

  1. flyctl 설치 및 로그인.
  2. 앱 런칭: fly launch
  3. 배포 진행: fly deploy

주의:

  • fly.toml에 정의된 대로 [mounts]를 통해 Fly Volume을 /data 경로에 마운트해야 SQLite DB 파일(/data/ranker.db)이 서버 재시작 후에도 유지됩니다. 기존 JSON 세션 파일이 있으면 앱 시작 시 자동 마이그레이션됩니다.
  • 현재 저장소 계층은 프로세스 로컬 asyncio 락을 사용하므로 단일 uvicorn 워커 전제를 둡니다. 프로덕션 Dockerfile은 이를 위해 --workers 1을 명시합니다.
  • HTTPS 배포에서는 COOKIE_SECURE=true를 사용해야 하며, 기본 Fly 설정에는 이 값이 포함되어 있습니다.

🎨 프런트엔드 빌드

  • 프로덕션 이미지는 package.jsonpackage-lock.json을 복사한 뒤 npm ci로 Tailwind CLI 의존성을 설치합니다.
  • CSS는 npm run build:css/static/output.css를 생성합니다.
  • 로컬 개발에서는 docker composetailwind 서비스가 npm cinpm run build:css:watch를 실행합니다.

✅ 입력 검증

  • /battle/vote는 Pydantic 스키마와 서버 발급 라운드 토큰으로 검증됩니다.
  • JSON import는 schemas.py의 세션 스키마를 통해 settings, criteria, items 전체를 검증합니다.
  • 잘못된 투표 페이로드, 누락된 rating key, 중복 item id 같은 데이터는 저장 전에 거부됩니다.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages