Files
Jyotisha/frontend/src/lib/declared-window-chart.ts
T
Jesse_Chen 9958e00abc fix(web): keep unrectified birth data usable without inventing a minute
Rectification stays optional. Reported minutes can consult and generate reports; date-plus-period uses a declared window instead of a midpoint or 00:00. Updates BUG-341.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-21 15:29:58 +08:00

86 lines
3.0 KiB
TypeScript

import { z } from "zod";
import type { DeclaredBirthWindowConsultation } from "./consultation-route-service.ts";
const clockSchema = z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/);
export const declaredWindowChartPacketSchema = z.object({
declared_range: z.object({
start: clockSchema,
end: clockSchema,
wraps_midnight: z.boolean(),
}),
probe_count: z.number().int().min(2).max(4),
probes: z.array(z.object({
clock: clockSchema,
role: z.enum(["range_start", "interior", "range_end"]),
}).strict()).min(2).max(4),
stable_layers: z.object({
planet_signs: z.record(z.string()),
moon_nakshatra: z.string().optional(),
ascendant_sign: z.string().optional(),
house_cusp_signs: z.record(z.string()).optional(),
}).passthrough(),
varying_layers: z.object({
planet_signs: z.record(z.array(z.string())).optional(),
moon_nakshatra: z.array(z.string()).optional(),
ascendant_signs: z.array(z.string()).optional(),
house_cusp_signs: z.record(z.array(z.string())).optional(),
}).passthrough(),
blocked_layers: z.array(z.string()),
answer_policy: z.object({
can_answer_direction: z.boolean(),
can_answer_precise_timing: z.literal(false),
birth_time_confidence: z.literal("declared_window"),
candidate_is_confirmed: z.literal(false),
}).passthrough(),
result_hash: z.string().min(1),
}).passthrough();
export type DeclaredWindowChartPacket = z.infer<typeof declaredWindowChartPacketSchema>;
type LoadDeclaredWindowChartInput = Readonly<{
window: DeclaredBirthWindowConsultation;
fetchImpl?: typeof fetch;
apiBase?: string;
signal?: AbortSignal;
}>;
export async function fetchDeclaredWindowChart(
input: LoadDeclaredWindowChartInput,
): Promise<DeclaredWindowChartPacket> {
const fetchImpl = input.fetchImpl ?? fetch;
const apiBase = input.apiBase ?? process.env.JYOTISH_API_BASE ?? "http://127.0.0.1:5200";
const { toolInput } = input.window;
const response = await fetchImpl(`${apiBase}/api/declared_window_chart`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
year: toolInput.year,
month: toolInput.month,
day: toolInput.day,
lat: toolInput.lat,
lon: toolInput.lon,
tz: toolInput.tz,
range_start: toolInput.rangeStart,
range_end: toolInput.rangeEnd,
}),
cache: "no-store",
signal: input.signal,
});
if (!response.ok) {
throw new Error("declared_window_chart_unavailable");
}
const payload = await response.json().catch(() => null);
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
throw new Error("declared_window_chart_unavailable");
}
const record = payload as Record<string, unknown>;
if (record.success !== true || record.endpoint !== "declared_window_chart") {
throw new Error("declared_window_chart_unavailable");
}
if ("hour" in record || "minute" in record || "birth_time" in record) {
throw new Error("declared_window_chart_unavailable");
}
return declaredWindowChartPacketSchema.parse(record.packet);
}