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
This commit is contained in:
Kev
2026-08-01 00:57:12 -04:00
parent 68c5b65427
commit c79528abae
2 changed files with 169 additions and 1 deletions
+3 -1
View File
@@ -54,7 +54,9 @@ model-shaped is downstream of it:
| **MLB isotonic `p_win`** | **PROVISIONAL** | calibrated on the bent ruler; does not promote until re-run |
| **WNBA** | **still abstains** | a MODEL problem, not a coverage problem — coverage was never its constraint |
| **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` + `/exports/resolved-props` **403** | free tier behaving exactly as its contract says |
| **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 |
| **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
+166
View File
@@ -0,0 +1,166 @@
# 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.