import { z } from "zod"; import { CHART_BIRTH_FAILURE_REASONS } from "./chart-birth-truth.ts"; import { PLANET_GLYPH_KEYS } from "./planet-glyphs.ts"; const isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/); export const chartViewUnavailableLayerSchema = z.object({ status: z.literal("unavailable"), reason: z.string().min(1), copy: z.string().min(1), }); export const northIndianChartSchema = z.object({ houses: z.array(z.object({ houseNumber: z.number().int().min(1).max(12), sign: z.string().min(1), occupants: z.array(z.string()), // The chart draws symbols, so it needs the body and its direction rather // than the formatted label. Optional because the schema also has to accept // house tables that only ever carried display text. occupantGlyphs: z.array(z.object({ key: z.enum(PLANET_GLYPH_KEYS), retrograde: z.boolean(), degree: z.number().gte(0).lt(30), })).optional(), })).length(12), planets: z.array(z.object({ name: z.string().min(1), retrograde: z.boolean(), })).optional(), }); export const chartViewPlanetRowSchema = z.object({ name: z.string().min(1), label: z.string().min(1), sign: z.string().min(1), signLabel: z.string().min(1), degreeInSign: z.number(), house: z.number().int().min(1).max(12), retrograde: z.boolean(), status: z.string().min(1), nakshatra: z.string().min(1), pada: z.number().int().min(1).max(4), nakshatraLord: z.string().min(1), d9Sign: z.string().nullable(), d9SignLabel: z.string().nullable(), symbol: z.string().nullable(), }); /** * The ascendant is a chart point, not a body: the engine returns its sign and * degree but no nakshatra, so those three fields are nullable rather than * filled in with a plausible value. */ export const chartViewAscendantRowSchema = z.object({ label: z.string().min(1), abbr: z.string().min(1), sign: z.string().min(1), signLabel: z.string().min(1), degreeInSign: z.number(), house: z.literal(1), retrograde: z.literal(false), nakshatra: z.string().min(1).nullable(), pada: z.number().int().min(1).max(4).nullable(), nakshatraLord: z.string().min(1).nullable(), }); export const chartViewVargaSchema = z.object({ id: z.string().regex(/^D\d{1,3}$/), title: z.string().min(1), meaning: z.string().min(1), division: z.number().int().positive(), chart: northIndianChartSchema, }); export const chartViewPeriodSchema = z.object({ lord: z.string().min(1), label: z.string().min(1), start: isoDate, end: isoDate, current: z.boolean(), derived: z.boolean(), }); export const chartViewMahadashaSchema = chartViewPeriodSchema.extend({ sign: z.string().nullable(), antardashas: z.array(chartViewPeriodSchema), }); export const chartViewDashaTrackSchema = z.object({ id: z.enum(["vimshottari", "chara"]), title: z.string().min(1), method: z.string().min(1), currentLabel: z.string().min(1), antardashaNote: z.string().nullable(), periods: z.array(chartViewMahadashaSchema), }); export const westernPointSchema = z.object({ id: z.string().min(1), label: z.string().min(1), longitude: z.number(), sign: z.string().min(1), signLabel: z.string().min(1), degreeInSign: z.number(), house: z.number().int().min(1).max(12).nullable(), retrograde: z.boolean(), }); export const chartViewWesternOkSchema = z.object({ status: z.literal("ok"), zodiac: z.literal("tropical"), houseSystem: z.string().min(1), boundary: z.string().min(1), ayanamsaGapNote: z.string().min(1), ascendantLongitude: z.number(), planets: z.array(westernPointSchema), houses: z.array(z.object({ house: z.number().int().min(1).max(12), longitude: z.number(), sign: z.string().min(1), signLabel: z.string().min(1), degreeInSign: z.number(), })).length(12), aspects: z.array(z.object({ left: z.string().min(1), right: z.string().min(1), aspect: z.string().min(1), aspectLabel: z.string().min(1), orb: z.number(), })), elements: z.array(z.object({ id: z.string().min(1), label: z.string().min(1), count: z.number().int().nonnegative(), })), modes: z.array(z.object({ id: z.string().min(1), label: z.string().min(1), count: z.number().int().nonnegative(), })), }); export const chartViewQizhengOkSchema = z.object({ status: z.literal("ok"), coordinateSystem: z.string().min(1), ketuMode: z.string().min(1), boundary: z.string().min(1), palaces: z.array(z.object({ branch: z.string().min(1), lifePalace: z.string().min(1), mansions: z.array(z.string()), occupants: z.array(z.string()), })), bodies: z.array(z.object({ name: z.string().min(1), mansion: z.string().min(1), mansionDegree: z.number(), palace: z.string().min(1), dignity: z.string().min(1), dignityClosed: z.boolean(), })), }); export const chartViewProfileSchema = z.object({ name: z.string().min(1), date: isoDate, time: z.string().regex(/^\d{2}:\d{2}/), placeLabel: z.string().min(1), latitude: z.number(), longitude: z.number(), timezoneOffset: z.number().finite().optional(), timezoneLabel: z.string().min(1), ayanamsa: z.string().min(1), ayanamsaDisplay: z.string().min(1), nodeMode: z.enum(["mean", "true"]), nodeModeLabel: z.string().min(1), birthTimeStatus: z.string().min(1), birthTimeStatusLabel: z.string().min(1), engineName: z.string().min(1), moonNakshatra: z.string().min(1), moonPada: z.number().int().min(1).max(4), ascendantSign: z.string().min(1), ascendantSignLabel: z.string().min(1), currentDashaLabel: z.string().min(1), }); export const chartViewFailureStatusSchema = z.enum([ ...CHART_BIRTH_FAILURE_REASONS, "client_timeout", "unavailable", "unauthenticated", "rate_limited", ]); export type ChartViewFailureStatus = z.infer; export const chartViewOkSchema = z.object({ layerFailures: z.object({ varga: chartViewFailureStatusSchema.optional(), chara: chartViewFailureStatusSchema.optional(), western: chartViewFailureStatusSchema.optional(), qizheng: chartViewFailureStatusSchema.optional(), }).optional(), status: z.literal("ok"), billed: z.literal(false), profile: chartViewProfileSchema, vedic: z.object({ coordinateSystem: z.literal("sidereal"), boundary: z.string().min(1), vargas: z.array(chartViewVargaSchema).min(1), ascendant: chartViewAscendantRowSchema.nullable(), planets: z.array(chartViewPlanetRowSchema).min(1), }), dasha: z.object({ vimshottari: chartViewDashaTrackSchema, chara: chartViewDashaTrackSchema, dualTrackNote: z.string().min(1), }), western: z.union([chartViewWesternOkSchema, chartViewUnavailableLayerSchema]), qizheng: z.union([chartViewQizhengOkSchema, chartViewUnavailableLayerSchema]), accountId: z.string().min(1).optional(), profileFingerprint: z.string().min(1).optional(), }); const chartViewIdentitySchema = { accountId: z.string().min(1).optional(), profileFingerprint: z.string().min(1).optional(), }; export const chartViewMessageSchema = z.object({ status: z.enum(["unauthenticated", "birth_profile_incomplete", "chart_unavailable", "rate_limited", "client_timeout"]), billed: z.literal(false), message: z.string().min(1), reason: z.enum(CHART_BIRTH_FAILURE_REASONS).optional(), ...chartViewIdentitySchema, }); export const chartViewResponseSchema = z.union([chartViewOkSchema, chartViewMessageSchema]); export type ChartViewOk = z.infer; export type ChartViewMessage = z.infer; export type ChartViewResponse = z.infer; export type ChartViewUnavailableLayer = z.infer; export type ChartViewWesternOk = z.infer; export type ChartViewQizhengOk = z.infer; export type ChartViewVarga = z.infer; export type ChartViewPlanetRow = z.infer; export type NorthIndianChart = z.infer; export type ChartViewAscendantRow = z.infer; export const CHART_VIEW_TABS = [ { id: "vedic", label: "星盘" }, { id: "basics", label: "基础信息" }, { id: "dasha", label: "大运" }, { id: "western", label: "西洋盘" }, { id: "qizheng", label: "七政四余" }, ] as const; export type ChartViewTabId = (typeof CHART_VIEW_TABS)[number]["id"];