Accuracy panel has no scoring branch and goes quieter once it has data #11
Labels
No labels
architecture
cleanup
dependencies
performance
priority: high
priority: medium
reliability
security
testing
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
solvreven/FPL#11
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
backend/web/calibration.pydefinesbrier_score,log_loss,calibration_curveanddisagreement_tally, all unit-tested.accuracy_reportcalls none of them.
Below
MIN_RESOLVED(20) it writes a refusal and a reason on each metric. At orabove it, the function does nothing at all — so the metrics keep
value=None,keep the
AWAITING_RESULTSbanner, and theirdetailstring goes empty.The panel gets quieter at the moment it first has enough data to say
something. Ten fixtures are resolved as of 2026-08-27, so this is roughly one
gameweek away from being visible.
Found because the self-dating gate in
tests/web/test_calibration.pycame due:it asserted
resolved_fixtures == 0with the note "a fixture has resolved —update this test", and GW1 resolved it. The gate worked exactly as designed.
test_the_panel_has_no_scoring_branch_yetnow pins this state and is written tofail the moment scoring is wired, so the gap is asserted rather than
rediscovered.
Not a quick fix: scoring needs stored predictions to compare against, which
is what
data/pit/snapshots hold. That is a real build, and it should not bebolted onto an unrelated change.
The scoring helpers are ready, but the prediction/outcome join needs a declared contract. Please specify:
MIN_RESOLVED = 20counted in fixtures, player-fixtures, or another observation unit?These choices materially change the Brier/log-loss sample and cannot be recovered safely from the current placeholder branch.
Answering all five. These are the right questions — the join is where a scoring harness silently grades the wrong thing.
1. The prediction is the latest PIT snapshot before the DEADLINE, not before kickoff.
The deadline is when the squad locks and the decision is made; that is the moment the prediction was acted on. A snapshot taken between the deadline and kickoff has seen team news the decision could not use, and grading against it flatters the model. This is the vaastav
ep_thistrap the snapshots exist to prevent — their historical file carries a field scraped after the gameweek, so any backtest touching it sees the future.data/pit/<stamp>/manifest.jsonalready carriescaptured_atandnext_gw, so the selection is: newest snapshot wherenext_gw == Nandcaptured_at <= deadline(N). There are 14 snapshots; GW1 has seven and GW2 six, so a "latest before deadline" rule is not hypothetical — it picks a different one from "any snapshot for that gameweek".Record WHICH snapshot each score came from in the report. A number that cannot name its input is the thing this repo keeps finding.
2. Canonical fields.
backtest_rpsmetric): predicted = the three-way from the snapshot's solved market/model lambdas; observed =team_h_score/team_a_scorefrom the resolved fixture. Note the snapshot stores our fit AND the market — score ours, and report the market alongside as the benchmark, never blend them.cs_h/cs_a; observed = opponent score == 0 and the fixture finished.start_prob; observed fromevent/{gw}/live/minutes. This already has a harness (backtest_minutes.py, Brier 0.0894 against a 0.1926 base rate) — the panel should read the same definition rather than inventing a second one.p_score_sim, against realised goals.scripts/score_goalscorer.pyalready does this and the chore runner calls it; the panel should consume its output, not re-derive.3. Postponed and doubles.
4. Aggregate to FIXTURE level for the match metrics; keep player-fixture for minutes and goalscorer. They are different claims: "Arsenal 2-1" is one prediction, "Saka starts" is 22 per fixture. Mixing them into one Brier gives the player metrics ~20x the weight of the match ones and the number stops meaning anything.
5.
MIN_RESOLVED = 20in FIXTURES, for the match metrics. At 10 fixtures a gameweek that is two rounds, which is the right order for "enough to say anything". For player-level metrics the floor should be separate and much higher —backtest_minutesruns on 23,884 rows for a reason.One thing to fix while you are in there.
accuracy_reportcurrently writes a refusal on each metric below the floor and does nothing at or above it, so the metrics keepvalue=None, keep the AWAITING_RESULTS banner, and theirdetailgoes empty — the panel gets quieter the moment it has enough data to speak.tests/web/test_calibration.py::test_the_panel_has_no_scoring_branch_yetpins that gap and is written to fail the moment scoring is wired, so it will tell you when this issue is done.