290 lines
11 KiB
TypeScript
290 lines
11 KiB
TypeScript
import { TeamRatingEvent } from '../entities/TeamRatingEvent';
|
|
import { TeamRatingDelta } from '../value-objects/TeamRatingDelta';
|
|
import { TeamRatingDimensionKey } from '../value-objects/TeamRatingDimensionKey';
|
|
import { TeamRatingEventId } from '../value-objects/TeamRatingEventId';
|
|
import { TeamRatingValue } from '../value-objects/TeamRatingValue';
|
|
import { TeamRatingSnapshotCalculator } from './TeamRatingSnapshotCalculator';
|
|
|
|
describe('TeamRatingSnapshotCalculator', () => {
|
|
describe('calculate', () => {
|
|
it('should return default ratings for empty events', () => {
|
|
const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', []);
|
|
|
|
expect(snapshot.teamId).toBe('team-123');
|
|
expect(snapshot.driving.value).toBe(50);
|
|
expect(snapshot.adminTrust.value).toBe(50);
|
|
expect(snapshot.overall).toBe(50);
|
|
expect(snapshot.eventCount).toBe(0);
|
|
});
|
|
|
|
it('should calculate single dimension rating', () => {
|
|
const events = [
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(10),
|
|
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
|
createdAt: new Date('2024-01-01T10:00:00Z'),
|
|
source: { type: 'race', id: 'race-1' },
|
|
reason: { code: 'RACE_FINISH', description: 'Finished 1st' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
];
|
|
|
|
const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', events);
|
|
|
|
expect(snapshot.driving.value).toBe(60); // 50 + 10
|
|
expect(snapshot.adminTrust.value).toBe(50); // Default
|
|
expect(snapshot.overall).toBeCloseTo(57, 1); // 60 * 0.7 + 50 * 0.3 = 57
|
|
});
|
|
|
|
it('should calculate multiple events with weights', () => {
|
|
const events = [
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(10),
|
|
weight: 1,
|
|
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
|
createdAt: new Date('2024-01-01T10:00:00Z'),
|
|
source: { type: 'race', id: 'race-1' },
|
|
reason: { code: 'RACE_FINISH', description: 'Finished 1st' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(-5),
|
|
weight: 2,
|
|
occurredAt: new Date('2024-01-01T11:00:00Z'),
|
|
createdAt: new Date('2024-01-01T11:00:00Z'),
|
|
source: { type: 'race', id: 'race-2' },
|
|
reason: { code: 'RACE_PENALTY', description: 'Incident penalty' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
];
|
|
|
|
const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', events);
|
|
|
|
// Weighted average: (10*1 + (-5)*2) / (1+2) = 0/3 = 0
|
|
// So driving = 50 + 0 = 50
|
|
expect(snapshot.driving.value).toBe(50);
|
|
});
|
|
|
|
it('should calculate mixed dimensions', () => {
|
|
const events = [
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(15),
|
|
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
|
createdAt: new Date('2024-01-01T10:00:00Z'),
|
|
source: { type: 'race', id: 'race-1' },
|
|
reason: { code: 'RACE_FINISH', description: 'Finished 1st' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('adminTrust'),
|
|
delta: TeamRatingDelta.create(5),
|
|
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
|
createdAt: new Date('2024-01-01T10:00:00Z'),
|
|
source: { type: 'adminAction', id: 'action-1' },
|
|
reason: { code: 'ADMIN_BONUS', description: 'Helpful admin work' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
];
|
|
|
|
const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', events);
|
|
|
|
expect(snapshot.driving.value).toBe(65); // 50 + 15
|
|
expect(snapshot.adminTrust.value).toBe(55); // 50 + 5
|
|
expect(snapshot.overall).toBeCloseTo(62, 1); // 65 * 0.7 + 55 * 0.3 = 62
|
|
});
|
|
|
|
it('should clamp values between 0 and 100', () => {
|
|
const events = [
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(60), // Would make it 110
|
|
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
|
createdAt: new Date('2024-01-01T10:00:00Z'),
|
|
source: { type: 'race', id: 'race-1' },
|
|
reason: { code: 'RACE_FINISH', description: 'Finished 1st' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
];
|
|
|
|
const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', events);
|
|
|
|
expect(snapshot.driving.value).toBe(100); // Clamped
|
|
});
|
|
|
|
it('should track last updated date', () => {
|
|
const events = [
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(5),
|
|
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
|
createdAt: new Date('2024-01-01T10:00:00Z'),
|
|
source: { type: 'race', id: 'race-1' },
|
|
reason: { code: 'RACE_FINISH', description: 'Finished 1st' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(3),
|
|
occurredAt: new Date('2024-01-02T10:00:00Z'),
|
|
createdAt: new Date('2024-01-02T10:00:00Z'),
|
|
source: { type: 'race', id: 'race-2' },
|
|
reason: { code: 'RACE_FINISH', description: 'Finished 2nd' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
];
|
|
|
|
const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', events);
|
|
|
|
expect(snapshot.lastUpdated).toEqual(new Date('2024-01-02T10:00:00Z'));
|
|
expect(snapshot.eventCount).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('calculateDimensionChange', () => {
|
|
it('should calculate net change for a dimension', () => {
|
|
const events = [
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(10),
|
|
weight: 1,
|
|
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
|
createdAt: new Date('2024-01-01T10:00:00Z'),
|
|
source: { type: 'race', id: 'race-1' },
|
|
reason: { code: 'RACE_FINISH', description: 'Finished 1st' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(-5),
|
|
weight: 2,
|
|
occurredAt: new Date('2024-01-01T11:00:00Z'),
|
|
createdAt: new Date('2024-01-01T11:00:00Z'),
|
|
source: { type: 'race', id: 'race-2' },
|
|
reason: { code: 'RACE_PENALTY', description: 'Incident penalty' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
];
|
|
|
|
const change = TeamRatingSnapshotCalculator.calculateDimensionChange(
|
|
TeamRatingDimensionKey.create('driving'),
|
|
events
|
|
);
|
|
|
|
// (10*1 + (-5)*2) / (1+2) = 0/3 = 0
|
|
expect(change).toBe(0);
|
|
});
|
|
|
|
it('should return 0 for no events', () => {
|
|
const change = TeamRatingSnapshotCalculator.calculateDimensionChange(
|
|
TeamRatingDimensionKey.create('driving'),
|
|
[]
|
|
);
|
|
|
|
expect(change).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('calculateOverWindow', () => {
|
|
it('should calculate ratings for a time window', () => {
|
|
const allEvents = [
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(10),
|
|
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
|
createdAt: new Date('2024-01-01T10:00:00Z'),
|
|
source: { type: 'race', id: 'race-1' },
|
|
reason: { code: 'RACE_FINISH', description: 'Finished 1st' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
TeamRatingEvent.create({
|
|
id: TeamRatingEventId.generate(),
|
|
teamId: 'team-123',
|
|
dimension: TeamRatingDimensionKey.create('driving'),
|
|
delta: TeamRatingDelta.create(5),
|
|
occurredAt: new Date('2024-01-02T10:00:00Z'),
|
|
createdAt: new Date('2024-01-02T10:00:00Z'),
|
|
source: { type: 'race', id: 'race-2' },
|
|
reason: { code: 'RACE_FINISH', description: 'Finished 2nd' },
|
|
visibility: { public: true },
|
|
version: 1,
|
|
}),
|
|
];
|
|
|
|
const snapshot = TeamRatingSnapshotCalculator.calculateOverWindow(
|
|
'team-123',
|
|
allEvents,
|
|
new Date('2024-01-01T00:00:00Z'),
|
|
new Date('2024-01-01T23:59:59Z')
|
|
);
|
|
|
|
// Only first event in window
|
|
expect(snapshot.driving.value).toBe(60); // 50 + 10
|
|
expect(snapshot.eventCount).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('calculateDelta', () => {
|
|
it('should calculate differences between snapshots', () => {
|
|
const before = {
|
|
teamId: 'team-123',
|
|
driving: TeamRatingValue.create(50),
|
|
adminTrust: TeamRatingValue.create(50),
|
|
overall: 50,
|
|
lastUpdated: new Date('2024-01-01'),
|
|
eventCount: 10,
|
|
};
|
|
|
|
const after = {
|
|
teamId: 'team-123',
|
|
driving: TeamRatingValue.create(65),
|
|
adminTrust: TeamRatingValue.create(55),
|
|
overall: 62,
|
|
lastUpdated: new Date('2024-01-02'),
|
|
eventCount: 15,
|
|
};
|
|
|
|
const delta = TeamRatingSnapshotCalculator.calculateDelta(before, after);
|
|
|
|
expect(delta.driving).toBe(15);
|
|
expect(delta.adminTrust).toBe(5);
|
|
expect(delta.overall).toBe(12);
|
|
});
|
|
});
|
|
}); |