Player Props Desk Build Template

Purpose: This document is a universal template for building a new player props desk in EdgeClaw. An AI reading this should know exactly what to build, how to wire it up, and what questions to ask before starting.

Reference implementations: MLB Player Props Desk (docs/mlb-player-props-desk-construction-spec.md), MLB Desk (docs/mlb-desk-construction-spec.md)


The Pipeline Pattern

Every player props desk follows the same 4-table pipeline:

Table 1: Kalshi Prop Data (soft book — what we trade against)
    ↓
Table 2: Anchor Book Prop Data (sharp book — our fair value reference)
    ↓
Table 3: Prop Probability Curves (book curve + model curve vs Kalshi price)
    ↓
Table 4: Prop Edge Scanner Output (mispricings found)

The rule: Each table is sport-specific. No mixing data across sports. No mixing props data with game-level data. Each props desk gets its own isolated databases, separate from the game desk for the same sport.


Table 1 — Kalshi Prop Data

What: Raw player prop contract prices pulled from the Kalshi API.

Database: kalshi-{sport}-props.db Table name: kalshi_{sport}_props

Standard columns:

Column Type Description
ticker TEXT Full Kalshi ticker (e.g., KXMLBHR-26APR07-A.BREGMAN-O1)
player_name TEXT Parsed player name (standardized)
prop_type TEXT What stat: pitcher_strikeouts, batter_hits, batter_home_runs, etc.
threshold REAL The line (e.g., 6.5 strikeouts, 1.5 hits)
yes_bid INTEGER Cents (0-100)
yes_ask INTEGER Cents (0-100)
yes_exec REAL Midpoint of yes bid/ask
no_bid INTEGER Cents (0-100)
no_ask INTEGER Cents (0-100)
no_exec REAL Midpoint of no bid/ask
spread INTEGER Ask minus bid
volume INTEGER Contracts traded
scan_type TEXT Collection window: 6am, 8am, 10am, 2pm, 6pm, close
snapshot_type TEXT scheduled / event_triggered / closing
captured_at TEXT ISO timestamp

Data quality rules:

Player name parsing: Kalshi tickers encode player names in abbreviated format (e.g., "A.BREGMAN"). You need a parser to extract this and a crosswalk table to map it to the full name used by other sources. This is critical — without it, you can't match Kalshi props to anchor book props.


Table 2 — Anchor Book Prop Data

What: Sharp book prop lines that serve as our fair value reference.

For player props desks, the anchor is typically FanDuel (sharpest prop book). SBR multi-book lines serve as alternative reference for cross-validation.

Database: fd-{sport}-props.db (FanDuel) Table names: fd_{sport}_prop_lines

Standard columns:

Column Type Description
player_name TEXT Full player name as listed by the book
market TEXT Base prop type: pitcher_strikeouts, batter_hits, batter_home_runs, etc.
threshold REAL The rung: 1 (1+), 2 (2+), 3 (3+), etc.
side TEXT Yes (always for FD direct data)
price INTEGER American odds
implied_prob REAL Implied probability for Yes (0-1)
no_implied_prob REAL Implied probability for No = 1 - implied_prob
line REAL Raw handicap from API (0 for batter props, not used)
event_id TEXT FD event ID
home_team TEXT
away_team TEXT
game_date TEXT YYYY-MM-DD
scan_type TEXT Collection window
captured_at TEXT ISO timestamp

Key insight from MLB build: FanDuel's direct API gives you a full prop ladder as separate Yes/No markets at each threshold. "To Record A Hit" (1+), "To Record 2+ Hits" (2+), etc. The FD price IS the implied probability at each threshold — no distribution math needed for the book side. Store with market = batter_hits and threshold = 1, 2, 3, 4 — NOT as separate market types per threshold.

FD Direct API pattern: sbapi.{state}.sportsbook.fanduel.com/api/event-page?_ak={key}&eventId={id}&tab=batter-props (and tab=pitcher-props). No auth needed. Returns markets with runners, each runner has American odds. Use tab=batter-props and tab=pitcher-props per event.

Cross-validation rule: If FanDuel and SBR multi-book consensus both offer the same prop and their de-vigged probabilities agree within 2%, that edge gets tagged HIGH confidence. If they disagree by more than 10%, tag it LOW confidence.


Table 3 — Prop Probability Curves

What: For each player, for each prop type, for each game, build TWO probability curves and compare both against every Kalshi threshold:

  1. Book curve — Derived from FanDuel's de-vigged prop ladder. FD gives you the probability at each threshold directly. De-vig their over/under prices to get true probability.

  2. Model curve — Derived from the desk's own player model. This uses player baselines (EWMA of recent performance), matchup adjustments (opponent quality, venue factors), and a statistical distribution to generate an independent probability at each threshold.

Both curves are compared against Kalshi's price at each threshold. When either curve says a Kalshi contract is mispriced by more than the fee (typically 7%), that's an edge.

Database: {sport}-prop-edges.db Table name: {sport}_prop_probability_curves

Standard columns:

Column Type Description
scan_type TEXT Collection window
game_date TEXT YYYY-MM-DD
game_time TEXT HH:MM ET
player_name TEXT Standardized player name
player_id TEXT Crosswalk ID
prop_type TEXT pitcher_strikeouts, batter_hits, etc.
threshold REAL The alt-line value (e.g., 5.5, 6.5, 7.5 for strikeouts)
fd_anchor REAL FanDuel's posted line for this prop
fd_yes REAL FD de-vigged probability of Over at this threshold
fd_no REAL FD de-vigged probability of Under at this threshold
model_yes REAL Model probability of Over at this threshold
model_no REAL Model probability of Under at this threshold
kalshi_yes REAL Kalshi exec price for YES
kalshi_no REAL Kalshi exec price for NO
rung INTEGER 0 = main line, positive = further from 50/50
is_main_line INTEGER 1 if this threshold is the book's headline line (closest to 50/50), 0 otherwise
actual_stat REAL What actually happened (filled by settlement after game ends)
outcome TEXT "over" or "under" relative to this row's threshold
fd_was_right INTEGER 1 if FD's implied probability favored the correct side, 0 if not
model_was_right INTEGER 1 if the model favored the correct side, 0 if not
fd_error REAL How far off FD was (positive = underconfident on correct side, negative = wrong side)
model_error REAL Same for model
settled_at TEXT Timestamp when result was recorded
captured_at TEXT ISO timestamp

Distribution Selection for Model Curve

The model curve needs a statistical distribution to convert a player baseline into probabilities at each threshold. The right distribution depends on the stat type:

General rules:

Stat characteristic Distribution Why
Counting stats, low mean (0-2 range) Poisson or Zero-Inflated Poisson Many zeros, rare events (home runs, stolen bases)
Counting stats, medium mean (2-8 range) Negative Binomial Overdispersed counts — variance > mean (strikeouts, hits)
Sum of multiple stats Normal approximation Central limit theorem (H+R+RBI, PRA)
High-count stats (20+ range) Normal Large enough for Normal to work (points, fantasy score)

MLB examples (for reference):

Prop type Distribution Parameters
Pitcher strikeouts Negative Binomial mean from EWMA, dispersion from game log variance
Batter hits Negative Binomial mean from EWMA
Batter home runs Zero-Inflated Poisson lambda from per-PA rate × projected PA
Batter total bases Negative Binomial mean from EWMA
H+R+RBI combo Normal mean and sigma from component sums
Stolen bases Zero-Inflated Negative Binomial Very rare event, many zeros
Runs scored Poisson Low-count event
Pitcher outs Normal High enough count for Normal

Model Inputs

The model curve is built from player-specific baselines adjusted for matchup context. Standard inputs:

Player baselines:

Matchup adjustments (sport-specific):

The adjustment formula:

adjusted_rate = blended_rate × opponent_factor × venue_factor × other_factors

Cap adjustments to reasonable range (e.g., 0.75 to 1.30) to prevent extreme outputs.


Table 4 — Prop Edge Scanner Output

What: The final output — player prop mispricings found by comparing Kalshi prices against both the book curve and model curve.

Database: {sport}-prop-edges.db (same DB as curves) Table name: player_prop_edges

Standard columns:

Column Type Description
player_name TEXT
prop_type TEXT pitcher_strikeouts, batter_hits, etc.
side TEXT over / under
line REAL Kalshi threshold
anchor_line REAL FD's posted line
fd_prob REAL FD de-vigged fair probability
sbr_prob REAL SBR multi-book consensus probability (cross-validation)
model_prob REAL Model probability
kalshi_price REAL What Kalshi is offering
execution_price REAL What you'd actually pay
raw_edge_book REAL fd_prob - kalshi_price
raw_edge_model REAL model_prob - kalshi_price
net_edge REAL Edge after fee (7% Kalshi fee)
confidence_tier TEXT HIGH / MEDIUM / LOW
executable BOOLEAN Spread tight enough to trade?
distribution_type TEXT Which distribution was used
player_sigma REAL Player-specific variance parameter
scan_type TEXT Collection window
detected_at TEXT ISO timestamp
actual_outcome TEXT win / loss / push (filled after settlement)
settled_at TEXT
closing_price REAL For CLV tracking
clv REAL Closing line value

Confidence tier rules:

Steam Detection

In addition to edges, track sharp line movement:

Table: {sport}_prop_steam

Signals to detect:


Scheduling

Everything runs on Eastern Time (ET).

Layer 1 — Prop Data Collection (top of hour)

Minute What fires Cron pattern
:00 FanDuel prop lines pull 0 6,8,10,14,18 * * *
:00 Kalshi prop contracts pull 0 6,8,10,14,18 * * *
:00 Game-level anchor (Pinnacle) for matchup context 0 6,8,10,14,18 * * *

Game-day windows: 6 AM, 8 AM, 10 AM, 2 PM, 6 PM ET Closing snapshot: 1 minute before each game's start time (staggered per game)

Layer 2 — Player Stats & Baselines (daily morning)

Three staggered groups:

Group Time What runs
Group 1 — Raw Data 9:00 AM Stats API pulls: player game logs, season stats, external scrapes
Group 2 — Baselines 9:05 AM EWMA baselines, career stats, blended rates, player crosswalk
Group 3 — Derived 9:10 AM Matchup context, player variance, adjusted rates, correlations

Layer 3 — Curves & Edge Detection (after data lands)

Minute What fires Cron pattern
:03 Steam detection (compare consecutive snapshots) 3 6,8,10,14,18 * * *
:10 Prop probability curves rebuild 10 6,8,10,14,18 * * *
:12 Prop edge scanner 12 6,8,10,14,18 * * *

Critical: Same rule as game desks — edge scanner runs AFTER curves, curves run AFTER data collection. The timing chain is: data (:00) → steam (:03) → curves (:10) → edges (:12).

Layer 4 — Season Props & Awards (adaptive)

If the sport has season-long player props or award futures:

Phase Frequency When to transition
Phase 1 Weekly (Monday) Start of season → ~2 months before end
Phase 2 Every 3 days ~2 months before end → ~1 month before end
Phase 3 Daily ~1 month before end → season end
Expired Stop scanning After season ends

Database: kalshi-{sport}-prop-futures.db (season props), kalshi-{sport}-awards.db (awards)


Dashboard Wiring

desk-config.ts

{
  name: '{Sport} Player Props',
  slug: '{sport}-player-props',
  kalshi: [{
    category: 'Sports / {Category} / {Sport} Props',
    hasScraper: true,
    scraperName: 'collector.ts (Kalshi sports cron)',
    freshnessKey: 'kalshi-{sport}-props',
    series: [
      { ticker: 'KXSPORTPROPTYPE', label: 'Human Label', dataViewKey: 'kalshi-{sport}-prop-type' },
      // one per Kalshi prop series
    ],
  }],
  sources: [
    // Sources organized by group — see Standard Groups below
  ],
}

Standard Groups for Player Props Desks

Every player props desk should have these groups on the dashboard:

  1. Edge Detection — Prop edge scanner results (per prop type), steam detection, edge summary
  2. Game-Day Props — FanDuel prop lines, SBR multi-book lines, Kalshi player props, anchor book (game context), prop probability curves (book + model vs Kalshi per prop type)
  3. Season Props — Season-long player prop futures (if applicable)
  4. Awards — Individual award futures (if applicable)
  5. Player Data (sport-specific subgroups) — Player game logs, baselines, splits, career stats, crosswalk
  6. Game Context — Lineups/rosters, schedule, venue factors, weather, official tendencies
  7. Computed Analytics — Blended rates, matchup-adjusted rates, player variance, correlations, matchup context

source-tables.ts

Same pattern as game desks. Every freshnessKey needs a mapping:

'freshnessKey': {
  db: 'database-name',
  tables: ['table_name'],
  filter?: { column: 'col', value: 'val' },
}

Naming convention for freshnessKeys:

scheduler.ts

Same pattern as game desks. Register each cron job, update freshness after each run.

Freshness Thresholds

Source type Yellow (stale) Red (alert)
FD prop lines (6/8/10/2/6) 30 min after window 90 min after window
Kalshi props (6/8/10/2/6) 30 min after window 90 min after window
Season props/awards (adaptive) 2 days after expected 7 days after expected
Prop edge scanner 30 min after window 90 min after window
Player baselines (9 AM) 60 min after expected 180 min after expected
Matchup context (9 AM) 60 min after expected 180 min after expected
Steam detection 60 min after last signal 180 min after last signal

Shared Data (Read-Only from Parent Game Desk)

Player props desks READ from the parent game desk's databases but don't write to them. This gives props access to game-level context without duplicating scrapers.

What the props desk reads from the game desk:

The props desk does NOT duplicate these scrapers. It just reads the tables that the game desk already populates.


Isolation Checklist

# Item What to check
1 Databases isolated Props in own .db files, separate from game desk
2 Scraper queue independent Props scrapers don't depend on game desk timing
3 Recovery queue Props scrapers in recovery queue
4 Freshness tracking Every props source has freshnessKey on dashboard
5 Dashboard views Every freshnessKey resolves to working view
6 Edge scanner Props scanner separate from game scanner
7 Scan windows Every row tagged with scan_type
8 Data cleanliness No live, no settled, same-day for game-day props
9 Column formatting Human-readable on dashboard
10 Filters Column filters on all views
11 No cross-desk dependencies Props desk works even if game desk scraper fails
12 Player name crosswalk Names standardized across all sources
13 Alerts Freshness alerts for all props sources
14 Season props isolated Separate DB from game-day props
15 Awards isolated Separate DB from game-day props
16 Probability curves Both book and model curves, per prop type
17 Matchup context Daily refresh wired to Group 3 cron

Things to Watch Out For

Hard-won lessons from building the MLB Player Props desk. Read these before starting any new props desk.

  1. Player name mismatch is the #1 problem. Kalshi uses abbreviated tickers ("A. BREGMAN"), FanDuel uses full names ("Alex Bregman"), and stats APIs use yet another format. Without a crosswalk table that maps names across ALL sources, you can't match Kalshi props to FD props, and your curves table will have nulls everywhere. Build the crosswalk FIRST. Handle edge cases: Jr./Sr. suffixes, accented characters, misspellings (Kalshi had "SUREZ" for Suarez), nicknames.

  2. Wrong scanner wired to cron. The MLB prop cron was calling the generic scanner (scanPropEdges('mlb') which read from an empty player_props table) instead of the dedicated MLB scanner (scanMlbPropEdges() which read from mlb_prop_lines). The edges table was empty for days. Always test the cron manually after wiring.

  3. Filter too aggressive on minimum price. Initial 5-cent minimum filter was dropping valid alt lines. Lowered to 1 cent. Be conservative with data-side filters — you can always filter tighter on the dashboard.

  4. Kalshi API pagination. Some sports have 5+ pages of prop contracts. The initial pull only fetched page 1 and missed most data. Always paginate fully. Add 500ms+ delays between pages to avoid rate limits.

  5. FD price IS the probability (for book curve). FanDuel gives you a full ladder of over/under lines at different thresholds. You de-vig each line and that's your book probability. No distribution math needed for the book side. The distribution math is only for the MODEL curve.

  6. FanDuel direct API vs Odds API — use the direct API. The Odds API only returned 3 prop types for MLB FanDuel (stolen bases, strikeouts, pitcher outs). FanDuel's own public API (sbapi.il.sportsbook.fanduel.com/api/event-page) returns 25 prop types including all hits, HR, TB, RBI, runs, doubles, triples, singles, combos, and alt strikeout lines. No auth needed — just the public _ak key. The Odds API is a backup, not the primary source. Build a direct scraper first.

  7. FD stores one market name per prop type with a threshold column. FanDuel's data uses a single market value per prop type (e.g. batter_hits, pitcher_strikeouts, batter_total_bases) with a separate threshold column (1, 2, 3, 4, etc.) for each rung. Do NOT create separate market names per threshold like batter_hits_1plus, batter_hits_2plus — that creates 25 market types instead of 11 and makes filtering/grouping impossible. When building probability curves, query by market and group by threshold. The line column is always 0 for props — the real value is in threshold.

  8. Filter out combined player props. FanDuel offers combined pitcher props like "Zac Gallen & Freddy Peralta" for total strikeouts between both starters. Skip any player name containing " & " — there's no model rate for pairs, Kalshi doesn't offer combined markets, and they produce meaningless curve rows.

  9. Store both Yes and No implied probabilities. MLB props on Kalshi have both Yes and No contracts. Store implied_prob (Yes) and no_implied_prob (1 - Yes) for FD data. For Kalshi, store yes_bid, yes_ask, no_bid (= 100 - yes_ask), no_ask (= 100 - yes_bid). The edge scanner needs both sides to find the best trade.

  10. FD runner name parsing is tricky. For batter props, the runner name IS the player name ("Alex Bregman"). For pitcher K alt lines, the runner name includes the threshold ("Gavin Williams 3+ Strikeouts") — strip the "3+ Strikeouts" part. For pitcher K O/U, the runner name includes Over/Under ("Mike Burrows Over") — if you don't catch this, "Mike Burrows Over" becomes a player name. Handle all three cases in the parser.

  11. Edge scanner must run AFTER curves. Curves must run AFTER data collection. The timing chain is data (:00) → curves (:10) → edges (:12). If the scanner runs at :00, it reads yesterday's curves.

  12. Staggered morning schedule matters. Raw stats (Group 1, 9:00) → baselines (Group 2, 9:05) → derived metrics & matchup context (Group 3, 9:10). If you compute matchup adjustments before baselines are updated, you get yesterday's matchup context.

  13. Live data leaks in without explicit checks. Always check game start time. Skip props for games that have already started. Filter settled contracts (bid <= 5 or bid >= 95). This isn't just about cleanliness — live data corrupts your curves and produces fake edges.

  14. Kalshi API rate limits are aggressive. Kalshi returns 429 (too many requests) if you hit their API too fast. When scanning multiple series in a loop, use at least 2-second delays between calls. The collector already handles this with sleep between series, but one-off scripts and new scrapers need it too. If you're scanning 15+ series (like league leaders), expect it to take 30+ seconds. Don't retry 429s immediately — wait and try on the next cron cycle. The cadence system handles this automatically for scheduled runs.


Questions to Ask Before Building

An AI starting a new player props desk MUST ask and get answers for these before writing any code:

Props-Specific

  1. What Kalshi prop series exist for this sport? (e.g., KXMLBHR, KXMLBHIT, KXNBAPTS, KXNHLSOG)
  2. What prop types does Kalshi offer? (strikeouts, hits, HR, points, assists, rebounds, shots on goal, saves, etc.)
  3. What thresholds does Kalshi typically offer per prop type? (e.g., HR: 1+, 2+; K: 4.5, 5.5, 6.5, 7.5, etc.)
  4. Are there combo props? (H+R+RBI for MLB, PRA for NBA, etc.)

Anchor Book

  1. Which book is the sharp anchor for this sport's props? (FanDuel for most, but confirm)
  2. Which book is the cross-validation? (SBR multi-book for alt reference)
  3. Does the anchor book offer the same prop types as Kalshi?
  4. How do you get the anchor book's data? (Direct API, Odds API, scrape?)
  5. Does the Odds API cover this sport's props? (It has monthly quota limits)

Player Model

  1. What player stats are available from official APIs? (Game logs, season stats, advanced metrics)
  2. What external data sources exist? (Statcast for MLB, PBP for NBA, etc.)
  3. What's the right distribution for each prop type in this sport? (See distribution selection guide above)
  4. What matchup adjustments matter for this sport? (Opponent defense, venue, referee, rest, platoon, etc.)
  5. What's the Bayesian shrinkage k-value for each stat? (How many games/PA/possessions until season data outweighs career?)
  6. Are there sport-specific factors that change prop distributions? (Pace for NBA, power play for NHL, surface for tennis, etc.)

Roster & Lineup

  1. How do you know which players are active? (Confirmed lineups, injury reports, game-time decisions)
  2. When are lineups typically confirmed? (Affects when props become reliable)
  3. Are there position-specific quirks? (Goalies in NHL, starting pitchers in MLB, etc.)

Season Props & Awards

  1. Does Kalshi offer season-long player props? (Season HR totals, season point totals, etc.)
  2. Does Kalshi offer individual awards? (MVP, Cy Young, DPOY, etc.)
  3. What are the season dates for adaptive scheduling?

Dashboard

  1. What groups should appear on the dashboard? (Use standard groups as starting point)
  2. What prop-type sub-views are needed? (One filtered view per prop type)
  3. Are there sport-specific data groups to add? (Pitcher Data and Batter Data for MLB, etc.)

Document Control

This template is updated as new desks are built and new lessons are learned. The "Things to Watch Out For" section grows with every desk.

Last updated: 2026-04-08


Changelog

Running log of changes and decisions for the player props pipeline. Each entry has two sections:

If you're building this from scratch, read every Boss Notes section first — that's how the boss thinks about this system.


2026-04-08 — Threshold bug fix (curves showing 0 for all thresholds)

Changes:

The probability curves table had threshold = 0 for all pitcher strikeout rows, and batter prop rows were mostly missing entirely. Three bugs:

  1. Curve builder was reading fdData.line (always 0) instead of fdData.threshold (the actual number like 5, 6, 7). Fix: use fdData.threshold - 0.5 for exceedance calculation.
  2. Dedupe keyed by market name only — for pitcher strikeouts, every threshold shares the same market name, so only 1 threshold per pitcher was kept. Fix: key by market + threshold.
  3. PROP_CONFIGS listed market names that don't exist (batter_hits_1plus, pitcher_alt_strikeouts, etc.). Actual FD data uses single market names (batter_hits, pitcher_strikeouts) with a threshold column. Fix: updated all 5 prop type configs.

Also filtered out combined pitcher props ("Zac Gallen & Freddy Peralta") — useless for curves (no model rate for pairs, Kalshi doesn't offer them).

Result: 424 broken rows → 4,257 correct rows. Lessons #7 and #8 in "Things to Watch Out For" rewritten.

Files: src/pipeline/data/scrapers/mlb-prop-probability-curves.ts

Boss Notes:


2026-04-08 — Settlement system + main line tracking + beat-the-books scorecard

Changes:

Built MLB prop settlement and added tracking columns to the probability curves table.

New columns on mlb_prop_probability_curves:

New file: src/pipeline/data/scrapers/settle-mlb-props.ts

Cron: 0 15,17,19,21,23,1 * * * (every 2h from 3PM-1AM ET) — settles as games finish, not waiting until morning.

Schema update added to ensureTable() with migration logic (ALTERs if columns missing).

Updated Table 3 standard schema in this template with all settlement columns.

Files: src/pipeline/data/scrapers/mlb-prop-probability-curves.ts, src/pipeline/data/scrapers/settle-mlb-props.ts (new), src/cron/scheduler.ts

Boss Notes:


2026-04-08 — Name normalization fix (42 → 14 missing players)

Changes:

Added normalizeName() to the curve builder and settlement function. Strips accents (ñ→n, é→e), removes Jr./Sr. suffixes, lowercases. Applied on both sides — when building the model rates map and when looking up rates for FD players. Same normalizer added to settlement for matching box score names.

Players like "Ronald Acuña Jr." (model) now match "Ronald Acuna Jr." (FanDuel). Missing model data went from 42 players to 14. Remaining 14 are genuinely not in the model (rookies, bench players, or unusual name formats like "C.J. Abrams", "Max P. Muncy").

Files: src/pipeline/data/scrapers/mlb-prop-probability-curves.ts, src/pipeline/data/scrapers/settle-mlb-props.ts

Boss Notes:


2026-04-08 — Dashboard 125% display bug

Changes:

The dashboard's percentage formatter was multiplying edge_yes/edge_no by 100, but these columns are already stored as 0-100 percentages. A value of 1.25 (meaning 1.25%) was displayed as 125%. Removed edge_yes and edge_no from the explicit ×100 formatting list in data-view.ts. These columns only exist in probability curves tables, which all store values as 0-100.

Files: src/pipeline/data-status/data-view.ts

Boss Notes:


2026-04-10 — SP hand, platoon splits, batter Statcast, settlement fixes

Changes:

Files: mlb-tier2-metrics.ts, mlb-prop-matchup-context.ts, mlb-prop-edge-scanner.ts, mlb-props-edge-scanner.ts, scrape-baseball-savant.ts, data-view.ts, desk-config.ts, source-tables.ts, scheduler.ts

Boss Notes:


2026-04-08 — Kalshi MLB league leader markets (15 series)

Changes:

Added all 15 KXLEADERMLB series to the collector, dual-writer, and dashboard. New DB: kalshi-mlb-leaders.db with kalshi_mlb_leaders table. Columns: ticker, leader_type, player_name, yes_bid, yes_ask, volume, snapshot_type, captured_at. Initial scan pulled 951 contracts across 13 active series (Saves and Batter Strikeouts have 0 markets).

Series: HR, hits, RBI, runs, steals, doubles, triples, batting avg, OPS, ERA, pitcher wins, saves, pitcher K, batter K, WAR.

Dashboard: new "League Leaders" group with filtered views for HR, hits, ERA, WAR.

Adaptive schedule via existing cadence system: weekly when 3+ months out, every 3 days at 1-3 months, daily in final month.

Added lesson #9 to "Things to Watch Out For": Kalshi API rate limits — 2-second delays between series scans.

Files: src/pipeline/sports/collector.ts, src/pipeline/sports/mlb-dual-write.ts, src/pipeline/mlb-db.ts, src/pipeline/data-status/source-tables.ts, src/pipeline/data-status/desk-config.ts, scripts/scan-mlb-leaders.ts (new)

Boss Notes:

Source: ~/edgeclaw/docs/desk-template-player-props.md