brackt/app/services/simulations/input-policy.ts

402 lines
16 KiB
TypeScript
Raw Normal View History

Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
import { convertFuturesToElo } from "~/services/probability-engine";
import { simulatorInputLabel, type SimulatorManifestProfile } from "./manifest";
Fix NCAAW futures odds simulation and admin import UX (#420) - Revert ncaaw-simulator to Barthag win probability formula; set realistic rating bounds (ratingMin: 0.70, ratingMax: 0.97) so derived ratings stay in the range where the formula behaves well - Add batchSaveFuturesOddsForSimulator which clears all ratings (manual and generated) before upserting sourceOdds, so futures odds always drive the simulation rather than being silently overridden by existing Barthag ratings from Simulator Setup - Add clearSourceOddsForParticipants to zero out both tables for participants excluded from a bulk import - Add "Clear existing odds" checkbox to the bulk import card; applies client-side on match and server-side on submit - Fix missing sportsSeasonId filter in batchSaveFuturesOddsForSimulator pre-clear UPDATE (could have wiped ratings across other seasons) - Fix race condition: run batchSaveSourceOdds then batchSaveFuturesOddsForSimulator sequentially so the simulator inputs table always ends in the correct cleared state - Fix Math.round in convertFuturesToElo collapsing Barthag-scale ratings to 0 or 1; Elo callers already round after clamping - Handle all-identical-odds edge case in convertFuturesToElo (assign midpoint instead of throwing) - Add missingRatingStrategy: worstKnownMinus to ncaaw_bracket manifest so fallbackRatingDelta is live config, not dead - Log warning in resolveRatings when only 1 participant has odds - Reset clearExisting checkbox after applyMatches Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:06:16 -07:00
import { logger } from "~/lib/logger";
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
export type MissingEloStrategy = "block" | "fallbackElo" | "averageKnown" | "worstKnownMinus";
export type MissingRatingStrategy = "block" | "fallbackRating" | "averageKnown" | "worstKnownMinus";
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
/**
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
* "Base" strength sources the ways to produce a participant's underlying Elo
* before any futures-odds blend. These are *substitutes* (a season rarely has
* more than one), so they are tried in order and the first available one wins.
* Futures odds are not in this list: they blend on top of the base Elo via
* `oddsWeight`, rather than being one more interchangeable base.
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
*/
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
export type BaseEloKey = "sourceElo" | "projectedWins" | "projectedTablePoints";
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
export const BASE_ELO_KEYS: readonly BaseEloKey[] = [
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
"sourceElo",
"projectedWins",
"projectedTablePoints",
];
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
/** Default order for choosing the base Elo: raw Elo, then projections. */
export const DEFAULT_BASE_ELO_PRIORITY: BaseEloKey[] = [
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
"sourceElo",
"projectedWins",
"projectedTablePoints",
];
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
export interface SimulatorInputPolicy {
missingEloStrategy: MissingEloStrategy;
missingRatingStrategy: MissingRatingStrategy;
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
/** Order for choosing the base strength Elo from its substitutable sources. */
baseEloPriority: BaseEloKey[];
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
/**
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
* Weight (01) of the futures-odds-derived Elo when blending with the base Elo
* into the single Elo that feeds every simulator. `0` = base only (Elo /
* projections), `1` = futures fully override the base, in between = blend.
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
*/
oddsWeight: number;
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
fallbackElo: number;
fallbackEloDelta: number;
eloMin: number;
eloMax: number;
fallbackRating: number;
fallbackRatingDelta: number;
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
ratingMin: number;
ratingMax: number;
}
export interface ParticipantInputForPolicy {
participantId: string;
sourceOdds: number | null;
sourceElo: number | null;
rating: number | null;
projectedWins: number | null;
projectedTablePoints: number | null;
}
export interface ResolvedSourceElo {
participantId: string;
sourceElo: number;
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
method: "direct" | "projectedWins" | "projectedTablePoints" | "sourceOdds" | "blend" | MissingEloStrategy;
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
}
export interface ResolvedRating {
participantId: string;
rating: number;
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
method: "direct" | "sourceOdds" | "blend" | MissingRatingStrategy;
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
}
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
const DEFAULT_ODDS_WEIGHT = 0.3;
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
const DEFAULT_POLICY: SimulatorInputPolicy = {
missingEloStrategy: "block",
missingRatingStrategy: "block",
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
baseEloPriority: DEFAULT_BASE_ELO_PRIORITY,
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
oddsWeight: DEFAULT_ODDS_WEIGHT,
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
fallbackElo: 1400,
fallbackEloDelta: 25,
eloMin: 1100,
eloMax: 1900,
fallbackRating: 0,
fallbackRatingDelta: 5,
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
ratingMin: -20,
ratingMax: 40,
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function optionalNumber(value: unknown, fallback: number): number {
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
}
function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
function toWinRate(projection: number, maxProjection: number): number {
if (maxProjection <= 0) return 0.5;
return clamp(projection / maxProjection, 0.01, 0.99);
}
function projectionToElo(
projection: number,
maxProjection: number,
parityFactor: number,
policy: SimulatorInputPolicy
): number {
const rate = toWinRate(projection, maxProjection);
const elo = 1500 + parityFactor * Math.log10(rate / (1 - rate));
return clamp(Math.round(elo), policy.eloMin, policy.eloMax);
}
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
function parseBaseEloPriority(value: unknown, fallback: BaseEloKey[]): BaseEloKey[] {
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
if (!Array.isArray(value)) return fallback;
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
const seen = new Set<BaseEloKey>();
const parsed: BaseEloKey[] = [];
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
for (const entry of value) {
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
if (typeof entry === "string" && (BASE_ELO_KEYS as readonly string[]).includes(entry) && !seen.has(entry as BaseEloKey)) {
seen.add(entry as BaseEloKey);
parsed.push(entry as BaseEloKey);
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
}
}
return parsed.length > 0 ? parsed : fallback;
}
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
export function getSimulatorInputPolicy(config: Record<string, unknown>): SimulatorInputPolicy {
const rawPolicy = isRecord(config.inputPolicy) ? config.inputPolicy : {};
const eloStrategy = rawPolicy.missingEloStrategy;
const ratingStrategy = rawPolicy.missingRatingStrategy;
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
return {
missingEloStrategy:
eloStrategy === "fallbackElo" || eloStrategy === "averageKnown" || eloStrategy === "worstKnownMinus"
? eloStrategy
: "block",
missingRatingStrategy:
ratingStrategy === "fallbackRating" || ratingStrategy === "averageKnown" || ratingStrategy === "worstKnownMinus"
? ratingStrategy
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
: "block",
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
baseEloPriority: parseBaseEloPriority(rawPolicy.baseEloPriority, DEFAULT_POLICY.baseEloPriority),
// The futures blend weight lives only under inputPolicy (single home).
oddsWeight: clamp(optionalNumber(rawPolicy.oddsWeight, DEFAULT_POLICY.oddsWeight), 0, 1),
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
fallbackElo: optionalNumber(rawPolicy.fallbackElo, DEFAULT_POLICY.fallbackElo),
fallbackEloDelta: optionalNumber(rawPolicy.fallbackEloDelta, DEFAULT_POLICY.fallbackEloDelta),
eloMin: optionalNumber(rawPolicy.eloMin, DEFAULT_POLICY.eloMin),
eloMax: optionalNumber(rawPolicy.eloMax, DEFAULT_POLICY.eloMax),
fallbackRating: optionalNumber(rawPolicy.fallbackRating, DEFAULT_POLICY.fallbackRating),
fallbackRatingDelta: optionalNumber(rawPolicy.fallbackRatingDelta, DEFAULT_POLICY.fallbackRatingDelta),
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
ratingMin: optionalNumber(rawPolicy.ratingMin, DEFAULT_POLICY.ratingMin),
ratingMax: optionalNumber(rawPolicy.ratingMax, DEFAULT_POLICY.ratingMax),
};
}
export function sourceEloRequirementLabel(profile: SimulatorManifestProfile): string {
const alternatives = profile.derivableInputs?.sourceElo ?? [];
// Omit "configured fallback" from the label: if a participant appears in
// missingInputs it means no fallback resolved it, so mentioning fallback
// as an option would be misleading.
return ["Elo rating", ...alternatives.map(simulatorInputLabel)].join(" / ");
}
export function ratingRequirementLabel(profile: SimulatorManifestProfile): string {
const alternatives = profile.derivableInputs?.rating ?? [];
return ["rating", ...alternatives.map(simulatorInputLabel)].join(" / ");
}
/**
* Combine a participant's base strength value and its odds-derived value into the
* single value that feeds the simulator, by `oddsWeight`: 0 = base only,
* 1 = odds only, in between = weighted blend. Odds-derived and blended results
* pass through `clampDerived` (they are mapped onto the configured band by
* design); a base value is returned untouched (a directly entered Elo/rating is
* trusted as-is, a projection-derived Elo was already clamped upstream). Returns
* undefined when neither source is present (the caller falls back to its
* missing-input strategy). Shared by Elo and rating resolution.
*/
function blendBaseAndOdds<M extends string>(
base: { value: number; method: M } | undefined,
odds: number | undefined,
oddsWeight: number,
clampDerived: (value: number) => number
): { value: number; method: M | "sourceOdds" | "blend" } | undefined {
if (base !== undefined && odds !== undefined) {
if (oddsWeight <= 0) return { value: base.value, method: base.method };
if (oddsWeight >= 1) return { value: clampDerived(odds), method: "sourceOdds" };
return { value: clampDerived((1 - oddsWeight) * base.value + oddsWeight * odds), method: "blend" };
}
if (base !== undefined) return { value: base.value, method: base.method };
if (odds !== undefined) return { value: clampDerived(odds), method: "sourceOdds" };
return undefined;
}
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
export function resolveSourceElos(
inputs: ParticipantInputForPolicy[],
profile: Pick<SimulatorManifestProfile, "derivableInputs">,
config: Record<string, unknown>
): Map<string, ResolvedSourceElo> {
const policy = getSimulatorInputPolicy(config);
const resolved = new Map<string, ResolvedSourceElo>();
const alternatives = new Set(profile.derivableInputs?.sourceElo ?? []);
const parityFactor = optionalNumber(config.parityFactor, 400);
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
// 1. Base Elo: the first available *substitutable* source per baseEloPriority.
// `sourceElo` (raw) is always permitted; projections must be declared in
// the profile's derivableInputs.sourceElo.
const baseElo = new Map<string, { elo: number; method: ResolvedSourceElo["method"] }>();
for (const source of policy.baseEloPriority) {
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
if (source !== "sourceElo" && !alternatives.has(source)) continue;
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
if (source === "sourceElo") {
for (const input of inputs) {
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
if (baseElo.has(input.participantId)) continue;
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
if (input.sourceElo !== null && input.sourceElo !== undefined) {
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
baseElo.set(input.participantId, { elo: input.sourceElo, method: "direct" });
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
}
}
} else if (source === "projectedWins") {
const seasonGames = optionalNumber(config.seasonGames, Math.max(...inputs.map((input) => input.projectedWins ?? 0), 1));
for (const input of inputs) {
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
if (baseElo.has(input.participantId) || input.projectedWins === null || input.projectedWins === undefined) continue;
baseElo.set(input.participantId, {
elo: projectionToElo(input.projectedWins, seasonGames, parityFactor, policy),
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
method: "projectedWins",
});
}
} else if (source === "projectedTablePoints") {
const seasonGames = optionalNumber(config.seasonGames, 38);
const maxPoints = optionalNumber(config.maxTablePoints, seasonGames * 3);
for (const input of inputs) {
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
if (baseElo.has(input.participantId) || input.projectedTablePoints === null || input.projectedTablePoints === undefined) continue;
baseElo.set(input.participantId, {
elo: projectionToElo(input.projectedTablePoints, maxPoints, parityFactor, policy),
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
method: "projectedTablePoints",
Fix futures odds being ignored when stale Elo exists When an admin entered futures (preseason) odds for a season that already had Elo ratings stored, the simulator kept using the old Elo and silently ignored the new odds. This affected any Elo-based simulator (e.g. NHL). Root cause: resolveSourceElos() ranks a direct sourceElo above the sourceOdds -> convertFuturesToElo branch, but batchSaveFuturesOddsForSimulator() only cleared the bracket-seeding `rating`/`ratingMethod` — never the stale `sourceElo`/`sourceEloMethod`. A manually entered Elo (method "direct") is not treated as generated, so it survived and short-circuited the resolver. Fix: - batchSaveFuturesOddsForSimulator now also nulls sourceElo and strips sourceEloMethod (both the pre-update and upsert-conflict paths), so the existing futures -> Elo conversion drives the run. - resolveSourceElos' sourceOdds branch now guards for >= 2 participants (mirroring resolveRatings), so a lone-odds season falls through to the configured missing-Elo strategy instead of getting a flat ~1500. - batchSaveSourceOdds clears the legacy EV sourceElo and marks source as futures_odds so the elo-ratings page won't resurrect a stale rating. Adds unit coverage for odds-derived Elo, the single-participant guard, the post-clear regression, generated-vs-direct sourceElo suppression, and the new clearing behavior in batchSaveFuturesOddsForSimulator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNfUEd9RzD3zm84oLHBHUH
2026-06-25 17:43:31 +00:00
});
}
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
}
}
// 2. Odds-derived Elo (field-relative): only when futures odds are a permitted
// source and at least two participants have odds (a spread is required).
const oddsElo = new Map<string, number>();
if (alternatives.has("sourceOdds")) {
const oddsInputs = inputs
.filter((input) => input.sourceOdds !== null && input.sourceOdds !== undefined)
.map((input) => ({ participantId: input.participantId, odds: input.sourceOdds as number }));
if (oddsInputs.length === 1) {
logger.warn(
`resolveSourceElos: only 1 participant has sourceOdds (${oddsInputs[0].participantId}). ` +
`convertFuturesToElo requires at least 2 to derive a meaningful Elo spread — ignoring the lone odds value.`
);
}
if (oddsInputs.length >= 2) {
for (const [participantId, elo] of convertFuturesToElo(oddsInputs)) {
oddsElo.set(participantId, elo);
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
}
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
}
}
// 3. Blend base + odds into the single Elo that feeds the simulator.
// Directly entered Elos (some sports use ratings above the default ceiling,
// e.g. snooker ~2450) pass through unclamped; only derived values are
// clamped onto [eloMin, eloMax].
const clampDerived = (elo: number): number => clamp(Math.round(elo), policy.eloMin, policy.eloMax);
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
for (const input of inputs) {
const base = baseElo.get(input.participantId);
const blended = blendBaseAndOdds(
base ? { value: base.elo, method: base.method } : undefined,
oddsElo.get(input.participantId),
policy.oddsWeight,
clampDerived
);
if (!blended) continue;
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
resolved.set(input.participantId, {
participantId: input.participantId,
sourceElo: blended.value,
method: blended.method,
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
});
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
}
const knownElos = [...resolved.values()].map((value) => value.sourceElo);
const averageKnown = knownElos.length > 0
? knownElos.reduce((sum, elo) => sum + elo, 0) / knownElos.length
: policy.fallbackElo;
const worstKnown = knownElos.length > 0 ? Math.min(...knownElos) : policy.fallbackElo;
for (const input of inputs) {
if (resolved.has(input.participantId) || policy.missingEloStrategy === "block") continue;
let sourceElo: number;
if (policy.missingEloStrategy === "averageKnown") {
sourceElo = averageKnown;
} else if (policy.missingEloStrategy === "worstKnownMinus") {
sourceElo = worstKnown - policy.fallbackEloDelta;
} else {
sourceElo = policy.fallbackElo;
}
resolved.set(input.participantId, {
participantId: input.participantId,
sourceElo: clamp(Math.round(sourceElo), policy.eloMin, policy.eloMax),
method: policy.missingEloStrategy,
});
}
return resolved;
}
export function resolveRatings(
inputs: ParticipantInputForPolicy[],
profile: Pick<SimulatorManifestProfile, "derivableInputs">,
config: Record<string, unknown>
): Map<string, ResolvedRating> {
const policy = getSimulatorInputPolicy(config);
const resolved = new Map<string, ResolvedRating>();
const alternatives = new Set(profile.derivableInputs?.rating ?? []);
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
// Ratings follow the same single-value blend as Elo: the base is the direct
// `rating`; futures odds (when permitted, scaled onto the rating range) blend
// on top by oddsWeight. `0` = rating only, `1` = futures override.
const baseRating = new Map<string, number>();
for (const input of inputs) {
if (input.rating !== null && input.rating !== undefined) {
baseRating.set(input.participantId, input.rating);
}
}
const oddsRating = new Map<string, number>();
if (alternatives.has("sourceOdds")) {
const oddsInputs = inputs
.filter((input) => input.sourceOdds !== null && input.sourceOdds !== undefined)
.map((input) => ({ participantId: input.participantId, odds: input.sourceOdds as number }));
if (oddsInputs.length === 1) {
logger.warn(
`resolveRatings: only 1 participant has sourceOdds (${oddsInputs[0].participantId}). ` +
`convertFuturesToElo requires at least 2 to derive a meaningful rating — ignoring the lone odds value.`
);
}
if (oddsInputs.length >= 2) {
const converted = convertFuturesToElo(oddsInputs, "american", {
exponent: 0.33,
eloMin: policy.ratingMin,
eloMax: policy.ratingMax,
});
for (const [participantId, rating] of converted) {
oddsRating.set(participantId, rating);
Unify futures odds with the simulation system Make the futures-vs-Elo relationship an explicit, configurable rule and fix futures odds failing to override stored Elo. Root causes addressed: - resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo -> projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo on futures entry but not projections, which still outranked odds. - The override relied on a destructive null-on-save hack that also wiped manually entered Elo. - Blended simulators buried their Elo/odds weight in module constants. - Futures odds were not surfaced on the /admin/simulators inventory. Changes: - Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight, parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now resolve each participant by the configured priority instead of a fixed order. Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football, college_hockey) default to a futures-override priority. - Drop the destructive nulling in batchSaveFuturesOddsForSimulator and batchSaveSourceOdds; override is now governed by policy, preserving stored Elo. - Thread an optional SimulationContext (oddsWeight) through the Simulator interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight from the season policy; defaults preserve prior calibration when no context is passed. - Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the Simulator Setup input-policy card, persisted via save-input-policy. - Surface futures on /admin/simulators: a source badge and a Futures Odds quick link; extend listSportsSeasonSimulatorSummaries with odds source info. - Tests: configurable priority override (Elo/projections/rating), oddsWeight parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football context-blend behavioral test, and an updated non-destructive save test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-25 22:11:32 +00:00
}
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
}
}
// As with Elo: a directly entered rating passes through untouched; only
// derived values are clamped onto the rating band.
const clampDerived = (rating: number): number => clamp(rating, policy.ratingMin, policy.ratingMax);
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
for (const input of inputs) {
const base = baseRating.get(input.participantId);
const blended = blendBaseAndOdds(
base !== undefined ? { value: base, method: "direct" as const } : undefined,
oddsRating.get(input.participantId),
policy.oddsWeight,
clampDerived
);
if (!blended) continue;
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
resolved.set(input.participantId, {
participantId: input.participantId,
rating: blended.value,
method: blended.method,
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
});
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
}
const knownRatings = [...resolved.values()].map((value) => value.rating);
const averageKnown = knownRatings.length > 0
? knownRatings.reduce((sum, rating) => sum + rating, 0) / knownRatings.length
: policy.fallbackRating;
const worstKnown = knownRatings.length > 0 ? Math.min(...knownRatings) : policy.fallbackRating;
for (const input of inputs) {
if (resolved.has(input.participantId) || policy.missingRatingStrategy === "block") continue;
let rating: number | null;
if (policy.missingRatingStrategy === "averageKnown") {
rating = averageKnown;
} else if (policy.missingRatingStrategy === "worstKnownMinus") {
rating = knownRatings.length > 0 ? worstKnown - policy.fallbackRatingDelta : null;
} else {
rating = policy.fallbackRating;
}
if (rating === null) continue;
resolved.set(input.participantId, {
participantId: input.participantId,
rating: clamp(rating, policy.ratingMin, policy.ratingMax),
method: policy.missingRatingStrategy,
});
}
Formalize simulator system with manifest, input-policy, runner, and admin UI (#409) Introduces three new schema tables (simulator_profiles, sports_season_simulator_configs, season_participant_simulator_inputs), a central model layer (app/models/simulator.ts), and a single runner entry point so every simulator run follows the same prepare → simulate → persist → snapshot → recalculate flow. Key additions: - manifest.ts: per-simulator display names, default configs, required/ optional inputs, derivable-input declarations, and setup sections - input-policy.ts: resolves sourceElo from projectedWins, projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds; supports block / fallbackElo / averageKnown / worstKnownMinus strategies - runner.ts: single entry point for admin simulation runs; materialises derived inputs, normalises result columns, zeroes omitted participants, snapshots EVs, and recalculates linked fantasy standings - /admin/simulators: inventory page with per-season readiness and bulk run - /admin/sports-seasons/:id/simulator: per-season setup page with readiness summary, input-policy editor, raw JSON config override, and CSV bulk input - NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs, falling back to the hardcoded name-keyed maps while DB data is being populated - Clone flow copies simulator config by default; volatile inputs (odds, Elo) only copied when explicitly requested Code-review fixes included in this commit: - source field in compatibility bridge checked with !== null instead of !== undefined - sourceEloRequirementLabel no longer appends "configured fallback" when the participant is already excluded from all resolved sources - Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel - save-config preserves existing inputPolicy when the submitted JSON omits it - Input table truncation label added (Showing 20 of N) - CSV description notes values must not contain commas - N+1 comment added to listSportsSeasonSimulatorSummaries - assertRegistrySchemaDriftFree called in manifest tests - Runner test suite added covering happy path, already-running guard, readiness failure, empty results, and error recovery with status reset Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:09:53 -07:00
return resolved;
}