export const READ_REPORT_CANDIDATE_RANGE_RPC = "read_report_candidate_range"; export const ADOPTED_CREDIBLE_RANGE_SOURCE = "inference_credible_range"; export type ReportCandidateClockRange = Readonly<{ startTime: string; endTime: string; }>; export type AdoptedCredibleRangePayload = Readonly<{ start_time: string; end_time: string; representative_time: string; width_minutes: number; source: typeof ADOPTED_CREDIBLE_RANGE_SOURCE; }>; export type ReportCandidateRangeRpcClient = Readonly<{ rpc: ( fn: string, args: Record, ) => PromiseLike<{ data: unknown; error: unknown }>; }>; export type ReportCandidateRangeWarn = (payload: Readonly<{ event: "report_candidate_range_unavailable"; reason: string; }>) => void; const candidateClockPattern = /^((?:[01]\d|2[0-3]):[0-5]\d)(?::00(?:\.0+)?)?$/; export function reportCandidateRangeErrorCode(error: unknown): string { if (error && typeof error === "object" && "code" in error) { const code = error.code; if (typeof code === "string" && code.trim().length > 0) return code.trim(); } if (error instanceof Error && error.name.trim().length > 0) return error.name; return "unknown"; } export function parseReportCandidateRange(value: unknown): ReportCandidateClockRange | null { const row = value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record : null; if (!row) return null; const startTime = matchClock(row.start_time ?? row.startTime); const endTime = matchClock(row.end_time ?? row.endTime); if (!startTime || !endTime || startTime > endTime) return null; return { startTime, endTime }; } export function adoptedCredibleRangePayload(input: Readonly<{ startTime?: string | null; endTime?: string | null; representativeTime?: string | null; }>): AdoptedCredibleRangePayload | null { const startTime = matchClock(input.startTime); const endTime = matchClock(input.endTime); if (!startTime || !endTime || startTime > endTime) return null; const representativeTime = matchClock(input.representativeTime) ?? startTime; const from = clockMinutes(startTime); const to = clockMinutes(endTime); if (from == null || to == null) return null; const span = to >= from ? to - from : to + 24 * 60 - from; return { start_time: startTime, end_time: endTime, representative_time: representativeTime, width_minutes: span + 1, source: ADOPTED_CREDIBLE_RANGE_SOURCE, }; } export async function loadReportCandidateRange( client: ReportCandidateRangeRpcClient, input: Readonly<{ userId: string; rectificationCaseId?: string | null }>, warn: ReportCandidateRangeWarn = defaultWarn, ): Promise { try { const result = await client.rpc(READ_REPORT_CANDIDATE_RANGE_RPC, { p_user_id: input.userId, p_rectification_case_id: input.rectificationCaseId ?? null, }); if (result.error) { warn({ event: "report_candidate_range_unavailable", reason: reportCandidateRangeErrorCode(result.error), }); return null; } return parseReportCandidateRange(result.data); } catch (error) { warn({ event: "report_candidate_range_unavailable", reason: reportCandidateRangeErrorCode(error), }); return null; } } function matchClock(value: unknown): string | null { if (typeof value !== "string") return null; return value.trim().match(candidateClockPattern)?.[1] ?? null; } function clockMinutes(value: string): number | null { const match = /^(\d{2}):(\d{2})$/.exec(value); if (!match) return null; return Number(match[1]) * 60 + Number(match[2]); } function defaultWarn(payload: Readonly<{ event: string; reason: string }>): void { console.warn(JSON.stringify(payload)); }