import { z } from "zod"; import { REVIEW_TREND_LIMITS, isDiagnosticToken, isDiagnosticPlainRecord, isRealIsoDate, validateReviewTrendFitRequest, validateReviewTrendSelectionRequest, validateReviewTrendApplicationRequest, fitReviewTrend, selectReviewTrendAssumption, applyReviewTrend, replayReviewTrendFit, replayReviewTrendSelection, replayReviewTrendApplication, type ReviewTrendFitRequest, type ReviewTrendSelectionRequest, type ReviewTrendApplicationRequest, type ReviewTrendFit, type ReviewTrendSelection, type ReviewTrendApplication } from "@actuarial-ts/core"; import { analysisSnapshotSchema, assumptionSnapshotSchema, reviewAssumptionDefinitionSchema } from "./reviewContracts.js"; import { reviewOriginSeriesRequestSchema } from "./reviewOriginSeriesContracts.js"; import { parseReviewBoundary } from "./reviewBoundary.js"; const token = z.string().refine(isDiagnosticToken), date = z.string().refine(isRealIsoDate); const clock = z.enum(["calendar-year-fraction", "month-start-years"]); const declaration = z.object({ actorId: token, knownDate: date, rationale: token }).strict(); const base = { id: token, version: token, limits: z.object({ maximumOrigins: z.number().int().positive().max(REVIEW_TREND_LIMITS.maximumOrigins), maximumInputCharacters: z.number().int().positive().max(REVIEW_TREND_LIMITS.maximumInputCharacters), maximumOutputCharacters: z.number().int().positive().max(REVIEW_TREND_LIMITS.maximumOutputCharacters), maximumAssumptions: z.number().int().positive().max(REVIEW_TREND_LIMITS.maximumAssumptions) }).strict() }; // Preserve the complete result for core projection validation and execution replay. const retainedSeries = z.unknown().superRefine((value, context) => { const parsed = z.object({ request: reviewOriginSeriesRequestSchema }).passthrough().safeParse(value); if (!parsed.success) parsed.error.issues.forEach(issue => context.addIssue(issue)); }); export const reviewTrendFitRequestSchema = z.object({ ...base, series: retainedSeries, temporal: z.object({ clock, pointsPerYear: z.union([z.literal(1), z.literal(4), z.literal(12)]), windows: z.literal("usable-observations") }).strict(), comparabilityRationale: token, declaration }).strict(); const retainedFit = z.unknown().superRefine((value, context) => { const parsed = z.object({ request: reviewTrendFitRequestSchema }).passthrough().safeParse(value); if (!parsed.success) parsed.error.issues.forEach(issue => context.addIssue(issue)); }); export const reviewTrendSelectionRequestSchema = z.object({ ...base, fit: retainedFit, window: z.enum(["all", "last5", "last3", "exhilo"]), sourceRelationship: z.enum(["current-data", "separate-data"]), knowledgeDate: date, assumption: reviewAssumptionDefinitionSchema.omit({ contractVersion: true, value: true, provenance: true }), snapshots: z.array(analysisSnapshotSchema) }).strict(); export const reviewTrendApplicationRequestSchema = z.object({ ...base, series: retainedSeries, rate: assumptionSnapshotSchema, snapshots: z.array(analysisSnapshotSchema), clock, target: z.discriminatedUnion("kind", [z.object({ kind: z.literal("common"), levelDate: date }).strict(), z.object({ kind: z.literal("by-origin"), rows: z.array(z.object({ origin: token, levelDate: date }).strict()) }).strict()]), order: z.literal("after-existing-adjustments"), policies: z.object({ unavailable: z.enum(["retain-unavailable", "reject"]), unadoptedAssumption: z.enum(["reject", "allow-qualified"]) }).strict(), declaration }).strict(); export function parseReviewTrendFitRequest(input: unknown): ReviewTrendFitRequest { return validateReviewTrendFitRequest(parseReviewBoundary(reviewTrendFitRequestSchema, input) as ReviewTrendFitRequest).request; } export function parseReviewTrendSelectionRequest(input: unknown): ReviewTrendSelectionRequest { return validateReviewTrendSelectionRequest(parseReviewBoundary(reviewTrendSelectionRequestSchema, input) as ReviewTrendSelectionRequest).request; } export function parseReviewTrendApplicationRequest(input: unknown): ReviewTrendApplicationRequest { return validateReviewTrendApplicationRequest(parseReviewBoundary(reviewTrendApplicationRequestSchema, input) as ReviewTrendApplicationRequest).request; } export function fitReviewTrendFromInput(input: unknown): ReviewTrendFit { return fitReviewTrend(parseReviewTrendFitRequest(input)); } export function selectReviewTrendAssumptionFromInput(input: unknown): ReviewTrendSelection { return selectReviewTrendAssumption(parseReviewTrendSelectionRequest(input)); } export function applyReviewTrendFromInput(input: unknown): ReviewTrendApplication { return applyReviewTrend(parseReviewTrendApplicationRequest(input)); } export function replayReviewTrendFitFromInput(input: unknown): ReviewTrendFit { return replayReviewTrendFit(parseReviewBoundary(z.custom(isDiagnosticPlainRecord), input)); } export function replayReviewTrendSelectionFromInput(input: unknown): ReviewTrendSelection { return replayReviewTrendSelection(parseReviewBoundary(z.custom(isDiagnosticPlainRecord), input)); } export function replayReviewTrendApplicationFromInput(input: unknown): ReviewTrendApplication { return replayReviewTrendApplication(parseReviewBoundary(z.custom(isDiagnosticPlainRecord), input)); }