The shared range loader attaches unreliableVargas for segment-v1 adoptions; the consultation tool input carries it, runConsultationWorkflow strips it from the engine payload and writes answer_policy.unreliable_vargas, deterministic_claims_forbidden_for and a user-facing limitation. Legacy and unrectified profiles are byte-identical (request, context, packet). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
166 lines
6.7 KiB
TypeScript
166 lines
6.7 KiB
TypeScript
import { loadAdoptedChartTiers } from "./rectification-adopted-chart-tiers.ts";
|
|
import type { TargetChart } from "./rectification-agentic/core/segment-summary.ts";
|
|
|
|
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;
|
|
candidate_intervals?: readonly Readonly<{ start_at: string; end_at: string }>[];
|
|
/** Charts a segment-v1 adoption could not determine (BUG-1138/1139); absent otherwise. */
|
|
unreliableVargas?: readonly TargetChart[];
|
|
}>;
|
|
|
|
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;
|
|
if (row.candidate_intervals !== undefined) {
|
|
if (!Array.isArray(row.candidate_intervals) || !row.candidate_intervals.length) return null;
|
|
const intervals: { start_at: string; end_at: string }[] = [];
|
|
for (const value of row.candidate_intervals) {
|
|
const interval = value as { start_at?: unknown; end_at?: unknown } | null;
|
|
if (!interval || typeof interval.start_at !== "string" || typeof interval.end_at !== "string"
|
|
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(interval.start_at)
|
|
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(interval.end_at)
|
|
|| !Number.isFinite(Date.parse(interval.start_at + "Z"))
|
|
|| !Number.isFinite(Date.parse(interval.end_at + "Z"))
|
|
|| new Date(interval.start_at + "Z").toISOString().slice(0, 16) !== interval.start_at
|
|
|| new Date(interval.end_at + "Z").toISOString().slice(0, 16) !== interval.end_at
|
|
|| interval.end_at < interval.start_at
|
|
|| (intervals.length > 0 && interval.start_at <= intervals.at(-1)!.end_at)) return null;
|
|
intervals.push({ start_at: interval.start_at, end_at: interval.end_at });
|
|
}
|
|
return { startTime: intervals[0].start_at.slice(11, 16), endTime: intervals.at(-1)!.end_at.slice(11, 16), candidate_intervals: intervals };
|
|
}
|
|
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;
|
|
}
|
|
const range = parseReportCandidateRange(result.data);
|
|
if (!range) return null;
|
|
const unreliable = await loadUnreliableVargas(client, input.userId);
|
|
return unreliable.length ? { ...range, unreliableVargas: unreliable } : range;
|
|
} catch (error) {
|
|
warn({
|
|
event: "report_candidate_range_unavailable",
|
|
reason: reportCandidateRangeErrorCode(error),
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
|
|
type ProfileProvenanceQuery = Readonly<{
|
|
select: (columns: string) => Readonly<{
|
|
eq: (column: string, value: string) => Readonly<{ maybeSingle: () => PromiseLike<{ data: unknown; error: unknown }> }>;
|
|
}>;
|
|
}>;
|
|
|
|
/**
|
|
* Report routes and the consultation route share this loader (the consultation
|
|
* route wires it by name), so the adopted chart tiers ride on the range it
|
|
* already returns. Only the service client has `from`; a failure here never
|
|
* drops the range itself.
|
|
*/
|
|
async function loadUnreliableVargas(client: ReportCandidateRangeRpcClient, userId: string): Promise<readonly TargetChart[]> {
|
|
const from = (client as { from?: (table: string) => ProfileProvenanceQuery }).from;
|
|
if (typeof from !== "function") return [];
|
|
try {
|
|
const { data, error } = await from.call(client, "profiles").select("active_birth_provenance").eq("id", userId).maybeSingle();
|
|
if (error || !data || typeof data !== "object") return [];
|
|
const tiers = await loadAdoptedChartTiers(client, { userId, provenance: (data as Record<string, unknown>).active_birth_provenance });
|
|
return tiers?.unreliable ?? [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|