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:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
@@ -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);
});
});