Fix intermittent test timeouts in simulator-inputs and llws-simulator
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 2m27s
🚀 Deploy / ʦ TypeScript (pull_request) Successful in 1m16s
🚀 Deploy / 🔍 Lint (pull_request) Successful in 48s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 2m27s
🚀 Deploy / ʦ TypeScript (pull_request) Successful in 1m16s
🚀 Deploy / 🔍 Lint (pull_request) Successful in 48s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
simulator-inputs: move dynamic import to top level so heavy module initialization happens during file load (no timeout) instead of inside the test body. llws-simulator: make iteration count injectable via constructor (default 50_000 for production, tests pass 1_000). Cuts test suite from ~4s per test to <150ms for all 21 tests combined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
880b7faa6b
commit
3ebdd16b60
3 changed files with 30 additions and 29 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import { getParticipantSimulatorInputs } from "../simulator";
|
||||
|
||||
const mockDb = vi.hoisted(() => ({
|
||||
query: {
|
||||
|
|
@ -18,8 +19,6 @@ describe("simulator input model", () => {
|
|||
});
|
||||
|
||||
it("does not treat previously generated ratings as direct rating inputs", async () => {
|
||||
const { getParticipantSimulatorInputs } = await import("../simulator");
|
||||
|
||||
mockDb.query.seasonParticipants.findMany.mockResolvedValue([
|
||||
{ id: "direct-rating" },
|
||||
{ id: "generated-rating" },
|
||||
|
|
|
|||
|
|
@ -70,13 +70,13 @@ describe("LLWSSimulator", () => {
|
|||
describe("output structure", () => {
|
||||
it("returns one result per participant (20 total)", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
expect(results).toHaveLength(20);
|
||||
});
|
||||
|
||||
it("every result has source 'llws_monte_carlo'", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
for (const r of results) {
|
||||
expect(r.source).toBe("llws_monte_carlo");
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ describe("LLWSSimulator", () => {
|
|||
|
||||
it("all probability values are between 0 and 1", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
for (const r of results) {
|
||||
const p = r.probabilities;
|
||||
for (const v of Object.values(p)) {
|
||||
|
|
@ -100,35 +100,35 @@ describe("LLWSSimulator", () => {
|
|||
describe("probability conservation (one winner per sim)", () => {
|
||||
it("probFirst sums to ~1.0 across all participants", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
const total = results.reduce((s, r) => s + r.probabilities.probFirst, 0);
|
||||
expect(total).toBeCloseTo(1.0, 1);
|
||||
});
|
||||
|
||||
it("probSecond sums to ~1.0 across all participants", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
const total = results.reduce((s, r) => s + r.probabilities.probSecond, 0);
|
||||
expect(total).toBeCloseTo(1.0, 1);
|
||||
});
|
||||
|
||||
it("probThird sums to ~1.0 across all participants", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
const total = results.reduce((s, r) => s + r.probabilities.probThird, 0);
|
||||
expect(total).toBeCloseTo(1.0, 1);
|
||||
});
|
||||
|
||||
it("probFourth sums to ~1.0 across all participants", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
const total = results.reduce((s, r) => s + r.probabilities.probFourth, 0);
|
||||
expect(total).toBeCloseTo(1.0, 1);
|
||||
});
|
||||
|
||||
it("sum of probFifth across participants equals ~1.0 (4 bracket losers, split evenly)", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
// 4 bracket losers per sim, each assigned bracketLoser/(4*N) → sum = 1.0
|
||||
const total = results.reduce((s, r) => s + r.probabilities.probFifth, 0);
|
||||
expect(total).toBeCloseTo(1.0, 1);
|
||||
|
|
@ -136,7 +136,7 @@ describe("LLWSSimulator", () => {
|
|||
|
||||
it("probFifth through probEighth are equal for every participant (even bracket-loser split)", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
for (const r of results) {
|
||||
const p = r.probabilities;
|
||||
expect(p.probFifth).toBeCloseTo(p.probSixth, 10);
|
||||
|
|
@ -151,7 +151,7 @@ describe("LLWSSimulator", () => {
|
|||
describe("odds-driven win probability", () => {
|
||||
it("strong favourite (us-1) has higher probFirst than a weak team", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS, { includeOdds: true }));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
const byId = new Map(results.map((r) => [r.participantId, r]));
|
||||
const us1prob = byId.get("us-1")?.probabilities.probFirst ?? 0;
|
||||
const us10prob = byId.get("us-10")?.probabilities.probFirst ?? 0;
|
||||
|
|
@ -160,7 +160,7 @@ describe("LLWSSimulator", () => {
|
|||
|
||||
it("works when no odds are entered (all 50/50 fallback)", async () => {
|
||||
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS)); // no odds
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
const total = results.reduce((s, r) => s + r.probabilities.probFirst, 0);
|
||||
expect(total).toBeCloseTo(1.0, 1);
|
||||
});
|
||||
|
|
@ -169,7 +169,7 @@ describe("LLWSSimulator", () => {
|
|||
// Equal positive odds (+5000 for every team) → vig-removed prob ≈ 1/20 each.
|
||||
const eqOddsRows = ALL_IDS.map((id) => ({ participantId: id, sourceOdds: 5000 }));
|
||||
setupMockDb(defaultParticipants(), eqOddsRows);
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
for (const r of results) {
|
||||
// With equal odds and random pools, each team should win ~5% of the time.
|
||||
// Allow a generous band given Monte Carlo variance.
|
||||
|
|
@ -184,7 +184,7 @@ describe("LLWSSimulator", () => {
|
|||
describe("pool assignment modes", () => {
|
||||
it("fixed pools (US:A / US:B / Intl:A / Intl:B) produce valid results", async () => {
|
||||
setupMockDb(defaultParticipants("fixed"), makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
expect(results).toHaveLength(20);
|
||||
const total = results.reduce((s, r) => s + r.probabilities.probFirst, 0);
|
||||
expect(total).toBeCloseTo(1.0, 1);
|
||||
|
|
@ -197,7 +197,7 @@ describe("LLWSSimulator", () => {
|
|||
...INTL_IDS.map((id) => ({ id, name: `Team ${id}`, externalId: "Intl" })),
|
||||
];
|
||||
setupMockDb(participants, makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
expect(results).toHaveLength(20);
|
||||
const total = results.reduce((s, r) => s + r.probabilities.probFirst, 0);
|
||||
expect(total).toBeCloseTo(1.0, 1);
|
||||
|
|
@ -215,7 +215,7 @@ describe("LLWSSimulator", () => {
|
|||
externalId: i < 10 ? "US" : "Intl",
|
||||
}));
|
||||
setupMockDb(participants, makeEvRows(nineteen));
|
||||
await expect(new LLWSSimulator().simulate("season-1")).rejects.toThrow(/exactly 20/);
|
||||
await expect(new LLWSSimulator(1_000).simulate("season-1")).rejects.toThrow(/exactly 20/);
|
||||
});
|
||||
|
||||
it("infers US side from name prefix when externalId is null", async () => {
|
||||
|
|
@ -224,7 +224,7 @@ describe("LLWSSimulator", () => {
|
|||
...INTL_IDS.map((id) => ({ id, name: `Japan ${id}`, externalId: null })),
|
||||
];
|
||||
setupMockDb(participants, makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
expect(results).toHaveLength(20);
|
||||
const total = results.reduce((s, r) => s + r.probabilities.probFirst, 0);
|
||||
expect(total).toBeCloseTo(1.0, 1);
|
||||
|
|
@ -236,7 +236,7 @@ describe("LLWSSimulator", () => {
|
|||
...INTL_IDS.map((id) => ({ id, name: `Team ${id}`, externalId: null })),
|
||||
];
|
||||
setupMockDb(participants, makeEvRows(ALL_IDS));
|
||||
const results = await new LLWSSimulator().simulate("season-1");
|
||||
const results = await new LLWSSimulator(1_000).simulate("season-1");
|
||||
expect(results).toHaveLength(20);
|
||||
const total = results.reduce((s, r) => s + r.probabilities.probFirst, 0);
|
||||
expect(total).toBeCloseTo(1.0, 1);
|
||||
|
|
@ -249,7 +249,7 @@ describe("LLWSSimulator", () => {
|
|||
{ id: "intl-10", name: "Team intl-10", externalId: "CANADA" }, // unrecognized
|
||||
];
|
||||
setupMockDb(participants, makeEvRows(ALL_IDS));
|
||||
await expect(new LLWSSimulator().simulate("season-1")).rejects.toThrow(/invalid externalId/);
|
||||
await expect(new LLWSSimulator(1_000).simulate("season-1")).rejects.toThrow(/invalid externalId/);
|
||||
});
|
||||
|
||||
it("throws when US team count is not 10", async () => {
|
||||
|
|
@ -259,7 +259,7 @@ describe("LLWSSimulator", () => {
|
|||
...Array.from({ length: 9 }, (_, i) => ({ id: `intl-${i + 1}`, name: `Team ${i + 1}`, externalId: "Intl" })),
|
||||
];
|
||||
setupMockDb(participants, makeEvRows(ALL_IDS));
|
||||
await expect(new LLWSSimulator().simulate("season-1")).rejects.toThrow(/10 US teams/);
|
||||
await expect(new LLWSSimulator(1_000).simulate("season-1")).rejects.toThrow(/10 US teams/);
|
||||
});
|
||||
|
||||
it("throws when fixed pools have unequal A/B split", async () => {
|
||||
|
|
@ -270,7 +270,7 @@ describe("LLWSSimulator", () => {
|
|||
...INTL_IDS.map((id) => ({ id, name: `Team ${id}`, externalId: "Intl" })),
|
||||
];
|
||||
setupMockDb(participants, makeEvRows(ALL_IDS));
|
||||
await expect(new LLWSSimulator().simulate("season-1")).rejects.toThrow(/exactly 5 teams each/);
|
||||
await expect(new LLWSSimulator(1_000).simulate("season-1")).rejects.toThrow(/exactly 5 teams each/);
|
||||
});
|
||||
|
||||
it("throws when US externalIds mix pool suffixes and bare side", async () => {
|
||||
|
|
@ -281,7 +281,7 @@ describe("LLWSSimulator", () => {
|
|||
...INTL_IDS.map((id) => ({ id, name: `Team ${id}`, externalId: "Intl" })),
|
||||
];
|
||||
setupMockDb(participants, makeEvRows(ALL_IDS));
|
||||
await expect(new LLWSSimulator().simulate("season-1")).rejects.toThrow(/mixed externalId formats/);
|
||||
await expect(new LLWSSimulator(1_000).simulate("season-1")).rejects.toThrow(/mixed externalId formats/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -259,6 +259,8 @@ function determineRandomized(sideTeams: Team[], sideName: string): boolean {
|
|||
// ─── Simulator ────────────────────────────────────────────────────────────────
|
||||
|
||||
export class LLWSSimulator implements Simulator {
|
||||
constructor(private numSimulations = NUM_SIMULATIONS) {}
|
||||
|
||||
async simulate(sportsSeasonId: string): Promise<SimulationResult[]> {
|
||||
const db = database();
|
||||
|
||||
|
|
@ -345,7 +347,7 @@ export class LLWSSimulator implements Simulator {
|
|||
};
|
||||
|
||||
// 6. Run Monte Carlo simulations.
|
||||
for (let s = 0; s < NUM_SIMULATIONS; s++) {
|
||||
for (let s = 0; s < this.numSimulations; s++) {
|
||||
// Assign pools for this simulation.
|
||||
const [usPoolA, usPoolB] = assignPools(usTeams, usRandomized);
|
||||
const [intlPoolA, intlPoolB] = assignPools(intlTeams, intlRandomized);
|
||||
|
|
@ -376,7 +378,7 @@ export class LLWSSimulator implements Simulator {
|
|||
// 7. Convert counts to probability distributions.
|
||||
// bracketLosers: 4 per sim (2 US + 2 Intl) → split evenly.
|
||||
const bracketLosersPerSim = 4;
|
||||
const bracketDivisor = bracketLosersPerSim * NUM_SIMULATIONS;
|
||||
const bracketDivisor = bracketLosersPerSim * this.numSimulations;
|
||||
|
||||
const zeroCounts: PlacementCounts = { champion: 0, finalist: 0, thirdPlace: 0, fourthPlace: 0, bracketLoser: 0 };
|
||||
return allIds.map((id) => {
|
||||
|
|
@ -385,10 +387,10 @@ export class LLWSSimulator implements Simulator {
|
|||
return {
|
||||
participantId: id,
|
||||
probabilities: {
|
||||
probFirst: c.champion / NUM_SIMULATIONS,
|
||||
probSecond: c.finalist / NUM_SIMULATIONS,
|
||||
probThird: c.thirdPlace / NUM_SIMULATIONS,
|
||||
probFourth: c.fourthPlace / NUM_SIMULATIONS,
|
||||
probFirst: c.champion / this.numSimulations,
|
||||
probSecond: c.finalist / this.numSimulations,
|
||||
probThird: c.thirdPlace / this.numSimulations,
|
||||
probFourth: c.fourthPlace / this.numSimulations,
|
||||
probFifth: bracketProb,
|
||||
probSixth: bracketProb,
|
||||
probSeventh: bracketProb,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue