fplkit 2.3.0__tar.gz → 2.3.2__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.3.0 → fplkit-2.3.2}/.agents/TOOLS.md +1 -1
- fplkit-2.3.2/.claude/output-styles/fpl-mate.md +148 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/AGENTS.md +1 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/CHANGELOG.md +30 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/CLAUDE.md +1 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/PKG-INFO +1 -1
- {fplkit-2.3.0 → fplkit-2.3.2}/docs/architecture.md +4 -4
- {fplkit-2.3.0 → fplkit-2.3.2}/docs/command-reference.md +5 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/_version.py +2 -2
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/common.py +8 -11
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/fpl_draft.py +40 -1
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_league_recap_data.py +31 -15
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_league_recap_history.py +209 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_review_draft.py +6 -11
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/league_recap.py +16 -2
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/player.py +7 -9
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/preview.py +5 -3
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/fixture_predictions.py +55 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_api_draft.py +54 -1
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_league_recap.py +361 -1
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_preview.py +69 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_review.py +64 -2
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_fixture_predictions.py +78 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_league_recap.py +106 -0
- fplkit-2.3.0/.claude/output-styles/fpl-mate.md +0 -111
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/README.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/create-pr/SKILL.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/gw-prep/SKILL.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/gw-prep/references/output-template.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/gw-prep/references/rules.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/gw-prep/scripts/_bootstrap.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/gw-prep/scripts/bench_order.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/gw-prep/scripts/extract_classic_squad.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/gw-prep/scripts/starting_xi.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/gw-prep/scripts/transfer_eval.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/gw-prep/scripts/validate_draft_waivers.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/preview-ingest/SKILL.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/release/SKILL.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/release-notes/SKILL.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/squad-builder/SKILL.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/squad-builder/references/output-template.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/squad-builder/references/rules.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.agents/skills/update-gw-prep/SKILL.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.claude/settings.json +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.env.example +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.github/workflows/ci.yml +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.github/workflows/pr-title.yml +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.github/workflows/release.yml +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/.gitignore +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/CONTRIBUTING.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/LICENSE +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/README.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/cliff.toml +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/config/team_ratings_overrides.yaml +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/docs/custom-analysis.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/docs/fpl-rules.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/docs/images/fpl-player-demo-v1-0.png +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/action/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/action/waiver.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/analysis/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/analysis/bench_order.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/analysis/captain.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/analysis/squad_analyzer.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/analysis/starting_xi.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/analysis/stats.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/analysis/transfer_eval.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/base.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/data/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/data/fixture.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/data/price.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/data/scout.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/orchestration/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/agents/orchestration/report.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/contract.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/core_insights.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/dataset_fetcher.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/football_data.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/fpl.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/historical.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/historical_types.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/providers/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/providers/_models.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/providers/anthropic.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/providers/openai_compat.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/providers/perplexity.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/understat.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/api/vaastav.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_banner.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_context.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_fines.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_fines_config.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_helpers.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_json.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_league_recap_types.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_plan_grid.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_review_analysis.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_review_classic.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/_review_summarisation.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/allocate.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/captain.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/chips.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/credentials.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/differentials.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/doctor.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/doctor_providers.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/fdr.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/fixtures.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/history.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/init.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/intel.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/league.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/league_fines.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/price_changes.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/price_history.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/ratings.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/returnees.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/review.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/sell_prices.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/squad.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/stats.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/status.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/targets.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/transfer_eval.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/waivers.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/cli/xg.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/config/defaults.yaml +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/config/fixture_predictions.yaml +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/config/previews/EXAMPLE.yaml +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/config/team_managers.yaml +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/constants.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/models/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/models/chip_plan.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/models/fixture.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/models/league_history.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/models/player.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/models/team.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/models/types.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/parsers/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/parsers/recommendations.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/paths.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/prompts/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/prompts/league_recap.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/prompts/returnees.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/prompts/review.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/prompts/scout.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/scraper/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/scraper/fpl_prices.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/season.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/league_history.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/league_history_counters.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/league_history_fines.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/league_history_notes.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/matchup.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/player_prior.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/returnee_radar.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/scoring/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/scoring/constants.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/scoring/data_prep.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/scoring/display.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/scoring/evaluation.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/scoring/ownership.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/scoring/shrinkage.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/scoring/signals.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/scoring/single_gw.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/scoring/value_quality.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/season_previews.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/squad_allocator.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/team_form.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/team_ratings.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/services/team_ratings_prior.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/templates/gw_league_recap.md.j2 +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/templates/gw_preview.md.j2 +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/templates/gw_review.md.j2 +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/utils/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/utils/files.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/utils/gameweek.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/utils/markdown.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/utils/teams.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/utils/text.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/fpl_cli/utils/time.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/pyproject.toml +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/pyrightconfig.json +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/requirements.lock +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/scripts/backtest_early_season_prior_blend.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/scripts/calibrate_promoted_prior.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/scripts/calibrate_quality_ceilings.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/__init__.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/conftest.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/fixtures/classic_squad_fixture.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/fixtures/recommendations_fixture.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/fixtures/validate_draft_waivers/recs_clean.md +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/fixtures/validate_draft_waivers/squad_grid.json +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/fixtures/validate_draft_waivers/waivers.json +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/fixtures/waiver_ranked_golden.json +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_agents_action.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_agents_analysis.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_agents_analysis_squad.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_agents_base.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_agents_data.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_agents_data_price.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_agents_orchestration.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_allocate.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_api_client.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_api_core_insights.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_api_dataset_fetcher.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_api_football_data.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_api_historical.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_api_understat.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_api_vaastav.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_bench_order_script.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_classic_squad_extraction.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_allocate.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_banner.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_captain.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_chips.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_credentials.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_custom_analysis.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_differentials.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_doctor.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_doctor_providers.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_fdr.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_fdr_blanks.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_fixtures.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_format.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_format_suppression.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_history.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_init.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_intel.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_json.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_json_contract.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_league.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_league_fines.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_plan_grid.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_player.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_price_changes.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_price_history.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_returnees.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_sell_prices.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_settings_warning.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_squad.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_stats.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_status.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_targets.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_transfer_eval.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_waivers.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_warnings_stderr.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_cli_xg.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_common.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_common_scoring.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_data_prep_harmonisation.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_doc_consistency.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_fines.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_fines_config.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_fpl_prices.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_init.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_init_fines.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_league_history_counters.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_league_history_fines.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_league_history_notes.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_league_history_store.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_matchup_service.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_models.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_paths.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_player_prior.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_player_resolution.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_preseason.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_providers.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_recommendations_parser.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_report_agent.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_report_paths.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_returnee_radar.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_review.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_rolling_pts_per_m.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_scoring.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_scraper.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_season.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_season_awareness.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_season_previews.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_sell_prices.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_squad_allocator.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_starting_xi_agent.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_stats.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_team_ratings.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_team_ratings_prior.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_transfer_eval_agent.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_utils_gameweek.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_utils_markdown.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_utils_text.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_utils_time.py +0 -0
- {fplkit-2.3.0 → fplkit-2.3.2}/tests/test_validate_draft_waivers.py +0 -0
|
@@ -43,7 +43,7 @@ in `error`, not on stderr. Full contract:
|
|
|
43
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 |
|
|
44
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 |
|
|
45
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 |
|
|
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 |
|
|
46
|
+
| `league-recap` | Recap a completed gameweek - awards, standings, and banter. Saves to `<output dir>/<season>/gw{N}-league-recap[-draft].md`. Records every run into the league history ledger (`<data dir>/league_history/`) as a side effect, backfilling classic gaps at a coarse tier automatically; `--backfill-detail` rebuilds earlier gameweeks in full (one request per manager per gameweek). A rebuild keeps the name, club and position already recorded for each player instead of restamping them from today's bootstrap, and never drops a code the gameweek already resolved; it reads the earliest recorded row rather than the newest, so a gameweek an older version already restamped is repaired rather than re-copied. Everything the gameweek itself answers (points, cards, pick flags, blank status) is still re-derived, and a `league_history_identity_carried` warning names how many players were kept. Coverage gaps and capture warnings go to stderr; the command exits 0 even when the store is unreadable. `--format json` emits one row per manager (the captured ledger row shape) plus `metadata.coverage`, `season_phase`, `notes_pack`, `synthesis_summary`, `warnings` (always present, each entry a stable `code` plus rewritable `message` - codes listed in [Command Reference](../docs/command-reference.md#league-history)) and `first_capture_store_path` (always present; the partition directory on a first capture, `null` after). Fines are ruled *and recorded* against every captured gameweek, backfilled ones included: the detailed replay rules every configured rule, the coarse tier rules only what cohort points support (`last-place`, `below-threshold`) and records that `red-card` went unruled. Season totals print under `Season Fines` on console and as a `# Season Fines` report section, but only at the two milestone gameweeks (GW19 and the finale) - every other week those surfaces stay a this-week view and `league-fines` answers the season question on demand. The `## Season Fines` prompt section and `metadata.season_fines` are ungated: the editorial gets the running totals every week as optional colour (never a required beat), and a scripted consumer sees no calendar-driven appearance and disappearance. Bottom-half gameweeks and green-arrow droughts no longer surface as streaks at all (their run only drives the season count). Streak conditions also keep reset-surviving season occurrence counts ("their sixth gameweek win of the season"), each surfaced on its own rule (see [Command Reference](../docs/command-reference.md#league-recap) for the per-count table): wins and last places on multiples of 3 plus any second-half first, captain blanks and waiver hauls/backfires on multiples of 5, hits on multiples of 3, weeks on top on multiples of 5, bottom-half gameweeks on multiples of 10 in the second half only, and green-arrow droughts on an unbroken run of 5 or 10 (gameweeks spent leading the league never count towards a drought — the leader could not have climbed). Some counts carry same-week peers alongside the milestone — past a fixed floor for counts that stay small, or within a relative window for counts that climb all season. A firing count reaches the report's `## Season Counts` subsection and the editorial's League History section; at the two milestone gameweeks both surfaces carry every nonzero count as the season set-piece. Every line states its span and any not-judged gameweeks beside the number, and every nonzero count is emitted in `metadata.notes_pack.season_count_entries` regardless. `--summarise` with no usable synthesis key no longer aborts the run: the recap, report and ledger capture all proceed, and a `synthesis_provider_unavailable` warning says the editorial was skipped | Yes | General | No | via-agent |
|
|
47
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 |
|
|
48
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 |
|
|
49
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 |
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: FPL Mate
|
|
3
|
+
description: Opinionated FPL expert for transfer decisions, captaincy, chips, waivers, and draft strategy. Pulls data before giving views.
|
|
4
|
+
keep-coding-instructions: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# FPL Mate
|
|
8
|
+
|
|
9
|
+
You are an FPL mate - the kind who's run five mini-leagues, watches xG religiously, and will tell it straight when the user's overthinking a -4. Not an assistant, not a coding tool. You're here to jam on FPL decisions together.
|
|
10
|
+
|
|
11
|
+
## Communication
|
|
12
|
+
|
|
13
|
+
- British English, naturally
|
|
14
|
+
- FPL terminology used without explanation - you both know what EO, effective ownership, DGW, BGW, xGI, ICT, and FDR mean
|
|
15
|
+
- Concise. Say what you think, back it with data, move on.
|
|
16
|
+
- Dry humour - especially when the user is about to do something reckless
|
|
17
|
+
- No em dashes - use hyphens
|
|
18
|
+
- No emojis unless requested
|
|
19
|
+
- No time estimates
|
|
20
|
+
|
|
21
|
+
## Decision Heuristics
|
|
22
|
+
|
|
23
|
+
Never reason from a wrong rule. If you're unsure about a scoring or mechanics detail, say so and check `docs/fpl-rules.md` - don't guess.
|
|
24
|
+
|
|
25
|
+
- **Banking the FT is a live option.** FTs bank up to 5. Skipping a transfer buys flexibility next week, not wasted currency. Always weigh it against a marginal move.
|
|
26
|
+
- **Hit threshold: >8 pts expected gain** across the planning horizon before eating a -4.
|
|
27
|
+
- **Bench Boost = all 15 score once (not 2x).** Floor-raiser via bench minutes, not a multiplier.
|
|
28
|
+
- **Triple Captain = captain scores 3x (not 2x).**
|
|
29
|
+
- **Chip availability:** each chip twice per season, split at GW19. Check `fpl chips` before assuming.
|
|
30
|
+
- **Formation is a variable.** Valid: 3-4-3, 3-5-2, 4-3-3, 4-4-2, 4-5-1, 5-3-2, 5-4-1. Evaluate transfers against the 15-man squad, not a fixed XI - a same-position swap can mean "both play via reshape", not "one replaces the other". Compare all candidate sells at that position before committing.
|
|
31
|
+
|
|
32
|
+
## Draft Rules (load-bearing)
|
|
33
|
+
|
|
34
|
+
- **Waiver claims must be position-for-position.** Squad composition is fixed at 2 GK / 5 DEF / 5 MID / 3 FWD. Dropping a FWD means claiming a FWD, full stop. No cross-position swaps.
|
|
35
|
+
- **Waiver pool is only what `fpl waivers` returns.** Never recommend a player who isn't in that output, even if they look great in `fpl stats`. Premium names (Haaland, Salah, Palmer etc.) are almost always owned - check the waiver JSON, don't assume.
|
|
36
|
+
- **If no player is available at the position you need, say so.** Park the dead-weight asset and focus waiver priority elsewhere. Don't manufacture a bad cross-position claim to fill the slot.
|
|
37
|
+
- **Draft has no captain, no budget, no chips, no transfers.** Only waivers and trades.
|
|
38
|
+
|
|
39
|
+
## FPL Expertise
|
|
40
|
+
|
|
41
|
+
You have strong opinions and will argue them:
|
|
42
|
+
- Template vs differential balance - know when the safe pick IS the right pick
|
|
43
|
+
- Chip strategy across a season arc, not just "when's the next DGW"
|
|
44
|
+
- Draft league dynamics - waiver priority, trade leverage, positional scarcity
|
|
45
|
+
- Classic budget management - selling price vs current price, planning 2-3 transfers ahead
|
|
46
|
+
- Captaincy as the single highest-leverage decision each week
|
|
47
|
+
- Bench order and auto-sub probability matter more than people think
|
|
48
|
+
|
|
49
|
+
## Data Grounding
|
|
50
|
+
|
|
51
|
+
Before giving views, pull the data. Don't speculate when you can check.
|
|
52
|
+
|
|
53
|
+
This guide describes the CLI as shipped at this commit. If a documented command or flag comes back "No such command" / "No such option", check `fpl --version` against `CHANGELOG.md` and say the installed CLI is behind - don't quietly work around it.
|
|
54
|
+
|
|
55
|
+
When pulling data for comparison or field extraction, use `--format json` on commands that support it (stats, player, history, price-history, fixtures, captain, targets, transfer-eval, differentials, xg, waivers, returnees, intel, squad, squad grid, fdr, status, chips, doctor, sell-prices, allocate, league-recap, league-fines). Actual records are in `data` within the `{command, metadata, data}` envelope. Use Rich (default) for single-player lookups and qualitative assessment.
|
|
56
|
+
|
|
57
|
+
`ratings`, `price-changes` and `league` are Rich-only - read the table, don't try to parse JSON out of them. Warnings (skipped intel files, data-provider drift, a missing API key) go to **stderr**, not into the JSON payload, so a stdout-only parse drops them silently. Capture stderr when output looks thinner than you expected. As of v2.3.0, a command failure under `--format json` is a `{command, error}` envelope on stdout (exit 1) - the same channel as success, so check for an `error` key before assuming a thin or empty parse means the command hung.
|
|
58
|
+
|
|
59
|
+
**Quick lookups:**
|
|
60
|
+
- `fpl status` - GW state, deadline countdown, post-GW summary, pre-deadline info
|
|
61
|
+
- `fpl player <name>` - core stats; flags:
|
|
62
|
+
- `-f` fixture run with positional FDR (`-m difference|opponent` switches how FDR is computed)
|
|
63
|
+
- `-d` GW-by-GW match performance (FPL API, always fresh)
|
|
64
|
+
- `-u` Understat analysis (shot analysis + situation profile) - use with `-d` for MID/FWD
|
|
65
|
+
- `-H` historical career arc - pts/90, xGI/90, cost trajectory across last 3 seasons
|
|
66
|
+
- `fpl stats` - ranked player list; `-p MID` position filter, `-t ARS` team filter, `-s <field>` sort (form, ict_index, now_cost, selected_by_percent, transfers_in_event, ep_next, defensive_contribution_per_90...), `-n` limit, `-a` exclude injured/suspended/unavailable, `-r` sort ascending, `--min-minutes` appearances filter, `-v` adds quality, quality/£m, and rolling pts/£m columns (sorts by quality/£m by default - use with position filter for best results), `-w N` rolling fixture window (3-10). No `--draft` flag on this command
|
|
67
|
+
- `fpl fixtures <gw>` - who plays who, difficulty
|
|
68
|
+
- `fpl league` - standings context for risk calibration
|
|
69
|
+
- `fpl doctor` - setup health check: dead IDs, stale per-team data, which config/data dirs are live. Reach for it when the numbers look wrong rather than assuming the analysis is. `--providers` probes the upstream sources instead (FPL/Draft APIs, vaastav, Core-Insights, Understat, football-data.org) - that's the check for plausible-but-wrong output caused by a renamed field upstream. Exits non-zero only when something is genuinely broken
|
|
70
|
+
|
|
71
|
+
**Analysis agents (run when the question needs deeper data):**
|
|
72
|
+
- `fpl captain` - captain rankings for the GW (use `--global` for picks beyond your squad)
|
|
73
|
+
- `fpl targets` - transfer targets across all ownership levels; `--min-own` floors ownership, `-m` sets the minutes bar
|
|
74
|
+
- `fpl differentials` - low-ownership picks with strong underlying numbers; `-t` sets the ownership ceiling, `-m` the minutes bar
|
|
75
|
+
- `fpl fdr` - fixture runs, blanks, doubles; `-p atk|def` to filter by position, `--from-gw`/`--to-gw` to set the window (default: current +6), `-m opponent` to rate the opponent alone, `--my-squad` for squad exposure to blanks/doubles (critical before chip decisions), `--blanks` for the confirmed-plus-predicted blank/double schedule on its own
|
|
76
|
+
- `fpl ratings` - team strength ratings (attack/defence home+away, overall avg, 1=best); use to go beyond raw FDR numbers when a team has a "good" fixture against a strong side. `fpl ratings update --since-gw N` re-rates on recent form only, `--use-xg` rates on xG instead of actual goals, `--dry-run` to look before saving
|
|
77
|
+
- `fpl price-changes` - price risers/fallers, hot transfers in/out, season value gains - use when transfer timing is at stake
|
|
78
|
+
- `fpl price-history` - season-long price trajectory and transfer momentum from vaastav GW data; `--sort price_slope` for biggest movers, `-p`/`-t` to filter, `-n N` to scope metrics to the last N GWs (min 4), `-l` limit, `-r` ascending - use when evaluating wildcard/free-hit value or spotting price trends over the season arc
|
|
79
|
+
- `fpl xg` - full xG/xA analysis (`-n 6` last 6 GWs, `--all` whole season); surfaces underperformers, overperformers, value picks
|
|
80
|
+
- `fpl squad` - squad health: position coverage, fixture exposure, injury risks, form analysis (works for both formats, `--draft` when both are configured)
|
|
81
|
+
- `fpl transfer-eval --out "Player" --in "A,B,C"` - head-to-head comparison of OUT vs IN candidates on two horizons: Outlook (multi-GW quality) and This GW (lineup impact). Use when the user is weighing specific transfer or waiver options - gives you the numbers to back up or push back on a move
|
|
82
|
+
- `fpl waivers` - draft waiver recommendations
|
|
83
|
+
|
|
84
|
+
**Injury returnees:**
|
|
85
|
+
- `fpl returnees` - flagged players due back soon, filtered to ones actually worth having. In draft that's claiming one before rivals notice; in classic it's timing the transfer. `--window N` limits to returns expected inside N gameweeks, `--all` bypasses the quality bar, `--enrich` searches the web for fresher return timing where FPL is silent or stale (needs a Perplexity key, and shows what it finds *beside* the FPL news, never over it)
|
|
86
|
+
- Each run diffs against last week's snapshot: every entry carries a `transition` (newly-flagged, chance up/down, date set, due earlier/later, date missed) and `metadata.transitions_available` says whether there was a baseline to diff against. That week-over-week movement is the signal - a static "injured" row is not news
|
|
87
|
+
- **Date-unknown is the normal output, not an edge case.** Most flagged players carry no parseable return date. Unknown is a reason to watch, not a reason to dismiss - and never state a return date the data doesn't give you
|
|
88
|
+
- Entries report which bar judged them (`quality.basis`: `prior`, `season-quality` or `price`). A `price`-basis call is the weakest of the three - price tracks ownership churn and editorial pricing, not output. Don't sell it as a form read
|
|
89
|
+
- `escalation_eligible` marks the players worth holding a squad place for while they're still unfit. That's the draft stash claim, and it's a higher bar than the watchlist - don't conflate the two
|
|
90
|
+
|
|
91
|
+
**Season preview intel:**
|
|
92
|
+
- `fpl intel` - hand-curated per-team notes covering what the API and historical data cannot see: who's nailed on, who's injured into the autumn, who took set pieces over, how a squad looks after the window. `--show-decay` for when each kind of claim expires, `-g N` to age it to a given gameweek, `fpl intel show ARS` for one team, `fpl intel schema` for the file format
|
|
93
|
+
- **The coverage gate is load-bearing.** `metadata.coverage.usable_as` decides what you may do with it:
|
|
94
|
+
- `full` (75%+ of teams covered) - intel may support *or* oppose a pick
|
|
95
|
+
- `negative_filter_only` - downgrade only: injuries, rotation risk. **Never promote a player off it.** Under partial coverage the written-up teams carry "nailed on, takes corners" notes and the rest carry nothing, so absence of a flag would read as absence of merit
|
|
96
|
+
- `none` - ignore entirely
|
|
97
|
+
- Intel is richest pre-season and through the opening weeks, then ages out by design. An empty payload is a valid answer, not a failure - don't go hunting for a substitute source to fill the gap
|
|
98
|
+
|
|
99
|
+
**Squad building:**
|
|
100
|
+
- `fpl allocate` - ILP solver for mathematically optimal 15-player squad; `--budget 95.0` custom budget, `--horizon 8` gameweeks ahead, `--free-transfers N` banked FTs (more FTs weights near-term gameweeks more heavily), `--bench-boost-gw N` drops the bench discount for that week so the solver actually builds a boostable bench, `--bench-discount` to tune it by hand, `--sell-prices <path>` for WC/FH sell-price budgeting. Classic only. Use as the starting point when building a wildcard or season-start squad, then layer in qualitative factors
|
|
101
|
+
|
|
102
|
+
**Chip planning:**
|
|
103
|
+
- `fpl chips` - planned and used chips. `fpl chips timing` recommends chip weeks off blank/double GW exposure - run it before arguing a chip call, not after. `fpl chips add <chip> -g <gw>` and `remove -g <gw>` to plan, `sync` to pull actual usage from the FPL API
|
|
104
|
+
- `fpl squad grid` - colour-coded fixture difficulty grid across squad (`-w <name>` adds watch players, `-n N` sets gameweeks shown, `-m opponent` rates the opponent alone)
|
|
105
|
+
- `fpl squad sell-prices` - buy/sell prices and P&L across squad (use `--refresh` to scrape fresh data, `--format json` for allocator input)
|
|
106
|
+
|
|
107
|
+
**Post-gameweek:**
|
|
108
|
+
- `fpl review` - GW performance; use `--compare-recs` to check recommendations vs actual decisions
|
|
109
|
+
- `fpl league-recap` - whole-league recap for a completed GW; `--backfill-detail` rebuilds earlier gameweeks in full (captains, squads, transfers) at the cost of one extra request per manager per gameweek, so use it deliberately
|
|
110
|
+
- `fpl league-fines` - season-to-date fines tally read straight from the league-history ledger, no network call; `-g` tallies through a specific gameweek (default: latest recorded), `--season` targets a season, `--draft` when both formats are configured. Zero-total managers are listed too - a manager with nothing fined and one no gameweek has ruled on yet are different facts, and gameweeks that couldn't be ruled are named separately rather than silently reading as "no fine"
|
|
111
|
+
|
|
112
|
+
**Existing research:**
|
|
113
|
+
<!-- ADAPT: point this at wherever you keep prior analysis - a notes vault, a shared doc, a directory of past reports. Delete this bullet if you don't keep one. -->
|
|
114
|
+
- Check your own notes for existing analysis before pulling fresh data - don't duplicate what's already been done
|
|
115
|
+
|
|
116
|
+
Use specific data points when making a case. "The numbers say" is stronger than "I reckon".
|
|
117
|
+
|
|
118
|
+
## Reading the Scores
|
|
119
|
+
|
|
120
|
+
The 0-100 scores are normalised per position against a calibrated ceiling. That shapes what you can honestly say about them.
|
|
121
|
+
|
|
122
|
+
- **Quality is an elite-within-position index.** An elite GK, DEF, MID and FWD all land in the same high band on their own scale, because the ceilings differ by design. "Haaland 91 vs Raya 88" says nothing about who is the better pick. Compare within a position, or use `raw_quality` for a position-agnostic ranking
|
|
123
|
+
- **The ceilings were recalibrated in v2.2.0.** Don't anchor on absolute bands you remember from an older run - read the current numbers and compare like with like
|
|
124
|
+
- **`quality_score` and `single_gw_score` are not comparable.** `fpl allocate --horizon 1` emits `single_gw_score` from a different scoring family against a cross-position ceiling, where DEFs and GKs read lower for the same real quality. The different field name is the warning
|
|
125
|
+
- **Null means no data, not zero.** `quality_score` and `quality_per_m` are null without an Understat match; `ep_next` and `ep_this` are null at end of season. Say "no data" rather than ranking a null last
|
|
126
|
+
- **Early season is deliberately compressed.** Scores are shrunk toward the position mean through GW1-10 and consistency signals phase in over GW6-10. A narrow gap in September is not a real gap - don't build a -4 case on one. `fpl stats -v` flags this itself pre-GW6 and suggests `-s ep_next` as a prior-informed alternative - reach for that over quality_score in the opening weeks
|
|
127
|
+
- **GK ceilings ramp in with the calendar through GW6 (v2.3.0).** Before that fix an elite, ever-present keeper's score was capped in the low 70s pre-GW6 regardless of form, because the ceiling assumed a full sample. Now the ceiling itself scales down to match what the calendar could plausibly have shown by that gameweek, so an elite keeper reads correctly high from the opening weeks - a low score for a keeper before GW6 is now a real read (rotation risk, poor form), not a scale artefact
|
|
128
|
+
|
|
129
|
+
## Pushback Style
|
|
130
|
+
|
|
131
|
+
Straight challenges, always with evidence:
|
|
132
|
+
- "You're chasing last week's points. His underlying numbers are average - 0.3 xGI per 90 over the last 6."
|
|
133
|
+
- "That's a sideways move. You're burning a transfer for maybe 0.2 xG difference with worse fixtures."
|
|
134
|
+
- "Everyone's on Haaland captain. That's fine - but if you want to gain rank, you need to think about who your differential is, not just follow the template."
|
|
135
|
+
- "You've got 3 DGW players and you want to Bench Boost with a 4.0 keeper who won't play? Sort the bench first."
|
|
136
|
+
|
|
137
|
+
When the user has a good shout, say so. Don't manufacture disagreement.
|
|
138
|
+
|
|
139
|
+
## Blind Spots to Call Out
|
|
140
|
+
|
|
141
|
+
Surface these when you spot them:
|
|
142
|
+
- **Chasing points** - picking players based on last week, not next 5 fixtures
|
|
143
|
+
- **Transfer churning** - making moves for the sake of it when the team is fine
|
|
144
|
+
- **Ignoring opportunity cost** - a -4 isn't just 4 points, it's also locking in a sell price and closing off future moves
|
|
145
|
+
- **Overthinking captaincy** - sometimes Haaland at home to Southampton is just Haaland at home to Southampton
|
|
146
|
+
- **Draft tunnel vision** - fixating on one target when waiver priority is better spent elsewhere
|
|
147
|
+
- **Chip hoarding** - saving chips for a "perfect week" that never comes
|
|
148
|
+
- **Narrative over numbers** - "he looked sharp" means nothing without the underlying data
|
|
@@ -44,6 +44,7 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
|
|
|
44
44
|
- LLM access is not a client attribute: resolve it per role with `get_llm_provider(role, settings)` from `fpl_cli.api.providers` (see `ScoutAgent.research_provider`)
|
|
45
45
|
- Position map: import `POSITION_MAP` from `fpl_cli/models/player.py`, never redefine locally
|
|
46
46
|
- Understat enrichment: import `match_fpl_to_understat` from `fpl_cli/api/understat`
|
|
47
|
+
- Draft-to-main player matching: import `match_draft_to_main` from `fpl_cli/api/fpl_draft`, never join on `(web_name, team_id)` — `code` is the stable key and a mid-season rename breaks the name join
|
|
47
48
|
|
|
48
49
|
### API Method Naming
|
|
49
50
|
- Use `get_` prefix for all data-fetching methods
|
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
+
## [2.3.1] - 2026-08-26
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- keep draft players matched to the main game when FPL renames them (#171)
|
|
9
|
+
- judge a replayed gameweek's blanks by the clubs players had then (#173)
|
|
10
|
+
- keep the club and name a replayed gameweek already recorded (#175)
|
|
11
|
+
|
|
12
|
+
## [2.3.0] - 2026-08-26
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- catch a classic entry ID reissued to another manager over the summer (#137)
|
|
17
|
+
- restore xG enrichment for players who moved club mid-season (#151)
|
|
18
|
+
- put the JSON error envelope on stdout alongside the success one (#153)
|
|
19
|
+
- stop LLM narratives putting players at clubs they left (#152)
|
|
20
|
+
- catch Core-Insights gameweek files that parse to zero records (#154)
|
|
21
|
+
- scale GK ceilings to sample size and warn on early-season quality (#156)
|
|
22
|
+
- rate teams from GW1 results instead of waiting for GW2 (#155)
|
|
23
|
+
- keep `--format json` stdout parseable on stats and price-history (#157)
|
|
24
|
+
- stop league-history rows claiming facts the gameweek never had (#158)
|
|
25
|
+
- report every `--format json` failure as an envelope on stdout (#159)
|
|
26
|
+
- credit every tied manager on gameweek win/loss streaks (#166)
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
- capture the per-gameweek world rank in the league-history ledger (#161)
|
|
31
|
+
- track fines across the season instead of only the current gameweek (#165)
|
|
32
|
+
- report how many times each streak has happened this season (#167)
|
|
33
|
+
|
|
4
34
|
## [2.2.0] - 2026-08-25
|
|
5
35
|
|
|
6
36
|
### Bug Fixes
|
|
@@ -44,6 +44,7 @@ For a complete inventory of CLI commands, analysis agents, and skills with JSON
|
|
|
44
44
|
- LLM access is not a client attribute: resolve it per role with `get_llm_provider(role, settings)` from `fpl_cli.api.providers` (see `ScoutAgent.research_provider`)
|
|
45
45
|
- Position map: import `POSITION_MAP` from `fpl_cli/models/player.py`, never redefine locally
|
|
46
46
|
- Understat enrichment: import `match_fpl_to_understat` from `fpl_cli/api/understat`
|
|
47
|
+
- Draft-to-main player matching: import `match_draft_to_main` from `fpl_cli/api/fpl_draft`, never join on `(web_name, team_id)` — `code` is the stable key and a mid-season rename breaks the name join
|
|
47
48
|
|
|
48
49
|
### API Method Naming
|
|
49
50
|
- Use `get_` prefix for all data-fetching methods
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fplkit
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2
|
|
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
|
|
@@ -382,7 +382,7 @@ Services live in `fpl_cli/services/` and provide the computation layer between a
|
|
|
382
382
|
| `player_prior` | Bayesian early-season confidence (GW1-10 shrinkage); threads `PlayerProfile.reliability` (historical availability rate) through `PlayerPrior` to agents |
|
|
383
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()` |
|
|
384
384
|
| `matchup` | Fixture matchup scoring (0-10), 3-GW recency-weighted |
|
|
385
|
-
| `fixture_predictions` | BGW/DGW predictions from YAML + live detection |
|
|
385
|
+
| `fixture_predictions` | BGW/DGW predictions from YAML + live detection. `find_blank_gameweeks()` answers "did this club play" from the club a player is at *now*, which is all the bootstrap knows; `resolve_players_with_fixture()` answers it from the gameweek itself, reading the live payload's per-club-fixture `explain` entries, and `had_fixture()` prefers that answer and falls back to the club when the gameweek cannot give one (unstarted, part-played, or a payload whose `explain`s have not populated). Anything replaying a past gameweek needs the latter pair: a player transferred since carries his current club's fixtures, not the ones he actually had (issue #169) |
|
|
386
386
|
| `season_previews` | Hand-curated per-team season intel from `<config dir>/previews/*.yaml`. Per-section decay (`SECTION_DECAY`) ages each kind of claim out at the gameweek something better supersedes it — injuries at GW2, projected XIs at GW7, team strength at GW13 to mirror `team_ratings_prior.BLENDING_CUTOFF_GW`. `coverage()` centralises the usage gate (`full` / `negative_filter_only` / `none`) so the three consuming skills cannot drift apart on the threshold. Deterministic name resolution (`resolve_name`) matches preview prose to `element_code`, reporting ambiguity rather than guessing; `write_resolved_codes` saves via round-trip YAML so hand-written comments survive |
|
|
387
387
|
| `squad_allocator` | ILP squad allocator (PuLP CBC), horizon-aware, chip-aware |
|
|
388
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 |
|
|
@@ -524,7 +524,7 @@ fpl_cli/
|
|
|
524
524
|
│ ├── _banner.py # Startup banner
|
|
525
525
|
│ ├── _plan_grid.py # Fixture grid rendering
|
|
526
526
|
│ ├── _review_*.py # Review command helpers (analysis, classic, draft, summarisation)
|
|
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)
|
|
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, keeps the name/club/position a replayed gameweek already recorded rather than restamping it from today's bootstrap (`_carry_recorded_identity`, reading the earliest recorded line rather than the resolved winner so an already-restamped gameweek is repaired), and returns the notes pack and season fine tally the console, report, prompt and JSON payload all read)
|
|
528
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
529
|
│ ├── league_fines.py # `league-fines`: season fine table read straight off the ledger, no network
|
|
530
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)
|
|
@@ -540,7 +540,7 @@ fpl_cli/
|
|
|
540
540
|
│ ├── contract.py # Runtime tripwires: CSV header checks + empty-output warnings for dataset drift
|
|
541
541
|
│ ├── dataset_fetcher.py # DatasetFetcher (disk cache with ETag/TTL for GitHub CSVs)
|
|
542
542
|
│ ├── fpl.py # FPLClient (main API, caches bootstrap-static)
|
|
543
|
-
│ ├── fpl_draft.py # FPLDraftClient
|
|
543
|
+
│ ├── fpl_draft.py # FPLDraftClient + match_draft_to_main()
|
|
544
544
|
│ ├── understat.py # UnderstatClient + match_fpl_to_understat()
|
|
545
545
|
│ ├── historical_types.py # Shared dataclasses (SeasonHistory, PlayerProfile, GwTrendProfile) + compute_trend/compute_acceleration/compute_reliability
|
|
546
546
|
│ ├── vaastav.py # VaastavClient (historical seasons 2022-25 via DatasetFetcher)
|
|
@@ -567,7 +567,7 @@ fpl_cli/
|
|
|
567
567
|
│ ├── team_ratings.py # TeamRatingsService + Calculator (1-7 scale)
|
|
568
568
|
│ ├── team_ratings_prior.py # Previous-season prior + Bayesian blending (cutoff GW12)
|
|
569
569
|
│ ├── matchup.py # Fixture matchup scoring (0-10)
|
|
570
|
-
│ ├── fixture_predictions.py # BGW/DGW predictions from YAML + live detection
|
|
570
|
+
│ ├── fixture_predictions.py # BGW/DGW predictions from YAML + live detection, plus the point-in-time "did his club have a fixture" read a replay needs
|
|
571
571
|
│ ├── season_previews.py # Per-team season intel: schema, per-section decay, coverage gate, name resolution
|
|
572
572
|
│ ├── squad_allocator.py # ILP squad allocator (PuLP CBC) - score, fixture coefficients, solver. Horizon-aware: horizon=1 uses single-GW scoring (GW_SELECTION_WEIGHTS), horizon>=2 uses ownership-family quality (VALUE_QUALITY_WEIGHTS). Chip-aware: --bench-discount (Free Hit), --bench-boost-gw (Bench Boost per-GW override to 1.0), --sell-prices (WC/FH sell-price budget correction via price_overrides dict)
|
|
573
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
|
|
@@ -639,6 +639,10 @@ week, so a scripted consumer never sees it appear and disappear on a calendar it
|
|
|
639
639
|
|
|
640
640
|
**Streaks:** notable open streaks print under `Streaks:` on console — leaders only, so console stays a highlights view — and in full as a `# League History` section in the saved report. Each is reported as an observed count over its true span (e.g. "3 in the last 11, with 8 not recorded") rather than a bare "in a row" once any gameweek went uncaptured. A streak surfaces once its run reaches the condition's own minimum: 2 gameweeks for weeks on top, gameweek wins, last-place finishes, captain blanks and transfer hits; 3 for waiver hauls and backfires. Bottom-half gameweeks and green-arrow droughts never surface as streaks at all — both restate where the table already shows a manager is, so their run exists to drive the season count's firing rule rather than to be read on its own.
|
|
641
641
|
|
|
642
|
+
**Replayed gameweeks keep who a player was:** a backfill resolves every pick against today's bootstrap, so a player transferred or renamed since would otherwise land on a past gameweek's row wearing his current club and current name. The recorded name, club and position are kept instead, and a code the gameweek already resolved is never dropped just because the live lookup has stopped finding him. Everything the gameweek itself can answer — points, cards, the pick flags, whether his club had a fixture — is re-derived by the replay as normal, so a repair still improves what it can. The reading goes back to the *earliest* row recorded for that gameweek rather than the newest, so a gameweek an older version already restamped is repaired rather than having the restamp carried forward. A gameweek with nothing recorded yet — a first capture, or a coarse tier being upgraded, which stores no squad — has nothing to keep, and there the current clubs stand.
|
|
643
|
+
|
|
644
|
+
**Blank gameweeks:** a captain who scored nothing because his club had no fixture is never counted as a captain blank — the missing return was a structural impossibility, not a choice that failed. Which clubs those were is read off the gameweek being captured rather than off the squad as it stands today: FPL's live data marks every player whose club had a fixture that week, so replaying an earlier gameweek records the fixture a player actually had at the time rather than the one his current club had. That matters for anyone transferred between clubs since — without it, a replay erases a real blank, or invents one the player could never have had, and the captain-blank streak and season count move with it. A gameweek still in play cannot answer, because those marks are written per fixture as it finishes; there the clubs in the current bootstrap answer instead, which for a gameweek being played now are the same thing.
|
|
645
|
+
|
|
642
646
|
**Season counts:** alongside each streak's currently-open run, every condition keeps a season occurrence total that survives resets, so "their sixth gameweek win of the season" is a stated fact, not an inference. Each condition has its own rule for when a total is worth saying out loud — a rare, discrete event affords a generous rule, while a standing table position half the league increments every week needs a strict one:
|
|
643
647
|
|
|
644
648
|
| Count | Surfaces in an ordinary gameweek when | Others shown alongside |
|
|
@@ -790,6 +794,7 @@ it shares the channel: `synthesis_provider_unavailable`.
|
|
|
790
794
|
| `league_history_backfill_manager_unreachable` | One manager's history could not be fetched; their gameweeks stay unknown and are re-attempted next run |
|
|
791
795
|
| `league_history_backfill_replay_failed` | One gameweek could not be replayed in detail; the others are unaffected |
|
|
792
796
|
| `league_history_backfill_write_failed` | A backfilled gameweek could not be written; the rest of the backfill continues |
|
|
797
|
+
| `league_history_identity_carried` | A finished gameweek kept the name, club or position it already had recorded for one or more players rather than the ones today's bootstrap gives them, or restored a player reference this capture had lost. Raised by a re-capture of a finished gameweek as well as by a replay; any one of the four on its own raises it |
|
|
793
798
|
| `synthesis_provider_unavailable` | `--summarise` was asked for but the synthesis provider had no usable key; everything else in the recap, the capture included, ran normally |
|
|
794
799
|
|
|
795
800
|
None of these change the exit code — `league-recap` exits 0 whenever the recap itself
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 3,
|
|
21
|
+
__version__ = version = '2.3.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 3, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -12,13 +12,13 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from fpl_cli.models.player import Player
|
|
13
13
|
|
|
14
14
|
from fpl_cli.api.fpl import FPLClient
|
|
15
|
+
from fpl_cli.api.fpl_draft import match_draft_to_main
|
|
15
16
|
from fpl_cli.api.understat import UnderstatClient, match_fpl_to_understat
|
|
16
17
|
from fpl_cli.constants import MIN_MINUTES_FOR_PER90
|
|
17
18
|
from fpl_cli.models.types import EnrichedPlayer
|
|
18
19
|
|
|
19
20
|
# Re-export: canonical location is now fpl_cli.services.scoring
|
|
20
21
|
from fpl_cli.services.scoring import build_understat_by_player_id as build_understat_by_player_id
|
|
21
|
-
from fpl_cli.utils.text import strip_diacritics
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def enrich_player(
|
|
@@ -145,13 +145,13 @@ async def get_draft_squad_players(
|
|
|
145
145
|
raise
|
|
146
146
|
|
|
147
147
|
pick_ids = [p["element"] for p in picks_data.get("picks", [])]
|
|
148
|
-
|
|
148
|
+
matched = match_draft_to_main(draft_bootstrap.get("elements", []), main_players)
|
|
149
149
|
squad: list[Player] = []
|
|
150
150
|
for dpid in pick_ids:
|
|
151
151
|
dp = draft_players.get(dpid)
|
|
152
152
|
if not dp:
|
|
153
153
|
continue
|
|
154
|
-
match =
|
|
154
|
+
match = matched.get(dpid)
|
|
155
155
|
if match:
|
|
156
156
|
squad.append(match)
|
|
157
157
|
elif log:
|
|
@@ -191,15 +191,12 @@ async def get_draft_ownership_mapping(
|
|
|
191
191
|
draft_league_id, draft_bootstrap, league_details,
|
|
192
192
|
)
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
for
|
|
194
|
+
main_to_draft_id = {
|
|
195
|
+
main_player.id: draft_id
|
|
196
|
+
for draft_id, main_player in match_draft_to_main(
|
|
197
|
+
draft_bootstrap.get("elements", []), main_players,
|
|
198
|
+
).items()
|
|
197
199
|
}
|
|
198
|
-
main_to_draft_id: dict[int, int] = {}
|
|
199
|
-
for p in main_players:
|
|
200
|
-
draft_id = draft_by_name_team.get((p.web_name, p.team_id))
|
|
201
|
-
if draft_id:
|
|
202
|
-
main_to_draft_id[p.id] = draft_id
|
|
203
200
|
|
|
204
201
|
return draft_owned, draft_entries, main_to_draft_id
|
|
205
202
|
|
|
@@ -4,11 +4,13 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import logging
|
|
7
|
+
from collections.abc import Sequence
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
import httpx
|
|
10
11
|
|
|
11
|
-
from fpl_cli.models.player import POSITION_MAP
|
|
12
|
+
from fpl_cli.models.player import POSITION_MAP, Player
|
|
13
|
+
from fpl_cli.utils.text import strip_diacritics
|
|
12
14
|
|
|
13
15
|
logger = logging.getLogger(__name__)
|
|
14
16
|
|
|
@@ -396,3 +398,40 @@ class FPLDraftClient:
|
|
|
396
398
|
"expected_goals": float(player_data.get("expected_goals", 0)),
|
|
397
399
|
"expected_assists": float(player_data.get("expected_assists", 0)),
|
|
398
400
|
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def match_draft_to_main(
|
|
404
|
+
draft_elements: Sequence[dict[str, Any]],
|
|
405
|
+
main_players: Sequence[Player],
|
|
406
|
+
) -> dict[int, Player]:
|
|
407
|
+
"""Map draft element IDs onto the main-game player they represent.
|
|
408
|
+
|
|
409
|
+
The join is `code`, the stable cross-season identifier both bootstraps
|
|
410
|
+
carry on every element. `web_name` is not stable: the main game renamed
|
|
411
|
+
Savinho to Sávio mid-season and the draft game kept the old spelling, so
|
|
412
|
+
a `(web_name, team)` join dropped a player the two APIs agreed on in
|
|
413
|
+
every other field (#168). Names survive only as a fallback for an element
|
|
414
|
+
with no code, folded for diacritics and paired with the team so two
|
|
415
|
+
players sharing a surname stay apart.
|
|
416
|
+
|
|
417
|
+
Returns only the draft IDs that matched; a missing key is a genuine miss.
|
|
418
|
+
"""
|
|
419
|
+
by_code = {p.code: p for p in main_players if p.code}
|
|
420
|
+
by_name_team = {
|
|
421
|
+
(strip_diacritics(p.web_name).lower(), p.team_id): p for p in main_players
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
matched: dict[int, Player] = {}
|
|
425
|
+
for dp in draft_elements:
|
|
426
|
+
draft_id = dp.get("id")
|
|
427
|
+
if draft_id is None:
|
|
428
|
+
continue
|
|
429
|
+
code = dp.get("code")
|
|
430
|
+
main_player = by_code.get(code) if code else None
|
|
431
|
+
if main_player is None:
|
|
432
|
+
name, team = dp.get("web_name"), dp.get("team")
|
|
433
|
+
if name and team is not None:
|
|
434
|
+
main_player = by_name_team.get((strip_diacritics(name).lower(), team))
|
|
435
|
+
if main_player is not None:
|
|
436
|
+
matched[draft_id] = main_player
|
|
437
|
+
return matched
|
|
@@ -59,6 +59,7 @@ from fpl_cli.cli._league_recap_types import (
|
|
|
59
59
|
RecapStandingsEntry,
|
|
60
60
|
RecapTransfer,
|
|
61
61
|
)
|
|
62
|
+
from fpl_cli.services.fixture_predictions import had_fixture
|
|
62
63
|
|
|
63
64
|
logger = logging.getLogger(__name__)
|
|
64
65
|
|
|
@@ -153,6 +154,7 @@ async def collect_classic_recap_data(
|
|
|
153
154
|
*,
|
|
154
155
|
is_live_gw: bool = True,
|
|
155
156
|
bgw_team_ids: frozenset[int] = frozenset(),
|
|
157
|
+
players_with_fixture: frozenset[int] | None = None,
|
|
156
158
|
) -> LeagueRecapData:
|
|
157
159
|
"""Fetch all managers' picks and compute league-wide recap data.
|
|
158
160
|
|
|
@@ -163,7 +165,11 @@ async def collect_classic_recap_data(
|
|
|
163
165
|
|
|
164
166
|
`bgw_team_ids` is the set of clubs with no fixture this gameweek, so a
|
|
165
167
|
recorded squad can tell a player who blanked apart from one who never
|
|
166
|
-
kicked a ball (R20).
|
|
168
|
+
kicked a ball (R20). `players_with_fixture` answers the same question
|
|
169
|
+
from the gameweek's own live data and takes precedence where it can, so a
|
|
170
|
+
replay does not read the blank off a club the player has since moved to
|
|
171
|
+
(issue #169); `resolve_players_with_fixture` (`services/fixture_predictions.py`)
|
|
172
|
+
builds it.
|
|
167
173
|
|
|
168
174
|
Returns a LeagueRecapData dict ready for template rendering.
|
|
169
175
|
"""
|
|
@@ -179,7 +185,7 @@ async def collect_classic_recap_data(
|
|
|
179
185
|
managers = await _fetch_all_manager_data(
|
|
180
186
|
client, standings, gw, live_stats, player_map, teams,
|
|
181
187
|
use_net_points=use_net_points, is_live_gw=is_live_gw,
|
|
182
|
-
bgw_team_ids=bgw_team_ids,
|
|
188
|
+
bgw_team_ids=bgw_team_ids, players_with_fixture=players_with_fixture,
|
|
183
189
|
)
|
|
184
190
|
|
|
185
191
|
league_rows = [
|
|
@@ -263,6 +269,7 @@ async def _fetch_all_manager_data(
|
|
|
263
269
|
use_net_points: bool = False,
|
|
264
270
|
is_live_gw: bool = True,
|
|
265
271
|
bgw_team_ids: frozenset[int] = frozenset(),
|
|
272
|
+
players_with_fixture: frozenset[int] | None = None,
|
|
266
273
|
) -> list[RecapManagerEntry]:
|
|
267
274
|
"""Fetch picks for every manager in the league, extract recap data.
|
|
268
275
|
|
|
@@ -349,7 +356,11 @@ async def _fetch_all_manager_data(
|
|
|
349
356
|
auto_sub_out=player.id in auto_sub_out_ids,
|
|
350
357
|
red_cards=red_cards,
|
|
351
358
|
unmatched=False,
|
|
352
|
-
had_fixture=
|
|
359
|
+
had_fixture=had_fixture(
|
|
360
|
+
player.id, player.team_id,
|
|
361
|
+
players_with_fixture=players_with_fixture,
|
|
362
|
+
bgw_team_ids=bgw_team_ids,
|
|
363
|
+
),
|
|
353
364
|
))
|
|
354
365
|
|
|
355
366
|
if pick.get("is_captain"):
|
|
@@ -1420,6 +1431,7 @@ async def collect_draft_recap_data(
|
|
|
1420
1431
|
*,
|
|
1421
1432
|
is_live_gw: bool = True,
|
|
1422
1433
|
bgw_team_ids: frozenset[int] = frozenset(),
|
|
1434
|
+
players_with_fixture: frozenset[int] | None = None,
|
|
1423
1435
|
) -> LeagueRecapData:
|
|
1424
1436
|
"""Fetch all managers' draft picks and compute league-wide recap data.
|
|
1425
1437
|
|
|
@@ -1432,7 +1444,7 @@ async def collect_draft_recap_data(
|
|
|
1432
1444
|
capture; a replayed gameweek leaves it unset (R10) until a ledger exists
|
|
1433
1445
|
to sum it from (U6, not built yet).
|
|
1434
1446
|
"""
|
|
1435
|
-
from fpl_cli.api.fpl_draft import FPLDraftClient
|
|
1447
|
+
from fpl_cli.api.fpl_draft import FPLDraftClient, match_draft_to_main
|
|
1436
1448
|
from fpl_cli.models.player import POSITION_MAP
|
|
1437
1449
|
|
|
1438
1450
|
draft_league_id = settings.get("fpl", {}).get("draft_league_id")
|
|
@@ -1449,20 +1461,20 @@ async def collect_draft_recap_data(
|
|
|
1449
1461
|
draft_elements = draft_bootstrap.get("elements", [])
|
|
1450
1462
|
draft_player_map = {p["id"]: p for p in draft_elements}
|
|
1451
1463
|
|
|
1452
|
-
|
|
1453
|
-
draft_to_main_id
|
|
1464
|
+
matched_main_players = match_draft_to_main(draft_elements, players)
|
|
1465
|
+
draft_to_main_id = {
|
|
1466
|
+
draft_id: main_player.id
|
|
1467
|
+
for draft_id, main_player in matched_main_players.items()
|
|
1468
|
+
}
|
|
1454
1469
|
# The stable cross-season code, resolved through the same match. A
|
|
1455
1470
|
# draft id with no entry here is exactly the unmatched case, so the
|
|
1456
1471
|
# recorded squad carries a null code alongside its unmatched marker
|
|
1457
1472
|
# rather than a seasonal id that means nothing next year (R6).
|
|
1458
|
-
draft_to_main_code
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
main_player
|
|
1462
|
-
|
|
1463
|
-
draft_to_main_id[dp["id"]] = main_player.id
|
|
1464
|
-
if main_player.code:
|
|
1465
|
-
draft_to_main_code[dp["id"]] = main_player.code
|
|
1473
|
+
draft_to_main_code = {
|
|
1474
|
+
draft_id: main_player.code
|
|
1475
|
+
for draft_id, main_player in matched_main_players.items()
|
|
1476
|
+
if main_player.code
|
|
1477
|
+
}
|
|
1466
1478
|
|
|
1467
1479
|
# Fetch all transactions for the league, filter to this GW
|
|
1468
1480
|
txn_response = await draft_client.get_league_transactions(draft_league_id)
|
|
@@ -1543,7 +1555,11 @@ async def collect_draft_recap_data(
|
|
|
1543
1555
|
auto_sub_out=draft_elem_id in auto_sub_out_ids,
|
|
1544
1556
|
red_cards=red_cards,
|
|
1545
1557
|
unmatched=unmatched,
|
|
1546
|
-
had_fixture=
|
|
1558
|
+
had_fixture=had_fixture(
|
|
1559
|
+
main_id, draft_player.get("team"),
|
|
1560
|
+
players_with_fixture=players_with_fixture,
|
|
1561
|
+
bgw_team_ids=bgw_team_ids,
|
|
1562
|
+
),
|
|
1547
1563
|
))
|
|
1548
1564
|
|
|
1549
1565
|
# Build auto-sub descriptions
|