Files
vyndr/specs/order-zero-widening.md
builtbykev c79528abae Order Zero: tier-reality report + widening fingerprint
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
2026-08-01 00:57:12 -04:00

167 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.