# SPEC — LEDGER TAKEABLE TAGGING (built) + MULTIPLICATIVE EFFICIENCY SCALING (BLOCKED) 2026-07-31. Champion grade UNCHANGED. Push scoring untouched. --- ## 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`, or `efficiency_score` in `src/` or `web/src/`. `src/config/` holds no efficiency file (the one `0.80` there is `providers.js WARN_PCT`, a quota warning). - **0.2 base thresholds — DO NOT EXIST.** There is no "MLB A: 5% edge / NBA A: 7% edge" table. `engine1.js` contains **zero `edge` references**; the grade is not an edge-vs-threshold comparison. `grade_thresholds.json` holds PROBABILITY bands. - **0.3 the ±0.05 additive nudge — DOES NOT EXIST.** Searched the whole grade path. The only `0.05` values are `featureCache.teammate_absence_bump` (a feature), a `bvp_advantage` cutoff for a matchup letter, and `p*0.9 + 0.05` inside `probabilityEstimator` (the `0.5 × 0.1` term of the shrink-toward-0.5, not efficiency). **There is no additive efficiency application to replace.** No market-conditional logic exists in `engine1.js` or `analyzeViaEngine1.js` at all. ### Why the challenger cannot be built as specified The order requires a challenger differing from the champion in **EXACTLY ONE thing — multiplicative vs additive scaling**. That construction is impossible here: - there is no additive scaling to swap, - there is no base threshold to multiply, - and `engine1.js` has **zero `sport` references**, so market/sport cannot reach the grade. To have a threshold to scale, the challenger must FIRST grade on probability-vs-threshold — which is rule **R1 of `specs/full-output-grade-mapping.md`**, an explicitly held separate order. Shipping R1+R4 together would (a) violate the isolate-one-variable requirement and (b) make the Phase-3 delta report **misleading**: the re-letter would be driven mostly by switching to probability grading, presented as the efficiency fix. Kev would be approving the wrong attribution. **That is why this half stopped rather than guessed.** ### 0.4 — coverage gap that would sink the build anyway The spec names 5 scores (NFL passing .85, NBA points .80, MLB K .60, MLB TB .55, NBA role .55). The live public ledger contains **11 markets**: MLB hits (376 rows), doubles (247), total_bases (44), stolen_bases (17), earned_runs (6), home_runs (4), outs (2); WNBA points (246), rebounds (156), assists (142), threes (10). **Only MLB total_bases maps to a specced score.** MLB K's and NFL passing do not appear at all; WNBA is not in the spec list. So **9 of 11 live markets have NO score** — and the guardrail ("all scored markets, or it's incoherent") cannot be satisfied without inventing 9 numbers. Inventing them would be fabricated calibration. **What Part A needs before it can run:** (1) R1 probability grading, so a threshold exists; (2) efficiency scores for the markets we actually grade — ideally **FIT from each sport's own accrued record**, not hand-set (that is R4's stated design). --- ## PART B — LEDGER TAKEABLE TAGGING: **BUILT** (the deferred C2) Independent of Part A: it needs no threshold, no scaling, no grade change. ### The standard (`src/config/takeableStandard.js`) isLedgerTakeable(american) = american >= LEDGER_TAKEABLE_FLOOR // default -160 // FLOOR on the minus side, UNCAPPED on the plus side. **🔴 It is deliberately NOT `valueEngine.isTakeable`.** Those answer different questions: | | shape | question | |---|---|---| | `valueEngine.isTakeable` | −160..+200, **bounded both ends** | "may we PROMOTE this to the hero / top of board?" | | `takeableStandard.isLedgerTakeable` | ≥ −160, **uncapped plus** | "was this a price a bettor could actually have TAKEN?" | A **+400** prop is **NOT promotable but IS takeable** — both true at once. A unit test asserts they diverge on the plus side and agree at the minus floor, so the two can never quietly merge. **Absent price → `null`, never `false`.** Without the strict guard `Number(null) === 0` is ≥ −160 and a MISSING price would be tagged takeable — fabricated data in the record. **The floor is POLICY, not derived, and is labelled so everywhere.** C1 (2026-07-30) could not derive one: every price bucket's 95% ROI interval contained zero, the curve was non-monotone (deepest buckets positive), and resolving a 5-point edge needs 661–2,285 rows/bucket against actual sizes of 8–71. −160 is held because it is directionally the better half of the only available comparison (in-band ROI +3.84% vs out-of-band −0.04%, both spanning zero). **Re-derivation trigger:** any negative bucket reaching n ≥ 300, or the end of the MLB regular season — and adopt a derived floor only when a bucket's 95% CI excludes zero. ### Schema (migration 034, applied + tracked) `ledger_entries.takeable boolean` + `takeable_floor numeric`, both nullable, plus a partial index on `(sport, takeable) WHERE user_id IS NULL`. **`takeable_floor` records the floor each row was judged under**, so a future re-derivation can re-tag safely instead of guessing which standard a row was tagged by. Additive only — nothing deleted, nothing re-settled. ### Forward + backfill Forward: `ledgerService` stamps `takeable`/`takeable_floor` at row build from the locked odds. Backfill: every row with a parseable locked price, one statement, no outcome/grade/price touched. **Result — 1,254 rows: 1,246 tagged (781 takeable / 465 below floor), 8 NULL, and `null_despite_price = 0`** — i.e. every priced row is tagged and the 8 NULLs are genuinely priceless rows (honest absence). One distinct floor (−160). Settled 1,163 and graded 1,254 both unchanged. --- ## PART C — MODEL-VERSION BOUNDARY TAG: **DELIBERATELY NOT APPLIED** Phase 2.4 asks for a boundary separating pre-fix (additive) from post-fix (multiplicative) grades. **No scaling change shipped, so no such boundary exists.** Stamping one would mark a model transition that never happened — a fabricated era in the permanent record, which is the exact class of thing the Data Semantics Rule forbids. `src/config/modelEras.js` (Session 64) is the correct home for it **on the day a real scaling change promotes**. --- ## VERIFY Champion grade path untouched (`engine1`, `analyzeViaEngine1`, `probabilityEstimator`, `gradeSlateService`, `grade_thresholds.json` — no diff). **312 suites / 3,890 tests green** (8 new), **web build exit 0**. Grade visuals are auth-gated → Chrome audit; this order changed nothing visual. ## TAGS VERIFIED: efficiency scores/base thresholds/±0.05 nudge all absent; 11-market coverage gap; tagging built + backfilled + integrity-checked. CANNOT DETERMINE: none. **BLOCKED: Part A (the efficiency challenger) — needs R1 probability grading plus fitted per-market scores.**