| name | heater-appb-lineup-rec-review |
| description | Use when assessing whether a HEATER lineup recommendation is safe to enact — /api/lineup/optimize or /api/start-sit output, the Optimizer page's start/sit calls, daily swaps, streaming pickups — "does this lineup make sense?", "why is it benching X?", or before applying any roster moves it suggests. |
Lineup/Start-Sit Recommendation Review
REQUIRED BACKGROUND: heater-appb-manager-rec-review (league truth, data gathering, verdict protocol). This is the lineup-specific checklist. Payload fields: LineupSlot{status, value 0-100, matchup, current_slot, forced_start, reason} + daily: DailyMeta{urgency, rate_modes, ip_pace, swaps} + bench/impact/fa_suggestions{urgency_categories} (api/contracts/lineup.py:24-91).
The checklist
- Slot legality. Every started player eligible for the assigned slot (eligibility = Yahoo positions /
roster_slot; assignment = selected_position/current_slot — do not confuse them: confusing them once routed SP,RP swingmen into the hitter table). Counts exactly C/1B/2B/3B/SS/3×OF/2×Util/2×SP/2×RP/4×P. No IL/DTD player in an active slot.
- Probables gate. Every started pure SP must be a confirmed/probable starter for the scope date (check the probables board or statsapi, ET dates). The engine zeroes non-probable pure SPs (
volume=0) — a started NOT_PROBABLE SP means the gate broke (it WAS dead code once; the DCV gate fix pinned it).
- Locked games. Today-scope recs must not move players whose games already started (ET clock —
src/et_today.py; UTC evenings falsely unlock). Locked rows should render "—"/non-actionable, not 0.00.
- IP pace coherence.
ip_pace vs floor 20 / target ≈53.85 (src/ip_tracker.py:16,20). A "DANGER — stream SP immediately" call must square with (a) actual remaining probables this week and (b) adds_remaining_this_week > 0. Failure story: live showed "0 IP DANGER" for a healthy staff — the pool's projected ip was 0 (FanGraphs-403/Marcel gap), fixed by the _ip_pace YTD fallback (lineup_service.py:~506-569). Check freshness before believing an extreme pace claim.
- Rate-mode sanity.
rate_modes (ERA/WHIP → protect|compete|abandon) must match the live matchup state: protect when clearly winning the cat, abandon only when it's mathematically gone. An "abandon" stance on a winnable cat once traced to zeroed pitching projections — data, not strategy.
- Bench-a-stud test. For every benched player with strong consensus/recent production, the payload must contain the evidence: low DCV
value, bad matchup (elite opposing offense, hitter park), not probable, locked, or IL. forced_start=True marks the engine admitting a bad start was roster-forced — surface those. No evidence = DO-NOT-ENACT + investigate.
- Cross-surface agreement. Same player, same day: Optimizer lines/slots must agree with the Matchup tab (shared slot order —
web/src/lib/slot-order.ts mirrors the backend; ordering by RECOMMENDED position instead of assigned slot was a live bug). Win-prob mentions must equal the seeded outcome_probs number.
- Scope arithmetic.
today = daily DCV; rest_of_week/rest_of_season = LP with window-scaled totals (counting ÷ 26 per week). A weekly total that looks like a season total (or vice versa) is the window-scale bug class.
- Streaming/FA suggestions. For each
fa_suggestions entry: live-verify the player is STILL an FA (never trust a cached list — standing rule); urgency_categories are genuinely losing/tied cats; the implied drop is legal (not rounds-1-3 can't-drop, not an IL stash the UI protects); budget respects the 10/week combined cap; two-start weeks valued with the 0.93 second-start fatigue, not double full-quality.
- Recompute one number. E.g. proposed staff WHIP = Σ(bb+h)/Σip from the payload's own lines — catches mean-of-rates and volume-weighting regressions instantly.
Red flags (instant DO-NOT-ENACT pending investigation)
Empty matchup strings across the board (the statsapi home_name mapping bug class) · every value ≈ 100 (normalization saturation) · literal "nan" anywhere (reason NaN class, fixed at lineup_service.py:459-460) · an empty lineup with HTTP 200 (the pipeline.run() no-such-method incident shipped exactly this) · started SP facing an elite offense with low value and forced_start=False.
When NOT to apply
Judging the ALGORITHM (weights, DCV math) → heater-appb-fantasy-math-invariants + calibration-backtest. Yahoo write execution is out of scope (writes 401 by design — enactment is manual).