rename to core
This commit is contained in:
142
core/identity/domain/services/RatingUpdateService.ts
Normal file
142
core/identity/domain/services/RatingUpdateService.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import type { IDomainService } from '@gridpilot/shared/domain';
|
||||
import type { IUserRatingRepository } from '../repositories/IUserRatingRepository';
|
||||
import { UserRating } from '../value-objects/UserRating';
|
||||
|
||||
/**
|
||||
* Domain Service: RatingUpdateService
|
||||
*
|
||||
* Handles updating user ratings based on various events and performance metrics.
|
||||
* Centralizes rating calculation logic and ensures consistency across the system.
|
||||
*/
|
||||
export class RatingUpdateService implements IDomainService {
|
||||
constructor(
|
||||
private readonly userRatingRepository: IUserRatingRepository
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Update driver ratings after race completion
|
||||
*/
|
||||
async updateDriverRatingsAfterRace(
|
||||
driverResults: Array<{
|
||||
driverId: string;
|
||||
position: number;
|
||||
totalDrivers: number;
|
||||
incidents: number;
|
||||
startPosition: number;
|
||||
}>
|
||||
): Promise<void> {
|
||||
for (const result of driverResults) {
|
||||
await this.updateDriverRating(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update individual driver rating based on race result
|
||||
*/
|
||||
private async updateDriverRating(result: {
|
||||
driverId: string;
|
||||
position: number;
|
||||
totalDrivers: number;
|
||||
incidents: number;
|
||||
startPosition: number;
|
||||
}): Promise<void> {
|
||||
const { driverId, position, totalDrivers, incidents, startPosition } = result;
|
||||
|
||||
// Get or create user rating
|
||||
let userRating = await this.userRatingRepository.findByUserId(driverId);
|
||||
if (!userRating) {
|
||||
userRating = UserRating.create(driverId);
|
||||
}
|
||||
|
||||
// Calculate performance score (0-100)
|
||||
const performanceScore = this.calculatePerformanceScore(position, totalDrivers, startPosition);
|
||||
|
||||
// Calculate fairness score based on incidents (lower incidents = higher fairness)
|
||||
const fairnessScore = this.calculateFairnessScore(incidents, totalDrivers);
|
||||
|
||||
// Update ratings
|
||||
const updatedRating = userRating
|
||||
.updateDriverRating(performanceScore)
|
||||
.updateFairnessScore(fairnessScore);
|
||||
|
||||
// Save updated rating
|
||||
await this.userRatingRepository.save(updatedRating);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate performance score based on finishing position and field strength
|
||||
*/
|
||||
private calculatePerformanceScore(
|
||||
position: number,
|
||||
totalDrivers: number,
|
||||
startPosition: number
|
||||
): number {
|
||||
// Base score from finishing position (reverse percentile)
|
||||
const positionScore = ((totalDrivers - position + 1) / totalDrivers) * 100;
|
||||
|
||||
// Bonus for positions gained
|
||||
const positionsGained = startPosition - position;
|
||||
const gainBonus = Math.max(0, positionsGained * 2); // 2 points per position gained
|
||||
|
||||
// Field strength adjustment (harder fields give higher scores for same position)
|
||||
const fieldStrengthMultiplier = 0.8 + (totalDrivers / 50); // Max 1.0 for 30+ drivers
|
||||
|
||||
const rawScore = (positionScore + gainBonus) * fieldStrengthMultiplier;
|
||||
|
||||
// Clamp to 0-100 range
|
||||
return Math.max(0, Math.min(100, rawScore));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate fairness score based on incident involvement
|
||||
*/
|
||||
private calculateFairnessScore(incidents: number, totalDrivers: number): number {
|
||||
// Base fairness score (100 = perfect, 0 = terrible)
|
||||
let fairnessScore = 100;
|
||||
|
||||
// Deduct points for incidents
|
||||
fairnessScore -= incidents * 15; // 15 points per incident
|
||||
|
||||
// Additional deduction for high incident rate relative to field
|
||||
const incidentRate = incidents / totalDrivers;
|
||||
if (incidentRate > 0.5) {
|
||||
fairnessScore -= 20; // Heavy penalty for being involved in many incidents
|
||||
}
|
||||
|
||||
// Clamp to 0-100 range
|
||||
return Math.max(0, Math.min(100, fairnessScore));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update trust score based on sportsmanship actions
|
||||
*/
|
||||
async updateTrustScore(driverId: string, trustChange: number): Promise<void> {
|
||||
let userRating = await this.userRatingRepository.findByUserId(driverId);
|
||||
if (!userRating) {
|
||||
userRating = UserRating.create(driverId);
|
||||
}
|
||||
|
||||
// Convert trust change (-50 to +50) to 0-100 scale
|
||||
const currentTrust = userRating.trust.value;
|
||||
const newTrustValue = Math.max(0, Math.min(100, currentTrust + trustChange));
|
||||
|
||||
const updatedRating = userRating.updateTrustScore(newTrustValue);
|
||||
await this.userRatingRepository.save(updatedRating);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update steward rating based on protest handling quality
|
||||
*/
|
||||
async updateStewardRating(stewardId: string, ratingChange: number): Promise<void> {
|
||||
let userRating = await this.userRatingRepository.findByUserId(stewardId);
|
||||
if (!userRating) {
|
||||
userRating = UserRating.create(stewardId);
|
||||
}
|
||||
|
||||
const currentRating = userRating.steward.value;
|
||||
const newRatingValue = Math.max(0, Math.min(100, currentRating + ratingChange));
|
||||
|
||||
const updatedRating = userRating.updateStewardRating(newRatingValue);
|
||||
await this.userRatingRepository.save(updatedRating);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user