Files
gridpilot.gg/core/identity/domain/value-objects/DrivingReasonCode.ts
2026-01-16 13:48:18 +01:00

133 lines
3.6 KiB
TypeScript

import type { ValueObject } from '@core/shared/domain';
import { IdentityDomainValidationError } from '../errors/IdentityDomainError';
/**
* Driving Reason Code Value Object
*
* Stable machine codes for driving rating events to support:
* - Filtering and analytics
* - i18n translations
* - Consistent UI explanations
*
* Based on ratings-architecture-concept.md section 5.1.2
*/
export type DrivingReasonCodeValue =
// Performance
| 'DRIVING_FINISH_STRENGTH_GAIN'
| 'DRIVING_POSITIONS_GAINED_BONUS'
| 'DRIVING_PACE_RELATIVE_GAIN'
// Clean driving
| 'DRIVING_INCIDENTS_PENALTY'
| 'DRIVING_MAJOR_CONTACT_PENALTY'
| 'DRIVING_PENALTY_INVOLVEMENT_PENALTY'
// Reliability
| 'DRIVING_DNS_PENALTY'
| 'DRIVING_DNF_PENALTY'
| 'DRIVING_DSQ_PENALTY'
| 'DRIVING_AFK_PENALTY'
| 'DRIVING_SEASON_ATTENDANCE_BONUS';
export interface DrivingReasonCodeProps {
value: DrivingReasonCodeValue;
}
const VALID_REASON_CODES: DrivingReasonCodeValue[] = [
// Performance
'DRIVING_FINISH_STRENGTH_GAIN',
'DRIVING_POSITIONS_GAINED_BONUS',
'DRIVING_PACE_RELATIVE_GAIN',
// Clean driving
'DRIVING_INCIDENTS_PENALTY',
'DRIVING_MAJOR_CONTACT_PENALTY',
'DRIVING_PENALTY_INVOLVEMENT_PENALTY',
// Reliability
'DRIVING_DNS_PENALTY',
'DRIVING_DNF_PENALTY',
'DRIVING_DSQ_PENALTY',
'DRIVING_AFK_PENALTY',
'DRIVING_SEASON_ATTENDANCE_BONUS',
];
export class DrivingReasonCode implements ValueObject<DrivingReasonCodeProps> {
readonly value: DrivingReasonCodeValue;
private constructor(value: DrivingReasonCodeValue) {
this.value = value;
}
static create(value: string): DrivingReasonCode {
if (!value || value.trim().length === 0) {
throw new IdentityDomainValidationError('DrivingReasonCode cannot be empty');
}
const trimmed = value.trim() as DrivingReasonCodeValue;
if (!VALID_REASON_CODES.includes(trimmed)) {
throw new IdentityDomainValidationError(
`Invalid driving reason code: ${value}. Valid options: ${VALID_REASON_CODES.join(', ')}`
);
}
return new DrivingReasonCode(trimmed);
}
static fromValue(value: DrivingReasonCodeValue): DrivingReasonCode {
return new DrivingReasonCode(value);
}
get props(): DrivingReasonCodeProps {
return { value: this.value };
}
equals(other: IValueObject<DrivingReasonCodeProps>): boolean {
return this.value === other.props.value;
}
toString(): string {
return this.value;
}
/**
* Check if this is a performance-related reason code
*/
isPerformance(): boolean {
return this.value === 'DRIVING_FINISH_STRENGTH_GAIN' ||
this.value === 'DRIVING_POSITIONS_GAINED_BONUS' ||
this.value === 'DRIVING_PACE_RELATIVE_GAIN';
}
/**
* Check if this is a clean driving-related reason code
*/
isCleanDriving(): boolean {
return this.value === 'DRIVING_INCIDENTS_PENALTY' ||
this.value === 'DRIVING_MAJOR_CONTACT_PENALTY' ||
this.value === 'DRIVING_PENALTY_INVOLVEMENT_PENALTY';
}
/**
* Check if this is a reliability-related reason code
*/
isReliability(): boolean {
return this.value === 'DRIVING_DNS_PENALTY' ||
this.value === 'DRIVING_DNF_PENALTY' ||
this.value === 'DRIVING_DSQ_PENALTY' ||
this.value === 'DRIVING_AFK_PENALTY' ||
this.value === 'DRIVING_SEASON_ATTENDANCE_BONUS';
}
/**
* Check if this is a penalty (negative impact)
*/
isPenalty(): boolean {
return this.value.endsWith('_PENALTY');
}
/**
* Check if this is a bonus (positive impact)
*/
isBonus(): boolean {
return this.value.endsWith('_BONUS') || this.value.endsWith('_GAIN');
}
}