{"id":50492785,"url":"https://github.com/t-rhex/pl-winner","last_synced_at":"2026-06-02T04:30:38.237Z","repository":{"id":356648999,"uuid":"1233325523","full_name":"t-rhex/pl-winner","owner":"t-rhex","description":"Premier League title-race predictor + FPL recommender. Dixon-Coles + Monte Carlo + ILP. TUI / web / CLI.","archived":false,"fork":false,"pushed_at":"2026-05-09T03:27:19.000Z","size":249,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-09T04:38:40.994Z","etag":null,"topics":["dixon-coles","fantasy-premier-league","football","fpl","linear-programming","monte-carlo","premier-league","python","streamlit","textual"],"latest_commit_sha":null,"homepage":"https://github.com/t-rhex/pl-winner","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/t-rhex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","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-05-08T20:51:00.000Z","updated_at":"2026-05-09T03:25:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/t-rhex/pl-winner","commit_stats":null,"previous_names":["t-rhex/pl-winner"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/t-rhex/pl-winner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t-rhex%2Fpl-winner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t-rhex%2Fpl-winner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t-rhex%2Fpl-winner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t-rhex%2Fpl-winner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/t-rhex","download_url":"https://codeload.github.com/t-rhex/pl-winner/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t-rhex%2Fpl-winner/sbom","scorecard":{"id":1247163,"data":{"date":"2026-05-09T03:25:27Z","repo":{"name":"github.com/t-rhex/pl-winner","commit":"99c37bd29a7b53c547f652356ee41485702c6e26"},"scorecard":{"version":"v5.1.1","commit":"cd152cb6742c5b8f2f3d2b5193b41d9c50905198"},"score":5.8,"checks":[{"name":"Dependency-Update-Tool","score":10,"reason":"update tool detected","details":["Info: detected update tool: Dependabot: .github/dependabot.yml:1"],"documentation":{"short":"Determines if the project uses a dependency update tool.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#dependency-update-tool"}},{"name":"CI-Tests","score":-1,"reason":"no pull request found","details":null,"documentation":{"short":"Determines if the project runs tests before pull requests are merged.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#ci-tests"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#security-policy"}},{"name":"Code-Review","score":0,"reason":"Found 0/8 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"project was created in last 90 days. please review its contents carefully","details":["Warn: Repository was created in last 90 days."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#maintained"}},{"name":"SAST","score":10,"reason":"SAST tool detected: CodeQL","details":["Info: SAST configuration detected: CodeQL","Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#sast"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":10,"reason":"GitHub workflow tokens follow principle of least privilege","details":["Info: jobLevel 'contents' permission set to 'read': .github/workflows/ci.yml:21","Info: jobLevel 'contents' permission set to 'read': .github/workflows/ci.yml:64","Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql.yml:19","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql.yml:20","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/cut-release.yml:23","Info: jobLevel 'contents' permission set to 'read': .github/workflows/deploy.yml:23","Info: jobLevel 'contents' permission set to 'read': .github/workflows/release.yml:17","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/release.yml:112","Info: jobLevel 'contents' permission set to 'read': .github/workflows/scorecard.yml:23","Info: jobLevel 'actions' permission set to 'read': .github/workflows/scorecard.yml:24","Info: jobLevel 'contents' permission set to 'read': .github/workflows/security.yml:64","Info: jobLevel 'contents' permission set to 'read': .github/workflows/security.yml:91","Info: jobLevel 'contents' permission set to 'read': .github/workflows/security.yml:23","Info: jobLevel 'contents' permission set to 'read': .github/workflows/security.yml:42","Info: found token with 'none' permissions: .github/workflows/ci.yml:1","Info: found token with 'none' permissions: .github/workflows/codeql.yml:1","Info: found token with 'none' permissions: .github/workflows/cut-release.yml:1","Info: found token with 'none' permissions: .github/workflows/deploy.yml:1","Info: found token with 'none' permissions: .github/workflows/release.yml:1","Info: found token with 'none' permissions: .github/workflows/scorecard.yml:1","Info: found token with 'none' permissions: .github/workflows/security.yml:1"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":6,"reason":"dependency not pinned by hash detected -- score normalized to 6","details":["Warn: containerImage not pinned by hash: Dockerfile:13","Warn: containerImage not pinned by hash: Dockerfile:28","Warn: pipCommand not pinned by hash: Dockerfile:23-25","Warn: pipCommand not pinned by hash: .github/workflows/ci.yml:44","Warn: pipCommand not pinned by hash: .github/workflows/ci.yml:45","Warn: pipCommand not pinned by hash: .github/workflows/ci.yml:77","Warn: pipCommand not pinned by hash: .github/workflows/cut-release.yml:55","Warn: pipCommand not pinned by hash: .github/workflows/cut-release.yml:56","Warn: pipCommand not pinned by hash: .github/workflows/cut-release.yml:59","Warn: pipCommand not pinned by hash: .github/workflows/release.yml:53","Warn: pipCommand not pinned by hash: .github/workflows/release.yml:63","Warn: pipCommand not pinned by hash: .github/workflows/security.yml:78","Warn: pipCommand not pinned by hash: .github/workflows/security.yml:104","Warn: pipCommand not pinned by hash: .github/workflows/security.yml:106","Info:  31 out of  31 GitHub-owned GitHubAction dependencies pinned","Info:  19 out of  19 third-party GitHubAction dependencies pinned","Info:   0 out of   2 containerImage dependencies pinned","Info:   2 out of  14 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":5,"reason":"5 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-6w46-j5rx-g56g","Warn: Project is vulnerable to: GHSA-7p48-42j8-8846","Warn: Project is vulnerable to: GHSA-8qw9-gf7w-42x5","Warn: Project is vulnerable to: PYSEC-2024-153 / GHSA-rxff-vr5r-8cj5","Warn: Project is vulnerable to: GHSA-g7vv-2v7x-gj9p"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#vulnerabilities"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v0.2.0 not signed: https://api.github.com/repos/t-rhex/pl-winner/releases/319818314","Warn: release artifact v0.2.0 does not have provenance: https://api.github.com/repos/t-rhex/pl-winner/releases/319818314"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#license"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/release.yml:80"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#packaging"}},{"name":"Contributors","score":0,"reason":"project has 0 contributing companies or organizations -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project has a set of contributors from multiple organizations (e.g., companies).","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#contributors"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#fuzzing"}},{"name":"Branch-Protection","score":3,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'main'","Info: 'force pushes' disabled on branch 'main'","Warn: 'branch protection settings apply to administrators' is disabled on branch 'main'","Warn: could not determine whether codeowners review is allowed","Warn: 'up-to-date branches' is disabled on branch 'main'","Info: status check found to merge onto on branch 'main'","Warn: PRs are not required to make changes on branch 'main'; or we don't have data to detect it.If you think it might be the latter, make sure to run Scorecard with a PAT or use Repo Rules (that are always public) instead of Branch Protection settings"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#branch-protection"}}]},"last_synced_at":"2026-05-09T04:47:39.983Z","repository_id":356648999,"created_at":"2026-05-09T04:47:39.983Z","updated_at":"2026-05-09T04:47:39.983Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33806987,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-02T02:00:07.132Z","response_time":109,"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":["dixon-coles","fantasy-premier-league","football","fpl","linear-programming","monte-carlo","premier-league","python","streamlit","textual"],"created_at":"2026-06-02T04:30:35.797Z","updated_at":"2026-06-02T04:30:38.228Z","avatar_url":"https://github.com/t-rhex.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pl-winner\n\n[![CI](https://github.com/t-rhex/pl-winner/actions/workflows/ci.yml/badge.svg)](https://github.com/t-rhex/pl-winner/actions/workflows/ci.yml)\n[![Deploy](https://github.com/t-rhex/pl-winner/actions/workflows/deploy.yml/badge.svg)](https://github.com/t-rhex/pl-winner/actions/workflows/deploy.yml)\n[![CodeQL](https://github.com/t-rhex/pl-winner/actions/workflows/codeql.yml/badge.svg)](https://github.com/t-rhex/pl-winner/actions/workflows/codeql.yml)\n[![Security](https://github.com/t-rhex/pl-winner/actions/workflows/security.yml/badge.svg)](https://github.com/t-rhex/pl-winner/actions/workflows/security.yml)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/t-rhex/pl-winner/badge)](https://scorecard.dev/viewer/?uri=github.com/t-rhex/pl-winner)\n[![Live](https://img.shields.io/website?url=https%3A%2F%2Fpl.andrewadhikari.com%2F_stcore%2Fhealth\u0026label=pl.andrewadhikari.com\u0026up_message=live\u0026down_message=down)](https://pl.andrewadhikari.com)\n[![PyPI](https://img.shields.io/pypi/v/pl-winner.svg)](https://pypi.org/project/pl-winner/)\n[![Python](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n\n\u003e Premier League title-race predictor and Fantasy Premier League recommender.\n\u003e Dixon-Coles + Monte Carlo for match outcomes. PuLP ILP for FPL squad selection.\n\u003e Terminal UI, web UI, and a single `pl-winner` CLI.\n\n```\n$ pl-winner predict --runs 10000\n...\nPredicted champion: Arsenal  (P = 87.1%)\n\n$ pl-winner fpl\n=== ILP-optimal 15-man squad (£100m, max 3 per club) ===\n  cost £86.1m   squad pts 209.3   XI pts 163.5   captain Cherki   vice Doku\n```\n\n## Quickstart\n\n### 1. Install\n\n```bash\npip install pl-winner            # core CLI + TUI\npip install 'pl-winner[web]'     # + Streamlit web UI\n```\n\n…or from source:\n\n```bash\ngit clone https://github.com/t-rhex/pl-winner \u0026\u0026 cd pl-winner\npython -m venv .venv \u0026\u0026 source .venv/bin/activate\npip install -e '.[web]'\n```\n\n### 2. Run\n\n```bash\npl-winner predict                    # title race + simulation projections\npl-winner fpl                        # top picks, captains, ILP squad, chips\npl-winner tui                        # interactive Textual UI (8 tabs)\npl-winner web                        # Streamlit web UI on :8501\n```\n\n### 3. Or run with Docker\n\n```bash\ndocker compose up\n# → http://localhost:8501\n```\n\n### 4. Or just visit the live demo\n\n🌐 **https://pl.andrewadhikari.com** — same app, deployed to Fly.io, auto-updated on every commit. See [DEPLOY.md](DEPLOY.md) for how it's wired up.\n\n## What you get\n\n| Command | Output |\n|---|---|\n| `pl-winner predict` | Title / top-4 / relegation probabilities for every team |\n| `pl-winner fixtures` | Every remaining fixture with model H/D/A probs |\n| `pl-winner backtest` | Walk-forward title hit-rate + match log-loss vs Bet365 |\n| `pl-winner fpl` | Top 8 per position, captains, ILP-optimal 15, differentials, chip advice |\n| `pl-winner value` | Brier / log-loss with bootstrap CIs, ROI of edges, break-even odds |\n| `pl-winner league --league-id 314` | Mini-league finish-position probabilities |\n| `pl-winner track record/score/report` | SQLite log of predictions scored against actuals |\n| `pl-winner tune` | Cross-validate the half-life parameter |\n| `pl-winner tui` | Interactive 8-tab terminal UI |\n| `pl-winner web` | Streamlit web app with the same data + Plotly charts |\n\n```bash\npl-winner --help                     # full subcommand list\npl-winner fpl --help                 # per-subcommand options\n```\n\n## How it works\n\n### Match outcomes — Dixon-Coles\n\nEach team has an attack rating $\\alpha_i$ and a defense rating $\\delta_i$. Expected goals are\n\n$$\\lambda_{home} = e^{\\alpha_h + \\delta_a + h}, \\qquad \\mu_{away} = e^{\\alpha_a + \\delta_h}$$\n\nA correlation term $\\tau(\\cdot, \\rho)$ corrects 0-0 / 1-0 / 0-1 / 1-1 dependence that pure independent Poissons miss. Fit by weighted MLE with exponential time decay (default half-life 180 days, cross-validated optimum 270 days).\n\n### Title race — Monte Carlo\n\nFor each remaining fixture build the joint score pmf, sample 10k full seasons, count how often each club finishes 1st / top-4 / bottom-3. Vectorized, ~50ms per 1k seasons.\n\n### FPL squads — ILP\n\nMaximize $\\sum_i \\text{proj}_i \\cdot x_i$ subject to:\n- £100m budget\n- 2 GK / 5 DEF / 5 MID / 3 FWD\n- ≤ 3 per club\n- All players available (injury / suspension filtered)\n\nSolved with PuLP / CBC. The same ILP in `Free Hit` mode (single-GW) and Wildcard mode (re-pick over remaining GWs) underpins the chip advisor.\n\n### Honest framing\n\nThe model is **well-calibrated** (reliability table ticks the diagonal) but **doesn't beat Bet365's closing line** on Brier or log-loss — we verified this with bootstrap CIs and the diff is statistically significant. Useful as a probability estimator and FPL fixture-difficulty signal; *don't* treat the break-even odds as a money printer against sharp markets.\n\n## Configuration\n\n| Env var | Purpose | Default |\n|---|---|---|\n| `PL_WINNER_DATA_DIR` | Where caches and SQLite live | `\u003crepo\u003e/data` |\n| `STREAMLIT_SERVER_PORT` | Web UI port | `8501` |\n\nCaches honor TTLs (FPL bootstrap: 6h; player history: 24h; match CSVs: forever — pass `--refresh`).\n\n## Layout\n\n```\nsrc/                 # pl_winner package\n  cli.py             # `pl-winner` entry, subparsers\n  commands/          # one module per subcommand\n  data.py            # match data (E0/E1/SP1/D1/I1/F1/N1/P1)\n  model.py           # Dixon-Coles\n  simulate.py        # Monte Carlo\n  fpl.py             # FPL API client + projections\n  fpl_optimizer.py   # PuLP ILP (squad / Free Hit / Wildcard / transfers)\n  chips.py           # Triple Captain / Bench Boost\n  league.py          # mini-league simulator\n  value.py           # implied probabilities, EV, break-even\n  calibration.py     # Brier, log-loss, bootstrap CIs, reliability\n  tracker.py         # SQLite log\n  tune.py            # half-life CV\n  elo.py             # Elo + DC hybrid (kept for experiments)\n  http_utils.py      # robust HTTP with retries + cache TTL\n  paths.py           # data-dir resolution\n  tui.py             # Textual TUI\napp/\n  streamlit_app.py   # web UI\ntests/               # pytest suite (~50 tests)\n```\n\n## Data sources\n\n- **Match results / odds:** [football-data.co.uk](https://www.football-data.co.uk/) — free CSVs, no API key\n- **FPL data:** [official FPL public API](https://fantasy.premierleague.com/api/bootstrap-static/) — no API key\n- **Live odds for unplayed matches:** intentionally not scraped (ToS-grey, fragile per-bookmaker)\n\nAll requests retry with exponential backoff, cache to disk with TTLs, and degrade gracefully when the API is unavailable or a season hasn't been published.\n\n## Caveats\n\n- Dixon-Coles is symmetric across clubs — doesn't model transfers/managerial changes/fatigue beyond the time-decay weight.\n- Promoted clubs have little prior history; ratings stabilize as the season progresses.\n- The mini-league simulator uses Normal samples around player projections (σ ≈ √(μ+1)) — adequate for ranking but conservative on tail outcomes.\n- 10k Monte Carlo simulations: title-probability SE ≈ 0.5pp at p≈0.5. Bump `--runs` for tighter intervals.\n- ILP is \"optimal under the projection\" — the projection itself has noise, so don't read £0.1m / 0.05-pt differences as meaningful.\n\n## Privacy\n\n`pl-winner` makes **no telemetry calls**. The only network traffic is to\n[football-data.co.uk](https://www.football-data.co.uk/) for match CSVs and\n[fantasy.premierleague.com/api](https://fantasy.premierleague.com/api/bootstrap-static/)\nfor FPL data. Caches stay on your machine. Streamlit usage stats are disabled.\n\nSee [SECURITY.md](SECURITY.md) for the full posture and how to report\nvulnerabilities.\n\n## Releases\n\nThree workflows automate the entire release flow — no API tokens stored\nanywhere (uses [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/)).\n\n| Workflow | Trigger | What it does |\n|---|---|---|\n| `ci.yml` | push, PR | tests + ruff + smoke on Python 3.10/3.11/3.12 |\n| `cut-release.yml` | manual (Actions tab) | bumps version + CHANGELOG, commits, tags, pushes |\n| `release.yml` | tag `v*` push | builds, twine-checks, smoke-installs, publishes to PyPI, creates a GitHub Release |\n| `deploy.yml` | CI passes on main | deploys the Streamlit app to Fly.io at `pl.andrewadhikari.com` |\n\n### Cut a new release (one click)\n\n[Run the **Cut release** workflow](https://github.com/t-rhex/pl-winner/actions/workflows/cut-release.yml)\nwith a bump type (`patch` / `minor` / `major` / explicit `0.4.2`):\n\n```\nActions → Cut release → Run workflow → bump: patch → Run\n```\n\nThis handles the full chain: version bump → CHANGELOG roll → commit → tag →\nwhich triggers `release.yml` → which publishes to PyPI and drafts a GitHub\nrelease. End-to-end, ~3 minutes.\n\n### Or release locally\n\n```bash\nmake release-check          # build + twine check locally\npython tools/bump_version.py patch\ngit commit -am \"Release v$(grep '^version' pyproject.toml | cut -d'\"' -f2)\"\ngit tag \"v$(grep '^version' pyproject.toml | cut -d'\"' -f2)\"\ngit push origin HEAD --tags\n```\n\nSee [CHANGELOG.md](CHANGELOG.md) for release notes.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome for modeling, FPL features,\ntests. Run `make test lint` before opening a PR.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft-rhex%2Fpl-winner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ft-rhex%2Fpl-winner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft-rhex%2Fpl-winner/lists"}