Pure functions in src/lib/scoring implementing docs/SCORING.md: cell values (override > judge average), weighted raw totals, raw and placement points with tie averaging, and competition-ranked standings. Coverage thresholds are enforced at 100% for the module. Also silences the hydration warning caused by browser extensions modifying <html>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
39 lines
1.3 KiB
TypeScript
39 lines
1.3 KiB
TypeScript
import type { CellResult, ScoreInput, ScoringMode } from "./types";
|
||
|
||
/**
|
||
* SCORING.md §1: the value of one team × criterion cell.
|
||
* `cellScores` are all score rows for that cell; `activeJudgeIds` are the game's non-revoked judges.
|
||
*/
|
||
export function computeCell(
|
||
mode: ScoringMode,
|
||
cellScores: ScoreInput[],
|
||
activeJudgeIds: ReadonlySet<string>,
|
||
): CellResult {
|
||
const adminRow = cellScores.find((score) => score.judgeId === null);
|
||
|
||
if (mode === "direct") {
|
||
// Judge rows are ignored in direct games (e.g. left over from a mode switch).
|
||
return adminRow
|
||
? { value: adminRow.value, source: "admin", judgeAverage: null, judgeCount: 0 }
|
||
: { value: null, source: "none", judgeAverage: null, judgeCount: 0 };
|
||
}
|
||
|
||
// Missing judges are ignored, not counted as zero.
|
||
const judgeValues = cellScores
|
||
.filter((score) => score.judgeId !== null && activeJudgeIds.has(score.judgeId))
|
||
.map((score) => score.value);
|
||
const judgeCount = judgeValues.length;
|
||
const judgeAverage =
|
||
judgeCount > 0 ? judgeValues.reduce((sum, value) => sum + value, 0) / judgeCount : null;
|
||
|
||
if (adminRow) {
|
||
return { value: adminRow.value, source: "override", judgeAverage, judgeCount };
|
||
}
|
||
return {
|
||
value: judgeAverage,
|
||
source: judgeAverage === null ? "none" : "judges",
|
||
judgeAverage,
|
||
judgeCount,
|
||
};
|
||
}
|