/** * Server-facing candidate plateau helpers. These numbers are for Agent * projection only; they never grant exact-minute confirmation. */ import { RECTIFICATION_POLICY } from "../../rectification-policy.ts"; export function timeToMinutes(value: string): number | null { const match = /^(?:[01]\d|2[0-3]):([0-5]\d)$/.exec(value); if (!match) return null; const hours = Number(value.slice(0, 2)); const minutes = Number(match[1]); return hours * 60 + minutes; } /** * Width of the indistinguishable top cluster, in minutes. * Uses the engine's tied_minute_count and the inclusive span of public times. */ export function indistinguishableWidthMinutes( candidates: readonly Readonly<{ time: string; rank: number; tiedMinuteCount: number; }>[], ): number { if (candidates.length === 0) return 0; const ranked = [...candidates].sort((left, right) => left.rank - right.rank); const top = ranked[0]!; const minutes = ranked .map((candidate) => timeToMinutes(candidate.time)) .filter((value): value is number => value !== null); const span = minutes.length === 0 ? 0 : Math.max(...minutes) - Math.min(...minutes) + 1; return Math.max(top.tiedMinuteCount, span, 1); } export function confirmationAllowedForWidth( storedConfirmationAllowed: boolean, widthMinutes: number, ): boolean { return storedConfirmationAllowed && widthMinutes <= RECTIFICATION_POLICY.maxConfirmationWidthMinutes; }