c79528abae
PHASE 1 resolved on our real keys, and a bad source was discarded 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 (200-redacted, and 6,196 two-sided PRICED groups on MLB). Not cited. The report uses only the machine-readable OpenAPI contract and the verbatim detail bodies our keys received. Verdict: every one of the six endpoints behaves exactly as the Free tier's published contract says. error:"upgrade_required" with an explicit required_tier is unambiguous -- NOT a key-permission problem, NOT a plan problem. $9/mo Hobby buys /results + /odds/closing (the CLV instrument) + /movement (steam across 18 books); $19/mo Pro adds the 90-day settlement export. Priced and evidenced; not recommended here -- it is a decision. PHASE 2 fingerprint on the SERVED feed: 5 books -> 13, props rendered 546 -> 2,780 (5.1x), mean 4.22 books/prop. The unflattering half, stated up front: of 2,234 newly-visible props only 698 (31.2%) carry a real non-DFS market price; 1,536 (68.8%) are DFS-only pick'em rows. The honest headline is not "80% of the slate unlocked" -- the board is 5x fuller, about a third of the new depth is real market data, and the rest is pick'em inventory now shown but tagged. PHASE 3 verified: 546 gradeable props, unchanged. CURRENT_RULER_VERSION still v1_first_book. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
167 lines
7.5 KiB
Markdown
167 lines
7.5 KiB
Markdown
# 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.
|