{"id":49377300,"url":"https://github.com/rafacmaia/book-brawl","last_synced_at":"2026-05-24T02:01:20.320Z","repository":{"id":340289861,"uuid":"1164484533","full_name":"rafacmaia/book-brawl","owner":"rafacmaia","description":"A book ranking mini-game powered by adaptive Elo and confidence modeling; fun and mathematically robust.","archived":false,"fork":false,"pushed_at":"2026-05-24T00:11:34.000Z","size":31788,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T01:20:47.283Z","etag":null,"topics":["book-ranking","books","clerk-auth","elo-rating","fast-api","full-stack","postgresql","python","react","reading","rest-api","typescript"],"latest_commit_sha":null,"homepage":"https://bookbrawl.app","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rafacmaia.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-23T06:09:20.000Z","updated_at":"2026-05-24T00:11:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rafacmaia/book-brawl","commit_stats":null,"previous_names":["rafacmaia/book-ranker","rafacmaia/book-brawl"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/rafacmaia/book-brawl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafacmaia%2Fbook-brawl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafacmaia%2Fbook-brawl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafacmaia%2Fbook-brawl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafacmaia%2Fbook-brawl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rafacmaia","download_url":"https://codeload.github.com/rafacmaia/book-brawl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafacmaia%2Fbook-brawl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33418550,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"online","status_checked_at":"2026-05-24T02:00:06.296Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["book-ranking","books","clerk-auth","elo-rating","fast-api","full-stack","postgresql","python","react","reading","rest-api","typescript"],"created_at":"2026-04-28T03:06:43.851Z","updated_at":"2026-05-24T02:01:20.303Z","avatar_url":"https://github.com/rafacmaia.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 📚 Book Brawl\n\n![Status](https://img.shields.io/badge/status-in--development-yellow)\n![Python](https://img.shields.io/badge/Python-3.12-3776AB?logo=python\u0026logoColor=white)\n![FastAPI](https://img.shields.io/badge/FastAPI-009688?logo=fastapi\u0026logoColor=white)\n![React](https://img.shields.io/badge/React-19-61DAFB?logo=react\u0026logoColor=black)\n![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript\u0026logoColor=white)\n![PostgreSQL](https://img.shields.io/badge/PostgreSQL-4169E1?logo=postgresql\u0026logoColor=white)\n![Last commit](https://img.shields.io/github/last-commit/rafacmaia/book-brawl)\n\nYou've read dozens _(hundreds?!)_ of books and vaguely know you like some better than\nothers. But which one was actually your favourite? Your top 20? Top 42? And was that\nrating of 7 you gave in 2021 really fair compared to the 8 you handed out last week?\n\nBook Brawl cuts through the noise by turning your reading log into a tournament. It pits\ntwo books head-to-head and asks one simple question:\n\n\u003e Which book means more to you, A or B?\n\nOver time, an Elo-based rating system does the math and builds a ranked list that\nreflects your ultimate breakdown. No more stuttering when someone asks you what your 33rd\nfavorite book of all time is. Those days are over!\n\n\u003e _**😈 TL;DR:** Book Brawl is a ranking system that becomes more reliable over time,\nwithout ever fully freezing into rigidity. Giving book lovers and ranking enthusiasts a\nfun **and** mathematically robust way to reflect on their books._\n\n## 🪩 Live App: **[bookbrawl.app](https://bookbrawl.app)**\n\n## 🪄 Preview\n\n\u003cdetails\u003e\n\u003csummary\u003e\u0026nbsp;\u003cb\u003eBook Pit (main game loop)\u003c/b\u003e\u003c/summary\u003e\n\n\u003cimg src=\"screenshots/web-brawlpit-1.png\" alt=\"Brawl Pit Start\"\u003e\n\u003cimg src=\"screenshots/web-brawlpit-2.png\" alt=\"Brawl Pit Hover\"\u003e\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"screenshots/mobile-brawlpit-1.jpeg\" alt=\"Brawl Pit mobile\" width=\"300\"\u003e\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\n\u003cimg src=\"screenshots/mobile-brawlpit-2.jpeg\" alt=\"Brawl Pit mobile\" width=\"300\"\u003e\n\u003c/p\u003e\n\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003e\u0026nbsp;\u003cb\u003eLeaderboard\u003c/b\u003e\u003c/summary\u003e\n\n\u003cimg src=\"screenshots/web-leaderboard-1.png\" alt=\"Rankings display\"\u003e\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"screenshots/mobile-leaderboard-1.jpeg\" alt=\"Rankings display mobile\" width=\"300\"\u003e\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\n\u003cimg src=\"screenshots/mobile-leaderboard-2.jpeg\" alt=\"Rankings display mobile\" width=\"300\"\u003e\n\u003c/p\u003e\n\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003e\u0026nbsp;\u003cb\u003eThe Stacks\u003c/b\u003e\u003c/summary\u003e\n\n\u003cimg src=\"screenshots/web-managepit-1.png\" alt=\"The Stacks\"\u003e\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"screenshots/mobile-managepit-1.jpeg\" alt=\"The Stacks on Mobile\" width=\"300\"\u003e\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\n\u003cimg src=\"screenshots/mobile-managepit-2.jpeg\" alt=\"The Stacks on Mobile\" width=\"300\"\u003e\n\u003c/p\u003e\n\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003e \u003cb\u003e\u0026nbsp;Legacy CLI (where it all started)\u003c/b\u003e\u003c/summary\u003e\n\n\u003ch4\u003eFirst Run:\u003c/h4\u003e\n\u003cimg src=\"screenshots/cli-onboarding-csv.png\" alt=\"First run with CSV import\"\u003e\n\u003ch4\u003eMain Menu:\u003c/h4\u003e\n\u003cimg src=\"screenshots/cli-main-menu.png\" alt=\"Main Menu\"\u003e\n\u003ch4\u003eBrawl Pit (main game loop):\u003c/h4\u003e\n\u003cimg src=\"screenshots/cli-brawl-pit.png\" alt=\"Brawl Pit ongoing comparisons\"\u003e\n\u003ch4\u003eLeaderboard:\u003c/h4\u003e\n\u003cimg src=\"screenshots/cli-rankings.png\" alt=\"Rankings display\"\u003e\n\u003ch4\u003eExport Rankings:\u003c/h4\u003e\n\u003cimg src=\"screenshots/cli-export-rankings.png\" alt=\"Export Rankings\"\u003e\n\n\u003c/details\u003e\n\n## ⭐️ Features\n\n- **Brawl Pit:** head-to-head book comparisons on loop\n- **Elo-based ranking** system with accuracy tiers and **adaptive K-values**\n- **Smart matchmaking:** prioritizes low-accuracy book, similar Elo scores, and rare\n  pairings\n- **Multifactor accuracy scoring:** tracks individual and overall rank accuracy and\n  stability\n- **The Stacks:** import books via CSV, add/edit/delete books manually, or factory\n  reset your collection\n- **Per-user data isolation:** full multi-user support via Clerk authentication (JWT\n  verification with PyJWT and JWKS)\n- **Persistent rankings** via PostgreSQL, supporting up to 2000 books per user\n- **Tied rankings** broken by head-to-head wins, then by any initial rating\n\n_See the [How it Works](https://github.com/rafacmaia/book-brawl#-how-it-works-deep-dive)\nand [Architecture](https://github.com/rafacmaia/book-brawl#%EF%B8%8F-architecture)\nsections below\nfor more details._\n\n## ⚙️ Setup\n\n\u003e **Prerequisites:** Python 3.10+, Node.js 18+\n\n\u003e **Database Setup:** The app connects to a PostgreSQL database. To run it locally, you\n\u003e can spin up a free instance on Railway or Supabase, or use a local PostgreSQL\n\u003e installation, and add your connection string to .env as described below.\n\n\u003e **Auth Setup:** Auth is handled via [Clerk](https://clerk.com). To run the app locally,\n\u003e you'll need to create a free Clerk application and add your API keys to `.env` and\n`frontend/.env.local` as described below.\n\n\u003e **Legacy CLI:** The original terminal app is still available, archived in [\n`cli/`](cli/). It's SQLite-based, self-contained, no database setup required. Just follow\n\u003e step one below using `cli/requirements.txt` instead and then launch it,\n`python main.py`.\n\n#### 1. Clone the repo and set up the Python environment\n\n```bash\ngit clone https://github.com/rafacmaia/book-brawl.git\ncd book-brawl\npython -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n```\n\n#### 2. Set up Clerk auth and configure the backend\n\nCreate a `.env` file at the root with your _Clerk credentials_ and your _PostgreSQL\nconnection string_:\n\n```env\nCLERK_JWKS_URL=your_clerk_jwks_url_here\nCLERK_SECRET_KEY=your_clerk_jwt_secret_key_here\nDATABASE_URL=your_postgres_connection_string_here\n```\n\nThen launch the API server:\n\n```bash\nuvicorn api:app --reload\n```\n\n#### 3. Set up and launch the frontend server\n\nInside `frontend/`, create a `.env.local` file with your _Clerk publishable key_:\n\n```env\nVITE_CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key_here\n```\n\nIn a separate terminal than the one running the API server, install frontend\ndependencies and launch the dev server:\n\n```bash\ncd frontend\nnpm install\nnpm run dev\n```\n\n#### 4. (Optional) Have Fun with It\n\n## 📃 CSV Format\n\nBook Brawl accepts CSV files with the following columns:\n\n\u003e title, author, rating\n\nWhere `rating` is a number from 1 to 10, inclusive. Decimals encouraged! It's used to\ngive new books a smarter starting Elo rather than a flat default.\n\nExample:\n\n```csv\ntitle,author,rating\nLove in the Time of Cholera,Gabriel García Márquez,9\nThe Lost World,Arthur Conan Doyle,6.5\nAmerican Psycho,Brett Easton Ellis,7.5\n```\n\n**To get you started, note that a sample CSV with 25 books is included at [\n`sample-data/sample.csv`](sample-data/sample.csv).**\n\n## 🏗️ Architecture\n\n\u003e **Stack:** FastAPI · PostgreSQL · React 19 · TypeScript · Tailwind CSS · Clerk  \n\u003e **Deployed on:** Railway (API + PostgreSQL) · Vercel (frontend)\n\nBook Brawl uses a classic layered architecture:\n\n```\nFrontend (React/Vite)  →  API (FastAPI)  →  Services  →  Repository  →  PostgreSQL\n```\n\nEach layer has a single responsibility and only communicates with the layer directly\nbelow it. The services layer contains all business logic and is framework-agnostic, being\nbuilt with both a web app and a CLI in mind.\n\n## 🗂️ Project Structure\n\n### Backend\n\n| File/Directory                                               | Layer       | Description                             |\n|--------------------------------------------------------------|-------------|-----------------------------------------|\n| [`api.py`](api.py)                                           | API         | FastAPI endpoints                       |\n| [`auth.py`](auth.py)                                         | API         | Clerk JWT verification                  |\n| [`config.py`](config.py)                                     | Config      | Environment variables and app constants |\n| [`models.py`](models.py)                                     | Domain      | Book data class                         |\n| [`services/game_service.py`](services/game_service.py)       | Service     | Matchmaking and match resolution        |\n| [`services/scoring_service.py`](services/scoring_service.py) | Service     | Elo calculation and accuracy scoring    |\n| [`services/ranking_service.py`](services/ranking_service.py) | Service     | Book ranking and tiebreaking            |\n| [`services/library_service.py`](services/library_service.py) | Service     | Book import and initial Elo mapping     |\n| [`db/schema.sql`](db/schema.sql)                             | Persistence | Database schema (source of truth)       |\n| [`db/books_repo.py`](db/books_repo.py)                       | Persistence | Book queries                            |\n| [`db/comparisons_repo.py`](db/comparisons_repo.py)           | Persistence | Comparison queries                      |\n| [`db/readers_repo.py`](db/readers_repo.py)                   | Persistence | User (reader) queries                   |\n| [`db/connection.py`](db/connection.py)                       | Persistence | Database connection and schema init     |\n\n### Frontend _(in `frontend/`)_\n\n| File                                                              | Description                                       |\n|-------------------------------------------------------------------|---------------------------------------------------|\n| [`src/App.tsx`](frontend/src/App.tsx)                             | Root component with routing and auth              |\n| [`src/pages/BrawlPit.tsx`](frontend/src/pages/BrawlPit.tsx)       | Main game loop; head-to-head matches              |\n| [`src/pages/Leaderboard.tsx`](frontend/src/pages/Leaderboard.tsx) | Rankings and progress page                        |\n| [`src/pages/TheStacks.tsx`](frontend/src/pages/TheStacks.tsx)     | Collection management page: add/edit/delete books |\n| [`src/components/layout/`](frontend/src/components/layout/)       | Site chrome (header, footer)                      \n| [`src/api.ts`](frontend/src/api.ts)                               | Authenticated API fetch helper                    |\n\n### CLI _(legacy, in `cli/`)_\n\nThe original terminal app is still functional and fully self-contained in `cli/`, but is\nno longer getting new features. It's being formally retired in a future update, with the\nweb app having taken center stage.\n\n## 🗺️ Roadmap\n\n- [X] Edit/delete books via the web UI\n- [ ] Book import from Goodreads, Fable, and maybe others\n- [ ] Onboarding/instructions page for new users\n- [ ] Filter rankings by genre, author, or year read (e.g., \"2021\" or \"Fantasy\")\n- [ ] CLI retirement and archival\n\n## 📬 Contact\n\nBuilt by [Rafa Maia](https://github.com/rafacmaia) at **Zou Labs 🐈‍⬛**.\n\nFeedback, questions, cat photos, and book recommendations welcome any\ntime – [zoulabs.dev@gmail.com](mailto:zoulabs.dev@gmail.com)\n\n---\n\n## 🧠 How It Works: Deep Dive\n\nEach book starts with an Elo score derived from the user's initial rating (1–10 scale,\nmapped to an initial 800–1200 Elo range). From there, rankings are determined entirely\nthrough head-to-head comparisons.\n\nEvery time a book wins a matchup, both books’ Elo scores are updated using the standard\nElo formula. However, the app extends basic Elo with adaptive volatility and stability\nmodeling to improve convergence speed and ranking reliability.\n\n#### Adaptive K-Factor\n\nThe Elo K-value adapts dynamically based on each book's accuracy score:\n\n- New books start with K=40, allowing fast movement toward their correct tier\n- As confidence grows, K steps down through 32 → 24 → 16\n- High-accuracy books remain correctable but won't swing wildly from a single match\n\nThis ensures fast early convergence without sacrificing long-term ranking stability.\n\n### 🔍 Accuracy Scoring\n\nA book’s accuracy score captures the stability of its current rank. It's\ncalculated as a weighted combination of three signals:\n\n1. **Absolute Coverage** – How many unique opponents the book has faced. This drives its\n   general placement within the rankings (top/mid/bottom).\n2. **Local Coverage** – How thoroughly the book has been tested against competitively\n   similar opponents (based on expected win probability). This refines placement within\n   its tier.\n3. **Local Density (Rank Fragility)** – Measures how many books sit within a narrow Elo\n   band. Even if a book has strong coverage, tight clusters imply potential rank\n   instability, so this measure prevents premature “Very High” accuracy assignments.\n\n#### Accuracy Tiers\n\n| Tier        | Meaning                                              |\n|-------------|------------------------------------------------------|\n| 🔴 Very Low | Early data, ranking based mostly on initial rating   |\n| 🟠 Low      | Broad tier (top/mid/bottom) likely correct           |\n| 🟡 Moderate | General position reliable, exact rank still shifting |\n| 🟢 High     | Position well established, likely within ~5 spots    |\n| ✅ Very High | Locked in, unlikely to shift significantly           |\n\n### 🎯 Intelligent Matchmaking\n\nMatchups are not random.\n\nThe Brawl Pit uses weighted stochastic matchmaking that prioritizes:\n\n- Books with very few matches (to get some baseline data on every book)\n- Rare or unmatched pairings\n- Books with similar Elo score (most informative matchups)\n- Books with lower accuracy scores (to progress overall ranking accuracy)\n\nThis maximizes information gain per match and speeds up convergence without requiring\nfull pairwise comparisons\n\n## 📄 License\n\n[MIT License](https://github.com/rafacmaia/book-brawl/blob/main/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafacmaia%2Fbook-brawl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frafacmaia%2Fbook-brawl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafacmaia%2Fbook-brawl/lists"}