PHASE 1 — CLOSE-CAPTURE RETRY, test-first. The closing capture gets a
retry the snapshot path deliberately does not: a snapshot re-runs at the
next slot, but a MISSED CLOSE IS PERMANENT, and the feed flaked once on a
dry induce. Three hard rules, each driven by a test written before the
logic:
- BOUNDED attempts (default 3) with short backoff so every attempt fits
inside the window. Never infinite.
- HARD LOCK-WALL: inside lockWallMinutes of first pitch (or past it) it
stops and records missed_close. A price captured AT or AFTER lock is
NOT a close; storing one would fabricate the CLV baseline.
- NO BOUND LOCK TIME -> refuse immediately, never burn retries on a prop
whose close cannot be timed.
On exhaustion it records missed_close with NO price — never a stale,
mid-day or post-lock line.
PHASE 3 — MLB opp_rank_stat DERIVED, contract-locked. MLB previously had
no opponent metric at all (ESPN's MLB team endpoint carries none), so
engine1's +/-1.0 opponent factor never fired for the sport carrying most
of our volume. Derived from data we already ingest: statsapi team pitching
splits, all 30 teams in ONE free unauthenticated call.
THE SHARED CONTRACT is documented and TESTED, not assumed: 0-1 scale,
HIGH (>=0.70) = WEAK opponent, LOW (<=0.30) = TOUGH — identical to WNBA's
live semantics. Polarity is the highest-risk part: backwards polarity does
not fail loudly, it silently adjusts every MLB grade the wrong way. A test
asserts MLB polarity EQUALS WNBA polarity using engine1's own thresholds.
PROVEN AGAINST THE LIVE FEED:
Colorado Rockies BAA .286 -> opp_rank 0.983 (weak, fires weak_opponent)
LA Dodgers BAA .215 -> opp_rank 0.017 (tough, fires top_opponent)
POLARITY HOLDS: true
HONEST NULLS, tested: thin league baseline, thin opponent sample, unmapped
stat, unknown opponent, or a missing field all return NULL with a reason —
we are FIXING a silent null, so it is never replaced by a confident guess
off three games. opponentStrengthHealth pages on an empty source AND on
derived-null-for-a-sport-we-expect-to-derive.
Suite 285/3435 green, build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
PART A — HARNESS ARMED ON OUR OWN INFRA. snapshotScheduler now runs the
nightly backtest at HARNESS_HOUR_UTC (default 14), appends to
harness_results, and pages via opsWatch.harnessStaleAlarm — a validator
that stops running looks exactly like one that keeps passing. No external
dependency: the join is plain SQL through the service client and the
harness is a pure function. POST /api/internal/harness/run induces the
same code path on demand, because a scheduled mechanism is verified by
inducing it, never by waiting for a slot.
PART B PHASE 0 — GATE PASSED for what is capturable:
- C4 diagnosed: closing_line is ONE overwritable field with no timestamp
and no provenance. captureClosing writes the current line and, when a
prop fails to match, silently leaves the earlier value (= the lock) in
place — so "captured a real close" is indistinguishable from "never
updated". It is 92% equal, not 100%: 56 rows DID record movement, so
the defect is provenance, not the value.
- Feeds: normalized props already carry BOTH raw side prices per book,
with game_time, and the intraday refresh polls every ~20 min during
slate hours — so the last observable pre-lock line is available.
- SHARP close: pinnacle is in ALLOWED_BOOKS -> a no-vig reference is
capturable ("beat the market").
- ODAWA: NOT capturable. 'odawa' exists only as a UI preference option in
onboarding/settings; it is in no adapter, no ALLOWED_BOOKS, no feed. An
un-capturable source is a finding, not a gap to paper over.
- JOIN: must drop `line` from the natural key, because a close that MOVED
off the graded line is the entire point of CLV. Verified safe — all 164
current identity groups have exactly ONE line per
(sport, player_key, stat, side, game_date). Zero ambiguity.
PART B PHASE 1 — CAPTURE ONLY, built test-first. The refusal was proven
before the capture logic existed: unbound game_time, doubleheader
ambiguity, a missed pre-lock window, or a one-sided price all record
missed_reason with NO price. A stale or mid-day line substituted for a
close would manufacture a CLV proof from a number that was never the
close.
migration 029 closing_captures: append-only, never overwritten (that is
the provenance C4 lacked), BOTH raw side prices so the existing de-vig
engine can compute a fair closing probability later, sharp vs book line
types kept distinct. Wired into the intraday refresh with a capture-rate
alarm — a missed close is unrecoverable.
NO CLV metric built, as ordered. This starts the clock.
Suite 284/3417 green, build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA