Move any input and both totals recompute. Every number below feeds a real FanGraphs WAR formula, so you can see exactly what a home run, a caught stealing, or a run-saving catch is worth in wins. The ↻ beside an input snaps that one value back to the player's actual number and leaves everything else where you put it.
?url= to the source and returns the body unchanged. Used for both FanGraphs and Baseball-Reference, and tried automatically when this page is served over http(s).
Hitting. Your event counts build wOBA with linear weights, then
wRAA = (wOBA − lgwOBA) / wOBAScale × PA. Raising HR without raising PA converts outs into home runs, which is the right behaviour for "what if he hit more."
Defense. Fielding runs come from whichever metric the active framework uses. Position is three dropdown slots with a games slider each, so a player who splits time is handled properly. The adjustments are C +12.5, SS +7.5, 2B / 3B / CF +2.5, LF / RF −7.5, 1B −12.5, DH −17.5 runs per 162 games, and Neutral is 0. Everything else is held fixed when you change a position, so the swing you see is the positional adjustment and nothing else.
Framework. The two buttons at the top swap the model, not just the label. fWAR takes fielding from Outs Above Average and pitching from FIP. bWAR takes fielding from Defensive Runs Saved and judges pitchers on the runs that actually scored. Inputs belonging to the other framework grey out but stay editable, and each keeps its own anchor and its own live feed.
Pitching. FIP from HR, BB, HBP and K, converted to a runs-allowed scale, then run through FanGraphs' dynamic runs-per-win with a starter replacement level of .380. Both players carry the same pitching block; Crow-Armstrong simply starts at zero innings, which zeroes the whole term.
Playing out the season. Where they are now is the season to date. Play out the season holds each man's rates steady — plate appearances, home runs, steals, outs above average, games by position — and scales him to 162 games from however many he has actually played, using the live game counts from the MLB feed. Touch any input afterwards and the switch drops to Custom, so you always know whether you are looking at a preset or your own tinkering.
The arm is projected separately, because "rest of season" means something different for a pitcher than for a lineup spot, and Ohtani has not started since July 3. Three choices, and per-inning rates are held constant in all of them so only the workload changes:
A player with no innings on the board is untouched by all three, so the control never invents a pitching line for Crow-Armstrong.
Total. (batting + baserunning + fielding + position + replacement) / runs-per-win, plus pitching WAR. Both cards run the identical set of inputs and the identical formula, so any difference you see is coming from the numbers, not from the model treating them differently.
On load the page makes three live calls. First the MLB Stats API (statsapi.mlb.com) for season-to-date hitting, pitching and fielding, which fills the sliders. Then FanGraphs for current fWAR and the Baseball-Reference WAR archive for current bWAR, each pulling batting and pitching and summing them per player, since a two-way player's total is the sum of the two. Whatever succeeds wins; whatever fails leaves the previous values in place and says so.
The bWAR call tries two things in order. First the player pages, reading the WAR figure straight out of the summary block at the top — the same number you see when you visit the site, already combined for a two-way player, and about a hundredth the weight of the alternative. If a page does not answer, or its summary is showing a different season, it falls back to the daily WAR archive: plain CSV that Baseball-Reference links from their own WAR Explained page and keys on the same MLBAM ids the MLB feed uses, so there is no name-parsing guesswork. The archive is the full history in one file, so it is streamed and filtered as it arrives rather than buffered. Only the players the pages failed to answer for are looked up there, so nothing is counted twice.
Both WAR calls try three routes in order, and the Fetch routes panel shows exactly what each one did for the selected framework:
?proxy=https://host/?url={url} in the address bar. {url} is replaced with the encoded FanGraphs URL./war-proxy?url=... on whatever host is serving this page, tried automatically over http(s). If you stand up a route there that forwards the query and returns the JSON, there is no cross-origin problem at all and this is the route that will win.fangraphs.com. Works only where the browser is allowed to read the response.Override the endpoints with ?fg= and ?br=, and the same-origin path with ?path=, if your setup differs.
Most of a WAR calculation is reachable from the free MLB Stats API, which is why this model runs at all. Two pieces are not, and they are the reason a published anchor is worth having:
So a from-scratch figure is an estimate of bWAR rather than bWAR. Turn calibration off and the model will happily produce one, clearly labelled; the gap it opens against the published number is roughly the size of the pieces listed above.
The model is calibrated by a flat run offset: the fWAR anchor minus what the model produces on the inputs that were true when that anchor was measured. The offset is then held fixed, so when the MLB feed advances the counting stats the totals move with them rather than staying pinned. The Calibration row in each breakdown shows the offset in runs. A successful FanGraphs pull re-points the calibration to the current stats; the toggle switches it off entirely.
qual=0 sorted by WAR, 400 rows, widening to 4000 if a player is missing. Ohtani needs qual=0 because his innings do not qualify.