refactor racing use cases
This commit is contained in:
@@ -7,90 +7,114 @@
|
||||
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { AsyncUseCase } from '@core/shared/application/AsyncUseCase';
|
||||
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
|
||||
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
|
||||
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
||||
import type { GetDriverRatingInputPort } from '../ports/input/GetDriverRatingInputPort';
|
||||
import type { GetDriverRatingOutputPort } from '../ports/output/GetDriverRatingOutputPort';
|
||||
import type { RaceWithSOFOutputPort } from '../ports/output/RaceWithSOFOutputPort';
|
||||
import {
|
||||
AverageStrengthOfFieldCalculator,
|
||||
type StrengthOfFieldCalculator,
|
||||
} from '../../domain/services/StrengthOfFieldCalculator';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import type { Race } from '../../domain/entities/Race';
|
||||
|
||||
export interface GetRaceWithSOFQueryParams {
|
||||
export interface GetRaceWithSOFInput {
|
||||
raceId: string;
|
||||
}
|
||||
|
||||
type GetRaceWithSOFErrorCode = 'RACE_NOT_FOUND';
|
||||
export type GetRaceWithSOFErrorCode = 'RACE_NOT_FOUND' | 'REPOSITORY_ERROR';
|
||||
|
||||
export class GetRaceWithSOFUseCase implements AsyncUseCase<GetRaceWithSOFQueryParams, RaceWithSOFOutputPort, GetRaceWithSOFErrorCode> {
|
||||
export type GetRaceWithSOFResult = {
|
||||
race: Race;
|
||||
strengthOfField: number | null;
|
||||
participantCount: number;
|
||||
registeredCount: number;
|
||||
maxParticipants: number;
|
||||
};
|
||||
|
||||
type GetDriverRating = (input: { driverId: string }) => Promise<{ rating: number | null }>;
|
||||
|
||||
export class GetRaceWithSOFUseCase {
|
||||
private readonly sofCalculator: StrengthOfFieldCalculator;
|
||||
|
||||
constructor(
|
||||
private readonly raceRepository: IRaceRepository,
|
||||
private readonly registrationRepository: IRaceRegistrationRepository,
|
||||
private readonly resultRepository: IResultRepository,
|
||||
private readonly getDriverRating: (input: GetDriverRatingInputPort) => Promise<GetDriverRatingOutputPort>,
|
||||
private readonly getDriverRating: GetDriverRating,
|
||||
private readonly output: UseCaseOutputPort<GetRaceWithSOFResult>,
|
||||
sofCalculator?: StrengthOfFieldCalculator,
|
||||
) {
|
||||
this.sofCalculator = sofCalculator ?? new AverageStrengthOfFieldCalculator();
|
||||
}
|
||||
|
||||
async execute(params: GetRaceWithSOFQueryParams): Promise<Result<RaceWithSOFOutputPort, ApplicationErrorCode<GetRaceWithSOFErrorCode>>> {
|
||||
async execute(
|
||||
params: GetRaceWithSOFInput,
|
||||
): Promise<Result<void, ApplicationErrorCode<GetRaceWithSOFErrorCode, { message: string }>>> {
|
||||
const { raceId } = params;
|
||||
|
||||
const race = await this.raceRepository.findById(raceId);
|
||||
if (!race) {
|
||||
return Result.err({ code: 'RACE_NOT_FOUND' });
|
||||
try {
|
||||
const race = await this.raceRepository.findById(raceId);
|
||||
if (!race) {
|
||||
return Result.err({
|
||||
code: 'RACE_NOT_FOUND',
|
||||
details: { message: `Race with id ${raceId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
// Get participant IDs based on race status
|
||||
let participantIds: string[] = [];
|
||||
|
||||
if (race.status === 'completed') {
|
||||
// For completed races, use results
|
||||
const results = await this.resultRepository.findByRaceId(raceId);
|
||||
participantIds = results.map(r => r.driverId.toString());
|
||||
} else {
|
||||
// For upcoming/running races, use registrations
|
||||
participantIds = await this.registrationRepository.getRegisteredDrivers(raceId);
|
||||
}
|
||||
|
||||
// Use stored SOF if available, otherwise calculate
|
||||
let strengthOfField = race.strengthOfField ?? null;
|
||||
|
||||
if (strengthOfField === null && participantIds.length > 0) {
|
||||
// Get ratings for all participants using clean ports
|
||||
const ratingPromises = participantIds.map(driverId =>
|
||||
this.getDriverRating({ driverId }),
|
||||
);
|
||||
|
||||
const ratingResults = await Promise.all(ratingPromises);
|
||||
const driverRatings = participantIds.reduce<{ driverId: string; rating: number }[]>(
|
||||
(acc, driverId, index) => {
|
||||
const ratingResult = ratingResults[index];
|
||||
if (ratingResult && ratingResult.rating !== null) {
|
||||
acc.push({ driverId, rating: ratingResult.rating });
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
strengthOfField = this.sofCalculator.calculate(driverRatings);
|
||||
}
|
||||
|
||||
const result: GetRaceWithSOFResult = {
|
||||
race,
|
||||
strengthOfField,
|
||||
registeredCount: race.registeredCount ?? participantIds.length,
|
||||
maxParticipants: race.maxParticipants ?? participantIds.length,
|
||||
participantCount: participantIds.length,
|
||||
};
|
||||
|
||||
this.output.present(result);
|
||||
|
||||
return Result.ok(undefined);
|
||||
} catch (error) {
|
||||
const message =
|
||||
(error as Error)?.message ?? 'Failed to load race with SOF';
|
||||
return Result.err({
|
||||
code: 'REPOSITORY_ERROR',
|
||||
details: { message },
|
||||
});
|
||||
}
|
||||
|
||||
// Get participant IDs based on race status
|
||||
let participantIds: string[] = [];
|
||||
|
||||
if (race.status === 'completed') {
|
||||
// For completed races, use results
|
||||
const results = await this.resultRepository.findByRaceId(raceId);
|
||||
participantIds = results.map(r => r.driverId);
|
||||
} else {
|
||||
// For upcoming/running races, use registrations
|
||||
participantIds = await this.registrationRepository.getRegisteredDrivers(raceId);
|
||||
}
|
||||
|
||||
// Use stored SOF if available, otherwise calculate
|
||||
let strengthOfField = race.strengthOfField ?? null;
|
||||
|
||||
if (strengthOfField === null && participantIds.length > 0) {
|
||||
// Get ratings for all participants using clean ports
|
||||
const ratingPromises = participantIds.map(driverId =>
|
||||
this.getDriverRating({ driverId })
|
||||
);
|
||||
|
||||
const ratingResults = await Promise.all(ratingPromises);
|
||||
const driverRatings = participantIds
|
||||
.filter((_, index) => ratingResults[index].rating !== null)
|
||||
.map((driverId, index) => ({
|
||||
driverId,
|
||||
rating: ratingResults[index].rating!
|
||||
}));
|
||||
|
||||
strengthOfField = this.sofCalculator.calculate(driverRatings);
|
||||
}
|
||||
|
||||
const outputPort: RaceWithSOFOutputPort = {
|
||||
id: race.id,
|
||||
leagueId: race.leagueId,
|
||||
scheduledAt: race.scheduledAt,
|
||||
track: race.track ?? '',
|
||||
car: race.car ?? '',
|
||||
status: race.status,
|
||||
strengthOfField,
|
||||
registeredCount: race.registeredCount ?? participantIds.length,
|
||||
maxParticipants: race.maxParticipants ?? participantIds.length,
|
||||
participantCount: participantIds.length,
|
||||
};
|
||||
|
||||
return Result.ok(outputPort);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user