fplkit 2.2.0__tar.gz → 2.3.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.2.0 → fplkit-2.3.0}/.agents/TOOLS.md +13 -8
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/squad-builder/SKILL.md +1 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/AGENTS.md +1 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/CHANGELOG.md +20 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/CLAUDE.md +1 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/PKG-INFO +31 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/README.md +30 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/docs/architecture.md +19 -14
- {fplkit-2.2.0 → fplkit-2.3.0}/docs/command-reference.md +200 -19
- {fplkit-2.2.0 → fplkit-2.3.0}/docs/custom-analysis.md +11 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/_version.py +2 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/analysis/stats.py +23 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/analysis/transfer_eval.py +9 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/core_insights.py +165 -120
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/understat.py +18 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/__init__.py +2 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_fines.py +58 -6
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_helpers.py +44 -8
- fplkit-2.3.0/fpl_cli/cli/_json.py +186 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_league_recap_data.py +189 -39
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_league_recap_history.py +245 -24
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_league_recap_types.py +43 -6
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_plan_grid.py +26 -18
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_review_classic.py +9 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_review_draft.py +8 -5
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_review_summarisation.py +18 -4
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/allocate.py +26 -7
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/chips.py +9 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/differentials.py +9 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/doctor.py +100 -26
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/doctor_providers.py +310 -79
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/fdr.py +75 -22
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/fixtures.py +13 -3
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/intel.py +12 -15
- fplkit-2.3.0/fpl_cli/cli/league_fines.py +209 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/league_recap.py +243 -14
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/player.py +17 -5
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/price_history.py +3 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/ratings.py +36 -3
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/sell_prices.py +97 -48
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/squad.py +21 -18
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/stats.py +66 -14
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/status.py +3 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/transfer_eval.py +9 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/models/league_history.py +102 -14
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/prompts/league_recap.py +146 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/prompts/review.py +3 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/league_history.py +13 -3
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/league_history_counters.py +291 -29
- fplkit-2.3.0/fpl_cli/services/league_history_fines.py +552 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/league_history_notes.py +261 -16
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/returnee_radar.py +11 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/scoring/__init__.py +6 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/scoring/constants.py +83 -5
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/scoring/display.py +9 -3
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/scoring/evaluation.py +26 -12
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/scoring/ownership.py +6 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/scoring/value_quality.py +3 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/team_ratings.py +184 -54
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/team_ratings_prior.py +15 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/templates/gw_league_recap.md.j2 +29 -1
- fplkit-2.3.0/fpl_cli/utils/gameweek.py +32 -0
- fplkit-2.3.0/scripts/backtest_early_season_prior_blend.py +438 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/scripts/calibrate_promoted_prior.py +5 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/scripts/calibrate_quality_ceilings.py +12 -4
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_api_core_insights.py +20 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_api_understat.py +106 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_chips.py +5 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_doctor.py +97 -4
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_doctor_providers.py +254 -11
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_fdr.py +77 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_fixtures.py +3 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_intel.py +4 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_json.py +55 -2
- fplkit-2.3.0/tests/test_cli_json_contract.py +319 -0
- fplkit-2.3.0/tests/test_cli_league_fines.py +275 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_league_recap.py +758 -36
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_plan_grid.py +2 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_player.py +8 -4
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_returnees.py +3 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_review.py +88 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_sell_prices.py +83 -7
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_stats.py +203 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_fines.py +74 -2
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_league_history_counters.py +425 -19
- fplkit-2.3.0/tests/test_league_history_fines.py +463 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_league_history_notes.py +496 -5
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_league_history_store.py +119 -6
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_league_recap.py +452 -36
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_preseason.py +3 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_returnee_radar.py +13 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_review.py +117 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_scoring.py +202 -1
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_team_ratings.py +711 -0
- fplkit-2.3.0/tests/test_utils_gameweek.py +38 -0
- fplkit-2.2.0/fpl_cli/cli/_json.py +0 -92
- fplkit-2.2.0/fpl_cli/utils/gameweek.py +0 -10
- fplkit-2.2.0/tests/test_utils_gameweek.py +0 -18
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/README.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/create-pr/SKILL.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/gw-prep/SKILL.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/gw-prep/references/output-template.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/gw-prep/references/rules.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/gw-prep/scripts/_bootstrap.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/gw-prep/scripts/bench_order.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/gw-prep/scripts/extract_classic_squad.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/gw-prep/scripts/starting_xi.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/gw-prep/scripts/transfer_eval.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/gw-prep/scripts/validate_draft_waivers.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/preview-ingest/SKILL.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/release/SKILL.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/release-notes/SKILL.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/squad-builder/references/output-template.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/squad-builder/references/rules.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.agents/skills/update-gw-prep/SKILL.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.claude/output-styles/fpl-mate.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.claude/settings.json +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.env.example +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.github/workflows/ci.yml +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.github/workflows/pr-title.yml +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.github/workflows/release.yml +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/.gitignore +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/CONTRIBUTING.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/LICENSE +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/cliff.toml +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/config/team_ratings_overrides.yaml +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/docs/fpl-rules.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/docs/images/fpl-player-demo-v1-0.png +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/action/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/action/waiver.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/analysis/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/analysis/bench_order.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/analysis/captain.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/analysis/squad_analyzer.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/analysis/starting_xi.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/base.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/common.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/data/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/data/fixture.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/data/price.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/data/scout.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/orchestration/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/agents/orchestration/report.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/contract.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/dataset_fetcher.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/football_data.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/fpl.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/fpl_draft.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/historical.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/historical_types.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/providers/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/providers/_models.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/providers/anthropic.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/providers/openai_compat.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/providers/perplexity.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/api/vaastav.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_banner.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_context.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_fines_config.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/_review_analysis.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/captain.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/credentials.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/history.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/init.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/league.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/preview.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/price_changes.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/returnees.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/review.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/targets.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/waivers.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/cli/xg.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/config/defaults.yaml +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/config/fixture_predictions.yaml +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/config/previews/EXAMPLE.yaml +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/config/team_managers.yaml +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/constants.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/models/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/models/chip_plan.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/models/fixture.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/models/player.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/models/team.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/models/types.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/parsers/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/parsers/recommendations.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/paths.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/prompts/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/prompts/returnees.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/prompts/scout.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/scraper/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/scraper/fpl_prices.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/season.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/fixture_predictions.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/matchup.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/player_prior.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/scoring/data_prep.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/scoring/shrinkage.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/scoring/signals.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/scoring/single_gw.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/season_previews.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/squad_allocator.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/services/team_form.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/templates/gw_preview.md.j2 +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/templates/gw_review.md.j2 +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/utils/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/utils/files.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/utils/markdown.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/utils/teams.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/utils/text.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/fpl_cli/utils/time.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/pyproject.toml +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/pyrightconfig.json +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/requirements.lock +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/__init__.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/conftest.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/fixtures/classic_squad_fixture.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/fixtures/recommendations_fixture.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/fixtures/validate_draft_waivers/recs_clean.md +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/fixtures/validate_draft_waivers/squad_grid.json +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/fixtures/validate_draft_waivers/waivers.json +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/fixtures/waiver_ranked_golden.json +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_agents_action.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_agents_analysis.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_agents_analysis_squad.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_agents_base.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_agents_data.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_agents_data_price.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_agents_orchestration.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_allocate.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_api_client.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_api_dataset_fetcher.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_api_draft.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_api_football_data.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_api_historical.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_api_vaastav.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_bench_order_script.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_classic_squad_extraction.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_allocate.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_banner.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_captain.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_credentials.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_custom_analysis.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_differentials.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_fdr_blanks.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_format.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_format_suppression.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_history.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_init.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_league.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_preview.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_price_changes.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_price_history.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_settings_warning.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_squad.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_status.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_targets.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_transfer_eval.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_waivers.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_warnings_stderr.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_cli_xg.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_common.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_common_scoring.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_data_prep_harmonisation.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_doc_consistency.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_fines_config.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_fixture_predictions.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_fpl_prices.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_init.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_init_fines.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_matchup_service.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_models.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_paths.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_player_prior.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_player_resolution.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_providers.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_recommendations_parser.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_report_agent.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_report_paths.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_rolling_pts_per_m.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_scraper.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_season.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_season_awareness.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_season_previews.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_sell_prices.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_squad_allocator.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_starting_xi_agent.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_stats.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_team_ratings_prior.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_transfer_eval_agent.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_utils_markdown.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_utils_text.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_utils_time.py +0 -0
- {fplkit-2.2.0 → fplkit-2.3.0}/tests/test_validate_draft_waivers.py +0 -0
|
@@ -18,7 +18,11 @@ All `--format json` commands emit a standard envelope via `emit_json()` in `fpl_
|
|
|
18
18
|
- `metadata` - command-specific context (typically includes `gameweek`)
|
|
19
19
|
- `data` - the payload (list or dict, varies per command)
|
|
20
20
|
|
|
21
|
-
Errors use `emit_json_error()` with `{"command", "error"}` shape.
|
|
21
|
+
Errors use `emit_json_error()` with `{"command", "error"}` shape. **Both envelopes go to
|
|
22
|
+
stdout** and exit 1 on failure, so parse stdout either way and read the exit code to know
|
|
23
|
+
which envelope you got. Warnings and progress go to stderr; the failure message itself is
|
|
24
|
+
in `error`, not on stderr. Full contract:
|
|
25
|
+
[Command Reference](../docs/command-reference.md#json-output).
|
|
22
26
|
|
|
23
27
|
## CLI Commands
|
|
24
28
|
|
|
@@ -26,29 +30,30 @@ Errors use `emit_json_error()` with `{"command", "error"}` shape.
|
|
|
26
30
|
|---------|-------------|------|--------|-------------|---------|
|
|
27
31
|
| `init` | Set up fpl-cli with your FPL IDs and optional features | No | General | No | direct-api |
|
|
28
32
|
| `status` | Show FPL gameweek status and upcoming deadlines. JSON `metadata` carries `gameweek`, `format` and `season` (hyphenated label, e.g. `2026-27`) - `season` is the directory segment skills must use when writing alongside fpl-cli's own reports | Yes | General | No | direct-api |
|
|
29
|
-
| `doctor` | Check the setup for dead IDs, stale data, and config problems: resolves each configured ID against the live API (reporting the team/league name back so a wrong-but-valid ID is visible), verifies per-team data files (team ratings, managers, season preview intel, finances, player prior) describe the current season's clubs, and reports which config/data/cache directories are in use. With `--providers`, probes the external data sources instead: live shape-and-volume checks against the FPL and Draft APIs, the vaastav and Core-Insights datasets, Understat, and football-data.org, including that every club resolves across sources (the drift that otherwise surfaces as plausible but wrong output). Distinguishes broken (needs a fix today) from stale (self-corrects). Exits non-zero when something is broken | Yes | General | No | direct-api |
|
|
33
|
+
| `doctor` | Check the setup for dead IDs, stale data, and config problems: resolves each configured ID against the live API (reporting the team/league name back so a wrong-but-valid ID is visible), verifies per-team data files (team ratings, managers, season preview intel, finances, player prior) describe the current season's clubs, and reports which config/data/cache directories are in use. With `--providers`, probes the external data sources instead: live shape-and-volume checks against the FPL and Draft APIs, the vaastav and Core-Insights datasets, Understat, and football-data.org, including that every club resolves across sources and that the Core-Insights per-gameweek files parse to records through the parsers the scoring commands use (the drift that otherwise surfaces as plausible but wrong output). Distinguishes broken (needs a fix today) from stale (self-corrects). Exits non-zero when something is broken | Yes | General | No | direct-api |
|
|
30
34
|
| `fixtures` | Show fixtures for a gameweek | Yes | General | No | direct-api |
|
|
31
35
|
| `player` | Look up a player's stats, xG, ownership and fixture run. JSON includes `ep_next` (predicted pts next GW) and `ep_this` (current GW), emitting `null` when FPL has no projection; panel shows `xPts` (ep_next) when available, omits the segment when FPL has no projection. `--detail` (`-d`) shows GW-by-GW performance and, for FWD/MID with sufficient history, xGI sustainability (per-match GI-xGI divergence -> form modifier). With `custom_analysis`: JSON adds `info.adjusted_npxg_per_90` (fixture-adjusted) and `info.raw_npxg_per_90` (Understat season avg); panel shows `adj. npxG/90: X.XXX (raw: Y.YYY)` | Yes | General | No | direct-api |
|
|
32
|
-
| `stats` | List players with filtering and sorting. `--value` adds quality/value per £m columns; `--window N` sets rolling lookback (3-10) for `rolling_pts_per_m`. Sortable by `ep_next`/`ep_this` (FPL predicted points; `ep_this` is only meaningful before the gameweek's first kickoff — FPL rolls it forward to `ep_next` as each player's match finishes); both in JSON output (emit `null` when FPL has no projection; table renders `—`). Players with no projection sort to the bottom in either direction | Yes | General | No | direct-api |
|
|
36
|
+
| `stats` | List players with filtering and sorting. `--value` adds quality/value per £m columns; `--window N` sets rolling lookback (3-10) for `rolling_pts_per_m`. Before GW6, `--value` warns that quality scores are small-sample dominated (stderr notice; JSON `metadata.warnings` code `early_season_small_sample`) — GK ceilings scale with the calendar-possible sample, reaching full scale at GW6. Sortable by `ep_next`/`ep_this` (FPL predicted points; `ep_this` is only meaningful before the gameweek's first kickoff — FPL rolls it forward to `ep_next` as each player's match finishes); both in JSON output (emit `null` when FPL has no projection; table renders `—`). Players with no projection sort to the bottom in either direction | Yes | General | No | direct-api |
|
|
33
37
|
| `history` | Show historical player performance across seasons | Yes | General | No | direct-api |
|
|
34
38
|
| `league` | Show live league standings for Classic and Draft leagues | No | General | No | direct-api |
|
|
35
|
-
| `fdr` | Analyse fixture difficulty - easy runs, blanks, doubles | Yes | General | No | via-agent |
|
|
39
|
+
| `fdr` | Analyse fixture difficulty - easy runs, blanks, doubles. `--blanks` mixes confirmed fixtures with predictions; when those predictions are stale, table mode warns on stderr and JSON adds a `fixture_predictions_stale` entry to `metadata.warnings` | Yes | General | No | via-agent |
|
|
36
40
|
| `xg` | Analyse underlying stats: xG, xA, overperformers | Yes | General | No | via-agent |
|
|
37
41
|
| `price-changes` | Show price changes and transfer activity | No | General | No | via-agent |
|
|
38
42
|
| `price-history` | Show price trajectory and transfer momentum | Yes | General | No | direct-api |
|
|
39
43
|
| `returnees` | Track injured and suspended players due back soon, so a returnee can be claimed in draft before they are fit or planned around in classic. Reads the FPL availability news, filters to returns inside a window (`--window N`, 1-38) and through a source-aware quality bar, and diffs against the stored watchlist for week-over-week changes. Most flagged players carry no parseable return date, so date-unknown entries are the norm rather than the exception; `--enrich` searches the web for fresher timing on those and on stale-news entries (needs a Perplexity key; skips with a note without one) and shows it beside the FPL news, never over it. `--all` bypasses the quality bar and deliberately does not persist the snapshot. JSON `metadata` carries `window`, `escalation_window`, `stash_upgrade_margin`, `transitions_available`, `quality_bar_available`, `quality_bar_applied` and `enrichment_requested`/`_available`/`_note`/`_count`; `data` is `{entries, departures}`, each entry carrying `quality.basis`/`quality.meets_stash`, `expected_return`/`return_gameweek`/`return_source`, `escalation_eligible`/`escalation_basis`, `transition`, and `enrichment` (null unless enriched) | Yes | General | No | direct-api |
|
|
40
44
|
| `preview` | Run full pre-gameweek analysis and generate report. `--save` writes to `<output dir>/<season>/gw{N}-preview.md`; `--scout` writes to `<research dir>/ai-scout-reports/<season>/gw{N}-scout-preview[-referenced].md` | No | General | No | via-agent |
|
|
41
45
|
| `review` | Review a completed gameweek - squad performance and standings. `--save` writes to `<output dir>/<season>/gw{N}-review.md`; `--compare-recs` reads `gw{N}-recommendations.md` from the same season directory | No | General | No | via-agent |
|
|
42
|
-
| `league-recap` | Recap a completed gameweek - awards, standings, and banter. Saves to `<output dir>/<season>/gw{N}-league-recap[-draft].md`. Records every run into the league history ledger (`<data dir>/league_history/`) as a side effect, backfilling classic gaps at a coarse tier automatically; `--backfill-detail` rebuilds earlier gameweeks in full (one request per manager per gameweek). Coverage gaps and capture warnings go to stderr; the command exits 0 even when the store is unreadable. `--format json` emits one row per manager (the captured ledger row shape) plus `metadata.coverage`, `season_phase`, `notes_pack`, `synthesis_summary`, `warnings` (always present, each entry a stable `code` plus rewritable `message` - codes listed in [Command Reference](../docs/command-reference.md#league-history)) and `first_capture_store_path` | Yes | General | No | via-agent |
|
|
46
|
+
| `league-recap` | Recap a completed gameweek - awards, standings, and banter. Saves to `<output dir>/<season>/gw{N}-league-recap[-draft].md`. Records every run into the league history ledger (`<data dir>/league_history/`) as a side effect, backfilling classic gaps at a coarse tier automatically; `--backfill-detail` rebuilds earlier gameweeks in full (one request per manager per gameweek). Coverage gaps and capture warnings go to stderr; the command exits 0 even when the store is unreadable. `--format json` emits one row per manager (the captured ledger row shape) plus `metadata.coverage`, `season_phase`, `notes_pack`, `synthesis_summary`, `warnings` (always present, each entry a stable `code` plus rewritable `message` - codes listed in [Command Reference](../docs/command-reference.md#league-history)) and `first_capture_store_path` (always present; the partition directory on a first capture, `null` after). Fines are ruled *and recorded* against every captured gameweek, backfilled ones included: the detailed replay rules every configured rule, the coarse tier rules only what cohort points support (`last-place`, `below-threshold`) and records that `red-card` went unruled. Season totals print under `Season Fines` on console and as a `# Season Fines` report section, but only at the two milestone gameweeks (GW19 and the finale) - every other week those surfaces stay a this-week view and `league-fines` answers the season question on demand. The `## Season Fines` prompt section and `metadata.season_fines` are ungated: the editorial gets the running totals every week as optional colour (never a required beat), and a scripted consumer sees no calendar-driven appearance and disappearance. Bottom-half gameweeks and green-arrow droughts no longer surface as streaks at all (their run only drives the season count). Streak conditions also keep reset-surviving season occurrence counts ("their sixth gameweek win of the season"), each surfaced on its own rule (see [Command Reference](../docs/command-reference.md#league-recap) for the per-count table): wins and last places on multiples of 3 plus any second-half first, captain blanks and waiver hauls/backfires on multiples of 5, hits on multiples of 3, weeks on top on multiples of 5, bottom-half gameweeks on multiples of 10 in the second half only, and green-arrow droughts on an unbroken run of 5 or 10 (gameweeks spent leading the league never count towards a drought — the leader could not have climbed). Some counts carry same-week peers alongside the milestone — past a fixed floor for counts that stay small, or within a relative window for counts that climb all season. A firing count reaches the report's `## Season Counts` subsection and the editorial's League History section; at the two milestone gameweeks both surfaces carry every nonzero count as the season set-piece. Every line states its span and any not-judged gameweeks beside the number, and every nonzero count is emitted in `metadata.notes_pack.season_count_entries` regardless. `--summarise` with no usable synthesis key no longer aborts the run: the recap, report and ledger capture all proceed, and a `synthesis_provider_unavailable` warning says the editorial was skipped | Yes | General | No | via-agent |
|
|
47
|
+
| `league-fines` | Show who has been fined this season and how much of it was actually ruled, folded out of the league history ledger. Reads local files only - no network - so it works for any season still on disk (`--season 2025-26`). Nothing is re-ruled: a fine is counted exactly as its gameweek recorded it. Counts, never money (`penalty` is free text; settlement and escalation are not modelled). Every gameweek that could not be ruled is named beneath the table - never captured, unreadable, captured before rulings were recorded, captured with no rules configured, or captured at a fidelity that could not rule a given rule. `--format json` emits one entry per recorded manager, fined or not, each with `counts` (zero included), `total`, `fined_gameweeks`, `ruled_gameweeks`, `unruled_gameweeks`, `first_recorded_gameweek`, `last_recorded_gameweek` and `is_fully_ruled`; `metadata` carries `season`, `fpl_format`, `league_id`, `gameweek`, `start_gameweek`, `rule_types`, `total_fines` and `qualifiers`. Exits 1 with the shared error envelope when no league id is configured for the format | Yes | General | No | direct-api |
|
|
43
48
|
| `captain` | Analyse and rank captain options for next gameweek. JSON candidates include `adjusted_npxg_per_90` and `raw_npxg_per_90` when fixture adjustment is active. Consistency tiebreaker (CV-xGI percentile) phased in GW6-10. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Classic | Yes | via-agent |
|
|
44
49
|
| `differentials` | Find differential picks - high potential, low ownership. Inverted consistency bonus (volatile players score higher, phased in GW6-10). Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Classic | Yes | via-agent |
|
|
45
50
|
| `targets` | Find transfer targets - high performers across all ownership. Consistency bonus (CV-xGI percentile, phased in GW6-10). Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Classic | Yes | via-agent |
|
|
46
51
|
| `transfer-eval` | Compare transfer OUT player against IN candidates. JSON includes `adjusted_npxg_per_90` and `raw_npxg_per_90` per player when fixture adjustment is active. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | General | Yes | via-agent |
|
|
47
52
|
| `allocate` | Select mathematically optimal 15-player squad within budget. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean; they stay in the solver pool | Yes | Classic | Yes | direct-api |
|
|
48
53
|
| `waivers` | Show waiver recommendations for your draft league. JSON `data` carries `top_targets` and `targets_by_position` (ranked claims) plus `pool` — the full unowned roster, one `{id, player_name, position, team_short}` row each, unranked and untruncated, so a consumer can answer "is this player claimable at all" without inferring it from the ranked lists. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Draft | Yes | via-agent |
|
|
49
|
-
| `squad` | Analyse your FPL squad health and fixtures | Yes | General | No | via-agent |
|
|
50
|
-
| `squad grid` | Show squad fixture difficulty grid | Yes | General | No | via-agent |
|
|
51
|
-
| `squad sell-prices` | Show squad sell prices and financial breakdown | Yes | Classic | No | direct-api |
|
|
54
|
+
| `squad` | Analyse your FPL squad health and fixtures. Exits 1 with an `error` envelope when no entry ID is configured for the active format | Yes | General | No | via-agent |
|
|
55
|
+
| `squad grid` | Show squad fixture difficulty grid. Exits 1 with an `error` envelope when no entry ID is configured for the active format, or when the squad fetch fails | Yes | General | No | via-agent |
|
|
56
|
+
| `squad sell-prices` | Show squad sell prices and financial breakdown. Nothing cached and no `--refresh`, a failed scrape, draft format, or a scrape the extraction heuristics distrust each exit 1 with an `error` envelope under `--format json`; table mode still renders a suspect scrape under a `Squad Budget (Suspect)` panel, since a reader can judge it and `fpl allocate --sell-prices` cannot | Yes | Classic | No | direct-api |
|
|
52
57
|
| `chips` | View and plan FPL chip usage | Yes | Classic | No | direct-api |
|
|
53
58
|
| `chips timing` | Recommend chip timing based on blank/double GW exposure | Yes | Classic | No | via-agent |
|
|
54
59
|
| `chips add` | Plan a chip for a gameweek | No | Classic | No | direct-api |
|
|
@@ -309,7 +309,7 @@ Include all of this in the prompt field, populated with position-specific data:
|
|
|
309
309
|
Run `fpl player "{name}" -f -H` **in parallel** for all candidates in your list.
|
|
310
310
|
For season-start modes with no current-season data, also use `fpl history` data passed in context.
|
|
311
311
|
|
|
312
|
-
7. **Scoring:** Score each candidate against the mode-specific criteria from rules. Use `quality_score` and `quality_per_m` when available (null for players without Understat data — don't penalise). **Both fields are elite-within-position: never rank or compare candidates across positions by `quality_score` or `quality_per_m`.** A GK showing 90 is "top of the GK pool", not "better than a MID showing 85" — every position is normalised against its own calibrated ceiling, MID and FWD included. Within a position the numbers are trustworthy
|
|
312
|
+
7. **Scoring:** Score each candidate against the mode-specific criteria from rules. Use `quality_score` and `quality_per_m` when available (null for players without Understat data — don't penalise). **Both fields are elite-within-position: never rank or compare candidates across positions by `quality_score` or `quality_per_m`.** A GK showing 90 is "top of the GK pool", not "better than a MID showing 85" — every position is normalised against its own calibrated ceiling, MID and FWD included. Within a position the numbers are trustworthy **from ~GW6**: elite players in every position read 80+, so a MID at 55 genuinely is mid-tier among MIDs. Only compare within the position you are currently filling. **Before ~GW6 treat quality scores as provisional** — they measure only the opening gameweek(s), so a hot-starting role player can out-read a quiet-starting elite (the JSON carries an `early_season_small_sample` warning when this applies); lean on `ep_next`, price and prior-season pedigree for early wildcards and free hits instead.
|
|
313
313
|
|
|
314
314
|
8. **Return format:** Return a structured ranked list. Per candidate:
|
|
315
315
|
```
|
|
@@ -35,7 +35,7 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
|
|
|
35
35
|
- Analytical commands -> via agent (comment `# Pattern: via-agent`)
|
|
36
36
|
- Help text: describe what the user sees, never reference internal components ("agent", "client")
|
|
37
37
|
- Inner async function: always name `_run`
|
|
38
|
-
- `--format json` commands emit the shared envelope via `emit_json()` / `emit_json_error()` (`cli/_json.py`), never a bare `json.dumps`
|
|
38
|
+
- `--format json` commands emit the shared envelope via `emit_json()` / `emit_json_error()` (`cli/_json.py`), never a bare `json.dumps`. Both envelopes go to stdout and every human-readable line to stderr — success is `{command, metadata, data}` and exit 0, failure is `{command, error}` and exit 1, so a consumer parses one stream either way
|
|
39
39
|
- **Format awareness:** New commands classified in `CLASSIC_ONLY`/`DRAFT_ONLY` frozensets in `_context.py` (omit for General). Shared commands use `@click.pass_context` and `fmt = get_format(ctx)` to gate irrelevant sections (see `league.py` for pattern).
|
|
40
40
|
|
|
41
41
|
### Agent Patterns
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
+
## [2.2.0] - 2026-08-25
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- stop early-season fixture difficulty falling back to a flat 4.0 or one gameweek (#109)
|
|
9
|
+
- repair stale team ratings and stop over-weighting last season (#114)
|
|
10
|
+
- stop NOT/NFO mismatch and SHE tla collisions corrupting team priors (#113)
|
|
11
|
+
- stop early-season scoring signals crashing on incomplete history (#120)
|
|
12
|
+
- stop scores printing negative in columns documented as 0-100 (#121)
|
|
13
|
+
- stop select_starting_xi labelling an empty XI as 4-4-2 (#124)
|
|
14
|
+
- stop promoted teams being rated among the Premier League's best (#125)
|
|
15
|
+
- stop unavailable players outranking ones who are actually playing (#126)
|
|
16
|
+
- calibrate MID/FWD/GK quality ceilings so elite players read 85+ (#127)
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
- allow choosing the browser for TLS-inspecting proxies (#108)
|
|
21
|
+
- warn on data-provider drift and add fpl doctor --providers (#123)
|
|
22
|
+
- surface injured players worth claiming before they return (#133)
|
|
23
|
+
|
|
4
24
|
## [2.1.0] - 2026-08-23
|
|
5
25
|
|
|
6
26
|
### Bug Fixes
|
|
@@ -35,7 +35,7 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
|
|
|
35
35
|
- Analytical commands -> via agent (comment `# Pattern: via-agent`)
|
|
36
36
|
- Help text: describe what the user sees, never reference internal components ("agent", "client")
|
|
37
37
|
- Inner async function: always name `_run`
|
|
38
|
-
- `--format json` commands emit the shared envelope via `emit_json()` / `emit_json_error()` (`cli/_json.py`), never a bare `json.dumps`
|
|
38
|
+
- `--format json` commands emit the shared envelope via `emit_json()` / `emit_json_error()` (`cli/_json.py`), never a bare `json.dumps`. Both envelopes go to stdout and every human-readable line to stderr — success is `{command, metadata, data}` and exit 0, failure is `{command, error}` and exit 1, so a consumer parses one stream either way
|
|
39
39
|
- **Format awareness:** New commands classified in `CLASSIC_ONLY`/`DRAFT_ONLY` frozensets in `_context.py` (omit for General). Shared commands use `@click.pass_context` and `fmt = get_format(ctx)` to gate irrelevant sections (see `league.py` for pattern).
|
|
40
40
|
|
|
41
41
|
### Agent Patterns
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fplkit
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: CLI tool for Fantasy Premier League analysis - classic and draft formats
|
|
5
5
|
Project-URL: Homepage, https://github.com/rossgroomio/fpl-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/rossgroomio/fpl-cli
|
|
@@ -95,8 +95,32 @@ $ fpl review --save --summarise # Full review with LLM narrative
|
|
|
95
95
|
$ fpl league # Live league standings
|
|
96
96
|
$ fpl league-recap # Awards, standings movement, and running streaks
|
|
97
97
|
$ fpl league-recap --summarise # Add the LLM editorial for the group chat
|
|
98
|
+
$ fpl league-fines # Who owes what this season, and which weeks were ruled
|
|
98
99
|
```
|
|
99
100
|
|
|
101
|
+
Fines are ruled per gameweek by `league-recap` and recorded against it, so `fpl
|
|
102
|
+
league-fines` reads them straight back off disk — no network, and any season still
|
|
103
|
+
on disk (`--season 2025-26`). Every gameweek that could not be ruled is named
|
|
104
|
+
beneath the table, because a zero there means "not known", not "not fined".
|
|
105
|
+
|
|
106
|
+
The recap prints the season table only at GW19 and at the finale, so the set-piece
|
|
107
|
+
stays a set-piece — though with `--summarise` its editorial knows the running totals
|
|
108
|
+
every week and may work one into a sentence. `fpl league-fines` answers the season
|
|
109
|
+
question any week you want it.
|
|
110
|
+
|
|
111
|
+
Streaks come with season totals: the recap counts every time a condition has occurred
|
|
112
|
+
this season — gameweek wins, last places, weeks on top, captain blanks — across resets,
|
|
113
|
+
so a report or editorial can say "their sixth gameweek win of the season", not just
|
|
114
|
+
"two in a row". Each count has its own rule for when it is worth mentioning, tuned to
|
|
115
|
+
how often it happens: wins and last places on every third, captain blanks and waiver
|
|
116
|
+
hauls on every fifth, bottom-half gameweeks only in the second half of the season and
|
|
117
|
+
only on every tenth, and a green-arrow drought only while unbroken at five or ten
|
|
118
|
+
gameweeks, never counting the weeks a manager spent top of the table with nowhere
|
|
119
|
+
to climb. Some bring along others who added to the same count that week. The full list
|
|
120
|
+
prints twice a season, at GW19 and the finale, alongside the fines table. Unjudged
|
|
121
|
+
gameweeks are always stated beside the number — see
|
|
122
|
+
[Season counts](docs/command-reference.md#league-recap) for the per-count rules.
|
|
123
|
+
|
|
100
124
|
### Checking Your Setup
|
|
101
125
|
|
|
102
126
|
```console
|
|
@@ -106,7 +130,7 @@ $ fpl doctor --providers # Probe the external data sources for shape a
|
|
|
106
130
|
|
|
107
131
|
Every ID in settings.yaml is resolved against the live API and the team/league name reported back, so a dead or recycled ID (which otherwise fails silently) is visible. Per-team data files are checked against the current season's clubs. Exits non-zero when something needs fixing.
|
|
108
132
|
|
|
109
|
-
`--providers` checks the other side of the bargain: that each external data source (FPL and Draft APIs, the historical datasets, Understat, football-data.org) still serves data of the expected shape and size,
|
|
133
|
+
`--providers` checks the other side of the bargain: that each external data source (FPL and Draft APIs, the historical datasets, Understat, football-data.org) still serves data of the expected shape and size, that every club resolves across sources, and that the per-gameweek match files actually parse into records through the same code the scoring commands run — the upstream drift that otherwise surfaces as plausible but wrong output, especially at a season rollover.
|
|
110
134
|
|
|
111
135
|
### Scouting Players
|
|
112
136
|
|
|
@@ -191,8 +215,13 @@ $ fpl stats --format json -p MID -s expected_goal_involvements
|
|
|
191
215
|
$ fpl status --format json
|
|
192
216
|
$ fpl fdr --blanks --format json
|
|
193
217
|
$ fpl league-recap --format json
|
|
218
|
+
$ fpl league-fines --format json
|
|
194
219
|
```
|
|
195
220
|
|
|
221
|
+
Failures come back on the same stream: `{"command", "error"}` on stdout with exit code 1.
|
|
222
|
+
Parse stdout either way, and read the message from `error` rather than from stderr — see
|
|
223
|
+
[JSON Output](docs/command-reference.md#json-output).
|
|
224
|
+
|
|
196
225
|
## Configuration
|
|
197
226
|
|
|
198
227
|
Run `fpl init` to configure interactively. Settings stored in your platform's config directory (override with `FPL_CLI_CONFIG_DIR`, and also home to the optional `previews/` intel files); generated data such as team ratings, chip plans and the league history ledger `league-recap` builds up is stored in your platform's data directory (override with `FPL_CLI_DATA_DIR`). In ephemeral environments (e.g. Claude Code on the web), point both at a persistent workspace. Both overrides must be absolute paths — see [Directories](docs/command-reference.md#directories). Saved reports (`--save`, `--output`) are written to a season subdirectory — `<output_dir>/2026-27/gw21-review.md` — so a new season never overwrites the last one's files; see [Report layout](docs/command-reference.md#report-layout).
|
|
@@ -61,8 +61,32 @@ $ fpl review --save --summarise # Full review with LLM narrative
|
|
|
61
61
|
$ fpl league # Live league standings
|
|
62
62
|
$ fpl league-recap # Awards, standings movement, and running streaks
|
|
63
63
|
$ fpl league-recap --summarise # Add the LLM editorial for the group chat
|
|
64
|
+
$ fpl league-fines # Who owes what this season, and which weeks were ruled
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
Fines are ruled per gameweek by `league-recap` and recorded against it, so `fpl
|
|
68
|
+
league-fines` reads them straight back off disk — no network, and any season still
|
|
69
|
+
on disk (`--season 2025-26`). Every gameweek that could not be ruled is named
|
|
70
|
+
beneath the table, because a zero there means "not known", not "not fined".
|
|
71
|
+
|
|
72
|
+
The recap prints the season table only at GW19 and at the finale, so the set-piece
|
|
73
|
+
stays a set-piece — though with `--summarise` its editorial knows the running totals
|
|
74
|
+
every week and may work one into a sentence. `fpl league-fines` answers the season
|
|
75
|
+
question any week you want it.
|
|
76
|
+
|
|
77
|
+
Streaks come with season totals: the recap counts every time a condition has occurred
|
|
78
|
+
this season — gameweek wins, last places, weeks on top, captain blanks — across resets,
|
|
79
|
+
so a report or editorial can say "their sixth gameweek win of the season", not just
|
|
80
|
+
"two in a row". Each count has its own rule for when it is worth mentioning, tuned to
|
|
81
|
+
how often it happens: wins and last places on every third, captain blanks and waiver
|
|
82
|
+
hauls on every fifth, bottom-half gameweeks only in the second half of the season and
|
|
83
|
+
only on every tenth, and a green-arrow drought only while unbroken at five or ten
|
|
84
|
+
gameweeks, never counting the weeks a manager spent top of the table with nowhere
|
|
85
|
+
to climb. Some bring along others who added to the same count that week. The full list
|
|
86
|
+
prints twice a season, at GW19 and the finale, alongside the fines table. Unjudged
|
|
87
|
+
gameweeks are always stated beside the number — see
|
|
88
|
+
[Season counts](docs/command-reference.md#league-recap) for the per-count rules.
|
|
89
|
+
|
|
66
90
|
### Checking Your Setup
|
|
67
91
|
|
|
68
92
|
```console
|
|
@@ -72,7 +96,7 @@ $ fpl doctor --providers # Probe the external data sources for shape a
|
|
|
72
96
|
|
|
73
97
|
Every ID in settings.yaml is resolved against the live API and the team/league name reported back, so a dead or recycled ID (which otherwise fails silently) is visible. Per-team data files are checked against the current season's clubs. Exits non-zero when something needs fixing.
|
|
74
98
|
|
|
75
|
-
`--providers` checks the other side of the bargain: that each external data source (FPL and Draft APIs, the historical datasets, Understat, football-data.org) still serves data of the expected shape and size,
|
|
99
|
+
`--providers` checks the other side of the bargain: that each external data source (FPL and Draft APIs, the historical datasets, Understat, football-data.org) still serves data of the expected shape and size, that every club resolves across sources, and that the per-gameweek match files actually parse into records through the same code the scoring commands run — the upstream drift that otherwise surfaces as plausible but wrong output, especially at a season rollover.
|
|
76
100
|
|
|
77
101
|
### Scouting Players
|
|
78
102
|
|
|
@@ -157,8 +181,13 @@ $ fpl stats --format json -p MID -s expected_goal_involvements
|
|
|
157
181
|
$ fpl status --format json
|
|
158
182
|
$ fpl fdr --blanks --format json
|
|
159
183
|
$ fpl league-recap --format json
|
|
184
|
+
$ fpl league-fines --format json
|
|
160
185
|
```
|
|
161
186
|
|
|
187
|
+
Failures come back on the same stream: `{"command", "error"}` on stdout with exit code 1.
|
|
188
|
+
Parse stdout either way, and read the message from `error` rather than from stderr — see
|
|
189
|
+
[JSON Output](docs/command-reference.md#json-output).
|
|
190
|
+
|
|
162
191
|
## Configuration
|
|
163
192
|
|
|
164
193
|
Run `fpl init` to configure interactively. Settings stored in your platform's config directory (override with `FPL_CLI_CONFIG_DIR`, and also home to the optional `previews/` intel files); generated data such as team ratings, chip plans and the league history ledger `league-recap` builds up is stored in your platform's data directory (override with `FPL_CLI_DATA_DIR`). In ephemeral environments (e.g. Claude Code on the web), point both at a persistent workspace. Both overrides must be absolute paths — see [Directories](docs/command-reference.md#directories). Saved reports (`--save`, `--output`) are written to a season subdirectory — `<output_dir>/2026-27/gw21-review.md` — so a new season never overwrites the last one's files; see [Report layout](docs/command-reference.md#report-layout).
|
|
@@ -288,6 +288,7 @@ flowchart LR
|
|
|
288
288
|
returnees["returnees"]
|
|
289
289
|
credentials["credentials"]
|
|
290
290
|
init["init"]
|
|
291
|
+
fines["league-fines"]
|
|
291
292
|
end
|
|
292
293
|
|
|
293
294
|
subgraph ViaAgent["Agent-Backed Commands"]
|
|
@@ -327,7 +328,8 @@ flowchart LR
|
|
|
327
328
|
review --> RA
|
|
328
329
|
recap --> RA
|
|
329
330
|
recap -->|"records every run"| LH
|
|
330
|
-
LH -->|"streaks, notes pack,<br/>season phase"| recap
|
|
331
|
+
LH -->|"streaks, notes pack,<br/>season phase, season fines"| recap
|
|
332
|
+
LH -->|"season fine tally"| fines
|
|
331
333
|
LH -.->|"rebuilds when stale"| LHC
|
|
332
334
|
returnees -->|"stores the watchlist it showed"| RS
|
|
333
335
|
RS -->|"week-over-week transitions"| returnees
|
|
@@ -376,7 +378,7 @@ Services live in `fpl_cli/services/` and provide the computation layer between a
|
|
|
376
378
|
|
|
377
379
|
| Service | Purpose |
|
|
378
380
|
|---|---|
|
|
379
|
-
| `scoring/` | Central scoring engine, a package with the public API re-exported from its root (agents import `fpl_cli.services.scoring`). `constants` holds every weight set, ceiling, and consistency magnitude — the ownership/value normalisation ceilings are empirically calibrated per (family, position) by `scripts/calibrate_quality_ceilings.py` (a generated `QUALITY_CEILINGS` block plus a fingerprint of every scoring input; a drift-guard test fails when weights change without recalibration); `data_prep` holds `prepare_scoring_data()`; `evaluation` builds enrichment and `PlayerEvaluation`/`PlayerIdentity`; the scoring families split into `value_quality` (quality baseline + `compute_quality_value()`), `ownership` (target/differential/waiver), and `single_gw` (captain/bench/lineup/XI); `display` owns 0-100 normalisation and `pick_display_ceiling()`; `shrinkage` owns `shrink_scores()` plus the hold-out helpers (`is_known_unavailable()` / `unavailable_player_ids()`) that keep players known not to be playing out of the position means and out of the adjustment. `signals` holds the form modifiers — `compute_form_trajectory()` (direction over recent GWs) and `compute_xgi_sustainability()` (ATK-only rolling xGI divergence -> [0.85, 1.15] multiplier) — plus fixture-adjusted npxG (`compute_adjusted_npxg()` / `build_adjusted_npxg_lookup()` normalise historical xG by opponent Elo; `apply_adjusted_npxg()` overwrites `npxG_per_90` in agent enrichment when data is available) and consistency signals (`build_consistency_lookup()` computes 5 per-player signals — CV-xGI percentile, blank rate, floor percentile, involvement rate, GK consistency — with GW6-10 phase-in; additive bonuses per scoring family: target/waiver cv*1.5, differential inverted cv*0.75, lineup cv*0.75, bench floor*1.5+inv*0.75) |
|
|
381
|
+
| `scoring/` | Central scoring engine, a package with the public API re-exported from its root (agents import `fpl_cli.services.scoring`). `constants` holds every weight set, ceiling, and consistency magnitude — the ownership/value normalisation ceilings are empirically calibrated per (family, position) by `scripts/calibrate_quality_ceilings.py` (a generated `QUALITY_CEILINGS` block plus a fingerprint of every scoring input; a drift-guard test fails when weights change without recalibration; `gk_ceiling_attainability()` scales the GK anchors' ramped-signal share by the calendar-possible share of the enrichment's 450-minute sample ramp — identity from GW6, uniform across keepers at a gameweek, and only on paths whose numerators carry the GK signals — issue #143); `data_prep` holds `prepare_scoring_data()`; `evaluation` builds enrichment and `PlayerEvaluation`/`PlayerIdentity`; the scoring families split into `value_quality` (quality baseline + `compute_quality_value()`), `ownership` (target/differential/waiver), and `single_gw` (captain/bench/lineup/XI); `display` owns 0-100 normalisation and `pick_display_ceiling()`; `shrinkage` owns `shrink_scores()` plus the hold-out helpers (`is_known_unavailable()` / `unavailable_player_ids()`) that keep players known not to be playing out of the position means and out of the adjustment. `signals` holds the form modifiers — `compute_form_trajectory()` (direction over recent GWs) and `compute_xgi_sustainability()` (ATK-only rolling xGI divergence -> [0.85, 1.15] multiplier) — plus fixture-adjusted npxG (`compute_adjusted_npxg()` / `build_adjusted_npxg_lookup()` normalise historical xG by opponent Elo; `apply_adjusted_npxg()` overwrites `npxG_per_90` in agent enrichment when data is available) and consistency signals (`build_consistency_lookup()` computes 5 per-player signals — CV-xGI percentile, blank rate, floor percentile, involvement rate, GK consistency — with GW6-10 phase-in; additive bonuses per scoring family: target/waiver cv*1.5, differential inverted cv*0.75, lineup cv*0.75, bench floor*1.5+inv*0.75) |
|
|
380
382
|
| `player_prior` | Bayesian early-season confidence (GW1-10 shrinkage); threads `PlayerProfile.reliability` (historical availability rate) through `PlayerPrior` to agents |
|
|
381
383
|
| `team_ratings` | TeamRatingsService + Calculator (1-7 scale, 4 axes). `seed_from_prior()` rebuilds from the previous-season prior instead of serving last season's table whenever the current season cannot rate teams: pre-season, and again in the gap after GW1 kicks off, where the pre-season branch has closed but `calculate_from_fixtures()` still returns nothing (it needs a home *and* an away result per club) — without the second case every fixture fell through to a neutral 4.0. Season-aware: a file stamped (or dated) to a previous season is ignored rather than served, taking its `based_on_gws` with it so a new season recalculates. `check_team_set()` diffs the rated clubs against `bootstrap-static` and names the promoted clubs missing and the relegated ones still rated — the check that catches a rollover a date cannot. `has_ratings` / `is_uniform` / `is_preseason_estimate` flag rating sets that cannot produce meaningful fixture difficulty; every warning surfaces via `get_staleness_warning()` |
|
|
382
384
|
| `matchup` | Fixture matchup scoring (0-10), 3-GW recency-weighted |
|
|
@@ -385,9 +387,10 @@ Services live in `fpl_cli/services/` and provide the computation layer between a
|
|
|
385
387
|
| `squad_allocator` | ILP squad allocator (PuLP CBC), horizon-aware, chip-aware |
|
|
386
388
|
| `returnee_radar` | Injury returnee radar behind `fpl returnees`. Parses the FPL `news` grammar into a `ReturnSignal` (four shapes, only two of them dated), filters on a window and a **source-aware** quality bar, and diffs the result against a persisted snapshot for week-over-week transitions. The bar is source-aware because `player_prior` only assigns `source="history"` at 450+ minutes in the previous season and caps `prior_strength` at 0.5 on the `"price"` fallback, so one flat threshold would exclude exactly the players who missed most of last season through injury: history-sourced players are gated on `prior_strength`, price-sourced ones are scored through `value_quality` over their most recent season carrying real minutes, and the within-position price percentile is the last resort. `run_radar()` is the public entry point (snapshot I/O included); `build_radar()` is the pure core. **Fetches nothing** — the caller injects historical profiles and Understat season data, because `prepare_scoring_data` discards the profiles it built priors from and skips fetching them at all on a cache hit. Optional AI-search enrichment (`--enrich`) attaches an `EnrichedReturn` beside the FPL signal, never over it, and only a *cited* enriched date can decide an escalation |
|
|
387
389
|
| `team_form` | Rolling team form stats (last 6 matches, venue splits) |
|
|
388
|
-
| `league_history` | Durable league-history ledger: one NDJSON file per gameweek under `<data dir>/league_history/<season>/<format>-<league id>/`, written as a side effect of `league-recap`. Append-only — an identical row writes nothing, a differing one appends a superseding line, and readers resolve by highest fidelity tier then latest capture, with an unknown row ranking below every tier. Two deliberate inversions of house convention: loading **fails closed** (unlike `chip_plan`, which resets on a corrupt file) because the API cannot rebuild a past gameweek, and season is a **partition key** (unlike `team_ratings`, which discards a previous season) because per-gameweek granularity is destroyed at the July rollover. The consequence of partitioning is that the store only ever grows — nothing prunes a prior season, by design — at roughly a few megabytes per league per season |
|
|
389
|
-
| `league_history_counters` | Declarative streak-condition registry (9 conditions: `weeks_on_top`, `bottom_half_run`, `gw_win_streak`, `gw_loss_streak`, `green_arrow_drought` shared; `captain_blank_run`, `hit_run` classic-only; `waiver_win_run`, `waiver_burn_run` draft-only) and the rebuildable counters projection it drives. Each predicate returns extend/reset/hold for one manager's row — hold on an unknown row, a fixture-less blank,
|
|
390
|
-
| `
|
|
390
|
+
| `league_history` | Durable league-history ledger: one NDJSON file per gameweek under `<data dir>/league_history/<season>/<format>-<league id>/`, written as a side effect of `league-recap`. Append-only — an identical row writes nothing, a differing one appends a superseding line, and readers resolve by highest fidelity tier then latest capture, with an unknown row ranking below every tier. Each row carries its own schema version: an older one is upgraded in memory on read (the line on disk is never rewritten, so a store shared with an older install stays readable by it), a newer one is skipped with a warning and preserved byte-for-byte. Two deliberate inversions of house convention: loading **fails closed** (unlike `chip_plan`, which resets on a corrupt file) because the API cannot rebuild a past gameweek, and season is a **partition key** (unlike `team_ratings`, which discards a previous season) because per-gameweek granularity is destroyed at the July rollover. The consequence of partitioning is that the store only ever grows — nothing prunes a prior season, by design — at roughly a few megabytes per league per season |
|
|
391
|
+
| `league_history_counters` | Declarative streak-condition registry (9 conditions: `weeks_on_top`, `bottom_half_run`, `gw_win_streak`, `gw_loss_streak`, `green_arrow_drought` shared; `captain_blank_run`, `hit_run` classic-only; `waiver_win_run`, `waiver_burn_run` draft-only) and the rebuildable counters projection it drives. Each entry declares the minimum run worth surfacing as a streak, `None` for the two conditions (bottom half, green-arrow drought) whose run only drives their season count's firing rule. Each predicate returns extend/reset/hold for one manager's row — hold on an unknown row, a fixture-less blank, a gameweek the condition does not apply to, or one it structurally could not have ruled (a green-arrow drought holds for a gameweek that began in first place, where climbing was impossible), so neither a capture gap nor an impossibility ever lies about a streak in either direction. Alongside each currently-open run, the state carries season-wide occurrence totals (issue #164): every extending gameweek counts once and survives a reset, holds are tallied separately as the count's "not judged" qualifier, and the last-occurrence and first-evaluated gameweeks bound what a consumer may claim — so "their sixth gameweek win of the season" is derivable, not just the open run. Each entry also declares a `CountSurfacePolicy`, the condition's own rule for when that total is worth rendering weekly (U9 applies it; the milestone set-pieces and `--format json` ignore it). The projection is a disposable cache (never a second source of truth) under `<data dir>/league_history_counters/<season>/<format>-<league id>/counters.json`: it advances one gameweek at a time when its stamp is exactly one behind, and **fails open** to a full rebuild from `league_history`'s rows otherwise — missing, unreadable, wrong-version, or a stamp that isn't stamp+1 (which is also what makes the season totals retroactive: the version bump that added them rebuilds silently from rows already on disk) |
|
|
392
|
+
| `league_history_fines` | Season fine tally (`build_season_fines_tally()`): a pure fold over `league_history`'s rows into per-manager, per-rule counts, with no cache and no second source of truth. Counts only what a row already recorded, so a settings change moves future rulings and leaves history alone — a guarantee `_freeze_recorded_fines` (`cli/_league_recap_history.py`) carries into backfill, where a repair would otherwise re-rule an unchanged cohort under whatever config is current at repair time. Every gameweek that could not be ruled is named as a qualifier rather than folded in as a zero — never captured, unreadable, captured but reaching nobody (every row unknown), holding no record of what was ruled (schema version < 4, or an evaluation that raised), recording a ruling on no rules at all, or captured at a fidelity that could not rule a given rule. Each qualifier states only what the ledger can prove: where two causes are indistinguishable from a row, neither is named. A mid-season joiner keeps their real, lower totals and is qualified; a manager who has since left keeps the fines already ruled against them, bounded to the gameweeks they were recorded for |
|
|
393
|
+
| `league_history_notes` | Notes pack (`build_notes_pack()`), season-phase marker (`derive_season_phase()`, standalone) and the `is_season_milestone()` predicate the recap gates its *printed* season fines table on (its prompt section is ungated) - the phase's *first* gameweek and the finale, deliberately not the whole 13-gameweek midpoint phase, since a once-a-season set-piece fired thirteen times is wallpaper built from `league_history_counters`'s projection plus a bounded trailing window of raw ledger rows (`TRAILING_WINDOW_GAMEWEEKS = 6`, reused as the run-in phase's own length). Every open streak renders as an observed count over its true span rather than "in a row" once any gameweek held (e.g. "3 in the last 11, with 8 not recorded"); each entry declares which of console/report/prompt it reaches — a reportable streak reaches all three, the season-phase marker and coverage statements reach report+prompt only, and a below-minimum streak is retained (exposed via `--format json`) with no surfaces. Season-count entries (issue #164) render each condition's season occurrence total over the manager's own evaluated span, with holds stated as "not judged either way", surfaced per the registry's own declarative `CountSurfacePolicy` (a step the total must land on, run-length milestones read off the open run rather than the total, a first-occurrence rule gated on the season's half, a whole-condition second-half gate, and a ride-along rule for same-gameweek peers — an absolute floor for counts that stay small, a window relative to the nearest firing total for counts that climb all season). Evaluation is two-pass because firing is cross-manager: who fired their condition this gameweek, then which same-gameweek incrementers ride along beside them. Every nonzero count is retained for `--format json`, and at the two `is_season_milestone()` gameweeks the whole nonzero set carries report+prompt — the report's `## Season Counts` set-piece and the retrospective editorial's season-spanning facts. A "since GW X" qualifier is stated only when a partition's or a specific manager's own recorded coverage genuinely begins later than the league's start gameweek, never merely because a trailing window was read. Only the finale phase rescans every captured gameweek (via `rebuild_counters_through`, never the cached `compute_counters_through`) so weekly cost stays flat as the season progresses |
|
|
391
394
|
|
|
392
395
|
## LLM Provider Abstraction
|
|
393
396
|
|
|
@@ -521,9 +524,10 @@ fpl_cli/
|
|
|
521
524
|
│ ├── _banner.py # Startup banner
|
|
522
525
|
│ ├── _plan_grid.py # Fixture grid rendering
|
|
523
526
|
│ ├── _review_*.py # Review command helpers (analysis, classic, draft, summarisation)
|
|
524
|
-
│ ├── _league_recap_*.py # League recap helpers & types (`_league_recap_history.py` orchestrates capture: builds ledger rows, corrects previous league position from recorded rows, runs the two-tier backfill, and returns the notes pack the console, report, prompt and JSON payload all read)
|
|
525
|
-
│ ├── _fines.py / _fines_config.py # League fines system
|
|
526
|
-
│ ├──
|
|
527
|
+
│ ├── _league_recap_*.py # League recap helpers & types (`_league_recap_history.py` orchestrates capture: builds ledger rows, corrects previous league position from recorded rows, runs the two-tier backfill including the coarse tier's partial fine ruling, and returns the notes pack and season fine tally the console, report, prompt and JSON payload all read)
|
|
528
|
+
│ ├── _fines.py / _fines_config.py # League fines system, including the cohort-only/needs-a-squad rule split the coarse ledger tier narrows by
|
|
529
|
+
│ ├── league_fines.py # `league-fines`: season fine table read straight off the ledger, no network
|
|
530
|
+
│ ├── doctor_providers.py # Live provider probes for `doctor --providers` (shape/volume/join checks; the Core-Insights per-GW probe runs the real parsers, so it cannot pass a file the runtime reads as zero records)
|
|
527
531
|
│ └── [command files] # One file per command/group
|
|
528
532
|
├── agents/
|
|
529
533
|
│ ├── base.py # Agent ABC, AgentResult, AgentStatus
|
|
@@ -569,20 +573,21 @@ fpl_cli/
|
|
|
569
573
|
│ ├── returnee_radar.py # Injury returnee radar: FPL news grammar -> ReturnSignal, source-aware quality bar, window filter, snapshot diff, optional AI-search enrichment. Pure core (build_radar) + I/O entry point (run_radar); fetches nothing itself
|
|
570
574
|
│ ├── team_form.py # Rolling team form stats
|
|
571
575
|
│ ├── league_history.py # League history ledger store: per-gameweek NDJSON, fail-closed load, supersession, coverage query
|
|
572
|
-
│ ├── league_history_counters.py # Streak-condition registry (9 conditions, extend/reset/hold predicates) + rebuildable counters projection: own version + computed-through-gameweek stamp, fails open to a full rebuild from league_history
|
|
573
|
-
│
|
|
576
|
+
│ ├── league_history_counters.py # Streak-condition registry (9 conditions, extend/reset/hold predicates) + rebuildable counters projection: own version + computed-through-gameweek stamp, fails open to a full rebuild from league_history. State carries both the open run and reset-surviving season occurrence totals with a held-gameweek tally
|
|
577
|
+
│ ├── league_history_fines.py # Season fine tally (build_season_fines_tally): per-manager, per-rule counts folded straight off the ledger, plus honest coverage qualifiers. No cache, no re-ruling
|
|
578
|
+
│ └── league_history_notes.py # Notes pack (build_notes_pack) + derive_season_phase() + is_season_milestone(): per-manager streak factoids in observed-count phrasing, season-count factoids (surfaced per each condition's own CountSurfacePolicy — who fired it this gameweek plus qualifying ride-alongs; full set at the two milestones), season-phase marker, coverage/"since GW X" statements. Counters projection + a bounded trailing window of rows; full season only at the finale
|
|
574
579
|
├── models/
|
|
575
580
|
│ ├── player.py # Player, PlayerStatus, PlayerPosition, POSITION_MAP, BLANK_POINTS_THRESHOLD
|
|
576
581
|
│ ├── team.py # Team
|
|
577
582
|
│ ├── fixture.py # Fixture
|
|
578
583
|
│ ├── chip_plan.py # ChipPlan, ChipType, PlannedChip, UsedChip
|
|
579
|
-
│ ├── league_history.py # LeagueHistoryRow + Ledger* sub-models, CaptureStatus, FidelityTier, schema version constants; ConditionRunState + LeagueHistoryCountersProjection for the counters cache; ManagerEarliestGameweekCache for the first-captured-gameweek memo
|
|
584
|
+
│ ├── league_history.py # LeagueHistoryRow + Ledger* sub-models, CaptureStatus, FidelityTier, schema version constants (v4 adds `fine_rules_evaluated`, which tells an unfined gameweek from an unruled one); ConditionRunState + LeagueHistoryCountersProjection for the counters cache; ManagerEarliestGameweekCache for the first-captured-gameweek memo
|
|
580
585
|
│ └── types.py # TypedDicts: CaptainCandidate, WaiverTarget, EnrichedPlayer, etc.
|
|
581
586
|
├── prompts/
|
|
582
587
|
│ ├── scout.py # ScoutAgent system/user prompts
|
|
583
|
-
│ ├── review.py # Review research prompts
|
|
588
|
+
│ ├── review.py # Review research + synthesis prompts. Squad and transfer lines in the synthesis data block carry each player's full club name, and the hard constraints bind club affiliation and blanket scored/blanked claims to that supplied data — an LLM's own club knowledge goes stale every transfer window
|
|
584
589
|
│ ├── returnees.py # Return-intel search prompt for `fpl returnees --enrich` (one player per query, so a citation list belongs to a single player)
|
|
585
|
-
│ └── league_recap.py # League recap synthesis prompts, including the anchored League History section, its never-infer-history rule (emitted even when the pack is empty), and the season-phase framing instruction
|
|
590
|
+
│ └── league_recap.py # League recap synthesis prompts, including the anchored League History section, its never-infer-history rule (emitted even when the pack is empty), and the season-phase framing instruction. `collect_player_clubs()` regroups the club each squad/transfer row already resolved off its `team_id` by the name the prose uses, feeding both a `## Player Clubs` roster and the club printed inline on each captain group, so club claims bind to supplied data instead of recall; a name the data gives two clubs for is dropped rather than guessed at, since the prose can't tell the two players apart
|
|
586
591
|
├── parsers/
|
|
587
592
|
│ └── recommendations.py # Parse gw{N}-recommendations.md into structured decisions
|
|
588
593
|
├── scraper/
|
|
@@ -680,5 +685,5 @@ User settings deep-merged over committed defaults via `platformdirs`. `.env` loa
|
|
|
680
685
|
- **Agent-friendly.** `--format json` on key commands with a consistent envelope. See [Agent Tools & Skills](../.agents/TOOLS.md).
|
|
681
686
|
- **LLM features are opt-in.** Core analysis works without any API keys. LLM providers add narrative and research capabilities.
|
|
682
687
|
- **The returnee radar is deliberately absent from [custom-analysis.md](custom-analysis.md).** That document covers the scoring formulas and the early-season shrinkage they share, and the radar has neither: it runs no shrinkage, so it needs no hold-out set for players known not to be playing, and it scores nothing for the ownership family, so the -3 availability penalty never applies — a flagged player is the radar's entire population, not a discount applied within it. The one existing formula it reuses, the VALUE quality score, it reuses unchanged. Its methodology is documented under [Injury Returnees](command-reference.md#injury-returnees) instead. The omission is the decision, not a gap.
|
|
683
|
-
- **Deterministic memory before LLM memory.** `league-recap` records every run to an append-only ledger and computes streaks, trends and season
|
|
688
|
+
- **Deterministic memory before LLM memory.** `league-recap` records every run to an append-only ledger and computes streaks, trends, season phase and the season fine tally from it in Python. The model is handed those as vetted facts through anchored prompt sections and is never asked to remember, infer, or re-derive history — the one conduit per fact is what makes an editorial's historical claims checkable against the store. The fines section makes the rule explicit: season totals may only be quoted from it, never summed by the model out of the current gameweek's fines. It is handed over every week as optional colour while the *printed* table waits for a season milestone -- a table every week is wallpaper, but a sentence every week is what makes a recap read as though it remembers.
|
|
684
689
|
|