From 8a0103e4c4727ea23ba1956f4f1f0a4c972f34bf Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 14 May 2026 19:18:13 +0000 Subject: [PATCH] Polish MLS simulator: configNumber zero, logger warning, Map lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three small fixes from secondary code review: 1. configNumber: allow value >= 0 (not just > 0) so admins can legitimately set baseDrawRate or drawDecay to 0 without the value being silently discarded and replaced with the default. 2. Add logger.warn when a participant is excluded from simulation due to a missing Elo rating, matching the NLL bracket-aware pattern. Gives admins a visible signal instead of a silent exclusion. 3. getBySeeds: build a Map once instead of calling Array.find() per seed. Eliminates 4M linear scans across 50k iterations for a 9- element array — trivially fast either way, but Map is the right tool. https://claude.ai/code/session_015wkBJ3SYGcMGjsddKKGkwa --- app/services/simulations/mls-simulator.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/services/simulations/mls-simulator.ts b/app/services/simulations/mls-simulator.ts index 3904d38..b159855 100644 --- a/app/services/simulations/mls-simulator.ts +++ b/app/services/simulations/mls-simulator.ts @@ -46,6 +46,7 @@ import { getParticipantSimulatorInputs, getSportsSeasonSimulatorConfig } from "~ import { simulateEloSoccerMatch, simulateSimpleSoccerGoals } from "./soccer-helpers"; import type { EloSoccerMatchOptions } from "./soccer-helpers"; import { normalizeSimulationResultColumns } from "./simulation-probabilities"; +import { logger } from "~/lib/logger"; // ─── Default constants (overridable via season simulator config) ─────────────── @@ -62,7 +63,7 @@ const MLS_PLAYOFF_TEAMS_PER_CONF = 9; function configNumber(config: Record | undefined, key: string, fallback: number): number { const value = config?.[key]; - return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback; + return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback; } // ─── Public types ───────────────────────────────────────────────────────────── @@ -205,8 +206,9 @@ function getBySeeds( seeds: MlsConferenceSeed[], ...seedNums: number[] ): MlsConferenceSeed[] { + const byNum = new Map(seeds.map((s) => [s.seed, s])); return seedNums.map((n) => { - const entry = seeds.find((s) => s.seed === n); + const entry = byNum.get(n); if (!entry) throw new Error(`MLS playoff seed ${n} not found in conference bracket.`); return entry; }); @@ -397,7 +399,13 @@ export class MLSSimulator implements Simulator { const teamsWithElo = participants.flatMap((p): MlsTeamEntry[] => { const elo = eloMap.get(p.id); const conf = conferenceMap.get(p.id); - if (elo === undefined || conf === undefined) return []; + if (elo === undefined || conf === undefined) { + logger.warn( + `MLSSimulator: no Elo rating for participant "${p.name}" (${p.id}). ` + + `They will be excluded from simulation. Enter sourceElo or projectedTablePoints via Admin → Elo Ratings.` + ); + return []; + } const standing = standingsMap.get(p.id); const wins = standing?.wins ?? 0; const draws = standing?.ties ?? 0;