import { resolveAyanamsa } from "./ayanamsa.ts"; import { chartViewMessageSchema, type ChartViewResponse, } from "./chart-view-contract.ts"; import { enginePayload, logEngineCallFailure, type ChartViewLayer, type EngineCallResult, } from "./chart-view-engine.ts"; import { CHART_VIEW_COPY, VARGA_CHIP_ORDER } from "./chart-view-labels.ts"; import { buildChartView, engineChartHasPlanets, type ChartViewProfileInput, } from "./chart-view-mapper.ts"; export type ChartViewEnginePost = ( path: string, body: Record, ) => Promise; export type ChartViewLoadInput = { userId: string | null; profile: ChartViewProfileInput | null; postEngine: ChartViewEnginePost; asOf: string; rateLimited?: boolean; layers?: readonly ChartViewLayer[]; }; function message( status: "unauthenticated" | "birth_profile_incomplete" | "chart_unavailable" | "rate_limited", copy: string, ): ChartViewResponse { return chartViewMessageSchema.parse({ status, billed: false, message: copy }); } function birthPayload(profile: ChartViewProfileInput): Record { const [year, month, day] = profile.date.split("-").map(Number); const [hour, minute] = profile.time.split(":").map(Number); return { year, month, day, hour, minute, second: 0, lat: profile.latitude, lon: profile.longitude, tz: profile.timezoneOffset, latitude: profile.latitude, longitude: profile.longitude, timezone: profile.timezoneOffset, ayanamsa: resolveAyanamsa({ ayanamsa: profile.ayanamsa }), node_mode: "mean", house_system: "P", skip_vedastro_main_entry_overview: true, }; } function wantedLayers(layers: readonly ChartViewLayer[] | undefined): Set { return new Set(layers ?? []); } async function callEngine( postEngine: ChartViewEnginePost, path: string, body: Record, ): Promise { const result = await postEngine(path, body); if (result.status !== "ok") logEngineCallFailure(result); return result; } export async function assembleChartView(input: ChartViewLoadInput): Promise<{ httpStatus: number; body: ChartViewResponse; }> { if (!input.userId) { return { httpStatus: 401, body: message("unauthenticated", CHART_VIEW_COPY.unauthenticated), }; } if (input.rateLimited) { return { httpStatus: 429, body: message("rate_limited", CHART_VIEW_COPY.rateLimited), }; } const profile = input.profile; if (!profile) { return { httpStatus: 200, body: message("birth_profile_incomplete", CHART_VIEW_COPY.incomplete), }; } const payload = birthPayload(profile); const chartResult = await callEngine(input.postEngine, "/api/chart", payload); if (chartResult.status !== "ok") { return { httpStatus: 200, body: message( "chart_unavailable", chartResult.status === "busy" ? CHART_VIEW_COPY.busy : CHART_VIEW_COPY.unavailable, ), }; } const chart = chartResult.payload; if (chart.success === false || !engineChartHasPlanets(chart)) { logEngineCallFailure({ status: "bad_payload", path: "/api/chart", elapsedMs: 0, errorName: "engine_rejected", }); return { httpStatus: 200, body: message("chart_unavailable", CHART_VIEW_COPY.unavailable), }; } const planets = chart.planets; const ascendant = chart.ascendant; const houses = chart.houses; const followUp = { ...payload, planets, ascendant, houses, }; const layers = wantedLayers(input.layers); const [varga, chara, western, qizheng] = await Promise.all([ layers.has("varga") ? callEngine(input.postEngine, "/api/varga_full", { ...followUp, divisions: VARGA_CHIP_ORDER.map((division) => `D${division}`), }).then(enginePayload) : Promise.resolve(null), layers.has("chara") ? callEngine(input.postEngine, "/api/dasha/chara", { ...followUp, antardasha: true, mode: "dasha", }).then(enginePayload) : Promise.resolve(null), layers.has("western") ? callEngine(input.postEngine, "/api/western", payload).then(enginePayload) : Promise.resolve(null), layers.has("qizheng") ? callEngine(input.postEngine, "/api/qizheng", payload).then(enginePayload) : Promise.resolve(null), ]); try { const body = buildChartView({ chart, varga, chara, western, qizheng, profile, asOf: input.asOf, }); return { httpStatus: 200, body }; } catch (error) { const errorName = error instanceof Error ? error.name : "Error"; const errorMessage = error instanceof Error ? error.message : "unknown"; console.warn("chart_view_assemble_failed", { errorName, message: errorMessage }); return { httpStatus: 200, body: message("chart_unavailable", CHART_VIEW_COPY.unavailable), }; } }