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
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* THE LEDGER TAKEABLE STANDARD (2026-07-31, specs/takeable-tagging.md).
|
||||
*
|
||||
* 🔴 THIS IS NOT `config/valueEngine.isTakeable` AND MUST NOT BE CONFLATED WITH IT.
|
||||
* They answer different questions and have DIFFERENT shapes:
|
||||
*
|
||||
* valueEngine.isTakeable — the PROMOTION BAND, -160..+200 (BOUNDED BOTH ENDS).
|
||||
* "Which reads may we put on the hero / top of board?"
|
||||
* It caps the plus side deliberately: a +400 longshot is
|
||||
* not something we promote as the read of the day.
|
||||
*
|
||||
* THIS module — the LEDGER RECORD STANDARD: a FLOOR on the minus side,
|
||||
* **UNCAPPED on the plus side** (founder-ratified shape).
|
||||
* "Was this a price a bettor could actually have taken?"
|
||||
* Plus-money is where genuine edge and ladder calls live,
|
||||
* so it gets no ceiling.
|
||||
*
|
||||
* A +400 prop is NOT promotable but IS takeable. Both statements are true; keep the
|
||||
* two functions separate.
|
||||
*
|
||||
* THE FLOOR IS A **POLICY** NUMBER, NOT A DERIVED ONE — say so wherever it surfaces.
|
||||
* The C1 derivation (2026-07-30) could not derive a floor: on the clean population
|
||||
* (296 decided MLB overs) EVERY price bucket's 95% ROI interval contained zero, the
|
||||
* curve was non-monotone (the deepest buckets were the POSITIVE ones), and resolving a
|
||||
* 5-point edge needs 661-2,285 rows per bucket against actual bucket sizes of 8-71.
|
||||
* So -160 is inherited policy, held because it is directionally the better half of the
|
||||
* only comparison available (in-band ROI +3.84% vs out-of-band -0.04%, both intervals
|
||||
* spanning zero). Rows record the floor they were tagged under so a future
|
||||
* re-derivation can re-tag safely.
|
||||
*
|
||||
* RE-DERIVATION TRIGGER: any negative price bucket reaching n >= 300, or the end of the
|
||||
* MLB regular season, whichever comes first — and adopt a DERIVED floor only when a
|
||||
* bucket's 95% CI excludes zero.
|
||||
*/
|
||||
|
||||
/** Provisional policy floor (American odds). Env-tunable; changing it does NOT
|
||||
* retroactively re-tag — `takeable_floor` on each row records what it was tagged under. */
|
||||
const LEDGER_TAKEABLE_FLOOR = Number(process.env.LEDGER_TAKEABLE_FLOOR || -160);
|
||||
|
||||
/** Is this a price a bettor could actually have taken? Floor on the minus side,
|
||||
* UNCAPPED on the plus side. Strict on input — `Number(null) === 0` would otherwise
|
||||
* land a MISSING price above the floor and tag it takeable. */
|
||||
function isLedgerTakeable(american) {
|
||||
if (american == null || american === '') return null; // absent, not false
|
||||
const a = Number(american);
|
||||
if (!Number.isFinite(a)) return null;
|
||||
return a >= LEDGER_TAKEABLE_FLOOR;
|
||||
}
|
||||
|
||||
module.exports = { LEDGER_TAKEABLE_FLOOR, isLedgerTakeable, FLOOR_IS_POLICY_NOT_DERIVED: true };
|
||||
@@ -29,6 +29,10 @@
|
||||
|
||||
const { nameKey, normalizeName } = require('../utils/playerName');
|
||||
const { settleResult, statValue, logRowOnDate } = require('./outcomeService');
|
||||
// The LEDGER takeable standard (floor on the minus side, UNCAPPED plus) — a
|
||||
// DIFFERENT question from valueEngine's -160..+200 promotion band. See
|
||||
// config/takeableStandard.js for why the two must not be merged.
|
||||
const { LEDGER_TAKEABLE_FLOOR, isLedgerTakeable: takeableFor } = require('../config/takeableStandard');
|
||||
|
||||
const CONFLICT = 'user_id,player_key,stat,line,side,game_id';
|
||||
const UPSERT_CHUNK = 200;
|
||||
@@ -236,6 +240,16 @@ function rowsFromSnapshot(sport, grades, oddsProps, nowIso) {
|
||||
line,
|
||||
side,
|
||||
locked_odds: locked.odds != null ? String(locked.odds) : oddsForSide(prop, side),
|
||||
// TAKEABLE TAG (2026-07-31, specs/takeable-tagging.md) — was this a price a
|
||||
// bettor could actually have taken? FLOOR on the minus side, UNCAPPED plus.
|
||||
// 🔴 NOT `valueEngine.isTakeable` (the -160..+200 PROMOTION band the hero and
|
||||
// board rank on): a +400 prop is NOT promotable but IS takeable. Keep separate.
|
||||
// The floor is POLICY, not derived (C1 could not derive one — every price
|
||||
// bucket's ROI interval contained zero), so each row records the floor it was
|
||||
// tagged under and a re-derivation can re-tag safely. Absent price → NULL, an
|
||||
// honest absence, never false.
|
||||
takeable: takeableFor(locked.odds != null ? locked.odds : oddsForSide(prop, side)),
|
||||
takeable_floor: LEDGER_TAKEABLE_FLOOR,
|
||||
book: (prop && prop.book) || g.book || null,
|
||||
grade: g.grade,
|
||||
edge: numOrNull(g.edge_pct),
|
||||
|
||||
Reference in New Issue
Block a user