Spaces:
Running
Running
Commit History
3 days ago
-
Auto: refresh team ratings (market prior gw10) Running
5d31f42
5 days ago
-
Auto: refresh team ratings (market prior gw10)
d071bac
6 days ago
-
updates
64d231b
7 days ago
-
Auto: refresh team ratings (market prior gw9)
47a71e2
9 days ago
-
Blend defensive actions as a share of the team's
A centre-back's raw CBIT rate carries how much defending his team had to do, so a season-weighted blend held movers to their old club's load: António Silva's four Benfica seasons put him at 7.8 CBIT p90 against 10.5 at Bournemouth. build_player_stats.py now records team defensive totals over each player's matches; baselines blend each season's share of team CBIT/CBITR and turn it back into a rate at his latest team. Next-season CBIT MAE 0.720 -> 0.599 (bias -0.27 -> +0.05), 0.906 -> 0.601 where the team's load moved >20%. GW10 projection vs FPL defensive contributions: correlation DEF 0.812 -> 0.814, MID 0.800 -> 0.821, FWD 0.739 -> 0.775. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>eca2ded -
Auto: refresh team ratings (market prior gw9)
4ade562
11 days ago
-
Weight penalty records from previous clubs at a quarter
A taker score pooled every penalty a player took at any club, so a new signing's record competed equally with his new club's incumbents: Tzolis held 57% of Arsenal's GW10 penalties off thirteen taken for Club Brugge. Records are now split by club, and penalties at previous clubs count at OTHER_CLUB_WEIGHT = 0.25, fitted by predicting who took each of 5,092 penalties since July 2024 from the record before it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>58037a0 -
Fix season blend, FPL assist rate and bonus totals in player EV
Baselines: fixed season weights [1, .33, .11, .036] ignored minutes and always divided by the full weight sum. A 685-minute hot start outweighed a full season 3:1 (Barry 0.57 npxG, above Haaland in GW10), and players with one or two seasons on record got 68%/90% of their own rates. Now minutes x recency (0.7) with a 900-minute role prior; backtested on 1017 player-seasons npxG MAE 0.0458 -> 0.0391, xA 0.0317 -> 0.0260, bias -0.022 -> -0.003. baseline_xMins divides by the weights it used for the same reason. Assists: FPL awards 0.93 assists per goal (rebounds, deflections, pens won) against the 0.67 a team's xA comes to, so every creator was under-projected ~30%. The assist pot is now the team's goals at FPL's measured rate. Bonus: positional rates measured on 180+ minute players handed out 6.7-7.3 a match against 6.35 paid; each match is now scaled to the measured total. Single-player recalcs reuse the full run's scale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>6d1a467 -
Auto: refresh team ratings (market prior gw9)
b7a8379
13 days ago
-
Auto: refresh team ratings (market prior gw9)
6b5028a
14 days ago
-
updates
a41486c
15 days ago
-
Auto: refresh team ratings (market prior gw8)
07f5db7
17 days ago
-
Auto: refresh team ratings (market prior gw8)
1fcb53d
18 days ago
-
Warn after every Solio read about players with no baseline
An unmatched name is zero-filled and his share handed to teammates, with no error anywhere - Amad going unmatched put Mbeumo above Haaland in GW8. run_auto now lists every player on 10+ Solio xMins who joins to no baseline, with a same-surname rename.json suggestion, and toasts when a new one appears. The join moves out of main.py into baseline_join.py so the check runs the exact code production does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>b42d1b5 -
Fix stale name mappings that left starters without baselines
Amad, Kadıoğlu, António Silva, Emersonn, Chema Andrés, Sávio, Philogene, Tsimikas and Yalcouyé joined to no baseline row, so they projected 0 xG/xA and their teammates' closed shares were inflated to cover them - Mbeumo came out at 0.62 xG and above Haaland in GW8. Several old entries mapped FPL names to spellings the stats file no longer uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>abf1bf4 -
Stop multiplying Solio xMins by FPL chance of playing
Solio already moves its minutes for injuries, so the FPL chance blend counted every doubt twice and dropped minutes nobody else was handed. Manual availability overrides still apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>f3ceeb6 -
Fix inflated defcon/attacking shares; refresh xMins every 3 hours
Projections: - closed_shares filled missing squad minutes by inflating everyone's per-90 rate. Spurs/Man Utd sat near 780 of 990 minutes in GW8, so their CBITR came out 1.64x and pushed Tonali past the defcon threshold. Unlisted minutes now keep their share at the squad's own rate. - Team CBIT/CBITR totals include the keeper but keeper baselines are 0; hand out only the outfield share (GK 2.1% CBIT, 9.0% CBITR, PL 2022-). - Baselines used 'Blocked shots' (own shots blocked) for CBIT and the BPS floor instead of defensive 'Blocks'. Automation: - run_auto.py --xmins-only: Solio -> Supabase in ~1 min, for a 3-hourly task. A shared lock keeps it off the Chrome profile while the full run has it. - main.py re-reads admin_xmins every 15 min and recalculates on change, instead of only at startup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>c91d10c
19 days ago
-
Auto: refresh team ratings (market prior gw8)
1868e7d
20 days ago
-
updates
835eb8f
21 days ago
-
Auto: refresh team ratings (market prior gw7)
9294efc
23 days ago
-
Auto: refresh team ratings (market prior gw7)
4f009af
24 days ago
-
updates
b115f10
25 days ago
-
Auto: refresh team ratings (market prior gw7)
a60e729
27 days ago
-
Auto: refresh team ratings (market prior gw7)
d7dea49
29 days ago
-
Auto: refresh team ratings (market prior gw6)
80d2875
-
Auto: refresh team ratings (market prior gw6)
a844571
-
Auto: refresh team ratings (market prior gw6)
6758861
-
Auto: refresh team ratings (market prior gw6)
f50e397
-
Auto: refresh team ratings (market prior gw6)
94c66b2
-
Auto: refresh team ratings (market prior gw6)
9b2e752
-
Auto: refresh team ratings (market prior gw5)
1d05f5b
-
updates
efe108e
-
Auto: refresh team ratings (market prior gw5)
c93b0ff
-
updates
1fc7304 -
Bonus from positional base rates, tilted gently by projected BPS
Bonus was projected BPS / 29.4. That handed out about 10 bonus points per match when only 6 exist, leaned hardest on steady-BPS positions - defenders projected 0.49 bonus per 90 against 0.225 actually awarded this season, midfielders 0.48 against 0.34 - and keepers were hard-coded to 0 against 0.25. Bonus is too noisy per player to project from first principles: between defenders the spread of bonus per 90 is larger than its mean, and the top 10% of players take about half of it. So no match simulation. Instead: bonus = position's base rate per 90 x minutes / 90 x (his projected BPS per 90 / position's average) ** 0.5 Base rates are what each position actually earns, from FPL's own history: last season (per-player element-summary, cached and re-fetched only at rollover) pooled with this season by minutes, about 650,000 minutes in all - GK 0.218, DEF 0.246, MID 0.328, FWD 0.626. The square-root tilt keeps a player's own profile a gentle adjustment: twice his position's av…53aec5e -
Penalties from the match data, keyed on the Opta code, as their own pot
Penalty shares were hand-set in the DB keyed on FPL element id, which FPL renumbers every season. Last season's shares had landed on whoever inherited the numbers: Lewis Hall, a Newcastle full-back, was sixth on the whole board on 0.71 pts/match of penalties he does not take, likewise Jacquet, Dewsbury-Hall, Xhaka and O'Reilly - while Bruno Fernandes, Palmer and Haaland projected none. Separately, a taker's penalty xG was 60% inside his blended xG share and then counted again as constant per-position penalty points. build_penalty_takers.py (new cron step, after the match CSVs) reads every penalty in data/ - taker, team, outcome - across all leagues, so a signing's record at his old club counts. Per player: penalties taken over penalties his team won while he was on the pitch, recency-weighted, shrunk toward zero with a 2-penalty prior. Keyed on the Opta id, which Fotmob carries as optaId and FPL as code: 40 of 40 in a sample match join, no names involved, and it is stable across seaso…ebb9657 -
Rebuild baselines with the positional overrides cleared
positional_shares_overrides.json reset to {} (26 hand-set role mixes, backed up locally; the file is not tracked). Every player's role now comes from the automatic forecast. Only the 17 previously overridden players with baselines move, and only slightly - Mbeumo xG p90 0.641 -> 0.614, Enzo 0.279 -> 0.272. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>bf946d8 -
Learn position effects league-wide; blend seasons by minutes played
A season's per-position Fotmob rows were blended by a forecast role share rather than by the minutes actually played, so a tiny sample could carry most of the weight - Gvardiol's 181 minutes in one role took 74% of his 2025-26 blend and doubled his xG p90; a 2-minute cameo of Víctor Muñoz's took 94% - while large samples could be all but discarded (Philogene: 800 minutes weighted at 1%). Across 712 multi-position seasons, 172 had xG p90 moved more than 20% from what the player produced. The role idea is kept, but the position effect no longer comes from each player's own few minutes: estimate_position_effects rate = ability(player-season) x effect(position), fit by Poisson ML over every position row. The per-player-season ability cancels league, team and form, so effects come from players compared with themselves across roles. Comes out football-shaped: …d255105 -
Allocate each team's projection across its squad exactly once
Shares were a player's per-90 rate over his team's historical per-90 total, measured independently, so nothing made a squad add up to its team. Weighted by projected minutes at GW5, Newcastle's players were allotted 67% of Newcastle's goals, Spurs 75%, the league 88% on average; Ipswich's players were allotted 133% of Ipswich's assists. Much of it is transfers - a signing's rate earned at his old club, divided by his new club's total. closed_shares() now builds each gameweek's shares against the squad itself: every player's rate weighted by the minutes he is projected for in that match. Each team's goals (less own goals, 3.66% of 6,700 PL goals in data/), assists, CBIT, CBITR and cards are handed out exactly once. Checked across all 200 team-gameweeks: allocation minus target is at most 2e-16 for all six stats. Team_xG and friends are no longer in the scoring path. The admin single-player endpoint recalculates one player alone, where a squad of one would take the whole team's output.…1e2855b -
Join baselines accent-insensitively; cap baseline xMins at 90
FPL writes some names without their accents and Fotmob keeps them, so 'Joao Gomes' never met 'João Gomes' in the baseline join, and the miss was zero-filled. 18 players had every baseline stat at zero - xG, xA, CBIT, BPS, not just minutes - including Gvardiol, Kerkez, Šeško, Kovačić, Lavia and Bournemouth's first-choice keeper Petrović. Rows that miss the exact join are now retried on an accent-folded, casefolded name, and only taken when exactly one stats row carries it. Rows that already matched cannot change. Checked against the loader with the retry disabled: exactly those 18 players' projections move, and nobody else's. Neither CSV has two names that collide once folded. Gomes baseline 0 -> 81.2, xG p90 0 -> 0.052 Gvardiol baseline 0 -> 85.0, xG p90 0 -> 0.118 Šeško baseline 0 -> 56.9, xG p90 0 -> 0.397 Baseline xMins is also capped at 90 per match. Fotmob's minutes include stoppage time, so Ben Wilson's 570 minutes over 6 games read 95. Only two baselines move (…3d03f4b -
Stop last season's overrides leaking into this one
Past GW10, 133 players still jumped even once the xMins chain carried Solio's values. Almost all of it was hand-set config from last season, untouched since April and keyed by gameweek number or by nothing at all, so it read as this season's: player_status 324 Suzuki "injured, weeks_out 29" - out until GW29 admin_baselines 436 73 zeroed baseline_xMins: Saka, Isak, Gibbs-White availability 125 plus its local mirror, which the pipeline applies inside GW5-10 - 35 players, one zeroed for GW8-10 admin_xmins 567 keys past the horizon, e.g. Gakpo 88.4 forced at GW11 All reset in production (backed up first), admin_xmins pruned to the GW5-10 the pipeline writes, and the pipeline re-run without the stale multipliers. new_season_prep.py now offers to back up and clear all of these at rollover, since nothing did before. Two engine changes close out the seam: - A sub-30-minute outfielder with no baseline set nothing in the healthy branch,…d15b332 -
Carry xMins overrides into the engine's chain, not just the display
engine.py built its xMins chain from baseline_xMins and never fed an override back into it - the override only replaced the displayed value. So the chain ran on its own underneath Solio's numbers the whole time, and the first gameweek past the override horizon jumped back onto it: Haaland 83 at GW10 -> 71 at GW11 (his baseline 75, decayed 6 times) Enzo 76 -> 81 Botman 71 -> 73 Gibbs-White 83 -> 0 (baseline 0) Seeding the chain from each override makes the next gameweek continue from the last number we actually believed. All four now decay smoothly through GW14. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>68a6964
-
updates
45ca01c -
Snapshot projections before each deadline and score them afterwards
The accuracy loop existed only as two notebooks nobody ran. accuracy_check still had gw = 38 hardcoded from last season and model_accuracy_test read a projections_check.xlsx last written 2026-05-25, so four months of projections had never been scored against what happened. The two halves run on different clocks, which is the actual difficulty. Projections have to be captured before kickoff, because ewmapois_model.csv is overwritten in place and the numbers we were betting on are gone the moment it is rebuilt. Results can only be filled in afterwards, once scrape_luigi has the JSONs. run_auto is on a two-day cycle with no relation to the FPL deadline, so rather than trying to fire exactly before one, every run re-snapshots every gameweek still ahead of its deadline and freezes each gameweek as its deadline passes. A missed run costs how close to the deadline the capture was, never the capture itself. A gameweek already past its deadline with nothing captured is left empty rather than …a621117
-
Score appearance, clean sheets and minutes BPS on Solio's curve
engine.py gated five things on xMins crossing 60 - appearance points, clean sheet points, CS bonus, minutes BPS, and the reported CS figure - and that threshold was applied to a mean. A step function on an expected value is wrong wherever the expectation sits near the step, and ours sit there constantly: - 100 players on the GW5 board had between 0 and 5 expected minutes and collected a full appearance point each, on `xMins > 0`. That alone is 100 phantom points from players Solio says will barely feature. - Across the 40-75 minute rotation band, 84 players were paid a flat 1 where the honest answer runs 1.08 to 1.33. - A defender at 59.9 expected minutes lost his entire clean-sheet expectation; one at 60.1 kept all of it. Over the whole GW5 board the threshold pays 643 appearance points against the curve's 515.5. solio_store already recovered p_start and p_60 from the stored minutes distribution - these are read off the curve, not inferred from the mean - and …f961b9d -
Rebuild match_data.csv and newdata.csv in the cron, by stat title
ewmapois fits on both, and both were built by hand out of match.ipynb and newdata.ipynb - which meant not at all. They were last written 2026-05-25 while the JSONs behind them kept arriving. Porting them surfaced a live data bug. The notebooks read Fotmob's stats by position, and the layout has moved twice since they were written: Top stats gained "Touches in opposition box" and "Yellow cards", shifting the rows inside the block, so the notebook read Big chances missed as accurate passes and yellow cards as corners. From 2025-26 the Defence block renamed "Tackles won" (rendered "8 (67%)") to "Tackles" (a plain int). The notebook split that on "(", which raises on an int, so tackles - and so tackles_attempted, CBIT and CBITR - came out as zero for every match in the last two seasons. CBIT and CBITR are what ewmapois actually uses, 45 references between them. Season means, notebook vs this module: 2020-21 .. 2024-25 identical, every column 2025-26 CBIT 36.7 -> …9232595 -
Count only fixtures a player can feature in
Solio carries a player in every fixture of a gameweek when his club is unsettled - Awoniyi appeared in all ten, nine of them all-zero placeholders - so the raw count read as a ten-fixture gameweek. The zeros never touched the minutes or probability sums (checked: exactly one real fixture per gameweek for every player flagged, so no value was inflated), but anything reading this column as a double-gameweek flag would have been badly misled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T5DtX6CNHDVBnUwX1Ukrhq17ae9c5 -
xMins from Solio's client store, keyed on fplId instead of names
Solio is a Rocicorp Zero app: it replicates a relational dataset over a websocket into IndexedDB rather than serving its projections as JSON. Those rows carry a literal fplId, so there is a real join key and the name matching can go entirely. It had been costing us players. On 2026-09-15 the grid parser dropped seven rows it could not disambiguate - two Martinezes, two Davieses, Silva, Kamara, and one mojibake row - including one player on 89.1 xMins, and the virtualized grid capture also came back a row short at 555/556. Minutes are stored per (element, fixture) as a piecewise-linear survival curve: p0 = P(any minutes), a vertex (vv, vp), p1 = P(90). Expected minutes is the area under it, summed over a gameweek's fixtures, which makes doubles and blanks fall out for free. Checked against every cell Solio's own grid rendered for GW5-GW9: 2770 of 2770 exact. The curve also yields p_start and p_60 directly - the thing appearance points turn on - which the grid never exposed. Two horiz…b167a8e -
Auto: refresh team ratings (market prior gw5)
ea6d0a8
-
Auto: refresh team ratings (market prior gw4)
f393ffa