Files
Jyotisha/frontend/src/lib/report-candidate-range.ts
T
Jesse_ChenandClaude Opus 5.5 7f39c9346d feat(consult): adopted segment charts that cannot be determined reach the model as policy and one plain sentence (BUG-1138, T2)
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
2026-10-01 10:15:10 +08:00

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