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, ): 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, }; }