Add fotmob-match-xg fallback to loadXGData (+ arg-b-nacional mapping)
Inventory audit found that fotmob-match-xg already ships 269 files covering 50 leagues, including the 4 leagues I'd previously marked as "at data ceiling" for FF(xG)/Context/DynXG:
Inventory audit found that fotmob-match-xg already ships 269 files covering 50 leagues, including the 4 leagues I'd previously marked as "at data ceiling" for FF(xG)/Context/DynXG:
data/fotmob-match-xg/argentine-b-nacional-2026.json ✓ data/fotmob-match-xg/argentine-primera-2026.json ✓ data/fotmob-match-xg/brazilian-serie-a-2026.json ✓ data/fotmob-match-xg/austrian-bundesliga-2025-26.json ✓
loadXGData() only read data/match-xg/ (understat wrapped format) with no fallback. Refactored to:
- Extract aggregation into an inline helper parameterised on the field
map, so the same logic drives understat (homeXG / awayXG UPPER) and fotmob (homeXg / awayXg camel) file formats.
- Try data/match-xg/ first (preserves existing Big-5 + other coverage).
- Fall through to data/fotmob-match-xg/ when match-xg has nothing, using
a FOTMOB_FILE_MAP for the 4 league-name renames.
Also added arg-b-nacional -> argentine-b-nacional to the existing FOTMOB_MAP inside checkXgVarianceRegression so XGVar/MatchXG pick up the same file.
Expected coverage lift on the 182 settled bets:
- FF(xG) / Context / DynXG: 162/182 → ~182/182 (+20 for the 4 leagues)
- XGVar / MatchXG: 159-160/182 → ~180/182 (+20)
Not fixing: FS-All at 181/182. The 1-bet gap is Ath Bilbao v Betis where ALL 29 Athletic Bilbao matches in footystats la-liga-2025-26.json have 0 home/away xG, same for Real Betis, Real Sociedad, Atletico Madrid — systematic FootyStats provider gap. FS-All is a FootyStats- only signal by definition; falling through to sofascore would compromise the semantic. Accepting the 0.5% ceiling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>