fplkit 2.4.1__tar.gz → 2.6.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.
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/TOOLS.md +9 -9
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/build-test-plan/SKILL.md +28 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/build-test-plan/references/plan-template.md +25 -3
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/scripts/_bootstrap.py +12 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/.claude/output-styles/fpl-mate.md +20 -10
- {fplkit-2.4.1 → fplkit-2.6.0}/AGENTS.md +10 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/CHANGELOG.md +65 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/CLAUDE.md +10 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/CONTRIBUTING.md +44 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/PKG-INFO +34 -7
- {fplkit-2.4.1 → fplkit-2.6.0}/README.md +29 -3
- {fplkit-2.4.1 → fplkit-2.6.0}/docs/architecture.md +23 -13
- {fplkit-2.4.1 → fplkit-2.6.0}/docs/command-reference.md +178 -30
- {fplkit-2.4.1 → fplkit-2.6.0}/docs/custom-analysis.md +17 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/_version.py +2 -2
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/action/waiver.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/analysis/bench_order.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/analysis/captain.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/analysis/squad_analyzer.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/analysis/starting_xi.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/analysis/stats.py +4 -3
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/analysis/transfer_eval.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/base.py +4 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/data/fixture.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/data/price.py +6 -3
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/data/scout.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/orchestration/report.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/core_insights.py +4 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/football_data.py +18 -5
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/fpl.py +19 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/fpl_draft.py +4 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/historical.py +4 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/providers/__init__.py +7 -5
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/providers/_http.py +1 -1
- fplkit-2.6.0/fpl_cli/api/providers/_models.py +124 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/providers/anthropic.py +29 -3
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/providers/openai_compat.py +19 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/providers/perplexity.py +3 -3
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/understat.py +397 -84
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/vaastav.py +4 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/__init__.py +4 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_context.py +108 -11
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_fines.py +47 -9
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_helpers.py +16 -12
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_league_recap_data.py +524 -86
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_league_recap_history.py +653 -48
- fplkit-2.6.0/fpl_cli/cli/_league_recap_types.py +544 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_plan_grid.py +8 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_review_analysis.py +184 -12
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_review_classic.py +32 -7
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_review_draft.py +20 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_review_summarisation.py +460 -94
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/captain.py +2 -2
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/chips.py +29 -26
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/credentials.py +3 -3
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/differentials.py +14 -6
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/doctor.py +169 -36
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/doctor_providers.py +157 -6
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/fdr.py +7 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/fixtures.py +13 -6
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/history.py +14 -8
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/league.py +2 -2
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/league_fines.py +14 -2
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/league_recap.py +179 -24
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/player.py +13 -8
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/preview.py +10 -6
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/price_changes.py +2 -2
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/price_history.py +15 -10
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/ratings.py +42 -5
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/review.py +150 -39
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/sell_prices.py +4 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/stats.py +2 -2
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/status.py +6 -2
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/targets.py +2 -2
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/transfer_eval.py +12 -6
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/waivers.py +18 -10
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/xg.py +7 -7
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/models/league_history.py +29 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/models/types.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/paths.py +43 -11
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/prompts/league_recap.py +233 -21
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/prompts/review.py +463 -18
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/prompts/scout.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/scraper/fpl_prices.py +11 -7
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/season.py +135 -9
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/fixture_predictions.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/league_history.py +30 -0
- fplkit-2.6.0/fpl_cli/services/player_clubs.py +366 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/player_prior.py +2 -7
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/returnee_radar.py +7 -3
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/scoring/__init__.py +4 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/scoring/constants.py +30 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/season_previews.py +1 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/team_ratings.py +40 -11
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/team_ratings_prior.py +402 -36
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/templates/gw_league_recap.md.j2 +20 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/templates/gw_review.md.j2 +26 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/utils/text.py +21 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/pyproject.toml +29 -6
- {fplkit-2.4.1 → fplkit-2.6.0}/scripts/calibrate_quality_ceilings.py +15 -2
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/conftest.py +9 -6
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_api_client.py +29 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_api_understat.py +584 -0
- fplkit-2.6.0/tests/test_calibrate_quality_ceilings.py +111 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_chips.py +95 -24
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_differentials.py +30 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_doctor.py +228 -10
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_doctor_providers.py +149 -5
- fplkit-2.6.0/tests/test_cli_failure_parity.py +113 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_failure_streams.py +8 -4
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_format.py +85 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_format_suppression.py +1 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_fpl_block.py +1 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_history.py +46 -13
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_league_fines.py +23 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_league_recap.py +1758 -38
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_preview.py +27 -5
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_price_history.py +32 -21
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_review.py +1266 -56
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_status.py +21 -1
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_waivers.py +16 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_warnings_stderr.py +14 -6
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_xg.py +12 -0
- fplkit-2.6.0/tests/test_dependencies.py +149 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_fines.py +43 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_gw_prep_script_bootstrap.py +34 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_league_history_store.py +64 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_league_recap.py +1697 -32
- fplkit-2.6.0/tests/test_output_style_sync.py +108 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_paths.py +145 -0
- fplkit-2.6.0/tests/test_player_clubs.py +314 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_player_prior.py +10 -10
- fplkit-2.6.0/tests/test_prompt_tables.py +101 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_providers.py +244 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_report_agent.py +139 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_report_paths.py +37 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_returnee_radar.py +41 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_review.py +593 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_scoring.py +59 -0
- fplkit-2.6.0/tests/test_season.py +408 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_skill_stats_thresholds.py +24 -3
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_team_ratings.py +105 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_team_ratings_prior.py +522 -3
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_utils_text.py +9 -1
- fplkit-2.4.1/fpl_cli/api/providers/_models.py +0 -45
- fplkit-2.4.1/fpl_cli/cli/_league_recap_types.py +0 -235
- fplkit-2.4.1/requirements.lock +0 -400
- fplkit-2.4.1/tests/test_season.py +0 -237
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/README.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/build-test-plan/references/workspace-env.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/create-pr/SKILL.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/SKILL.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/references/entity-normalisation.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/references/output-template.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/references/rules.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/scripts/bench_order.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/scripts/extract_classic_squad.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/scripts/normalise_entities.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/scripts/starting_xi.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/scripts/transfer_eval.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/gw-prep/scripts/validate_draft_waivers.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/preview-ingest/SKILL.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/release/SKILL.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/release-notes/SKILL.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/squad-builder/SKILL.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/squad-builder/references/output-template.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/squad-builder/references/rules.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.agents/skills/update-gw-prep/SKILL.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.claude/settings.json +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.env.example +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.github/workflows/ci.yml +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.github/workflows/pr-title.yml +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.github/workflows/release.yml +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/.gitignore +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/LICENSE +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/cliff.toml +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/config/team_ratings_overrides.yaml +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/docs/fpl-rules.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/docs/images/fpl-player-demo-v1-0.png +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/action/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/analysis/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/common.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/data/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/agents/orchestration/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/contract.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/dataset_fetcher.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/api/historical_types.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_banner.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_fines_config.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/_json.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/allocate.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/init.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/intel.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/returnees.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/cli/squad.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/config/defaults.yaml +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/config/fixture_predictions.yaml +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/config/previews/EXAMPLE.yaml +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/config/team_managers.yaml +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/constants.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/models/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/models/chip_plan.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/models/fixture.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/models/player.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/models/team.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/parsers/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/parsers/recommendations.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/prompts/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/prompts/returnees.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/scraper/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/league_history_counters.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/league_history_fines.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/league_history_notes.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/matchup.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/scoring/data_prep.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/scoring/display.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/scoring/evaluation.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/scoring/ownership.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/scoring/shrinkage.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/scoring/signals.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/scoring/single_gw.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/scoring/value_quality.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/squad_allocator.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/services/team_form.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/templates/gw_preview.md.j2 +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/utils/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/utils/files.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/utils/gameweek.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/utils/markdown.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/utils/teams.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/fpl_cli/utils/time.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/pyrightconfig.json +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/scripts/backtest_early_season_prior_blend.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/scripts/calibrate_promoted_prior.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/__init__.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/fixtures/classic_squad_fixture.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/fixtures/recommendations_fixture.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/fixtures/validate_draft_waivers/recs_clean.md +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/fixtures/validate_draft_waivers/squad_grid.json +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/fixtures/validate_draft_waivers/waivers.json +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/fixtures/waiver_ranked_golden.json +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_agents_action.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_agents_analysis.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_agents_analysis_squad.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_agents_base.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_agents_data.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_agents_data_price.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_agents_orchestration.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_allocate.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_api_core_insights.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_api_dataset_fetcher.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_api_draft.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_api_football_data.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_api_historical.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_api_vaastav.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_bench_order_script.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_classic_squad_extraction.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_allocate.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_banner.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_captain.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_context_settings.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_credentials.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_custom_analysis.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_fdr.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_fdr_blanks.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_fixtures.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_init.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_intel.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_json.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_json_contract.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_league.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_plan_grid.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_player.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_price_changes.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_returnees.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_sell_prices.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_settings_warning.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_squad.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_stats.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_targets.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_cli_transfer_eval.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_common.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_common_scoring.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_data_prep_harmonisation.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_doc_consistency.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_fines_config.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_fixture_predictions.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_fpl_prices.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_init.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_init_fines.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_league_history_counters.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_league_history_fines.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_league_history_notes.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_matchup_service.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_models.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_normalise_entities.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_player_resolution.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_preseason.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_recommendations_parser.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_rolling_pts_per_m.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_scraper.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_season_awareness.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_season_previews.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_sell_prices.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_skill_fdr_quality_signals.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_squad_allocator.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_starting_xi_agent.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_starting_xi_script.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_stats.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_transfer_eval_agent.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_transfer_eval_script.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_utils_gameweek.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_utils_markdown.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_utils_time.py +0 -0
- {fplkit-2.4.1 → fplkit-2.6.0}/tests/test_validate_draft_waivers.py +0 -0
|
@@ -34,37 +34,37 @@ with `2>/dev/null` without losing the table, whichever command it runs.
|
|
|
34
34
|
|---------|-------------|------|--------|-------------|---------|
|
|
35
35
|
| `init` | Set up fpl-cli with your FPL IDs and optional features | No | General | No | direct-api |
|
|
36
36
|
| `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 |
|
|
37
|
-
| `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 (
|
|
37
|
+
| `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, reports whether the calibrated quality ceilings were measured against the newest completed season (the one season-derived artefact frozen in code, so a rollover with no recalibration leaves the scoring scale anchored to a cohort that has turned over), 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 players actually join by name (both Understat checks call the enrichment's own gate and matcher on the payload the scoring commands scan, so probe and runtime cannot disagree; a club resolving does not mean its players' names line up) 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 |
|
|
38
38
|
| `fixtures` | Show fixtures for a gameweek. With custom analysis on, FDR is the venue-aware ATK/DEF mean `fpl fdr` and `fpl preview` show, so one match reads the same on all three; `-m/--mode` picks `difference` (default) or `opponent`, an unrated club scores a neutral 4.0 rather than the FPL API's 1-5 difficulty, and when the ratings cannot support difficulty at all, table mode warns on stderr and JSON adds a `team_ratings_unusable` entry to `metadata.warnings` (always present, empty or not). With it off, the raw FPL API 1-5 difficulty, and an explicitly passed `-m` reports on stderr that it does not apply. JSON `metadata` carries `custom_analysis`, `fdr_scale` (`team_ratings_1_7` / `fpl_api_1_5`) and `fdr_mode` (null when it does not apply) | Yes | General | No | direct-api |
|
|
39
39
|
| `player` | Look up a player's stats, xG, ownership and fixture run. Before GW10 `quality_score` blends last season's pedigree in and JSON `metadata.warnings` says so (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded), same as `fpl stats --value`; `understat_team_unmatched` joins it for any club Understat carried no players for, whose whole squad loses xG enrichment. 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 |
|
|
40
40
|
| `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 GW10, `--value` quality scores blend last season's pts/90 pedigree with this season's observation and the command says so (stderr notice quoting the observation's weight band; JSON `metadata.warnings` code `early_season_prior_informed`); when last season's history cannot be loaded they are pure observation and the notice says that instead (`early_season_small_sample`, before GW6). `understat_team_unmatched` is raised once per club Understat carried no players for, whose whole squad scores without xG. 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 |
|
|
41
|
-
| `history` | Show historical player performance across seasons | Yes | General | No | direct-api |
|
|
41
|
+
| `history` | Show historical player performance across seasons. A provider that refuses exits 1 in both output formats, carrying the upstream's own reason: the error envelope used to say only `Failed to fetch historical data` while the cause went to stderr, and table mode printed the cause to stdout and exited 0 | Yes | General | No | direct-api |
|
|
42
42
|
| `league` | Show live league standings for Classic and Draft leagues | No | General | No | direct-api |
|
|
43
43
|
| `fdr` | Analyse fixture difficulty - easy runs, blanks, doubles. Three payloads, not one: the default call runs the agent and returns the full analysis plus `data.ratings_warning` (the team-ratings quality verdict, and with `--my-squad` also `data.predictions_stale` / `data.prediction_warnings` / `data.squad_exposure`); with custom analysis off it returns raw 1-5 FPL API difficulty (`data.easy_fixture_runs` only, `metadata.custom_analysis: false`, no ratings and so no warning); `--blanks` bypasses the agent for a schedule-only payload carrying neither the FDR analysis nor `ratings_warning`. `--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 |
|
|
44
44
|
| `xg` | Analyse underlying stats: xG, xA, overperformers. The minutes floor and the window scale to the gameweeks played (`min(60 x window, 45 x gameweeks played)`, so 90 minutes over 2 GWs rather than an unreachable 360 over 6); the configured bar binds from GW9, or GW11 for `--all`. JSON `metadata` carries `window_label`, `gameweeks_played` and `min_minutes`, and `metadata.warnings` an `early_season_minutes_floor` notice while the bar is scaled, plus `understat_team_unmatched` for any club Understat carried no players for. When nothing qualifies, `data.empty_reason` says whether the floor (`below_minutes_floor`) or the data (`no_minutes_played`) caused it | Yes | General | No | via-agent |
|
|
45
45
|
| `price-changes` | Show price changes and transfer activity | No | General | No | via-agent |
|
|
46
|
-
| `price-history` | Show price trajectory and transfer momentum | Yes | General | No | direct-api |
|
|
46
|
+
| `price-history` | Show price trajectory and transfer momentum. A trend provider that refuses exits 1 in both output formats with the upstream's own reason — the error envelope used to say only `Failed to fetch price history data` while the table path named the cause | Yes | General | No | direct-api |
|
|
47
47
|
| `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`, `transitions_baseline_gameweek` (the gameweek `transition` is measured against — every run in a gameweek diffs against the last state stored before it, so a repeat run reports the same changes), `quality_bar_available`, `quality_bar_applied` and `enrichment_requested`/`_available`/`_note`/`_count`/`_rate_limited` (true when the search provider rate-limited the run even after retrying — the one failure a re-run is likely to clear); `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 |
|
|
48
48
|
| `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 |
|
|
49
|
-
| `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 |
|
|
50
|
-
| `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
|
|
49
|
+
| `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. Under `--summarise` the personal analysis is handed next gameweek's fixtures — every club's opponent and venue plus each squad player's positional FDR (1-7 team ratings with custom analysis on, the FPL API's 1-5 difficulty with it off, carrying the same scale footer and unrated-club caveat the FDR tables print, plus any warning that the ratings cannot support difficulty) — so its "Next Week" calls are grounded in fixtures rather than in one week of points; with no next gameweek to look at, that section is narrowed to fixture-independent observations and points at `fpl gw-prep`, and a gameweek already played is marked retrospective. The answer is then checked back against that block — a cited FDR the block never printed, or any fixture claim made without one — and against the sections its prompt asked for: an answer missing a `## ` heading, ending mid-sentence, or that the provider says it stopped early on is retried once, and a still-incomplete one is warned about on stderr (detail in `data/debug/synthesis_corrections.txt` under `--debug`) and carries the same list as a callout in the saved report. The summary is an add-on the review does not depend on: the two provider roles resolve independently, and a role with no usable key is skipped with the reason on stderr (`Community narrative skipped: ...` / `Personal analysis skipped: ...`) while the review prints, saves and exits 0 — so one key produces that half rather than nothing, and a saved report names each skipped half in a warning callout of its own. A gameweek it cannot resolve - one still being played, an id the season does not have, a season with nothing finished yet - is refused with the specific reason on stderr and exit 1; stdout carries the review or nothing | No | General | No | via-agent |
|
|
50
|
+
| `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 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. The club is not carried but *derived*, from the gameweek's own fixtures: FPL's live data records which fixtures each player's club played, so a double gameweek settles his club outright and a single narrows it to two, with one `element-summary` request spent only where today's club is absent from that pair (proof he has moved); today's club being one of the two is left unanswered rather than assumed, so a recorded club still wins there. That reaches the two cases a carry structurally cannot — a first capture and a coarse-to-detailed upgrade, neither of which has a recorded squad — and it supersedes a stale recorded club rather than preserving it, with `league_history_club_rederived` naming how many were replaced. A club that blanked has no fixture to intersect and falls back to the recorded club, then today's; name and position are not derivable from fixtures at all and stay carry-only. 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. A rebuild likewise never writes away a league position or cumulative total it cannot re-derive: draft has no per-manager history endpoint and its standings describe a later gameweek, so a replayed draft gameweek keeps the figures already recorded (`league_history_standings_carried`) instead of nulling them, which used to cost the ledger every position that gameweek held and collapse the streaks built on them. Where they were already lost, every run restores them from the ledger itself — the superseded line, or a cumulative total re-summed from earlier gameweeks — with no flag and no request (`league_history_standings_repaired`); a draft gameweek's table is derivable from `gross_points` alone at the league's start 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` (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. A recap of the most recently finished gameweek keeps working once the next gameweek has started: the league table describes that later gameweek from its deadline onward, so the run takes the replay path (figures from each manager's own gameweek history, positions and cumulative totals derived rather than read off the table) and raises a `league_standings_moved_on` warning, instead of reconciling two different gameweeks' numbers against each other and exiting 1 with the ledger row unwritten. `--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; an editorial the provider stopped early on is written out as it stands with the reason on stderr and a `synthesis_stopped_early` warning in JSON. A gameweek that cannot be resolved at all exits 1 in both output formats - it used to exit 1 under `--format json` with a generic `"Could not resolve a gameweek to recap."` and 0 in table mode - and the resolver's own reason (`Gameweek 7 is not yet finished`) now reaches the error envelope and the stderr prose alike. On the league's opening gameweek only (GW1, or its own `start_event`), each classic manager's FPL-wide prior-season record — seasons played, last season's points/rank/top-% finish, best season — is fetched from the manager-history endpoint (one request per manager, that gameweek only, and only with `--save`/`--output` or `--summarise`; a league created after GW1 shares the fetch with its league-start offset) and rendered as a `# Prior Seasons` report section and a locked `## Prior Seasons` prompt section with an explicit count; tenure is phrased FPL-wide because entry and league IDs are reissued each season, and a manager with no record or a failed fetch is named as such rather than omitted. Nothing of it reaches the ledger or the JSON rows; draft has no such endpoint and no such section | Yes | General | No | via-agent |
|
|
51
51
|
| `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 |
|
|
52
52
|
| `captain` | Analyse and rank captain options for next gameweek. Your squad is the default, so an absent `classic_entry_id` exits 1 with an `error` envelope naming `--global` rather than returning the global list as though it were your options; `metadata.my_squad_mode` says which list the payload holds, and `metadata.warnings` carries `captain_global_fallback` when there is no squad to rank yet (before the first deadline) or `captain_no_next_gameweek` when there is no next gameweek at all (finished season, or the next one unpublished). An entry ID that does not resolve is a failure, not a fallback, worded exactly as `squad` words it (`get_own_squad_picks`, `agents/common.py`). 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 |
|
|
53
|
-
| `differentials` | Find differential picks - high potential, low ownership. Inverted consistency bonus (volatile players score higher, phased in GW6-10). Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage, so a quiet-starting elite is not ranked below a one-game wonder; the ownership, matchup and consistency bonuses stay pure observation; JSON `metadata.warnings` carries the early-season notice while the blend is live (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded) plus `understat_team_unmatched` for any club Understat carried no players for, table mode prints them to stderr. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the blend rather than handed last season's standing | Yes | Classic | Yes | via-agent |
|
|
53
|
+
| `differentials` | Find differential picks - high potential, low ownership. Inverted consistency bonus (volatile players score higher, phased in GW6-10). Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage, so a quiet-starting elite is not ranked below a one-game wonder; the ownership, matchup and consistency bonuses stay pure observation; JSON `metadata.warnings` carries the early-season notice while the blend is live (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded) plus `understat_team_unmatched` for any club Understat carried no players for, table mode prints them to stderr. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the blend rather than handed last season's standing. The gameweek stamped into JSON `metadata` is fetched after the agent succeeds, not before it runs, so an unreachable API is reported as the agent's failure in both output formats rather than as a client error under `--format json` and an agent error in table mode | Yes | Classic | Yes | via-agent |
|
|
54
54
|
| `targets` | Find transfer targets - high performers across all ownership. Consistency bonus (CV-xGI percentile, phased in GW6-10). Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage, so a quiet-starting elite is not ranked below a one-game wonder; the matchup and consistency bonuses stay pure observation, and the score agrees with `fpl transfer-eval`'s Outlook column; JSON `metadata.warnings` carries the early-season notice while the blend is live (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded) plus `understat_team_unmatched` for any club Understat carried no players for, table mode prints them to stderr. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the blend rather than handed last season's standing | Yes | Classic | Yes | via-agent |
|
|
55
55
|
| `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. Before GW10 `quality_score` carries the value family's prior blend (matching `fpl player` and `fpl stats --value`) and `target_score` the target family's (matching `fpl targets`); the single-GW `lineup_score` keeps position-mean shrinkage. JSON `metadata.warnings` carries the early-season notice naming both blended columns, or only `target_score` when no Understat match produced a quality score. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of whichever device applies, rather than pulled toward the position mean or handed last season's standing. A name two players answer to exactly (`Henderson`) exits 1 with an `error` envelope naming both, rather than resolving to whichever the API lists first; `Name (TEAM)` or a player ID picks one | Yes | General | Yes | via-agent |
|
|
56
56
|
| `allocate` | Select mathematically optimal 15-player squad within budget. At `--horizon >= 2` the solver ranks on the value family's GW1-9 prior blend (last season's pedigree weighted by confidence); players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of it rather than handed last season's standing, and stay in the solver pool; JSON `metadata.warnings` carries the early-season notice at `--horizon >= 2` (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded), plus `understat_team_unmatched` for any club Understat carried no players for, whose squad therefore scored without xG | Yes | Classic | Yes | direct-api |
|
|
57
|
-
| `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. Keeper scores carry the GK signal block (saves/90, defensive quality, clean-sheet rate), read from the main-game player each draft element is joined to on `code` because the draft bootstrap publishes none of it, so draft keepers rank on what separates them rather than on form and ppg alone. Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage; the matchup, position-need, team-stacking and consistency terms stay pure observation; JSON `metadata.warnings` carries the early-season notice while the blend is live, and reports `early_season_small_sample` both when history could not be loaded and when no draft element found a main-game counterpart to take a prior from. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the blend rather than handed last season's standing | Yes | Draft | Yes | via-agent |
|
|
57
|
+
| `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. Keeper scores carry the GK signal block (saves/90, defensive quality, clean-sheet rate), read from the main-game player each draft element is joined to on `code` because the draft bootstrap publishes none of it, so draft keepers rank on what separates them rather than on form and ppg alone. Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage; the matchup, position-need, team-stacking and consistency terms stay pure observation; JSON `metadata.warnings` carries the early-season notice while the blend is live, and reports `early_season_small_sample` both when history could not be loaded and when no draft element found a main-game counterpart to take a prior from. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the blend rather than handed last season's standing. No `draft_league_id` configured exits 1 in both output formats — table mode used to warn and exit 0 | Yes | Draft | Yes | via-agent |
|
|
58
58
|
| `squad` | Analyse your FPL squad health and fixtures. Exits 1 with an `error` envelope when no entry ID is configured for the active format. `--classic` / `--draft` (mutually exclusive) pin the format instead of inferring it from the configured IDs, so a caller that means classic gets the missing-ID error rather than the draft roster; with neither, single-format configs still auto-select. A picks 404 is checked against `entry/<id>/` before it is reported as "no squad submitted yet", so a reissued or wrong entry ID says so; `squad grid` and `captain` share that diagnosis through `agents/common.py` | Yes | General | No | via-agent |
|
|
59
59
|
| `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. Takes the same `--classic` / `--draft` pinning as `squad`, before or after the subcommand, and the same stale-entry diagnosis. A `--watch` name that does not resolve is non-fatal either way: unmatched, or matching two players exactly, it warns on stderr and is skipped, and the grid still renders | Yes | General | No | via-agent |
|
|
60
60
|
| `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 |
|
|
61
61
|
| `chips` | View and plan FPL chip usage | Yes | Classic | No | direct-api |
|
|
62
|
-
| `chips timing` | Recommend chip timing based on blank/double GW exposure | Yes | Classic | No | via-agent |
|
|
62
|
+
| `chips timing` | Recommend chip timing based on blank/double GW exposure. No `classic_entry_id` configured, and no completed gameweek to measure exposure against, each exit 1 in both output formats — table mode used to print the reason and exit 0. The missing-ID message is the shared one `fpl squad` and `fpl squad grid` give, and a failed fixture agent reports the agent's own reason in the same words either way (`Fixture agent failed: <reason>`), where the envelope and the terminal used to word it differently and neither passed the reason on | Yes | Classic | No | via-agent |
|
|
63
63
|
| `chips add` | Plan a chip for a gameweek | No | Classic | No | direct-api |
|
|
64
64
|
| `chips remove` | Remove a planned chip from a gameweek | No | Classic | No | direct-api |
|
|
65
65
|
| `chips sync` | Sync chip usage from FPL API | No | Classic | No | direct-api |
|
|
66
66
|
| `ratings` | Display team ratings | No | General | Yes | direct-api |
|
|
67
|
-
| `ratings update` | Recalculate ratings from fixture results (shrunk toward the previous-season prior before GW12; seeds from the prior when no results exist yet). Wherever it applies the prior it names the clubs rated from Championship results or the flat promoted estimate and points at `team_ratings_prior.yaml`, whose `inputs` block records each club's basis, matches and the rates it was ranked on | No | General | Yes | direct-api |
|
|
67
|
+
| `ratings update` | Recalculate ratings from fixture results (shrunk toward the previous-season prior before GW12; seeds from the prior when no results exist yet). Wherever it applies the prior it names the clubs rated from Championship results or the flat promoted estimate — with the remedy attached where no `FOOTBALL_DATA_API_KEY` is set — and points at `team_ratings_prior.yaml`, whose `inputs` block records each club's basis, matches and the rates it was ranked on. The prior rebuilds itself when an input it went without becomes available; `--refresh-prior` forces it | No | General | Yes | direct-api |
|
|
68
68
|
| `intel` | Show season preview intel collected per team, with coverage and per-gameweek decay. JSON `metadata` carries `coverage.usable_as` (`full` / `negative_filter_only` / `none`), `section_confidence`, `sections_live`/`sections_expired`, `decay_schedule`, `team_set_warning` and `warnings`. `-g/--gameweek` ages the payload to any gameweek; `--show-decay` prints the expiry schedule | Yes | General | No | direct-api |
|
|
69
69
|
| `intel schema` | Print the preview file format with every field explained | No | General | No | direct-api |
|
|
70
70
|
| `intel init` | Scaffold an empty preview file per Premier League team (`--force` overwrites). Stubs never count toward coverage | No | General | No | direct-api |
|
|
@@ -224,6 +224,34 @@ code path runs tells you nothing about whether the bug is gone.
|
|
|
224
224
|
the source. Commands, files on disk, exit codes, stdout vs stderr — never
|
|
225
225
|
internal functions.
|
|
226
226
|
|
|
227
|
+
**It writes nothing into the vault the run wouldn't have written anyway.**
|
|
228
|
+
The vault is someone's working record, not a scratch pad. `01_Reports/`
|
|
229
|
+
holds point-in-time snapshots — the gameweek as it was analysed at the
|
|
230
|
+
time — so a test that regenerates one overwrites the record with a later
|
|
231
|
+
model's output, and a test that saves a preview for a gameweek whose
|
|
232
|
+
deadline has not passed creates a file that should not exist yet. Both
|
|
233
|
+
land in a PR as noise the author then has to spot and revert, which has
|
|
234
|
+
happened on more than one run.
|
|
235
|
+
|
|
236
|
+
So: every `--save` you write into a plan carries `-o <scratch>/reports`.
|
|
237
|
+
`preview`, `review` and `league-recap` all take `-o/--output`, and the
|
|
238
|
+
report is just as assertable from a scratch path — you are checking its
|
|
239
|
+
content, not its location. Where a test must assert the *default* path is
|
|
240
|
+
used, assert it on a scratch `HOME`/cwd rather than by writing into the
|
|
241
|
+
real tree.
|
|
242
|
+
|
|
243
|
+
The same discipline covers `data/`: the ledger under
|
|
244
|
+
`data/league_history/` is the one thing worth capturing for real, because
|
|
245
|
+
its rows are append-only and cannot be reconstructed once the season moves
|
|
246
|
+
on. Everything else there — `returnee_snapshot.json`, `team_ratings*.yaml`,
|
|
247
|
+
`team_finances.json` — is regenerable state the CLI rewrites, so a test
|
|
248
|
+
that migrates or rewrites one points `FPL_CLI_DATA_DIR` at a throwaway
|
|
249
|
+
copy. A migration performed as a side effect of a test is not a change
|
|
250
|
+
anyone decided to make, and it reads in review as if it were.
|
|
251
|
+
|
|
252
|
+
The question to ask of every artifact a plan produces: *would this have
|
|
253
|
+
been created anyway, by real use, today?* If no, it goes to scratch.
|
|
254
|
+
|
|
227
255
|
**It builds a fixture before it gives up.** When live data can't reach a
|
|
228
256
|
code path, a scratch fixture often can: point `FPL_CLI_DATA_DIR` at a
|
|
229
257
|
throwaway copy of the store and doctor a row to the shape the bug needed,
|
|
@@ -28,6 +28,17 @@ produces. Which parts can slip to a later session.]
|
|
|
28
28
|
- [cost awareness: which flags spend LLM calls, scraper runs or FPL
|
|
29
29
|
requests]
|
|
30
30
|
- [anonymity: fpl-cli is public, this vault is private]
|
|
31
|
+
- **[reports go to a scratch directory, never the vault's report tree.**
|
|
32
|
+
`preview`, `review` and `league-recap` all take `-o/--output`, so every
|
|
33
|
+
`--save` in this plan must carry `-o <scratch>/reports`. Files under
|
|
34
|
+
`01_Reports/<season>/` are point-in-time snapshots of a gameweek as it
|
|
35
|
+
was analysed at the time — regenerating one overwrites the record with a
|
|
36
|
+
later model's output, and a preview for a gameweek whose deadline has
|
|
37
|
+
not passed is simply wrong to have on disk. Neither is a test artifact.
|
|
38
|
+
The same goes for rewritable state under `data/` that is not the ledger:
|
|
39
|
+
a test that migrates or rewrites a snapshot file (`returnee_snapshot.json`,
|
|
40
|
+
`team_ratings*.yaml`, `team_finances.json`) runs against a scratch
|
|
41
|
+
`FPL_CLI_DATA_DIR`, so the real one is untouched.]
|
|
31
42
|
## Preflight
|
|
32
43
|
**P1 — version.** [...]
|
|
33
44
|
**P2 — environment.** [...]
|
|
@@ -47,7 +58,14 @@ found, and whether this range claims to fix it.]
|
|
|
47
58
|
## Wrap-up
|
|
48
59
|
1. [replace the Status line under the title with the run's date, version
|
|
49
60
|
and PASS/FAIL/BLOCKED counts]
|
|
50
|
-
2. [commit generated state
|
|
61
|
+
2. [commit the filled plan, and **only** the generated state that is both
|
|
62
|
+
irreplaceable and genuinely new: new rows under `data/league_history/`
|
|
63
|
+
for a gameweek that had none. Do **not** commit regenerated reports
|
|
64
|
+
under `01_Reports/`, a preview for a future gameweek, or a rewritten
|
|
65
|
+
`data/` snapshot — revert those with `git checkout` before committing.
|
|
66
|
+
If a test needed a report, it was written to the scratch dir and the
|
|
67
|
+
Results-log row cites it from there. The test that a piece of state
|
|
68
|
+
passes: would this have been created anyway, by real use, today?]
|
|
51
69
|
3. [fill the Results log, summarise counts, separate fpl-cli bugs from
|
|
52
70
|
environment issues, and record anything noticed outside the test
|
|
53
71
|
matrix as a post-plan finding]
|
|
@@ -109,8 +127,12 @@ The wrap-up should also have the runner:
|
|
|
109
127
|
rows is noise. Give it the `testing` label, state the condition that
|
|
110
128
|
unblocks each item, and record the data state at the time of testing:
|
|
111
129
|
a later session cannot reconstruct why something was unreachable.
|
|
112
|
-
- Commit the filled plan alongside
|
|
113
|
-
and the evidence land together.
|
|
130
|
+
- Commit the filled plan alongside any genuinely new ledger rows, so the
|
|
131
|
+
record and the evidence land together. Evidence that lives in the
|
|
132
|
+
scratch dir stays there — quote the numbers into the Results-log row
|
|
133
|
+
instead, because the row has to stand on its own once the scratch dir is
|
|
134
|
+
gone. Never commit a report the run regenerated: the Results log is the
|
|
135
|
+
artifact, not a rewritten snapshot.
|
|
114
136
|
|
|
115
137
|
## The coverage map
|
|
116
138
|
|
|
@@ -63,7 +63,12 @@ def is_fpl_cli_missing(exc: ModuleNotFoundError) -> bool:
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
try:
|
|
66
|
-
from fpl_cli.paths import
|
|
66
|
+
from fpl_cli.paths import (
|
|
67
|
+
UserDirError,
|
|
68
|
+
ensure_legacy_migration,
|
|
69
|
+
ensure_user_dirs_valid,
|
|
70
|
+
load_env_files,
|
|
71
|
+
)
|
|
67
72
|
except ModuleNotFoundError as exc:
|
|
68
73
|
if not is_fpl_cli_missing(exc):
|
|
69
74
|
raise
|
|
@@ -78,9 +83,15 @@ def bootstrap_user_dirs() -> None:
|
|
|
78
83
|
repo-root dirs silently gets empty settings and regenerated ratings.
|
|
79
84
|
A bad FPL_CLI_* override surfaces here as a clean JSON error instead
|
|
80
85
|
of a traceback mid-agent.
|
|
86
|
+
|
|
87
|
+
`ensure_user_dirs_valid()` is the CLI's other startup step: without it, a
|
|
88
|
+
relative FPL_CLI_DATA_DIR/FPL_CLI_CACHE_DIR resolves silently against
|
|
89
|
+
this script's launching cwd instead of failing here, in an installed venv
|
|
90
|
+
where the legacy dirs migration checks for never exist (#139 review).
|
|
81
91
|
"""
|
|
82
92
|
try:
|
|
83
93
|
load_env_files()
|
|
84
94
|
ensure_legacy_migration()
|
|
95
|
+
ensure_user_dirs_valid()
|
|
85
96
|
except UserDirError as exc:
|
|
86
97
|
fail([str(exc)])
|
|
@@ -54,7 +54,11 @@ This guide describes the CLI as shipped at this commit. If a documented command
|
|
|
54
54
|
|
|
55
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
56
|
|
|
57
|
-
`ratings`, `price-changes` and `
|
|
57
|
+
`ratings`, `price-changes`, `league`, `preview` and `review` are Rich-only - read the table or the written report, 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
|
+
Seven commands below are off unless `custom_analysis: true` is set in `settings.yaml`: `captain`, `targets`, `differentials`, `waivers`, `transfer-eval`, `allocate` and `ratings` are hidden from `fpl --help` entirely with it off, and a call comes back "is a custom-analysis command and is currently switched off". That is a config state, not a missing feature - say so and point at `fpl init`, don't quietly substitute a weaker command. The rest still run with it off, but come back narrower: `fpl fdr` refuses `-p atk|def` outright, `fpl fixtures` falls back to the FPL API's raw 1-5 difficulty rather than the venue-aware FDR (so those numbers won't line up with `fpl fdr`'s), and `fpl xg`, `fpl stats -v`, `fpl player`, `fpl preview` and `fpl review` drop their custom-scored sections. `fpl status` just prints a note that the features exist.
|
|
60
|
+
|
|
61
|
+
Format shapes the help listing rather than availability: a draft-only setup drops the classic commands (`captain`, `targets`, `differentials`, `allocate`, `chips`, `credentials`) out of `fpl --help`, and a classic-only one drops `waivers`. They still resolve when called, and fail on the entry or league ID that isn't configured - so a command missing from `--help` is a format signal, not proof it's gone.
|
|
58
62
|
|
|
59
63
|
**Quick lookups:**
|
|
60
64
|
- `fpl status` - GW state, deadline countdown, post-GW summary, pre-deadline info
|
|
@@ -62,18 +66,20 @@ When pulling data for comparison or field extraction, use `--format json` on com
|
|
|
62
66
|
- `-f` fixture run with positional FDR (`-m difference|opponent` switches how FDR is computed)
|
|
63
67
|
- `-d` GW-by-GW match performance (FPL API, always fresh)
|
|
64
68
|
- `-u` Understat analysis (shot analysis + situation profile) - use with `-d` for MID/FWD
|
|
65
|
-
- `-H` historical career arc - pts/90, xGI/90
|
|
69
|
+
- `-H` historical career arc - per-season points, minutes, starts, goals, assists, xGI and cost across the last four seasons, with pts/90, xGI/90 and cost trends beneath. The trend line says "trend" only on three or more qualifying seasons and "change" on fewer - a two-season "change" is one comparison, not a direction
|
|
66
70
|
- `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
|
|
71
|
+
- `fpl fixtures -g <gw>` - who plays who, difficulty. The gameweek is a flag, not a positional argument (`fpl fixtures 5` is an error); `-m opponent` rates the opponent alone
|
|
72
|
+
- `fpl history` - career arc for every current player in one pass: per-season pts/90 and xGI/90 plus points, xGI and cost trends. No filters and no flags beyond `--format`, so it's the bulk read - `fpl player -H` is the single-player one
|
|
68
73
|
- `fpl league` - standings context for risk calibration
|
|
69
74
|
- `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
|
|
75
|
+
- `fpl init` - the setup walkthrough (entry and league IDs, format, custom analysis). Where to send the user when `doctor` reports nothing configured, or when a command they want is switched off - never guess IDs on their behalf
|
|
70
76
|
|
|
71
77
|
**Analysis agents (run when the question needs deeper data):**
|
|
72
78
|
- `fpl captain` - captain rankings for the GW (use `--global` for picks beyond your squad)
|
|
73
79
|
- `fpl targets` - transfer targets across all ownership levels; `--min-own` floors ownership, `-m` sets the minutes bar
|
|
74
80
|
- `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
|
|
81
|
+
- `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; add `--draft` when both formats are configured), `--blanks` for the confirmed-plus-predicted blank/double schedule on its own
|
|
82
|
+
- `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, `--refresh-prior` rebuilds last season's prior from scratch (it rebuilds itself when the league's clubs change, so this is for a staleness it can't see)
|
|
77
83
|
- `fpl price-changes` - price risers/fallers, hot transfers in/out, season value gains - use when transfer timing is at stake
|
|
78
84
|
- `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
85
|
- `fpl xg` - full xG/xA analysis (`-n 6` last 6 GWs, `--all` whole season); surfaces underperformers, overperformers, value picks
|
|
@@ -94,19 +100,23 @@ When pulling data for comparison or field extraction, use `--format json` on com
|
|
|
94
100
|
- `full` (75%+ of teams covered) - intel may support *or* oppose a pick
|
|
95
101
|
- `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
102
|
- `none` - ignore entirely
|
|
103
|
+
- Authoring rather than asking: `fpl intel init` scaffolds an empty preview file per team and `fpl intel resolve <TEAM>` matches the player names in one to FPL player codes (`--write` saves them back, `--all` re-resolves already-coded players; ambiguity is reported, never guessed). That's the preview-ingest path - reach for it when the user is adding intel, not when they're asking a question of it
|
|
97
104
|
- 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
105
|
|
|
99
106
|
**Squad building:**
|
|
100
107
|
- `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
108
|
|
|
102
109
|
**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
|
|
110
|
+
- `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 `fpl chips remove -g <gw>` to plan (chip keys are `wildcard`, `freehit`, `bboost`, `3xc`), `fpl chips sync` to pull actual usage from the FPL API
|
|
104
111
|
- `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 (
|
|
112
|
+
- `fpl squad sell-prices` - buy/sell prices and P&L across squad. Classic only (draft has no budget); cached by default, `--refresh` scrapes the FPL site (needs credentials from `fpl credentials set` or `FPL_EMAIL`/`FPL_PASSWORD`), `--format json` for allocator input
|
|
113
|
+
|
|
114
|
+
**Pre-deadline write-up:**
|
|
115
|
+
- `fpl preview` - the whole pre-gameweek report in one run (fixtures, prices, stats, captaincy) rather than a command at a time; `-s` saves it under the configured output directory, `--scout` adds LLM deep research for BUY/SELL calls (needs API keys), `--dry-run` builds the scout prompts without spending a call. Rich-only, and it writes a markdown report - there's no JSON envelope to parse
|
|
106
116
|
|
|
107
117
|
**Post-gameweek:**
|
|
108
|
-
- `fpl review` - GW performance;
|
|
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
|
|
118
|
+
- `fpl review` - GW performance; `-g N` for a specific gameweek (default: last completed), `-s` saves the report, `--summarise` adds an LLM write-up (needs API keys; a missing key skips the summary rather than failing the run), `--compare-recs` checks recommendations vs actual decisions
|
|
119
|
+
- `fpl league-recap` - whole-league recap for a completed GW; `-g N` picks the gameweek (default: last completed), `--draft` when both formats are configured, `--summarise` adds the editorial narrative (needs API keys), and `--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
120
|
- `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
121
|
|
|
112
122
|
**Existing research:**
|
|
@@ -123,7 +133,7 @@ The 0-100 scores are normalised per position against a calibrated ceiling. That
|
|
|
123
133
|
- **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
134
|
- **`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
135
|
- **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
|
|
136
|
+
- **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 it names `quality_score` as the prior-informed estimate - `ep_next` is not one. Before ~GW6 `ep_next` tracks `form` almost exactly, until FPL's fixture factor moves off 1.0, so it is not a second opinion: read an `ep_next` sort as a form sort with doubtful players scaled down by chance of playing. Reach for `-s quality_score` when you want an ordering that carries last season's pedigree
|
|
127
137
|
- **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
138
|
|
|
129
139
|
## Pushback Style
|
|
@@ -60,6 +60,10 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
|
|
|
60
60
|
### Exception Handling
|
|
61
61
|
- Never use bare `except Exception`. Use specific types for narrow try blocks; use `# noqa: BLE001 — <justification>` for intentional broad handlers (agent top-level, scraper resilience, graceful degradation)
|
|
62
62
|
|
|
63
|
+
### Typing & Imports
|
|
64
|
+
- Every function signature in `fpl_cli/` and `scripts/` is fully annotated, arguments and return — ruff's `ANN` rules enforce it (`tests/` are exempt: thousands of `def test_*` with no type-safety payoff). `ANN401` is off, so a bare `Any` is allowed where it is the honest type (JSON/YAML values, `*args`/`**kwargs` pass-throughs, duck-typed player objects) — prefer the real type whenever one exists. A name a module needs only for an annotation goes under `if TYPE_CHECKING:` (with `from __future__ import annotations` at the top), which is also how a helper names the `FPLClient` it is handed without importing it at runtime
|
|
65
|
+
- Imports are absolute (`from fpl_cli.api.providers._models import ...`), never relative — ruff `TID252` with `ban-relative-imports = "all"` enforces it; the default setting only bans `from ..parent`, which is how sibling imports survived in `api/providers/`
|
|
66
|
+
|
|
63
67
|
### Timestamps
|
|
64
68
|
- User-facing timestamps (deadlines, kickoffs, `generated_at` stamps) must route through `fpl_cli/utils/time.py` (`format_deadline`, `format_kickoff`, `format_generated_at`, `now_uk`). Never `strftime` on a naive `datetime.now()` or print raw API ISO strings to users. Tool is UK-locked: display is always `Europe/London` with GMT/BST label. Internal datetime math stays UTC.
|
|
65
69
|
|
|
@@ -67,6 +71,10 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
|
|
|
67
71
|
- Generated reports are season-partitioned: `<output dir>/<season>/gw{N}-review.md`, `<research dir>/ai-scout-reports/<season>/gw{N}-scout-preview.md`. Filenames carry a gameweek but no season, so a flat directory lets a new season's GW21 report overwrite the previous season's. Resolve destinations with `resolve_output_dir(settings, output)` (`cli/_context.py`), which partitions the configured dir and an explicit `--output` alike; partition anything else with `season_partition()` (`fpl_cli/season.py`). `ReportAgent` writes to `output_dir` verbatim — never add a second season segment there
|
|
68
72
|
- Skills writing alongside these reports take the label from `fpl status --format json` (`metadata.season`), never hardcoded
|
|
69
73
|
|
|
74
|
+
### Dependencies
|
|
75
|
+
- No lockfile: every install, CI included, resolves fresh from `pyproject.toml`, so CI tests what `pip install fplkit` gives users. Never add a `requirements.lock` / `uv.lock` — `tests/test_dependencies.py` fails if one appears (#81)
|
|
76
|
+
- An upper bound is deliberate, not default: add one only when the dependency's next major is a real prospect and the code leans on surface it would change, and add its row to the table in CONTRIBUTING.md (`## Dependencies`), which the same test holds to `pyproject.toml`. Capped today: `httpx<1`, `pydantic<3`, `click<9`, `platformdirs<5`, `pulp<4`; everything else, dev tools included, stays unbounded and the weekly CI cron catches an upstream break
|
|
77
|
+
|
|
70
78
|
### Commits & Changelog
|
|
71
79
|
- Commit subjects and PR titles follow conventional commits. `feat:`/`fix:`/`refactor:`/`perf:` become release-notes lines via git-cliff (`cliff.toml`); `chore:`/`docs:`/`ci:`/`test:`/`style:` and merge commits are skipped
|
|
72
80
|
- A changelog-visible subject must read as a standalone user-facing change — the release pipeline publishes it verbatim in CHANGELOG.md and the GitHub release
|
|
@@ -91,8 +99,9 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
|
|
|
91
99
|
- README must stay in sync: any CLI command added/changed/removed requires updating the relevant job section under `## Usage` (After the Gameweek, Checking Your Setup, Scouting Players, Before the Deadline, Strategic Planning, Season Preview Intel, Custom Analysis, JSON Output) and, if the command has detailed flags or formulas, `docs/command-reference.md`
|
|
92
100
|
- Architecture doc must stay in sync: adding a new agent, service, API client, or CLI command requires updating `docs/architecture.md`
|
|
93
101
|
- TOOLS.md must stay in sync: adding, removing, or changing a CLI command, analysis agent, or skill requires updating `.agents/TOOLS.md`
|
|
102
|
+
- Output style must stay in sync: adding, removing, or renaming a CLI command requires updating the Data Grounding section of `.claude/output-styles/fpl-mate.md`, the fast-path index FPL Mate reaches for - `tests/test_output_style_sync.py` fails when a registered command is never named there, or when the file names one that no longer exists
|
|
94
103
|
- AGENTS.md must stay in sync: any change to project instructions in CLAUDE.md requires the same change in AGENTS.md
|
|
95
|
-
- CONTRIBUTING.md must stay in sync: changes to commit/PR conventions, CI checks, or the release pipeline require the same change in CONTRIBUTING.md (the human-facing copy)
|
|
104
|
+
- CONTRIBUTING.md must stay in sync: changes to commit/PR conventions, CI checks, the dependency policy, or the release pipeline require the same change in CONTRIBUTING.md (the human-facing copy)
|
|
96
105
|
- CLI changes require corresponding unit tests
|
|
97
106
|
- Changing a function's return format: update existing tests to match and confirm pytest passes
|
|
98
107
|
- Tests: `pytest-asyncio` with `asyncio_mode = "auto"`, factories in `tests/conftest.py` — `make_player()`, `make_team()`, `make_fixture()`, `make_history_row()`, `make_agent()`, and the draft equivalents (`make_draft_player()`, `make_draft_team()`, `make_draft_league_entry()`, `make_draft_standing()`). `make_agent()` is the async-context-manager stand-in every CLI test needs when it patches an agent — use it rather than rebuilding the `__aenter__`/`__aexit__`/`run()` mock locally, which is how six copies of the same shape accumulated
|
|
@@ -1,6 +1,71 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
+
## [2.5.0] - 2026-09-07
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- stop the fpl-mate output style calling ep_next prior-informed (#261)
|
|
9
|
+
- restore xG enrichment for players with an apostrophe in their name (#268)
|
|
10
|
+
- keep coverage readable when one ledger file is damaged (#271)
|
|
11
|
+
- stop the review scrubber deleting sentences naming full names (#270)
|
|
12
|
+
- recap the last completed gameweek once the next one has kicked off (#274)
|
|
13
|
+
- stop fpl xg table output crashing when custom analysis is off (#275)
|
|
14
|
+
- cap httpx, pydantic and click below their next majors (#278)
|
|
15
|
+
- stop a truncated LLM summary being saved without a warning (#276)
|
|
16
|
+
- stop a draft replay erasing the league positions it recorded (#280)
|
|
17
|
+
- validate FPL_CLI_DATA_DIR and FPL_CLI_CACHE_DIR eagerly like FPL_CLI_CONFIG_DIR (#281)
|
|
18
|
+
- raise the click floor to 8.2 so the test suite can capture stderr (#283)
|
|
19
|
+
- collapse a league-wide captain tie into one recap line (#282)
|
|
20
|
+
- fill the blank Source column in a review's Standout Performers (#284)
|
|
21
|
+
- exit 1 with the real reason when review or recap cannot run (#285)
|
|
22
|
+
- point doctor's draft ID fix hints at `fpl init` (#288)
|
|
23
|
+
- keep the current table's points out of a replayed gameweek's recap (#293)
|
|
24
|
+
- report a failure the same way whatever the output format (#292)
|
|
25
|
+
- keep league positions when one manager's replay data is partial (#295)
|
|
26
|
+
- stop a replay renaming the player a stored red-card fine names (#290)
|
|
27
|
+
- ground the review's "Next Week" advice in next gameweek's fixtures (#291)
|
|
28
|
+
- rebuild the ratings prior when a football-data key appears (#289)
|
|
29
|
+
- record the club a player played for in a replayed gameweek (#296)
|
|
30
|
+
- keep `review --summarise` running when an LLM key is missing (#297)
|
|
31
|
+
- derive season labels from GW1's deadline instead of the clock (#299)
|
|
32
|
+
- correct award move direction and free-agent mislabelling (#302)
|
|
33
|
+
|
|
34
|
+
### Features
|
|
35
|
+
|
|
36
|
+
- flag stale quality-score calibration in `fpl doctor` (#298)
|
|
37
|
+
- hand the league-recap editorial every manager's transfers (#300)
|
|
38
|
+
- give the draft recap editorial every waiver and free-agent move (#303)
|
|
39
|
+
- give the season-opener recap each manager's prior FPL seasons (#304)
|
|
40
|
+
|
|
41
|
+
## [2.4.1] - 2026-09-05
|
|
42
|
+
|
|
43
|
+
### Bug Fixes
|
|
44
|
+
|
|
45
|
+
- polish output inconsistencies found in the v2.4 test-plan run (#239)
|
|
46
|
+
- keep agent progress off stdout so JSON output stays parseable (#240)
|
|
47
|
+
- stop league-recap calling a manager's first fine their second (#241)
|
|
48
|
+
- write team_finances.json in stable diff order with real UTF-8 (#244)
|
|
49
|
+
- repair squad-builder and update-gw-prep's normalisation call (#245)
|
|
50
|
+
- restore xG data for a signing yet to play for their new club (#242)
|
|
51
|
+
- scale the `fpl xg` minutes floor to the gameweeks played (#243)
|
|
52
|
+
- show returnee week-over-week changes on every run of a gameweek (#246)
|
|
53
|
+
- stop review bench comparisons mixing combined and per-player (#249)
|
|
54
|
+
- stop fpl returnees flagging a healthy Understat team map (#248)
|
|
55
|
+
- say which league history file is unreadable, and how to repair it (#250)
|
|
56
|
+
- stop GW review dropping the gameweek's top scorer (#252)
|
|
57
|
+
- report every table-mode failure on stderr instead of stdout (#251)
|
|
58
|
+
- raise when include_match_data lacks include_players (#254)
|
|
59
|
+
- stop early-season advice offering ep_next as a second opinion (#253)
|
|
60
|
+
- centre GW review's Nearby Rivals window on the user (#257)
|
|
61
|
+
- report a malformed fines rule instead of crashing with a traceback (#258)
|
|
62
|
+
- report missing or wrong classic IDs instead of masking them (#259)
|
|
63
|
+
- rate a returning promoted club from its Championship record (#256)
|
|
64
|
+
|
|
65
|
+
### Refactoring
|
|
66
|
+
|
|
67
|
+
- read every command's settings off the Click context (#247)
|
|
68
|
+
|
|
4
69
|
## [2.4.0] - 2026-09-04
|
|
5
70
|
|
|
6
71
|
### Bug Fixes
|
|
@@ -60,6 +60,10 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
|
|
|
60
60
|
### Exception Handling
|
|
61
61
|
- Never use bare `except Exception`. Use specific types for narrow try blocks; use `# noqa: BLE001 — <justification>` for intentional broad handlers (agent top-level, scraper resilience, graceful degradation)
|
|
62
62
|
|
|
63
|
+
### Typing & Imports
|
|
64
|
+
- Every function signature in `fpl_cli/` and `scripts/` is fully annotated, arguments and return — ruff's `ANN` rules enforce it (`tests/` are exempt: thousands of `def test_*` with no type-safety payoff). `ANN401` is off, so a bare `Any` is allowed where it is the honest type (JSON/YAML values, `*args`/`**kwargs` pass-throughs, duck-typed player objects) — prefer the real type whenever one exists. A name a module needs only for an annotation goes under `if TYPE_CHECKING:` (with `from __future__ import annotations` at the top), which is also how a helper names the `FPLClient` it is handed without importing it at runtime
|
|
65
|
+
- Imports are absolute (`from fpl_cli.api.providers._models import ...`), never relative — ruff `TID252` with `ban-relative-imports = "all"` enforces it; the default setting only bans `from ..parent`, which is how sibling imports survived in `api/providers/`
|
|
66
|
+
|
|
63
67
|
### Timestamps
|
|
64
68
|
- User-facing timestamps (deadlines, kickoffs, `generated_at` stamps) must route through `fpl_cli/utils/time.py` (`format_deadline`, `format_kickoff`, `format_generated_at`, `now_uk`). Never `strftime` on a naive `datetime.now()` or print raw API ISO strings to users. Tool is UK-locked: display is always `Europe/London` with GMT/BST label. Internal datetime math stays UTC.
|
|
65
69
|
|
|
@@ -67,6 +71,10 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
|
|
|
67
71
|
- Generated reports are season-partitioned: `<output dir>/<season>/gw{N}-review.md`, `<research dir>/ai-scout-reports/<season>/gw{N}-scout-preview.md`. Filenames carry a gameweek but no season, so a flat directory lets a new season's GW21 report overwrite the previous season's. Resolve destinations with `resolve_output_dir(settings, output)` (`cli/_context.py`), which partitions the configured dir and an explicit `--output` alike; partition anything else with `season_partition()` (`fpl_cli/season.py`). `ReportAgent` writes to `output_dir` verbatim — never add a second season segment there
|
|
68
72
|
- Skills writing alongside these reports take the label from `fpl status --format json` (`metadata.season`), never hardcoded
|
|
69
73
|
|
|
74
|
+
### Dependencies
|
|
75
|
+
- No lockfile: every install, CI included, resolves fresh from `pyproject.toml`, so CI tests what `pip install fplkit` gives users. Never add a `requirements.lock` / `uv.lock` — `tests/test_dependencies.py` fails if one appears (#81)
|
|
76
|
+
- An upper bound is deliberate, not default: add one only when the dependency's next major is a real prospect and the code leans on surface it would change, and add its row to the table in CONTRIBUTING.md (`## Dependencies`), which the same test holds to `pyproject.toml`. Capped today: `httpx<1`, `pydantic<3`, `click<9`, `platformdirs<5`, `pulp<4`; everything else, dev tools included, stays unbounded and the weekly CI cron catches an upstream break
|
|
77
|
+
|
|
70
78
|
### Commits & Changelog
|
|
71
79
|
- Commit subjects and PR titles follow conventional commits. `feat:`/`fix:`/`refactor:`/`perf:` become release-notes lines via git-cliff (`cliff.toml`); `chore:`/`docs:`/`ci:`/`test:`/`style:` and merge commits are skipped
|
|
72
80
|
- A changelog-visible subject must read as a standalone user-facing change — the release pipeline publishes it verbatim in CHANGELOG.md and the GitHub release
|
|
@@ -91,8 +99,9 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
|
|
|
91
99
|
- README must stay in sync: any CLI command added/changed/removed requires updating the relevant job section under `## Usage` (After the Gameweek, Checking Your Setup, Scouting Players, Before the Deadline, Strategic Planning, Season Preview Intel, Custom Analysis, JSON Output) and, if the command has detailed flags or formulas, `docs/command-reference.md`
|
|
92
100
|
- Architecture doc must stay in sync: adding a new agent, service, API client, or CLI command requires updating `docs/architecture.md`
|
|
93
101
|
- TOOLS.md must stay in sync: adding, removing, or changing a CLI command, analysis agent, or skill requires updating `.agents/TOOLS.md`
|
|
102
|
+
- Output style must stay in sync: adding, removing, or renaming a CLI command requires updating the Data Grounding section of `.claude/output-styles/fpl-mate.md`, the fast-path index FPL Mate reaches for - `tests/test_output_style_sync.py` fails when a registered command is never named there, or when the file names one that no longer exists
|
|
94
103
|
- AGENTS.md must stay in sync: any change to project instructions in CLAUDE.md requires the same change in AGENTS.md
|
|
95
|
-
- CONTRIBUTING.md must stay in sync: changes to commit/PR conventions, CI checks, or the release pipeline require the same change in CONTRIBUTING.md (the human-facing copy)
|
|
104
|
+
- CONTRIBUTING.md must stay in sync: changes to commit/PR conventions, CI checks, the dependency policy, or the release pipeline require the same change in CONTRIBUTING.md (the human-facing copy)
|
|
96
105
|
- CLI changes require corresponding unit tests
|
|
97
106
|
- Changing a function's return format: update existing tests to match and confirm pytest passes
|
|
98
107
|
- Tests: `pytest-asyncio` with `asyncio_mode = "auto"`, factories in `tests/conftest.py` — `make_player()`, `make_team()`, `make_fixture()`, `make_history_row()`, `make_agent()`, and the draft equivalents (`make_draft_player()`, `make_draft_team()`, `make_draft_league_entry()`, `make_draft_standing()`). `make_agent()` is the async-context-manager stand-in every CLI test needs when it patches an agent — use it rather than rebuilding the `__aenter__`/`__aexit__`/`run()` mock locally, which is how six copies of the same shape accumulated
|
|
@@ -17,6 +17,12 @@ $ pytest # Tests
|
|
|
17
17
|
$ hatch build # Package build
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
Two of the ruff rules are conventions rather than bug-catchers, so they are
|
|
21
|
+
worth knowing before a first PR: every function signature in `fpl_cli/` and
|
|
22
|
+
`scripts/` carries full type annotations (`ANN`; `tests/` are exempt, and a
|
|
23
|
+
bare `Any` is allowed where it is the honest type), and imports are absolute,
|
|
24
|
+
never relative (`TID252`).
|
|
25
|
+
|
|
20
26
|
The test suite is hermetic: `pytest-socket` blocks all network access
|
|
21
27
|
(`--disable-socket --allow-unix-socket` in `addopts`), so a test that
|
|
22
28
|
reaches a live endpoint fails immediately rather than passing against
|
|
@@ -36,6 +42,44 @@ unreachability. It does not run on PRs.
|
|
|
36
42
|
A separate `PR Title` check (`.github/workflows/pr-title.yml`) fails the
|
|
37
43
|
PR when the title doesn't follow the conventional-commit format below.
|
|
38
44
|
|
|
45
|
+
## Dependencies
|
|
46
|
+
|
|
47
|
+
There is no lockfile. `pip install -e ".[dev]"` — locally and in CI —
|
|
48
|
+
resolves fresh from the ranges in `pyproject.toml`, which is the same
|
|
49
|
+
resolution `pip install fplkit` gives a user: CI tests what users get. A
|
|
50
|
+
lock could not change that for anyone installing from PyPI, and it would
|
|
51
|
+
keep CI green on the pinned version while a fresh install broke — the
|
|
52
|
+
class of failure #80 hit with PuLP. The `requirements.lock` that used to
|
|
53
|
+
sit at the repo root was that in miniature: three runtime dependencies
|
|
54
|
+
short, generated for macOS and Python 3.12 so a hash-checked install on
|
|
55
|
+
the Linux/3.11 CI runner refused it, and read by nothing (#81). Don't add
|
|
56
|
+
another. The weekly CI cron re-resolves from scratch, so an upstream
|
|
57
|
+
release that breaks main is caught within a week even with no commit
|
|
58
|
+
behind it.
|
|
59
|
+
|
|
60
|
+
An upper bound is deliberate, not default. A runtime dependency gets one
|
|
61
|
+
only when its next major is a real prospect *and* the code leans on
|
|
62
|
+
surface that major is expected to change:
|
|
63
|
+
|
|
64
|
+
| Bound | Why |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `httpx<1` | every API client, and `respx` in the tests couples to its transport internals; 0.28 already removed deprecated arguments and 1.0 is the announced cleanup |
|
|
67
|
+
| `pydantic<3` | every model is a v2 `BaseModel` with `ConfigDict`, aliases and `computed_field`; v3 is where the v2 deprecations are removed |
|
|
68
|
+
| `click<9` | the whole CLI, and `CliRunner` in every CLI test; 8.2 already changed how stderr is captured, and 9 removes the 8.x deprecations |
|
|
69
|
+
| `platformdirs<5` | decides where user config, data and cache live (`paths.py`); a major that moves a location strands existing users' files |
|
|
70
|
+
| `pulp<4` | 4.0 removes both solver calls `squad_allocator.py` makes (#80, #82) |
|
|
71
|
+
|
|
72
|
+
Everything else stays unbounded. `rich` and `keyring` bump majors
|
|
73
|
+
routinely without breaking anything here — a bound there is a PR every
|
|
74
|
+
few months for no protection — and the rest have no major on the
|
|
75
|
+
horizon. When a release does break main, fix the code or add a bound in
|
|
76
|
+
the PR that turns CI green, and add the row above. Dev tools are never
|
|
77
|
+
capped: CI runs them at latest, and a break there is caught and fixed on
|
|
78
|
+
the next PR without reaching a user.
|
|
79
|
+
|
|
80
|
+
`tests/test_dependencies.py` holds this table to `pyproject.toml` in
|
|
81
|
+
both directions and fails if a lockfile reappears.
|
|
82
|
+
|
|
39
83
|
## Commit subjects & PR titles
|
|
40
84
|
|
|
41
85
|
This repo is public. Keep real manager and league names, entry/league IDs, and FPL account details out of commit subjects, PR titles and bodies, issues, and the changelog — use placeholders when quoting a generated report. Footballer and club names are public data and are fine.
|