Polish MLS simulator: configNumber zero, logger warning, Map lookup

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
This commit is contained in:
Claude 2026-05-14 19:18:13 +00:00
parent 43c9128e59
commit 8a0103e4c4
No known key found for this signature in database

View file

@ -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<string, unknown> | 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;