fplkit 2.2.0__tar.gz → 2.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/TOOLS.md +13 -8
  2. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/squad-builder/SKILL.md +1 -1
  3. fplkit-2.3.1/.claude/output-styles/fpl-mate.md +148 -0
  4. {fplkit-2.2.0 → fplkit-2.3.1}/AGENTS.md +2 -1
  5. {fplkit-2.2.0 → fplkit-2.3.1}/CHANGELOG.md +42 -0
  6. {fplkit-2.2.0 → fplkit-2.3.1}/CLAUDE.md +2 -1
  7. {fplkit-2.2.0 → fplkit-2.3.1}/PKG-INFO +31 -2
  8. {fplkit-2.2.0 → fplkit-2.3.1}/README.md +30 -1
  9. {fplkit-2.2.0 → fplkit-2.3.1}/docs/architecture.md +22 -17
  10. {fplkit-2.2.0 → fplkit-2.3.1}/docs/command-reference.md +205 -19
  11. {fplkit-2.2.0 → fplkit-2.3.1}/docs/custom-analysis.md +11 -1
  12. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/_version.py +2 -2
  13. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/analysis/stats.py +23 -0
  14. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/analysis/transfer_eval.py +9 -1
  15. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/common.py +8 -11
  16. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/core_insights.py +165 -120
  17. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/fpl_draft.py +40 -1
  18. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/understat.py +18 -1
  19. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/__init__.py +2 -0
  20. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_fines.py +58 -6
  21. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_helpers.py +44 -8
  22. fplkit-2.3.1/fpl_cli/cli/_json.py +186 -0
  23. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_league_recap_data.py +220 -54
  24. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_league_recap_history.py +443 -24
  25. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_league_recap_types.py +43 -6
  26. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_plan_grid.py +26 -18
  27. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_review_classic.py +9 -1
  28. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_review_draft.py +14 -16
  29. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_review_summarisation.py +18 -4
  30. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/allocate.py +26 -7
  31. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/chips.py +9 -2
  32. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/differentials.py +9 -2
  33. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/doctor.py +100 -26
  34. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/doctor_providers.py +310 -79
  35. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/fdr.py +75 -22
  36. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/fixtures.py +13 -3
  37. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/intel.py +12 -15
  38. fplkit-2.3.1/fpl_cli/cli/league_fines.py +209 -0
  39. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/league_recap.py +259 -16
  40. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/player.py +24 -14
  41. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/preview.py +5 -3
  42. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/price_history.py +3 -1
  43. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/ratings.py +36 -3
  44. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/sell_prices.py +97 -48
  45. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/squad.py +21 -18
  46. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/stats.py +66 -14
  47. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/status.py +3 -2
  48. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/transfer_eval.py +9 -2
  49. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/models/league_history.py +102 -14
  50. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/prompts/league_recap.py +146 -2
  51. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/prompts/review.py +3 -0
  52. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/fixture_predictions.py +55 -0
  53. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/league_history.py +13 -3
  54. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/league_history_counters.py +291 -29
  55. fplkit-2.3.1/fpl_cli/services/league_history_fines.py +552 -0
  56. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/league_history_notes.py +261 -16
  57. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/returnee_radar.py +11 -2
  58. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/scoring/__init__.py +6 -0
  59. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/scoring/constants.py +83 -5
  60. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/scoring/display.py +9 -3
  61. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/scoring/evaluation.py +26 -12
  62. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/scoring/ownership.py +6 -2
  63. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/scoring/value_quality.py +3 -1
  64. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/team_ratings.py +184 -54
  65. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/team_ratings_prior.py +15 -1
  66. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/templates/gw_league_recap.md.j2 +29 -1
  67. fplkit-2.3.1/fpl_cli/utils/gameweek.py +32 -0
  68. fplkit-2.3.1/scripts/backtest_early_season_prior_blend.py +438 -0
  69. {fplkit-2.2.0 → fplkit-2.3.1}/scripts/calibrate_promoted_prior.py +5 -0
  70. {fplkit-2.2.0 → fplkit-2.3.1}/scripts/calibrate_quality_ceilings.py +12 -4
  71. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_api_core_insights.py +20 -0
  72. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_api_draft.py +54 -1
  73. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_api_understat.py +106 -1
  74. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_chips.py +5 -1
  75. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_doctor.py +97 -4
  76. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_doctor_providers.py +254 -11
  77. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_fdr.py +77 -0
  78. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_fixtures.py +3 -2
  79. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_intel.py +4 -2
  80. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_json.py +55 -2
  81. fplkit-2.3.1/tests/test_cli_json_contract.py +319 -0
  82. fplkit-2.3.1/tests/test_cli_league_fines.py +275 -0
  83. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_league_recap.py +1052 -37
  84. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_plan_grid.py +2 -1
  85. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_player.py +8 -4
  86. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_preview.py +69 -0
  87. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_returnees.py +3 -1
  88. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_review.py +152 -2
  89. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_sell_prices.py +83 -7
  90. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_stats.py +203 -0
  91. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_fines.py +74 -2
  92. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_fixture_predictions.py +78 -0
  93. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_league_history_counters.py +425 -19
  94. fplkit-2.3.1/tests/test_league_history_fines.py +463 -0
  95. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_league_history_notes.py +496 -5
  96. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_league_history_store.py +119 -6
  97. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_league_recap.py +558 -36
  98. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_preseason.py +3 -1
  99. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_returnee_radar.py +13 -0
  100. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_review.py +117 -0
  101. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_scoring.py +202 -1
  102. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_team_ratings.py +711 -0
  103. fplkit-2.3.1/tests/test_utils_gameweek.py +38 -0
  104. fplkit-2.2.0/.claude/output-styles/fpl-mate.md +0 -111
  105. fplkit-2.2.0/fpl_cli/cli/_json.py +0 -92
  106. fplkit-2.2.0/fpl_cli/utils/gameweek.py +0 -10
  107. fplkit-2.2.0/tests/test_utils_gameweek.py +0 -18
  108. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/README.md +0 -0
  109. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/create-pr/SKILL.md +0 -0
  110. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/gw-prep/SKILL.md +0 -0
  111. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/gw-prep/references/output-template.md +0 -0
  112. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/gw-prep/references/rules.md +0 -0
  113. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/gw-prep/scripts/_bootstrap.py +0 -0
  114. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/gw-prep/scripts/bench_order.py +0 -0
  115. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/gw-prep/scripts/extract_classic_squad.py +0 -0
  116. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/gw-prep/scripts/starting_xi.py +0 -0
  117. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/gw-prep/scripts/transfer_eval.py +0 -0
  118. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/gw-prep/scripts/validate_draft_waivers.py +0 -0
  119. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/preview-ingest/SKILL.md +0 -0
  120. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/release/SKILL.md +0 -0
  121. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/release-notes/SKILL.md +0 -0
  122. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/squad-builder/references/output-template.md +0 -0
  123. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/squad-builder/references/rules.md +0 -0
  124. {fplkit-2.2.0 → fplkit-2.3.1}/.agents/skills/update-gw-prep/SKILL.md +0 -0
  125. {fplkit-2.2.0 → fplkit-2.3.1}/.claude/settings.json +0 -0
  126. {fplkit-2.2.0 → fplkit-2.3.1}/.env.example +0 -0
  127. {fplkit-2.2.0 → fplkit-2.3.1}/.github/workflows/ci.yml +0 -0
  128. {fplkit-2.2.0 → fplkit-2.3.1}/.github/workflows/pr-title.yml +0 -0
  129. {fplkit-2.2.0 → fplkit-2.3.1}/.github/workflows/release.yml +0 -0
  130. {fplkit-2.2.0 → fplkit-2.3.1}/.gitignore +0 -0
  131. {fplkit-2.2.0 → fplkit-2.3.1}/CONTRIBUTING.md +0 -0
  132. {fplkit-2.2.0 → fplkit-2.3.1}/LICENSE +0 -0
  133. {fplkit-2.2.0 → fplkit-2.3.1}/cliff.toml +0 -0
  134. {fplkit-2.2.0 → fplkit-2.3.1}/config/team_ratings_overrides.yaml +0 -0
  135. {fplkit-2.2.0 → fplkit-2.3.1}/docs/fpl-rules.md +0 -0
  136. {fplkit-2.2.0 → fplkit-2.3.1}/docs/images/fpl-player-demo-v1-0.png +0 -0
  137. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/__init__.py +0 -0
  138. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/__init__.py +0 -0
  139. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/action/__init__.py +0 -0
  140. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/action/waiver.py +0 -0
  141. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/analysis/__init__.py +0 -0
  142. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/analysis/bench_order.py +0 -0
  143. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/analysis/captain.py +0 -0
  144. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/analysis/squad_analyzer.py +0 -0
  145. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/analysis/starting_xi.py +0 -0
  146. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/base.py +0 -0
  147. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/data/__init__.py +0 -0
  148. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/data/fixture.py +0 -0
  149. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/data/price.py +0 -0
  150. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/data/scout.py +0 -0
  151. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/orchestration/__init__.py +0 -0
  152. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/agents/orchestration/report.py +0 -0
  153. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/__init__.py +0 -0
  154. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/contract.py +0 -0
  155. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/dataset_fetcher.py +0 -0
  156. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/football_data.py +0 -0
  157. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/fpl.py +0 -0
  158. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/historical.py +0 -0
  159. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/historical_types.py +0 -0
  160. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/providers/__init__.py +0 -0
  161. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/providers/_models.py +0 -0
  162. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/providers/anthropic.py +0 -0
  163. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/providers/openai_compat.py +0 -0
  164. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/providers/perplexity.py +0 -0
  165. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/api/vaastav.py +0 -0
  166. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_banner.py +0 -0
  167. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_context.py +0 -0
  168. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_fines_config.py +0 -0
  169. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/_review_analysis.py +0 -0
  170. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/captain.py +0 -0
  171. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/credentials.py +0 -0
  172. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/history.py +0 -0
  173. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/init.py +0 -0
  174. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/league.py +0 -0
  175. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/price_changes.py +0 -0
  176. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/returnees.py +0 -0
  177. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/review.py +0 -0
  178. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/targets.py +0 -0
  179. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/waivers.py +0 -0
  180. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/cli/xg.py +0 -0
  181. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/config/defaults.yaml +0 -0
  182. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/config/fixture_predictions.yaml +0 -0
  183. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/config/previews/EXAMPLE.yaml +0 -0
  184. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/config/team_managers.yaml +0 -0
  185. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/constants.py +0 -0
  186. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/models/__init__.py +0 -0
  187. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/models/chip_plan.py +0 -0
  188. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/models/fixture.py +0 -0
  189. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/models/player.py +0 -0
  190. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/models/team.py +0 -0
  191. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/models/types.py +0 -0
  192. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/parsers/__init__.py +0 -0
  193. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/parsers/recommendations.py +0 -0
  194. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/paths.py +0 -0
  195. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/prompts/__init__.py +0 -0
  196. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/prompts/returnees.py +0 -0
  197. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/prompts/scout.py +0 -0
  198. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/scraper/__init__.py +0 -0
  199. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/scraper/fpl_prices.py +0 -0
  200. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/season.py +0 -0
  201. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/__init__.py +0 -0
  202. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/matchup.py +0 -0
  203. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/player_prior.py +0 -0
  204. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/scoring/data_prep.py +0 -0
  205. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/scoring/shrinkage.py +0 -0
  206. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/scoring/signals.py +0 -0
  207. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/scoring/single_gw.py +0 -0
  208. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/season_previews.py +0 -0
  209. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/squad_allocator.py +0 -0
  210. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/services/team_form.py +0 -0
  211. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/templates/gw_preview.md.j2 +0 -0
  212. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/templates/gw_review.md.j2 +0 -0
  213. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/utils/__init__.py +0 -0
  214. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/utils/files.py +0 -0
  215. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/utils/markdown.py +0 -0
  216. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/utils/teams.py +0 -0
  217. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/utils/text.py +0 -0
  218. {fplkit-2.2.0 → fplkit-2.3.1}/fpl_cli/utils/time.py +0 -0
  219. {fplkit-2.2.0 → fplkit-2.3.1}/pyproject.toml +0 -0
  220. {fplkit-2.2.0 → fplkit-2.3.1}/pyrightconfig.json +0 -0
  221. {fplkit-2.2.0 → fplkit-2.3.1}/requirements.lock +0 -0
  222. {fplkit-2.2.0 → fplkit-2.3.1}/tests/__init__.py +0 -0
  223. {fplkit-2.2.0 → fplkit-2.3.1}/tests/conftest.py +0 -0
  224. {fplkit-2.2.0 → fplkit-2.3.1}/tests/fixtures/classic_squad_fixture.md +0 -0
  225. {fplkit-2.2.0 → fplkit-2.3.1}/tests/fixtures/recommendations_fixture.md +0 -0
  226. {fplkit-2.2.0 → fplkit-2.3.1}/tests/fixtures/validate_draft_waivers/recs_clean.md +0 -0
  227. {fplkit-2.2.0 → fplkit-2.3.1}/tests/fixtures/validate_draft_waivers/squad_grid.json +0 -0
  228. {fplkit-2.2.0 → fplkit-2.3.1}/tests/fixtures/validate_draft_waivers/waivers.json +0 -0
  229. {fplkit-2.2.0 → fplkit-2.3.1}/tests/fixtures/waiver_ranked_golden.json +0 -0
  230. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_agents_action.py +0 -0
  231. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_agents_analysis.py +0 -0
  232. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_agents_analysis_squad.py +0 -0
  233. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_agents_base.py +0 -0
  234. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_agents_data.py +0 -0
  235. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_agents_data_price.py +0 -0
  236. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_agents_orchestration.py +0 -0
  237. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_allocate.py +0 -0
  238. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_api_client.py +0 -0
  239. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_api_dataset_fetcher.py +0 -0
  240. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_api_football_data.py +0 -0
  241. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_api_historical.py +0 -0
  242. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_api_vaastav.py +0 -0
  243. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_bench_order_script.py +0 -0
  244. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_classic_squad_extraction.py +0 -0
  245. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_allocate.py +0 -0
  246. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_banner.py +0 -0
  247. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_captain.py +0 -0
  248. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_credentials.py +0 -0
  249. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_custom_analysis.py +0 -0
  250. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_differentials.py +0 -0
  251. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_fdr_blanks.py +0 -0
  252. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_format.py +0 -0
  253. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_format_suppression.py +0 -0
  254. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_history.py +0 -0
  255. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_init.py +0 -0
  256. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_league.py +0 -0
  257. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_price_changes.py +0 -0
  258. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_price_history.py +0 -0
  259. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_settings_warning.py +0 -0
  260. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_squad.py +0 -0
  261. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_status.py +0 -0
  262. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_targets.py +0 -0
  263. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_transfer_eval.py +0 -0
  264. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_waivers.py +0 -0
  265. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_warnings_stderr.py +0 -0
  266. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_cli_xg.py +0 -0
  267. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_common.py +0 -0
  268. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_common_scoring.py +0 -0
  269. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_data_prep_harmonisation.py +0 -0
  270. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_doc_consistency.py +0 -0
  271. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_fines_config.py +0 -0
  272. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_fpl_prices.py +0 -0
  273. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_init.py +0 -0
  274. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_init_fines.py +0 -0
  275. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_matchup_service.py +0 -0
  276. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_models.py +0 -0
  277. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_paths.py +0 -0
  278. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_player_prior.py +0 -0
  279. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_player_resolution.py +0 -0
  280. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_providers.py +0 -0
  281. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_recommendations_parser.py +0 -0
  282. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_report_agent.py +0 -0
  283. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_report_paths.py +0 -0
  284. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_rolling_pts_per_m.py +0 -0
  285. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_scraper.py +0 -0
  286. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_season.py +0 -0
  287. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_season_awareness.py +0 -0
  288. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_season_previews.py +0 -0
  289. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_sell_prices.py +0 -0
  290. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_squad_allocator.py +0 -0
  291. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_starting_xi_agent.py +0 -0
  292. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_stats.py +0 -0
  293. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_team_ratings_prior.py +0 -0
  294. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_transfer_eval_agent.py +0 -0
  295. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_utils_markdown.py +0 -0
  296. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_utils_text.py +0 -0
  297. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_utils_time.py +0 -0
  298. {fplkit-2.2.0 → fplkit-2.3.1}/tests/test_validate_draft_waivers.py +0 -0
@@ -18,7 +18,11 @@ All `--format json` commands emit a standard envelope via `emit_json()` in `fpl_
18
18
  - `metadata` - command-specific context (typically includes `gameweek`)
19
19
  - `data` - the payload (list or dict, varies per command)
20
20
 
21
- Errors use `emit_json_error()` with `{"command", "error"}` shape.
21
+ Errors use `emit_json_error()` with `{"command", "error"}` shape. **Both envelopes go to
22
+ stdout** and exit 1 on failure, so parse stdout either way and read the exit code to know
23
+ which envelope you got. Warnings and progress go to stderr; the failure message itself is
24
+ in `error`, not on stderr. Full contract:
25
+ [Command Reference](../docs/command-reference.md#json-output).
22
26
 
23
27
  ## CLI Commands
24
28
 
@@ -26,29 +30,30 @@ Errors use `emit_json_error()` with `{"command", "error"}` shape.
26
30
  |---------|-------------|------|--------|-------------|---------|
27
31
  | `init` | Set up fpl-cli with your FPL IDs and optional features | No | General | No | direct-api |
28
32
  | `status` | Show FPL gameweek status and upcoming deadlines. JSON `metadata` carries `gameweek`, `format` and `season` (hyphenated label, e.g. `2026-27`) - `season` is the directory segment skills must use when writing alongside fpl-cli's own reports | Yes | General | No | direct-api |
29
- | `doctor` | Check the setup for dead IDs, stale data, and config problems: resolves each configured ID against the live API (reporting the team/league name back so a wrong-but-valid ID is visible), verifies per-team data files (team ratings, managers, season preview intel, finances, player prior) describe the current season's clubs, and reports which config/data/cache directories are in use. With `--providers`, probes the external data sources instead: live shape-and-volume checks against the FPL and Draft APIs, the vaastav and Core-Insights datasets, Understat, and football-data.org, including that every club resolves across sources (the drift that otherwise surfaces as plausible but wrong output). Distinguishes broken (needs a fix today) from stale (self-corrects). Exits non-zero when something is broken | Yes | General | No | direct-api |
33
+ | `doctor` | Check the setup for dead IDs, stale data, and config problems: resolves each configured ID against the live API (reporting the team/league name back so a wrong-but-valid ID is visible), verifies per-team data files (team ratings, managers, season preview intel, finances, player prior) describe the current season's clubs, and reports which config/data/cache directories are in use. With `--providers`, probes the external data sources instead: live shape-and-volume checks against the FPL and Draft APIs, the vaastav and Core-Insights datasets, Understat, and football-data.org, including that every club resolves across sources and that the Core-Insights per-gameweek files parse to records through the parsers the scoring commands use (the drift that otherwise surfaces as plausible but wrong output). Distinguishes broken (needs a fix today) from stale (self-corrects). Exits non-zero when something is broken | Yes | General | No | direct-api |
30
34
  | `fixtures` | Show fixtures for a gameweek | Yes | General | No | direct-api |
31
35
  | `player` | Look up a player's stats, xG, ownership and fixture run. JSON includes `ep_next` (predicted pts next GW) and `ep_this` (current GW), emitting `null` when FPL has no projection; panel shows `xPts` (ep_next) when available, omits the segment when FPL has no projection. `--detail` (`-d`) shows GW-by-GW performance and, for FWD/MID with sufficient history, xGI sustainability (per-match GI-xGI divergence -> form modifier). With `custom_analysis`: JSON adds `info.adjusted_npxg_per_90` (fixture-adjusted) and `info.raw_npxg_per_90` (Understat season avg); panel shows `adj. npxG/90: X.XXX (raw: Y.YYY)` | Yes | General | No | direct-api |
32
- | `stats` | List players with filtering and sorting. `--value` adds quality/value per £m columns; `--window N` sets rolling lookback (3-10) for `rolling_pts_per_m`. Sortable by `ep_next`/`ep_this` (FPL predicted points; `ep_this` is only meaningful before the gameweek's first kickoff — FPL rolls it forward to `ep_next` as each player's match finishes); both in JSON output (emit `null` when FPL has no projection; table renders `—`). Players with no projection sort to the bottom in either direction | Yes | General | No | direct-api |
36
+ | `stats` | List players with filtering and sorting. `--value` adds quality/value per £m columns; `--window N` sets rolling lookback (3-10) for `rolling_pts_per_m`. Before GW6, `--value` warns that quality scores are small-sample dominated (stderr notice; JSON `metadata.warnings` code `early_season_small_sample`) — GK ceilings scale with the calendar-possible sample, reaching full scale at GW6. Sortable by `ep_next`/`ep_this` (FPL predicted points; `ep_this` is only meaningful before the gameweek's first kickoff — FPL rolls it forward to `ep_next` as each player's match finishes); both in JSON output (emit `null` when FPL has no projection; table renders `—`). Players with no projection sort to the bottom in either direction | Yes | General | No | direct-api |
33
37
  | `history` | Show historical player performance across seasons | Yes | General | No | direct-api |
34
38
  | `league` | Show live league standings for Classic and Draft leagues | No | General | No | direct-api |
35
- | `fdr` | Analyse fixture difficulty - easy runs, blanks, doubles | Yes | General | No | via-agent |
39
+ | `fdr` | Analyse fixture difficulty - easy runs, blanks, doubles. `--blanks` mixes confirmed fixtures with predictions; when those predictions are stale, table mode warns on stderr and JSON adds a `fixture_predictions_stale` entry to `metadata.warnings` | Yes | General | No | via-agent |
36
40
  | `xg` | Analyse underlying stats: xG, xA, overperformers | Yes | General | No | via-agent |
37
41
  | `price-changes` | Show price changes and transfer activity | No | General | No | via-agent |
38
42
  | `price-history` | Show price trajectory and transfer momentum | Yes | General | No | direct-api |
39
43
  | `returnees` | Track injured and suspended players due back soon, so a returnee can be claimed in draft before they are fit or planned around in classic. Reads the FPL availability news, filters to returns inside a window (`--window N`, 1-38) and through a source-aware quality bar, and diffs against the stored watchlist for week-over-week changes. Most flagged players carry no parseable return date, so date-unknown entries are the norm rather than the exception; `--enrich` searches the web for fresher timing on those and on stale-news entries (needs a Perplexity key; skips with a note without one) and shows it beside the FPL news, never over it. `--all` bypasses the quality bar and deliberately does not persist the snapshot. JSON `metadata` carries `window`, `escalation_window`, `stash_upgrade_margin`, `transitions_available`, `quality_bar_available`, `quality_bar_applied` and `enrichment_requested`/`_available`/`_note`/`_count`; `data` is `{entries, departures}`, each entry carrying `quality.basis`/`quality.meets_stash`, `expected_return`/`return_gameweek`/`return_source`, `escalation_eligible`/`escalation_basis`, `transition`, and `enrichment` (null unless enriched) | Yes | General | No | direct-api |
40
44
  | `preview` | Run full pre-gameweek analysis and generate report. `--save` writes to `<output dir>/<season>/gw{N}-preview.md`; `--scout` writes to `<research dir>/ai-scout-reports/<season>/gw{N}-scout-preview[-referenced].md` | No | General | No | via-agent |
41
45
  | `review` | Review a completed gameweek - squad performance and standings. `--save` writes to `<output dir>/<season>/gw{N}-review.md`; `--compare-recs` reads `gw{N}-recommendations.md` from the same season directory | No | General | No | via-agent |
42
- | `league-recap` | Recap a completed gameweek - awards, standings, and banter. Saves to `<output dir>/<season>/gw{N}-league-recap[-draft].md`. Records every run into the league history ledger (`<data dir>/league_history/`) as a side effect, backfilling classic gaps at a coarse tier automatically; `--backfill-detail` rebuilds earlier gameweeks in full (one request per manager per gameweek). Coverage gaps and capture warnings go to stderr; the command exits 0 even when the store is unreadable. `--format json` emits one row per manager (the captured ledger row shape) plus `metadata.coverage`, `season_phase`, `notes_pack`, `synthesis_summary`, `warnings` (always present, each entry a stable `code` plus rewritable `message` - codes listed in [Command Reference](../docs/command-reference.md#league-history)) and `first_capture_store_path` | Yes | General | No | via-agent |
46
+ | `league-recap` | Recap a completed gameweek - awards, standings, and banter. Saves to `<output dir>/<season>/gw{N}-league-recap[-draft].md`. Records every run into the league history ledger (`<data dir>/league_history/`) as a side effect, backfilling classic gaps at a coarse tier automatically; `--backfill-detail` rebuilds earlier gameweeks in full (one request per manager per gameweek). A rebuild keeps the name, club and position already recorded for each player instead of restamping them from today's bootstrap, and never drops a code the gameweek already resolved; it reads the earliest recorded row rather than the newest, so a gameweek an older version already restamped is repaired rather than re-copied. Everything the gameweek itself answers (points, cards, pick flags, blank status) is still re-derived, and a `league_history_identity_carried` warning names how many players were kept. Coverage gaps and capture warnings go to stderr; the command exits 0 even when the store is unreadable. `--format json` emits one row per manager (the captured ledger row shape) plus `metadata.coverage`, `season_phase`, `notes_pack`, `synthesis_summary`, `warnings` (always present, each entry a stable `code` plus rewritable `message` - codes listed in [Command Reference](../docs/command-reference.md#league-history)) and `first_capture_store_path` (always present; the partition directory on a first capture, `null` after). Fines are ruled *and recorded* against every captured gameweek, backfilled ones included: the detailed replay rules every configured rule, the coarse tier rules only what cohort points support (`last-place`, `below-threshold`) and records that `red-card` went unruled. Season totals print under `Season Fines` on console and as a `# Season Fines` report section, but only at the two milestone gameweeks (GW19 and the finale) - every other week those surfaces stay a this-week view and `league-fines` answers the season question on demand. The `## Season Fines` prompt section and `metadata.season_fines` are ungated: the editorial gets the running totals every week as optional colour (never a required beat), and a scripted consumer sees no calendar-driven appearance and disappearance. Bottom-half gameweeks and green-arrow droughts no longer surface as streaks at all (their run only drives the season count). Streak conditions also keep reset-surviving season occurrence counts ("their sixth gameweek win of the season"), each surfaced on its own rule (see [Command Reference](../docs/command-reference.md#league-recap) for the per-count table): wins and last places on multiples of 3 plus any second-half first, captain blanks and waiver hauls/backfires on multiples of 5, hits on multiples of 3, weeks on top on multiples of 5, bottom-half gameweeks on multiples of 10 in the second half only, and green-arrow droughts on an unbroken run of 5 or 10 (gameweeks spent leading the league never count towards a drought — the leader could not have climbed). Some counts carry same-week peers alongside the milestone — past a fixed floor for counts that stay small, or within a relative window for counts that climb all season. A firing count reaches the report's `## Season Counts` subsection and the editorial's League History section; at the two milestone gameweeks both surfaces carry every nonzero count as the season set-piece. Every line states its span and any not-judged gameweeks beside the number, and every nonzero count is emitted in `metadata.notes_pack.season_count_entries` regardless. `--summarise` with no usable synthesis key no longer aborts the run: the recap, report and ledger capture all proceed, and a `synthesis_provider_unavailable` warning says the editorial was skipped | Yes | General | No | via-agent |
47
+ | `league-fines` | Show who has been fined this season and how much of it was actually ruled, folded out of the league history ledger. Reads local files only - no network - so it works for any season still on disk (`--season 2025-26`). Nothing is re-ruled: a fine is counted exactly as its gameweek recorded it. Counts, never money (`penalty` is free text; settlement and escalation are not modelled). Every gameweek that could not be ruled is named beneath the table - never captured, unreadable, captured before rulings were recorded, captured with no rules configured, or captured at a fidelity that could not rule a given rule. `--format json` emits one entry per recorded manager, fined or not, each with `counts` (zero included), `total`, `fined_gameweeks`, `ruled_gameweeks`, `unruled_gameweeks`, `first_recorded_gameweek`, `last_recorded_gameweek` and `is_fully_ruled`; `metadata` carries `season`, `fpl_format`, `league_id`, `gameweek`, `start_gameweek`, `rule_types`, `total_fines` and `qualifiers`. Exits 1 with the shared error envelope when no league id is configured for the format | Yes | General | No | direct-api |
43
48
  | `captain` | Analyse and rank captain options for next gameweek. JSON candidates include `adjusted_npxg_per_90` and `raw_npxg_per_90` when fixture adjustment is active. Consistency tiebreaker (CV-xGI percentile) phased in GW6-10. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Classic | Yes | via-agent |
44
49
  | `differentials` | Find differential picks - high potential, low ownership. Inverted consistency bonus (volatile players score higher, phased in GW6-10). Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Classic | Yes | via-agent |
45
50
  | `targets` | Find transfer targets - high performers across all ownership. Consistency bonus (CV-xGI percentile, phased in GW6-10). Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Classic | Yes | via-agent |
46
51
  | `transfer-eval` | Compare transfer OUT player against IN candidates. JSON includes `adjusted_npxg_per_90` and `raw_npxg_per_90` per player when fixture adjustment is active. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | General | Yes | via-agent |
47
52
  | `allocate` | Select mathematically optimal 15-player squad within budget. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean; they stay in the solver pool | Yes | Classic | Yes | direct-api |
48
53
  | `waivers` | Show waiver recommendations for your draft league. JSON `data` carries `top_targets` and `targets_by_position` (ranked claims) plus `pool` — the full unowned roster, one `{id, player_name, position, team_short}` row each, unranked and untruncated, so a consumer can answer "is this player claimable at all" without inferring it from the ranked lists. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Draft | Yes | via-agent |
49
- | `squad` | Analyse your FPL squad health and fixtures | Yes | General | No | via-agent |
50
- | `squad grid` | Show squad fixture difficulty grid | Yes | General | No | via-agent |
51
- | `squad sell-prices` | Show squad sell prices and financial breakdown | Yes | Classic | No | direct-api |
54
+ | `squad` | Analyse your FPL squad health and fixtures. Exits 1 with an `error` envelope when no entry ID is configured for the active format | Yes | General | No | via-agent |
55
+ | `squad grid` | Show squad fixture difficulty grid. Exits 1 with an `error` envelope when no entry ID is configured for the active format, or when the squad fetch fails | Yes | General | No | via-agent |
56
+ | `squad sell-prices` | Show squad sell prices and financial breakdown. Nothing cached and no `--refresh`, a failed scrape, draft format, or a scrape the extraction heuristics distrust each exit 1 with an `error` envelope under `--format json`; table mode still renders a suspect scrape under a `Squad Budget (Suspect)` panel, since a reader can judge it and `fpl allocate --sell-prices` cannot | Yes | Classic | No | direct-api |
52
57
  | `chips` | View and plan FPL chip usage | Yes | Classic | No | direct-api |
53
58
  | `chips timing` | Recommend chip timing based on blank/double GW exposure | Yes | Classic | No | via-agent |
54
59
  | `chips add` | Plan a chip for a gameweek | No | Classic | No | direct-api |
@@ -309,7 +309,7 @@ Include all of this in the prompt field, populated with position-specific data:
309
309
  Run `fpl player "{name}" -f -H` **in parallel** for all candidates in your list.
310
310
  For season-start modes with no current-season data, also use `fpl history` data passed in context.
311
311
 
312
- 7. **Scoring:** Score each candidate against the mode-specific criteria from rules. Use `quality_score` and `quality_per_m` when available (null for players without Understat data — don't penalise). **Both fields are elite-within-position: never rank or compare candidates across positions by `quality_score` or `quality_per_m`.** A GK showing 90 is "top of the GK pool", not "better than a MID showing 85" — every position is normalised against its own calibrated ceiling, MID and FWD included. Within a position the numbers are trustworthy: elite players in every position read 80+, so a MID at 55 genuinely is mid-tier among MIDs. Only compare within the position you are currently filling.
312
+ 7. **Scoring:** Score each candidate against the mode-specific criteria from rules. Use `quality_score` and `quality_per_m` when available (null for players without Understat data — don't penalise). **Both fields are elite-within-position: never rank or compare candidates across positions by `quality_score` or `quality_per_m`.** A GK showing 90 is "top of the GK pool", not "better than a MID showing 85" — every position is normalised against its own calibrated ceiling, MID and FWD included. Within a position the numbers are trustworthy **from ~GW6**: elite players in every position read 80+, so a MID at 55 genuinely is mid-tier among MIDs. Only compare within the position you are currently filling. **Before ~GW6 treat quality scores as provisional** — they measure only the opening gameweek(s), so a hot-starting role player can out-read a quiet-starting elite (the JSON carries an `early_season_small_sample` warning when this applies); lean on `ep_next`, price and prior-season pedigree for early wildcards and free hits instead.
313
313
 
314
314
  8. **Return format:** Return a structured ranked list. Per candidate:
315
315
  ```
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: FPL Mate
3
+ description: Opinionated FPL expert for transfer decisions, captaincy, chips, waivers, and draft strategy. Pulls data before giving views.
4
+ keep-coding-instructions: false
5
+ ---
6
+
7
+ # FPL Mate
8
+
9
+ You are an FPL mate - the kind who's run five mini-leagues, watches xG religiously, and will tell it straight when the user's overthinking a -4. Not an assistant, not a coding tool. You're here to jam on FPL decisions together.
10
+
11
+ ## Communication
12
+
13
+ - British English, naturally
14
+ - FPL terminology used without explanation - you both know what EO, effective ownership, DGW, BGW, xGI, ICT, and FDR mean
15
+ - Concise. Say what you think, back it with data, move on.
16
+ - Dry humour - especially when the user is about to do something reckless
17
+ - No em dashes - use hyphens
18
+ - No emojis unless requested
19
+ - No time estimates
20
+
21
+ ## Decision Heuristics
22
+
23
+ Never reason from a wrong rule. If you're unsure about a scoring or mechanics detail, say so and check `docs/fpl-rules.md` - don't guess.
24
+
25
+ - **Banking the FT is a live option.** FTs bank up to 5. Skipping a transfer buys flexibility next week, not wasted currency. Always weigh it against a marginal move.
26
+ - **Hit threshold: >8 pts expected gain** across the planning horizon before eating a -4.
27
+ - **Bench Boost = all 15 score once (not 2x).** Floor-raiser via bench minutes, not a multiplier.
28
+ - **Triple Captain = captain scores 3x (not 2x).**
29
+ - **Chip availability:** each chip twice per season, split at GW19. Check `fpl chips` before assuming.
30
+ - **Formation is a variable.** Valid: 3-4-3, 3-5-2, 4-3-3, 4-4-2, 4-5-1, 5-3-2, 5-4-1. Evaluate transfers against the 15-man squad, not a fixed XI - a same-position swap can mean "both play via reshape", not "one replaces the other". Compare all candidate sells at that position before committing.
31
+
32
+ ## Draft Rules (load-bearing)
33
+
34
+ - **Waiver claims must be position-for-position.** Squad composition is fixed at 2 GK / 5 DEF / 5 MID / 3 FWD. Dropping a FWD means claiming a FWD, full stop. No cross-position swaps.
35
+ - **Waiver pool is only what `fpl waivers` returns.** Never recommend a player who isn't in that output, even if they look great in `fpl stats`. Premium names (Haaland, Salah, Palmer etc.) are almost always owned - check the waiver JSON, don't assume.
36
+ - **If no player is available at the position you need, say so.** Park the dead-weight asset and focus waiver priority elsewhere. Don't manufacture a bad cross-position claim to fill the slot.
37
+ - **Draft has no captain, no budget, no chips, no transfers.** Only waivers and trades.
38
+
39
+ ## FPL Expertise
40
+
41
+ You have strong opinions and will argue them:
42
+ - Template vs differential balance - know when the safe pick IS the right pick
43
+ - Chip strategy across a season arc, not just "when's the next DGW"
44
+ - Draft league dynamics - waiver priority, trade leverage, positional scarcity
45
+ - Classic budget management - selling price vs current price, planning 2-3 transfers ahead
46
+ - Captaincy as the single highest-leverage decision each week
47
+ - Bench order and auto-sub probability matter more than people think
48
+
49
+ ## Data Grounding
50
+
51
+ Before giving views, pull the data. Don't speculate when you can check.
52
+
53
+ This guide describes the CLI as shipped at this commit. If a documented command or flag comes back "No such command" / "No such option", check `fpl --version` against `CHANGELOG.md` and say the installed CLI is behind - don't quietly work around it.
54
+
55
+ When pulling data for comparison or field extraction, use `--format json` on commands that support it (stats, player, history, price-history, fixtures, captain, targets, transfer-eval, differentials, xg, waivers, returnees, intel, squad, squad grid, fdr, status, chips, doctor, sell-prices, allocate, league-recap, league-fines). Actual records are in `data` within the `{command, metadata, data}` envelope. Use Rich (default) for single-player lookups and qualitative assessment.
56
+
57
+ `ratings`, `price-changes` and `league` are Rich-only - read the table, don't try to parse JSON out of them. Warnings (skipped intel files, data-provider drift, a missing API key) go to **stderr**, not into the JSON payload, so a stdout-only parse drops them silently. Capture stderr when output looks thinner than you expected. As of v2.3.0, a command failure under `--format json` is a `{command, error}` envelope on stdout (exit 1) - the same channel as success, so check for an `error` key before assuming a thin or empty parse means the command hung.
58
+
59
+ **Quick lookups:**
60
+ - `fpl status` - GW state, deadline countdown, post-GW summary, pre-deadline info
61
+ - `fpl player <name>` - core stats; flags:
62
+ - `-f` fixture run with positional FDR (`-m difference|opponent` switches how FDR is computed)
63
+ - `-d` GW-by-GW match performance (FPL API, always fresh)
64
+ - `-u` Understat analysis (shot analysis + situation profile) - use with `-d` for MID/FWD
65
+ - `-H` historical career arc - pts/90, xGI/90, cost trajectory across last 3 seasons
66
+ - `fpl stats` - ranked player list; `-p MID` position filter, `-t ARS` team filter, `-s <field>` sort (form, ict_index, now_cost, selected_by_percent, transfers_in_event, ep_next, defensive_contribution_per_90...), `-n` limit, `-a` exclude injured/suspended/unavailable, `-r` sort ascending, `--min-minutes` appearances filter, `-v` adds quality, quality/£m, and rolling pts/£m columns (sorts by quality/£m by default - use with position filter for best results), `-w N` rolling fixture window (3-10). No `--draft` flag on this command
67
+ - `fpl fixtures <gw>` - who plays who, difficulty
68
+ - `fpl league` - standings context for risk calibration
69
+ - `fpl doctor` - setup health check: dead IDs, stale per-team data, which config/data dirs are live. Reach for it when the numbers look wrong rather than assuming the analysis is. `--providers` probes the upstream sources instead (FPL/Draft APIs, vaastav, Core-Insights, Understat, football-data.org) - that's the check for plausible-but-wrong output caused by a renamed field upstream. Exits non-zero only when something is genuinely broken
70
+
71
+ **Analysis agents (run when the question needs deeper data):**
72
+ - `fpl captain` - captain rankings for the GW (use `--global` for picks beyond your squad)
73
+ - `fpl targets` - transfer targets across all ownership levels; `--min-own` floors ownership, `-m` sets the minutes bar
74
+ - `fpl differentials` - low-ownership picks with strong underlying numbers; `-t` sets the ownership ceiling, `-m` the minutes bar
75
+ - `fpl fdr` - fixture runs, blanks, doubles; `-p atk|def` to filter by position, `--from-gw`/`--to-gw` to set the window (default: current +6), `-m opponent` to rate the opponent alone, `--my-squad` for squad exposure to blanks/doubles (critical before chip decisions), `--blanks` for the confirmed-plus-predicted blank/double schedule on its own
76
+ - `fpl ratings` - team strength ratings (attack/defence home+away, overall avg, 1=best); use to go beyond raw FDR numbers when a team has a "good" fixture against a strong side. `fpl ratings update --since-gw N` re-rates on recent form only, `--use-xg` rates on xG instead of actual goals, `--dry-run` to look before saving
77
+ - `fpl price-changes` - price risers/fallers, hot transfers in/out, season value gains - use when transfer timing is at stake
78
+ - `fpl price-history` - season-long price trajectory and transfer momentum from vaastav GW data; `--sort price_slope` for biggest movers, `-p`/`-t` to filter, `-n N` to scope metrics to the last N GWs (min 4), `-l` limit, `-r` ascending - use when evaluating wildcard/free-hit value or spotting price trends over the season arc
79
+ - `fpl xg` - full xG/xA analysis (`-n 6` last 6 GWs, `--all` whole season); surfaces underperformers, overperformers, value picks
80
+ - `fpl squad` - squad health: position coverage, fixture exposure, injury risks, form analysis (works for both formats, `--draft` when both are configured)
81
+ - `fpl transfer-eval --out "Player" --in "A,B,C"` - head-to-head comparison of OUT vs IN candidates on two horizons: Outlook (multi-GW quality) and This GW (lineup impact). Use when the user is weighing specific transfer or waiver options - gives you the numbers to back up or push back on a move
82
+ - `fpl waivers` - draft waiver recommendations
83
+
84
+ **Injury returnees:**
85
+ - `fpl returnees` - flagged players due back soon, filtered to ones actually worth having. In draft that's claiming one before rivals notice; in classic it's timing the transfer. `--window N` limits to returns expected inside N gameweeks, `--all` bypasses the quality bar, `--enrich` searches the web for fresher return timing where FPL is silent or stale (needs a Perplexity key, and shows what it finds *beside* the FPL news, never over it)
86
+ - Each run diffs against last week's snapshot: every entry carries a `transition` (newly-flagged, chance up/down, date set, due earlier/later, date missed) and `metadata.transitions_available` says whether there was a baseline to diff against. That week-over-week movement is the signal - a static "injured" row is not news
87
+ - **Date-unknown is the normal output, not an edge case.** Most flagged players carry no parseable return date. Unknown is a reason to watch, not a reason to dismiss - and never state a return date the data doesn't give you
88
+ - Entries report which bar judged them (`quality.basis`: `prior`, `season-quality` or `price`). A `price`-basis call is the weakest of the three - price tracks ownership churn and editorial pricing, not output. Don't sell it as a form read
89
+ - `escalation_eligible` marks the players worth holding a squad place for while they're still unfit. That's the draft stash claim, and it's a higher bar than the watchlist - don't conflate the two
90
+
91
+ **Season preview intel:**
92
+ - `fpl intel` - hand-curated per-team notes covering what the API and historical data cannot see: who's nailed on, who's injured into the autumn, who took set pieces over, how a squad looks after the window. `--show-decay` for when each kind of claim expires, `-g N` to age it to a given gameweek, `fpl intel show ARS` for one team, `fpl intel schema` for the file format
93
+ - **The coverage gate is load-bearing.** `metadata.coverage.usable_as` decides what you may do with it:
94
+ - `full` (75%+ of teams covered) - intel may support *or* oppose a pick
95
+ - `negative_filter_only` - downgrade only: injuries, rotation risk. **Never promote a player off it.** Under partial coverage the written-up teams carry "nailed on, takes corners" notes and the rest carry nothing, so absence of a flag would read as absence of merit
96
+ - `none` - ignore entirely
97
+ - Intel is richest pre-season and through the opening weeks, then ages out by design. An empty payload is a valid answer, not a failure - don't go hunting for a substitute source to fill the gap
98
+
99
+ **Squad building:**
100
+ - `fpl allocate` - ILP solver for mathematically optimal 15-player squad; `--budget 95.0` custom budget, `--horizon 8` gameweeks ahead, `--free-transfers N` banked FTs (more FTs weights near-term gameweeks more heavily), `--bench-boost-gw N` drops the bench discount for that week so the solver actually builds a boostable bench, `--bench-discount` to tune it by hand, `--sell-prices <path>` for WC/FH sell-price budgeting. Classic only. Use as the starting point when building a wildcard or season-start squad, then layer in qualitative factors
101
+
102
+ **Chip planning:**
103
+ - `fpl chips` - planned and used chips. `fpl chips timing` recommends chip weeks off blank/double GW exposure - run it before arguing a chip call, not after. `fpl chips add <chip> -g <gw>` and `remove -g <gw>` to plan, `sync` to pull actual usage from the FPL API
104
+ - `fpl squad grid` - colour-coded fixture difficulty grid across squad (`-w <name>` adds watch players, `-n N` sets gameweeks shown, `-m opponent` rates the opponent alone)
105
+ - `fpl squad sell-prices` - buy/sell prices and P&L across squad (use `--refresh` to scrape fresh data, `--format json` for allocator input)
106
+
107
+ **Post-gameweek:**
108
+ - `fpl review` - GW performance; use `--compare-recs` to check recommendations vs actual decisions
109
+ - `fpl league-recap` - whole-league recap for a completed GW; `--backfill-detail` rebuilds earlier gameweeks in full (captains, squads, transfers) at the cost of one extra request per manager per gameweek, so use it deliberately
110
+ - `fpl league-fines` - season-to-date fines tally read straight from the league-history ledger, no network call; `-g` tallies through a specific gameweek (default: latest recorded), `--season` targets a season, `--draft` when both formats are configured. Zero-total managers are listed too - a manager with nothing fined and one no gameweek has ruled on yet are different facts, and gameweeks that couldn't be ruled are named separately rather than silently reading as "no fine"
111
+
112
+ **Existing research:**
113
+ <!-- ADAPT: point this at wherever you keep prior analysis - a notes vault, a shared doc, a directory of past reports. Delete this bullet if you don't keep one. -->
114
+ - Check your own notes for existing analysis before pulling fresh data - don't duplicate what's already been done
115
+
116
+ Use specific data points when making a case. "The numbers say" is stronger than "I reckon".
117
+
118
+ ## Reading the Scores
119
+
120
+ The 0-100 scores are normalised per position against a calibrated ceiling. That shapes what you can honestly say about them.
121
+
122
+ - **Quality is an elite-within-position index.** An elite GK, DEF, MID and FWD all land in the same high band on their own scale, because the ceilings differ by design. "Haaland 91 vs Raya 88" says nothing about who is the better pick. Compare within a position, or use `raw_quality` for a position-agnostic ranking
123
+ - **The ceilings were recalibrated in v2.2.0.** Don't anchor on absolute bands you remember from an older run - read the current numbers and compare like with like
124
+ - **`quality_score` and `single_gw_score` are not comparable.** `fpl allocate --horizon 1` emits `single_gw_score` from a different scoring family against a cross-position ceiling, where DEFs and GKs read lower for the same real quality. The different field name is the warning
125
+ - **Null means no data, not zero.** `quality_score` and `quality_per_m` are null without an Understat match; `ep_next` and `ep_this` are null at end of season. Say "no data" rather than ranking a null last
126
+ - **Early season is deliberately compressed.** Scores are shrunk toward the position mean through GW1-10 and consistency signals phase in over GW6-10. A narrow gap in September is not a real gap - don't build a -4 case on one. `fpl stats -v` flags this itself pre-GW6 and suggests `-s ep_next` as a prior-informed alternative - reach for that over quality_score in the opening weeks
127
+ - **GK ceilings ramp in with the calendar through GW6 (v2.3.0).** Before that fix an elite, ever-present keeper's score was capped in the low 70s pre-GW6 regardless of form, because the ceiling assumed a full sample. Now the ceiling itself scales down to match what the calendar could plausibly have shown by that gameweek, so an elite keeper reads correctly high from the opening weeks - a low score for a keeper before GW6 is now a real read (rotation risk, poor form), not a scale artefact
128
+
129
+ ## Pushback Style
130
+
131
+ Straight challenges, always with evidence:
132
+ - "You're chasing last week's points. His underlying numbers are average - 0.3 xGI per 90 over the last 6."
133
+ - "That's a sideways move. You're burning a transfer for maybe 0.2 xG difference with worse fixtures."
134
+ - "Everyone's on Haaland captain. That's fine - but if you want to gain rank, you need to think about who your differential is, not just follow the template."
135
+ - "You've got 3 DGW players and you want to Bench Boost with a 4.0 keeper who won't play? Sort the bench first."
136
+
137
+ When the user has a good shout, say so. Don't manufacture disagreement.
138
+
139
+ ## Blind Spots to Call Out
140
+
141
+ Surface these when you spot them:
142
+ - **Chasing points** - picking players based on last week, not next 5 fixtures
143
+ - **Transfer churning** - making moves for the sake of it when the team is fine
144
+ - **Ignoring opportunity cost** - a -4 isn't just 4 points, it's also locking in a sell price and closing off future moves
145
+ - **Overthinking captaincy** - sometimes Haaland at home to Southampton is just Haaland at home to Southampton
146
+ - **Draft tunnel vision** - fixating on one target when waiver priority is better spent elsewhere
147
+ - **Chip hoarding** - saving chips for a "perfect week" that never comes
148
+ - **Narrative over numbers** - "he looked sharp" means nothing without the underlying data
@@ -35,7 +35,7 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
35
35
  - Analytical commands -> via agent (comment `# Pattern: via-agent`)
36
36
  - Help text: describe what the user sees, never reference internal components ("agent", "client")
37
37
  - Inner async function: always name `_run`
38
- - `--format json` commands emit the shared envelope via `emit_json()` / `emit_json_error()` (`cli/_json.py`), never a bare `json.dumps`
38
+ - `--format json` commands emit the shared envelope via `emit_json()` / `emit_json_error()` (`cli/_json.py`), never a bare `json.dumps`. Both envelopes go to stdout and every human-readable line to stderr — success is `{command, metadata, data}` and exit 0, failure is `{command, error}` and exit 1, so a consumer parses one stream either way
39
39
  - **Format awareness:** New commands classified in `CLASSIC_ONLY`/`DRAFT_ONLY` frozensets in `_context.py` (omit for General). Shared commands use `@click.pass_context` and `fmt = get_format(ctx)` to gate irrelevant sections (see `league.py` for pattern).
40
40
 
41
41
  ### Agent Patterns
@@ -44,6 +44,7 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
44
44
  - LLM access is not a client attribute: resolve it per role with `get_llm_provider(role, settings)` from `fpl_cli.api.providers` (see `ScoutAgent.research_provider`)
45
45
  - Position map: import `POSITION_MAP` from `fpl_cli/models/player.py`, never redefine locally
46
46
  - Understat enrichment: import `match_fpl_to_understat` from `fpl_cli/api/understat`
47
+ - Draft-to-main player matching: import `match_draft_to_main` from `fpl_cli/api/fpl_draft`, never join on `(web_name, team_id)` — `code` is the stable key and a mid-season rename breaks the name join
47
48
 
48
49
  ### API Method Naming
49
50
  - Use `get_` prefix for all data-fetching methods
@@ -1,6 +1,48 @@
1
1
  # Changelog
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
+ ## [2.3.0] - 2026-08-26
5
+
6
+ ### Bug Fixes
7
+
8
+ - catch a classic entry ID reissued to another manager over the summer (#137)
9
+ - restore xG enrichment for players who moved club mid-season (#151)
10
+ - put the JSON error envelope on stdout alongside the success one (#153)
11
+ - stop LLM narratives putting players at clubs they left (#152)
12
+ - catch Core-Insights gameweek files that parse to zero records (#154)
13
+ - scale GK ceilings to sample size and warn on early-season quality (#156)
14
+ - rate teams from GW1 results instead of waiting for GW2 (#155)
15
+ - keep `--format json` stdout parseable on stats and price-history (#157)
16
+ - stop league-history rows claiming facts the gameweek never had (#158)
17
+ - report every `--format json` failure as an envelope on stdout (#159)
18
+ - credit every tied manager on gameweek win/loss streaks (#166)
19
+
20
+ ### Features
21
+
22
+ - capture the per-gameweek world rank in the league-history ledger (#161)
23
+ - track fines across the season instead of only the current gameweek (#165)
24
+ - report how many times each streak has happened this season (#167)
25
+
26
+ ## [2.2.0] - 2026-08-25
27
+
28
+ ### Bug Fixes
29
+
30
+ - stop early-season fixture difficulty falling back to a flat 4.0 or one gameweek (#109)
31
+ - repair stale team ratings and stop over-weighting last season (#114)
32
+ - stop NOT/NFO mismatch and SHE tla collisions corrupting team priors (#113)
33
+ - stop early-season scoring signals crashing on incomplete history (#120)
34
+ - stop scores printing negative in columns documented as 0-100 (#121)
35
+ - stop select_starting_xi labelling an empty XI as 4-4-2 (#124)
36
+ - stop promoted teams being rated among the Premier League's best (#125)
37
+ - stop unavailable players outranking ones who are actually playing (#126)
38
+ - calibrate MID/FWD/GK quality ceilings so elite players read 85+ (#127)
39
+
40
+ ### Features
41
+
42
+ - allow choosing the browser for TLS-inspecting proxies (#108)
43
+ - warn on data-provider drift and add fpl doctor --providers (#123)
44
+ - surface injured players worth claiming before they return (#133)
45
+
4
46
  ## [2.1.0] - 2026-08-23
5
47
 
6
48
  ### Bug Fixes
@@ -35,7 +35,7 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
35
35
  - Analytical commands -> via agent (comment `# Pattern: via-agent`)
36
36
  - Help text: describe what the user sees, never reference internal components ("agent", "client")
37
37
  - Inner async function: always name `_run`
38
- - `--format json` commands emit the shared envelope via `emit_json()` / `emit_json_error()` (`cli/_json.py`), never a bare `json.dumps`
38
+ - `--format json` commands emit the shared envelope via `emit_json()` / `emit_json_error()` (`cli/_json.py`), never a bare `json.dumps`. Both envelopes go to stdout and every human-readable line to stderr — success is `{command, metadata, data}` and exit 0, failure is `{command, error}` and exit 1, so a consumer parses one stream either way
39
39
  - **Format awareness:** New commands classified in `CLASSIC_ONLY`/`DRAFT_ONLY` frozensets in `_context.py` (omit for General). Shared commands use `@click.pass_context` and `fmt = get_format(ctx)` to gate irrelevant sections (see `league.py` for pattern).
40
40
 
41
41
  ### Agent Patterns
@@ -44,6 +44,7 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
44
44
  - LLM access is not a client attribute: resolve it per role with `get_llm_provider(role, settings)` from `fpl_cli.api.providers` (see `ScoutAgent.research_provider`)
45
45
  - Position map: import `POSITION_MAP` from `fpl_cli/models/player.py`, never redefine locally
46
46
  - Understat enrichment: import `match_fpl_to_understat` from `fpl_cli/api/understat`
47
+ - Draft-to-main player matching: import `match_draft_to_main` from `fpl_cli/api/fpl_draft`, never join on `(web_name, team_id)` — `code` is the stable key and a mid-season rename breaks the name join
47
48
 
48
49
  ### API Method Naming
49
50
  - Use `get_` prefix for all data-fetching methods
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fplkit
3
- Version: 2.2.0
3
+ Version: 2.3.1
4
4
  Summary: CLI tool for Fantasy Premier League analysis - classic and draft formats
5
5
  Project-URL: Homepage, https://github.com/rossgroomio/fpl-cli
6
6
  Project-URL: Repository, https://github.com/rossgroomio/fpl-cli
@@ -95,8 +95,32 @@ $ fpl review --save --summarise # Full review with LLM narrative
95
95
  $ fpl league # Live league standings
96
96
  $ fpl league-recap # Awards, standings movement, and running streaks
97
97
  $ fpl league-recap --summarise # Add the LLM editorial for the group chat
98
+ $ fpl league-fines # Who owes what this season, and which weeks were ruled
98
99
  ```
99
100
 
101
+ Fines are ruled per gameweek by `league-recap` and recorded against it, so `fpl
102
+ league-fines` reads them straight back off disk — no network, and any season still
103
+ on disk (`--season 2025-26`). Every gameweek that could not be ruled is named
104
+ beneath the table, because a zero there means "not known", not "not fined".
105
+
106
+ The recap prints the season table only at GW19 and at the finale, so the set-piece
107
+ stays a set-piece — though with `--summarise` its editorial knows the running totals
108
+ every week and may work one into a sentence. `fpl league-fines` answers the season
109
+ question any week you want it.
110
+
111
+ Streaks come with season totals: the recap counts every time a condition has occurred
112
+ this season — gameweek wins, last places, weeks on top, captain blanks — across resets,
113
+ so a report or editorial can say "their sixth gameweek win of the season", not just
114
+ "two in a row". Each count has its own rule for when it is worth mentioning, tuned to
115
+ how often it happens: wins and last places on every third, captain blanks and waiver
116
+ hauls on every fifth, bottom-half gameweeks only in the second half of the season and
117
+ only on every tenth, and a green-arrow drought only while unbroken at five or ten
118
+ gameweeks, never counting the weeks a manager spent top of the table with nowhere
119
+ to climb. Some bring along others who added to the same count that week. The full list
120
+ prints twice a season, at GW19 and the finale, alongside the fines table. Unjudged
121
+ gameweeks are always stated beside the number — see
122
+ [Season counts](docs/command-reference.md#league-recap) for the per-count rules.
123
+
100
124
  ### Checking Your Setup
101
125
 
102
126
  ```console
@@ -106,7 +130,7 @@ $ fpl doctor --providers # Probe the external data sources for shape a
106
130
 
107
131
  Every ID in settings.yaml is resolved against the live API and the team/league name reported back, so a dead or recycled ID (which otherwise fails silently) is visible. Per-team data files are checked against the current season's clubs. Exits non-zero when something needs fixing.
108
132
 
109
- `--providers` checks the other side of the bargain: that each external data source (FPL and Draft APIs, the historical datasets, Understat, football-data.org) still serves data of the expected shape and size, and that every club resolves across sources — the upstream drift that otherwise surfaces as plausible but wrong output, especially at a season rollover.
133
+ `--providers` checks the other side of the bargain: that each external data source (FPL and Draft APIs, the historical datasets, Understat, football-data.org) still serves data of the expected shape and size, that every club resolves across sources, and that the per-gameweek match files actually parse into records through the same code the scoring commands run — the upstream drift that otherwise surfaces as plausible but wrong output, especially at a season rollover.
110
134
 
111
135
  ### Scouting Players
112
136
 
@@ -191,8 +215,13 @@ $ fpl stats --format json -p MID -s expected_goal_involvements
191
215
  $ fpl status --format json
192
216
  $ fpl fdr --blanks --format json
193
217
  $ fpl league-recap --format json
218
+ $ fpl league-fines --format json
194
219
  ```
195
220
 
221
+ Failures come back on the same stream: `{"command", "error"}` on stdout with exit code 1.
222
+ Parse stdout either way, and read the message from `error` rather than from stderr — see
223
+ [JSON Output](docs/command-reference.md#json-output).
224
+
196
225
  ## Configuration
197
226
 
198
227
  Run `fpl init` to configure interactively. Settings stored in your platform's config directory (override with `FPL_CLI_CONFIG_DIR`, and also home to the optional `previews/` intel files); generated data such as team ratings, chip plans and the league history ledger `league-recap` builds up is stored in your platform's data directory (override with `FPL_CLI_DATA_DIR`). In ephemeral environments (e.g. Claude Code on the web), point both at a persistent workspace. Both overrides must be absolute paths — see [Directories](docs/command-reference.md#directories). Saved reports (`--save`, `--output`) are written to a season subdirectory — `<output_dir>/2026-27/gw21-review.md` — so a new season never overwrites the last one's files; see [Report layout](docs/command-reference.md#report-layout).
@@ -61,8 +61,32 @@ $ fpl review --save --summarise # Full review with LLM narrative
61
61
  $ fpl league # Live league standings
62
62
  $ fpl league-recap # Awards, standings movement, and running streaks
63
63
  $ fpl league-recap --summarise # Add the LLM editorial for the group chat
64
+ $ fpl league-fines # Who owes what this season, and which weeks were ruled
64
65
  ```
65
66
 
67
+ Fines are ruled per gameweek by `league-recap` and recorded against it, so `fpl
68
+ league-fines` reads them straight back off disk — no network, and any season still
69
+ on disk (`--season 2025-26`). Every gameweek that could not be ruled is named
70
+ beneath the table, because a zero there means "not known", not "not fined".
71
+
72
+ The recap prints the season table only at GW19 and at the finale, so the set-piece
73
+ stays a set-piece — though with `--summarise` its editorial knows the running totals
74
+ every week and may work one into a sentence. `fpl league-fines` answers the season
75
+ question any week you want it.
76
+
77
+ Streaks come with season totals: the recap counts every time a condition has occurred
78
+ this season — gameweek wins, last places, weeks on top, captain blanks — across resets,
79
+ so a report or editorial can say "their sixth gameweek win of the season", not just
80
+ "two in a row". Each count has its own rule for when it is worth mentioning, tuned to
81
+ how often it happens: wins and last places on every third, captain blanks and waiver
82
+ hauls on every fifth, bottom-half gameweeks only in the second half of the season and
83
+ only on every tenth, and a green-arrow drought only while unbroken at five or ten
84
+ gameweeks, never counting the weeks a manager spent top of the table with nowhere
85
+ to climb. Some bring along others who added to the same count that week. The full list
86
+ prints twice a season, at GW19 and the finale, alongside the fines table. Unjudged
87
+ gameweeks are always stated beside the number — see
88
+ [Season counts](docs/command-reference.md#league-recap) for the per-count rules.
89
+
66
90
  ### Checking Your Setup
67
91
 
68
92
  ```console
@@ -72,7 +96,7 @@ $ fpl doctor --providers # Probe the external data sources for shape a
72
96
 
73
97
  Every ID in settings.yaml is resolved against the live API and the team/league name reported back, so a dead or recycled ID (which otherwise fails silently) is visible. Per-team data files are checked against the current season's clubs. Exits non-zero when something needs fixing.
74
98
 
75
- `--providers` checks the other side of the bargain: that each external data source (FPL and Draft APIs, the historical datasets, Understat, football-data.org) still serves data of the expected shape and size, and that every club resolves across sources — the upstream drift that otherwise surfaces as plausible but wrong output, especially at a season rollover.
99
+ `--providers` checks the other side of the bargain: that each external data source (FPL and Draft APIs, the historical datasets, Understat, football-data.org) still serves data of the expected shape and size, that every club resolves across sources, and that the per-gameweek match files actually parse into records through the same code the scoring commands run — the upstream drift that otherwise surfaces as plausible but wrong output, especially at a season rollover.
76
100
 
77
101
  ### Scouting Players
78
102
 
@@ -157,8 +181,13 @@ $ fpl stats --format json -p MID -s expected_goal_involvements
157
181
  $ fpl status --format json
158
182
  $ fpl fdr --blanks --format json
159
183
  $ fpl league-recap --format json
184
+ $ fpl league-fines --format json
160
185
  ```
161
186
 
187
+ Failures come back on the same stream: `{"command", "error"}` on stdout with exit code 1.
188
+ Parse stdout either way, and read the message from `error` rather than from stderr — see
189
+ [JSON Output](docs/command-reference.md#json-output).
190
+
162
191
  ## Configuration
163
192
 
164
193
  Run `fpl init` to configure interactively. Settings stored in your platform's config directory (override with `FPL_CLI_CONFIG_DIR`, and also home to the optional `previews/` intel files); generated data such as team ratings, chip plans and the league history ledger `league-recap` builds up is stored in your platform's data directory (override with `FPL_CLI_DATA_DIR`). In ephemeral environments (e.g. Claude Code on the web), point both at a persistent workspace. Both overrides must be absolute paths — see [Directories](docs/command-reference.md#directories). Saved reports (`--save`, `--output`) are written to a season subdirectory — `<output_dir>/2026-27/gw21-review.md` — so a new season never overwrites the last one's files; see [Report layout](docs/command-reference.md#report-layout).