# M3 Phase 2 Ship Summary

Phase 2 is shipped as the user-facing layer for the Palantir / Fangorn / Valinor model-lane architecture. No new models were trained in this phase. The important outcome is that users can now see which lane they are viewing, understand when a lane is missing or selectively quiet, compare full three-lane consensus where it exists, and read the three model identities as methodology archetypes instead of HR-only marketing pages.

## Ladder Summary

| Step | Status | Primary artifact |
|---|---|---|
| Step 1: HR reference switcher | Shipped | `src/pitcher_card_engine/web/templates/home_run_edges.html` |
| Step 2: All-board switcher rollout | Shipped | Betting board templates for K, TB, Hits, Outs, Futures |
| Step 3: Consensus surface | Shipped | `/betting/consensus` and `betting_consensus.html` |
| Step 4: Model identity rewrites | Shipped | `/models/palantir`, `/models/fangorn`, `/models/valinor` |
| Step 5: Verification and sparse-state polish | Shipped | This document |

## Commits Shipped

| Step | Commit | Description |
|---|---|---|
| 1 | `7820de8` | Lane switcher and HR reference implementation |
| 2 | `ba668d2` | Switcher rollout to all betting boards |
| 3 | `b5023ed` | Consensus surface |
| 4 | `354b78e` | Model identity pages as risk-archetype explainers |
| 5 | current verification commit | Verification, sparse-state banner, typo cleanup, ship summary |

## What Users Can See Now

Phase 2 turns the Phase 1 schema into an actual product surface:

- Every betting board has a Palantir / Fangorn / Valinor lane switcher with URL state, so links like `/betting/home-run-edges?lane=fangorn` are shareable and refresh-safe.
- Missing lanes render honest empty states instead of blank boards. The UI explicitly says that the lane is not built yet and points users toward the methodology audit.
- Available-but-selective lanes now render a sparse-state banner when they cover the market but have no qualifying rows on the current slate.
- `/betting/consensus` surfaces rows where all three independent model ideologies converge or split.
- `/models/palantir`, `/models/fangorn`, and `/models/valinor` now explain the lane methodology, what each lane is not, current market coverage, and links into boards filtered to that lane.

## Coverage Matrix

This is the current truth table rendered by the model identity pages and the board switchers.

| Model lane | Home Runs | Strikeouts | Total Bases | Hits | Pitcher Outs | Futures |
|---|---|---|---|---|---|---|
| Palantir | production | production | production | production | production | production |
| Fangorn | production | rebadged_research | rebadged_research | missing | missing | missing |
| Valinor | rebadged_research | missing | missing | missing | missing | missing |

Home Runs remain the only full three-lane market. Strikeouts and Total Bases have partial multi-lane coverage. Hits, Pitcher Outs, and Futures remain Palantir-only until Phase 3 training work fills the missing methodology cells.

## Consensus Surface

Current consensus counts after the forced scheduler cycle rolled the betting artifacts forward to `2026-04-30`:

| Agreement label | Rows | Markets contributing |
|---|---:|---|
| aligned | 9 | Home Runs |
| disagreement | 21 | Home Runs |
| mixed | 207 | Home Runs |

Sample aligned row:

| Market | Player | Team | Opponent | Line | Palantir | Fangorn | Valinor | Market fair | Spread |
|---|---|---:|---:|---:|---:|---:|---:|---:|---:|
| HR | Jacob Wilson | ATH | KC | 0.5 | 3.2% | 4.7% | 3.5% | 9.1% | 1.5 pts |

Sample disagreement row:

| Market | Player | Team | Opponent | Line | Palantir | Fangorn | Valinor | Market fair | Spread |
|---|---|---:|---:|---:|---:|---:|---:|---:|---:|
| HR | Nathan Lukes | TOR | MIN | 0.5 | 3.9% | 17.9% | 13.4% | 9.1% | 14.1 pts |

The consensus surface is intentionally precision-first, not volume-first. It should be read as model convergence or model disagreement, not as a guarantee or a betting command.

## Verification

Route and lane smoke testing passed for all tested combinations:

| Market / route family | Default | Palantir | Fangorn | Valinor | Notes |
|---|---|---|---|---|---|
| Home Runs | pass | pass | pass | pass | All three lanes render |
| Strikeouts | pass | pass | pass | pass | Valinor shows missing-lane empty state |
| Strikeouts alias/date | pass | pass | pass | pass | `/betting/strikeout-value-board`, `/betting/strikeouts/<date>` covered |
| Total Bases | pass | pass | pass | pass | Valinor shows missing-lane empty state |
| Total Bases alias/date | pass | pass | pass | pass | Historical `2026-04-28` Fangorn triggers sparse-state banner |
| Hits | pass | pass | pass | pass | Fangorn and Valinor show missing-lane empty states |
| Hits date route | pass | pass | pass | pass | `/betting/hits/2026-04-01` covered |
| Pitcher Outs | pass | pass | pass | pass | Fangorn and Valinor show missing-lane empty states |
| Pitcher Outs date route | pass | pass | pass | pass | `/betting/pitcher-outs/2026-03-30` covered |
| Futures | pass | pass | pass | pass | Fangorn and Valinor show missing-lane empty states |
| Consensus | pass | n/a | n/a | n/a | `/betting/consensus` renders |
| Model identities | pass | pass | pass | n/a | `/models/palantir`, `/models/fangorn`, `/models/valinor` render |
| Fangorn date board | pass | n/a | n/a | n/a | `/models/fangorn/2026-04-28` still renders |

Lane-contract tests:

- Command: `python -m unittest tests.test_home_run_model_lane_contract tests.test_strikeout_model_lane_contract tests.test_total_base_model_lane_contract tests.test_hits_model_lane_contract tests.test_pitcher_outs_model_lane_contract tests.test_futures_model_lane_contract tests.test_futures_gap_engine`
- Result: `Ran 27 tests in 0.019s — OK`

Compile check:

- Command: `python -m compileall src\pitcher_card_engine\web\app.py src\pitcher_card_engine\domains\optimization\followup.py`
- Result: passed

Forced scheduler cycle:

- Command: `python scripts\ops\schedule.py --force --once`
- Cycle artifact: `outputs/ops/scheduler_cycle_20260430T010549.json`
- Result: scheduler completed, but health check returned non-zero because of carried operational state listed below.

| Task | Status | Metric | Notes |
|---|---:|---:|---|
| `fetch_chadwick_register` | success | 539420 | Register refresh succeeded |
| `fetch_savant_leaderboards` | success | 3654 | Savant leaderboard refresh succeeded |
| `fetch_futures_markets` | success | 330 | Futures markets refresh succeeded |
| `run_season_projection_pipeline` | success | 1213 | M1 season projection pipeline ran |
| `build_futures_fair_price_gaps` | success | 330 | Futures gap artifact written |
| `update_scorecard` | success | 2176 | Scorecard artifact written |
| `fetch_daily_odds_fanduel` | success | 230 | Daily odds pull succeeded |
| `fetch_strikeout_props_sportsgameodds` | success | 0 | Health flags metric below min; free source returned zero rows |
| `run_daily_sportsbook_ingestion` | success | 121 | Sportsbook ingestion succeeded |
| `build_clv_foundation_snapshot` | success | 261 | CLV foundation artifact written |
| `build_clv_history_summary` | success | 17 | CLV history artifact written |
| `build_clv_market_structure_report` | success | 4 | CLV structure artifact written |
| `run_home_run_daily_production` | success | 0 | Existing zero-metric behavior remains carried |
| `run_total_base_daily_production` | success | 126 | Current-day TB board produced |
| `run_strikeout_edge_refresh` | success | 18 | Current-day K board produced |
| `build_hits_board` | failed | 0 | Same-day upstream hits artifact gap; known carried state |
| `build_pitcher_outs_board` | failed | 0 | Same-day upstream pitcher-outs artifact gap; known carried state |
| `run_daily_mithrandir` | success | 0 | Health threshold flags `metric_below_min<1`; carried operational state |

SMTP remains wired but dormant. The forced health-failure path logged `SMTP not configured, skipping alert.` rather than crashing.

## Step 5 Polish

Sparse-state banner:

- Implemented as a reusable `sparse_lane_panel` macro in `src/pitcher_card_engine/web/templates/_ui_macros.html`.
- Backed by generalized board helpers in `src/pitcher_card_engine/web/app.py`.
- Rendered by all six betting board templates.
- Triggers when the selected lane has `production` or `rebadged_research` coverage but no non-null signal values on the current artifact.
- Confirmed on historical Total Bases Fangorn route: `/betting/total-bases/2026-04-28?lane=fangorn`.
- Current `2026-04-30` Total Bases Fangorn no longer triggers the banner because the refreshed board has qualifying Fangorn rows.

Typo cleanup:

- Fixed `MATERIAL_WHIFf_DELTA_PCT` to `MATERIAL_WHIFF_DELTA_PCT` in `src/pitcher_card_engine/domains/optimization/followup.py`.

## Carried Debts To Phase 3

- The Phase 1.5 methodology audit still identifies 11 missing or partially built model-lane cells that require real training work. Rough effort estimate remains approximately 24-37 engineering-weeks.
- HR Valinor needs hardening. It is currently `rebadged_research`, not proven production calibration.
- Non-HR markets are mostly Palantir-only. The UI now exposes that honestly; it does not solve the missing methodology work.
- The scheduler health check still carries operational red states: same-day Hits and Pitcher Outs upstream gaps, SportsgameOdds zero-row K props, and zero-metric behavior for `run_daily_mithrandir` / HR daily production.
- Any earlier mlflow/environment health notes from M3 Milestone 1 remain operational debt if reproduced in the deployment environment.
- SMTP alerting is still intentionally dormant until credentials are activated.

## What Phase 2 Explicitly Does Not Do

- No new ML training.
- No new market coverage.
- No calibration improvements.
- No promotion of research lanes to production.
- No removal of old source columns or legacy template references.
- No betting recommendation language. The UI frames gaps, convergence, and disagreement as analysis surfaces, not betting commands.
