niftiIQLab
niftiNFL — Changelog
Open notes: what the system is built from, what every run does, and every change we've made · predictions are published to show the model works. nothing here is betting advice.

What every run does

Runs at 7:00 AM, 11:00 AM, 3:00 PM and 7:00 PM Eastern every day, plus 11:45 AM, 4:10 PM and 8:00 PM on Sundays and 8:00 PM on Mondays and Thursdays. The steps, in order, every time:
  1. Fetch — the latest schedule and results, play-by-play, injury reports and depth charts from nflverse.
  2. Grade — every finished game with a stored pick gets a result row: straight-up, against the spread, over/under, and the Vegas benchmark. Each analyst claim on those games is graded too.
  3. News — public feeds are read and tagged by team; new items are attached to the games they concern.
  4. Analyst — games with new items get one read; claims that pass the filters and aren't already counted become a capped adjustment. Declined claims are kept as shadow claims.
  5. Predict — the model retrains on every completed game since 2009 and re-predicts every game in the next eight days. The last run before kickoff is the pick of record.
  6. Markets — a snapshot of the DraftKings line (via ESPN), Polymarket, and the nflverse line, for open-to-close comparison.
  7. Rankings and projections — power ratings and a 5,000-season simulation for wins, playoff and championship odds.

What it's built from

The ingredients, without the exact recipe.
  • Team strength: an Elo rating with margin-of-victory and offseason regression.
  • Efficiency: opponent-adjusted expected points added per play, for offense, defense, passing, rushing and success rate, weighted toward recent games and the current season.
  • Quarterback: a rating of the named starter (recency-weighted EPA per dropback and completion percentage over expected, shrunk toward a prior for small samples) and a flag when the starter changed.
  • Availability: position-weighted listed starters Out or Doubtful, from the official report and depth charts.
  • Situation: rest days, short weeks, byes, division games, home field (zero at neutral sites).
  • Models: a regularized logistic regression for win probability; ridge regressions for margin and total. Retrained weekly. Tested walk-forward, 2010–2025, with a leak audit.
  • Analyst layer: news → typed, capped, source-linked claims, applied in log-odds, total cap ±10 points of probability, graded per claim.

Change log

Every change, dated. Model version numbers change only when the prediction method changes; package versions change for pages, tracking and tooling.

0.6.1 — 2026-09-13 (afternoon) · niftiLab 0.3.1

  • Markets grid: source columns centered under their headers; each market cell is shaded green when that source's closing favorite is our pick and red when we differ (legend in the subtitle).
  • FAQ "What does the AI do here?" now says plainly that AI helped build the site and runs the news-reading step, and that the predictions themselves are statistics; same note on the Lab's How-it-works page.
  • Remaining British spellings (favoured/favours, offences/defences) fixed.

0.6.0 — 2026-09-13 (afternoon) · niftiLab 0.3.0

  • Spread and total are now graded like the winner: results.ats_correct / ou_correct (our side fixed pre-kickoff, closing line, pushes excluded); Record page "Against the spread and the total" with the 52.4% break-even line and a by-week table. Picks table shows our spread and total.
  • Glossary (/glossary, 28 terms) and FAQ (/faq, 13 questions), each with schema.org JSON-LD (DefinedTermSet, FAQPage) for search and answer engines. Linked from the footer and About.
  • Changelog page (/changelog): what every run does, what the model is built from, and this file.
  • About: what the backtest does and doesn't cover — 65% is the floor, the live-only layers are graded beside the model, 70% would be exceptional.
  • SEO basics: per-page meta description + Open Graph tags, /sitemap.xml (all pages + weeks).
  • Google Analytics 4 via lab.ga4_id in config (empty = off); shared header carries the tag.
  • Legend: the picks-table subtitle explains the team-color edge on the pick chip.
  • Web app applies schema migrations itself on first request (no 500 before the next weekly run).
  • niftiLab: /contact form (message or subscribe; honeypot + rate limit; stored in data/lab.db; private /admin/submissions on the tunnel only, python -m niftilab.web submissions on the server); Local section "coming soon" (police-log card removed); /sitemap.xml, /sitemap-index.xml, /robots.txt; Contact in the header and footers.

0.5.2 — 2026-09-13 (midday, same delivery as 0.5.1)

  • "What moved" panel on This week and each week page: every run's prediction is kept, so the panel lists any game whose probability moved ≥1 point or whose pick flipped, before → after, and the biggest reason (analyst adjustment or the model factor that changed).
  • American English throughout (favorite, offense/defense, color, gray, summarize…): pages, code comments, docs. Function summarisesummarize.

0.5.1 — 2026-09-13 (midday) · niftiLab 0.2.0

  • Shared Lab header on every page: blue band with the brand and section tabs (Sports · Local · How it works) and a second row with this section's projects (NFL · NBA soon). One template (_labhead.html) owned by niftiLab and copied in by scripts/sync_header.py; lab: in config.yml holds the links.
  • Look: Sora display font for headings and big numbers, team-color edge on each pick chip (teams.COLOR), probability bars animate in (off under prefers-reduced-motion), deeper blue accent.
  • niftiLab: section pages /sports/ and /local/, an /about page, hover on cards, same header.

0.5.0 — 2026-09-13 (morning)

  • Serves under a path prefix behind nginx (lab.niftiiq.com/sports/nfl): ProxyFix reads X-Forwarded-Prefix, so every link is prefixed automatically; the tunnel on 127.0.0.1:8400 still works at the root. Verified through a real nginx with the shipped site file.
  • niftiIQ Lab bar above the nav (config lab: — name, tabs, which tab this app is).
  • /api/summary: one-card JSON (record, picks stored, next week) for the Lab landing page.
  • Sister package niftiLab (separate folder /opt/niftiLab, port 8500) — landing page, nginx site with one location per project, scripts/install.sh. D-017.

0.4.3 — 2026-09-12 (evening)

  • "How this works" page (/about): what it is, what to expect, how to read each page and when, update schedule, sources and credits. Linked in nav and footer.
  • Timer: extra Sunday runs at 11:45 / 16:10 / 20:00 ET and Mon/Thu 20:00 ET so Sunday inactives (announced ~90 min pre-kickoff) and night games get a last read.
  • Feeds: Yahoo Sports NFL added; ESPN kept as optional (rate-limits the VPS).
  • docs/RUNBOOK.md: the owner's weekly rhythm, monthly review, troubleshooting, deploy, going public.

v0.4.1 — 2026-09-12 (evening)

  • Reader prompt tightened after the first live read; deterministic claim filters (confidence floors, ruled-out language, offseason/logistics/soft-status patterns, report and named-QB cross-checks); read-tracking so zero-claim games aren't re-read.
  • Claims retire automatically once the schedule/injury feeds cover the same fact (no double counting).
  • Shadow claims: declined claims are stored with their reason and graded at zero weight; shadow scorecard by rejection reason on the Analyst page — the learning loop.

v0.4 — 2026-09-12 (package 0.4.0; model version stays v0.2 — grading is per pick, not per model)

  • Analyst layer (docs/ANALYST.md): news_items (RSS: PFT, CBS, ESPN), game_weather (NWS at kickoff, outdoor stadiums), claims (typed, confidence, signed points, source link, graded). One language-model call per game only when there is new news; strict schema; "already counted" list keeps it from double-counting injuries and named QBs.
  • Predictions now store home_win_prob_model (untouched) and analyst_adj; the adjusted probability is the pick of record; results store model_pick_correct beside pick_correct.
  • Grading: each claim gets helped/hurt and a Brier delta; scorecard by type on the new Analyst page; Record page shows model-alone vs model + analyst and flipped picks.
  • CLI: news, analyst [--test|--force|--notes FILE --game ID], claims [--week]; weekly = fetch → grade → news → analyst → predict → markets → ratings → project.
  • Injury rows now carry player names (feeds the reader's context); DraftKings probability derived from the spread when ESPN gives no moneyline (normal margin model, sd 13.4).
  • Config: analyst: block (mode api|manual|off, key_file, model auto, caps, feeds).

v0.3 — 2026-09-12 (package 0.3.0; model stays v0.2 — the live record is unchanged)

  • Markets tracker: odds_snapshots table; every run stores one row per game per source for games in the next 8 days — nflverse line (benchmark of record), Polymarket moneyline price + best-liquidity spread (public Gamma API, slug nfl-{away}-{home}-{utc date} with search fallback), ESPN scoreboard line (cdn.espn.com, provider as named). All free, no keys. Every fetch is isolated; a dead source is logged, never fatal.
  • python -m niftinfl markets (manual snapshot); weekly now runs fetch → grade → predict → markets → ratings → project.
  • Markets page (/markets, /markets/<season>/<week>): per game, our probability open → close (with run count) beside each source's open → close and spread; season record of our pick vs each source's opening and closing favorite, agree/differ split, and big-divergence (≥8 pts vs Polymarket) grading. "Open" = first snapshot we captured, "close" = last before kickoff.
  • Probe scripts scripts/probe_sources.py / probe_sources2.py (what answered from the VPS: Polymarket, ESPN cdn scoreboard, NWS weather, ESPN/PFT/CBS RSS; blocked: ESPN site.api, DraftKings; dead: NBC edge RSS, NFL.com feed).

v0.2 — 2026-09-12 (same day, before install)

  • Injury layer: injuries and starters tables loaded from nflverse injury reports and depth charts (weekly format ≤2024, dated snapshots 2025+), refreshed every run. New feature d_missing = position-weighted listed starters Out/Doubtful, away minus home (QB excluded — it has its own feature). Factor group "Injuries" in the why-line: "BAL missing 2 starters (+2)".
  • Backtest 2010–2025: 64.95% / Brier 0.2189 (v0.1: 64.79% / 0.2192). Marginal, adopted for the explanation value; see docs/EXPERIMENTS.md. Leak audit PASS (12/12).
  • fetch and seed now also pull injuries + depth charts; status shows their row counts.
  • Season projections use "healthy" strength for the rankings and this week's injuries for this week's remaining games only.

v0.1 — 2026-09-12

  • New project, built from the niftiNBA base (config/db/model/backtest/web pattern).
  • Data layer: nflverse schedule + play-by-play → games, team_game, qb_game (idempotent upserts; seed for history, fetch for routine refresh).
  • Features: Elo (MOV-weighted, 1/3 season regression), opponent-adjusted EPA per play via weighted ridge (total/pass/rush/success; offense and defense), QB rating (shrunk, recency-weighted EPA/dropback + CPOE) for the *named* starter, starter-change flags, rest/short week/bye, division, neutral site, form (EWM margin, season margin).
  • Models: logistic win / ridge margin / ridge total, no intercept, home field as a feature (zero on neutral sites). "Why this pick" factor groups.
  • Backtest: walk-forward by week 2010–2025 with Vegas benchmark; writes data/backtest_latest.json for the dashboard. Leak audit PASS.
  • Predict/grade: window of the next 8 days, last pre-kickoff run authoritative; ties pushed; Vegas favorite graded alongside.
  • Rankings + projections: strength = margin-model expectation vs an average team on a neutral field; 5,000-season Monte Carlo with NFL seeding rules and bracket; stored per run date (first run of the season kept as the baseline).
  • Dashboard: This week / Week / Record / Rankings / Season / Backtest / API / health; clean-analytics look reused from niftiNBA; disclaimer + attribution on every page.
  • Ops: scripts/install_step1.sh, install_step2.sh, systemd niftinfl-web, niftinfl-weekly (+timer), nginx template (opt-in).