import { z } from "zod"; export const FACT_TABLE_GROUPS = ["vimshottari", "ashtakavarga", "shadbala", "functional_roles", "avasthas", "special_points", "sade_sati", "annual"] as const; const sourcePath = z.string().min(1).max(400).regex(/^[A-Za-z0-9_.\[\]-]+$/); const gridShape = { sourcePath, note: z.string().max(500), columns: z.array(z.string().min(1).max(80)).min(1).max(16), rows: z.array(z.object({ sourcePath, cells: z.array(z.union([z.string().max(300), z.number().finite(), z.boolean(), z.null()])).min(1).max(16), }).strict()).max(2000), }; const within = (child: string, parent: string) => child === parent || child.startsWith(`${parent}.`) || child.startsWith(`${parent}[`); function validateRows(table: { sourcePath: string; columns: string[]; rows: { sourcePath: string; cells: unknown[] }[] }, context: z.RefinementCtx) { table.rows.forEach((row, index) => { if (row.cells.length !== table.columns.length) context.addIssue({ code: "custom", path: ["rows", index], message: "fact_table_cell_count" }); if (!within(row.sourcePath, table.sourcePath)) context.addIssue({ code: "custom", path: ["rows", index, "sourcePath"], message: "fact_table_source_mismatch" }); }); } export const reportFactSubtableSchema = z.object({ id: z.string().min(1).max(80).regex(/^[a-z][a-z0-9_-]*$/), title: z.string().min(1).max(120), ...gridShape, }).strict().superRefine(validateRows); /** Optional subtables are additive: persisted v2 snapshots keep their original rows. */ export const reportFactTableSchema = z.object({ id: z.enum(FACT_TABLE_GROUPS), title: z.string().min(1).max(120), claimStatus: z.enum(["multi_system_consensus", "single_system_inference", "parameter_sensitive", "unclosed_divisional_chart", "user_history_verification_required", "blocked"]), ...gridShape, subtables: z.array(reportFactSubtableSchema).max(8).optional(), }).strict().superRefine((table, context) => { validateRows(table, context); const subtables = table.subtables ?? []; if (new Set(subtables.map(subtable => subtable.id)).size !== subtables.length) context.addIssue({ code: "custom", path: ["subtables"], message: "duplicate_fact_subtable" }); subtables.forEach((subtable, index) => { if (!within(subtable.sourcePath, table.sourcePath)) context.addIssue({ code: "custom", path: ["subtables", index, "sourcePath"], message: "fact_table_source_mismatch" }); }); }); export const reportFactTablesSchema = z.array(reportFactTableSchema).max(8).superRefine((tables, context) => { if (new Set(tables.map((table) => table.id)).size !== tables.length) context.addIssue({ code: "custom", message: "duplicate_fact_table" }); }); export type ReportFactTable = z.infer; export type ReportFactSubtable = z.infer;