Keep askable cards after exhaustion, explain each probe, read the adopted credible range in reports and chat, and compare declared periods before the minute grid when the clock is unknown. Co-authored-by: Cursor <cursoragent@cursor.com>
114 lines
3.7 KiB
TypeScript
114 lines
3.7 KiB
TypeScript
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<string, unknown>,
|
|
) => 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<string, unknown>
|
|
: 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<ReportCandidateClockRange | null> {
|
|
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));
|
|
}
|