team rating
This commit is contained in:
181
core/racing/domain/entities/TeamRatingEvent.ts
Normal file
181
core/racing/domain/entities/TeamRatingEvent.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import type { IEntity } from '@core/shared/domain';
|
||||
import { TeamRatingEventId } from '../value-objects/TeamRatingEventId';
|
||||
import { TeamRatingDimensionKey } from '../value-objects/TeamRatingDimensionKey';
|
||||
import { TeamRatingDelta } from '../value-objects/TeamRatingDelta';
|
||||
import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';
|
||||
|
||||
export interface TeamRatingEventSource {
|
||||
type: 'race' | 'penalty' | 'vote' | 'adminAction' | 'manualAdjustment';
|
||||
id?: string; // e.g., raceId, penaltyId, voteId
|
||||
}
|
||||
|
||||
export interface TeamRatingEventReason {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface TeamRatingEventVisibility {
|
||||
public: boolean;
|
||||
}
|
||||
|
||||
export interface TeamRatingEventProps {
|
||||
id: TeamRatingEventId;
|
||||
teamId: string;
|
||||
dimension: TeamRatingDimensionKey;
|
||||
delta: TeamRatingDelta;
|
||||
weight?: number;
|
||||
occurredAt: Date;
|
||||
createdAt: Date;
|
||||
source: TeamRatingEventSource;
|
||||
reason: TeamRatingEventReason;
|
||||
visibility: TeamRatingEventVisibility;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export class TeamRatingEvent implements IEntity<TeamRatingEventId> {
|
||||
readonly id: TeamRatingEventId;
|
||||
readonly teamId: string;
|
||||
readonly dimension: TeamRatingDimensionKey;
|
||||
readonly delta: TeamRatingDelta;
|
||||
readonly weight: number | undefined;
|
||||
readonly occurredAt: Date;
|
||||
readonly createdAt: Date;
|
||||
readonly source: TeamRatingEventSource;
|
||||
readonly reason: TeamRatingEventReason;
|
||||
readonly visibility: TeamRatingEventVisibility;
|
||||
readonly version: number;
|
||||
|
||||
private constructor(props: TeamRatingEventProps) {
|
||||
this.id = props.id;
|
||||
this.teamId = props.teamId;
|
||||
this.dimension = props.dimension;
|
||||
this.delta = props.delta;
|
||||
this.weight = props.weight;
|
||||
this.occurredAt = props.occurredAt;
|
||||
this.createdAt = props.createdAt;
|
||||
this.source = props.source;
|
||||
this.reason = props.reason;
|
||||
this.visibility = props.visibility;
|
||||
this.version = props.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new TeamRatingEvent.
|
||||
*/
|
||||
static create(props: {
|
||||
id: TeamRatingEventId;
|
||||
teamId: string;
|
||||
dimension: TeamRatingDimensionKey;
|
||||
delta: TeamRatingDelta;
|
||||
weight?: number;
|
||||
occurredAt: Date;
|
||||
createdAt: Date;
|
||||
source: TeamRatingEventSource;
|
||||
reason: TeamRatingEventReason;
|
||||
visibility: TeamRatingEventVisibility;
|
||||
version: number;
|
||||
}): TeamRatingEvent {
|
||||
// Validate required fields
|
||||
if (!props.teamId || props.teamId.trim().length === 0) {
|
||||
throw new RacingDomainValidationError('Team ID is required');
|
||||
}
|
||||
|
||||
if (!props.dimension) {
|
||||
throw new RacingDomainValidationError('Dimension is required');
|
||||
}
|
||||
|
||||
if (!props.delta) {
|
||||
throw new RacingDomainValidationError('Delta is required');
|
||||
}
|
||||
|
||||
if (!props.source) {
|
||||
throw new RacingDomainValidationError('Source is required');
|
||||
}
|
||||
|
||||
if (!props.reason) {
|
||||
throw new RacingDomainValidationError('Reason is required');
|
||||
}
|
||||
|
||||
if (!props.visibility) {
|
||||
throw new RacingDomainValidationError('Visibility is required');
|
||||
}
|
||||
|
||||
if (props.weight !== undefined && (typeof props.weight !== 'number' || props.weight <= 0)) {
|
||||
throw new RacingDomainValidationError('Weight must be a positive number if provided');
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
if (props.occurredAt > now) {
|
||||
throw new RacingDomainValidationError('Occurrence date cannot be in the future');
|
||||
}
|
||||
|
||||
if (props.createdAt > now) {
|
||||
throw new RacingDomainValidationError('Creation date cannot be in the future');
|
||||
}
|
||||
|
||||
if (props.version < 1) {
|
||||
throw new RacingDomainValidationError('Version must be at least 1');
|
||||
}
|
||||
|
||||
// Validate invariants
|
||||
if (props.dimension.value === 'adminTrust' && props.source.type === 'race') {
|
||||
throw new RacingDomainInvariantError(
|
||||
'adminTrust dimension cannot be updated from race events'
|
||||
);
|
||||
}
|
||||
|
||||
if (props.dimension.value === 'driving' && props.source.type === 'vote') {
|
||||
throw new RacingDomainInvariantError(
|
||||
'driving dimension cannot be updated from vote events'
|
||||
);
|
||||
}
|
||||
|
||||
return new TeamRatingEvent(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rehydrate event from stored data (assumes data is already validated).
|
||||
*/
|
||||
static rehydrate(props: {
|
||||
id: TeamRatingEventId;
|
||||
teamId: string;
|
||||
dimension: TeamRatingDimensionKey;
|
||||
delta: TeamRatingDelta;
|
||||
weight?: number;
|
||||
occurredAt: Date;
|
||||
createdAt: Date;
|
||||
source: TeamRatingEventSource;
|
||||
reason: TeamRatingEventReason;
|
||||
visibility: TeamRatingEventVisibility;
|
||||
version: number;
|
||||
}): TeamRatingEvent {
|
||||
// Rehydration assumes data is already validated (from persistence)
|
||||
return new TeamRatingEvent(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare with another event.
|
||||
*/
|
||||
equals(other: IEntity<TeamRatingEventId>): boolean {
|
||||
return this.id.equals(other.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return plain object representation for serialization.
|
||||
*/
|
||||
toJSON(): object {
|
||||
return {
|
||||
id: this.id.value,
|
||||
teamId: this.teamId,
|
||||
dimension: this.dimension.value,
|
||||
delta: this.delta.value,
|
||||
weight: this.weight,
|
||||
occurredAt: this.occurredAt.toISOString(),
|
||||
createdAt: this.createdAt.toISOString(),
|
||||
source: this.source,
|
||||
reason: this.reason,
|
||||
visibility: this.visibility,
|
||||
version: this.version,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user