/** * Application Use Case: TeamRankingUseCase * * Computes team rankings from rating snapshots (ledger-based). * Orchestrates repositories to provide team ranking data to presentation layer. * Evolved from direct standings to use team rating events and snapshots. */ import type { Logger } from '@core/shared/application'; import type { ITeamRatingRepository } from '../../domain/repositories/ITeamRatingRepository'; import type { ITeamRepository } from '../../domain/repositories/ITeamRepository'; import type { ITeamRankingUseCase, TeamRanking } from './ITeamRankingUseCase'; export class TeamRankingUseCase implements ITeamRankingUseCase { constructor( private readonly teamRatingRepository: ITeamRatingRepository, private readonly teamRepository: ITeamRepository, private readonly logger: Logger ) { this.logger.info('[TeamRankingUseCase] Initialized with ledger-based team rating repositories'); } async getAllTeamRankings(): Promise { this.logger.debug('[TeamRankingUseCase] Computing rankings from team rating snapshots'); try { // Get all teams for name resolution const teams = await this.teamRepository.findAll(); const teamMap = new Map(teams.map(t => [t.id, t])); // Get all team IDs const teamIds = Array.from(teamMap.keys()); if (teamIds.length === 0) { this.logger.warn('[TeamRankingUseCase] No teams found'); return []; } // Get rating snapshots for all teams const rankingPromises = teamIds.map(async (teamId) => { const snapshot = await this.teamRatingRepository.findByTeamId(teamId); const team = teamMap.get(teamId); if (!snapshot || !team) { return null; } return { teamId, teamName: team.name.toString(), drivingRating: snapshot.driving.value, adminTrustRating: snapshot.adminTrust.value, overallRating: snapshot.overall, eventCount: snapshot.eventCount, lastUpdated: snapshot.lastUpdated, overallRank: null, // Will be assigned after sorting } as TeamRanking; }); const rankings = (await Promise.all(rankingPromises)).filter( (r): r is TeamRanking => r !== null ); if (rankings.length === 0) { this.logger.warn('[TeamRankingUseCase] No team rating snapshots found'); return []; } // Sort by overall rating descending and assign ranks rankings.sort((a, b) => b.overallRating - a.overallRating); rankings.forEach((r, idx) => r.overallRank = idx + 1); this.logger.info(`[TeamRankingUseCase] Computed rankings for ${rankings.length} teams`); return rankings; } catch (error) { this.logger.error('[TeamRankingUseCase] Error computing rankings:', error instanceof Error ? error : new Error(String(error))); throw error; } } async getTeamRanking(teamId: string): Promise { this.logger.debug(`[TeamRankingUseCase] Getting ranking for team ${teamId}`); try { const snapshot = await this.teamRatingRepository.findByTeamId(teamId); if (!snapshot) { this.logger.warn(`[TeamRankingUseCase] No rating snapshot found for team ${teamId}`); return null; } const team = await this.teamRepository.findById(teamId); if (!team) { this.logger.warn(`[TeamRankingUseCase] Team ${teamId} not found`); return null; } // Get all teams to calculate rank const allTeams = await this.teamRepository.findAll(); const allRankings: TeamRanking[] = []; for (const t of allTeams) { const s = await this.teamRatingRepository.findByTeamId(t.id); if (s) { allRankings.push({ teamId: t.id, teamName: t.name.toString(), drivingRating: s.driving.value, adminTrustRating: s.adminTrust.value, overallRating: s.overall, eventCount: s.eventCount, lastUpdated: s.lastUpdated, overallRank: null, }); } } // Sort and assign rank allRankings.sort((a, b) => b.overallRating - a.overallRating); const rank = allRankings.findIndex(r => r.teamId === teamId) + 1; return { teamId, teamName: team.name.toString(), drivingRating: snapshot.driving.value, adminTrustRating: snapshot.adminTrust.value, overallRating: snapshot.overall, eventCount: snapshot.eventCount, lastUpdated: snapshot.lastUpdated, overallRank: rank, }; } catch (error) { this.logger.error(`[TeamRankingUseCase] Error getting team ranking:`, error instanceof Error ? error : new Error(String(error))); throw error; } } }