ec815b0e37
Records the verification that matters: firing measured on a real prod snapshot rather than inferred. environment 248/293 (84.6%) -- also its FIRST confirmed ledger write, which the previous session could only infer -- and matchup 243/293 (82.9%) on tier batter_own_split. Both were 0/634. Collinearity guard passed at n=243: r = -0.003 vs the projection, +0.074 vs p_win, +0.003 vs line, -0.068 vs environment, -0.150 vs opportunity. The axis is not re-encoding recent form. The nudge distribution is also the right SHAPE -- mean +0.0007, 123 positive / 120 negative -- a balanced two-sided signal; a one-sided distribution would have suggested a sign or baseline error. Plan reconciled in place: arch-v1 condition axes marked firing, three challengers listed with coverage and their own holdout queries, and the next-order pointer moved to connecting the still-dormant layers (similarity, Bayesian, distribution ladder) with archetype_x_archetype as the named alternative. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
627 lines
36 KiB
Markdown
627 lines
36 KiB
Markdown
# VYNDR — MASTER PLAN
|
||
**Single source of truth. Sessions EXECUTE against this and UPDATE it in place.**
|
||
Created 2026-07-31 by consolidation. Last updated 2026-08-01.
|
||
|
||
---
|
||
|
||
## 🔷 PRODUCT IDENTITY — the thing being built
|
||
|
||
**VYNDR IS A PREDICTIVE MODEL.** It projects what a player will **DO** and picks
|
||
accurately — reading and pulling the market apart. Student of the game, and an
|
||
aggregator.
|
||
|
||
**Market edge is a BYPRODUCT of a good prediction, NEVER the success criterion.**
|
||
|
||
> **SUCCESS = the forecast is honest about its own confidence AND still ranks.**
|
||
> Calibration *and* resolution. **No edge/CLV term belongs in a pass/fail gate** —
|
||
> they are diagnostics we report, not thresholds a model must clear to ship.
|
||
|
||
**PER-SPORT DOCTRINE** (Rashad Phillips, *Basketball Position Metric*, 2022 —
|
||
classify players by **what they do**, not position labels): each sport is its OWN
|
||
model, with its own variables, archetypes, conditions, calibration and honest
|
||
ceiling. Shared across sports: **only the Bayesian inference math.**
|
||
|
||
**TRUTH LAW:** no fabricated data anywhere · honest-absent over invented · label
|
||
limitations in-band ("market consensus, **not sharp**") · **provisional results
|
||
stay provisional until re-run** · documented ≠ verified.
|
||
|
||
---
|
||
|
||
## ▶ NEXT EXECUTABLE ORDER
|
||
|
||
**CONNECT THE STILL-DORMANT LAYERS** — similarity, Bayesian, and the distribution
|
||
ladder (`projection/distribution.js`, proj-v1.1, built and unconnected). Same
|
||
pattern that just worked three times: audit what is actually firing, repair the
|
||
joins, add as a labelled challenger, prove on an axis-filtered holdout.
|
||
|
||
*Blocked on nothing.* The alternative next order is
|
||
**`archetype_x_archetype`** — the matchup ladder's upper rung, which needs the
|
||
opposing starter classified per game.
|
||
|
||
### THREE challengers accruing in parallel — do NOT re-run early
|
||
Verified firing on a real prod snapshot (293 grades), not inferred:
|
||
|
||
| axis | coverage | mean \|nudge\| | holdout query |
|
||
|---|---:|---:|---|
|
||
| **environment** | **84.6%** | 0.057 | *(shares the arch-v1 pattern)* |
|
||
| **matchup** (`batter_own_split`) | **82.9%** | 0.015 | `scripts/matchup-axis-holdout.sql` |
|
||
| **opportunity** | 30.0% | 0.142 | `scripts/opportunity-axis-holdout.sql` |
|
||
|
||
All three orthogonal (r ≈ 0 vs projection, `p_win`, line and each other). Each
|
||
promotes ONLY on its own axis-filtered holdout, and ONLY if **reliability AND
|
||
resolution** improve. **This is time, not code.**
|
||
|
||
Both promote only on their own axis-filtered holdout, and only if **reliability
|
||
AND resolution** improve.
|
||
|
||
## 📍 STATE AS OF 2026-08-01 (Order Zero, measured on prod with the real key)
|
||
|
||
| finding | number | what it means |
|
||
|---|---|---|
|
||
| **MLB slate invisible to us** | **64.8%** | our own allow-list, not the feed — now widened for DISPLAY |
|
||
| **books/prop, MLB** | 3.61 feed → 0.57 after filter | the filter cost, quantified |
|
||
| **books/prop, WNBA** | **4.21** feed → 1.20 | **WNBA is BETTER covered than MLB** |
|
||
| **consensus ruler** | **MARKET, not SHARP — ⏳ PENDING-RECOVERY, not permanent** | `matchbook`/`polymarket` = 0%, but **`pinnacle` ran until 07-30** (103,940 captures) and stopped. **Do not enshrine as permanent** until PropLine answers — see `BLOCKERS.md` |
|
||
| **ruler delta** (consensus − incumbent) | MLB mean +1.50 pts, median 0, **17% of props move ≥5 pts** | rulers genuinely differ; "better" is unproven |
|
||
| **MLB isotonic `p_win`** | **DECIDED** — reliability **0.0846**, resolution **0.190**, holdout **n=125** | **PROVISIONAL label RETRACTED 2026-08-01.** Calibration is **ruler-independent** (`estimateProbability` never sees a price; the fit is p_win-vs-outcome). Replicated on a fresh later window, both metrics improved |
|
||
| **edge vs the ruler** | corr(edge, outcome) **−0.010** (v1) → **−0.022** (v2), n=200 · corr(**p_win**, outcome) **+0.26** | **Subtracting the market DESTROYS the signal.** The consensus ruler does not rescue edge: *differs ≠ better* |
|
||
| **exchange-inclusive ruler** | **UNTESTABLE on existing data** | exchange quotes were never stored (discarded until 2026-08-01). Becomes testable only as v2-era captures accrue |
|
||
| **WNBA** | **MODEL NOT BUILT YET** — held out, *not* failed | **CORRECTED 2026-08-01.** The −0.12 was **NBA-template machinery run on WNBA data**. WNBA has never had its own archetypes/variables/conditions — the "sport stubbed in on another sport's template" CLAUDE.md forbids. That is an **unbuilt model's expected failure, not a verdict on the sport.** Its own build is QUEUED, after MLB |
|
||
| 🔴 **pinnacle feed** | **0 captures since 2026-07-31** (103,940 in the prior 10 days) | a live regression; **we had a sharp anchor and lost it.** Not caused by our changes |
|
||
| **soccer** | **settles** — ~15 competitions, 30d | "grades into a void" is a **$19/mo Pro-tier** problem, not a data problem |
|
||
| **CLV + results feeds** | `/odds/closing` + `/movement` **redacted**; `/results` **403 `required_tier: hobby`**; `/exports/resolved-props` **403 `required_tier: pro`** | **verified on our keys** — plain tier exclusion, not a key or plan fault. **$9/mo** buys CLV + steam + results; **$19/mo** adds the 90-day settlement export |
|
||
| **books SERVED** | 5 → **13**; props rendered **546 → 2,780** (5.1×); mean **4.22** books/prop | **AGGREGATOR widening is LIVE.** Of 2,234 newly-visible props, **31.2% carry a real non-DFS price**; **68.8% are DFS-only** — shown, tagged, never a market |
|
||
| ✅ **p_win ranking / edge retired** | DONE — boards rank on `forecast_rank`, edge is diagnostic-only, rollback armed (`FORECAST_RANK=0`) | `specs/rank-on-pwin-challenger.md` |
|
||
| ✅ **calibration** | **DECIDED** — ruler-independent; MLB isotonic **DECIDED**, not provisional | `specs/mlb-recalibration-vs-consensus-ruler.md` |
|
||
| ✅ **grade cap** | DONE — 25 → 500; board **7 → 365+** graded props | `specs/grade-cap-and-refusal-diagnosis.md` |
|
||
| ✅ **S59 join invariant** | **ARMED** — root cause was `searchPlayer` returning `team: null` (`currentTeam` has no `name`); fail-safe: drops only on a positive not-in-game | `tests/unit/slateJoinInvariant.test.js` |
|
||
| ✅ **arch-v1 condition axes** | **BOTH NOW FIRING** — environment 84.6%, matchup 82.9% (`batter_own_split`). Was: env + matchup on 0/634 prod rows — `team` was null on 416/416, so the venue join had no key. **Environment FIXED** (joins on the game; resolves 105/120). **Matchup still dead** — needs opposing SP + both hands, three separate absences | `specs/arch-v1-axis-audit.md` |
|
||
| **opportunity_drift** | built, orthogonal (r≈0), live as a challenger on 142 rows/slate | verdict n-blocked; `scripts/opportunity-axis-holdout.sql` |
|
||
| **opportunity layer** | **NOT BUILT** — `ab_per_game` is display-only; engine1 has no usage factor; MLB batting order unavailable in every wired source | Step 0 stopped before wiring. Recommended instead: an `opportunity_drift` axis on the EXISTING `challengerProjection` (arch-v1). See `specs/connect-opportunity-step0.md` |
|
||
| **MLB board size** | **7 → 365 graded props** (52×) in 114s | the 25-cap discarded 95.7% of the slate. Raised to 500 on measured cost. Refusals were **43.8% deliberate policy suppression**, not a data gap |
|
||
| **model input** | **byte-identical** — 546 gradeable props, `v1_first_book` | `MODEL_BOOKS` gate in `dedupeProps` + `indexOddsProps`. Lifts only on the re-run |
|
||
| **accrual clock** | **sequential, post-completion** | see §11. Pre-completion data does not count and is never pooled |
|
||
|
||
**The honest framing:** widening books is an **AGGREGATOR** win. **It does not fix
|
||
the model.** Do not let the free-side win read as model progress.
|
||
|
||
> **HOW TO USE:** this supersedes ad-hoc re-derivation. Before any order, read the
|
||
> phase you're in. After any order, tick the item and add one line. **Do not
|
||
> re-audit anything marked KNOWN** — that redundancy is what this document exists
|
||
> to kill.
|
||
|
||
---
|
||
|
||
## 0. VERIFICATION LEDGER (what was re-checked in this pass)
|
||
|
||
**NOTHING was re-verified. No query was run.** Everything required is already
|
||
captured in 22 artifacts produced this session plus the canonical board. Per the
|
||
order's own clause — *"If everything needed is already in the artifacts, say so and
|
||
skip verification"* — this is that case.
|
||
|
||
**Taken as KNOWN (source in brackets):**
|
||
- Model architecture, all layers [`model-architecture-recovery-map.md`]
|
||
- Grade↔outcome correlations, collapse cost [`full-output-grade-mapping.md`]
|
||
- p_win calibration + holdout verdicts [`grade-diagnostic-t0.md`, `pwin-recalibration-holdout.md`]
|
||
- Market-relative edge inversion [`grade-fix-part1-investigation.md`]
|
||
- Design implemented-vs-designed, 61 items [`design-vs-build-gap-audit.md`]
|
||
- Surface states, orphans, waves [`pre-audit-status-pull.md`, `incomplete-surface-triage.md`]
|
||
- Resolution pipeline true state [`resolution-and-clv-investigation.md`, `wave3-*.md`]
|
||
- Tier/monetization + founder mechanism [`tier-structure-pull.md`, `tier-redesign-spec.md`, `build2-review-zero-report.md`]
|
||
- Sport-boundary cost [`model-architecture-recovery-map.md` §Phase 2]
|
||
|
||
**Genuinely OPEN → carried as explicit unknowns (not verified because they need a
|
||
build or a decision, not a query):** sport order (Kev's call, §A2); board-reasoning
|
||
gating (a/b/c, §C2); CLV flag decision (§D3); the ~70 undefined team colours (§B).
|
||
|
||
---
|
||
|
||
## A. PER-SPORT MODELS — Phillips doctrine: each sport is its OWN model
|
||
|
||
### A1. MLB layer stack (the reference module), bottom-up
|
||
|
||
| # | layer | state | note |
|
||
|---|---|---|---|
|
||
| 1 | Data/feeds | **BUILT** | statsapi free+unlimited; statcast; park; weather; probables. Settles end-to-end. |
|
||
| 2 | Similarity (comparable instances) | **BUILT · NOT WIRED · NOT DEPLOYED** | `python/utils/similarity.py`; grade path skips to season/recent averages |
|
||
| 3 | Archetypes (batter + pitcher) | **BUILT · display only** | classifies + renders; **does NOT feed the grade** |
|
||
| 4 | Variable weights | **PARTIAL/ASSUMED** | engine1's flat ±1.0/±0.5 deltas are hand-set, never fitted |
|
||
| 5 | Conditions (park/weather/platoon/arsenal) | **BUILT · CHALLENGER ONLY** | rides `env_*`/`challenger_*`; "measured, never served" |
|
||
| 6 | Bayesian inference | **BUILT · NOT WIRED · NOT DEPLOYED** | `python/utils/bayesian.py`, 320 ln, genuinely sport-agnostic math |
|
||
| 7 | Calibration | **MEASURED, NOT APPLIED** | isotonic qualifies on holdout (rel .1038→.0939, res .139→.123, n=119) |
|
||
| 8 | Grade ladder | **BROKEN** | letter is a factor index, r≈0.005, **inverted** (B 52.4% < C 56.9%) |
|
||
|
||
**The through-line:** layers 2, 3, 5, 6 are built and *not connected*; layer 8 is
|
||
connected and *meaningless*. MLB's fix is connection, not construction.
|
||
|
||
### A2. Sport order (OPEN — Kev decides)
|
||
Proposed by readiness × clock: **1) MLB** (reference, only qualifying model) →
|
||
**2) CFB** (has a <30-day clock; soft-market thesis) → **3) NFL** → **4) NBA** →
|
||
**5) CBB** → **6) WNBA — first real build** (never had its own model) → **7) soccer** (quota-blocked).
|
||
Each gets the same 8-layer template. **No sport is abandoned — abstention is a
|
||
state, not a verdict.**
|
||
|
||
---
|
||
|
||
## B. DESIGN IMPLEMENTATION — 61 items catalogued
|
||
BUILT-TO-SPEC 20 · DRIFTED 7 · PARTIAL 16 · ABSENT 18. Ordered wire-in:
|
||
**D1-A done** (6 combat glyphs, boundary-blue completed, reaction primitives, READ-FAB).
|
||
**D1-finish done** (rationale/reveal/chips modules — *built, NOT mounted*).
|
||
Remaining: **D1-close** (mount — blocked on a ROW-GRAMMAR slot amendment + §C2) ·
|
||
**D1-B** (45 unwired glyphs + the 41-vs-74 archetype scope call) · S2 primitive set
|
||
(movement strip, crown, disagreement axis, SPLIT) · S3 article media · The Report
|
||
email + archive · Offseason artboards · **team colours: only 10 of ~80 defined —
|
||
the rest render honest-neutral until a real source exists.**
|
||
|
||
---
|
||
|
||
## C. SURFACES
|
||
**C1 — done:** Wave 1 wiring · `/compare` · `/record` · Build-1 gate.
|
||
**C2 — OPEN DECISION (blocks D1-close):** board reasoning is served ungated while
|
||
`tiers.js` declares `reasoning_visible:false`. Options (a) gate it, (b) accept as
|
||
free funnel, (c) leave unrendered.
|
||
**C3 — remaining:** `/record` **has no nav link** (the surface that justifies the
|
||
price) · `/notifications` · Offseason hub · `/system` · S3 media · `/soccer`
|
||
(quota) · share cards (blocked by D).
|
||
|
||
---
|
||
|
||
## D. RESOLUTION PIPELINE → USER OUTPUT
|
||
**KNOWN and load-bearing: settlement WORKS** (scheduler → `settleAllOutcomes` +
|
||
`settleAllLedgers`, 937+ settled, growing daily). **What is unreachable is the
|
||
user-output TAIL:** `/api/grading/resolve` has no caller, and its fanout holds
|
||
webPush/Telegram/Discord but **no share-card step and no recap**.
|
||
**D1** wire a trigger (or move the fanout into the settle pass) · **D2** share-card
|
||
generation + `/notifications` consent + result posts + recap · **D3 CLV flag
|
||
decision** — `clvCaptureReliable()` is *one env var*, and the pre-registered rule
|
||
stands: flip only if close_moved is a clear majority AND coverage is representative.
|
||
**🔴 Never wire `/api/grading/resolve` as a second settlement path — it double-counts.**
|
||
|
||
---
|
||
|
||
## E. SPORT BOUNDARY
|
||
Adding a sport is a **~10-file core edit** with four silent-failure modes
|
||
(MARKET_MAP → zero props; three stat whitelists → silent 400s; missing projection →
|
||
universal refusal; no settled feed → grades forever). **Collapse to a registry** so
|
||
a sport is a module. **Blocks all of A2 after MLB.**
|
||
|
||
---
|
||
|
||
## F. CHROME AUDIT — 11 items, 4 need a Desk session. **Runs when surfaces are stable, not before.**
|
||
|
||
---
|
||
|
||
# THE PHASES — 7 phases, ~18 orders
|
||
|
||
| phase | orders | contents | blocks |
|
||
|---|---|---|---|
|
||
| **1. MLB model truth** | 4 | promote isotonic p_win (MLB only — every other sport is NOT-BUILT, held out) · rebuild the ladder on calibrated p_win · re-adjudicate (ROI-by-grade, skew, proj-v1.1, C1 floor) · connect layers 2/3/5/6 | everything model-shaped |
|
||
| **2. Resolution tail** | 3 | trigger · share cards + notifications + posts + recap · CLV flag decision | share cards, social proof |
|
||
| **3. Surfaces + design lane** *(parallel with 1-2)* | 4 | C2 decision → D1-close mount · `/record` nav + remaining surfaces · D1-B glyphs/archetypes · S2 primitives | Chrome audit |
|
||
| **4. Sport boundary** | 2 | registry collapse · MLB re-expressed as the first module | all further sports |
|
||
| **5. Sport rollout** | 1 per sport | CFB → NFL → NBA → CBB → WNBA retry → soccer, each on the 8-layer template | — |
|
||
| **6. Monetization finish** | 2 | Stripe Phase-B live proof on first real signup · founder launch to the 3 existing users | — |
|
||
| **7. Chrome audit + hardening** | 2 | the 11-item visual sweep · credential rotation + migration-drift reconciliation | ship |
|
||
|
||
**Phases 1–4 and 6–7 = ~17 orders. Phase 5 = 1 order per sport (6 listed).**
|
||
|
||
> **RECONCILED 2026-08-01.** Phase 1 (MLB model truth) is the ACTIVE phase and is
|
||
> further along than the table above implies — but not because orders were
|
||
> ticked off as planned. Most of it turned out to be **connection and repair, not
|
||
> construction**: the ladder question dissolved (calibration is ruler-
|
||
> independent), the cap was discarding 95.7% of the slate, and two condition axes
|
||
> were wired but firing on zero rows.
|
||
>
|
||
> **Closed:** p_win ranking + edge retirement · calibration DECIDED · MLB isotonic
|
||
> DECIDED · grade cap 25→500 · book widening (display) · S59 invariant armed ·
|
||
> environment axis repaired.
|
||
>
|
||
> **Open in Phase 1:** matchup axis (next order) · the two accruing verdicts
|
||
> (`opportunity_drift`, `environment`) — **time, not code** · connect the still-
|
||
> dormant layers (similarity, Bayesian, distribution ladder).
|
||
>
|
||
> **Carried, not in Phase 1:** WNBA is **NOT BUILT**, not failed (its −0.12 was
|
||
> NBA-template machinery on WNBA data) · the consensus ruler is **MARKET, not
|
||
> SHARP**, and that is **PENDING-RECOVERY** until PropLine answers why Pinnacle
|
||
> MLB prop coverage stopped on 2026-07-31 (`BLOCKERS.md`) — do not enshrine it as
|
||
> permanent.
|
||
>
|
||
> **Remaining to the end state: ≈ 19 orders**, of which **~9 are unblocked today**.
|
||
|
||
---
|
||
|
||
# DEFINITION OF DONE
|
||
|
||
**VYNDR is complete when:**
|
||
1. **MLB layers 1–8 are BUILT AND CONNECTED** — similarity, archetypes, fitted
|
||
weights, conditions and Bayesian all feed the grade; calibration applied; the
|
||
ladder monotone (A>B>C, no inversion) and proven on held-out data.
|
||
2. **Every listed sport is finished on the same 8-layer template**, or explicitly
|
||
held out with its reason recorded — **"not built yet"** where no sport-specific
|
||
model exists, and only "measured and failed" where one was genuinely built and
|
||
tested. Never silently absent, and never a verdict on a sport we never attempted.
|
||
3. **Design fully implemented** — all 61 catalogued items BUILT-TO-SPEC.
|
||
4. **Every surface built, reachable and honest** — no orphans, no live-but-not-honest
|
||
surface, no dead component.
|
||
5. **Resolution pipeline live end-to-end** — settle → share card / notification /
|
||
post / recap, firing on a real settlement.
|
||
6. **Sport boundary is a registry** — a new sport is a module, not a core edit.
|
||
7. **Chrome audit passed**, logged-out and entitled.
|
||
8. **The record is publishable on its own terms** — CLV either trustworthy-and-
|
||
representative or honestly absent; no claim outruns its evidence.
|
||
|
||
**The remaining work is finite and countable: ~23 orders across 7 phases.**
|
||
|
||
---
|
||
|
||
## STANDING LAWS (carried into every order)
|
||
Truth Law — absent beats wrong, no fabrication up or down · per-sport models, never
|
||
a global engine · lookahead guard (lock-time fields only) · overfitting guard (fit
|
||
one split, prove another) · never mint A's without new information · aggregate proof
|
||
is free, itemized judgment is paid · one canonical founder flag · atomicity by unique
|
||
index, never a count · cache-bust every post-deploy check · verify-after-write.
|
||
|
||
|
||
---
|
||
|
||
# 9. WHAT'S ACTUALLY MISSING FOR THIS TO WORK AS A PRODUCT
|
||
|
||
*The phases above say what is UNBUILT. This says what is missing for VYNDR to
|
||
genuinely do what it claims. Some of it is not a build, and one of it is not
|
||
fixable by us at all. Written plainly because a plan that only counts code is the
|
||
comfortable version.*
|
||
|
||
## 9.1 🔴 THE CENTRAL ONE: there is no demonstrated edge yet
|
||
|
||
Every edge measurement this session came back **null, negative, or unproven**:
|
||
|
||
| measurement | result |
|
||
|---|---|
|
||
| served grade → outcome | **r ≈ 0.005**, and **inverted** (B 52.4% < C 56.9%) |
|
||
| p_win − fair_prob (3 formulations) | **negative in all three, both sports, both splits** |
|
||
| p_win alone, MLB, holdout | +0.165, **p ≈ 0.07 — not significant** |
|
||
| p_win alone, WNBA | **negative — but this measured an NBA-template model on WNBA data, so it is not a WNBA result at all** |
|
||
| CLV / beat-close | **null by guard** — instrument not trustworthy |
|
||
| ROI by grade | likely an artifact of a meaningless letter |
|
||
|
||
**The product's core claim — "our read is better than the market" — is not
|
||
currently supported by our own data.** Everything else in this plan is
|
||
scaffolding around that. Building all 23 orders and *not* closing this leaves a
|
||
beautifully-built product that doesn't do the one thing it sells.
|
||
|
||
**What closes it:** not code. **Sample and honest iteration.** The instrument
|
||
fields are ~10 days old (442 rows). At ~90 decided MLB rows/week, that is ~6–10
|
||
weeks *of accrual*.
|
||
|
||
> **CORRECTED 2026-07-31 — see §11.** The "6–10 weeks out" above quietly assumed
|
||
> the clock is **already running**. It is not. Those 442 rows measure a model
|
||
> with a bent single-book ruler and six disconnected layers — **a model that
|
||
> will not exist once Phase 1 lands.** They do not count toward the verdict and
|
||
> **must not be pooled** with post-completion rows.
|
||
>
|
||
> The clock starts at a **verified** "running as intended" gate, per sport.
|
||
> **6–10 weeks is the accrual duration, not the distance to the answer.** The
|
||
> distance to the answer is *build time + verification + 6–10 weeks.*
|
||
>
|
||
> Independently forced by arithmetic, not just discipline: the §10.1 ruler fix
|
||
> changes the denominator, so pre-fix and post-fix edge/CLV numbers are not the
|
||
> same measurement.
|
||
|
||
## 9.2 The projection — the actual engine — is thin and unvalidated
|
||
The grade's only real inputs today are **l5/l20 averages, an opponent rank, rest
|
||
and usage**. Similarity, archetypes, park/weather/platoon and the Bayesian layer
|
||
are all built and **not connected**. So VYNDR is currently a recent-form average
|
||
wearing an intelligence system's clothes. Phase 1 connects them — **but connecting
|
||
them is a hypothesis, not a guarantee.** They must each prove out on held-out data
|
||
or be left disconnected honestly.
|
||
|
||
## 9.3 A one-sport product marketed as multi-sport
|
||
MLB is the only model that has been BUILT and passed. WNBA's model does not exist
|
||
yet (what was measured was NBA-template machinery on WNBA data). NBA and soccer
|
||
**don't even settle** — they grade into a void. Until Phase 5, the honest framing
|
||
is *"an MLB product with other sports in development."* The site should not imply
|
||
otherwise.
|
||
|
||
## 9.4 No customers, therefore no feedback loop
|
||
**3 users, 0 paid.** The founder mechanism is built and race-proven, `/record`
|
||
exists, the gate works — and **none of it has met a real user.** Nothing here is
|
||
validated by usage: not the price, not the tier line, not whether the locked-shell
|
||
tease converts, not whether anyone wants this. **The first 10 real users will
|
||
teach more than the next 10 build orders.**
|
||
|
||
## 9.5 No distribution — the biggest non-code gap
|
||
There is no acquisition path at all. The newsletter send is unscheduled, share
|
||
cards are unbuilt (blocked on the resolution tail), social proof has no fuel
|
||
(needs a real record), partner/affiliate links are all `enabled:false`. **A product
|
||
nobody sees cannot be validated regardless of how good the model gets.** This
|
||
appears in no phase above and belongs on the board as its own track.
|
||
|
||
## 9.6 The read isn't actionable at the last mile
|
||
Push-to-book is a **teaser** — no affiliate is live, so a user who trusts a read
|
||
still leaves to place it manually. Bankroll guidance (Kelly) is Desk-gated. The
|
||
gap between *"here's a good read"* and *"I placed it"* is unclosed.
|
||
|
||
## 9.7 Operational fragility
|
||
Single-box, single Redis (persistence is a Coolify setting, not app-controlled),
|
||
one cron. Settlement silently covers 2 sports. **Three credentials remain flagged
|
||
for rotation, including a Stripe live key that transited a chat transcript.** No
|
||
staging environment — every verification this session ran against prod.
|
||
|
||
---
|
||
|
||
## THE HONEST SUMMARY
|
||
|
||
**Built well:** the truth infrastructure. Honest empty states, refusal paths,
|
||
n-gates, the append-only ledger, the settled/live gate, the atomic founder cap.
|
||
**This codebase does not lie about what it knows** — that is rare and it is real.
|
||
|
||
**Not yet true:** that the model beats the market. Not disproven either — *unmeasured
|
||
at adequate n*, on one sport, with a projection whose best layers aren't connected.
|
||
|
||
**So the finish line is not 23 orders.** It is 23 orders **plus a verdict from
|
||
accrued data that we cannot rush** — and the discipline to report that verdict
|
||
honestly if it says the edge isn't there. The plan above builds the machine. Only
|
||
time and honest measurement decide whether the machine is right.
|
||
|
||
|
||
---
|
||
|
||
# 10. TO BE A REAL AGGREGATOR *AND* A MODEL PEOPLE PAY FOR
|
||
|
||
*Kev's question: what makes this the top product, not just a finished one. The
|
||
answer that matters most: **the aggregator gap and the model gap are the SAME gap
|
||
in two places.** Fix the data breadth and both halves improve at once.*
|
||
|
||
## 10.1 The one finding that reframes everything
|
||
|
||
> **CORRECTED 2026-07-31 by Order Zero — `specs/order-zero-book-breadth-test.md`.**
|
||
> The original text below blamed a missing `regions`/`bookmakers` param. **That
|
||
> was wrong.** PropLine's OpenAPI contract states verbatim: `bookmakers` …
|
||
> **"Omitted = all books."** Omitting it is correct and always was.
|
||
>
|
||
> **The real cause is ours.** PropLine sends **18 books**; `oddsNormalizer`
|
||
> `ALLOWED_BOOKS` (11 entries) intersects them at **exactly 5** — which is
|
||
> precisely the "5 MLB books" the audit measured. We discard 13 of 18 ourselves,
|
||
> and 6 of our 11 allow-list entries don't exist at PropLine at all.
|
||
>
|
||
> Measured on real public data (MLB `pitcher_strikeouts`, 5 complete events):
|
||
> the feed carries **4.41 books/prop**; after our filter, **1.50** — and **12 of
|
||
> 34 props become invisible entirely** (zero allowed books).
|
||
>
|
||
> Also corrected: "73% single-book" is the **long tail** of deep/reliever props
|
||
> sole-posted by DraftKings or Bovada. **On the core props we actually grade,
|
||
> the market is 10–12 books wide.**
|
||
>
|
||
> And a real negative: **`pinnacle` appears on 0 of 40 MLB props.** The one
|
||
> sharp book in our allow-list contributes nothing here. The independent
|
||
> low-vig references that *are* present on 100% of core props are **exchanges**
|
||
> — `novig`, `smarkets`, `kalshi` (+ `matchbook`, `polymarket`). DFS pick'em
|
||
> (`prizepicks`/`underdog`/`sleeper`/`dabble`) also covers 100% but is **not a
|
||
> market price** and must never enter a consensus.
|
||
>
|
||
> **So this is not a test to run. It is a build we can do: split one allow-list
|
||
> into takeable / reference / excluded, and make `fair_prob_lock` a median
|
||
> consensus across reference books.** No param, no cost, no tier, no new source.
|
||
|
||
**Our "market" is often ONE book.** MLB props are **73% single-book** (2.18 audit).
|
||
And `proplineAdapter` sends only `{ apiKey, markets }` — **no `regions`, no
|
||
`bookmakers` param** (`:152`). We take PropLine's *default* response.
|
||
|
||
That single fact causes four separate problems we have been treating as unrelated:
|
||
1. **No line shopping** — the #1 free-tier hook in this category needs many books.
|
||
2. **`fair_prob_lock` is a de-vigged SINGLE SOFT BOOK**, not a consensus. That is
|
||
the ruler the model is judged against — a bent one. (Flagged as T1; never run.)
|
||
3. **CLV is weak** — you cannot measure "beat the close" against one book's close.
|
||
4. **No steam/disagreement detection** — needs ≥2 books to even exist.
|
||
|
||
*(Items 1–4 stand. Only the **cause** was wrong — and the fix is cheaper than
|
||
the original diagnosis implied.)*
|
||
|
||
### 10.1b We use 1 of PropLine's 29 endpoints
|
||
|
||
Reading the full spec surfaced ten unused endpoints that map directly onto §10.2
|
||
and §10.3 gaps — `/odds/closing` ("the canonical CLV helper"), `/odds/history`,
|
||
`/exports/odds-history`, `/movement` (steam across all 16 books), `/best-line`,
|
||
`/ev`, `/results` + `/exports/resolved-props` (**resolution across 33 sports**),
|
||
`/context` (**free**: probable pitchers, confirmed lineups, home-plate umpire,
|
||
first-pitch weather), `/markets/hit-rates`, `/players/{n}/trends`.
|
||
|
||
Two overturn standing beliefs: **"NBA/WNBA/soccer have no free settled feed"** may
|
||
be a **$19/mo** problem rather than a data problem; and we hand-built probable
|
||
pitchers / depth charts / lineup confirmation that `/context` serves free.
|
||
|
||
> **VERIFIED 2026-08-01 with the real key** — `specs/order-zero-consensus-ruler.md`.
|
||
> `/context` **WORKS, FREE** (umpire, roof, pitcher handedness, lineup
|
||
> confirmation — richer than what we hand-built). `/odds/closing` and
|
||
> `/movement` are **REDACTED** on our tier (full structure, **zero prices**) —
|
||
> my first pass wrongly called them "works" on a non-empty body. `/results` and
|
||
> `/exports/resolved-props` are **403**.
|
||
>
|
||
> **But the settlements exist to be bought:** `/markets/resolution-summary`
|
||
> shows **soccer graded across ~15 competitions in 30 days** (MLS 41k, Liga MX
|
||
> 15k, Brasileirão 12k, UCL/Europa/Conference…). **"Soccer grades into a void"
|
||
> is a $19/mo Pro-tier problem, not a data problem.** NBA is absent because it
|
||
> is July — seasonal, not a coverage gap, and not inferable either way.
|
||
>
|
||
> **WNBA is NOT thin at the feed** — 4.21 books/prop vs MLB's 3.61. It was
|
||
> allow-list-starved exactly as MLB was. This removes one candidate explanation
|
||
> for its −0.12 result. **And that result is not a WNBA verdict anyway** — it
|
||
> measured NBA-template machinery on WNBA data. WNBA is **NOT BUILT YET**, held
|
||
> out until it gets its own model.
|
||
>
|
||
> **No sharp anchor exists for props:** `pinnacle`, `matchbook` and `polymarket`
|
||
> all measured **0%** on both sports. The consensus ruler is therefore a MARKET
|
||
> consensus, not a SHARP one — stated as a permanent limitation, not a milestone.
|
||
|
||
## 10.2 What a real DATA AGGREGATOR has that we don't
|
||
|
||
| capability | ours | gap |
|
||
|---|---|---|
|
||
| **Book breadth** | 5 admitted of **18 sent**; 4.41→1.50 books/prop after our own filter | **not a feed gap — one allow-list (10.1).** Core props are already 10–12 books wide |
|
||
| **True consensus / no-vig line** | single-book de-vig | **unblocked now**: median across reference books (exchanges + pinnacle), n≥2 or labelled fallback |
|
||
| **Historical odds archive** | **STARTED** — `closing_captures` 844k rows, `lock_lines` (033) new, in-grade history capped at **24 points** | no full open→close series per prop. This is what makes CLV and backtesting real |
|
||
| **Market breadth** | 11 live markets | the category ships 50+ (alt lines, combos, innings, quarters) |
|
||
| **Alt-line ladders from books** | we *compute* a ladder; we don't *ingest* the books' | users shop rungs |
|
||
| **Injury / lineup wire** | partial (`depthChart`, confirmed-vs-projected) | no real-time news wire |
|
||
| **Player news** | `NewsWire` on Explore | not beat-level, not per-prop |
|
||
|
||
**None of this is model work. It's ingestion.** And it is the half competitors
|
||
compete on hardest, because it is visible to a free user in five seconds.
|
||
|
||
## 10.3 What a prediction model people PAY for has that we don't
|
||
|
||
1. **A distribution, not a point.** We project a point (l5/l20 average) and take
|
||
an empirical `P(over)`. Paid-tier models simulate a **full distribution per
|
||
stat** (negative-binomial / Poisson / MC). **We already have this** —
|
||
`projection/distribution.js` computes real survival probabilities and a rung
|
||
ladder — but it is **proj-v1.1, ledger-only, and it lost to the champion.** The
|
||
asset exists; it is unconnected and unproven.
|
||
2. **Opportunity modelled FIRST.** In props, playing time is the dominant driver —
|
||
plate appearances, snaps, minutes, batting-order slot. We carry `ab_per_game`
|
||
and minutes as *features*, not as a **projected opportunity** with its own
|
||
uncertainty. This is the single biggest modelling upgrade available.
|
||
3. **Per-stat models.** Hits, strikeouts and total bases have different shapes.
|
||
One additive factor index across all of them is why the ladder is meaningless.
|
||
4. **Matchup granularity that actually reaches the grade.** Arsenal, handedness,
|
||
park, weather, platoon — **all built, all challenger-only, none feed the grade.**
|
||
5. **Calibrated probabilities with honest intervals.** Measured (isotonic
|
||
qualifies on MLB) — **not applied.**
|
||
6. **A backtest harness on real historical odds.** Blocked by 10.2's archive gap:
|
||
you cannot backtest a price you never stored.
|
||
7. **CLV as the north-star metric**, published honestly. Instrument built,
|
||
guard-blocked, and weak until book breadth lands.
|
||
|
||
## 10.4 The uncomfortable pattern
|
||
|
||
**Almost every model capability above is ALREADY BUILT and DISCONNECTED**:
|
||
similarity, Bayesian, archetypes, park/weather/platoon, the distribution ladder,
|
||
calibration. VYNDR does not have a *building* problem. It has a **connection and
|
||
proof** problem — plus one genuine ingestion gap (book breadth) that starves both
|
||
halves at once.
|
||
|
||
That is good news: the expensive part is largely done. But it also means **no new
|
||
feature fixes this.** Connecting the layers and proving them on held-out data is
|
||
the work.
|
||
|
||
## 10.5 If I had to order it for "top product"
|
||
|
||
1. ~~Book breadth **test**~~ → **Book breadth FIX + consensus fair line** (10.1).
|
||
The test is done (Order Zero). It is now a build: split `ALLOWED_BOOKS` into
|
||
takeable / reference / excluded, make `fair_prob_lock` a median consensus.
|
||
Upgrades aggregator, model denominator and CLV together. **Gated on** exchange
|
||
prop-price validation + the WNBA measurement (needs the PropLine key).
|
||
2. **Opportunity projection** (10.3.2) — the biggest genuine modelling gain.
|
||
3. **Per-stat distributions** — connect `distribution.js`, prove per stat.
|
||
4. **Connect the built layers** (Phase 1) — each proven on held-out or left off.
|
||
5. **Full odds archive** — store every book's open→close; unlocks backtesting.
|
||
6. **Market breadth** — 11 → 50+ markets is mostly ingestion + the 4-layer wiring.
|
||
7. **Then** the sports rollout, on a template that is actually worth replicating.
|
||
|
||
**The ordering principle:** do not replicate a thin model across six sports. Get
|
||
MLB genuinely good first — a copied-six-times thin model is six times the
|
||
maintenance for the same absent edge.
|
||
|
||
## 10.6 The honest caveat on "top product"
|
||
|
||
The category's leaders are judged on one number: **do their picks beat the closing
|
||
line, at scale, published.** We cannot claim that yet — not because the product is
|
||
unfinished, but because **we have not measured it at adequate n on a market we can
|
||
trust.** Book breadth + the odds archive + accrued settlements are what make that
|
||
claim *possible*. Everything in §10 is in service of being able to make it — or of
|
||
being able to say honestly that we can't.
|
||
|
||
|
||
---
|
||
|
||
# 11. THE ACCRUAL CLOCK — SEQUENTIAL, POST-COMPLETION
|
||
|
||
*Kev's correction, 2026-07-31. Supersedes any "accrual runs in parallel with
|
||
building" framing anywhere in this document.*
|
||
|
||
## 11.1 The causality
|
||
|
||
You cannot meaningfully accrue until the product is **right and running as
|
||
intended** — model layers connected, ruler fixed (real consensus, not one soft
|
||
book), sports in, operating in the vision.
|
||
|
||
**Only then does the accrual clock start, and only then does time produce a
|
||
verdict. Building faster shortens the time TO clock-start. It never runs the
|
||
clock.**
|
||
|
||
The flawed assumption being removed: that today's half-connected model accrues
|
||
useful evidence while we build. It does not. That data measures a model that
|
||
**will not exist** after the layers are connected — a different model wearing the
|
||
same name.
|
||
|
||
## 11.2 Measurement rule (non-negotiable)
|
||
|
||
- Settled data accrued **before** completion does **NOT** count toward the edge
|
||
verdict.
|
||
- **NO POOLING across the completion boundary.** Pre-fix and post-fix are
|
||
different models — the same class of error as the model-version boundary, at
|
||
whole-model scale.
|
||
- The clock starts at **verified** "running as intended", not at "today".
|
||
|
||
## 11.3 Two clocks — stated separately so neither corner-cuts
|
||
|
||
**1. MLB-MODEL VERDICT clock.** Starts when MLB is genuinely complete: built
|
||
layers connected + honest consensus ruler + running as designed. Its accrued n
|
||
judges **MLB**.
|
||
|
||
**2. FULL-PRODUCT TRACK RECORD clock.** Starts when the vision is running:
|
||
sports in, aggregator built, operating as intended. Its accrued n judges **the
|
||
product claim**.
|
||
|
||
**Each subsequent sport gets its OWN clock**, starting when *that* sport's model
|
||
is complete — never when it is stubbed in. (Per-sport doctrine: a sport that
|
||
merely renders is not a sport that measures.)
|
||
|
||
## 11.4 "Complete" — defined honestly
|
||
|
||
A model/sport is **complete-enough-to-accrue** when its **built layers are
|
||
connected** and it runs against an **honest ruler** (real consensus), **operating
|
||
as designed** — *not* when every conceivable feature exists.
|
||
|
||
This definition is doing real work in both directions: it blocks the corner-cut
|
||
("close enough, start counting") **and** it blocks never-ship ("one more
|
||
feature"). The full-product claim additionally requires the vision's sports +
|
||
aggregator running.
|
||
|
||
## 11.5 The verification gate
|
||
|
||
**Between build and clock.** Before any accrual counts, verify — not assume —
|
||
that the product is running as intended:
|
||
|
||
- connected layers actually **fire** (present in the served payload, not merely
|
||
present in the repo)
|
||
- the ruler is a **real consensus** (`fair_prob_source: 'consensus_n'`, n≥2)
|
||
- the sport **settles correctly** (spot-checked against real box scores)
|
||
- surfaces are **honest** (no fabricated values; absent renders absent)
|
||
|
||
**This gate is verified, not assumed.** No accrual line item runs during the
|
||
build phases.
|
||
|
||
## 11.6 The 10-user track, reframed
|
||
|
||
Real users are onboarded to a **complete** product, so their usage teaches about
|
||
the real thing rather than a half-built one.
|
||
|
||
**We do NOT acquire users early to "start accrual."** That is the corner being
|
||
explicitly refused.
|
||
|
||
## 11.7 The finish line
|
||
|
||
```
|
||
build orders complete
|
||
↓
|
||
running-as-intended VERIFIED (§11.5 gate)
|
||
↓
|
||
clock starts (per sport, two clocks, §11.3)
|
||
↓
|
||
verdict reported honestly — including if it says the edge is not there
|
||
```
|
||
|
||
That last clause is the whole point. A clock you are willing to stop early is not
|
||
a measurement, and a verdict you are only willing to publish if it is favourable
|
||
is not a verdict.
|