# Order Zero — TIER REALITY + AGGREGATOR WIDENING **Date:** 2026-08-01 · Phase 1 verified on **our real prod keys** · Phase 2 **LIVE** · Phase 3 **model path byte-identical**. **Gates:** 4,028 tests / 322 suites green · `next build` **exit 0** · tier-reality report per endpoint · widening fingerprinted on the served feed · model input unchanged. --- ## PHASE 1 — THE FREE-TIER DISCREPANCY, RESOLVED The order was right to demand this. **I found and discarded a bad source on the way.** A fetched rendering of PropLine's docs "tier matrix" claimed `/odds/closing` is **403 on free** and that `/odds` returns **prices nulled** on free. Both are contradicted by direct observation: we get `200` redacted on `/odds/closing`, and **6,196 two-sided PRICED prop groups** on MLB `/odds`. That matrix is not cited anywhere below. **Two authoritative sources are used instead:** the machine-readable OpenAPI contract, and the verbatim `detail` bodies our own keys received. ### The verdict per endpoint | endpoint | our keys | verbatim reason | verdict | |---|---|---|---| | `/events/{id}/results` | **403** | `upgrade_required` · **`required_tier: "hobby"`** · *"Prop resolution is a paid feature — start at $9/mo Hobby"* | **Free-tier exclusion. $9/mo.** | | `/exports/resolved-props` | **403** ×4 sports | `upgrade_required` · **`required_tier: "pro"`** · *"Resolved-prop exports are a Pro feature"* | **Free-tier exclusion. $19/mo.** | | `/events/{id}/odds/closing` | **200, 1,847 outcomes, 0 priced**, `upgrade_url` present | OpenAPI: *"Paid tiers: full data. Free tier: redacted (structure only)."* | **Free-tier redaction. $9/mo.** | | `/events/{id}/movement` | **200, 1,847 outcomes, 0 priced**, `steam: []` | OpenAPI: *"Paid tiers (Hobby+): full data. Free tier: redacted."* | **Free-tier redaction. $9/mo.** | | `/events/{id}/context` (MLB) | **200, full data** | — | **Free. Works.** | | `/events/{id}/context` (WNBA) | 404 · *"No context on file for this event yet."* | — | **Not a tier issue — MLB-only coverage.** | **It is NOT a key-permission problem and NOT a plan problem.** The keys return exactly what the Free tier's published contract says they should, on every one of the six endpoints. `error: "upgrade_required"` with an explicit `required_tier` is as unambiguous as this gets. ### What money would buy | tier | cost | unlocks | |---|---|---| | **Hobby** | **$9/mo** | `/results` (settled outcomes + actual values) · `/odds/closing` (**the CLV instrument**) · `/movement` (**steam across 18 books**) · `/odds/history` (30d) · `/players/trends` + `/history` · `/ev` | | **Pro** | **$19/mo** | the above + `/exports/resolved-props` — **90-day bulk settlement CSV incl. closing price**, the backtest input | **$9/mo buys the two instruments we have been unable to build** — a real closing line and cross-book steam. `/exports/odds-history` (the full tick firehose) stays Enterprise-only at any price we'd consider. **I am not recommending the spend in this order.** It is a decision, priced and evidenced, for you to make. --- ## PHASE 2 — AGGREGATOR WIDENING (LIVE) `normalizeProps` now emits every **DISPLAY** book instead of 5 of 18. ### Fingerprint — the SERVED MLB feed, post-deploy | | before | after | |---|---:|---:| | distinct books served | 5 | **13** | | book rows | — | **11,737** | | props rendered (player+stat) | **546** | **2,780** | | mean books/prop | ~0.6 | **4.22** | **Props that render now and rendered nothing before: 2,234.** The slate a user sees is **5.1× larger.** ### The part that isn't flattering, stated up front Of those 2,234 newly-visible props: - **698 (31.2%)** carry at least one **real non-DFS market price** — genuinely new market data. - **1,536 (68.8%)** are **DFS-only** — PrizePicks/Underdog/Sleeper/Dabble rows, which are **not a market** at all. So the honest headline is **not** "80% of the slate was unlocked". It is: *the board is 5× fuller, about a third of the new depth is real market data, and the rest is pick'em inventory that is now shown but tagged.* Served role mix: `dfs` 8,515 · `reference` 2,042 · `both` 791 · `offshore` 389. ### `book_role` is load-bearing, not decorative Every row carries `book_role` ∈ {`both`, `takeable`, `reference`, `dfs`, `offshore`} — through the normalizer **and** through the odds route's grouping, which was silently dropping it. **Why it matters, from live data.** On one MLB prop: ``` betmgm +450 / -750 ← a real two-way market prizepicks +100 / +100 ← fixed-payout pick'em ``` Rendered side by side untagged, the PrizePicks row reads as a **dramatically better price**. It is a different product. Untagged breadth would have been a new way to mislead, dressed as a feature — which is exactly what the split exists to prevent. Consumers gate on `book_role !== 'dfs'` before treating a row as a market price. The `?book=` filter now accepts any DISPLAY book, since shopping a real book against an exchange is the point. --- ## PHASE 3 — THE MODEL DOES NOT MOVE `bookRoles.js` splits **`MODEL_BOOKS`** (the legacy allow-list, character for character) from **`DISPLAY_BOOKS`**. Both model paths re-filter before they pick a line: 1. **`gradeSlateService.dedupeProps`** — filters to `MODEL_BOOKS` **before** first-row-wins and **before** the 25-prop limit, so the graded set is byte-identical through the widening. A test asserts exactly this. 2. **`intradayRefreshService.indexOddsProps`** — the one that would have bitten. It **re-grades props at the current line**, so without a gate the widened feed would have silently moved locked lines onto DFS and exchange numbers the model has never been calibrated against. **Props the model can grade: 546 — unchanged.** `CURRENT_RULER_VERSION` is still **`v1_first_book`**. **The gate lifts only when the MLB calibration is re-run on the consensus ruler and v2 is promoted.** --- ## HONEST FRAMING — do not let this read as model progress - **This is an AGGREGATOR win. It does not fix the model.** Not one grade changed. - **WNBA still abstains.** It is a **model** problem, not a coverage problem — WNBA is *better* covered than MLB (4.21 vs 3.61 books/prop). Coverage was never its constraint. - **MLB isotonic `p_win` is still PROVISIONAL** until re-run on the consensus ruler. - **The consensus is MARKET, not SHARP.** `pinnacle`, `matchbook` and `polymarket` are **0%** on both sports. **No sharp anchor exists in our feed** — and none is purchasable from this provider, because it is a coverage fact, not a tier gate. - **A third of the new depth is real; two thirds is pick'em.** Both are shown; only one is a market. --- ## THREE SUPERSEDED TESTS, UPDATED TO STRONGER PROPERTIES (not deleted) 1. `roleOf(dfs)` returned `'excluded'` → now names the **kind** (`'dfs'`), plus assertions that it is display-visible, non-reference and non-model. 2. *"filters out books not in the allowed set"* → now asserts the display set **widens with correct role tags**, unknown books are **still dropped**, and the **model set does not move**. 3. *"returns 400 for invalid book"* (`bovada`) → split into: an **unknown** book still 400s, and a **newly-visible** one no longer does. ## OPEN 1. **$9/mo Hobby** — buys the CLV instrument and steam. Evidenced above; your call. 2. **$19/mo Pro** — adds the 90-day settlement export, which is the backtest input and the thing that makes soccer settle. 3. **The display layer should now surface multi-book line shopping** — the data is live and tagged; no surface consumes it yet. That is a frontend order.