43281bb885d3c8742583c792a8c41f5461644667
369 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
43281bb885 |
report: D1-close Review Zero — mount not performed, three findings
Nothing changed: no mount, no row edit, no data threading. Docs only. 1. THE RATIONALE DOES NOT REACH THE ROW. StripProp carries stat/line/side/grade/ gradedAt/delta/awaiting/outcome/movement/revisedFrom/book/bestBook/dead/ history — no reasoning, no kill_conditions_triggered — and buildPlayerStripsFromProps never threads them. Mounting the hover needs a new field on the strip contract threaded through the slate adapter: additive, but a data-path change rather than a mount. 2. THE 0.3 PREMISE INVERTS — THE RATIONALE IS ALREADY PUBLIC. Verified live and anonymously against prod: /api/snapshot/wnba returns reasoning.summary with no locked flag plus kill_conditions_triggered. stripModelPrice removes model_odds/p_win/ev_pct/value/takeable but NOT reasoning. So the full model rationale already ships to every anonymous browser on the main board, while the same content IS tier-gated on the scan path (tierGating.js). Mounting the hover would leak nothing new, but would surface content that is currently shipped-but-unrendered, and the product gates it in one place while serving it openly in another. That is a monetization/consistency decision, so it is reported with three options rather than resolved unilaterally. 3. ROW-GRAMMAR IS LAW AND LOCKS StatStrip's SOURCE ORDER. rowGrammar.test.js asserts element order via src.indexOf on the component source; adding a rationale affordance or a team chip moves those offsets, so specs/ROW-GRAMMAR.md and the test must be amended in the same commit. That makes this spec-amending work needing its own slot decisions, not an additive mount. Safely mountable with no blockers: reveal.js (wraps the row list, no StatStrip internals, no new data, no grammar slot). teamChips needs a grammar slot; rowRationale needs the data threading AND the gating decision AND a slot. Recommends splitting D1-close into: mount reveal now; a ROW-GRAMMAR amendment order for the chip + rationale slots; then the rationale mount once the gating decision is made. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
a0501f99c0 |
docs: D1 finish recorded (rationale real-or-absent, reveal once-on-view, 10/80 chip coverage)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
085e8a3a63 |
D1 finish: row-hover rationale, IntersectionObserver reveal, team-gradient chips
Additive frontend. Backend untouched (git diff src/ = empty): no grade, model,
classifier or ledger change. Scope held to the row anatomy these three items
need — no System-artboard-wide rebuild. Push scoring untouched.
REVIEW ZERO — the two checks that decided whether these could be honest:
0.2 RATIONALE SOURCE — VERIFIED REAL. Live snapshot grades carry `reasoning`
and `kill_conditions_triggered`. The summary is built by analyzeViaEngine1
from the actual feature vector (l5/l20 averages, gap to the line, home/away,
opponent defensive rank, rest days) and kills carry real codes + reasons.
So the hover shows genuine grade truth, not a placeholder.
0.3 TEAM COLOURS — PARTIAL, and deliberately left partial. The System artboard
defines a colour pair for only 10 teams (BOS CHC CHI DEN LAD MIL MIN NYY PIT
SD), lifted verbatim; lib/teams.js holds ~80. The other ~70 are NOT invented
— a wrong team colour is a recognition error the user reads as fact. Unknown
teams get the honest-neutral chip (muted border, no colour claim), never a
guess and never a blank gap. Coverage is reported by coverage(), not hidden.
SHIPPED:
- web/src/lib/rowRationale.js — rationaleFor() returns real summary + kills, or
NULL. No generic fallback: an empty hover is honest, a manufactured "why" is a
fabricated model explanation. A locked/tier-gated reasoning is treated as
ABSENT rather than paraphrased or leaked, and a kill condition with no reason
explains nothing so it is dropped.
- web/src/lib/reveal.js — IntersectionObserver reveal that fires ONCE then
unobserves ("react to truth, then rest"), reuses D1-A's bootDelayMs for the
60ms stagger so there is ONE source of truth for the timing, and reveals
IMMEDIATELY when IntersectionObserver is absent (SSR/test) so a missing API can
never hide real content. Reduced motion is handled by the existing CSS, so the
row is visible either way.
- web/src/lib/teamChips.js — Rev-3 geometry (10px, 135deg, before the abbr,
inside the row) plus the ranked opacity ramp 1/.86/.64/.48 so chips dim with
their row. Swap-ready for licensed logos at the same size.
Floor: 318 suites / 3961 tests green (15 new), web build exit 0.
The three modules are pure and unit-locked; mounting them into the live row
components is a follow-up, and the visual result belongs in the Chrome audit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
|
||
|
|
e34e99c426 |
docs: D1-A recorded (glyph buckets, boundary channel completed, primitives)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
3d1a3c7794 |
D1-A: combat glyphs, boundary-channel blue, reaction primitives, READ-FAB
Additive frontend/visual. Backend untouched (git diff src/ = empty): no grade,
model, classifier or ledger change. The 41->74 registry expansion is HELD for
D1-B. Push scoring untouched.
REVIEW ZERO — classifier coverage bounded the glyph wiring. Three buckets, and
the computation was redone three times before it was right (the frontend keys
GLYPHS by ARCHETYPE NAME while the backend keys `glyph:` by SHAPE NAME, and most
registry keys are unquoted identifiers — the first two passes mis-parsed both):
(a) classifier-backed, already wired: 38
(b) classifier-backed, package SVG exists, NOT wired -> WIRED HERE: 6
striker, grappler, pressure, counter, grinder, finisher — all MMA/combat
archetypes in archetypeService.js that were rendering EMOJI fallbacks
('*', 'x', '>', '<>') where the package ships real 24-grid duotone marks.
(c) package SVG with no classifier -> HELD for D1-B: 39 (wiring them would
render nothing)
(d) classifier-backed but NO package SVG: 2 ('dual threat', 'paint boss') —
a DESIGN gap, not a build gap; flagged for D1-B.
GLYPHS map 38 -> 44 keys, deliberately far short of the package's 83.
BOUNDARY CHANNEL — the blue tokens already existed (--priced-out set) and were
applied on NoMarketState and the scan void box, but PriceTriplet's NO_MODEL
("line not priced") still rendered in neutral text, so the channel was applied
inconsistently. NO_MODEL now renders in the channel, completing "every boundary
state or none". Token-only (no hex fallback and no hex in prose — PriceTriplet's
own test forbids literal hex, and it caught both).
REACTION PRIMITIVES — new web/src/lib/reactions.js + globals.css keyframes at the
exact HANDOFF timings: flash .75s ease-out, boot stagger 60ms steps, reactions
gated 1.5s, WIRE hold 6s. nudge() REFUSES a no-op (null/absent direction -> no
flash) so the primitive cannot be attached to an idle loop — a flash without a
new datum is the UI lying about the feed. Reduced-motion honoured.
READ-FAB — aligned to the exact package geometry: 50px circle, translateY(-14px),
6px void ring (was 46px, marginTop -16, 3px ring).
CARD TOKEN — audit correction: #0E0E14 was already tokenised as --bg-1/--card;
the audit's "1 file" was counting the raw hex, not the token. No change needed.
Floor: 317 suites / 3946 tests green (16 new), web build exit 0.
NOT DONE THIS ORDER (reported, not silently dropped): row-hover rationale and
IntersectionObserver reveal (Phase 3 item 8) and team-gradient chips (Phase 4
item 10) are not implemented — they need the System artboard's row anatomy,
which is a larger port than the rest of D1-A.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
|
||
|
|
49565b5f02 |
docs: design-vs-build gap audit recorded (61 items, 6 build waves)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
e257474cc8 |
report: design-vs-build gap audit — 61 items enumerated, 18 absent
Package specs/design-reference (Jul 22) audited against the CURRENT repo
(
|
||
|
|
91911cfb1c |
docs: Wave 3 recorded — /compare live-verified; resolution tail scoped
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
bf7c0a3c08 |
Wave 3: /compare built (real head-to-head); resolution tail scoped, not shipped
No grade, ledger or scoring change. Push scoring untouched.
REVIEW ZERO 0.3/0.4 — THE RESOLUTION TAIL DOES NOT FIRE. The resolver is
POST /api/grading/resolve (routes/grading.js:208), and its fanout at :356-371
covers webPush, telegram and discord — but:
- share-card generation: SPEC'D-NOT-BUILT. Not in the fanout at all (grep
shareCard in grading.js = 0). shareCards/renderer.js exists with ZERO
callers, so the component is built but no step would ever invoke it.
- push notifications: BUILT-NOT-FIRING. In the fanout but gated on
webPush.configured() (VAPID). push_subscriptions = 0 rows and
user_notifications = 0 rows — nothing ever subscribed or delivered.
- Telegram result posts: BUILT-NOT-FIRING (gated on BOT_TOKEN + CHANNEL_ID).
- Discord result posts: BUILT-NOT-FIRING (gated on webhookFor('results')).
- recap (all-Final trigger): SPEC'D-NOT-BUILT. No recap file exists in src/.
AND THE WHOLE TAIL IS UNREACHABLE: nothing calls /api/grading/resolve — there is
no ESPN poller in the repo. The live settlement path is the scheduler's
settleAllOutcomes + settleAllLedgers, which fans out to opsNotify only (ops
alerts), with no user-facing output. So even the built channels have no trigger.
Per the order's own rule, ShareCard, /notifications, result posts and recap are
therefore ALL SCOPED, none shipped — no dead shells over a silent pipeline.
BUILT — /compare. Semantics (0.2): a same-market head-to-head, two players with
every row a measure BOTH sides are scored on, aligned via alignRows so the
numbers are comparable — deliberately not two disconnected graded props. Reads
the live /api/stats/player/:name?sport= aggregate. Honest-absent three ways: an
unresolved side reads NO DATA while the other still renders; a measure only one
side has renders a dash, never 0; if neither resolves the page refuses to
compare. NO VERDICT — it shows measures and says the reader draws the call.
Two pre-existing tests (vyndrPhaseE, vyndrParityQA) asserted the in-development
placeholder; both superseded rather than deleted — they now assert the stronger
properties against the real page (live fetch, no sample players, NO VERDICT,
NO DATA, "not a zero").
Floor: 316 suites / 3930 tests green (10 new), web build exit 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
|
||
|
|
831d09bdde |
docs: Wave 1 wiring recorded + honest fingerprint limitation (nav entries -> Chrome audit)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
ff7f5d8d2d |
Wave 1: wire /intelligence, /slip, /parlay + /marketplace honesty pass
Wiring + one copy pass. No grade, ledger, model or scoring change (diff empty
across intelligence/, ledgerService, outcomeService, gradeSlateService).
REVIEW ZERO — each surface proven with real data BEFORE wiring:
0.1 /intelligence vs /system are NOT duplicates. System.dc.html is a
multi-surface artboard (TERMINAL + INTELLIGENCE + WIRE sections), not the
design for a distinct /system route; its INTELLIGENCE section is already
realised as the live app/intelligence/page.tsx. No /system page exists and
none should be built as a second copy — the prod 404 is correct.
0.2 /intelligence renders live and gates SERVER-side, not by blur: the proxy
requires auth and limits by tier (desk 50 signals / non-desk 8), and
returns 401 to an anonymous caller (verified live). No leak.
0.3 /slip parses a real DraftKings slip end to end: 3/3 legs,
needs_review false, Aaron Judge total_bases over 1.5 @ -115. Honest limit
recorded: parsers are layout-rigid, an unsupported layout yields ZERO legs
rather than wrong ones (never-guess), so real-world OCR hit-rate across
layouts is CANNOT DETERMINE until user slips arrive.
0.4 /parlay direct route hits the real correlation builder on the same
ParlayContext the drawer uses.
0.5 /marketplace advertised four unbuilt things but made NO performance or
profit claim, and its capture was already real (/api/waitlist upserts to a
waitlist table). The gap was tense, not fabrication.
WIRED: Nav MORE gains Intelligence, Slip Reader and Marketplace; Parlay Lab
re-pointed from the drawer hash to /parlay (the drawer is unaffected —
ParlayPanel stays mounted with its floating badge).
GATING: /intelligence added to GATED_ROUTES because its feed 401s signed-out, so
an ungated link would land visitors on a permanently empty page. /parlay stays
OPEN deliberately — it is the free parlay funnel and gating it would be a
monetization regression.
/marketplace honesty pass: every item body now opens "Not built yet." /
"Not written yet." / "Not produced yet." with what is planned; the subhead states
it is not a purchase, not a pre-order and not a promise of a ship date; the
playbook item carries "No profit claim, no promised return". The capture stays
real — no fake button. Unit-locked.
Floor: 315 suites / 3920 tests green (12 new), web build exit 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
|
||
|
|
6328a5ce92 |
docs: boundary not written (no promotion); build triage waves recorded
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
e70c02aca0 |
report: incomplete-surface build triage; boundary tag NOT applied (no promotion exists)
Report-only. Nothing built, wired, tagged, or removed.
PART 1 — the recalibration boundary was NOT written. The build order asks to tag
grades pre/post edge-shading at "the true promotion timestamp"; there is no such
timestamp, and writing the marker would insert a fabricated model transition
into an append-only public record — the exact corruption the order exists to
prevent. Three independent production proofs:
A. model_snapshots.code_sha — every sha that ran the pipeline in the last 5
days is a documented commit from this session (
|
||
|
|
d54eca0afc |
report: pre-audit status pull — no promotion occurred; matrix re-derived (15/26)
Verified three ways that nothing was promoted and no re-lettering happened:
HEAD is the no-flip commit with a clean tree, efficiencyShading is imported by
zero production files, and live grades carry 0.0% A-family (MLB {B:1,C:4},
WNBA {B:15,C:10}). Neither 92.9% nor 43.6% is a figure measured here - the
challenger's real numbers were 97.4% would-change / 79.8% up / 79.0% A, with
0 of 1250 rows actually shaded.
Takeable tags landed (1245/1250 tagged, takeable_floor on all, one floor -160;
the 5 untagged have no locked price). The model-version boundary is absent and
correctly so - there was no recalibration to mark. Not a pre-audit gap.
Matrix re-derived from live prod probes, importer counts and nav-link counts:
15 of 26 fully done. Book comparison RESOLVED (BookComparisonPanel routed to
the grade card). Grade card honesty improved by the edge_pct retirement.
ShareCard / MobileEdgeBoard / DemoScan still dead code. Seven live routes
remain orphaned with zero nav links; /system and /offseason are 404. No
LIVE-but-not-HONEST surface found.
Design is NOT complete: Live tracking, Slip reader and Newsletter are shipped
with no design artboard - design is the gap, not build. Offseason is the
reverse (designed, never built).
Chrome audit manifest assembled: 11 items with per-item session state, four
requiring an entitled Desk session that only Kev can drive.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
|
||
|
|
10aaaeb1da |
docs: promotion gate not passed (no flip); Order B edge_pct display retired
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
0997334f8b |
Order B: retire edge_pct display. Promotion gate NOT passed — no flip.
THE PROMOTION WAS NOT PERFORMED. Champion grade path byte-identical (diff empty
across intelligence/, gradeSlateService, snapshotService). Projection, p_win and
the CLV instrument untouched.
REVIEW ZERO IS A GATE AND THREE OF FOUR PREREQUISITES FAIL:
0.1 scores are ESTIMATED priors from the founding spec, not measured. The
premise's cited values are not in the code either — the module holds
nba:points .80 and mlb:total_bases .55; there is no NBA 0.72 and no WNBA
score at all.
0.2 VERSION-BOUNDARY TAG DID NOT LAND — config/modelEras.js has zero shading
references. It was deliberately not applied twice (nothing had been
promoted) and reported both times. The order's own rule says STOP.
0.3 NO ROLLBACK FLAG EXISTS — zero occurrences of SHADING_ENABLED /
EDGE_SHADING / shadingEnabled anywhere in src/.
0.4 takeable tags DID land (migration 034, 1246/1254 rows). PASS.
AND THE APPROVED DELTA DOES NOT MATCH THE MEASURED ONE. Approved: 43.6% of
grades re-letter, efficient markets tighten and soft hold. Measured on all 1250
live rows: 97.4% change (1217), 79.8% move UP, 17.6% down, resulting in 79.0%
A-family (MLB 93.4%) against the champion's 0.2%. And rows_actually_shaded = 0
of 1250 — 96.5% of markets are unscored (f=1) and the one scored market present
is the anchor (f=1.0 by construction). The entire re-letter comes from switching
to edge-vs-fixed-bar grading, NOT from efficiency shading, which is inert on
this board. That is an unapproved grading-basis change riding along, which the
order's own "no new scaling changes riding along" guardrail forbids.
Flipping would re-letter 97.4% of an append-only public record, move 79.8% of
grades UP and mint A's on 79% of the board, on a letter whose measured
correlation with outcomes is r ~ 0.005 — the exact scenario the permanent
founder ruling forbids.
SHIPPED — ORDER B (independent of the promotion, and a live falsehood):
edge_pct display retired from GradeResultCard (confidence strip, EDGE stat cell
now honest-absent, alt-ladder rung) and SoccerGradeResult. DeskShowcase kept
(already honest). Computation and the board's signed-edge sort fallback SURVIVE
— deleting them would re-break the sort fixed on 2026-07-29; a test asserts all
three survive and the sort still orders agrees -> disagrees -> absent.
Fixed two build-breakers the retirement caused (orphaned edgeColor import,
orphaned edge_pct destructure; edge_pct stays on the props contract). Two
pre-existing tests superseded rather than deleted: they asserted the edge figure
is sign-coloured, and now assert the stronger property that no edge percentage
renders at all.
Floor: 314 suites / 3908 tests green (9 new), web build exit 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
|
||
|
|
1e9c808d99 |
docs: edge-shading challenger measured — flooding persists, input scale is the bug
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
c2c7abbb65 |
Edge-shading challenger: built + measured. Flooding NOT fixed — input scale is the bug
Challenger only. Champion grade byte-identical (verified by diff). Nothing promoted, no live grade re-lettered, no ledger row deleted or re-settled. BUILT src/services/challengers/efficiencyShading.js (measured-never-served): adjusted_edge = raw_edge * f(efficiency); grade = band(adjusted_edge) against ONE fixed bar (A+>=10, A>=5, B>=3, C>=1, D>=0, F<0) that never moves. f(e) = E_SOFTEST/e bounded to (0,1] — soft markets intact (never amplified), sharp shaded toward but not past zero, unscored -> f=1 and FLAGGED. A fence test asserts no production grade path imports it. Cross-market behaviour is unit-proven: the same raw 6% edge grades A in soft mlb:total_bases and B in sharp nba:points. MEASURED on 1250 live ledger rows — Phase 2.5's answer is NO, the flooding is not gone: challenger 79.0% A and 80.9% A/B (MLB 93.4% A) vs champion 0.2% A. TWO findings explain why, and they are the point of the order: 1. The shading is a NO-OP on the live board: rows_actually_shaded = 0 of 1250. 96.5% of rows are UNSCORED (f=1), and the one scored market present (mlb:total_bases) is the anchor so its f is 1.0 by construction. mlb:strikeouts and nba:points do not appear in the ledger at all (our basketball is wnba, not nba). Challenger vs baseline: 0 rows changed. 2. Placement was never the bug — the INPUT SCALE is. Against a fixed 5% bar the RAW edge already clears A on 100% of MLB doubles, 89.6% of hits, before any shading. MLB median raw edge is 60%, twelve times the bar. Decisive test: apply the sharpest score in the spec (f=0.647) to EVERY row — the maximum the design permits — and 75.8% still clear A (MLB 91.7%). Since f is bounded <= 1, no achievable shading can close a 12x overshoot. Moving the multiply from the threshold to the edge does not change the outcome. This is edge_pct behaving as the 2026-07-29 diagnosis described: a price-free (proj-line)/line gap whose scale is a function of line size. It is not a betting edge, so no fixed betting-edge bar is meaningful against it. 2.6 efficient-market over-suppression: CANNOT DETERMINE — zero live rows are shaded, so there is no efficient market in the data to over-suppress. Phase 3: takeable tagging was completed in the previous order (migration 034, 1246/1254 rows) and is not repeated. The model-version boundary is again NOT applied: nothing promoted, so no boundary exists. Unblocking needs the input replaced, not the multiply moved: p_win vs fair_prob (both already computed) instead of edge_pct, plus scores FIT from our own record for the markets we actually grade. Floor: 313 suites / 3899 tests green (9 new), web build exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
896e6e1a00 |
docs: takeable tagging shipped, efficiency challenger blocked (board + state)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
2bfaeff572 |
Ledger takeable tagging (deferred C2); efficiency challenger BLOCKED
Champion grade UNCHANGED. Push scoring untouched. Additive tags only — nothing
deleted, nothing re-settled.
PART A — THE EFFICIENCY CHALLENGER: BLOCKED, NOT BUILT.
Review Zero came back ABSENT on all three inputs:
0.1 efficiency scores DO NOT EXIST (zero occurrences of market_efficiency /
marketEfficiency / efficiency_score in src/ or web/src/).
0.2 base thresholds DO NOT EXIST (engine1.js has zero `edge` references — the
grade is not an edge-vs-threshold comparison; grade_thresholds.json holds
PROBABILITY bands).
0.3 the +/-0.05 additive efficiency nudge DOES NOT EXIST. The only 0.05s on
the grade path are featureCache.teammate_absence_bump, a bvp_advantage
cutoff, and p*0.9+0.05 inside probabilityEstimator (the 0.5*0.1 term of
the shrink-toward-0.5). There is no additive scaling to replace.
So a challenger differing from the champion in EXACTLY ONE thing cannot be
constructed: there is no additive scaling to swap, no base threshold to
multiply, and engine1.js has zero `sport` references so market cannot reach the
grade. A threshold must exist first — that is R1 of
specs/full-output-grade-mapping.md, an explicitly held separate order. Shipping
R1+R4 together would make the Phase-3 delta report misleading: the re-letter
would be driven mostly by switching to probability grading while being
presented as the efficiency fix.
0.4 coverage: the spec names 5 scores; the live ledger has 11 markets and only
MLB total_bases maps to one. 9 of 11 have no score, so "all scored markets"
cannot be satisfied without inventing 9 numbers.
PART B — LEDGER TAKEABLE TAGGING: BUILT (the deferred C2).
New src/config/takeableStandard.js: floor on the minus side, UNCAPPED plus.
Deliberately NOT valueEngine.isTakeable (the -160..+200 PROMOTION band) — a
+400 prop is not promotable but IS takeable; a test asserts the two diverge on
the plus side and agree at the floor so they can never quietly merge. Absent
price returns null, never false (Number(null) === 0 would tag a missing price
takeable). The floor is POLICY not derived (C1 could not derive one) and is
labelled so; each row records takeable_floor so a re-derivation can re-tag.
Migration 034 (applied + tracked): ledger_entries.takeable boolean +
takeable_floor numeric, nullable, partial index. Forward tagging in
ledgerService at row build; backfill in one statement.
Result: 1254 rows, 1246 tagged (781 takeable / 465 below floor), 8 NULL with
null_despite_price = 0 (the NULLs are genuinely priceless rows). Settled 1163
and graded 1254 unchanged.
PART C — the model-version boundary tag is DELIBERATELY NOT APPLIED: no scaling
change shipped, so no boundary exists, and stamping one would mark a model
transition that never happened. modelEras.js is its home when a real one lands.
Floor: 312 suites / 3890 tests green (8 new), web build exit 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
|
||
|
|
a6afac43cc |
report: market-efficiency scaling check — FLAT, mechanism structurally absent
Report-only. No threshold, grade, or efficiency value changed. VERDICT: FLAT. marketEfficiency.js does not exist (zero occurrences of market_efficiency / efficiency_score in src/ or web/src/). The spec's 0.85/0.60/0.55 values appear in grade_thresholds.json only as PROBABILITY BANDS - a coincidental numeric overlap, not efficiency scores. The base edge thresholds (MLB A:5%, NBA A:7%) do not exist either: engine1.js has zero `edge` references, so the live grade is not an edge-vs-threshold comparison at all. The specced rule threshold = base x efficiency has no host. DISPOSITIVE: engine1.js contains ZERO `sport` references. computeFactors receives no sport or market, so per-market OR per-sport scaling is structurally impossible in the live grader - not merely unwired. Phase 2: the matched-edge test is confounded (edge is not the grading input - the same market emits both B and C at one edge). The aggregate that discriminates: mean grade index wnba points 4.71 at mean edge 10.2 vs mlb hits 4.58 at 69.5 vs mlb total_bases 4.32 at 84.9 - the efficient market earns the highest grades on one-eighth the edge, the opposite of spec. PREMISE CORRECTION (measured): this order's opening claim that full-output and collapsed grades "agree 100%" does not hold - on 512 rows carrying both they agree 17.8%, with 33.8% differing by 3+ tiers. The prior discrimination result stands (champion r=0.0050 null vs probability r=0.1313; MLB 0.0686 n.s. vs 0.2356 p~0.0004). Repo unchanged between orders. The collapse was not a phantom and the re-adjudication list stays open. Scope: flat thresholds are a grade-CALIBRATION gap only - the projection and the CLV edge (which measured p_win, never the letter) are untouched, so this is not a third shadow-model alarm. But the fix is NOT independently bounded: with no threshold step to multiply, efficiency scaling presupposes probability grading. It is rule R4 of specs/full-output-grade-mapping.md and belongs to that MLB-first challenger. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
708f0fde5c |
report: full-output grade mapping spec + collapse cost measured
Report-only. Nothing built, reconnected, or promoted. Premise corrected again: the three-layer engine is BUILT but NOT WIRED and NOT DEPLOYED (0 python refs in every grade-path file, 0 python in Dockerfile; there is no engine1Adapter). So no posterior/CI/similarity prior exists to inventory or diff. Measured against the collapse that actually exists instead. THREE collapses, not one: (A) estimateProbability's components discarded at analyzeViaEngine1:521-524; (B) THE SEVERE ONE - p_win never reaches the grade at all (engine1.js has zero probability references), so the probability is excluded from grading rather than collapsed into it; (C) grade_thresholds.json (probability->grade) read backwards to manufacture confidence. Market-efficiency scaling is never computed - a gap, not a collapse. MEASURED on 354 settled rows carrying the served letter and the locked pre-game p_win (forward, not lookahead). Grade->outcome point-biserial r: champion letter 0.0050 (p~0.93, null) vs probability letter 0.1313 (p~0.013). Per sport: MLB champ 0.0686 n.s. vs prob 0.2356 (p~0.0004); WNBA champ -0.0986 vs prob -0.1258 - BOTH INVERSE. The served letter is inverted between its only two populated tiers (B 52.4% n=168 vs C 56.9% n=174). Verdict: costly on MLB, and un-collapsing does NOT help WNBA -> the challenger must be MLB-FIRST. Five falsifiable mapping rules specced, incl. R2 (uncertainty grades down) stated explicitly and droppable if it fails. Hard requirement on the next order: persist per-row n, SE and pre-adjustment p, or R2/R4 can never be adjudicated (not stored today). Re-adjudication list flagged incl. proj-v1.1's NOT PROVEN verdict (judged against the collapsed champion, so not final) and ROI-by-grade (with B/C inverted, the MLB-C +4.57% segment is likely an artifact of a meaningless letter). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
dd98b0b614 |
report: model architecture recovery map — the live grade uses 0 of 3 specced layers
Archaeology only; nothing built, reconnected, or promoted. The champion is two DISCONNECTED estimates: the letter is engine1's additive factor index (zero references to p_win or any probability in engine1.js), and p_win is probabilityEstimator's frequencyOver + 5 heuristic layers, computed after and merely attached. The live grade path never calls the Python service. The Python three-layer engine is NOT DEPLOYED — no python/pip in the Dockerfile; app.js only health-checks it. So Layers 1-2 never shipped. Layer 3 is wired BACKWARDS: grade_thresholds.json maps PROBABILITY->GRADE and the live JS reads it in reverse to manufacture confidence from an already-chosen letter. Per-sport market-efficiency scaling is specced-absent. Consequence stated plainly: every metric audited to date is on the shadow model, not the specced engine, which has never been measured. Sport boundary TESTED not asserted: a new sport on the live path is a ~10-file core edit with four documented silent-failure modes. Per-sport records DO exist (sports.mlb n=526/62% vs pooled overall n=937/58%, each n>=20 gated), but /api/accuracy ignores ?sport= and the pooled overall would absorb a new sport. Park x weather confirmed challenger-only; xwOBA and leash absent. Recovery map is dependency-ordered with MLB as the reference module. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
f3bf300b36 |
report: C1 takeable-floor derivation — CANNOT DERIVE (every bucket CI spans zero)
MLB decided overs n=296, 100% with locked_odds. ROI by locked-price bucket shows every 95% CI containing zero; the curve is NON-MONOTONE and runs opposite to the premise (deepest buckets positive, the -111..-160 middle most negative); and price bucket is confounded with market (+200up = doubles/HR longshots). Rows needed per bucket to resolve a 5-pt edge: 661-2285 vs actual 8-71 (~187 days for one bucket at current accrual). The inherited -160 is neither confirmed nor refuted. The no-ceiling call is not supported by this data either (+200up is the worst bucket) though it is not refuted - it stays a design choice, not a data-backed one. Recommends C2 proceed with -160 as an explicitly-labelled POLICY floor plus a re-derivation trigger (any negative bucket n>=300, or end of MLB regular season; adopt a derived floor only when a bucket CI excludes zero). Enumerates all 9 takeable sites, incl. the live drift hazard (backend env-tunable, frontend hardcoded) and the user-visible band copy in PriceTriplet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
f310608ca4 |
docs: top-graded selector fingerprinted — strip-after-rank boundary proven in prod
The anonymous live order (Brionna Jones edge 29.4 ahead of Rhyne Howard edge 42.9) is only explicable by server-side p_win ranking (.90 vs .745, both takeable) while the payload carries no paid fields — the free caller got the paid RANKING without the paid SIGNAL, on live data. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
72a14dc4cd |
Build /api/props/top-graded server selector: rank with p_win, serve without it
New READ endpoint. No grade, ledger row, lock_line, or scoring write. Push
scoring untouched.
REVIEW ZERO CORRECTED THE PREMISE: the handler NEVER EXISTED in any commit
(searched git rev-list --all for a /top-graded definition in src/ — zero hits).
Not "removed" — the three axios callers (cheatsheetGenerator, gradeOfTheDay,
widget) and the Next proxy were written against a phantom endpoint, so those
three content generators have silently received [] for their entire life.
Contract recovered from the four consumers, not guessed: {props:[...]},
?sport=UPPERCASE (absent = all sports, which gradeOfTheDay relies on) + ?limit,
rows carrying player/stat/line/direction/sport/grade/confidence? plus the
player_name/stat_type aliases and game_id.
POPULATED-PATH RISK FOUND: the board's populated branch had never run in prod,
and dashboard/page.tsx:463 calls g.stat.replace(/_/g,' ') UNGUARDED (g.player
also feeds the row key, /scan URL and heading; sport must be UPPERCASE for
SportPill). toRow requires non-empty string player+stat and a finite line,
uppercases sport, and DROPS unrenderable rows — a shorter board beats a broken
one.
THE LEAK BOUNDARY (why this is server-side): the browser cannot rank on p_win
for all tiers because stripModelPrice deliberately withholds it from unentitled
tiers. Order of operations is
read cache -> RANK with p_win (every tier) -> map rows incl. model fields
-> stripModelPrice(rows, tier) -> serialize
so a free caller receives the paid RANKING without the paid VALUES. Tier comes
from resolveTierFromRequest, which FAILS CLOSED to 'free'. Cache-Control is
private under a bearer token, public otherwise (the /api/snapshot precedent).
ONE SHARED DEFINITION, no drift: new src/utils/gradeRanking.js
(takeablePWin/descNullsLast/rankGrades). heroPropService now imports
takeablePWin instead of its inline copy (behaviour unchanged — it was that
logic verbatim); the selector imports rankGrades; web/src/lib/slateAdapter
keeps its mirror (the browser cannot import src/, S25) and a test cross-checks
the two on identical fixtures (playerName.js precedent). Board is grade-first
("top GRADES"), hero is p_win-first ("top read") — they differ BY DESIGN and
agree within the leading tier.
HONEST LIMIT: the Next proxy (cachedBackendJson) sends no Authorization header
and caches under a shared key, so via the dashboard every viewer gets the
free-tier payload — correct order, no paid values. That is the SAFE behaviour;
forwarding auth into a shared cache is exactly how a paid payload leaks to
anonymous viewers. Per-tier delivery through the proxy needs a tier-keyed cache
and is not done here.
Verified on real prod snapshot data (anonymous path): MLB 8 props, WNBA 10,
0 paid-field leaks, render-contract safe on every row, sport uppercase.
Floor: 311 suites / 3882 tests green (18 new — leak test uses POPULATED p_win,
not today's nulls: entitled gets p_win and it drove the order, unentitled gets
a byte-identical order with all five MODEL_FIELDS absent and no trace in
JSON.stringify, while book/fair market facts survive). Web build exit 0.
Dashboard visual is auth-gated -> tagged for the Chrome audit, not faked.
Held: edge_pct rescale/retirement (Order B); board columns/contract unchanged;
tier-keyed proxy caching.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
|
||
|
|
69feab4d25 |
docs: grade-board sort fingerprinted (deploy boundary captured; ladder induced both directions)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc |
||
|
|
b85b351993 |
Grade-board sort: signed signal, takeable-gated p_win, missing sorts LAST
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
|
||
|
|
9b5235cf99 |
docs: hero ranking fix verified (new code serving — max-p_win takeable, chalk excluded)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
41b86e3874 |
Hero ranking fix: rank by champion p_win among takeable, honest empty state
Review Zero found the hero's ACTUAL behavior was worse than "unknown": it ranks on ev_pct (heroPropService v2), but ev_pct is NULL on served grades and Number(null)===0 made Number.isFinite(Number(null)) TRUE — so every prop tied at EV 0 and the "top read" was really the FIRST takeable A/B prop in cache order (arbitrary, dressed as ranked). v3: rank by the CHAMPION's p_win (the only signal with a promising, not proven, edge — its takeable-MLB-over CLV survived the skew audit) among A/B, TAKEABLE- priced reads (isTakeable band -160..+200, same as the proof/audit). Strict null guard kills the Number(null)=0 bug. Takeable filter is mandatory (raw p_win crowns -300 chalk). NO backfill: nothing qualifies → honest empty state (available:false, reason:'no_qualifying_read'), never a weak recent read. p_win is RANKING-ONLY, server-side — toHero never exposes it and the route strips it. Framing unchanged in substance (model number vs book number, grade, timestamp) — no proven-edge / +EV / best-bet claim, no CLV/ROI/edge number. Display-only: reads snapshot caches, writes to nothing (no grade/ledger/lock_lines). Full suite 3852 green, web build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
e7ec501054 |
docs: book-comparison verify note (live-card screenshot auth-blocked; data feed + tests verify)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
36014c7c30 |
docs: Book Comparison wired to the prop card (matrix row 18 DONE, 16/26)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
2ab2eeaa7d |
Wire BookComparison to the prop card (display-only, honest states)
BookComparison.tsx was built but UNROUTED (dead). Route it to the GradeResultCard via a new self-fetching BookComparisonPanel that reads the live /api/books feed (source:'bookprices' — the snapshot-locked, fenced, byte-identical store). Contract fix (Review Zero 0.1): books frequently sit at DIFFERENT lines (WNBA DK 21.5 / FD 18.5; MLB 2/3), so BookComparison now renders EACH book's own line per-row — never one shared header line implying a false same-number comparison. Honest states: single-book (the common case for MLB) → one book, "One book posting this prop.", NO crown/second row; multi-book → all books' own line+price, NONE crowned (BOOK_CROWN_ENABLED=false — no best-price claim, verified live crowned:false); no books → renders NULL (panel self-hides), never a placeholder. No regression: only the always-empty inline d.books section was replaced; grade, projection, PropLine line, and PriceTriplet price are untouched (wiring test asserts them). Freshness (0.4): bookprices is written in the SAME snapshot that locks the grade (intraday refresh touches neither gradedAt.line nor bookprices) — same fresh, no stale-label needed. Web-only → grade byte-identical trivially. Full suite 3851 green, web build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
3a05447f77 |
docs: lock-line persistence unblocks the staleness audit (migration 033, lock_lines)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
c7067c80c4 |
Persist lock-time multi-book lines to lock_lines (unblocks the staleness audit)
The over-side skew audit's confirming check — was our locked line stale-high vs consensus AT LOCK — was BLOCKED because multi-book lines at lock were never persisted (bookprices is Redis current-only). This persists them. - migration 033: lock_lines table (tracked + applied to prod). One row per (graded prop × book) with both odds + a lock timestamp. RLS enabled, NO policies -> service-role only (fence). UNIQUE key -> idempotent re-runs. - lockLineCapture.js: buildLockRows (pure, graded-props only, honest-absent single-book) + idempotent upsert persist. Built from the in-memory props at the LOCK moment (ts) -> no Redis re-read, no TTL race. - snapshotService: persist right after `enriched` (the lock moment; gradedAt uses the same ts). Best-effort + fenced. FENCE (measurement-only): lock_lines is read by NOTHING on the grade path (gradeSlateService, snapshot dedup/indexOdds, challengers, selector, ledger) — a grep test asserts it, and RLS locks it to the service role. Grade byte- identical proven: runSnapshot grades are identical with persist on/off (test). Volume ~1.5-3k rows/day (graded props x books x 5 snapshots); weeks retained, no pruning needed short-term. Does NOT retroactively fix the existing 62 rows — future accrual only; confirmation still needs weeks of settled rows. Full suite 3842 green, web build exit 0. No grade/locked_odds/outcome/served surface changed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
37261260d1 |
report: over-side skew audit — champion over-CLV SURVIVES baseline (promising, not confirmed)
Read-only. Takeable MLB overs n=62. Mechanical baseline (no-edge) CLV +1.51pt (n=20); high-edge +8.64pt (n=37); difference +7.14pt = real edge. Champion p_win->CLV partial r=0.375 (SIG p~0.003) survives price control. Skew one-sided (unders -7, over baseline +1.5). De-vig clean (same-book pairing, analyzeViaEngine1:539); close well-defined (DK/MGM r=0.92). Greenlights building the takeable-edge grade ON THE CHAMPION, not proj-v1.1. Flagged promising-not-confirmed: thin n, lock-time multi-book staleness check BLOCKED (not retained), pinnacle ref n=8. No fix, no promotion — diagnosis only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
59b77cdef2 |
report: proj-v1.1 takeable-edge proof — NOT PROVEN (n=45 MLB overs)
Read-only proof. N-gate passed (overlap 45). proj-v1.1 edge-CLV partial correlation controlling for price = 0.245 (n.s.); ~half the raw 0.455 is the shared -fair_prob_lock term (mechanical). Champion out-predicts proj on the same rows (champ partial-CLV 0.380 sig; champ-edge->hit 0.25 vs 0.12). Unders contaminated (CLV -9.3); WNBA proj-v1.1 doesn't run. Promotion HELD; the under-audit is moot since proj loses to the champion first. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
b8ee216c62 |
docs: CLV instrument repair + the straight finding (unders lag the close)
closing_prob 59 -> 406 (MLB 248, WNBA 158). Root cause was attachClosingProb's truncated read + write-once market_unavailable, not capture or the join. CLV measured: MLB unders lag the close (mean -9.1 prob-pts, 74% lose), MLB overs +2.0, WNBA flat -> the +4.57% MLB-C and over/under asymmetry are substantially stale-line artifacts. Unblocks the proj-v1.1 proof order. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
6552281661 |
CLV instrument repair: fix attachClosingProb read + recoverable market_unavailable
The closing_prob funnel collapsed 100k priced captures -> 59 usable. Root cause (VERIFIED against prod, join key is PERFECT with 0 mismatches): - attachClosingProb read closing_captures with .limit(50000) and NO ORDER BY on a 730k-row table that is 86% refusal rows -> saw ~7% for MLB, missed most priced closes and declared 200+ rows closeless that HAD a capture. - market_unavailable_reason was write-once/terminal, so a row wrongly declared (truncated read / premature declaration before the capture was visible) could never recover even once its genuine capture existed. 298 rows (204 MLB + 94 WNBA) were stuck this way. Fix (CLV computation only — no grade/locked_odds/outcome touched): - Read ONLY priced captures (missed_reason IS NULL, both odds NOT NULL), scoped to the candidate rows' game_dates -> small AND complete, no arbitrary truncation. - Drop the market_unavailable exclusion from candidates; make it a re-checkable absence: a genuine close now UPGRADES the row (writes closing_prob, clears the verdict). closing_prob stays write-once (first true close wins). No capture + past game -> still declared absent (honest). No churn on already-absent rows. - New internal trigger POST /api/internal/ledger/attach-closing[/:sport] for backfill + verification (scheduler already runs attach per tick). Recovers ~312 usable closes (59 -> ~371), MLB included. Capture itself was healthy all along (94.9% MLB / 95.8% WNBA per-prop coverage). Full suite 3835 green (17/17 instrument tests incl. 2 new recovery cases), web build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
afb56b144b |
NexaPay purge: VYNDR is Stripe-only — remove all NexaPay traces
NexaPay was cross-project contamination (from another venture) — never a real VYNDR payment path. Purged; Stripe path untouched. Removed: - web/src/services/nexapay.ts (createPaymentLink/getTransaction/HMAC verify) - web/src/app/api/webhook/nexapay/route.ts (the only importer; Next-registered, reachable — now gone) - NexaPay comments in email.ts + checkout/route.ts - Active NexaPay entries in docs/SYSTEM-MANIFEST.md (route list, NEXAPAY_* env table, service row) + stale claim in wiring-data-train.md - sw.js precache entry for the deleted webhook chunk Verified: ZERO NexaPay in code (web/src, src, tests). Full suite 3833 green (count unchanged — nothing depended on it, confirming it was dead). Web build exit 0. sw.js parses clean. Stripe checkout untouched (Next→Express→Stripe). FLAGGED FOR KEV (a repo delete cannot close these): - Coolify env: remove NEXAPAY_API_KEY / NEXAPAY_WEBHOOK_SECRET / NEXAPAY_API_URL - Revoke the NexaPay API key + webhook secret at NexaPay's dashboard; de-register the webhook if an account was ever configured - DB column user_profiles.nexapay_customer_id is orphaned (no reader/writer) — drop via a follow-up migration (migration 011 left as history) Cross-project check: ZERO Noctem-Supabase refs; VYNDR references only its own Supabase (zmdnczhtdxcddsxzttub). NexaPay was the sole contamination found. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
3592aba8d5 |
docs: log honesty pass in completion matrix + STATE (rows 19/20/24 HONEST fixed)
Records the six live fabrications removed/hidden, keeps media/newsletter/WIRE on the board as real work, logs the news/line-movement signal as a future model input, and logs the known honesty gaps (hit-rate-without-ROI, CLV starved). Honest state: "no KNOWN live fabrications," not "provably none." Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
6bc18d823c |
Honesty pass: remove every live fabrication (REMOVE/HIDE only, no feature cut)
Six live untruths corrected — no grade/snapshot/scorer/pipeline touched: 1. /compare — hardcoded Jokic A+/Wembanyama A + fake VERDICT replaced with an honest in-development state; removed from Nav + BottomTabBar (route still resolves, never the sample). Real two-player build is later. 2. Pricing — founder Desk $34.99→$44.99 (matches lib/checkout.js), Analyst $14.99; removed the struck $19.99/$44.99 "regular" numbers and DeskShowcase's stale $34.99. First-100 counter is real (ClaimMeter → Stripe countFounderSeats); no fake "first 50" desk claim added (no such counter exists). 3. FAQ "NexaPay" → Stripe (verified: live checkout is Next→Express→checkout.stripe.com). 4. FAQ + Features "Brier/CLV published from day one" removed (not surfaced yet) — returns when real. Backend Brier compute untouched. 5. MobileEdgeBoard removed from the Slate — its edge% feed was a miscalibrated placeholder (masked >40% as "—"); phones now show the real game cards. 6. Price triplet — never-computed model/EV now derives NO_MODEL (honest absent, MODEL "—" / "NOT PRICED", no verdict) instead of QUARANTINE's false "we suppressed our price / a leg is poisoned" copy. Fixes grade card + LiveHeroProp. Full suite 3833 green, web build exit 0. Tests updated to the new honest contracts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
be6b4a2849 |
inventory: VYNDR-COMPLETION-MATRIX (5-part DONE map of every surface + model)
Read-only inventory order — no code built/wired/fixed/deployed. Maps every
user-facing surface and model component against DESIGNED·BUILT·WIRED·LIVE·HONEST,
re-derived from repo
|
||
|
|
b0a51c8a0d |
docs: /api/books contract + crown gate (Book Comparison order)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1 |
||
|
|
e81c9b8c51 |
Book Comparison Phase 1-3(backend): fenced per-book store + honest gated crown
Per-book prices existed only transiently (odds cache, ~1h, raw names, grade-path
input); every grade-path persistence point collapses to one book. The
/api/books feature was built+mounted but non-functional (fed FLAT rows to a
GROUPED comparator -> always empty).
Phase 1: bookPriceStore captures per-book prices from `props` BEFORE dedupeProps,
keyed nameKey|stat, into bookprices:{sport} (SNAP_TTL) in snapshotService. Fenced:
reads props, writes its own key, read by nothing on the grade path. Grade proven
byte-identical (test + no-grade-path-reference grep test).
Phase 2: scripts/measure-book-spread.js reports same-line best-vs-worst spread
(cents + implied-prob pts), per sport, never pooled. Pre-registered crown
threshold: median >=8c OR >=2pp. Runs post-deploy on real data.
Phase 3 (backend): compareProp is honest-absent (single-book/flat -> no crown)
and the crown is gated (BOOK_CROWN_ENABLED, default OFF until Phase 2 clears).
/api/books repointed to the snapshot-locked store (fallback odds cache),
nameKey-matched; `source` field is the deploy fingerprint.
HELD unchanged: dedupeProps, snapshot dedup, selector, grade, champion,
challengers, ranking, edge_pct/ev_pct. UI routing of BookComparison + crown
treatment deferred to post-measurement (gated on Phase 2). Full suite 3834 green,
web build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
|
||
|
|
914a057611 |
proj-v1 book-implied: raw odds → de-vigged FAIR (fix self-flattering basis)
proj_book_implied derived from raw book_odds — VIG-INCLUSIVE. A -110/-110 market
implies 52.4%/side (104.8% sum); fair is 50%. Comparing our P against raw book
overstates the book on both sides, biasing the handicapper test IN OUR FAVOR; on
juiced longshots (the Judge HR -18.5pt case) much of that "edge" was vig, not
disagreement.
Fix (fenced to proj-v1's stored comparison basis): proj_book_implied now derives
from DE-VIGGED FAIR via the grade's g.fair_prob — the SAME multiplicative de-vig
the triplet uses (utils/devig.js), so the basis matches the product's shown fair.
Expressed on the OVER basis (under props → 1 - fair) to match our stored P(≥rung);
traded-rung ladder book_implied likewise. HONEST-NULL where fair is uncomputable
(one-sided market, ~14%) — NEVER a raw-book fallback (that would recreate the vig
bias on a subset and mix two bases in one ledger). proj_factors records
book_implied_basis ('fair_multiplicative'|'none').
Phase 0 (prod-verified): fair reachable at store point (g.fair_prob on the grade,
no threading); 86% batting coverage; method = multiplicative/proportional.
Phase 2 FLAG: multiplicative de-vig mis-splits vig on juiced longshots (favorite-
longshot bias), so a longshot fair still carries known method bias — flagged
per-row (longshot_devig_caveat); a better de-vig (Shin/power) is a separate item.
Phase 3: version bumped proj-v1 → proj-v1.1 so pre-fix (raw-book) and post-fix
(fair) rows never silently mix — the projection model is byte-identical, only the
basis changed; pre-fix rows can't be recomputed (only the graded side's odds were
stored). Champion + arch-v1 + contact-v1 + proj-v1's other columns untouched.
proj suites 26/26.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
|
||
|
|
e96b0dbb6d |
proj-v1: book-implied from book_odds (grades carry odds, not fair_prob)
The live fingerprint showed proj_book_implied null on every real row: grades carry book_odds/locked_odds (e.g. -264) but NOT a de-vigged fair_prob, so keying the book comparison off fair_prob yielded null. The book ODDS are exactly "the book's implied probability" the handicapper test needs. Now proj_book_implied + the traded rung's book_implied derive from americanToImplied(book_odds), expressed on the OVER basis (under props → 1 - implied) so it's directly comparable to our P(≥rung). Vigged (a known offset the ledger measures both sides of). proj-v1 suites 24/24. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj |
||
|
|
316b79733e |
proj-v1 sanity fixes (caught in the real-data induction)
1. matchupRead fly-ball signal: the batter metrics `gb_pct_bb`/`fb_ld_pct` are MISLABELED — they're exit velocities by batted-ball type (Judge fb_ld_pct = 100.3 mph, not a rate), not ground/fly RATES. Switched fly-ball lean to avg_launch_angle (league p10/p50/p90 = 7.1/13.9/20.1°), the correct signal. 2. Absolute rate now fits the FULL season (recency-weighted), not a 20-game window: the window under-sampled rare stats — Judge HR projected 0.11 vs his 0.28 season rate (a fake -32pt edge). Now point=0.27 (matches season); the last-5-2x recency lean is preserved. Post-fix induction (real statsapi logs + real statcast): Judge HR 0.27 (P>=1 0.235 vs book 0.42 -> flags the juiced over), Judge TB P>=2 0.548 vs 0.48 (+6.8pt), thin-hot 3-game P>=1 0.726 / P>=3 0.164 (credible low, thin high), .300 hitter != 3.0. proj-v1 suites 23/23. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj |
||
|
|
6386e737b9 |
proj-v1: absolute matchup projection challenger (distribution + full ladder)
A THIRD challenger (after arch-v1, contact-v1), MLB batting v1. Champion is market-relative P(stat>LINE); proj-v1 is ABSOLUTE — what the hitter will DO — emitted as a full distribution from which the WHOLE LADDER (P≥1,P≥2,P≥3) derives. Champion untouched; nothing claimed; the ledger decides per rung, per stat. - projection/distribution.js — Bayesian Gamma-Poisson → negative-binomial predictive. Admits over-dispersion; under-dispersion → Poisson approx (conservative, documented). Uncertainty scales with sample by construction (r=α): thin → WIDE (real mass on P≥1, honestly thin P≥3), thick → tight. NEVER abstains — width carries the honesty. - projection/matchupRead.js — the input the book doesn't use. HONEST FIDELITY: pitcher repertoire is rich (97% pitch-mix) but hitters have NO pitch-type performance, so TRUE repertoire-vs-profile is impossible today. This is the COARSE version (arsenal buckets fastball/sinker/breaking + whiff/hard-hit tendency × hitter whiff/chase/gb-fb/hard-hit) — beats generic L/R, derived + documented + TESTED two-sided. A hitter pitch-type feed unlocks the true form. - projectionChallenger.js — park RELATIVE to the player's own log exposure (isHome→own park, away→opp park; Phase B's raw-multiply bug solved), recency- weighted fit, per-factor breakdown (form/park/weather/platoon/matchup — show your work), full rung set + book-implied per rung. Combined non-form multiplier bounded. - Wired after contact-v1, own try, flag PROJ_V1_ENABLED, reusing arch-v1's already-computed park/weather/platoon (no duplicate env I/O). Own ledger columns (migration 032, applied to prod): distribution, ladder, point, line, our-P, book-implied, factor breakdown — measurable per rung/stat after settle. Phase 0 (prod-verified): venue join via isHome; NB family; uncertainty-as-width; coarse matchup honest fidelity; no lineup-slot (per-game rate, volume implicit). Sanity: thin-hot → wide (credible low rung, thin high rung); .300 hitter ≠ 3.0; matchup two-sided; champion byte-identical. proj-v1 suites 23/23; snapshot/ ledger/siblings 74 green. Forward-only, version-stamped, PROJ_V1_ENABLED kill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj |
||
|
|
b6f12daa98 |
Contact-quality challenger (contact-v1) — nominate, don't swap
Phase A #2: the champion grade (l5/l20 result-based form) is a HYPOTHESIS that contact quality predicts better — unmeasured on our props, with zero settled p_win yet. Swapping l5/l20 (the champion's two heaviest ±1.0 factors) blind could degrade the core grade undetectably for weeks. So this NOMINATES contact quality as a second challenger, records what it WOULD project per prop, and lets the settled ledger decide. Nothing users see changes; the champion is untouched. - src/services/contactChallenger.js — pure, mirrors challengerProjection. Log- odds lean (capped, never a re-forecast) from SEASON contact quality vs league percentiles. Metric→prop mapping is the whole game: barrel_pct→HR, hard_hit_pct→TB/doubles, k_pct-INVERSE→hits (singles resolve on contact frequency, not barrels), k_pct→batter K. rbi/runs/walks ABSTAIN (opportunity/ discipline — no clean contact predictor). Honest-absent: thin (<50 PA)/absent/ unmapped/non-batter → p_win_contact NULL (no projection), never a fallback; "measured but unremarkable" is distinct (equals champion, delta 0). - Wired in snapshotService AFTER arch-v1, reusing the already-loaded statcast rows; its own try so a second challenger can't break the pipeline. Reads g.p_win, never writes it. - Retained SEPARATELY on the ledger (p_win_contact/contact_delta/ contact_adjustments/contact_version='contact-v1') so each challenger's marginal contribution is measured independently; ledger_entries.stat gives per-prop-type segmentation. Migration 031 (applied to prod). Phase 0 (prod-verified): statcast_aggregates is SEASON cumulative (not rolling), 48h stale now but season-scoped so ~8 PA/600 is negligible; 100% of graded hitters covered, 92% at ≥50 PA; no xBA/xwOBA in the feed. Forward-only, version-stamped (contact_version null on pre-nomination rows). Promotion is a LATER decision on settled evidence, per prop type — never asserted here. contactChallenger 14/14; snapshot/ledger/arch-v1 suites 80 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj |