fplkit 1.1.0__tar.gz → 1.2.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 (224) hide show
  1. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/TOOLS.md +5 -5
  2. {fplkit-1.1.0 → fplkit-1.2.0}/.gitignore +1 -0
  3. fplkit-1.2.0/CHANGELOG.md +33 -0
  4. {fplkit-1.1.0 → fplkit-1.2.0}/PKG-INFO +1 -1
  5. fplkit-1.2.0/config/fixture_predictions.yaml +41 -0
  6. {fplkit-1.1.0 → fplkit-1.2.0}/docs/architecture.md +5 -5
  7. {fplkit-1.1.0 → fplkit-1.2.0}/docs/command-reference.md +7 -5
  8. {fplkit-1.1.0 → fplkit-1.2.0}/docs/custom-analysis.md +32 -1
  9. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/_version.py +2 -2
  10. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/action/waiver.py +29 -0
  11. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/analysis/bench_order.py +16 -0
  12. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/analysis/captain.py +14 -0
  13. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/analysis/starting_xi.py +16 -0
  14. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/analysis/stats.py +46 -3
  15. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/analysis/transfer_eval.py +39 -7
  16. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/core_insights.py +155 -1
  17. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/historical_types.py +60 -0
  18. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/vaastav.py +2 -0
  19. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/differentials.py +8 -0
  20. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/player.py +97 -16
  21. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/stats.py +42 -0
  22. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/targets.py +9 -0
  23. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/transfer_eval.py +7 -0
  24. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/waivers.py +9 -0
  25. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/models/player.py +2 -0
  26. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/models/types.py +8 -0
  27. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/services/player_prior.py +6 -0
  28. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/services/player_scoring.py +882 -50
  29. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/services/squad_allocator.py +2 -0
  30. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_agents_action.py +29 -0
  31. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_api_core_insights.py +305 -1
  32. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_api_historical.py +86 -1
  33. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_api_vaastav.py +42 -0
  34. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_differentials.py +14 -0
  35. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_player.py +192 -4
  36. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_stats.py +2 -1
  37. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_targets.py +14 -0
  38. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_transfer_eval.py +31 -2
  39. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_waivers.py +18 -0
  40. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_common_scoring.py +95 -1
  41. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_data_prep_harmonisation.py +11 -11
  42. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_player_prior.py +72 -1
  43. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_player_scoring.py +1603 -122
  44. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_stats.py +75 -0
  45. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_transfer_eval_agent.py +48 -0
  46. fplkit-1.1.0/CHANGELOG.md +0 -10
  47. fplkit-1.1.0/config/fixture_predictions.yaml +0 -67
  48. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/README.md +0 -0
  49. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/skills/gw-prep/SKILL.md +0 -0
  50. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/skills/gw-prep/references/output-template.md +0 -0
  51. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/skills/gw-prep/references/rules.md +0 -0
  52. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/skills/gw-prep/scripts/bench_order.py +0 -0
  53. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/skills/gw-prep/scripts/starting_xi.py +0 -0
  54. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/skills/gw-prep/scripts/transfer_eval.py +0 -0
  55. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/skills/squad-builder/SKILL.md +0 -0
  56. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/skills/squad-builder/references/output-template.md +0 -0
  57. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/skills/squad-builder/references/rules.md +0 -0
  58. {fplkit-1.1.0 → fplkit-1.2.0}/.agents/skills/update-gw-prep/SKILL.md +0 -0
  59. {fplkit-1.1.0 → fplkit-1.2.0}/.claude/output-styles/fpl-mate.md +0 -0
  60. {fplkit-1.1.0 → fplkit-1.2.0}/.claude/settings.json +0 -0
  61. {fplkit-1.1.0 → fplkit-1.2.0}/.env.example +0 -0
  62. {fplkit-1.1.0 → fplkit-1.2.0}/.github/workflows/ci.yml +0 -0
  63. {fplkit-1.1.0 → fplkit-1.2.0}/.github/workflows/release.yml +0 -0
  64. {fplkit-1.1.0 → fplkit-1.2.0}/AGENTS.md +0 -0
  65. {fplkit-1.1.0 → fplkit-1.2.0}/CLAUDE.md +0 -0
  66. {fplkit-1.1.0 → fplkit-1.2.0}/LICENSE +0 -0
  67. {fplkit-1.1.0 → fplkit-1.2.0}/README.md +0 -0
  68. {fplkit-1.1.0 → fplkit-1.2.0}/cliff.toml +0 -0
  69. {fplkit-1.1.0 → fplkit-1.2.0}/config/team_managers.yaml +0 -0
  70. {fplkit-1.1.0 → fplkit-1.2.0}/config/team_ratings.yaml +0 -0
  71. {fplkit-1.1.0 → fplkit-1.2.0}/config/team_ratings_overrides.yaml +0 -0
  72. {fplkit-1.1.0 → fplkit-1.2.0}/docs/fpl-rules.md +0 -0
  73. {fplkit-1.1.0 → fplkit-1.2.0}/docs/images/fpl-player-demo-v1-0.png +0 -0
  74. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/__init__.py +0 -0
  75. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/__init__.py +0 -0
  76. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/action/__init__.py +0 -0
  77. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/analysis/__init__.py +0 -0
  78. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/analysis/squad_analyzer.py +0 -0
  79. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/base.py +0 -0
  80. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/common.py +0 -0
  81. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/data/__init__.py +0 -0
  82. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/data/fixture.py +0 -0
  83. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/data/price.py +0 -0
  84. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/data/scout.py +0 -0
  85. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/orchestration/__init__.py +0 -0
  86. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/agents/orchestration/report.py +0 -0
  87. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/__init__.py +0 -0
  88. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/dataset_fetcher.py +0 -0
  89. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/football_data.py +0 -0
  90. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/fpl.py +0 -0
  91. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/fpl_draft.py +0 -0
  92. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/historical.py +0 -0
  93. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/providers/__init__.py +0 -0
  94. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/providers/_models.py +0 -0
  95. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/providers/anthropic.py +0 -0
  96. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/providers/openai_compat.py +0 -0
  97. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/providers/perplexity.py +0 -0
  98. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/api/understat.py +0 -0
  99. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/__init__.py +0 -0
  100. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_banner.py +0 -0
  101. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_context.py +0 -0
  102. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_fines.py +0 -0
  103. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_fines_config.py +0 -0
  104. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_helpers.py +0 -0
  105. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_json.py +0 -0
  106. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_league_recap_data.py +0 -0
  107. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_league_recap_types.py +0 -0
  108. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_plan_grid.py +0 -0
  109. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_review_analysis.py +0 -0
  110. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_review_classic.py +0 -0
  111. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_review_draft.py +0 -0
  112. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/_review_summarisation.py +0 -0
  113. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/allocate.py +0 -0
  114. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/captain.py +0 -0
  115. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/chips.py +0 -0
  116. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/credentials.py +0 -0
  117. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/fdr.py +0 -0
  118. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/fixtures.py +0 -0
  119. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/history.py +0 -0
  120. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/init.py +0 -0
  121. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/league.py +0 -0
  122. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/league_recap.py +0 -0
  123. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/preview.py +0 -0
  124. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/price_changes.py +0 -0
  125. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/price_history.py +0 -0
  126. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/ratings.py +0 -0
  127. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/review.py +0 -0
  128. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/sell_prices.py +0 -0
  129. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/squad.py +0 -0
  130. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/status.py +0 -0
  131. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/cli/xg.py +0 -0
  132. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/config/defaults.yaml +0 -0
  133. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/constants.py +0 -0
  134. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/models/__init__.py +0 -0
  135. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/models/chip_plan.py +0 -0
  136. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/models/fixture.py +0 -0
  137. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/models/team.py +0 -0
  138. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/parsers/__init__.py +0 -0
  139. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/parsers/recommendations.py +0 -0
  140. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/paths.py +0 -0
  141. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/prompts/__init__.py +0 -0
  142. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/prompts/league_recap.py +0 -0
  143. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/prompts/review.py +0 -0
  144. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/prompts/scout.py +0 -0
  145. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/scraper/__init__.py +0 -0
  146. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/scraper/fpl_prices.py +0 -0
  147. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/season.py +0 -0
  148. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/services/__init__.py +0 -0
  149. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/services/fixture_predictions.py +0 -0
  150. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/services/matchup.py +0 -0
  151. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/services/team_form.py +0 -0
  152. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/services/team_ratings.py +0 -0
  153. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/services/team_ratings_prior.py +0 -0
  154. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/templates/gw_league_recap.md.j2 +0 -0
  155. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/templates/gw_preview.md.j2 +0 -0
  156. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/templates/gw_review.md.j2 +0 -0
  157. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/utils/__init__.py +0 -0
  158. {fplkit-1.1.0 → fplkit-1.2.0}/fpl_cli/utils/text.py +0 -0
  159. {fplkit-1.1.0 → fplkit-1.2.0}/pyproject.toml +0 -0
  160. {fplkit-1.1.0 → fplkit-1.2.0}/pyrightconfig.json +0 -0
  161. {fplkit-1.1.0 → fplkit-1.2.0}/requirements.lock +0 -0
  162. {fplkit-1.1.0 → fplkit-1.2.0}/tests/__init__.py +0 -0
  163. {fplkit-1.1.0 → fplkit-1.2.0}/tests/conftest.py +0 -0
  164. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_agents_analysis.py +0 -0
  165. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_agents_analysis_squad.py +0 -0
  166. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_agents_base.py +0 -0
  167. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_agents_data.py +0 -0
  168. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_agents_data_price.py +0 -0
  169. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_agents_orchestration.py +0 -0
  170. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_allocate.py +0 -0
  171. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_api_client.py +0 -0
  172. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_api_dataset_fetcher.py +0 -0
  173. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_api_draft.py +0 -0
  174. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_api_football_data.py +0 -0
  175. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_api_understat.py +0 -0
  176. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_bench_order_script.py +0 -0
  177. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_allocate.py +0 -0
  178. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_banner.py +0 -0
  179. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_captain.py +0 -0
  180. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_chips.py +0 -0
  181. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_credentials.py +0 -0
  182. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_custom_analysis.py +0 -0
  183. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_fdr.py +0 -0
  184. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_fdr_blanks.py +0 -0
  185. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_fixtures.py +0 -0
  186. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_format.py +0 -0
  187. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_format_suppression.py +0 -0
  188. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_history.py +0 -0
  189. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_init.py +0 -0
  190. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_json.py +0 -0
  191. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_plan_grid.py +0 -0
  192. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_preview.py +0 -0
  193. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_price_history.py +0 -0
  194. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_review.py +0 -0
  195. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_sell_prices.py +0 -0
  196. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_squad.py +0 -0
  197. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_status.py +0 -0
  198. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_warnings_stderr.py +0 -0
  199. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_cli_xg.py +0 -0
  200. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_common.py +0 -0
  201. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_fines.py +0 -0
  202. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_fines_config.py +0 -0
  203. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_fixture_predictions.py +0 -0
  204. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_fpl_prices.py +0 -0
  205. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_init.py +0 -0
  206. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_init_fines.py +0 -0
  207. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_league_recap.py +0 -0
  208. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_matchup_service.py +0 -0
  209. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_models.py +0 -0
  210. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_paths.py +0 -0
  211. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_player_resolution.py +0 -0
  212. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_providers.py +0 -0
  213. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_recommendations_parser.py +0 -0
  214. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_report_agent.py +0 -0
  215. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_review.py +0 -0
  216. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_rolling_pts_per_m.py +0 -0
  217. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_scraper.py +0 -0
  218. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_season.py +0 -0
  219. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_sell_prices.py +0 -0
  220. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_squad_allocator.py +0 -0
  221. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_starting_xi_agent.py +0 -0
  222. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_team_ratings.py +0 -0
  223. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_team_ratings_prior.py +0 -0
  224. {fplkit-1.1.0 → fplkit-1.2.0}/tests/test_utils_text.py +0 -0
@@ -27,7 +27,7 @@ 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) | 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
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 |
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 |
@@ -38,10 +38,10 @@ Errors use `emit_json_error()` with `{"command", "error"}` shape.
38
38
  | `preview` | Run full pre-gameweek analysis and generate report | No | General | No | via-agent |
39
39
  | `review` | Review a completed gameweek - squad performance and standings | No | General | No | via-agent |
40
40
  | `league-recap` | Recap a completed gameweek - awards, standings, and banter | No | General | No | via-agent |
41
- | `captain` | Analyse and rank captain options for next gameweek | Yes | Classic | Yes | via-agent |
42
- | `differentials` | Find differential picks - high potential, low ownership | Yes | Classic | Yes | via-agent |
43
- | `targets` | Find transfer targets - high performers across all ownership | Yes | Classic | Yes | via-agent |
44
- | `transfer-eval` | Compare transfer OUT player against IN candidates | Yes | General | Yes | via-agent |
41
+ | `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 | Yes | Classic | Yes | via-agent |
42
+ | `differentials` | Find differential picks - high potential, low ownership. Inverted consistency bonus (volatile players score higher, phased in GW6-10) | Yes | Classic | Yes | via-agent |
43
+ | `targets` | Find transfer targets - high performers across all ownership. Consistency bonus (CV-xGI percentile, phased in GW6-10) | Yes | Classic | Yes | via-agent |
44
+ | `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 | Yes | General | Yes | via-agent |
45
45
  | `allocate` | Select mathematically optimal 15-player squad within budget | Yes | Classic | Yes | direct-api |
46
46
  | `waivers` | Show waiver recommendations for your draft league | Yes | Draft | Yes | via-agent |
47
47
  | `squad` | Analyse your FPL squad health and fixtures | Yes | General | No | via-agent |
@@ -69,6 +69,7 @@ docs/plans/
69
69
  docs/brainstorms/
70
70
  docs/solutions/
71
71
  docs/ideas/
72
+ docs/discarded/
72
73
  docs/positioning.md
73
74
  docs/releasing.md
74
75
 
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ ## [1.1.0] - 2026-04-07
5
+
6
+ ### Bug Fixes
7
+
8
+ - wrap migration in try/except, update architecture.md paths
9
+ - CLI hygiene batch (P10-P13) (#4)
10
+ - Understat name matching for abbreviated/punctuated web_names
11
+ - PR #4 code review follow-ups (#5)
12
+ - review fixes - gather resilience, CSV field protection, test gaps
13
+ - correct README data source count and Core-Insights repo URL
14
+
15
+ ### Features
16
+
17
+ - add cached dataset fetcher with ETag conditional requests (#6)
18
+ - add rolling pts/£m metric and rename value fields (#7)
19
+ - expose ep_next/ep_this (FPL predicted points) (#8)
20
+ - implement CoreInsightsClient for 2025-26 season data
21
+ - add HistoricalDataProvider composition layer, update call sites
22
+
23
+ ### Refactoring
24
+
25
+ - extract shared types, rename season helpers, separate cache dirs
26
+
27
+ ## [1.0.0] - 2026-04-03
28
+
29
+ ### Features
30
+
31
+ - CI pipeline and PyPI publish for v1.0.0 (#1)
32
+
33
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fplkit
3
- Version: 1.1.0
3
+ Version: 1.2.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
@@ -0,0 +1,41 @@
1
+ metadata:
2
+ last_updated: '2026-04-07'
3
+ notes: Updated from Ben Crellin BGW/DGW schedule (GW33-GW38)
4
+ predicted_blanks:
5
+ - gameweek: 34
6
+ teams:
7
+ - BHA
8
+ - BOU
9
+ - BUR
10
+ - CHE
11
+ - LEE
12
+ - MCI
13
+ confidence: confirmed
14
+ - gameweek: 37
15
+ teams:
16
+ - BOU
17
+ confidence: low
18
+ predicted_doubles:
19
+ - gameweek: 33
20
+ teams:
21
+ - BHA
22
+ - BOU
23
+ - BUR
24
+ - CHE
25
+ - LEE
26
+ - MCI
27
+ confidence: confirmed
28
+ - gameweek: 36
29
+ teams:
30
+ - CRY
31
+ - MCI
32
+ confidence: high
33
+ - gameweek: 36
34
+ teams:
35
+ - BOU
36
+ confidence: low
37
+ - gameweek: 37
38
+ teams:
39
+ - CRY
40
+ - MCI
41
+ confidence: low
@@ -361,8 +361,8 @@ Services live in `fpl_cli/services/` and provide the computation layer between a
361
361
 
362
362
  | Service | Purpose |
363
363
  |---|---|
364
- | `player_scoring` | Central scoring engine: `prepare_scoring_data()`, all score functions, `shrink_scores()` |
365
- | `player_prior` | Bayesian early-season confidence (GW1-10 shrinkage) |
364
+ | `player_scoring` | Central scoring engine: `prepare_scoring_data()`, all score functions, `shrink_scores()`. Form modifiers: `compute_form_trajectory()` (direction over recent GWs) and `compute_xgi_sustainability()` (ATK-only rolling xGI divergence -> [0.85, 1.15] multiplier). Fixture-adjusted npxG: `compute_adjusted_npxg()` / `build_adjusted_npxg_lookup()` normalise historical xG by opponent Elo; `apply_adjusted_npxg()` overwrites `npxG_per_90` in agent enrichment when data is available. Consistency signals: `build_consistency_lookup()` computes 5 per-player signals (CV-xGI percentile, blank rate, floor percentile, involvement rate, GK consistency) with GW6-10 phase-in; additive bonuses per scoring family (target/waiver cv*1.5, differential inverted cv*0.75, lineup cv*0.75, bench floor*1.5+inv*0.75) |
365
+ | `player_prior` | Bayesian early-season confidence (GW1-10 shrinkage); threads `PlayerProfile.reliability` (historical availability rate) through `PlayerPrior` to agents |
366
366
  | `team_ratings` | TeamRatingsService + Calculator (1-7 scale, 4 axes) |
367
367
  | `matchup` | Fixture matchup scoring (0-10), 3-GW recency-weighted |
368
368
  | `fixture_predictions` | BGW/DGW predictions from YAML + live detection |
@@ -416,7 +416,7 @@ All providers share the `LLMResponse` contract. `OpenAICompatProvider` supports
416
416
  | `FPLDraftClient` | FPL Draft API | Draft leagues, waivers, squad data |
417
417
  | `UnderstatClient` | understat.com | npxG, xA, xGChain, xGBuildup per-90 stats |
418
418
  | `VaastavClient` | vaastav/FPL GitHub | Historical CSV data (3 seasons: 2022-25), price trends, GW-level profiles |
419
- | `CoreInsightsClient` | Core-Insights/FPL GitHub | Current-season CSV data (2025-26+), season aggregates, GW trends |
419
+ | `CoreInsightsClient` | Core-Insights/FPL GitHub | Current-season CSV data (2025-26+), season aggregates, GW trends, per-GW match-level xG + Elo (from `By Tournament/Premier League/GW{n}/`) |
420
420
  | `HistoricalDataProvider` | Composition layer | Unifies vaastav + Core-Insights via `make_historical_provider()` |
421
421
  | `FootballDataClient` | football-data.org | League standings, match results |
422
422
  | `FPLPriceScraper` | FPL website | Price change scraping (needs credentials) |
@@ -516,7 +516,7 @@ fpl_cli/
516
516
  │ ├── fpl.py # FPLClient (main API, caches bootstrap-static)
517
517
  │ ├── fpl_draft.py # FPLDraftClient
518
518
  │ ├── understat.py # UnderstatClient + match_fpl_to_understat()
519
- │ ├── historical_types.py # Shared dataclasses (SeasonHistory, PlayerProfile, GwTrendProfile) + compute_trend/compute_acceleration
519
+ │ ├── historical_types.py # Shared dataclasses (SeasonHistory, PlayerProfile, GwTrendProfile) + compute_trend/compute_acceleration/compute_reliability
520
520
  │ ├── vaastav.py # VaastavClient (historical seasons 2022-25 via DatasetFetcher)
521
521
  │ ├── core_insights.py # CoreInsightsClient (current season 2025-26+ via DatasetFetcher)
522
522
  │ ├── historical.py # HistoricalDataProvider (composition: vaastav + Core-Insights)
@@ -527,7 +527,7 @@ fpl_cli/
527
527
  │ ├── openai_compat.py # OpenAICompatProvider (OpenAI, Groq, Together, Ollama)
528
528
  │ └── perplexity.py # PerplexityProvider (extends OpenAICompat)
529
529
  ├── services/
530
- │ ├── player_scoring.py # Scoring engines + prepare_scoring_data() + shrink_scores()
530
+ │ ├── player_scoring.py # Scoring engines + prepare_scoring_data() + shrink_scores() + consistency signals
531
531
  │ ├── player_prior.py # Player prior (Bayesian early-season confidence)
532
532
  │ ├── team_ratings.py # TeamRatingsService + Calculator (1-7 scale)
533
533
  │ ├── team_ratings_prior.py # Pre-GW5 prior ratings for blending
@@ -57,7 +57,7 @@ fpl captain --format json
57
57
 
58
58
  Output columns: Score, Atk, Def, Form±, Pos±.
59
59
 
60
- Score combines position-weighted matchup quality, recent form (with trajectory adjustment), and xGI per 90. DGW players are scored across both fixtures. See [Captain Score](custom-analysis.md#captain-score) for the full formula, [Matchup Scoring](custom-analysis.md#matchup-scoring) for column definitions.
60
+ Score combines position-weighted matchup quality, recent form (with trajectory adjustment), xGI per 90, and a consistency tiebreaker (CV-xGI percentile, phased in GW6-10). DGW players are scored across both fixtures. See [Captain Score](custom-analysis.md#captain-score) for the full formula, [Matchup Scoring](custom-analysis.md#matchup-scoring) for column definitions.
61
61
 
62
62
  ### Transfer Targets
63
63
 
@@ -75,7 +75,7 @@ Groups players into tiers:
75
75
  - **Popular** (15-30% owned): Emerging picks
76
76
  - **Differential** (<15% owned): Low-ownership value
77
77
 
78
- Target score combines xG metrics, form, PPG, and 3-GW matchup quality, normalised to 0-100. Subject to [early-season shrinkage](custom-analysis.md#early-season-confidence-gw1-10). See [Target Score](custom-analysis.md#target-score) for the full formula.
78
+ Target score combines xG metrics, form, PPG, 3-GW matchup quality, and consistency (CV-xGI percentile bonus, phased in GW6-10), normalised to 0-100. Subject to [early-season shrinkage](custom-analysis.md#early-season-confidence-gw1-10). See [Target Score](custom-analysis.md#target-score) for the full formula.
79
79
 
80
80
  ### Transfer Evaluation
81
81
 
@@ -98,6 +98,7 @@ Output columns:
98
98
  - **Fixtures** - next 3 opponents with FDR
99
99
  - **Form** - FPL form (last 30 days PPG)
100
100
  - **Status** - availability indicator
101
+ - **Avail** - historical availability rate (recency-weighted starts across previous seasons). Null when no historical data.
101
102
  - **Quality** - price-independent player quality (0-100). Uses `VALUE_QUALITY_WEIGHTS`. Null when no Understat match.
102
103
  - **Value** - quality per GBP million (`quality_score / price`). Higher = more output per pound. Null when no Understat match or price is 0. *(classic only)*
103
104
  - **Price** - current price *(classic only)*
@@ -120,7 +121,7 @@ fpl differentials -m 200 # Require 200+ minutes played
120
121
  fpl differentials --format json # JSON envelope (metadata: {gameweek})
121
122
  ```
122
123
 
123
- Differential score combines xG metrics, form, ownership bonus, and 3-GW matchup quality, normalised to 0-100. Subject to [early-season shrinkage](custom-analysis.md#early-season-confidence-gw1-10). See [Differential Score](custom-analysis.md#differential-score) for the full formula.
124
+ Differential score combines xG metrics, form, ownership bonus, 3-GW matchup quality, and consistency (inverted CV-xGI bonus - volatile players score higher, phased in GW6-10), normalised to 0-100. Subject to [early-season shrinkage](custom-analysis.md#early-season-confidence-gw1-10). See [Differential Score](custom-analysis.md#differential-score) for the full formula.
124
125
 
125
126
  ### Waiver Recommendations
126
127
 
@@ -133,7 +134,7 @@ fpl waivers --format json
133
134
 
134
135
  Identifies squad weaknesses by position, ranks available free agents by waiver score, suggests who to drop for each pickup. This covers the waiver wire (unclaimed players) only - trade recommendations between managers are not in scope.
135
136
 
136
- Waiver score combines xGI, form, PPG, and 3-GW matchup quality, normalised to 0-100. Uses a stricter minutes factor than target/differential because draft waivers are a season commitment. Subject to [early-season shrinkage](custom-analysis.md#early-season-confidence-gw1-10). See [Waiver Score](custom-analysis.md#waiver-score) for the full formula.
137
+ Waiver score combines xGI, form, PPG, 3-GW matchup quality, and consistency (CV-xGI percentile bonus, phased in GW6-10), normalised to 0-100. Uses a stricter minutes factor than target/differential because draft waivers are a season commitment. Subject to [early-season shrinkage](custom-analysis.md#early-season-confidence-gw1-10). See [Waiver Score](custom-analysis.md#waiver-score) for the full formula.
137
138
 
138
139
  ## Fixture & Strategic Planning
139
140
 
@@ -357,6 +358,7 @@ When a player has an Understat match, `fpl player` computes and displays two add
357
358
  | **pts_per_m** | `total_points / price` (per £m). Raw season points efficiency. |
358
359
  | **form_per_m** | `form / price` (per £m). Recent form efficiency. |
359
360
  | **rolling_pts_per_m** | Points per £m over the last N qualifying fixtures (configurable via `--window`). Captures recent form-adjusted value. |
361
+ | **adj. npxG/90** | Fixture-adjusted non-penalty xG per 90: npxG normalised by opponent Elo over a rolling window. Shown alongside raw when the adjustment changes the value. See [Fixture-Adjusted npxG](custom-analysis.md#fixture-adjusted-npxg). |
360
362
 
361
363
  `quality_score` and `quality_per_m` are `null` when no Understat match exists. In JSON output (`--format json`), they appear under `info.quality_score` and `info.quality_per_m`. In the Rich panel, they appear as `Quality: 85 | Value: 11.3/£m`.
362
364
 
@@ -373,7 +375,7 @@ Both fields default to `0.0` for unavailable/injured players. In JSON output, th
373
375
 
374
376
  #### Player Detail Flags
375
377
 
376
- **`--detail` (`-d`)**: GW-by-GW match performance from the FPL API. Shows gameweek, opponent, minutes, goals, xG, assists, xA, and points for the last 10 matches.
378
+ **`--detail` (`-d`)**: GW-by-GW match performance from the FPL API. Shows gameweek, opponent, minutes, goals, xG, assists, xA, and points for the last 10 matches. For FWD/MID players with sufficient recent history, also shows xGI sustainability: the per-match GI-xGI divergence and the resulting form modifier (e.g. `xGI Sustainability: +0.18/match -> 0.94x form`).
377
379
 
378
380
  **`--understat` (`-u`)**: Combined Understat analysis: shot analysis (total shots, shots on target, average xG per shot, body part split, situation breakdown) and situation profile. Includes a data-through date caveat since Understat data can lag behind the live season.
379
381
 
@@ -301,11 +301,42 @@ Current-season data is blended with a prior from the previous season's Understat
301
301
 
302
302
  Stored in `config/team_ratings.yaml`.
303
303
 
304
+ ## Fixture-Adjusted npxG
305
+
306
+ The scoring pipeline normally consumes npxG/90 as a flat season average from Understat. A player with a soft early schedule accumulates a high rate and also earns a strong matchup score — double-counting fixture difficulty.
307
+
308
+ Fixture-adjusted npxG normalises historical xG by the Elo strength of each opponent before computing the per-90 rate. This isolates fixture difficulty to the matchup component alone.
309
+
310
+ ### How it works
311
+
312
+ For each completed match within a 7-match / 12-GW rolling window:
313
+
314
+ 1. **npxG approximation**: `xg - (penalties_scored + penalties_missed) × 0.76` (removes penalty xG from the Core-Insights match total)
315
+ 2. **Adjustment factor**: `median_elo / opponent_elo`, capped at [0.80, 1.25] — a match against a 1900-Elo side is scaled up; a match against an 800-Elo side is scaled down
316
+ 3. **Adjusted per-90**: `(npxg × factor) / minutes × 90` per match, averaged over qualifying matches
317
+ 4. Players with fewer than 4 qualifying matches fall back to raw Understat npxG/90
318
+
319
+ The median Elo is computed fresh from the season's match data each run. Elo ratings come from per-gameweek `matches.csv` files in the Core-Insights dataset (`By Tournament/Premier League/GW{n}/`).
320
+
321
+ ### Display
322
+
323
+ When `custom_analysis: true` and match data is available, `fpl player` shows:
324
+
325
+ ```
326
+ adj. npxG/90: 0.312 (raw: 0.385)
327
+ ```
328
+
329
+ The existing `npxG` panel line continues to show the raw Understat season total. In JSON output, `info.adjusted_npxg_per_90` and `info.raw_npxg_per_90` are added when custom analysis is enabled. Captain and transfer evaluation agent outputs include both fields when an adjustment is active.
330
+
331
+ If Core-Insights data is unavailable, the pipeline falls back to raw npxG/90 with no error surfaced to users.
332
+
304
333
  ## Quality & Value Scores
305
334
 
306
335
  Available via `fpl stats --value` and `fpl player` when Understat data exists.
307
336
 
308
- **quality_score** (0-100): Normalised player output quality using `VALUE_QUALITY_WEIGHTS`. Weights form and PPG heavily to capture current FPL points production rate. GK/DEF use a defensive variant (dc_per_90 replaces attacking xG stats).
337
+ **quality_score** (0-100): Normalised player output quality using `VALUE_QUALITY_WEIGHTS`. Weights form and PPG heavily to capture current FPL points production rate. Position-specific scoring paths diverge for defensive players:
338
+ - **GK**: dedicated signals via `for_gk()` weights — saves per 90, defensive quality (inverted xGC/90, range 0-2), and clean sheet rate. Normalised against `GK_VALUE_CEILING` (28.2).
339
+ - **DEF**: `dc_per_90` (defensive contribution rate) replaces attacking xG stats via `without_xgi()`. Normalised against `VALUE_CEILING` (24.3).
309
340
 
310
341
  **quality_per_m**: `quality_score / price` (per £m). Within-position budget efficiency - higher means more output per pound. Not meaningful for cross-position comparison. Null when price is 0.
311
342
 
@@ -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.1.0'
22
- __version_tuple__ = version_tuple = (1, 1, 0)
21
+ __version__ = version = '1.2.0'
22
+ __version_tuple__ = version_tuple = (1, 2, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -14,11 +14,15 @@ from fpl_cli.api.fpl import FPLClient
14
14
  from fpl_cli.api.fpl_draft import FPLDraftClient
15
15
  from fpl_cli.models.types import EnrichedPlayer, WaiverTarget
16
16
  from fpl_cli.services.player_scoring import (
17
+ ConsistencySignals,
18
+ apply_adjusted_npxg,
19
+ apply_consistency,
17
20
  apply_shrinkage,
18
21
  build_player_evaluation,
19
22
  calculate_waiver_score,
20
23
  compute_aggregate_matchup,
21
24
  compute_form_trajectory,
25
+ compute_xgi_sustainability,
22
26
  prepare_scoring_data,
23
27
  )
24
28
 
@@ -40,6 +44,8 @@ class WaiverAgent(Agent):
40
44
  super().__init__(config)
41
45
  self.client = FPLDraftClient()
42
46
  self.fpl_client = FPLClient()
47
+ self._adjusted_npxg_lookup: dict[int, float] | None = None
48
+ self._consistency_lookup: dict[int, ConsistencySignals] | None = None
43
49
  self.league_id = config.get("draft_league_id") if config else None
44
50
  self.entry_id = config.get("draft_entry_id") if config else None
45
51
 
@@ -170,10 +176,13 @@ class WaiverAgent(Agent):
170
176
  data = await prepare_scoring_data(
171
177
  self.fpl_client, include_players=True,
172
178
  include_history=True, include_prior=True,
179
+ include_match_data=True,
173
180
  )
174
181
  next_gw_id = data.next_gw_id
175
182
  self._player_histories = data.player_histories or {}
176
183
  self._player_priors = data.player_priors
184
+ self._adjusted_npxg_lookup = data.adjusted_npxg_lookup
185
+ self._consistency_lookup = data.consistency_lookup
177
186
  scoring_ctx = data.scoring_ctx
178
187
 
179
188
  # Enrich available players with matchup and FDR
@@ -322,11 +331,21 @@ class WaiverAgent(Agent):
322
331
  history = histories.get(player.get("id", 0), [])
323
332
  if history:
324
333
  enrichment["form_trajectory"] = compute_form_trajectory(history, next_gw_id)
334
+ sustainability, divergence = compute_xgi_sustainability(history, next_gw_id, player.get("position", "MID"))
335
+ enrichment["xgi_sustainability"] = sustainability
336
+ enrichment["xgi_divergence"] = divergence
325
337
  priors = getattr(self, "_player_priors", None)
326
338
  if priors:
327
339
  prior = priors.get(player.get("id", 0))
328
340
  if prior:
329
341
  enrichment["prior_confidence"] = prior.confidence
342
+ if "npxG_per_90" not in enrichment:
343
+ npxg = player.get("npxG_per_90")
344
+ if npxg is not None:
345
+ enrichment["npxG_per_90"] = npxg
346
+ pid = int(player.get("id") or 0)
347
+ apply_adjusted_npxg(enrichment, pid, self._adjusted_npxg_lookup)
348
+ apply_consistency(enrichment, pid, self._consistency_lookup)
330
349
  evaluation, _ = build_player_evaluation(
331
350
  player,
332
351
  enrichment=enrichment,
@@ -497,6 +516,14 @@ class WaiverAgent(Agent):
497
516
  target, drop_candidate, team_counts
498
517
  )
499
518
 
519
+ priors = getattr(self, "_player_priors", None)
520
+ target_prior = priors.get(target.get("id", 0)) if priors else None
521
+ reliability = target_prior.reliability if target_prior is not None else None
522
+
523
+ con_lookup = getattr(self, "_consistency_lookup", None)
524
+ con_signals = con_lookup.get(target.get("id", 0)) if con_lookup else None
525
+ con_pct = con_signals.cv_xgi_percentile if con_signals is not None else None
526
+
500
527
  rec: dict[str, Any] = {
501
528
  "priority": len(recommendations) + 1,
502
529
  "target": {
@@ -505,6 +532,8 @@ class WaiverAgent(Agent):
505
532
  "position": pos,
506
533
  "form": target.get("form"),
507
534
  "waiver_score": target.get("waiver_score"),
535
+ "reliability": reliability,
536
+ "cv_xgi_percentile": con_pct,
508
537
  },
509
538
  "drop": {
510
539
  "name": drop_candidate.get("player_name") if drop_candidate else None,
@@ -8,12 +8,16 @@ from fpl_cli.agents.base import Agent, AgentResult, AgentStatus
8
8
  from fpl_cli.api.fpl import FPLClient
9
9
  from fpl_cli.models.player import FORMATION_LIMITS, PlayerStatus
10
10
  from fpl_cli.services.player_scoring import (
11
+ ConsistencySignals,
11
12
  ScoringContext,
13
+ apply_adjusted_npxg,
14
+ apply_consistency,
12
15
  apply_shrinkage,
13
16
  build_fixture_matchups,
14
17
  build_player_evaluation,
15
18
  calculate_bench_score,
16
19
  compute_form_trajectory,
20
+ compute_xgi_sustainability,
17
21
  prepare_scoring_data,
18
22
  )
19
23
 
@@ -67,6 +71,7 @@ class BenchOrderAgent(Agent):
67
71
  self.client,
68
72
  include_players=True, include_understat=True,
69
73
  include_history=True, include_prior=True,
74
+ include_match_data=True,
70
75
  )
71
76
  all_players = data.players or []
72
77
  player_map = {p.id: p for p in all_players}
@@ -74,6 +79,8 @@ class BenchOrderAgent(Agent):
74
79
  scoring_context = data.scoring_ctx
75
80
  team_map = data.team_map
76
81
  understat_by_id = data.understat_lookup
82
+ adjusted_npxg_lookup = data.adjusted_npxg_lookup
83
+ consistency_lookup = data.consistency_lookup
77
84
 
78
85
  bench_ids = context["bench"]
79
86
  bench_players = [player_map[pid] for pid in bench_ids if pid in player_map]
@@ -94,6 +101,8 @@ class BenchOrderAgent(Agent):
94
101
  understat_by_id=understat_by_id,
95
102
  player_histories=player_histories,
96
103
  player_priors=data.player_priors,
104
+ adjusted_npxg_lookup=adjusted_npxg_lookup,
105
+ consistency_lookup=consistency_lookup,
97
106
  )
98
107
  scored_bench.append(score_data)
99
108
 
@@ -239,6 +248,8 @@ class BenchOrderAgent(Agent):
239
248
  understat_by_id: dict[int, dict[str, float]] | None = None,
240
249
  player_histories: dict[int, list[dict[str, Any]]] | None = None,
241
250
  player_priors: dict[int, Any] | None = None,
251
+ adjusted_npxg_lookup: dict[int, float] | None = None,
252
+ consistency_lookup: dict[int, ConsistencySignals] | None = None,
242
253
  ) -> dict[str, Any]:
243
254
  """Score a bench player via the scoring engine."""
244
255
  team = context.team_map.get(player.team_id)
@@ -251,11 +262,16 @@ class BenchOrderAgent(Agent):
251
262
  us_data = understat_by_id.get(player.id)
252
263
  if us_data:
253
264
  enrichment.update(us_data)
265
+ apply_adjusted_npxg(enrichment, player.id, adjusted_npxg_lookup)
266
+ apply_consistency(enrichment, player.id, consistency_lookup)
254
267
 
255
268
  if player_histories:
256
269
  history = player_histories.get(player.id, [])
257
270
  if history:
258
271
  enrichment["form_trajectory"] = compute_form_trajectory(history, next_gw_id)
272
+ sustainability, divergence = compute_xgi_sustainability(history, next_gw_id, player.position_name)
273
+ enrichment["xgi_sustainability"] = sustainability
274
+ enrichment["xgi_divergence"] = divergence
259
275
 
260
276
  if player_priors:
261
277
  prior = player_priors.get(player.id)
@@ -12,12 +12,16 @@ from fpl_cli.api.fpl import FPLClient
12
12
  from fpl_cli.models.player import Player
13
13
  from fpl_cli.models.types import CaptainCandidate
14
14
  from fpl_cli.services.player_scoring import (
15
+ ConsistencySignals,
15
16
  ScoringContext,
17
+ apply_adjusted_npxg,
18
+ apply_consistency,
16
19
  apply_shrinkage,
17
20
  build_fixture_matchups,
18
21
  build_player_evaluation,
19
22
  calculate_captain_score,
20
23
  compute_form_trajectory,
24
+ compute_xgi_sustainability,
21
25
  prepare_scoring_data,
22
26
  )
23
27
 
@@ -43,6 +47,8 @@ class CaptainAgent(Agent):
43
47
  def __init__(self, config: dict[str, Any] | None = None):
44
48
  super().__init__(config)
45
49
  self.client = FPLClient()
50
+ self._adjusted_npxg_lookup: dict[int, float] | None = None
51
+ self._consistency_lookup: dict[int, ConsistencySignals] | None = None
46
52
 
47
53
  # Differential thresholds
48
54
  self.differential_threshold = config.get("differential_threshold", 10.0) if config else 10.0
@@ -63,11 +69,14 @@ class CaptainAgent(Agent):
63
69
  self.client,
64
70
  include_players=True, include_understat=True,
65
71
  include_history=True, include_prior=True,
72
+ include_match_data=True,
66
73
  )
67
74
  all_players = data.players or []
68
75
  player_map = {p.id: p for p in all_players}
69
76
  self._player_histories = data.player_histories or {}
70
77
  self._player_priors = data.player_priors
78
+ self._adjusted_npxg_lookup = data.adjusted_npxg_lookup
79
+ self._consistency_lookup = data.consistency_lookup
71
80
 
72
81
  return (
73
82
  player_map,
@@ -216,6 +225,8 @@ class CaptainAgent(Agent):
216
225
  us_data = understat_by_id.get(player.id)
217
226
  if us_data:
218
227
  enrichment.update(us_data)
228
+ apply_adjusted_npxg(enrichment, player.id, self._adjusted_npxg_lookup)
229
+ apply_consistency(enrichment, player.id, self._consistency_lookup)
219
230
 
220
231
  # xGI_per_90 fallback for players without Understat data
221
232
  minutes_safe = max(player.minutes, 1)
@@ -226,6 +237,9 @@ class CaptainAgent(Agent):
226
237
  history = histories.get(player.id, [])
227
238
  if history:
228
239
  enrichment["form_trajectory"] = compute_form_trajectory(history, next_gw_id)
240
+ sustainability, divergence = compute_xgi_sustainability(history, next_gw_id, player.position_name)
241
+ enrichment["xgi_sustainability"] = sustainability
242
+ enrichment["xgi_divergence"] = divergence
229
243
 
230
244
  # Bayesian prior confidence
231
245
  priors = getattr(self, "_player_priors", None)
@@ -7,13 +7,17 @@ from typing import TYPE_CHECKING, Any
7
7
  from fpl_cli.agents.base import Agent, AgentResult, AgentStatus
8
8
  from fpl_cli.api.fpl import FPLClient
9
9
  from fpl_cli.services.player_scoring import (
10
+ ConsistencySignals,
10
11
  ScoringContext,
12
+ apply_adjusted_npxg,
13
+ apply_consistency,
11
14
  apply_shrinkage,
12
15
  build_fixture_matchups,
13
16
  build_player_evaluation,
14
17
  calculate_lineup_score,
15
18
  compute_aggregate_matchup,
16
19
  compute_form_trajectory,
20
+ compute_xgi_sustainability,
17
21
  prepare_scoring_data,
18
22
  select_starting_xi,
19
23
  )
@@ -67,12 +71,15 @@ class StartingXIAgent(Agent):
67
71
  self.client,
68
72
  include_players=True, include_understat=True,
69
73
  include_history=True, include_prior=True,
74
+ include_match_data=True,
70
75
  )
71
76
  all_players = data.players or []
72
77
  player_map = {p.id: p for p in all_players}
73
78
  player_histories = data.player_histories or {}
74
79
  scoring_context = data.scoring_ctx
75
80
  understat_by_id = data.understat_lookup
81
+ adjusted_npxg_lookup = data.adjusted_npxg_lookup
82
+ consistency_lookup = data.consistency_lookup
76
83
 
77
84
  squad_ids = context["squad"]
78
85
  squad_players = [player_map[pid] for pid in squad_ids if pid in player_map]
@@ -95,6 +102,8 @@ class StartingXIAgent(Agent):
95
102
  understat_by_id=understat_by_id,
96
103
  player_histories=player_histories,
97
104
  player_priors=data.player_priors,
105
+ adjusted_npxg_lookup=adjusted_npxg_lookup,
106
+ consistency_lookup=consistency_lookup,
98
107
  )
99
108
  scored.append(score_data)
100
109
 
@@ -150,6 +159,8 @@ class StartingXIAgent(Agent):
150
159
  understat_by_id: dict[int, dict[str, float]] | None = None,
151
160
  player_histories: dict[int, list[dict[str, Any]]] | None = None,
152
161
  player_priors: dict[int, Any] | None = None,
162
+ adjusted_npxg_lookup: dict[int, float] | None = None,
163
+ consistency_lookup: dict[int, ConsistencySignals] | None = None,
153
164
  ) -> dict[str, Any]:
154
165
  """Score a squad player via the scoring engine."""
155
166
  team = context.team_map.get(player.team_id)
@@ -167,11 +178,16 @@ class StartingXIAgent(Agent):
167
178
  us_data = understat_by_id.get(player.id)
168
179
  if us_data:
169
180
  enrichment.update(us_data)
181
+ apply_adjusted_npxg(enrichment, player.id, adjusted_npxg_lookup)
182
+ apply_consistency(enrichment, player.id, consistency_lookup)
170
183
 
171
184
  if player_histories:
172
185
  history = player_histories.get(player.id, [])
173
186
  if history:
174
187
  enrichment["form_trajectory"] = compute_form_trajectory(history, next_gw_id)
188
+ sustainability, divergence = compute_xgi_sustainability(history, next_gw_id, player.position_name)
189
+ enrichment["xgi_sustainability"] = sustainability
190
+ enrichment["xgi_divergence"] = divergence
175
191
 
176
192
  if player_priors:
177
193
  prior = player_priors.get(player.id)