Small findings from 2026-09-13, each verified on the live page or in code #34
Labels
No labels
architecture
cleanup
dependencies
enhancement
needs-info
needs-triage
performance
priority: high
priority: medium
ready-for-agent
ready-for-human
reliability
security
testing
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
solvreven/FPL#34
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?
/api/autocompletecallsautocomplete(cands, locks)with the default budget). For entry 6261 that is 100.0 against 99.9 today.bestSwapFromcounts clubs fromSQUAD.picks, not from the squad after the first swap. (Its budget was fixed inad50fc2.).header-actions, right edge 580 px; the page scrolls sideways).Status of the four items on
feat/captaincy-board:/api/squadmoney model (FPL ledger sale prices, planned sales and buys, a bank that can be unknown), and it needs a decision on what "Complete my squad" does when the bank is unknown or the locks differ from the saved squad. Left for you.bestSwapFromnow counts the squad after the first move. There's one test per direction, and both were red before the fix.5f5a952. The page still overflowed by 10px through.gw-nav, the gameweek pager, and #41 makes that row wrap.test_header_narrow[400]is green again.🤖 Generated with Claude Code
Item 1 is done on
feat/captaincy-board(caf5e6c). The two questions you left open were decided on 19 Sep:_squad_payloadasked for the held squad, not from a second computation beside it (_autocomplete_moneyinmain.py)./api/autocompleteanswers 409 and names the player and the reason. It is never completed with 100.0, which is the number that caused this.NEW_TEAM.The response now carries
budget(tenths,source,detail), and each card carriescostbesideprice. The page prints the budget above the squad, and "sells £x" where a card's two prices differ.For entry 6261 on 19 Sep that is 99.9 (bank 2.8 + 97.1 selling value). It used to be 100.0 with the kept players at 97.8, their prices that day.
Tests are in
tests/web/test_autocomplete_budget.py. Each was proved red against the cut it names: the constant budget, held players at today's price, a planned sale taken back at today's price, the refusal replaced by 100.0, and the page dropping the budget line or the "sells" note. The full suite is green in the live checkout: 2092 passed, none skipped.With 2 and 3 fixed in #41 and 4 recorded, that is all four items. Close it when you're happy with the decisions above.
🤖 Generated with Claude Code
The three items I picked up from the setup audit are on
feat/captaincy-boardand pushed. The audit looked at what is RUNNING — the live/api/status, the chores ledger, the scheduled tasks, the disks, the CI workflow — rather than at what the code says, which is how these three surfaced at all.1. The data that cannot be re-acquired had no copy anywhere (
458db75)data/pitheld 17 point-in-time snapshots, 67 MB. Gitignored, as aresquad.json,overrides.json,analysis/, the Transfermarkt records that cost credits, and the newest Norsk Tipping runs. The code has this remote; that data had nothing. Every guard in the repo asks whether a value can be trusted; none asked whether it still exists.scripts/backup_state.py --to <dir>copies everything underdata/thatgit ls-filesdoes not name, minusdata/cacheanddata/logs.prune_nt_runsdeletes raw events by design, and a mirror would carry that into the only other copy.data/logs/backup.log, because a scheduled task has no console and a refusal with nowhere to print it is a backup that silently stopped.scripts/install_backup_task.ps1 -To <dir>registers it daily at 03:30 and at logon. First run: 323 files, 150.9 MB.2. A watch called waiting a failure (
aabd2dc)nt_watchhad 15 consecutive FAILED entries and had backed off to its six-hour ceiling. Nothing was broken — Norsk Tipping opens a round days before its deadline, sont_resolve_market.pywriting nothing is the ordinary state. It exited 1, and the status line read exactly as a real breakage would, the day before GW6's odds were due.It now exits
nt_market.NOT_ON_THE_COUPON, a constant both sides import so the contract cannot become two literals. The watch recordsNOT_OPENED, keeps its normal cadence, reports no error, and the planner line says the round "is not on the coupon yet". A failing fetch is still a failure whatever it exits with: the loop stops at the first failing step, so a fetch exiting 2 never has a resolve code beside it. Each half has its own cut intest_nt_watch.py.3. A warning that fired on prose (
aabd2dc)The header announced "server is running f5fc70d3 while projections were built from 71f1e200" for days, with the cache current and both projection digests equal. It compared
_git_sha, the byte digest of the import closure, so editing a docstring moved it. It now reads the paircache_stalenessreads —projection_digest, which covers only what can change a number — and falls back to the bytes only when the cache carries no projection digest. Both digests' own docstrings already said a warning nobody can act on is one people stop reading.Found, not fixed
Recorded in
wiki/notes/audit-2026-09-20-setup.mdrather than left in scrollback:captaincy.require_measured_rateshas no production caller —rank_captaincyrepeats the check inline. Same shape as the one you fixed for the optimiser's twin in #32: the protection is live, the named function is a decoy, and strengthening it would change nothing.model/finishing.pycarries its own gate result). The list still includescalibration_curveanddisagreement_tally— the scoring #11 is about.backend/model/finishing.pyrecords split-half persistence of -0.19 andalphafitted to infinity, i.e. pure xG. The Over/under work re-derived the same conclusion on 19 Sep without citing it; the two halves now point at each other.Not checked: performance and memory over time, the LAN exposure beyond the write-route refusal, whether Understat's or FPL's formats have moved, model quality (that has its own harnesses).
Full suite in the live checkout at
7b317df: 2124 passed, 0 skipped — worth stating as the count, since a worktree run skips 46 and a CI run skips 434.🤖 Generated with Claude Code
Correction to the list above: finding 4 was wrong, and fixing it properly meant doing the opposite of what I recommended.
f11109a.I reported
captaincy.require_measured_ratesas "a named guard nobody calls — the protection is live and the named function is a decoy". The implied fix is to wire it up. Reading it says otherwise.That function refused to name a captain if any XI member carried a rate prior.
1c4a2dfdeliberately replaced it with exclusion: the prior-rated player leaves the candidate pool, the card still renders, and it refuses only below two real candidates — the blast-radius lesson ("ask what else a refusal takes with it"). The function was left behind with its docstring still stating the old absolute rule.So the module held two statements of one policy, and the one a reader meets first was the one the code had stopped applying. Wiring it up would have reinstated a refusal that was narrowed on purpose, and let one unmeasured squad member blank the armband again.
Its test asserted it was "exported for the solver path". It was not — the solver has
optimize.require_measured_rates_in_xi, and that one is called atoptimize.py:316. Nothing imported this one.Deleted. The live rule stays pinned by
test_an_unmeasured_player_is_excluded_not_blocking; the new test pins the absence, since the risk is a third definition reappearing with nothing calling it. Both halves proved RED — reinstating a guard incaptaincy.py, and cutting the solver's caller.Worth stating because the same shape is in #32's family: an uncalled guard is not automatically an unwired guard. It can be a rule that was retired and left on the page.
🤖 Generated with Claude Code