Skip to content
arkndjlPublic

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

arkndjl.net

Personal site of arkndjl: sabermetrics, prospect evaluation, economics, art and music. Built with Hugo and the Terminal theme, deployed to Cloudflare, which runs Hugo at build time and serves the generated public/ folder (see wrangler.toml). public/ is build output and is not tracked in git.

Layout

Path What it is
hugo.toml Site config: title, menu, site-wide links ([params.links]), theme options
content/posts/ Articles. Front matter conventions are in archetypes/posts.md
content/eephus.md Page describing eephus.io / MLB PROSPX
content/arkboard.md ARKBOARD hub (/arkboard/): what ARKBOARD is, a card per sport with quick links, FV at a glance. The nav's ARKBOARD entry opens a dropdown of the ARKBOARD pages grouped by sport (children in hugo.toml with params.group and params.gname, grouped by layouts/partials/arkgroups.html for the dropdown, the phone Menu and {{< arknav >}}; pixel icons in static/img/icons/)
content/arkboard-college-football.md College Football / NFL Draft (/arkboard/college-football/): every CFB view in a grouped side navigation (players, games, teams, model & draft); #games etc. open a view
content/arkboard-nfl.md NFL (/arkboard/nfl/): tabs for NFL WAR leaderboards ({{< nflwar >}}, scripts/nfl_war.py; #pos=QB&view=pace opens a view), games (live scores and win probability), game table, power rankings, season odds, team WAR and QB value ({{< nflgames >}}, {{< nflgametable >}}, {{< nflpower >}}, {{< nflsim >}}, {{< nflteamwar >}}, {{< nflqb >}}, scripts/nfl_games.py) and fantasy projections, this week and rest of season in PPR, half PPR or standard, with K and DST, aware of teammates out and recent role changes ({{< nflfantasy >}}, {{< nflrank >}} rankings with tiers and VOR, {{< nflbreakout >}} breakout odds and if-starter points, {{< nflfantasytest >}}, scripts/nfl_fantasy.py; --fit writes the walk-forward backtest to data/nfl_fantasy_fit.json; #view=ros&fmt=half&pos=RB opens a view), all refreshed by the nfl job (.github/workflows/nfl.yml, September-February)
content/arkboard-nba.md NBA (/arkboard/nba/): the XPM leaderboard ({{< nbaxpm >}}): XPM = .5 databallr DPM + .5 Basketball-Reference BPM, O-XPM and D-XPM the same blend of each side, VORX = .030 x (XPM + 1.4) x MP / 48 (wins over replacement, the default sort; constants fit in project files cbb/vorx); every season since 2009-10 (static/data/nba-xpm/<season>.json, data/nba_xpm.json, built by scripts/nba_xpm.py), refreshed daily in season by nba-xpm.yml; #season=2025&side=d&mp=500&pos=C opens a view
content/arkboard-college-basketball.md, content/arkboard-baseball.md College Basketball / NBA Draft and College Baseball / MLB Draft pages
content/arkboard-arkranking.md ARKRANKING (/arkboard/arkranking/, the standings page; /arkboard/standings/ redirects here): every sport's ARK Ranking and season odds in one grouped tab strip, each tab the sport page's own shortcode in a lazytab (no copies of the tables or their logic); first tab {{< titleodds >}}: title odds per competition from the season sims, with the ARK #1, top-3 share and contenders (1 / sum p², inverse Herfindahl). Picks, lines and futures prices stay on Predictions
content/about.md, arkndjl.md, arklibrary.md Static pages linked from the menu. ARKNDJL is own art and music, ARKLIBRARY the collection; both list gallery pieces in their front matter ([[gallery]] blocks, images in static/img/art/). ARKLIBRARY is one side tab per medium (art, music, screen, games, books, architecture, brands, sports) under {{< libfind >}} (find.exe, searches every list); tools="true" on a winlist adds its sort / show bar
layouts/partials/extended_head.html Extra <head> tags: theme color, font preload, rel=me, schema.org JSON-LD
layouts/partials/footer.html Footer override with the site links from [params.links]
layouts/shortcodes/card.html {{< card url="..." title="..." >}}…{{< /card >}} call-out box
layouts/shortcodes/gallery.html, video.html art gallery grid from front matter; framed YouTube embed ({{< video id="..." width="560" >}})
static/style.css Custom CSS loaded after the theme (tables, cards, TOC)
static/img/, static/audio/ Media referenced from posts
themes/terminal/ Vendored theme (edit via overrides in layouts/, not in place)

College Football ARKBOARD (automated)

data/cfb.json is the board, built by scripts/fetch_cfb.mjs from PFN's impact rankings, On3's college grades and ESPN rosters, then graded by the 20-80 FV model in the same script. The {{< cfbboard >}} shortcode renders it on the ARKBOARD page, with filters and sortable columns.

npm i -D playwright && npx playwright install chromium   # once (On3 is behind Cloudflare)
node scripts/fetch_cfb.mjs             # fetch everything, regrade, rewrite data/cfb.json
node scripts/fetch_cfb.mjs --offline   # regrade from the cached pulls (scripts/.cache/cfb/), e.g. after tuning the model
node scripts/fetch_cfb.mjs --skip-on3  # refetch PFN + ESPN only

The board rules (RULES) and every model constant (FV_*, STARTERS) are at the top of the script. Hand overrides (dates of birth, FV grades, notes) go in data/cfb_manual.json. .github/workflows/cfb-board.yml runs the script Mondays and Wednesdays from August through January and commits the result, which triggers the Cloudflare build. It can also be run by hand from the Actions tab. Team logos are saved full-colour at 256 px into static/img/cfb/logos/ (NFL: static/img/nfl/) when ImageMagick is installed.

The College Football tab has fourteen views: all FBS (every graded player, static/data/cfb-all.json, loaded on demand, 50 to a page), draft board (the same, limited to players eligible for the 2027 draft), WAR leaders (the same, ranked by college WAR from scripts/cfb_av.mjs (over replacement starters 2 SD below the average one, so team WAR sums to wins over a replacement team; NFL WAR uses the same anchor, fit by scripts/nfl_war.py --calibrate), FCS players against FCS starters, kickers and punters against an average one; opens on FBS), games (data/cfb_games.json: the next unplayed week, with Elo win probabilities and spreads, playstyle from CFBD advanced stats, injuries from ESPN, and each side's top five prospects on offense and defense), projections (data/cfb_proj.json: the week's top 300 offensive players by projected PPR points, from the same opponent-scaled per-game lines), game table (the same week as one sortable table: model spread vs the market line and the edge between them, and each game's projected top three on offense and defense from this season's per-game CFBD stat lines scaled by the opponent), mock draft (data/cfb_mock.json: a projection of how the draft will go, not our board: the 2027 order from ESPN's NFL standings, needs from each team's whole position room (ESPN rosters for age, salary and contract years, nflverse rosters for draft slot: each player's quality through next season is the better of his draft capital on a rookie deal and his pay against the top of the position's market, cut for age and an expiring deal; a group's need is half 1 minus its starters' quality and half its rank among the 32 rooms, and each pick fills part of it), projected trades in rounds 1-2 (⇅: a team up to 20 slots back moves up when a player is worth more to it, as a slot on Massey and Thaler's market pick-value curve, than its best option at its own pick, paying its pick plus the smallest later picks that cover the chart gap plus 5%; the team on the clock accepts when the extra beats what it loses moving back; at most one trade-up per team, ~3-5 in the first round; shown as a dashed ⇅ tag on the table and the GFX; the GFX has a round picker that draws a whole round, and lists each trade in it under the table: picks each way with their chart values and the player taken), picks already traded before the draft (Wikipedia's list of trades for the draft, fetchNflTrades / applyNflTrades: each pick moves to the team that holds it, a solid "via" tag naming its original team, with the deal in its report), and an expected pick per prospect, a geometric blend of his consensus-board rank and his rank by our draft value, each mapped to a draft slot by the board-to-pick curve backtested on 2020-25 final boards (scripts/draft_backtest.py, data/cfb_draft_backtest.json: position, class, conference level and size added nothing out of sample once a board ranks a player; a missing combine invite and the 40 time did, and apply once the draft year's combine is published) (FV plus positional value and the per-round position counts in data/cfb_draft_priors.json, frame, and position-change flags at the new spot); three mocks share the order, needs and trade rules: predictive (data/cfb_mock_pred.json, the expected pick above: blending the model's order into the board cut slot error from .452 to .433 on the rebuilt 2020-23 boards, best at a board weight of .75-.85, scripts/cfb_fv_refit.mjs --mock), blended (data/cfb_mock.json: each slot halfway between predictive and conviction on the log scale) and conviction; the conviction mock (data/cfb_mock_conv.json) runs the same order, needs and trade rules on the model's own board alone: FV before the consensus blend, no market positional premium or price, no position-run penalty and no combine terms, so each team takes the best player for it by our grade and fit, and trades up when its target would go first on that board; predictive and conviction grade each other and both grade the blended mock: a pick the grading mock makes 2.5x+ and 16+ picks later is a reach, one it makes that much earlier a value (site table only; mockDrafts); a curve refit on fall boards (scripts/mock_fall_curve_backtest.py, data/cfb_mock_fall_backtest.json) won 3 of 5 drafts and lost both NIL-era ones, so the predictive mock keeps the final-board curve; clicking a pick opens its report: need, slot against projection, consensus and our board, value, position change, type and badges, and the player's strengths and concerns, all templated from the numbers in mockDraft), team talent and staff (data/cfb_teams.json: projected starting-22 talent, Elo with a preseason prior from last season's Elo, SP+ and recruiting, TvE, team xNIL, and head coach / coordinator credit). NIL comes in three numbers, all priced off On3's valuations: yNIL (year NIL) is what a player was likely paid going into the season, his share of his program's budget (anchored to the revenue-share cap: ~$20M at Power 4 schools, ~$3M in the Group of Five, XNIL) from what was known before the season (earlier seasons' production, On3 grade, recruiting, position); xNIL (expected NIL) is what he's worth on the open market now, from this season's FV, position and role (the on-field value is expected AV at his FV or this season's college AV, priced per college WAR in nilFit.perWar, p.sav, whichever fits On3's valuations better that run: nilFit.value and nilFit.valueTest in data/cfb.json), priced on a Power 4 or Group of Five curve (a G5 player good enough to start at P4 is priced in the P4 market); FCS players are priced off the FBS fit: their programs split ~$0.4M (FCS schools mostly have no revenue sharing), and an FCS player good enough to start in the G5 or P4 is priced on that curve at 40% (XNIL.fcsMove: he only collects it by transferring up); zNIL = xNIL − yNIL, the value gained this season, summed for teams (team talent tab) and coaches (staff tab: the roster for a head coach, the side for a coordinator). On the staff tab zNIL is a fifth of each coach's score: growth over what the roster was paid, log((xNIL + $250K) / (yNIL + $250K)), standardized within the role and the coach's tier (P4 / G5, since a G5 roster's yNIL comes from a small budget) and capped at 2.5 SD; the rest is Elo and TvE for head coaches, production over talent and development for coordinators. The fbs fv to xNIL and fcs fv to xNIL windows show each FV tier's xNIL range in each division; the FCS one adds the FCS discount at equal FV (FCS median / FBS median per tier, and overall as the FCS-player-weighted geometric mean, fcs.nilDiscount in data/cfb.json). For draft-eligible prospects, FV is then blended toward the grade implied by PFN's industry consensus big board (seven analyst boards averaged), up to 35% with all seven boards (CONS_W), because consensus rank was the strongest single predictor of NFL outcome in the backtest. scripts/cfb_teams.mjs holds the team, games, staff and mock models. Coordinators come from data/cfb_staff.json when present. Recruiting ratings, SP+ and advanced stats come from the CollegeFootballData API, which needs a free key in the CFBD_API_KEY environment variable (a repository secret for the workflow); the pulls are cached in scripts/.cache/cfb/. Game projections come from scripts/cfb_model.mjs. It rates teams with Elo plus opponent-adjusted EPA per play (garbage time removed). The ratings are solved as a ridge regression shrunk toward a preseason prior: last season's Elo, SP+ and efficiency, plus this season's 247 talent composite and returning production, weighted by a regression fit one season ahead. On top of the ratings it adds home field, rest, time zones, travel distance and altitude. The blend weights and settings (Elo K, Elo home field, prior strength) are fit on walk-forward predictions, where each week is rated only on earlier weeks. Since Oct 2026 the model is fit on every FBS game since 2015, built from ESPN's free play-by-play with our own expected-points model (scripts/cfb_espn_pbp.py pulls it, scripts/cfb_espn_md.mjs builds the inputs, history in scripts/data/cfb_hist_md.json.gz; season net EPA and success rate agree with CFBD's at r .95-.99): prior and margin weights are pooled over every season since 2016 (HIST_FIT in scripts/cfb_model.mjs, backtested in scripts/cfb_hist_backtest.mjs, data/cfb_hist_backtest.json: held out 2021-26, log loss .4858 to .4828 and MAE 12.91 to 12.72 against the old last-season fit), and the CFBD fit is only the fallback. Last season and this one are scored out of sample against the closing line (log loss, Brier, MAE, ATS by edge size). The model tab shows that report and the season odds tab shows 10,000 simulated seasons (projected wins, conference title game and title, playoff and bowl odds); both are in data/cfb_model.json. Win, cover and over chances are normal around the model margin and total. Totals are plays × points per play plus wind, rain and cold (Open-Meteo, cached by game in weather.json). A starting QB listed out is replaced by his backup's EPA per play. Every coming game's model line is logged to data/cfb_edges.json and graded against the closing line once played. Availability (data/cfb_injuries.json): Rotoballer's college news (the last ~5 days, each headline clause read for a status) and ESPN's report, matched to rosters; the newest item per player wins, short-term statuses lapse after 9 days, season-ending ones stand until news says otherwise, and injuries in data/cfb_manual.json overrides both. Statuses carry a chance of playing (out 0, doubtful .25, questionable .6, probable .9) that scales each starter's cost in the projections; a season-ending injury takes the player off the projected depth chart and costs 1 FV (2 for an ACL/Achilles). ESPN backups (scripts/cfb_espn_backups.mjs): every ESPN roster player the board doesn't grade joins cfb-all.json tagged ESPN, graded up from the 20 FV floor by a ridge fit of the board's own FV on position, height and weight against the position, years in college, division, 247 composite rating and national rank, an age proxy (years since the high school class beyond years in college) and BMI against the position among its under-60-snap players, plus the board's own NFL frame points (scripts/cfb_frame.mjs, shared with the board: the fit is on FV less frame, which is added back) (5-fold R² .63); each backup gets a short scouting report with each driver's FV points; backup kickers and punters sit at 20. They're scored against the board, not added to its pools, so no graded player's numbers move. Carried over: a player PFN stops listing mid-season (its playing-time cutoff moves) keeps the row he had when last listed in static/data/cfb-all.json, tagged with that date, until PFN lists him again or the season turns. ARKLIST (data/arklist.json, one hand-kept list per sport) marks favourites with the site glyph and fills each sport page's ARKLIST tab; /arkboard/arklist/ shows every sport's list together. Entries can be prospects (2027-29 recruits, internationals) and coaches; layouts/shortcodes/arklist.html matches them against the boards in the browser. Consensus adds Tankathon's and PFF's big boards to PFN's seven analyst boards (nine in all); a riser (↗) is a draft-eligible prospect the model grades 6+ FV above his consensus rank's grade, or a 55+ no board ranks. FV also weighs age on a curve (outliers move most), class runway, a first-year bonus at positions where young contributors are rare (measured each run), frame projected to age 22 along each position's age-weight slope, snap share, and ESPN's schedule-adjusted QBR for quarterbacks. At OL, CB and S the PFN efficiency stats FV used to weigh on trust (pressure rate and sacks allowed, forced incompletions, catch rate and rating allowed, tackle rate) were tested once PFN had two seasons there: against the 2025 and 2026 drafts, holding production fixed, none added anything (scripts/cfb_olsdb_backtest.mjs, data/cfb_olsdb_backtest.json), so they carry no weight, as at the other positions. Position changes (⇄, the position-change filter) flag a player whose weight at 22 and production fit another NFL spot. Every path is backtested on the 2020-26 combines (OL and the secondary 2025-26, PFN's first seasons there) against where the NFL used each player (nflverse depth charts, else the combine listing; scripts/cfb_move_backtest.mjs, data/cfb_move_backtest.json). The line between a DT and an end is the lightest NFL DTs' weight (10th pct, ~282 lb) in both directions. Light receiving tight ends, small receiving backs, undersized linebackers, tall corners and long guards were never moved, so those flags are gone. 23 of 35 flags were right, against 13 of 53 before. Badges (scripts/cfb_badges.mjs) are bronze-to-platinum trait badges (Blocker, Pass Rusher, Ballhawk, Route Runner…): each badge is one stat on the PFN line, regressed toward the position mean by volume (v' = m + (v − m) · n / (n + k), with k the volume at which the stat is half signal, from its year-to-year correlation in PFN's 2019-25 lines), then ranked among the position's 100+ snap players. Bronze is the 75th percentile (top 25%), Silver the 90th (top 10%), Gold the 97th (top 3%), Platinum the 99th (top 1%). Stats that barely repeat (QB INT rate, LB coverage, single coverage stats for DBs) are left out; corners and safeties get a coverage composite instead. Archetypes are a few general shapes per position built from the badges: each lists badge groups, scores the tier points (1-4) of the badges it covers, needs a badge in every group, and the top score of 2+ wins (two-group shapes like dual-threat TE or three-down end win when both halves are there); anyone short of that gets the position's base archetype. The badge and tier filters and the medals after the position use them. Running back roles (Every Down Back, Early Down Back, 3rd Down Back; the RB role filter and the chip after RB) come from estimated snap shares on standard and passing downs (passing downs: 2nd and 8+, 3rd or 4th and 5+): a back's snap share split by how his backfield share of carries + targets tilts between the two (rbRoles in scripts/cfb_pbp.mjs). Every-down is 50%+ of the snaps on both, early-down 30%+ on standard downs and 10+ points fewer on passing downs, 3rd-down the mirror; anyone else is a committee back. Calibrated on the 2024 NFL season, where participation data has the true snaps by down: the labels match the true ones for 82% of backs (81% six weeks in) and never mix up early-down and 3rd-down backs. Kickers and punters are graded from each team's field goals over expected and punt field position (CFBD drives) credited by share of attempts; a primary kicker's absence prices into the game projections. The games tab polls ESPN's scoreboard for live scores while games are on, and top performers lists the latest week's top three at each position from CFBD box scores. The workflow runs Sunday through Wednesday, Friday and Saturday. Play-by-play (scripts/cfb_pbp.mjs): CFBD plays, one call a week, parsed for ball carrier, target, sacker and hurrier, give each player EPA per carry, explosive rates and red-zone production; a stat enters FV only when it holds up in a split-half test (odd vs even games) this season, weighted by that reliability. The existing FV stats are reweighted the same way by their season-to-season correlation in PFN's history (pressure rate repeats at ~.5, sacks per pressure at ~0, so sack conversion is shown only as a regression flag). The age curve was backtested on the 2005-18 drafts (career AV vs the pick's expectation): youth is linear, the old tail steeper. Game-model inputs are kept only when they improve log loss in both the fit and the test season: opponent-adjusted success rate made it; explosiveness and red-zone points per trip did not. Breakouts (data/cfb_breakout.json) lists first- and second-year skill players whose touches and targets jumped over their last two games; portal (data/cfb_portal.json) lists this season's transfers with FV and each team's net expected NFL value in minus out; the staff tab adds scheme fingerprints (4th-down go rate, tempo, run rate, havoc, stuff rate, explosiveness allowed, as FBS percentiles). Draft% (draftOdds in scripts/cfb_teams.mjs) is the chance a draft-eligible player is picked in the next draft: the odds he declares (by class and projected slot) times the odds he's taken, with a spread that narrows as the draft nears, all fit on PFF's dated big boards for the 2020-26 drafts (scripts/cfb_draft_pct_backtest.py, data/cfb_draft_pct_backtest.json). FBS% on the FCS tab is an FCS player's chance of being on an FBS roster next season, fit on the 2023-26 FCS-to-FBS moves from ESPN rosters and stat lines (scripts/cfb_fcs_up_fit.py, data/cfb_fcs_up_fit.json; it also writes data/cfb_fcs_fifth.json, the FCS players in their 5th+ year, rerun each August). Transfer% (scripts/cfb_transfer.mjs, fit by scripts/cfb_transfer_fit.py into data/cfb_transfer_fit.json; rerun with python3 scripts/cfb_transfer_fit.py, or --refit from its cache in scripts/.cache/transfer/) is the chance a player is on another FBS or FCS roster next season: a ridge logit on every 2024 and 2025 FBS/FCS player followed to the next season's ESPN rosters (class, recruiting rank, playing time and production, a G5 producer's pull upward, team record, head-coach change odds, level, distance from home and home-state school, a transfer who just arrived, position), tested out of time on the 2025→26 cycle (AUC .736). Playing time is games with a stat line over the team's games (ESPN in the fit, CFBD box scores live): a P4 spot player (1-7 games) in his third year or later moves more than a regular, while a G5 or FCS regular moves more (bigger programs recruit him); adding it lifted AUC from .716, and PFN depth rank, volume share and the young players ahead of him added nothing. Program talent (the roster's mean recruiting grade, as a z-score) times production keeps a loaded program's starters home (8.5% of top-quarter producers at programs 1+ SD above average moved in 2024-25, over a third after a coach change), and a WR or TE whose starting QB transfers is likelier to go too (scored live as that QB's Transfer%); with both, AUC .738; an exit model shares the features and Draft% sets the floor on leaving. Hometowns are ESPN birthplaces geocoded with the Census Gazetteer (scripts/geo/us_places.json), schools are ESPN venues (scripts/geo/team_loc.json). It is a column on every board tab and a line in the scouting report. The team builder tab (static/data/cfb-builder.json, teamBuilder in the same module) plans each FBS program's offseason: budget = its yNIL sum up 4%, less the next recruiting class (this year's first-year players' yNIL) and the roster kept at market price × stay odds in order of value per dollar; next season's units from Monte Carlo returns against the level's 67th-percentile unit; targets are likely movers and FCS move-ups who'd start, priced at the level's going rate for their position and FV; targets are ranked by need-weighted WAR per dollar (college WAR = AV at FV × .095) × Transfer% × landing odds, the chance he picks this school if he moves, from a destination-choice model fit on 2024-25 transfers (scripts/cfb_landing_fit.py: level step, program talent vs his current school's, open playing time at his position, distance from home), so lower-tier programs lean on FCS move-ups (priced at 60% of the going rate) and players dropping a level; a round-robin market (at most four plans per target) signs the best value that fits; open starting spots with no likely returner push a team's mode up a step. Entrenched starters (top-quarter producers at such programs, with a steady head coach) are not portal targets, and a receiver's landing odds rise 7.2x (TE 5.7x) at a school whose plan signs his quarterback (QB_LINK, from 2024-25 co-moves). A program can also let a returner walk to free money (WALK_*): when his expected cost (xNIL × chance to return) signs a realistic target worth at least twice his edge over the next man up, for a net gain, he goes on the let-walk list (at most two a team). Head-coach change odds come from the coaching market (data/cfb_coach_market.json, its last run) where it has the team, and a target whose head coach could take the job gets that pull on his landing odds: in the 2025-26 cycle, 39% of transfers from programs whose head coach took another FBS job went with him (24x chance; HC_FOLLOW). First- and second-year players are kept and released on their projected next-season FV (the recruit projection's blend, else what their recruiting grade projects to), and a first-year player, or a second-year one projecting to 40+, is never let walk. Its GFX plan button draws the plan as a 4:5 card (the field runs the card's full width so every name fits in full; each starter's tile has his backup under it, the next player on next season's chart who is 50%+ to be there, as on the page's field; tiles are filled in a ten-step FV ramp: purples under 45 with a step at 25, teals 45-59, white at 60, gold at 70), and the hero sprite is hero-builder in brand/make-pixel-art.py. Coaching market (scripts/cfb_coach.mjs, fit by scripts/cfb_coach_fit.py into data/cfb_coach_fit.json with history in data/cfb_coach_pay.json; rerun with python3 scripts/cfb_coach_fit.py, or --offline from its cache in scripts/.cache/coach/) prices every head coach and coordinator and projects the coaching carousel. Pay is USA Today's salary database (head coaches 2006-25, assistants 2009-24, public schools; private schools report none). Value is log(pay) fit on the staff board's inputs and the program's, ridge, an input kept only if it cuts leave-one-out error by 1%+: for head coaches recruiting level, P4 and results (win% over three seasons and this one, plus pass rate over expected), LOO R² .87; for coordinators the program's head-coach pay, side and co-coordinator, LOO R² .75. Elo, TvE, zNIL, the staff score and 4th-down aggression add nothing to pay once those are in: the market doesn't price them, which is where value and pay part. Openings: a multinomial logit on 2010-25 school-seasons (same coach / hired away by another FBS program / fired or out) on this season's win% projected to the end (the season simulation), win% against the program's 10-year norm, a first or second year in the job and a Group of Five winner's pull upward; fit on 2010-19, tested on 2020-25 (log loss 16% under the base rate, AUC .72). Hires: a P4 opening went to a sitting FBS head coach 42% of the time, a coordinator (one of a staff's two best-paid assistants) 25%, someone else 33% (G5: 9 / 32 / 59); within each pool a conditional logit on 2008-25 hires picks the likeliest (head coaches: pay step up, results, level step, conference, distance; coordinators: pay, school's results, level, internal promotion; top-10 hit rate out of sample 29% and 38% against 8% and 5% by chance). Staff: when the head coach stays, 72% of coordinators are back; when he is hired away, 43% follow him; when he is fired, 9% stay. Each coach's Leave% and destinations come from those; the staff tab shows value and Leave%, the ▸ detail adds pay, surplus, buyout and where he'd go, and the coaching jobs and coach pay tabs list every job's odds with its likely hires and every public salary against value. data/cfb_coach_market.json holds the jobs and every projected move (flows: from, to, role, chance), for the team builder (players following a coach). FV constants are calibrated against NFL outcomes in data/cfb_calibration.json (nflverse drafts 2010-19: tier counts per position, composite reliability, size norms, per-round draft counts); the comments next to each constant give the numbers.

Any markdown table window can get the same sort/filter bar: {{< wintable filter="FV,Team" >}} or {{< board ... filter="POS,FV" >}} adds a dropdown per named column, plus an "age ≤" box when the table has an Age column.

College Basketball ARKBOARD

static/data/cbb-all.json is the 2026-27 preseason board: every player on an ESPN D-I roster, graded on the 20-80 FV scale and rendered by {{< cbbboard >}} (filters, sortable columns, a scouting report per player, and a season select that shows past preseason boards from static/data/cbb-past.json with each player's NBA outcome). {{< cbbfv >}} draws the measured FV scale and {{< cbbfv view="model" >}} the drivers kept, both from data/cbb.json.

node scripts/cbb_on3.mjs 2026 2027      # On3 industry rankings (playwright, as for fetch_cfb.mjs); history: 2009 on
node scripts/cbb_dpm.mjs 2010 2026      # databallr DPM by NBA season (public stats page, playwright)
python3 scripts/cbb_link.py             # Barttorvik careers 2008-26 -> NBA careers (Basketball-Reference) + On3/247 grades
python3 scripts/cbb_fit.py              # driver test, ladder fit, FV scale, backtests -> data/cbb_fit.json, static/data/cbb-past.json
python3 scripts/fetch_cbb.py            # ESPN rosters + Barttorvik 2026-27 rosters -> the board (--offline: from the cache)
python3 scripts/cbb_live_backtest.py    # in-season blend weights from Barttorvik game logs 2009-10 on -> data/cbb_live_fit.json
python3 scripts/cbb_live.py             # in season: this season to date blended into the board (daily, cbb-board.yml)
python3 scripts/nba_xpm.py 2010 2026   # NBA XPM leaderboard by season; --current for the season in progress (daily, nba-xpm.yml)

In season (from Nov 1) the cbb board workflow runs scripts/cbb_live.py every morning: each player's preseason grade and the grade his career gets with this season's Barttorvik line to date are blended by minutes played, w = minutes / (minutes + k), k backtested per newcomers and returners; the drivers, P(NBA) and xBPM move the same way, and the board is regraded. Its prior is scripts/cbb_prior.json.gz, the preseason board as fetch_cbb.py graded it (written by every fetch_cbb.py run, so commit it with a rebuild). fetch_cbb.py refuses a fresh pull once the season starts, because Barttorvik's 2026-27 file then holds this season's lines. {{< cbblive >}} shows the blend's backtest; data/cbb_live.json holds the in-season summary.

Raw pulls are cached in scripts/.cache/cbb/ (Barttorvik getadvstats.php CSVs, Basketball-Reference draft and advanced pages, 247 composite pages, On3, ESPN, DPM). The model: realized FV = the NBA role a college career reached (TIERS in cbb_link.py: All-NBA, All-Star, seasons as a starter, rotation seasons, NBA games); E[FV] = 20 + 10 x P(FV 30+) + ... + 10 x P(70+), one ridge logistic per rung, fit on the 2009-16 preseason boards; newcomers and returners fit apart; a driver stays only if it lowers out-of-sample deviance in 4 of 5 folds. FV assigns the scarcity counts (players per board who reached each role) in E[FV] order. 2017-26 boards are the forward test, scored on draft slot.

Writing a post

hugo new posts/my-post-slug.md   # uses archetypes/posts.md

Set draft = false when ready. Useful front matter:

  • description is shown in the post list and used for SEO / share cards.
  • tags become #tag links; keywords feed the <meta name="keywords"> tag.
  • Toc = true renders a table of contents from ## / ### headings.
  • Images: {{< image src="/img/..." alt="meaningful description" position="center" >}}.

Local preview

hugo server -D      # http://localhost:1313, includes drafts
hugo --minify       # production build into public/

buildFuture = true is on, so posts dated in the future still publish.

Share image and favicons

brand/ holds the brand guide, palette, logo files and the HTML templates for the share image, favicons and social banners; see brand/README.md. render.js screenshots the templates with Playwright:

npm i -D playwright && npx playwright install chromium   # once
node brand/render.js

Edit the templates and re-run. Banners, logos and the brand guide render into brand/exports/; run python3 brand/make-glyph.py first if the glyph font changes.

Typeface

The site is set in MS PGothic's 16px bitmap strike (MS Gothic's pixel drawings with proportional widths), converted pixel for pixel into a small outline web font (static/fonts/mspgothic-pixel.woff2) so it renders identically on every OS. brand/make-pixel-font.py builds it from brand/fonts/msgothic.ttc (copied from C:\Windows\Fonts; the source file is not committed since Microsoft's license does not allow redistribution). Code blocks keep Fira Code.

1NT/LAURSEN_111: ESEA and FACEIT

content/laursen_111.md is the 1NT section, wrapped in {{< n1hub >}} (layouts/shortcodes/n1hub.html: the 1NT Night palette over the whole page, the Orbit logo hero from static/img/1nt/, the next-match ticker) with three tabs: 1NT (fans: matchday, season, playoff race, player cards, roster, results, standings, division history by season, all-time leaders, VODs, kit, logos and stickers), LAURSEN_111 (profile, 1NT career with rank on the team each season, FACEIT, splits, stream, clips, results) and 1NTERLYTICS (the team's prep: match preview and veto, scouting board of every S59 opponent, map book, half-time and close-map records, season trends, economy, form, lineups, player rankings, model log, method). The section's own windows are {{< n1 part="..." >}} (layouts/shortcodes/n1.html); the map book, halves, trends, career and head-to-head columns read data/esea_lab.json, built by scripts/esea_1nt_lab.py from research/esea_1nt_history.json.gz plus the live season (run in the esea workflow; descriptive only, since team map records failed as predictors). Any tab on the site opens from the URL hash (/laursen_111/#laursen-111, /laursen_111/#1nterlytics, /arkboard/college-football/#draft-board); old /arkboard/#… links forward to the right sport page, and static/_redirects adds short links like /1nt and /1nterlytics.

data/esea.json is written by scripts/fetch_esea.mjs from the FACEIT Data API and rendered by {{< esea part="..." >}}:

  • season, matches, cards, players: the S59 record, standings rank, every match with both teams' box scores (country flags, ARK Rating, swing, map MVP), player cards and season stats. GFX buttons on the season window and each box score make 16:9 graphics.

  • preview: the next opponent (roster, elo, S59 results and ARK Ratings), map edges from each likely five's lifetime FACEIT map splits (every match the players have played, years of it: each player's map win rate vs his own lifetime rate in log-odds, shrunk by n/(n+50), averaged over the five; map win chance = match chance moved by 1.18 x ours minus theirs, fit on 21,960 ESEA S53-S59 league maps by scripts/esea_ark_research.py, held out t -3.96; team map records and pistol-kill share failed), a Bo1 veto by minimax on those edges (optional standing bans in data/cs.json fixedBans, none by default), and a round spread: FACEIT elo (600-point elo scale) .80 and team talent .20 blended in log-odds into a map win chance (the map and role terms are shown, unweighted; refit on S59 by scripts/esea_research.py, which also set the power rank to .20 round diff/map, .20 team ARK, .60 elo and moved the season sim and stakes odds from shrunk records to elo matchups; data from scripts/esea_research_fetch.mjs via the esea research workflow), then turned into a per-round win chance and expected round differential with an exact race-to-13 model (MR3 overtime). The spread and the total both use that race with the per-round chance varying map to map (mixture(); the spread refit by scripts/esea_spread_research.py, which beat coin-flip rounds on the exact margin and on cover, so favourites' lines are longer). Total rounds over/under (totals(), TOTALS): the same race with the per-round chance varying map to map (logit sd .55, rounds run in streaks), so the line is the half-point hook nearest a 50/50 over; scripts/esea_totals_research.py (writes data/esea_totals_research.json) fit it on 1,719 S58-S59 league maps leave-one-day-out and found the strength gap, the map and each team's round-share spread don't move totals, so every match gets the same line (ou for unpreviewed matches, preview.total, logged in calibration as ou/over and scored as wentOver).

  • stakes: the playoff line (top 48 per Main group), move-ups (top 4 to Advanced, top 2 to the Season Finals), move-downs (under 4 wins) and the Finals prize table, with binomial odds.

  • alltime: every player's maps for 1NT (from each player's FACEIT match stats, cached and fetched incrementally), with tenure and the seasons/events played.

  • split (LAURSEN_111 tab): his FACEIT maps split into PUGs, scrims (queue/hub matches with five 1NT members on his side) and 1NT matches (ESEA plus cups), with per-map and per-season tables.

  • form: every player's last 100 FACEIT maps in 90 days, with All Games / Official (league, cups, qualifiers) / PUGs / Scrims tabs. Talent is 1-5 stars in half steps on the same rolling window (form ARK .70, elo .30; 3 stars = an average player in our league's matches, one star = one SD). The preview's team talent, role matchups, game plan, fanGFX and teamGFX use it.

  • Every player gets Entry+, Duel+, Multi+, Clutch+, Util+, AWP+ (AWP impact vs everyone) and Surv+ on the rolling table and player cards, plus a hot/cold flag (ARK over the last 20 maps vs 100). Team talent is the projected five's mean stars, shown next to each team.

  • stakes adds a 10,000-run season simulation (seed odds, top 4/16/48, the cut line); model (model_log.exe) freezes each preview's projection at kickoff and scores it (calls, covers, Brier); lineups shows map W-L by five-man group and pair (official and scrims, 90 days); the preview's veto dropdown shows the opponent's official maps (90 days). One-match lineups go in data/cs.json matchLineups (by match id).

Round-level data (pistols, eco/anti-eco, setups, positions) needs match demos, which this API key can't download (FACEIT's Downloads API needs separate approval). ESEA_DEMO_PROBE (the workflow's probe input) checks access.

ARK Rating 2 fits its weights on round outcomes instead of hand-setting them: over every cached map (both teams' box scores, 90 days), each team's round share is regressed (ridge toward prior weights, sign-constrained, clutches capped) on its players' per-round kills, deaths, damage, multi-kill rounds, entry ±, clutches, assists, enemies flashed, utility damage and flash/utility successes minus the other team's. A player's value is the rounds he adds per round; a KAST-style outcome credit shares half the team's round share over .500 among its five (half equally, half by round MVPs). 1.00 = the average line in our matches, spread matched to HLTV Rating 1.0. Over two or more maps the rating is a talent estimate: each stat (and the outcome credit) is regressed to the league mean by k rounds, k = rounds x (1 - r) / r from its split-half reliability on 21,960 ESEA S53-S59 league maps (ARK_STAB: kills 145, deaths 279, damage 118, entry 535, clutch 916, credit 486...); held out it predicts the next maps' rating better than the raw one (log loss on team results t -2.06 on S53-S59, -2.58 on S58-S59; scripts/esea_ark_research.py, data/esea_ark_research.json). Pistol-kill rates repeat too little to help (r .25; adding them hurt, t +4.98). data/esea.json arkModel has the weights, fit size and R². rankings (S59 rankings) lists every team in the division with its standing, round differential, team ARK, talent stars, elo and a power rank.

It needs a FACEIT server API key (developers.faceit.com) in FACEIT_API_KEY, a repository secret for .github/workflows/esea.yml, which runs every 20 minutes and commits only when something changed. ESEA_SEASON moves it to the next season (it otherwise follows season in the data file, starting at 59).

Automation (no manual steps)

Every product refreshes on a schedule (the workflows in .github/workflows/, sized to each sport's season, with weekly off-season runs). Three things keep it honest without anyone watching:

  • data health (scripts/data_health.py, every 3 hours and after every data workflow) opens a GitHub issue labelled data-alert for a red run, a feed past its limit in data/freshness.json (and dispatches that sport's workflow), a live site that stops serving main, or a season constant the calendar has passed. Each issue closes itself once fixed. python3 scripts/data_health.py report prints every feed's age.
  • model refits (scripts/refit.py, Mondays) refits each model on the data to date in its season months, keeps a fit only if it runs clean and writes strict JSON, then reruns games_picks.py --fit / --rules and units.py --fit. python3 scripts/refit.py status lists the jobs; Run workflow with all forces every refit.
  • The Predictions page, game models tab, shows the pipeline table: every feed, its stamp and limit, and each refit. New product: add its data file to data/freshness.json (and feedHours if it refreshes weekly); new model fit: add a job to JOBS in scripts/refit.py.

Deploying

Cloudflare builds main with Hugo and serves public/. Each build writes /build.json (commit, branch, build time). Before a commit lands, python3 scripts/check_build.py checks every JSON file under data/ and static/ is strict JSON (a bare NaN fails Hugo) and no static file is over Cloudflare's 25 MiB limit; --hugo adds a full build. The build gate workflow runs it with Hugo on every PR and push to main, and every bot workflow runs the fast checks before committing. Locally: git config core.hooksPath .githooks. After a merge, run python3 scripts/check_live.py (polls until the live site serves your commit, 20 minute limit). The deploy check workflow does the same on every push to main and fails if the deploy never lands.

If Cloudflare's Git build is stuck, the deploy cloudflare workflow (Actions tab, Run workflow) builds and deploys with wrangler. One-time setup from a computer:

  1. dash.cloudflare.com > My Profile > API Tokens > Create Token > "Edit Cloudflare Workers" template > Create. Copy the token.
  2. The Account ID is on the Workers & Pages overview page (right sidebar).
  3. github.com/arkndjl/website > Settings > Secrets and variables > Actions > New repository secret: CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages