Files
gridpilot.gg/adapters/bootstrap/racing/RacingTeamFactory.ts
2025-12-31 15:39:28 +01:00

284 lines
9.6 KiB
TypeScript

import { Driver } from '@core/racing/domain/entities/Driver';
import { League } from '@core/racing/domain/entities/League';
import { Team } from '@core/racing/domain/entities/Team';
import { MediaReference } from '@core/domain/media/MediaReference';
import type { TeamJoinRequest, TeamMembership } from '@core/racing/domain/types/TeamMembership';
import { faker } from '@faker-js/faker';
import { seedId } from './SeedIdHelper';
export interface TeamStats {
performanceLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro';
specialization: 'endurance' | 'sprint' | 'mixed';
region: string;
languages: string[];
totalWins: number;
totalRaces: number;
rating: number;
category?: string;
}
export class RacingTeamFactory {
constructor(
private readonly baseDate: Date,
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
) {}
createTeams(drivers: Driver[], leagues: League[]): Team[] {
const teamCount = 50; // Increased from 15 to 50
return Array.from({ length: teamCount }, (_, idx) => {
const i = idx + 1;
const owner = faker.helpers.arrayElement(drivers);
const teamLeagues = faker.helpers.arrayElements(
leagues.map((l) => l.id.toString()),
{ min: 0, max: 3 },
);
// 30-50% of teams are recruiting
const isRecruiting = faker.datatype.boolean({ probability: 0.4 });
const teamId = seedId(`team-${i}`, this.persistence);
return Team.create({
id: teamId,
name: faker.company.name() + ' Racing',
tag: faker.string.alpha({ length: 4, casing: 'upper' }),
description: faker.lorem.sentences(2),
ownerId: owner.id,
leagues: teamLeagues,
isRecruiting,
createdAt: faker.date.past({ years: 2, refDate: this.baseDate }),
logoRef: MediaReference.generated('team', teamId),
});
});
}
createTeamMemberships(drivers: Driver[], teams: Team[]): TeamMembership[] {
const memberships: TeamMembership[] = [];
const usedDrivers = new Set<string>();
teams.forEach((team, teamIndex) => {
const availableDrivers = drivers.filter(d => !usedDrivers.has(d.id.toString()) && d.id.toString() !== team.ownerId.toString());
// Create varied team compositions
let memberCount: number;
let hasManager: boolean;
if (teamIndex % 5 === 0) {
// Solo teams (just owner)
memberCount = 0;
hasManager = false;
} else if (teamIndex % 5 === 1) {
// Small teams (2-3 members)
memberCount = faker.number.int({ min: 1, max: 2 });
hasManager = faker.datatype.boolean();
} else if (teamIndex % 5 === 2) {
// Medium teams (3-5 members)
memberCount = faker.number.int({ min: 2, max: 4 });
hasManager = true;
} else if (teamIndex % 5 === 3) {
// Large teams (5-7 members)
memberCount = faker.number.int({ min: 4, max: 6 });
hasManager = true;
} else {
// Mixed - sometimes with manager, sometimes without
memberCount = faker.number.int({ min: 1, max: 5 });
hasManager = faker.datatype.boolean();
}
const members = faker.helpers.arrayElements(availableDrivers, memberCount);
// Add owner
memberships.push({
teamId: team.id.toString(),
driverId: team.ownerId.toString(),
role: 'owner',
status: 'active',
joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }),
});
usedDrivers.add(team.ownerId.toString());
// Add manager if needed
if (hasManager && members.length > 0) {
const managerIndex = faker.number.int({ min: 0, max: members.length - 1 });
const manager = members[managerIndex]!;
memberships.push({
teamId: team.id.toString(),
driverId: manager.id.toString(),
role: 'manager',
status: 'active',
joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }),
});
usedDrivers.add(manager.id.toString());
}
// Add remaining members as drivers
members.forEach((driver) => {
if (!usedDrivers.has(driver.id.toString())) {
memberships.push({
teamId: team.id.toString(),
driverId: driver.id.toString(),
role: 'driver',
status: 'active',
joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }),
});
usedDrivers.add(driver.id.toString());
}
});
});
return memberships;
}
createTeamJoinRequests(
drivers: Driver[],
teams: Team[],
teamMemberships: TeamMembership[],
): TeamJoinRequest[] {
const membershipIds = new Set<string>(teamMemberships.map(m => `${m.teamId}:${m.driverId}`));
const requests: TeamJoinRequest[] = [];
const addRequest = (request: TeamJoinRequest): void => {
requests.push(request);
};
const team1 = teams[0];
if (team1) {
const candidateDriverIds = drivers
.map(d => d.id.toString())
.filter(driverId => !membershipIds.has(`${team1.id.toString()}:${driverId}`))
.slice(0, 8);
candidateDriverIds.forEach((driverId, idx) => {
addRequest({
id: seedId(`team-join-${team1.id.toString()}-${driverId}`, this.persistence),
teamId: team1.id.toString(),
driverId,
requestedAt: this.addDays(this.baseDate, -(5 + idx)),
...(idx % 3 === 0
? { message: 'Can I join as a substitute driver for endurance events?' }
: idx % 3 === 2
? { message: '' }
: {}),
});
});
// Conflict edge case: owner submits a join request to own team.
addRequest({
id: seedId(`team-join-${team1.id.toString()}-${team1.ownerId.toString()}-conflict`, this.persistence),
teamId: team1.id.toString(),
driverId: team1.ownerId.toString(),
requestedAt: this.addDays(this.baseDate, -1),
message: 'Testing edge case: owner submitted join request.',
});
}
const team3 = teams[2];
if (team3 && drivers[0]) {
const driverId = drivers[0].id.toString();
addRequest({
id: seedId('dup-team-join-req-1', this.persistence),
teamId: team3.id.toString(),
driverId,
requestedAt: this.addDays(this.baseDate, -10),
message: 'First request message (will be overwritten).',
});
addRequest({
id: seedId('dup-team-join-req-1', this.persistence),
teamId: team3.id.toString(),
driverId,
requestedAt: this.addDays(this.baseDate, -9),
message: 'Updated request message (duplicate id).',
});
}
return requests;
}
/**
* Generate team statistics and metadata for display
* This would be stored in a separate stats table/service in production
*/
generateTeamStats(teams: Team[]): Map<string, TeamStats> {
const statsMap = new Map<string, TeamStats>();
// Available regions
const regions = ['Europe', 'North America', 'South America', 'Asia', 'Oceania', 'Africa'];
// Available languages
const allLanguages = ['English', 'German', 'French', 'Spanish', 'Italian', 'Portuguese', 'Japanese', 'Korean', 'Russian', 'Chinese'];
teams.forEach((team, idx) => {
const i = idx + 1;
// Determine performance level based on index
let performanceLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro';
let totalRaces: number;
let rating: number;
let totalWins: number;
if (i % 8 === 0) {
// Pro teams
performanceLevel = 'pro';
totalRaces = faker.number.int({ min: 80, max: 150 });
rating = faker.number.int({ min: 1700, max: 2000 });
totalWins = faker.number.int({ min: 15, max: 40 });
} else if (i % 5 === 0) {
// Advanced teams
performanceLevel = 'advanced';
totalRaces = faker.number.int({ min: 40, max: 100 });
rating = faker.number.int({ min: 1500, max: 1800 });
totalWins = faker.number.int({ min: 8, max: 20 });
} else if (i % 3 === 0) {
// Intermediate teams
performanceLevel = 'intermediate';
totalRaces = faker.number.int({ min: 20, max: 60 });
rating = faker.number.int({ min: 1200, max: 1600 });
totalWins = faker.number.int({ min: 3, max: 12 });
} else {
// Beginner teams
performanceLevel = 'beginner';
totalRaces = faker.number.int({ min: 5, max: 25 });
rating = faker.number.int({ min: 900, max: 1300 });
totalWins = faker.number.int({ min: 0, max: 5 });
}
// Determine specialization
let specialization: 'endurance' | 'sprint' | 'mixed';
if (i % 7 === 0) {
specialization = 'endurance';
} else if (i % 4 === 0) {
specialization = 'sprint';
} else {
specialization = 'mixed';
}
// Determine category - use all available categories
const categories = ['beginner', 'intermediate', 'advanced', 'pro', 'endurance', 'sprint'];
const category = faker.helpers.arrayElement(categories);
// Generate region and languages
const region = faker.helpers.arrayElement(regions);
const languageCount = faker.number.int({ min: 1, max: 3 });
const languages = faker.helpers.arrayElements(allLanguages, languageCount);
statsMap.set(team.id.toString(), {
performanceLevel,
specialization,
region,
languages,
totalWins,
totalRaces,
rating,
category,
});
});
return statsMap;
}
private addDays(date: Date, days: number): Date {
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
}
}