Books signal non-availability by declining to quote; we read only the price #16
Labels
No labels
architecture
cleanup
dependencies
performance
priority: high
priority: medium
reliability
security
testing
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
solvreven/FPL#16
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?
Found 2026-08-27 from a user observation: Ollie Watkins, widely reported as
sold and expected to leave, was ranked 6th most likely scorer for GW2.
The mapping is correct (book "Ollie Watkins" -> FPL id 55) and the existing
moved-player guard cannot help: it keys on FPL's club, and FPL still lists him
at Aston Villa, status
a, no news. Both we and the books are downstream ofthat.
But the books did say something, by omission:
Three of five books that priced the match declined to quote him. We average the
two that did, label it
n_books: 2, confidence: LOW, and rank him 6th.This is not rare. Across the GW2 run, on fixtures with >=4 books:
The repo already articulates the principle and applies it too narrowly.
_unpriced_but_ourssays: "the omission is not a gap -- it is the book'sopinion. It quoted 14 Liverpool players and left Munoz out, which says they do
not expect him to feature." That reasoning is applied only to players NO book
quoted, never to one a MAJORITY declined.
Suggested fix. Compute
quote_coverage = books_quoting / books_pricing_the _fixtureand carry it per player. It is free -- the data is already banked inevery run. Use it to demote or flag thin-coverage rows, and surface it beside
n_booksso a reader can see the difference between "two books agree" and "twobooks agree and three refused".
Note this is a genuine availability signal that is INDEPENDENT of our
start_prob, which is derived from the 25/26 archive and cannot know about a
transfer either. Related: #9 (gameweek contamination in the same runs).
Implemented in
726c73b(2026-08-28). The suggested fix is in, and themeasured effect was slightly different from what this issue predicted — worth
recording, because the difference is the interesting part.
quote_coverageexists asn_books / n_books_fixture, computed inprops_load(n_books_fixture: len(f["books"])) and read by_will_playinmain.pyatMIN_BOOK_SHARE = 0.80. A SHARE rather than a count, exactly asthis issue argued: every fixture in the GW2 run drew 5 or 6 books, so a raw
count conflates "two books like him" with "the fixture only drew two books".
Measured on run
gw2_20260827T185145Z, all 422 priced players joined to GW1'srealised minutes:
0.80 rather than unanimity, chosen against pre-registered alternatives on the
same data: unanimity cuts 185 players and takes 11 GW1 starters with them,
against 2 at this threshold, and buys only two fewer dead rows in the top 20.
One thing this issue did not anticipate, and it matters. Applying the share
alone PROMOTED Osula to rank 19 — quoted by six of six books, with
status=i,chance_of_playing_next_round=0and "Foot injury - Unknown return date" inFPL's own feed. Full coverage is what a bookmaker leaves standing when it has
not repriced, so the availability signal in
n_booksis stale exactly where itlooks most confident.
So the fix is three witnesses, deliberately kept apart rather than blended:
our archive-derived minutes model, the book coverage share, and FPL's own
availability_by_gw(which was computed inabsence.pyand written to everycached player since injuries were modelled, and which this surface had never
read). They catch different players — the model catches Rashford, the share
catches G.Jesus and Zirkzee, the flag catches Osula.
Live GW2 board, before and after:
The six that remain are all
status=a— available rotation risks, which is ajudgement about what the board is for rather than a data fault.
Closing. #9 (gameweek contamination in the same runs), referenced at the end of
this issue, was also fixed in the same series —
this_round()now filtersbanked prices on the FPL fixture pair rather than the run's name.