brackt/app/models/__tests__/cfp-12-bracket.test.ts

94 lines
3.1 KiB
TypeScript
Raw Permalink Normal View History

Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
import { describe, it, expect } from "vitest";
import { CFP_12, BRACKET_TEMPLATES, getScoringRoundType } from "~/lib/bracket-templates";
describe("CFP_12 Bracket Template", () => {
describe("Template structure", () => {
it("has correct total teams", () => {
expect(CFP_12.totalTeams).toBe(12);
});
it("has 4 rounds", () => {
expect(CFP_12.rounds).toHaveLength(4);
});
it("has correct round names", () => {
expect(CFP_12.rounds[0].name).toBe("First Round");
expect(CFP_12.rounds[1].name).toBe("Quarterfinals");
expect(CFP_12.rounds[2].name).toBe("Semifinals");
expect(CFP_12.rounds[3].name).toBe("National Championship");
});
it("has correct match counts per round", () => {
expect(CFP_12.rounds[0].matchCount).toBe(4); // 8 seeds play, 4 byes
expect(CFP_12.rounds[1].matchCount).toBe(4); // 8 → 4
expect(CFP_12.rounds[2].matchCount).toBe(2); // 4 → 2
expect(CFP_12.rounds[3].matchCount).toBe(1); // 2 → 1
});
it("has correct round advancement chain", () => {
expect(CFP_12.rounds[0].feedsInto).toBe("Quarterfinals");
expect(CFP_12.rounds[1].feedsInto).toBe("Semifinals");
expect(CFP_12.rounds[2].feedsInto).toBe("National Championship");
expect(CFP_12.rounds[3].feedsInto).toBeNull();
});
});
describe("Scoring configuration", () => {
it("scoring starts at Quarterfinals", () => {
expect(CFP_12.scoringStartsAtRound).toBe("Quarterfinals");
});
it("First Round is not scoring", () => {
expect(CFP_12.rounds[0].isScoring).toBe(false);
});
it("Quarterfinals is scoring", () => {
expect(CFP_12.rounds[1].isScoring).toBe(true);
});
it("Semifinals is scoring", () => {
expect(CFP_12.rounds[2].isScoring).toBe(true);
});
it("National Championship is scoring", () => {
expect(CFP_12.rounds[3].isScoring).toBe(true);
});
});
describe("Template ID and registry", () => {
it("has id cfp_12", () => {
expect(CFP_12.id).toBe("cfp_12");
});
it("is registered in BRACKET_TEMPLATES", () => {
expect(BRACKET_TEMPLATES["cfp_12"]).toBeDefined();
expect(BRACKET_TEMPLATES["cfp_12"]).toBe(CFP_12);
});
});
describe("getScoringRoundType", () => {
it("classifies Quarterfinals as quarterfinals (losers share 5th8th)", () => {
expect(getScoringRoundType("Quarterfinals", CFP_12)).toBe("quarterfinals");
});
it("classifies Semifinals as semifinals (losers share 3rd4th)", () => {
expect(getScoringRoundType("Semifinals", CFP_12)).toBe("semifinals");
});
it("classifies National Championship as finals", () => {
expect(getScoringRoundType("National Championship", CFP_12)).toBe("finals");
});
it("returns null for First Round (non-scoring)", () => {
expect(getScoringRoundType("First Round", CFP_12)).toBeNull();
});
});
describe("Total matches", () => {
it("has 11 total matches across all rounds", () => {
const total = CFP_12.rounds.reduce((sum, r) => sum + r.matchCount, 0);
expect(total).toBe(11); // 4 + 4 + 2 + 1
});
});
});