Files
Jyotisha/frontend/src/lib/chart-view-contract.ts
T
jesse-uxandClaude Code 1420471ab1
Independent Staging Quality Gate / validate (push) Successful in 10m34s
Independent Staging Quality Gate / publish (push) Successful in 3m36s
feat: add chart waiting states and report block exports
Add bounded chart loading, per-layer retry states, and shared SVG skeletons. Add report block downloads with SVG, localized metadata, and inline deletion.

Record verification and retain CRLF export, full-build, and controlled-device acceptance blockers. User authorized staging delivery with these gaps documented.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 12:33:09 +08:00

258 lines
8.3 KiB
TypeScript

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<typeof chartViewFailureStatusSchema>;
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<typeof chartViewOkSchema>;
export type ChartViewMessage = z.infer<typeof chartViewMessageSchema>;
export type ChartViewResponse = z.infer<typeof chartViewResponseSchema>;
export type ChartViewUnavailableLayer = z.infer<typeof chartViewUnavailableLayerSchema>;
export type ChartViewWesternOk = z.infer<typeof chartViewWesternOkSchema>;
export type ChartViewQizhengOk = z.infer<typeof chartViewQizhengOkSchema>;
export type ChartViewVarga = z.infer<typeof chartViewVargaSchema>;
export type ChartViewPlanetRow = z.infer<typeof chartViewPlanetRowSchema>;
export type NorthIndianChart = z.infer<typeof northIndianChartSchema>;
export type ChartViewAscendantRow = z.infer<typeof chartViewAscendantRowSchema>;
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"];