Connect or retire the unused depth-chart refusal mechanism #4
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#4
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?
Finding
backend/web/depth.pydocuments and implements a decision-critical refusal for players whose historical minutes no longer describe the current depth chart.apply_depth_refusal()mutates provenance todepth-chart-changed, andbackend/web/provenance.pyclassifies that source as untrusted.However, repository-wide reference analysis finds no caller of either
apply_depth_refusal()orblockers_for()outside their definitions. No tests exercise the module. The mechanism therefore looks complete in isolation but cannot affect production projections—the exact disconnected-wiring failure described inCLAUDE.md.Related clearly unused definitions found by the same pass:
backend/web/tm_parse.py:232render_incomplete()(superseded by the structuralpage_is_rendered()guard)backend/engine.py:186project_gameweek()(no repository caller; confirm whether it is intended public API before removal)Suggested direction
Acceptance criteria
I can implement this once the intended model behavior is decided. Please confirm:
backend/web/depth.pyand itsdepth-chart-changedprovenance branch be removed?engine.project_gameweek()andtm_parse.render_incomplete()supported external APIs, or may they be removed when repository callers are absent?The first answer determines whether the safe change is wiring or deletion; inferring it risks changing projection policy.
Answering all three, with a measurement rather than a preference.
First, a correction to the premise this issue inherited from CLAUDE.md. That file says
depth.pyis unwired because "FPL's four position buckets cannot express a depth chart — Saliba is rank 2 among Arsenal defenders and Mosquera rank 7". That describes reassigning minutes. This module explicitly refuses to do that, and says so at length in its own docstring: knowing Saliba is out tells you those minutes are available, not who gets them. Its only output is a refusal plus the reason. So the stated blocker does not apply to what the code actually does.1. Keep it — but as a BADGE, not a provenance refusal.
Measured against the live index for GW3,
blockers_forfires on 11 players of 623 (1.8%):The rate is fine; the accuracy is position-dependent, and that is the finding. Calafiori and Hincapié behind Saliba are exactly the case the module was written for — three centre-backs, one out. The six
MIDrows are the coarse-bucket problem in the open: FPL's "MID" spans a defensive midfielder and two wingers, so one absent midfielder refuses three players whose roles it says nothing about.DEFis narrower (CB/FB) and does better, though Livramento at right-back blocked by Burn is still weak.So roughly half the refusals would be wrong, and a refusal is not free — it drops the player from the candidate pool. Wiring it as designed buys one real signal and five false ones.
A badge does not have that asymmetry. "Measured behind Saliba, who is out" is true for every row above, including the weak ones — the reader can judge Livramento themselves — and it costs nothing when wrong. It is also this repo's own rule for prior-derived values: render the row, suppress the number, mark the cell, not remove the row.
Concretely: keep
blockers_for, keepdepth_blocked_byand theDEPTH-CHANGEDbadge, and drop thestart_source = "depth-chart-changed"write. That line is what turns it into a refusal.SOURCEshould then go, and theUSER-override carve-out with it (a badge does not need to yield to an override; it is a statement of fact about where the number came from).Re-open the refusal question if detailed position data ever arrives — but note Understat was probed 2026-07-31 and its
positionis coarse groups (SalibaD S, MosqueraD S, identical), so that is not the source.2. Boundary: after availability, before provenance is consumed. It reads
availability_by_gwfor the blocker, so availability must already be assembled; and it writes a badge that the projection and the UI both read. Inbuild_players_index, immediately afteravailability_by_gwis populated.3. Both may be removed. Neither is a supported external API — there is no external consumer of this repo.
tm_parse.render_incomplete()— delete. It is a blocklist ("tm_spinner", "Loading ...") superseded by the allowlistpage_is_rendered, which is whatscrape_transfermarkt.pyactually calls to setRENDER_INCOMPLETE. CLAUDE.md is explicit that a blocklist only ever catches yesterday's failure; leaving it beside the allowlist that replaced it invites someone to call the wrong one.engine.project_gameweek()— delete. Zero callers acrossbackend/,scripts/andtests/. The live path isproject_all→project_fixture. It also carriesp_assist: float = 0.8as a default, which is exactly the kind of unowned constant this repo keeps finding in dead code.