Dashboard rebuild v2 — scope (2026-07-21)
Owner ask: "rebuild of the dashboard… make sure we can get more information
and make sure it's super relevant… data-loading monitoring from the new data
and any other bet intelligence." The new vendor (theoddsapi.com) supplies far
richer data — the dashboard should surface it and prove it's flowing.
This is a SCOPE, not a build. It sequences the rebuild into shippable slices
so each lands behind tests and the value board never regresses (the model
attribution chip has been silently deleted twice — see
tests/test_agent_guardrails.py).
Principle: relevance over volume
More fields is not the goal; the decision-relevant fields are. Every panel
answers one question the bettor actually has. Anything that doesn't is noise.
The canonical app stays web/main.py (Jinja); this is not a React migration.
Slice 1 — Bet Intelligence panel (highest value)
A per-match panel driven by the new data, showing for the recommended side:
| Field | Source | Why it's relevant |
|---|---|---|
| Our fair prob | model + market_reference.devig_two_way |
what we think |
| Pinnacle fair prob | /intelligence/fair-odds/ (sharp anchor) |
what the sharpest book thinks |
| Consensus prob | /intelligence/consensus/ + our median |
what the field thinks |
| Best available price | /best-lines/ |
the price we could actually take |
| Soft-vs-sharp divergence | market_reference.soft_vs_sharp_divergence |
the open-softness signal, in pp |
| Vendor edge/value output | /intelligence/edges/ and /intelligence/value/ |
hypotheses — labelled "unvalidated" |
The design rule: our number and the sharp number side by side. When they
agree, confidence; when they diverge, that's the bet or the warning. The
vendor edge score is shown but visually marked as unvalidated (it has not
cleared our gates) so it never reads as advice.
Slice 2 — Data-loading monitor for the new feed (partly shipped)
/ops now carries three new-vendor rows (historical backfill, match-archive
ticks, intelligence/edges) with honest empty states until data lands. Extend
with: per-sport coverage (NRL/AFL/UFC), rate-limit remaining (x-ratelimit-*),
and last-successful-intelligence-pull age. A red row here = the intelligence
panel is stale, so it gates Slice 1's trustworthiness.
Slice 3 — Repricing-curve view (the timing thesis, made visible)
Once the match archive accrues: a small sparkline per match of the price path
from open → Tuesday drop → final hour, with the team-list drop marked. This
is the single most differentiated view — it visualises the edge the whole
program is built on. Data source: stg_match_price_ticks kickoff-relative.
Slice 4 — Multi-sport board (NRL primary)
A sport switcher (NRL/AFL/UFC) on the value board. NRL keeps full model
cards; AFL/UFC show market + intelligence only (no models yet — Law 2). This
makes the archive visible without implying we model those sports.
Explicit non-goals
No React migration. No props betting UI (collect-only). No in-play. No vendor
"value bets" presented as recommendations — always as validation targets.
Build order
Slice 2 (finish) → Slice 1 → Slice 3 → Slice 4. Each is a CLI round behind
tests; the guardrail suite must stay green (canonical app + attribution).
Every new panel needs a service-layer test asserting it renders with real
context AND degrades to an honest empty state when the feed is absent.