---
title: Triage the 3,377 markets whose stored type contradicts their own classifier
type: task
status: completed
description: >-
  The largest unowned prop-market finding: 3,186 of 3,377 are DraftKings 2025, and a wrongly-typed
  market is settled and graded, so its hit-rate aggregates pool the wrong population.
base_uri: user:task/league/triage-the-contradicted-prop-market-types.md
created_at: '2026-09-24T23:00:57.915Z'
entity_id: 5710671f-a4d5-41b9-aec3-0d9bcdf3f8d1
finished_at: '2026-09-24T23:27:30.980Z'
observations:
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
public_read: true
started_at: '2026-09-24T23:08:39.626Z'
tags:
  - user:tag/league-xo-football.md
  - user:tag/software-task.md
updated_at: '2026-09-24T23:27:31.167Z'
visibility_analyzed_at: '2026-11-05T23:50:33.585Z'
---

## Problem

`prop-market-type-classifier-agreement` stands RED at 3,377 markets whose stored
`market_type` contradicts what the book's own classifier produces for that key and
line. Nothing owns it — the check names no task, and it is the largest unowned
prop-market finding by an order of magnitude.

A wrongly-typed market is worse than an untyped one. It is settled and graded, so
the hit-rate aggregates for that type silently pool the wrong population, and every
null-counting check reads those rows as healthy.

## Measured 2026-09-24

| book, season | findings |
|---|---|
| DRAFTKINGS 2025 | 3,186 |
| CAESARS 2024 | 158 |
| DRAFTKINGS 2026 | 32 |
| CAESARS 2023 | 1 |

94% is one book-season. A finding is ONE market and needs no conversion — that is
new as of the 2026-09-16 regrain, and briefs written before it quote this arm's
counts as (market, line) PAIRS.

## The first question is not "which side is wrong" but "is this a defect or a deploy"

They look identical from inside the check and have opposite remedies. The
discriminator is cheap and is recorded on the check: replay the repo mapper over
rows RE-OBSERVED in the last few hours, which isolates what the LIVE worker is
writing from what any backfill did. If the stored value equals what the previous
commit's mapper would say, it is deploy drift and the remedy is a deploy. If it
equals nothing any generation of the mapper would produce, it is a real defect.

The concentration in one book-season is itself evidence worth reading before
touching anything — a single mapper change or a single undeployed commit explains
that shape far more economically than 3,186 independent defects.

## Two properties that make a naive repair worse than nothing

**A repair to this column is TRANSIENT unless the deployed classifier agrees.**
`market_type` is the first entry in `MARKET_MERGE_COLUMNS`
(`libs-server/insert-prop-markets.mjs:118`), so every re-observation OVERWRITES it
with whatever the deployed worker computes. A DraftKings retype of 1,050 rows
verified complete had rows written back to the old type within the hour.

**A repair is not done when the type is fixed.** The grades computed beneath a
wrongly-typed row are wrong too, across all 22 derived columns, and they do not
recompute themselves.

Do not rewrite `market_type` to match the classifier as a first move. The
classifier is as likely to be the wrong side.

## Plan

- [x] Re-measure, and confirm the DraftKings 2025 concentration still holds
- [x] Run the deploy-drift discriminator over recently re-observed rows before any
  other analysis, since it can retire most of the population in one step
- [x] For whatever is a real defect, establish which side moved per market family,
  never per market
- [x] If the mapper is wrong, fix the mapper and deploy BEFORE touching stored rows
      — not needed, the mapper was already right and already deployed
- [x] Re-grade, having measured the cross-book collision with
  `find_cross_book_regrade_collisions` first. The derived columns were NOT cleared
  first and must not be — see below
- [x] Recompute the derived hit-rate columns for every affected market type

## What it turned out to be

Every finding was a mis-scoped operand, and the classifier was right on all 3,377.

The 3,186 DraftKings rows are period-scoped markets — 1H/1Q receiving, rushing and
passing yards, and 1Q passing completions — stored under their book's FULL-GAME
type. The three mapper commits of 2026-09-19 (`7124c7aed`, `b590a5e28`,
`866d88013`) corrected the mapping forward and walked back over no stored row. The
159 Caesars rows are in-play odd/even totals stored as `GAME_MATCHUP_TOTAL`, a
product that book already types correctly on 359 other markets.

**It was not deploy drift, and the discriminator said so cheaply.** Subcategory
18523 markets observed since 2026-09-20 carry the corrected period-scoped type;
the same subcategory last observed 2026-09-17 still carried the full-game one. The
deployed worker holds the fix, so the retype is durable.

**The transience premise did not apply, and a future reader should re-test it
rather than inherit it.** `market_type` does ride `MARKET_MERGE_COLUMNS`, but
DraftKings 2025 has had no row in `prop_market_observations` since 2026-02-08 — 94
percent of this population is not re-observed at all — and where re-observation
does happen the deployed worker now writes the same value the repair writes.

**Do not clear `selection_result` first.** The result writer overwrites; there is
no `selection_result is null` predicate outside `--missing_only`, pinned by
`test/prop-market-settlement.result-writer-overwrite.spec.mjs`. Clearing would only
open a window where the rows are neither right nor wrong.

## Verification

The check must go green because types were corrected, not because the population
narrowed. Assert the finding count per book-season and the graded unit count
together; a market whose selections were lost is excluded from this check by
design and must not be swept into the repair.

Met. `prop-market-type-classifier-agreement` reads 0 findings with 21 gradeable
units and 28 un-gradeable, both unchanged from the red reading, and every repaired
book-season holds its denominator: Caesars 2023 9,643, Caesars 2024 203,141,
DraftKings 2025 453,080, DraftKings 2026 82,932 rising to 86,684 as that live
season grew. `prop-market-mis-scoped-settlement-operand` also reads 0, though it
was not baselined before the repair so no causation is claimed.

The re-grade is the non-degenerate result, not the green check. It moved 11,330 of
12,784 graded selections and flipped 918 WON/LOST verdicts. The 2026 slice fell
from 93.20 percent WON to 26.21 percent, with `metric_result_value` going 25 to 10
and 15 to 3 — a full-game completion count replaced by a first-quarter one. Two
residue rows are `no_selection_pid_on_player_market`, which belongs to
[[█████████████████████████-████-█████████-████-██-████-██████████]]; zero were
unexplained.

Hit rates went from zero rows to populated for all three `FIRST_HALF` types:
receiving yards 53,284, rushing yards 22,436, passing yards 19,132, plus 1Q passing
yards 49,320 and 1Q passing completions 14,768. The rows still standing under the
OLD full-game types were deliberately left: the natural key carries no market id,
so a row at (player, full-game type, line, side, game) is a correct fact about that
player's full-game history regardless of which market caused it to be computed.

## Two traps this repair walked into and backed out of

**Scoping the re-grade on the CORRECTED market type is the name-scoping trap.** It
pulled in 199 markets in 2025 and 92 in 2026 that already held the right type and
were graded correctly — inflating the verification numbers enough to hide whether
the repair had done anything. The population must come from the check's own output,
captured BEFORE the retype lands, because afterwards the check reports nothing.

**`--market_types` on `calculate-historical-hit-rates.mjs` is a yargs ARRAY
option.** A comma-separated value arrives as one unsplit string, matches no
`market_type`, and the run reports `Processing 0 prop selections` and exits 0. It
reads exactly like a correctly-scoped run over an empty population. Repeat the flag
per type. The control that exposed it was a single type with no comma in it, which
is why a one-name spot check cannot catch this.

## Tooling this left behind

`scripts/repair-contradicted-market-types.mjs` (league `9dfb75223`) takes its
population from the check, folds it to flip families, prints the scoped re-grade
and its cross-book collision reading, and applies the retype under `--apply`. It
exists because a mapper fix leaves stored rows contradicting the deployed
classifier every time, and this was the third such repair done by hand.

## Origin

Surfaced 2026-09-24 by `repl-69f47b3c` running every prop-market check in the
registry and mapping each RED arm to its owning task. This one had none.
