Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
calculateTeamResult,
|
||||
calculateRankings,
|
||||
getCompletionStatus,
|
||||
calculateAllResults,
|
||||
} from '../aggregation.js';
|
||||
import { Score, Team, TeamResult } from '../../shared/types.js';
|
||||
|
||||
function makeScore(jurorId: string, teamId: string, criterionId: string, value: number): Score {
|
||||
return { jurorId, teamId, criterionId, value, updatedAt: new Date().toISOString() };
|
||||
}
|
||||
|
||||
describe('calculateTeamResult', () => {
|
||||
it('returns zero total and average when no scores exist', () => {
|
||||
const result = calculateTeamResult('team-a', 'Team A', [], 5);
|
||||
expect(result.total).toBe(0);
|
||||
expect(result.average).toBe(0);
|
||||
expect(result.jurorCount).toBe(0);
|
||||
expect(result.complete).toBe(false);
|
||||
});
|
||||
|
||||
it('calculates total as sum of all score values for the team', () => {
|
||||
const scores: Score[] = [
|
||||
makeScore('juror1', 'team-a', 'c1', 8),
|
||||
makeScore('juror1', 'team-a', 'c2', 7),
|
||||
makeScore('juror2', 'team-a', 'c1', 9),
|
||||
makeScore('juror1', 'team-b', 'c1', 10), // different team, should be excluded
|
||||
];
|
||||
const result = calculateTeamResult('team-a', 'Team A', scores, 2);
|
||||
expect(result.total).toBe(8 + 7 + 9);
|
||||
});
|
||||
|
||||
it('counts only jurors who submitted all criteria', () => {
|
||||
const scores: Score[] = [
|
||||
makeScore('juror1', 'team-a', 'c1', 8),
|
||||
makeScore('juror1', 'team-a', 'c2', 7),
|
||||
makeScore('juror2', 'team-a', 'c1', 9), // only 1 of 2 criteria
|
||||
];
|
||||
const result = calculateTeamResult('team-a', 'Team A', scores, 2);
|
||||
expect(result.jurorCount).toBe(1); // only juror1 has all 2 criteria
|
||||
});
|
||||
|
||||
it('calculates average as total / jurorCount', () => {
|
||||
const scores: Score[] = [
|
||||
makeScore('juror1', 'team-a', 'c1', 8),
|
||||
makeScore('juror1', 'team-a', 'c2', 6),
|
||||
makeScore('juror2', 'team-a', 'c1', 10),
|
||||
makeScore('juror2', 'team-a', 'c2', 4),
|
||||
];
|
||||
const result = calculateTeamResult('team-a', 'Team A', scores, 2);
|
||||
// total = 28, jurorCount = 2, average = 14
|
||||
expect(result.total).toBe(28);
|
||||
expect(result.jurorCount).toBe(2);
|
||||
expect(result.average).toBe(14);
|
||||
});
|
||||
|
||||
it('marks complete when all jurors with scores have all criteria', () => {
|
||||
const scores: Score[] = [
|
||||
makeScore('juror1', 'team-a', 'c1', 8),
|
||||
makeScore('juror1', 'team-a', 'c2', 7),
|
||||
makeScore('juror2', 'team-a', 'c1', 9),
|
||||
makeScore('juror2', 'team-a', 'c2', 6),
|
||||
];
|
||||
const result = calculateTeamResult('team-a', 'Team A', scores, 2);
|
||||
expect(result.complete).toBe(true);
|
||||
});
|
||||
|
||||
it('marks incomplete when some jurors have partial scores', () => {
|
||||
const scores: Score[] = [
|
||||
makeScore('juror1', 'team-a', 'c1', 8),
|
||||
makeScore('juror1', 'team-a', 'c2', 7),
|
||||
makeScore('juror2', 'team-a', 'c1', 9), // missing c2
|
||||
];
|
||||
const result = calculateTeamResult('team-a', 'Team A', scores, 2);
|
||||
expect(result.complete).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateRankings', () => {
|
||||
it('sorts by total descending and assigns ranks', () => {
|
||||
const results: TeamResult[] = [
|
||||
{ teamId: 'a', teamName: 'A', total: 200, average: 0, stddev: 0, rank: 0, jurorCount: 0, complete: false },
|
||||
{ teamId: 'b', teamName: 'B', total: 250, average: 0, stddev: 0, rank: 0, jurorCount: 0, complete: false },
|
||||
{ teamId: 'c', teamName: 'C', total: 230, average: 0, stddev: 0, rank: 0, jurorCount: 0, complete: false },
|
||||
];
|
||||
const ranked = calculateRankings(results);
|
||||
expect(ranked[0].teamId).toBe('b');
|
||||
expect(ranked[0].rank).toBe(1);
|
||||
expect(ranked[1].teamId).toBe('c');
|
||||
expect(ranked[1].rank).toBe(2);
|
||||
expect(ranked[2].teamId).toBe('a');
|
||||
expect(ranked[2].rank).toBe(3);
|
||||
});
|
||||
|
||||
it('assigns same rank to ties', () => {
|
||||
const results: TeamResult[] = [
|
||||
{ teamId: 'a', teamName: 'A', total: 250, average: 0, stddev: 0, rank: 0, jurorCount: 0, complete: false },
|
||||
{ teamId: 'b', teamName: 'B', total: 240, average: 0, stddev: 0, rank: 0, jurorCount: 0, complete: false },
|
||||
{ teamId: 'c', teamName: 'C', total: 240, average: 0, stddev: 0, rank: 0, jurorCount: 0, complete: false },
|
||||
{ teamId: 'd', teamName: 'D', total: 230, average: 0, stddev: 0, rank: 0, jurorCount: 0, complete: false },
|
||||
];
|
||||
const ranked = calculateRankings(results);
|
||||
expect(ranked[0].rank).toBe(1); // 250
|
||||
expect(ranked[1].rank).toBe(2); // 240
|
||||
expect(ranked[2].rank).toBe(2); // 240 (tie)
|
||||
expect(ranked[3].rank).toBe(4); // 230
|
||||
});
|
||||
|
||||
it('handles empty input', () => {
|
||||
const ranked = calculateRankings([]);
|
||||
expect(ranked).toEqual([]);
|
||||
});
|
||||
|
||||
it('handles single team', () => {
|
||||
const results: TeamResult[] = [
|
||||
{ teamId: 'a', teamName: 'A', total: 100, average: 0, stddev: 0, rank: 0, jurorCount: 0, complete: false },
|
||||
];
|
||||
const ranked = calculateRankings(results);
|
||||
expect(ranked[0].rank).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCompletionStatus', () => {
|
||||
it('returns pending when no scores exist', () => {
|
||||
expect(getCompletionStatus('juror1', 'team-a', [], 5)).toBe('pending');
|
||||
});
|
||||
|
||||
it('returns pending when scores exist for other juror/team', () => {
|
||||
const scores: Score[] = [
|
||||
makeScore('juror2', 'team-a', 'c1', 8),
|
||||
makeScore('juror1', 'team-b', 'c1', 7),
|
||||
];
|
||||
expect(getCompletionStatus('juror1', 'team-a', scores, 5)).toBe('pending');
|
||||
});
|
||||
|
||||
it('returns partial when some but not all criteria scored', () => {
|
||||
const scores: Score[] = [
|
||||
makeScore('juror1', 'team-a', 'c1', 8),
|
||||
makeScore('juror1', 'team-a', 'c2', 7),
|
||||
];
|
||||
expect(getCompletionStatus('juror1', 'team-a', scores, 5)).toBe('partial');
|
||||
});
|
||||
|
||||
it('returns complete when all criteria scored', () => {
|
||||
const scores: Score[] = [
|
||||
makeScore('juror1', 'team-a', 'c1', 8),
|
||||
makeScore('juror1', 'team-a', 'c2', 7),
|
||||
makeScore('juror1', 'team-a', 'c3', 9),
|
||||
makeScore('juror1', 'team-a', 'c4', 6),
|
||||
makeScore('juror1', 'team-a', 'c5', 10),
|
||||
];
|
||||
expect(getCompletionStatus('juror1', 'team-a', scores, 5)).toBe('complete');
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateAllResults', () => {
|
||||
it('calculates results for all teams with rankings', () => {
|
||||
const teams: Team[] = [
|
||||
{ id: 'team-a', number: 1, name: 'Team A', description: '' },
|
||||
{ id: 'team-b', number: 2, name: 'Team B', description: '' },
|
||||
];
|
||||
const scores: Score[] = [
|
||||
makeScore('juror1', 'team-a', 'c1', 8),
|
||||
makeScore('juror1', 'team-a', 'c2', 7),
|
||||
makeScore('juror1', 'team-b', 'c1', 10),
|
||||
makeScore('juror1', 'team-b', 'c2', 9),
|
||||
];
|
||||
const results = calculateAllResults(scores, teams, 2);
|
||||
expect(results[0].teamId).toBe('team-b'); // higher total (19)
|
||||
expect(results[0].rank).toBe(1);
|
||||
expect(results[0].total).toBe(19);
|
||||
expect(results[1].teamId).toBe('team-a'); // lower total (15)
|
||||
expect(results[1].rank).toBe(2);
|
||||
expect(results[1].total).toBe(15);
|
||||
});
|
||||
|
||||
it('handles empty scores', () => {
|
||||
const teams: Team[] = [
|
||||
{ id: 'team-a', number: 1, name: 'Team A', description: '' },
|
||||
];
|
||||
const results = calculateAllResults([], teams, 5);
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].total).toBe(0);
|
||||
expect(results[0].rank).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user