186 lines
7.9 KiB
TypeScript
186 lines
7.9 KiB
TypeScript
import { Driver } from '@core/racing/domain/entities/Driver';
|
|
import { faker } from '@faker-js/faker';
|
|
import { seedId } from './SeedIdHelper';
|
|
|
|
export interface DriverStats {
|
|
rating: number;
|
|
safetyRating: number;
|
|
sportsmanshipRating: number;
|
|
totalRaces: number;
|
|
wins: number;
|
|
podiums: number;
|
|
dnfs: number;
|
|
avgFinish: number;
|
|
bestFinish: number;
|
|
worstFinish: number;
|
|
consistency: number;
|
|
experienceLevel: 'beginner' | 'intermediate' | 'advanced' | 'veteran';
|
|
overallRank: number | null;
|
|
}
|
|
|
|
export class RacingDriverFactory {
|
|
constructor(
|
|
private readonly driverCount: number,
|
|
private readonly baseDate: Date,
|
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
|
) {}
|
|
|
|
create(): Driver[] {
|
|
const countries = ['DE', 'NL', 'FR', 'GB', 'US', 'CA', 'SE', 'NO', 'IT', 'ES', 'AU', 'BR', 'JP', 'KR', 'RU', 'PL', 'CZ', 'HU', 'AT', 'CH'] as const;
|
|
|
|
return Array.from({ length: this.driverCount }, (_, idx) => {
|
|
const i = idx + 1;
|
|
|
|
// Vary bio presence: 80% have bio, 20% empty
|
|
const hasBio = i % 5 !== 0;
|
|
|
|
// Vary joined dates: some recent (new drivers), some old (veterans)
|
|
let joinedAt: Date;
|
|
if (i % 10 === 0) {
|
|
// Very recent drivers (within last week)
|
|
joinedAt = faker.date.recent({ days: 7, refDate: this.baseDate });
|
|
} else if (i % 7 === 0) {
|
|
// Recent drivers (within last month)
|
|
joinedAt = faker.date.recent({ days: 30, refDate: this.baseDate });
|
|
} else if (i % 5 === 0) {
|
|
// Medium tenure (6-12 months ago)
|
|
joinedAt = faker.date.between({
|
|
from: new Date(this.baseDate.getTime() - 365 * 24 * 60 * 60 * 1000),
|
|
to: new Date(this.baseDate.getTime() - 180 * 24 * 60 * 60 * 1000)
|
|
});
|
|
} else {
|
|
// Veterans (1-2 years ago)
|
|
joinedAt = faker.date.past({ years: 2, refDate: this.baseDate });
|
|
}
|
|
|
|
const driverData: {
|
|
id: string;
|
|
iracingId: string;
|
|
name: string;
|
|
country: string;
|
|
bio?: string;
|
|
joinedAt?: Date;
|
|
} = {
|
|
id: seedId(`driver-${i}`, this.persistence),
|
|
iracingId: String(100000 + i),
|
|
name: faker.person.fullName(),
|
|
country: faker.helpers.arrayElement(countries),
|
|
joinedAt,
|
|
};
|
|
|
|
if (hasBio) {
|
|
driverData.bio = faker.lorem.sentences(2);
|
|
}
|
|
|
|
return Driver.create(driverData);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Generate driver statistics for profile display
|
|
* This would be stored in a separate stats table/service in production
|
|
*/
|
|
generateDriverStats(drivers: Driver[]): Map<string, DriverStats> {
|
|
const statsMap = new Map<string, DriverStats>();
|
|
|
|
drivers.forEach((driver, idx) => {
|
|
const i = idx + 1;
|
|
|
|
// Determine experience level based on join date and index
|
|
let experienceLevel: 'beginner' | 'intermediate' | 'advanced' | 'veteran';
|
|
let totalRaces: number;
|
|
let rating: number;
|
|
let safetyRating: number;
|
|
let sportsmanshipRating: number;
|
|
|
|
if (i % 10 === 0) {
|
|
// Very recent drivers
|
|
experienceLevel = 'beginner';
|
|
totalRaces = faker.number.int({ min: 1, max: 10 });
|
|
rating = faker.number.int({ min: 800, max: 1200 });
|
|
safetyRating = faker.number.int({ min: 60, max: 85 });
|
|
sportsmanshipRating = Math.round(faker.number.float({ min: 3.0, max: 4.2 }) * 10) / 10;
|
|
} else if (i % 7 === 0) {
|
|
// Recent drivers
|
|
experienceLevel = 'beginner';
|
|
totalRaces = faker.number.int({ min: 5, max: 25 });
|
|
rating = faker.number.int({ min: 1000, max: 1400 });
|
|
safetyRating = faker.number.int({ min: 70, max: 90 });
|
|
sportsmanshipRating = Math.round(faker.number.float({ min: 3.5, max: 4.5 }) * 10) / 10;
|
|
} else if (i % 5 === 0) {
|
|
// Medium tenure
|
|
experienceLevel = 'intermediate';
|
|
totalRaces = faker.number.int({ min: 20, max: 60 });
|
|
rating = faker.number.int({ min: 1300, max: 1700 });
|
|
safetyRating = faker.number.int({ min: 75, max: 95 });
|
|
sportsmanshipRating = Math.round(faker.number.float({ min: 3.8, max: 4.8 }) * 10) / 10;
|
|
} else if (i % 3 === 0) {
|
|
// Advanced
|
|
experienceLevel = 'advanced';
|
|
totalRaces = faker.number.int({ min: 50, max: 120 });
|
|
rating = faker.number.int({ min: 1600, max: 1900 });
|
|
safetyRating = faker.number.int({ min: 80, max: 98 });
|
|
sportsmanshipRating = Math.round(faker.number.float({ min: 4.0, max: 5.0 }) * 10) / 10;
|
|
} else {
|
|
// Veterans
|
|
experienceLevel = 'veteran';
|
|
totalRaces = faker.number.int({ min: 100, max: 200 });
|
|
rating = faker.number.int({ min: 1700, max: 2000 });
|
|
safetyRating = faker.number.int({ min: 85, max: 100 });
|
|
sportsmanshipRating = Math.round(faker.number.float({ min: 4.2, max: 5.0 }) * 10) / 10;
|
|
}
|
|
|
|
// Calculate performance stats based on total races
|
|
const winRate = experienceLevel === 'beginner' ? faker.number.float({ min: 0, max: 5, fractionDigits: 1 }) :
|
|
experienceLevel === 'intermediate' ? faker.number.float({ min: 2, max: 10, fractionDigits: 1 }) :
|
|
experienceLevel === 'advanced' ? faker.number.float({ min: 5, max: 15, fractionDigits: 1 }) :
|
|
faker.number.float({ min: 8, max: 20, fractionDigits: 1 });
|
|
|
|
const podiumRate = experienceLevel === 'beginner' ? faker.number.float({ min: 5, max: 15, fractionDigits: 1 }) :
|
|
experienceLevel === 'intermediate' ? faker.number.float({ min: 10, max: 25, fractionDigits: 1 }) :
|
|
experienceLevel === 'advanced' ? faker.number.float({ min: 15, max: 35, fractionDigits: 1 }) :
|
|
faker.number.float({ min: 20, max: 45, fractionDigits: 1 });
|
|
|
|
const wins = Math.round((winRate / 100) * totalRaces);
|
|
const podiums = Math.round((podiumRate / 100) * totalRaces);
|
|
const dnfs = Math.round(faker.number.float({ min: 0.05, max: 0.15, fractionDigits: 2 }) * totalRaces);
|
|
|
|
const avgFinish = experienceLevel === 'beginner' ? faker.number.float({ min: 8, max: 15, fractionDigits: 1 }) :
|
|
experienceLevel === 'intermediate' ? faker.number.float({ min: 5, max: 10, fractionDigits: 1 }) :
|
|
experienceLevel === 'advanced' ? faker.number.float({ min: 3, max: 7, fractionDigits: 1 }) :
|
|
faker.number.float({ min: 2, max: 5, fractionDigits: 1 });
|
|
|
|
const bestFinish = experienceLevel === 'beginner' ? faker.number.int({ min: 3, max: 8 }) :
|
|
experienceLevel === 'intermediate' ? faker.number.int({ min: 1, max: 5 }) :
|
|
faker.number.int({ min: 1, max: 3 });
|
|
|
|
const worstFinish = experienceLevel === 'beginner' ? faker.number.int({ min: 12, max: 20 }) :
|
|
experienceLevel === 'intermediate' ? faker.number.int({ min: 8, max: 15 }) :
|
|
experienceLevel === 'advanced' ? faker.number.int({ min: 5, max: 12 }) :
|
|
faker.number.int({ min: 4, max: 10 });
|
|
|
|
const consistency = experienceLevel === 'beginner' ? faker.number.float({ min: 40, max: 65, fractionDigits: 0 }) :
|
|
experienceLevel === 'intermediate' ? faker.number.float({ min: 60, max: 80, fractionDigits: 0 }) :
|
|
experienceLevel === 'advanced' ? faker.number.float({ min: 75, max: 90, fractionDigits: 0 }) :
|
|
faker.number.float({ min: 85, max: 98, fractionDigits: 0 });
|
|
|
|
statsMap.set(driver.id.toString(), {
|
|
rating,
|
|
safetyRating,
|
|
sportsmanshipRating,
|
|
totalRaces,
|
|
wins,
|
|
podiums,
|
|
dnfs,
|
|
avgFinish: parseFloat(avgFinish.toFixed(1)),
|
|
bestFinish,
|
|
worstFinish,
|
|
consistency: parseFloat(consistency.toFixed(1)),
|
|
experienceLevel,
|
|
overallRank: null, // Will be calculated after all stats are loaded
|
|
});
|
|
});
|
|
|
|
return statsMap;
|
|
}
|
|
} |