import { Driver } from '@core/racing/domain/entities/Driver'; import { League } from '@core/racing/domain/entities/League'; import { Race } from '@core/racing/domain/entities/Race'; import { Result } from '@core/racing/domain/entities/Result'; import { Standing } from '@core/racing/domain/entities/Standing'; import { SessionType } from '@core/racing/domain/value-objects/SessionType'; import { faker } from '../../helpers/faker/faker'; /** * Core racing seed types and generators (drivers, leagues, teams, races, standings). * Extracted from the legacy StaticRacingSeed module to keep files smaller and focused. */ export type RacingMembership = { driverId: string; leagueId: string; teamId?: string; }; export type Friendship = { driverId: string; friendId: string; }; import { MediaReference } from '@core/domain/media/MediaReference'; export interface DemoTeamDTO { id: string; name: string; tag: string; description: string; logoRef: MediaReference; primaryLeagueId: string; memberCount: number; } /** * Championship points table used when aggregating standings. */ export const POINTS_TABLE: Record = { 1: 25, 2: 18, 3: 15, 4: 12, 5: 10, 6: 8, 7: 6, 8: 4, 9: 2, 10: 1, }; export function pickOne(items: readonly T[]): T { if (items.length === 0) { throw new Error('pickOne: empty items array'); } const index = faker.number.int({ min: 0, max: items.length - 1 }); return items[index]!; } export function createDrivers(count: number): Driver[] { const drivers: Driver[] = []; for (let i = 0; i < count; i++) { const id = `driver-${i + 1}`; const name = faker.person.fullName(); const country = faker.location.countryCode('alpha-2'); const iracingId = faker.string.numeric(6); drivers.push( Driver.create({ id, iracingId, name, country, bio: faker.lorem.sentence(), joinedAt: faker.date.past(), }), ); } return drivers; } export function createLeagues(ownerIds: string[]): League[] { const leagueNames = [ 'GridPilot Sprint Series', 'GridPilot Endurance Cup', 'GridPilot Club Ladder', 'Sprint Challenge League', 'Club Racers Collective', 'Sim Racing Alliance', 'Pacific Time Attack', 'Nordic Night Series', ]; const leagues: League[] = []; const leagueCount = 6 + faker.number.int({ min: 0, max: 2 }); for (let i = 0; i < leagueCount; i++) { const id = `league-${i + 1}`; const name = leagueNames[i] ?? faker.company.name(); // Ensure league-5 (demo league with running race) is owned by driver-1 const ownerId = i === 4 ? 'driver-1' : pickOne(ownerIds); const maxDriversOptions = [24, 32, 48, 64]; let settings = { pointsSystem: faker.helpers.arrayElement(['f1-2024', 'indycar']), sessionDuration: faker.helpers.arrayElement([45, 60, 90, 120]), qualifyingFormat: faker.helpers.arrayElement(['open', 'single-lap']), maxDrivers: faker.helpers.arrayElement(maxDriversOptions), } as const; if (i === 0) { settings = { ...settings, maxDrivers: 24, }; } else if (i === 1) { settings = { ...settings, maxDrivers: 24, }; } else if (i === 2) { settings = { ...settings, maxDrivers: 40, }; } const socialLinks = i === 0 ? { discordUrl: 'https://discord.gg/gridpilot-demo', youtubeUrl: 'https://youtube.com/@gridpilot-demo', websiteUrl: 'https://gridpilot-demo.example.com', } : i === 1 ? { discordUrl: 'https://discord.gg/gridpilot-endurance', youtubeUrl: 'https://youtube.com/@gridpilot-endurance', } : i === 2 ? { websiteUrl: 'https://virtual-touring.example.com', } : undefined; if (socialLinks) { leagues.push( League.create({ id, name, description: faker.lorem.sentence(), ownerId, settings, createdAt: faker.date.past(), socialLinks, }), ); } else { leagues.push( League.create({ id, name, description: faker.lorem.sentence(), ownerId, settings, createdAt: faker.date.past(), }), ); } } return leagues; } export function createTeams(leagues: League[]): DemoTeamDTO[] { const teams: DemoTeamDTO[] = []; const teamCount = 24 + faker.number.int({ min: 0, max: 12 }); for (let i = 0; i < teamCount; i++) { const id = `team-${i + 1}`; const primaryLeague = pickOne(leagues); const name = faker.company.name(); const tag = faker.string.alpha({ length: 4 }).toUpperCase(); const memberCount = faker.number.int({ min: 2, max: 8 }); teams.push({ id, name, tag, description: faker.lorem.sentence(), logoRef: MediaReference.systemDefault('logo'), primaryLeagueId: primaryLeague.id, memberCount, }); } return teams; } export function createMemberships( drivers: Driver[], leagues: League[], teams: DemoTeamDTO[], ): RacingMembership[] { const memberships: RacingMembership[] = []; const teamsByLeague = new Map(); teams.forEach((team) => { const list = teamsByLeague.get(team.primaryLeagueId) ?? []; list.push(team); teamsByLeague.set(team.primaryLeagueId, list); }); drivers.forEach((driver) => { // Each driver participates in 1–3 leagues const leagueSampleSize = faker.number.int({ min: 1, max: Math.min(3, leagues.length) }); const shuffledLeagues = faker.helpers.shuffle(leagues).slice(0, leagueSampleSize); shuffledLeagues.forEach((league) => { const leagueTeams = teamsByLeague.get(league.id) ?? []; const team = leagueTeams.length > 0 && faker.datatype.boolean() ? pickOne(leagueTeams) : undefined; const membership: RacingMembership = { driverId: driver.id, leagueId: league.id, }; if (team) { membership.teamId = team.id; } memberships.push(membership); }); }); return memberships; } export function createRaces(leagues: League[]): Race[] { const races: Race[] = []; const raceCount = 60 + faker.number.int({ min: 0, max: 20 }); const tracks = [ 'Monza GP', 'Spa-Francorchamps', 'Suzuka', 'Mount Panorama', 'Silverstone GP', 'Interlagos', 'Imola', 'Laguna Seca', ]; const cars = [ 'GT3 – Porsche 911', 'GT3 – BMW M4', 'LMP3 Prototype', 'GT4 – Alpine', 'Touring – Civic', ]; const baseDate = new Date(); for (let i = 0; i < raceCount; i++) { const id = `race-${i + 1}`; let league = pickOne(leagues); const offsetDays = faker.number.int({ min: -30, max: 45 }); const scheduledAt = new Date(baseDate.getTime() + offsetDays * 24 * 60 * 60 * 1000); let status: 'scheduled' | 'completed' | 'running' = scheduledAt.getTime() < baseDate.getTime() ? 'completed' : 'scheduled'; let strengthOfField: number | undefined; // Special case: Make race-1 a running race in league-5 (user's admin league) if (i === 0) { const league5 = leagues.find(l => l.id === 'league-5'); if (league5) { league = league5; status = 'running'; // Calculate SOF for the running race (simulate 12-20 drivers with average rating ~1500) const participantCount = faker.number.int({ min: 12, max: 20 }); const averageRating = 1500 + faker.number.int({ min: -200, max: 300 }); strengthOfField = Math.round(averageRating); } } races.push( Race.create({ id, leagueId: league.id, scheduledAt, track: faker.helpers.arrayElement(tracks), car: faker.helpers.arrayElement(cars), sessionType: SessionType.main(), status, ...(strengthOfField !== undefined ? { strengthOfField } : {}), ...(status === 'running' ? { registeredCount: faker.number.int({ min: 12, max: 20 }) } : {}), }), ); } return races; } export function createResults(drivers: Driver[], races: Race[]): Result[] { const results: Result[] = []; const completedRaces = races.filter((race) => race.status === 'completed'); completedRaces.forEach((race) => { const participantCount = faker.number.int({ min: 20, max: 32 }); const shuffledDrivers = faker.helpers.shuffle(drivers).slice(0, participantCount); shuffledDrivers.forEach((driver, index) => { const position = index + 1; const startPosition = faker.number.int({ min: 1, max: participantCount }); const fastestLap = 90_000 + index * 250 + faker.number.int({ min: 0, max: 2_000 }); const incidents = faker.number.int({ min: 0, max: 6 }); results.push( Result.create({ id: `${race.id}-${driver.id}`, raceId: race.id, driverId: driver.id, position, startPosition, fastestLap, incidents, }), ); }); }); return results; } export function createStandings(leagues: League[], results: Result[]): Standing[] { const standingsByLeague = new Map(); leagues.forEach((league) => { const leagueRaceIds = new Set( results .filter((result) => { return result.raceId.startsWith('race-'); }) .map((result) => result.raceId), ); const leagueResults = results.filter((result) => leagueRaceIds.has(result.raceId)); const standingsMap = new Map(); leagueResults.forEach((result) => { const key = result.driverId; let standing = standingsMap.get(key); if (!standing) { standing = Standing.create({ leagueId: league.id, driverId: result.driverId, }); } standing = standing.addRaceResult(result.position, POINTS_TABLE); standingsMap.set(key, standing); }); const sortedStandings = Array.from(standingsMap.values()).sort((a, b) => { if (b.points !== a.points) { return b.points - a.points; } if (b.wins !== a.wins) { return b.wins - a.wins; } return b.racesCompleted - a.racesCompleted; }); const finalizedStandings = sortedStandings.map((standing, index) => standing.updatePosition(index + 1), ); standingsByLeague.set(league.id, finalizedStandings); }); return Array.from(standingsByLeague.values()).flat(); } export function createFriendships(drivers: Driver[]): Friendship[] { const friendships: Friendship[] = []; drivers.forEach((driver, index) => { const friendCount = faker.number.int({ min: 3, max: 8 }); for (let offset = 1; offset <= friendCount; offset++) { const friendIndex = (index + offset) % drivers.length; const friend = drivers[friendIndex]; if (!friend) continue; if (friend.id === driver.id) continue; friendships.push({ driverId: driver.id, friendId: friend.id, }); } }); return friendships; }