Display ORDERING only. No grade, ledger row, lock_line, scoring, or edge_pct
scale/display change. Push scoring untouched.
Two defects removed from selectTopGrades (wrong at ANY scale, independent of
edge_pct's separate retirement):
1. edge: Math.abs(numOr(g.edge, -Infinity)) — abs() on an already-
direction-signed value ranked the model's strongest DISAGREEMENTS level
with its strongest agreements (177 public ledger rows carry a negative
edge; positive = the model AGREES with the graded side).
2. Math.abs(-Infinity) === Infinity, so a row with NO edge sorted FIRST —
absent data presented as the top pick (the Number(null) class).
New key: grade -> confidence -> takeable-gated p_win (nulls LAST) -> SIGNED
edge (nulls LAST) -> input order. Scales are never mixed in one comparator.
Takeable band = web valueState.isTakeable, asserted byte-equal to the hero's
config/valueEngine.isTakeable (-160..+200) incl. strict-null.
Alt-line ladder (analyzeViaEngine1:506) no longer sorts by edge_pct: ordered
highest-p_win-first derived analytically at zero added compute — P(stat >= k)
is monotone non-increasing in k, so p_win-desc is line-ASC for an over and
line-DESC for an under. base stays marked; no consumer depends on
alt_lines[0]; deskShowcaseService.rungsOf already re-sorted by line.
THREE PREMISE BREAKS found report-first, before code:
- /api/props/top-graded 404s in prod (absent from src/) so the dashboard
board renders receipts/empty — the edge sort orders nothing there today.
The prior order's "97.3% of rows tie" was a LEDGER measurement wrongly
extrapolated to that board. Fix is correct-in-itself and lands when the
feed is restored.
- p_win cannot be a client-side key for all tiers: snapshotGating strips it
for unentitled tiers ("shipping p_win is shipping the model price").
Verified live: prod /api/snapshot carries p_win on 0/8 MLB, 0/25 WNBA.
- Ladder rungs carry no per-rung price, so the takeable gate is inapplicable.
Verified on real data, both sports, both paths: unentitled — WNBA (n=25)
ordering CHANGED, MLB (n=8) unchanged, signed edge non-increasing in every
(grade,confidence) tie group (20 pairs, 0 violations); entitled — 40 real
ledger rows with p_win+locked_odds, p_win-descending, untakeable chalk NOT
promoted (Trea Turner .757 @-275 does not beat Rhyne Howard .745 @-120)
(36 pairs, 0 violations).
Hero consistency, stated honestly: same signal + same gate, different
precedence BY CONTRACT (board = grade-tier-first "top GRADES"; hero =
p_win-first "top read"). Identical within the leading tier (verified); across
tiers the board may lead with an A the hero doesn't pick. Not a contradiction.
Floor: 310 suites / 3864 tests green, web build exit 0. Dashboard + Desk
visuals are auth/feed-gated -> tagged for the Chrome audit, no visual faked.
Held: edge_pct rescale/display retirement (Order B); building the missing
/api/props/top-graded selector; exposing p_win to unentitled tiers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
21 KiB
VYNDR — CANONICAL STATE FILE
Read-only ground-truth audit. Written 2026-07-26 by Claude Code (repo + deployed DB access). Every item tagged VERIFIED (file/line or measured number), CANNOT DETERMINE (with reason), or BLOCKED (with what unblocks it). Where a prior claim conflicts with code/data, the code/data wins. Nothing was built, changed, deployed, or migrated by this audit.
REVIEW ZERO — PREMISE
- 0.1 What I can read — VERIFIED. The repo (full source), the deployed Supabase DB
(read-only via MCP, project
zmdnczhtdxcddsxzttub), and the deployed API (api.vyndr.app). - 0.2 Key reachability — VERIFIED.
PROPLINE_API_KEY_*are NOT on the box (.envabsent); they were pasted in-session earlier this conversation and are usable for probes.ODDS_API_KEYis on the box but exhausted (0/500).ODDSPAPI_KEYnot on box. DB-dependent items were run against prod directly, so nothing here is BLOCKED on PropLine keys.
PHASE 0 — THE ROI RECONCILIATION (headline)
-
0.1 ROI computed in code? — VERIFIED. Not for the model ledger. ROI exists only for user bet-tracking:
performanceService.js:42(roi: stats.roi) andbetService.js:172-174(profit = payout - amount). There is no ROI computation overledger_entries(the model's public record). That is why the model's ROI has been invisible. -
0.2 Price-at-grade persisted? — VERIFIED. YES —
ledger_entries.locked_odds(text, American), written byledgerService.recordPipelineGrades. Populated on essentially all settled rows (5 nulls on B). ROI IS computable for accrued history. -
0.3 11-point index persisted? — VERIFIED (nuanced). NOT in
ledger_entries— only the collapsed letter (grade);_grade_11is explicitlydeleted atgradeSlateService.js:97before the ledger write. BUT it IS retained inmodel_snapshots.grade_11(migration025:51,retentionService.js:117) — 2,888 of 4,600 snapshot rows carry it. So sub-tier is lost on the settled-outcome table but recoverable by joiningmodel_snapshotstoledger_entrieson (player, stat, line, date). STATE.md's "grade_11 is stored" and this audit's "deleted before ledger write" are BOTH correct — different tables. -
0.4 Price distribution by grade (American odds, settled) — VERIFIED:
sport grade n(decided) median q1 q3 MLB B 285 −270 −650 −155 MLB C 176 −169 −500 +109 WNBA B 208 −124 −145 −110 WNBA C 160 −120 −134 −108 Blended B median −160, C median −130. Overall range −10000 … +600. MLB grades skew to **deep favorites**; WNBA grades cluster tight around −120. -
0.5 Flat-stake unit ROI by grade (1u/row at
locked_odds, decided rows hit/miss, voids excluded) — VERIFIED:sport grade n hit% ROI% MLB B 285 70.2 −1.02 MLB C 176 60.8 +4.57 WNBA B 208 52.4 −4.79 WNBA C 160 51.9 −5.26 A / D / F (all) 1 / 2 / 4 0 / 0 / 0 −100 each (negligible n) Including voids as net-0 (my first pass) gives blended B −2.31%, C −0.10%. CONCLUSION — the premise's binary is a false dichotomy; the truth decomposes: - MLB B = high-hit (70%) break-even favorites — the premise's "favorites at fair prices, zero edge" hypothesis is CONFIRMED here.
- WNBA B/C = losing (~52% hit at ~−120 where break-even is ~54.5%).
- MLB C = genuinely +EV (+4.57% on 176 decided) — a real edge the blended "no ROI" masked. So: ROI is not uniformly zero-edge. It is not computed in code, and when computed here it shows one profitable segment (MLB-C) hidden under WNBA losses and break-even MLB-B.
-
0.6 64/56 population — VERIFIED. The displayed
/api/ledger/accuracyfigures arehits/(hits+misses), excluding voids (88 void rows, 8.8%) and unsettled (67). Distinctoutcomevalues are {hit, miss, void, null} — nopush(pushes structurally impossible: half-number lines). The ROI table above uses the SAME decided (hit/miss) population, so hit-rate and ROI are comparable. Blended B hit is 63.1% onhits/(hit+miss)but 55.9% on all-settled (the 64 void B rows are the gap). -
0.7 Hit rate in UI/marketing? — VERIFIED. Displayed on ≥10 surfaces:
app/page.tsx(landing),GradeCard,TopSignals,vyndr/TierRecord,vyndr/ModelRecord,vyndr/AccuracyBadge,game/[id]/page,u/[handle]/portrait,ledger/page,scan/page. ROI / edge is displayed NOWHERE. Honesty gap: users see "63% / 70% HIT" with no indication MLB-B is −1% and WNBA is −5%. -
0.8 CLV computed / closing persisted? — VERIFIED (with a broken-ness caveat).
closing_oddson 962/996 rows,clvon 842/996 — so CLV IS computed and closing IS persisted. BUTclosingCapture.js:7-8documents it as effectively broken:closing_line == locked_line on 92% of rows(only ~56 rows show real movement), becausecaptureClosingoverwrites the field with the current feed on every snapshot and most props leave the feed near their lock. CLV exists but is largely degenerate (≈0).
PHASE 1 — THE GRADED LINE
- 1.9 Selector location — VERIFIED. Two stages: (a)
gradeSlateService.dedupeProps(gradeSlateService.js:34, called:144); (b) thesnapshotServicededup atsnapshotService.js:425-434. - 1.10 Reads over which set? — VERIFIED. One provider at a time — PropLine-normalized rows
(primary) via
oddsService→recordDownstream→gradeAndCacheSlate. Falls back to odds-api / oddspapi only if PropLine fails (oddsServicefallback chain). Not all providers merged. - 1.11 dedupeProps exists? — VERIFIED. YES, it EXISTS (
gradeSlateService.js:34). Logic: Set on key`${player}::${stat_type}::${line}`— keeps the FIRST row per (player, stat, line), discards later rows at the same (player, stat, line) (i.e. other books at the same line), caps atlimit. Written Session 32 (f0c8b4f, "Grades pipeline + NFL/NHL wiring"). This settles the asserted/un-asserted question: it is real, not imagined. - 1.12 snapshotService 411-434 — VERIFIED. Confirmed: dedup by
`${nameKey}|${stat_type}`, keeps the row with the highestconfidence.confidencederives from the grade letter (band-midpoint fromgrade_thresholds.json,confidence_basis:'grade_band') — so "highest confidence" == "highest grade letter". It carries no information beyond the letter. - 1.13 consensus vs first-book — VERIFIED. NO consensus rule exists in code. The graded line is chosen by first-book-at-each-line (dedupeProps) then highest-grade-across-lines (snapshotService). The "book-agnostic consensus rule" description is a prior order's proposal, never built. The "first book" description is CLOSER to correct. Code wins: no consensus.
- 1.14 5 books at 3 lines → which line? — VERIFIED (walked).
normalizePropsemits one row per book →dedupePropskeeps the first book at each of the 3 distinct lines (3 survivors) →gradeAndCacheSlategrades both sides of each →snapshotServicekeeps the highest-grade of the 3. The graded line = whichever of the 3 lines grades highest (a best-grade-for-us selection, active now that the feed is 27% MLB / 82% WNBA multi-book). - 1.15 Consumers assuming one book/prop — VERIFIED (partial list): the grade path
(
dedupePropscollapses book multiplicity),analyzeViaEngine1(book_odds/fair_probuse the single surviving row's odds), the snapshot GameCard overlay,detectBestBook(no-ops <2 books). - 1.16 Test pinning graded line vs book-set change? — VERIFIED: NONE.
dedupePropsandsnapshotServicehave unit tests for dedup mechanics, but no test pins graded-line stability against a change in the book set (a multi-book different-line scenario). - 1.17 Ledger flag for pre/post book-set change? — VERIFIED. No dedicated flag.
model_versionstamps the model era and challenger-version columns exist, but nothing distinguishes grades by book-set basis. A silent book-set change would not be visible on the ledger.
PHASE 2 — THE FEED
- 2.18 Books-per-prop TODAY (2026-07-26, live
/api/odds) — VERIFIED, and it CORRECTS the prior "137/140 single-book, DK129/FD14" measurement — the feed has broadened:- MLB: n=322 →
{1 book: 234 (73%), 2: 51, 3: 26, 4: 10, 5: 1}. Books: draftkings 273, betmgm 105, betrivers 49, pinnacle 30, fanduel 2. So 73% single-book (mostly DK), 27% multi-book, 5 books now present (not DK-only). - WNBA: n=163 →
{1 book: 29 (18%), 2 books: 134 (82%)}. Books: fanduel 149, draftkings 148. WNBA is graded off a genuine 2-book feed (DK + FD) — better multi-book coverage than MLB.
- MLB: n=322 →
- 2.19 ALLOWED_BOOKS — VERIFIED.
oddsNormalizer.js:9= 11 books (draftkings, fanduel, betmgm, caesars, fanatics, bet365, hardrockbet, pointsbet, betrivers, pinnacle, thescore), applied at:110/:198/:235. Drops nothing today (every book in the live feed is on the list). - 2.20 PropLine request — VERIFIED.
proplineAdapter.js:128buildUrl = ${BASE}/${sportKey}/odds;:151-152params ={ apiKey, markets }. Noregions/bookmakersparam is sent. The feed broadening (2.18) happens on PropLine's DEFAULT response, not a param we added. - 2.21 PropLine docs / multi-book param — CANNOT DETERMINE (not re-tested this order). Prior
research (WebSearch): PropLine advertises "13 books + 5 exchanges; every payload includes a
bookmakers array" and is The-Odds-API-compatible (which uses
regions/bookmakers). Whether a param unlocks the full set on our tier is unconfirmed — would need a keyed test with the param.
PHASE 3 — THE CHALLENGER LEDGER
- 3.22 Row counts — VERIFIED:
challenger total settled first row note arch-v1 164 128 2026-07-21 94 with non-zero delta contact-v1 122 86 2026-07-23 107 non-null p_win_contact(15 abstained)proj-v1 + proj-v1.1 76 + 46 = 122 86 2026-07-23 119 with proj_pointAll MLB (challengers are statcast-gated → MLB only; the 407 WNBA rows carry none). Public ledger total = 996 rows (929 settled, 67 unsettled). **The earlier "ZERO settled p_win / measurement not begun" is now STALE — 86-128 settled per challenger.** - 3.23 Population rate — VERIFIED. 872 rows in the last 14 days across 12 active days (2 days had no rows). Challengers populate a fraction of rows (arch 164/996) — the rest are pre-deploy or WNBA.
- 3.24 Idempotent-lock lag — VERIFIED, still structural. The ledger upsert is
ignoreDuplicates:true, so a challenger's fields land ONLY on rows first written AFTER that challenger's code deployed (arch 07-21, contact/proj 07-23); re-running a snapshot never backfills challenger fields onto an already-locked row. Any challenger added later inherits the same gap. Settlement itself is healthy (stale_unsettled = 0). - 3.25 Silent-failure / abstain path — VERIFIED: none producing fake accrual. contact-v1's nulls are honest abstentions (thin/absent Statcast); proj-v1 projects 119/122; arch-v1's non-moved rows are byte-identical-to-champion (no distinctive axis). No caught-throw-writes-null path masquerading as accrual.
- 3.26 Void/DNP rate — VERIFIED. 88 void (8.8%) + 67 unsettled of 996. Matches the ~9% premise.
- 3.27 model_snapshots / archetype / opp_rank — VERIFIED (partial).
model_snapshotsis LIVE: 4,600 rows, latest 2026-07-26 22:01,grade_11on 2,888. Archetype +opp_rank_statwere verified live per-sport (MLB + WNBA) in prior orders; CANNOT re-confirm per-sport freshness here without additional queries (not run to keep this pass bounded).
PHASE 4 — WHAT IS ACTUALLY WIRED
- 4.28 SportsGameOdds wired? — VERIFIED. NOTHING. No SGO reference in
src/,web/src/, or.env. Not wired, configured, committed, or deployed. (The audit that qualified it as a source was report-only.) - 4.29 Design surfaces live vs designed — VERIFIED:
- S2 (book comparison / crown / disagreement):
BookComparison.tsxEXISTS inweb/src/components/but is NOT imported/routed anywhere (dead component).BookChip+BookWordmarkexist and are used.MovementStrip,CrownBadge→ NOT FOUND (never built). (Corrects an earlier order that said "BookComparison doesn't exist" — it exists, just unrouted.) - THE WIRE: = the daily newsletter/content format (see 4.30). Newsletter engine built
(
newsletterService); send is unscheduled (internal endpoint only). - S3 article media: NOT FOUND (no article-media generator;
mediaEngineonly has wire/ share text templates). - S-2 Offseason hub / SeasonBoard: NOT FOUND (never built).
- System / Intelligence: design files present in
specs/design-reference/; live components partial.
- S2 (book comparison / crown / disagreement):
- 4.30 What is THE WIRE? — VERIFIED. It is VYNDR's daily newsletter / content voice, not a UI
surface:
newsletterService.js:219-220(THE WIRE — {date}) +mediaEngine.js:6,121(MORNING WIRE,SIGNALdeterministic templates). It's the editorial format for the daily report. - 4.31 detectBestBook + LineSparkline — VERIFIED.
slateAdapter.detectBestBookreturns a book only when ≥2 books post the SAME line at differing prices (no-ops on 1 book, never marks a lone price "best").StatStrip.LineSparkline(StatStrip.tsx:130) renders only at ≥3 history points, returnsnullbelow. Both degrade honest-absent on single-book/shallow data. - 4.32 Line history / closing overwrite — VERIFIED.
history({t,line}, line-deduped, cap 24) is shallow — most props sit at 1-2 flat points (line moves are rare; books move odds, not the half-point line). Two distinct closing mechanisms: theclosing_capturesTABLE is INSERTED / appended (closingCapture.js:234), but theledger_entries.closing_line/closing_oddsis OVERWRITTEN every snapshot (ledgerService.js:18-21doc; overwrite is why CLV is degenerate — 0.8). - 4.33 Migration drift — VERIFIED, still present. Repo
supabase/migrations/has 001-022, 025, 030, 031, 032. MISSING: 023, 024, 026, 027, 028, 029 — applied to prod but untracked (they carry the challenger columnsp_win_challenger/challenger_*,model_version,env_*,archetype_vector— confirmed live in prod). The repo does NOT reflect prod schema; checkinformation_schema.columnsvia MCP, not the repo, before schema work. - 4.34 WNBA grading live? — VERIFIED. YES. 407 WNBA ledger rows, 376 settled, graded off a 2-book (DK+FD) feed. WNBA carries no challenger rows (statcast-gated MLB-only).
PHASE 5 — DOCTRINE AND THE BOARD
- 5.35 CLAUDE.md (read in full) — VERIFIED. Standing rules & findings:
- NO CODE WITHOUT A SPEC; 5 quality gates; WSL2 heredoc rule (python3 for >10-line files); update BUILD-STATE.md / BLOCKERS.md.
- Data Semantics Rule: VYNDR never generates lines/odds — market values are REAL captured book
numbers; only model_value/grade/edge are model output.
Number(null)===0is the recurring fabrication bug; use strict null guards. - Grade internals: grade is an additive integer index (
engine1, NEUTRAL_INDEX 3), moved by flat ±1.0/±0.5 deltas; A needs sum ≥+4.5, D ≤−1.51.confidenceis NOT a probability (grade-band midpoint).p_winis the real signal. NEVER rescale thresholds to mint A's (permanent founder ruling). A-RATED marketing on hold until a prod fingerprint shows real A grades. - Three stat_type whitelists must stay in sync (analyze.js, scan.js, validation.py).
- Snapshot pipeline is the product model (scheduled grade → lock to line → read from cache); on-demand "Read" retired. SNAP_TTL 24h.
- DO-NOT-WIRE / DO-NOT-TOUCH: Tank01 player props = empty (do not wire); ParlayAPI host dead;
gameLogService.getGameLogsreturns null for MLB (a trap — usefeatureCache.getStatRows);mlbGrader.jsis dead code; the legacy--grade-atoken alias block kept until consumers migrate. - Three separate MLB stat maps on purpose (featureCache / outcomeService / liveTrackingService) — do not merge. Settlement is MLB-only (WNBA/NBA/soccer never settle via that path — but WNBA IS grading + settling per 4.34, so confirm the settle path).
- 5.36 Running task list / open items — VERIFIED. Primary:
specs/STATE.md(1,886 lines, "STATE OF THE WORLD," CURRENT STATUS + OPEN ITEMS block, last dated 2026-07-22). AlsoBUILD-STATE.md,BLOCKERS.md,DECISIONS.md,AUTONOMY.md,PROMISE-AUDIT.md,ROADMAP.md. Open threads I have been tracking across recent orders that a strategist chat may not have: (a) challenger measurement now HAS settled rows (arch 128 / contact 86 / proj 86) — promotion is a future per-prop-type ledger decision; (b) the design-migration arc (Landing hero migrated; scanner S6/S7 blue-channel reskin shipped; S2/THE WIRE/Offseason/article-media are GAP/unbuilt); (c) multi-book: SGO qualified (report-only), nothing wired; (d) ROI is uncomputed in code and decomposes to MLB-C +4.57% / MLB-B −1% / WNBA −5% (this file). - 5.37 Spec / roadmap files — VERIFIED (in
specs/):STATE.md(running state),VYNDR-NORTH-STAR.md,DESIGN-SPEC.md,ROW-GRAMMAR.md(row grammar law),LIVE-TRACKING.md,VOICE.md,model-train.md,phase-0-kill-the-lies.md,phase-1-truth-infrastructure.md,propline-audit.md,feature-1-1…4-1+a1-s3/s7/s9/s10feature specs,combat-intelligence.md,design-reference/(the design bundle). Root docs:CLAUDE.md,DECISIONS.md,ARCHITECTURE.md,AUTONOMY.md,BACKEND_HANDOFF.md,BUILD-STATE.md,BLOCKERS.md,PROMISE-AUDIT.md. - 5.38 Standing decisions a new session might contradict — VERIFIED:
DECISIONS.md(DECISION-001+ architecture log); CLAUDE.md's permanent rulings (never mint A's; A-RATED hold; data-semantics; do-not-wire list); STATE.md open items (A does not emit in prod; EV overconfident/ unvalidated — hero ranks on ev_pct and picks the most overconfident read; grade_11 stored in model_snapshots).AUTONOMY.md= the zero-touch loop trace.
CLAIMS I WAS ASKED ABOUT THAT TURNED OUT TO BE FALSE OR UNSUPPORTED
- "the ledger reports … 597 settled with 'no ROI'" — the population is now 929 settled / 996
total (597 was an earlier snapshot); and ROI IS computable (
locked_oddspersisted) — it is simply not computed in the ledger code. "No ROI" = no computation, not incomputable. - "Either ROI is not computed, or the model is selecting heavy favorites at fair prices" — the binary is false; both are partially true and it decomposes by sport/grade: MLB-B = high-hit break-even favorites (the hypothesis), WNBA = losing, MLB-C = genuinely +4.57% EV. Not uniformly zero-edge.
- "the graded line is chosen by a book-agnostic consensus rule" — no consensus rule exists in
code. It is first-book-at-line (
dedupeProps) + highest-grade-across-lines (snapshotService). - Prior "137/140 single-book, draftkings 129 / fanduel 14" (~98% single-book) — corrected: today MLB is 73% single-book with 5 books present (DK/BetMGM/BetRivers/Pinnacle/FanDuel), and WNBA is 82% two-book (DK+FD). The feed broadened.
- "the 11-point index is lost / unrecoverable" — it is retained in
model_snapshots.grade_11(2,888 rows); lost only fromledger_entries. Recoverable via join. - "ZERO settled p_win yet / challenger measurement not begun" (from prior challenger orders) — stale: arch-v1 128, contact-v1 86, proj-v1 86 rows are now settled.
- "BookComparison doesn't exist, only BookChip" (an earlier order) — BookComparison.tsx EXISTS, it is just unrouted/dead.
- CLV framed as "held / not computed" — CLV IS computed (842 rows) and closing IS persisted (962), but it is largely degenerate (closing==locked on 92%) due to the overwrite — a different problem than "not computed."
- WNBA implicitly treated as not-really-grading (settlement described as MLB-only in CLAUDE.md) — WNBA IS grading AND settling (376 settled). The doctrine note about MLB-only settlement is contradicted by the data; the WNBA settle path should be confirmed.
End of canonical state. Regenerate the measured numbers before citing them in a later session — they move as the ledger accrues. Structural facts (file/line, schema, wiring) are stable until code changes.