Starters-on-pitch, chart movers sort, pre-filled what-if bracket, vs/@ fix
- Finished matches show the STARTING XIs on the pitch (red markers with the minute on everyone subbed off) with 'Subbed on' / 'Subbed off' lists below, minutes and ratings included; live matches keep the current-XI view that swaps substitutes in - Title-race chart gets a sort toggle, defaulting to 'Biggest movers' (the teams whose odds changed most at the latest update — what's relevant now) with 'Title favorites' as the alternative - Entering what-if on the bracket now pre-fills the model's most likely tournament: group slots seeded from win-group/advance odds (best- thirds assigned to their allowed slots via backtracking), every pairing picked by the model — a complete overview to edit by tapping; Reset returns to the projection - Team fixtures drop the US 'vs/@' convention: always 'vs' plus an H/A badge with a tooltip Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+52
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { championPath, effectiveBracket, prunePicks, type Picks } from './whatif';
|
||||
import { championPath, effectiveBracket, modelBracket, projectedSeeds, prunePicks, type Picks } from './whatif';
|
||||
import type { Fixture } from './types';
|
||||
|
||||
const slot = (team: string | null, placeholder: string | null = null) =>
|
||||
@@ -70,3 +70,54 @@ describe('championPath', () => {
|
||||
expect(championPath(bracket(), effectiveBracket(bracket(), { 90: 'home' }))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('projectedSeeds + modelBracket', () => {
|
||||
const slot = (team: string | null, placeholder: string | null) => ({ team, placeholder, label: team ?? placeholder ?? '' });
|
||||
const groupFx = (num: number, group: string, home: string, away: string) => ({
|
||||
num, stage: 'group' as const, group, round: '', groupRound: 1,
|
||||
kickoff: '2026-06-11T00:00:00Z', venue: 'X',
|
||||
home: slot(home, null), away: slot(away, null),
|
||||
status: 'scheduled' as const, homeScore: null, awayScore: null, minute: null,
|
||||
});
|
||||
const koFx = (num: number, stage: 'r32' | 'r16', hp: string, ap: string) => ({
|
||||
num, stage, group: null, round: '', groupRound: null,
|
||||
kickoff: '2026-06-29T00:00:00Z', venue: 'X',
|
||||
home: slot(null, hp), away: slot(null, ap),
|
||||
status: 'scheduled' as const, homeScore: null, awayScore: null, minute: null,
|
||||
});
|
||||
const odds = (team: string, winGroup: number, qualify: number) => ({
|
||||
team, winGroup, qualify, reachR16: 0, reachQF: 0, reachSF: 0, reachFinal: 0, champion: 0,
|
||||
});
|
||||
|
||||
const fixtures = [
|
||||
groupFx(1, 'A', 'Alpha', 'Beta'), groupFx(2, 'A', 'Gamma', 'Beta'),
|
||||
groupFx(3, 'B', 'Delta', 'Echo'), groupFx(4, 'B', 'Foxtrot', 'Echo'),
|
||||
koFx(73, 'r32', '1A', '2B'), koFx(74, 'r32', '1B', '3A/B'),
|
||||
koFx(75, 'r16', 'W73', 'W74'),
|
||||
];
|
||||
const teamOdds = [
|
||||
odds('Alpha', 0.7, 0.95), odds('Beta', 0.2, 0.6), odds('Gamma', 0.1, 0.3),
|
||||
odds('Delta', 0.6, 0.9), odds('Echo', 0.3, 0.7), odds('Foxtrot', 0.1, 0.4),
|
||||
];
|
||||
|
||||
it('seeds winners, runners-up and third-place slots from the odds', () => {
|
||||
const seeds = projectedSeeds(fixtures, teamOdds);
|
||||
expect(seeds['1A']).toBe('Alpha');
|
||||
expect(seeds['2A']).toBe('Beta');
|
||||
expect(seeds['1B']).toBe('Delta');
|
||||
// best third by qualify between Gamma (0.3) and Foxtrot (0.4) → Foxtrot
|
||||
expect(seeds['3A/B']).toBe('Foxtrot');
|
||||
});
|
||||
|
||||
it('fills every resolvable pairing with the model-preferred side', () => {
|
||||
const seeds = projectedSeeds(fixtures, teamOdds);
|
||||
// favor the alphabetically-first team so outcomes are deterministic
|
||||
const adv = (_f: unknown, home: string, away: string) => (home < away ? 0.8 : 0.2);
|
||||
const picks = modelBracket(fixtures, seeds, adv);
|
||||
expect(picks[73]).toBe('home'); // Alpha vs Echo → Alpha
|
||||
expect(picks[74]).toBe('home'); // Delta vs Foxtrot → Delta
|
||||
expect(picks[75]).toBe('home'); // Alpha vs Delta → Alpha
|
||||
const eff = effectiveBracket(fixtures, picks, seeds);
|
||||
expect(eff.get(75)?.winner).toBe('Alpha');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user