fplkit 1.4.2__tar.gz → 2.0.0__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 (233) hide show
  1. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/TOOLS.md +2 -2
  2. {fplkit-1.4.2 → fplkit-2.0.0}/CHANGELOG.md +12 -0
  3. {fplkit-1.4.2 → fplkit-2.0.0}/PKG-INFO +1 -1
  4. {fplkit-1.4.2 → fplkit-2.0.0}/docs/command-reference.md +1 -1
  5. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/_version.py +2 -2
  6. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_helpers.py +5 -1
  7. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/player.py +11 -3
  8. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/stats.py +10 -5
  9. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/models/player.py +2 -2
  10. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_format_suppression.py +2 -2
  11. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_player.py +44 -0
  12. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_stats.py +56 -0
  13. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/README.md +0 -0
  14. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/gw-prep/SKILL.md +0 -0
  15. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/gw-prep/references/output-template.md +0 -0
  16. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/gw-prep/references/rules.md +0 -0
  17. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/gw-prep/scripts/bench_order.py +0 -0
  18. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/gw-prep/scripts/extract_classic_squad.py +0 -0
  19. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/gw-prep/scripts/starting_xi.py +0 -0
  20. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/gw-prep/scripts/transfer_eval.py +0 -0
  21. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/gw-prep/scripts/validate_draft_waivers.py +0 -0
  22. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/squad-builder/SKILL.md +0 -0
  23. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/squad-builder/references/output-template.md +0 -0
  24. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/squad-builder/references/rules.md +0 -0
  25. {fplkit-1.4.2 → fplkit-2.0.0}/.agents/skills/update-gw-prep/SKILL.md +0 -0
  26. {fplkit-1.4.2 → fplkit-2.0.0}/.claude/output-styles/fpl-mate.md +0 -0
  27. {fplkit-1.4.2 → fplkit-2.0.0}/.claude/settings.json +0 -0
  28. {fplkit-1.4.2 → fplkit-2.0.0}/.env.example +0 -0
  29. {fplkit-1.4.2 → fplkit-2.0.0}/.github/workflows/ci.yml +0 -0
  30. {fplkit-1.4.2 → fplkit-2.0.0}/.github/workflows/release.yml +0 -0
  31. {fplkit-1.4.2 → fplkit-2.0.0}/.gitignore +0 -0
  32. {fplkit-1.4.2 → fplkit-2.0.0}/AGENTS.md +0 -0
  33. {fplkit-1.4.2 → fplkit-2.0.0}/CLAUDE.md +0 -0
  34. {fplkit-1.4.2 → fplkit-2.0.0}/LICENSE +0 -0
  35. {fplkit-1.4.2 → fplkit-2.0.0}/README.md +0 -0
  36. {fplkit-1.4.2 → fplkit-2.0.0}/cliff.toml +0 -0
  37. {fplkit-1.4.2 → fplkit-2.0.0}/config/team_managers.yaml +0 -0
  38. {fplkit-1.4.2 → fplkit-2.0.0}/config/team_ratings.yaml +0 -0
  39. {fplkit-1.4.2 → fplkit-2.0.0}/config/team_ratings_overrides.yaml +0 -0
  40. {fplkit-1.4.2 → fplkit-2.0.0}/docs/architecture.md +0 -0
  41. {fplkit-1.4.2 → fplkit-2.0.0}/docs/custom-analysis.md +0 -0
  42. {fplkit-1.4.2 → fplkit-2.0.0}/docs/fpl-rules.md +0 -0
  43. {fplkit-1.4.2 → fplkit-2.0.0}/docs/images/fpl-player-demo-v1-0.png +0 -0
  44. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/__init__.py +0 -0
  45. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/__init__.py +0 -0
  46. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/action/__init__.py +0 -0
  47. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/action/waiver.py +0 -0
  48. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/analysis/__init__.py +0 -0
  49. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/analysis/bench_order.py +0 -0
  50. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/analysis/captain.py +0 -0
  51. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/analysis/squad_analyzer.py +0 -0
  52. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/analysis/starting_xi.py +0 -0
  53. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/analysis/stats.py +0 -0
  54. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/analysis/transfer_eval.py +0 -0
  55. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/base.py +0 -0
  56. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/common.py +0 -0
  57. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/data/__init__.py +0 -0
  58. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/data/fixture.py +0 -0
  59. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/data/price.py +0 -0
  60. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/data/scout.py +0 -0
  61. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/orchestration/__init__.py +0 -0
  62. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/agents/orchestration/report.py +0 -0
  63. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/__init__.py +0 -0
  64. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/core_insights.py +0 -0
  65. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/dataset_fetcher.py +0 -0
  66. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/football_data.py +0 -0
  67. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/fpl.py +0 -0
  68. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/fpl_draft.py +0 -0
  69. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/historical.py +0 -0
  70. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/historical_types.py +0 -0
  71. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/providers/__init__.py +0 -0
  72. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/providers/_models.py +0 -0
  73. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/providers/anthropic.py +0 -0
  74. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/providers/openai_compat.py +0 -0
  75. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/providers/perplexity.py +0 -0
  76. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/understat.py +0 -0
  77. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/api/vaastav.py +0 -0
  78. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/__init__.py +0 -0
  79. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_banner.py +0 -0
  80. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_context.py +0 -0
  81. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_fines.py +0 -0
  82. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_fines_config.py +0 -0
  83. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_json.py +0 -0
  84. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_league_recap_data.py +0 -0
  85. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_league_recap_types.py +0 -0
  86. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_plan_grid.py +0 -0
  87. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_review_analysis.py +0 -0
  88. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_review_classic.py +0 -0
  89. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_review_draft.py +0 -0
  90. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/_review_summarisation.py +0 -0
  91. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/allocate.py +0 -0
  92. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/captain.py +0 -0
  93. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/chips.py +0 -0
  94. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/credentials.py +0 -0
  95. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/differentials.py +0 -0
  96. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/fdr.py +0 -0
  97. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/fixtures.py +0 -0
  98. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/history.py +0 -0
  99. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/init.py +0 -0
  100. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/league.py +0 -0
  101. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/league_recap.py +0 -0
  102. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/preview.py +0 -0
  103. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/price_changes.py +0 -0
  104. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/price_history.py +0 -0
  105. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/ratings.py +0 -0
  106. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/review.py +0 -0
  107. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/sell_prices.py +0 -0
  108. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/squad.py +0 -0
  109. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/status.py +0 -0
  110. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/targets.py +0 -0
  111. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/transfer_eval.py +0 -0
  112. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/waivers.py +0 -0
  113. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/cli/xg.py +0 -0
  114. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/config/defaults.yaml +0 -0
  115. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/config/fixture_predictions.yaml +0 -0
  116. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/constants.py +0 -0
  117. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/models/__init__.py +0 -0
  118. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/models/chip_plan.py +0 -0
  119. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/models/fixture.py +0 -0
  120. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/models/team.py +0 -0
  121. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/models/types.py +0 -0
  122. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/parsers/__init__.py +0 -0
  123. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/parsers/recommendations.py +0 -0
  124. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/paths.py +0 -0
  125. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/prompts/__init__.py +0 -0
  126. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/prompts/league_recap.py +0 -0
  127. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/prompts/review.py +0 -0
  128. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/prompts/scout.py +0 -0
  129. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/scraper/__init__.py +0 -0
  130. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/scraper/fpl_prices.py +0 -0
  131. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/season.py +0 -0
  132. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/services/__init__.py +0 -0
  133. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/services/fixture_predictions.py +0 -0
  134. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/services/matchup.py +0 -0
  135. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/services/player_prior.py +0 -0
  136. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/services/player_scoring.py +0 -0
  137. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/services/squad_allocator.py +0 -0
  138. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/services/team_form.py +0 -0
  139. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/services/team_ratings.py +0 -0
  140. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/services/team_ratings_prior.py +0 -0
  141. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/templates/gw_league_recap.md.j2 +0 -0
  142. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/templates/gw_preview.md.j2 +0 -0
  143. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/templates/gw_review.md.j2 +0 -0
  144. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/utils/__init__.py +0 -0
  145. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/utils/text.py +0 -0
  146. {fplkit-1.4.2 → fplkit-2.0.0}/fpl_cli/utils/time.py +0 -0
  147. {fplkit-1.4.2 → fplkit-2.0.0}/pyproject.toml +0 -0
  148. {fplkit-1.4.2 → fplkit-2.0.0}/pyrightconfig.json +0 -0
  149. {fplkit-1.4.2 → fplkit-2.0.0}/requirements.lock +0 -0
  150. {fplkit-1.4.2 → fplkit-2.0.0}/tests/__init__.py +0 -0
  151. {fplkit-1.4.2 → fplkit-2.0.0}/tests/conftest.py +0 -0
  152. {fplkit-1.4.2 → fplkit-2.0.0}/tests/fixtures/classic_squad_fixture.md +0 -0
  153. {fplkit-1.4.2 → fplkit-2.0.0}/tests/fixtures/recommendations_fixture.md +0 -0
  154. {fplkit-1.4.2 → fplkit-2.0.0}/tests/fixtures/validate_draft_waivers/recs_clean.md +0 -0
  155. {fplkit-1.4.2 → fplkit-2.0.0}/tests/fixtures/validate_draft_waivers/squad_grid.json +0 -0
  156. {fplkit-1.4.2 → fplkit-2.0.0}/tests/fixtures/validate_draft_waivers/waivers.json +0 -0
  157. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_agents_action.py +0 -0
  158. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_agents_analysis.py +0 -0
  159. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_agents_analysis_squad.py +0 -0
  160. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_agents_base.py +0 -0
  161. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_agents_data.py +0 -0
  162. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_agents_data_price.py +0 -0
  163. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_agents_orchestration.py +0 -0
  164. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_allocate.py +0 -0
  165. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_api_client.py +0 -0
  166. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_api_core_insights.py +0 -0
  167. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_api_dataset_fetcher.py +0 -0
  168. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_api_draft.py +0 -0
  169. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_api_football_data.py +0 -0
  170. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_api_historical.py +0 -0
  171. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_api_understat.py +0 -0
  172. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_api_vaastav.py +0 -0
  173. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_bench_order_script.py +0 -0
  174. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_classic_squad_extraction.py +0 -0
  175. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_allocate.py +0 -0
  176. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_banner.py +0 -0
  177. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_captain.py +0 -0
  178. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_chips.py +0 -0
  179. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_credentials.py +0 -0
  180. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_custom_analysis.py +0 -0
  181. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_differentials.py +0 -0
  182. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_fdr.py +0 -0
  183. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_fdr_blanks.py +0 -0
  184. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_fixtures.py +0 -0
  185. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_format.py +0 -0
  186. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_history.py +0 -0
  187. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_init.py +0 -0
  188. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_json.py +0 -0
  189. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_plan_grid.py +0 -0
  190. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_preview.py +0 -0
  191. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_price_history.py +0 -0
  192. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_review.py +0 -0
  193. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_sell_prices.py +0 -0
  194. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_squad.py +0 -0
  195. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_status.py +0 -0
  196. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_targets.py +0 -0
  197. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_transfer_eval.py +0 -0
  198. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_waivers.py +0 -0
  199. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_warnings_stderr.py +0 -0
  200. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_cli_xg.py +0 -0
  201. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_common.py +0 -0
  202. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_common_scoring.py +0 -0
  203. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_data_prep_harmonisation.py +0 -0
  204. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_fines.py +0 -0
  205. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_fines_config.py +0 -0
  206. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_fixture_predictions.py +0 -0
  207. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_fpl_prices.py +0 -0
  208. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_init.py +0 -0
  209. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_init_fines.py +0 -0
  210. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_league_recap.py +0 -0
  211. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_matchup_service.py +0 -0
  212. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_models.py +0 -0
  213. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_paths.py +0 -0
  214. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_player_prior.py +0 -0
  215. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_player_resolution.py +0 -0
  216. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_player_scoring.py +0 -0
  217. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_providers.py +0 -0
  218. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_recommendations_parser.py +0 -0
  219. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_report_agent.py +0 -0
  220. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_review.py +0 -0
  221. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_rolling_pts_per_m.py +0 -0
  222. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_scraper.py +0 -0
  223. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_season.py +0 -0
  224. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_sell_prices.py +0 -0
  225. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_squad_allocator.py +0 -0
  226. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_starting_xi_agent.py +0 -0
  227. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_stats.py +0 -0
  228. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_team_ratings.py +0 -0
  229. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_team_ratings_prior.py +0 -0
  230. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_transfer_eval_agent.py +0 -0
  231. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_utils_text.py +0 -0
  232. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_utils_time.py +0 -0
  233. {fplkit-1.4.2 → fplkit-2.0.0}/tests/test_validate_draft_waivers.py +0 -0
@@ -27,8 +27,8 @@ Errors use `emit_json_error()` with `{"command", "error"}` shape.
27
27
  | `init` | Set up fpl-cli with your FPL IDs and optional features | No | General | No | direct-api |
28
28
  | `status` | Show FPL gameweek status and upcoming deadlines | Yes | General | No | direct-api |
29
29
  | `fixtures` | Show fixtures for a gameweek | Yes | General | No | direct-api |
30
- | `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); panel shows `xPts` (ep_next). `--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 |
31
- | `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); both in JSON output | Yes | General | No | direct-api |
30
+ | `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 |
31
+ | `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); 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 |
32
32
  | `history` | Show historical player performance across seasons | Yes | General | No | direct-api |
33
33
  | `league` | Show live league standings for Classic and Draft leagues | No | General | No | direct-api |
34
34
  | `fdr` | Analyse fixture difficulty - easy runs, blanks, doubles | Yes | General | No | via-agent |
@@ -1,6 +1,17 @@
1
1
  # Changelog
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
+ ## [1.4.2] - 2026-05-24
5
+
6
+ ### Bug Fixes
7
+
8
+ - feat(scout): bind player clubs to current-season reference (#40)
9
+ - feat(sell-prices refresh): Add retry logic to /api/me/ polling with hydration race handling (#39)
10
+
11
+ ### Scout
12
+
13
+ - enforce player_reference as a strict allowlist (#41)
14
+
4
15
  ## [1.4.1] - 2026-05-17
5
16
 
6
17
  ### Refactoring
@@ -114,3 +125,4 @@ All notable changes to this project will be documented in this file.
114
125
  ### Features
115
126
 
116
127
  - CI pipeline and PyPI publish for v1.0.0 (#1)
128
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fplkit
3
- Version: 1.4.2
3
+ Version: 2.0.0
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
@@ -379,7 +379,7 @@ The FPL API provides gameweek point predictions for every player:
379
379
  | **ep_next** | FPL's predicted points for the next gameweek. Shown as `xPts` on the Points/PPG panel line. Available as a `--sort` field in `fpl stats`. |
380
380
  | **ep_this** | FPL's predicted points for the current gameweek (freezes after GW deadline, enabling predicted-vs-actual comparison with `event_points`). JSON only - not shown in the panel. |
381
381
 
382
- Both fields default to `0.0` for unavailable/injured players. In JSON output, they appear under `info.ep_next` and `info.ep_this` for `fpl player`, and as top-level record fields in `fpl stats --format json`.
382
+ Both fields are `None` on the `Player` model when FPL provides no projection (e.g. unavailable/injured players, end of season). The Rich panel for `fpl player` omits the `xPts` segment of the Points line in that case, and the `fpl stats` table renders the cell as `—`. JSON output emits `null` when FPL provides no projection, so consumers can distinguish missing data from a genuine `0.0` projection `info.ep_next`/`info.ep_this` for `fpl player`, and the top-level `ep_next`/`ep_this` record fields in `fpl stats --format json`.
383
383
 
384
384
  #### Player Detail Flags
385
385
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.4.2'
22
- __version_tuple__ = version_tuple = (1, 4, 2)
21
+ __version__ = version = '2.0.0'
22
+ __version_tuple__ = version_tuple = (2, 0, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -268,5 +268,9 @@ def _format_sort_value(field: str, value) -> str:
268
268
  if fmt == "pct":
269
269
  return f"{value:.1f}%"
270
270
  if field in _PLAYERS_FLOAT_FIELDS:
271
- return f"{value:.1f}"
271
+ # Missing-data UX: render em dash. A table column can't be omitted
272
+ # per-row, so we substitute a visible placeholder. The Rich panel
273
+ # in `fpl player` drops the whole segment instead — different
274
+ # surface, different right answer.
275
+ return f"{value:.1f}" if value is not None else "—"
272
276
  return str(value)
@@ -288,8 +288,8 @@ def player_command(
288
288
  "form": float(p.form),
289
289
  "total_points": p.total_points,
290
290
  "points_per_game": float(p.points_per_game),
291
- "ep_next": float(p.ep_next),
292
- "ep_this": float(p.ep_this),
291
+ "ep_next": p.ep_next,
292
+ "ep_this": p.ep_this,
293
293
  "goals_scored": p.goals_scored,
294
294
  "assists": p.assists,
295
295
  "expected_assists": float(p.expected_assists),
@@ -423,11 +423,19 @@ def player_command(
423
423
 
424
424
  # Build panel lines conditionally
425
425
  is_gk = p.position_name == "GK"
426
+ # Missing-data UX: drop the xPts segment entirely. The panel is
427
+ # a single-row free-form layout, so removing the label reads cleaner
428
+ # than rendering "xPts: —". The stats table takes the opposite
429
+ # approach (em dash) because per-row column omission isn't an option.
426
430
  lines = [
427
431
  f"[bold]{p.web_name}[/bold] ({p.full_name})",
428
432
  f"Team: {team_name} | Position: {p.position_name}",
429
433
  f"Price: £{p.price:.1f}m | Form: {p.form:.1f}",
430
- f"Points: {p.total_points} | PPG: {p.points_per_game:.1f} | xPts: {p.ep_next:.1f}",
434
+ (
435
+ f"Points: {p.total_points} | PPG: {p.points_per_game:.1f} | xPts: {p.ep_next:.1f}"
436
+ if p.ep_next is not None
437
+ else f"Points: {p.total_points} | PPG: {p.points_per_game:.1f}"
438
+ ),
431
439
  ]
432
440
  rv, rc = rolling_scores.get(p.id, (None, None))
433
441
  if rv is not None:
@@ -280,6 +280,8 @@ def stats_command(
280
280
  effective_sort = "total_points"
281
281
 
282
282
  # Sort
283
+ # Null-scored players sort to bottom regardless of direction
284
+ bottom = float("-inf") if not reverse else float("inf")
283
285
  if effective_sort in _VALUE_SORT_FIELDS:
284
286
  if effective_sort == "rolling_pts_per_m":
285
287
  rolling_score_map: dict[int, float | None] = {
@@ -290,8 +292,6 @@ def stats_command(
290
292
  score_map = quality_map
291
293
  else:
292
294
  score_map = value_map
293
- # Null-scored players sort to bottom regardless of direction
294
- bottom = float("-inf") if not reverse else float("inf")
295
295
 
296
296
  def _value_key(p: Player) -> float:
297
297
  v = score_map.get(p.id)
@@ -300,7 +300,12 @@ def stats_command(
300
300
  filtered.sort(key=_value_key, reverse=not reverse)
301
301
  else:
302
302
  attr = _SORT_FIELD_ALIASES.get(effective_sort, effective_sort)
303
- filtered.sort(key=lambda p: getattr(p, attr), reverse=not reverse)
303
+
304
+ def _attr_key(p: Player) -> float:
305
+ v = getattr(p, attr)
306
+ return v if v is not None else bottom
307
+
308
+ filtered.sort(key=_attr_key, reverse=not reverse)
304
309
 
305
310
  # Limit
306
311
  filtered = filtered[:limit]
@@ -366,8 +371,8 @@ def stats_command(
366
371
  "defensive_contribution_per_90": float(p.defensive_contribution_per_90),
367
372
  "form_per_m": float(p.value_form),
368
373
  "pts_per_m": float(p.value_season),
369
- "ep_next": float(p.ep_next),
370
- "ep_this": float(p.ep_this),
374
+ "ep_next": p.ep_next,
375
+ "ep_this": p.ep_this,
371
376
  **(
372
377
  {
373
378
  "quality_score": quality_map.get(p.id),
@@ -90,8 +90,8 @@ class Player(BaseModel):
90
90
  expected_goals_conceded: float = Field(default=0.0, alias="expected_goals_conceded")
91
91
 
92
92
  # FPL predicted points
93
- ep_next: float = 0.0
94
- ep_this: float = 0.0
93
+ ep_next: float | None = None
94
+ ep_this: float | None = None
95
95
 
96
96
  # Defensive
97
97
  defensive_contribution: int = 0
@@ -248,8 +248,8 @@ class TestPlayerFormatSuppression:
248
248
  player.direct_freekicks_order = None
249
249
  player.defensive_contribution_per_90 = 0.0
250
250
  player.penalties_saved = 0
251
- player.ep_next = 0.0
252
- player.ep_this = 0.0
251
+ player.ep_next = None
252
+ player.ep_this = None
253
253
  return player
254
254
 
255
255
  def _mock_team(self):
@@ -729,6 +729,50 @@ class TestPlayerQualityValueScores:
729
729
  assert "xPts: 8.5" in result.output
730
730
  assert "ep_this" not in result.output
731
731
 
732
+ def test_rich_panel_omits_ep_next_when_none(self):
733
+ client, fixture_agent, ratings_svc = _make_mocks()
734
+ client.get_players = AsyncMock(return_value=[
735
+ make_player(id=308, web_name="Salah", first_name="Mohamed",
736
+ second_name="Salah", team_id=1,
737
+ position=PlayerPosition.MIDFIELDER,
738
+ ep_next=None),
739
+ ])
740
+ result = _run([], client, fixture_agent, ratings_svc)
741
+ assert result.exit_code == 0, result.output
742
+ assert "xPts" not in result.output
743
+ # Rest of the Points line must still render — confirms only the xPts
744
+ # segment was dropped, not the entire row.
745
+ assert "Points:" in result.output
746
+ assert "PPG:" in result.output
747
+
748
+ def test_rich_panel_shows_ep_next_when_zero(self):
749
+ # Guards against a future `if p.ep_next:` regression that would
750
+ # swallow genuine 0.0 projections (distinct from None).
751
+ client, fixture_agent, ratings_svc = _make_mocks()
752
+ client.get_players = AsyncMock(return_value=[
753
+ make_player(id=308, web_name="Salah", first_name="Mohamed",
754
+ second_name="Salah", team_id=1,
755
+ position=PlayerPosition.MIDFIELDER,
756
+ ep_next=0.0),
757
+ ])
758
+ result = _run([], client, fixture_agent, ratings_svc)
759
+ assert result.exit_code == 0, result.output
760
+ assert "xPts: 0.0" in result.output
761
+
762
+ def test_json_ep_next_none_serialises_as_null(self):
763
+ client, fixture_agent, ratings_svc = _make_mocks()
764
+ client.get_players = AsyncMock(return_value=[
765
+ make_player(id=308, web_name="Salah", first_name="Mohamed",
766
+ second_name="Salah", team_id=1,
767
+ position=PlayerPosition.MIDFIELDER,
768
+ ep_next=None, ep_this=None),
769
+ ])
770
+ result = _run_json([], client, fixture_agent, ratings_svc)
771
+ assert result.exit_code == 0, result.output
772
+ info = json.loads(result.output)["data"][0]["info"]
773
+ assert info["ep_next"] is None
774
+ assert info["ep_this"] is None
775
+
732
776
  def test_gk_uses_without_xgi_weights(self):
733
777
  """GK quality_score should differ from MID due to without_xgi path."""
734
778
  client, fixture_agent, ratings_svc = _make_mocks()
@@ -235,6 +235,47 @@ class TestPlayersSort:
235
235
  assert result.exit_code == 0, result.output
236
236
  assert result.output.index("Fit") < result.output.index("Injured")
237
237
 
238
+ def test_sort_ep_next_none_sorts_to_bottom(self):
239
+ players = [
240
+ make_player(id=1, web_name="HasValue", team_id=1, ep_next=5.0),
241
+ make_player(id=2, web_name="NullEp", team_id=1, ep_next=None),
242
+ ]
243
+ client = _make_client(players, _sample_teams())
244
+ result = _run(["--sort", "ep_next"], client=client)
245
+ assert result.exit_code == 0, result.output
246
+ assert result.output.index("HasValue") < result.output.index("NullEp")
247
+
248
+ def test_sort_ep_next_all_none_does_not_crash(self):
249
+ players = [
250
+ make_player(id=1, web_name="Alpha", team_id=1, ep_next=None),
251
+ make_player(id=2, web_name="Beta", team_id=1, ep_next=None),
252
+ ]
253
+ client = _make_client(players, _sample_teams())
254
+ result = _run(["--sort", "ep_next"], client=client)
255
+ assert result.exit_code == 0, result.output
256
+
257
+ def test_sort_ep_next_none_sorts_to_bottom_in_ascending(self):
258
+ # --reverse activates the float("inf") sentinel branch of the sort
259
+ players = [
260
+ make_player(id=1, web_name="HasValue", team_id=1, ep_next=5.0),
261
+ make_player(id=2, web_name="NullEp", team_id=1, ep_next=None),
262
+ ]
263
+ client = _make_client(players, _sample_teams())
264
+ result = _run(["--sort", "ep_next", "--reverse"], client=client)
265
+ assert result.exit_code == 0, result.output
266
+ assert result.output.index("HasValue") < result.output.index("NullEp")
267
+
268
+ def test_sort_ep_next_none_renders_em_dash_in_table(self):
269
+ # Exercises the _format_sort_value None -> "—" branch
270
+ players = [
271
+ make_player(id=1, web_name="HasValue", team_id=1, ep_next=5.0),
272
+ make_player(id=2, web_name="NullEp", team_id=1, ep_next=None),
273
+ ]
274
+ client = _make_client(players, _sample_teams())
275
+ result = _run(["--sort", "ep_next"], client=client)
276
+ assert result.exit_code == 0, result.output
277
+ assert "—" in result.output
278
+
238
279
 
239
280
  class TestPlayersErrors:
240
281
  def test_invalid_team_shows_valid_options(self):
@@ -294,6 +335,21 @@ class TestPlayersJsonFormat:
294
335
  assert "Salah" in result.output
295
336
  assert result.output.strip()[0] != "["
296
337
 
338
+ def test_json_ep_next_none_serialises_as_null(self):
339
+ players = [
340
+ make_player(
341
+ id=1, web_name="NullEp", team_id=1,
342
+ position=PlayerPosition.MIDFIELDER,
343
+ ep_next=None, ep_this=None,
344
+ ),
345
+ ]
346
+ client = _make_client(players, _sample_teams())
347
+ result = _run(["--format", "json"], client=client)
348
+ assert result.exit_code == 0, result.output
349
+ record = json.loads(result.output)["data"][0]
350
+ assert record["ep_next"] is None
351
+ assert record["ep_this"] is None
352
+
297
353
 
298
354
  class TestStatsDraftOwnership:
299
355
  """Tests for auto-enabled draft ownership column based on format."""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes