import { z } from "zod"; import { REVIEW_SENSITIVITY_LIMITS, isDiagnosticToken, isDiagnosticPlainRecord, isRealIsoDate, runReviewSensitivity, replayReviewSensitivity, validateReviewSensitivityRequest, type ReviewSensitivity, type ReviewSensitivityRequest, type ReviewSensitivityChange, type ReviewSensitivityObservation } from "@actuarial-ts/core"; import { reviewAssumptionDefinitionSchema, bfPriorCalibrationRequestSchema, analysisSnapshotSchema } from "./reviewContracts.js"; import { reviewOnLevelHistorySchema } from "./reviewOnLevelContracts.js"; import { reviewPlanSchema } from "./reviewPlanContracts.js"; import { parseReviewBoundary } from "./reviewBoundary.js"; import { developmentPatternSelectionSchema, developmentSelectionAssumptionSchema } from "./developmentSelectionContracts.js"; const token = z.string().refine(isDiagnosticToken), base = { id: token, node: token, rationale: token }; export const reviewSensitivityChangeSchema: z.ZodType = z.discriminatedUnion("kind", [ z.object({ ...base, kind: z.literal("on-level-target"), targetDate: z.string().refine(isRealIsoDate) }).strict(), z.object({ ...base, kind: z.literal("on-level-history"), history: reviewOnLevelHistorySchema, snapshots: z.array(analysisSnapshotSchema) }).strict(), z.object({ ...base, kind: z.literal("tail"), assumption: reviewAssumptionDefinitionSchema.omit({ contractVersion: true }) }).strict(), z.object({ ...base, kind: z.literal("expected-loss-rate"), assumption: reviewAssumptionDefinitionSchema.omit({ contractVersion: true }) }).strict(), z.object({ ...base, kind: z.literal("trend-rate"), assumption: reviewAssumptionDefinitionSchema.omit({ contractVersion: true }) }).strict(), z.object({ ...base, kind: z.literal("calibration-window"), selection: bfPriorCalibrationRequestSchema.shape.selection, assumption: bfPriorCalibrationRequestSchema.shape.assumption, calibrationVersion: token }).strict(), z.object({ ...base, kind: z.literal("factor-intent"), selection: developmentPatternSelectionSchema, assumption: developmentSelectionAssumptionSchema, selectionVersion: token }).strict(), z.object({ ...base, kind: z.literal("trend-window"), window: z.enum(["all", "last5", "last3", "exhilo"]), assumption: developmentSelectionAssumptionSchema, selectionVersion: token }).strict(), z.object({ ...base, kind: z.literal("cape-cod-decay"), value: z.number().finite().min(0).max(1) }).strict(), z.object({ ...base, kind: z.literal("clark-curve"), value: z.enum(["weibull", "loglogistic"]) }).strict(), ]); export const reviewSensitivityObservationSchema: z.ZodType = z.discriminatedUnion("kind", [ z.object({ id: token, node: token, kind: z.literal("on-level"), metric: z.enum(["factor", "premium"]), origin: token }).strict(), z.object({ id: token, node: token, kind: z.literal("model"), series: z.enum(["ultimate", "unpaid", "additional-incurred-development", "loss-through-development-age", "future-paid-through-development-age", "future-incurred-development-through-development-age", "ultimate-recoveries", "future-recoveries"]), origin: token.nullable(), basis: z.object({ kind: z.literal("controlled-on-level"), node: token, rationale: token }).strict().optional() }).strict(), z.object({ id: token, node: token, kind: z.literal("selection"), coordinate: token.nullable() }).strict(), z.object({ id: token, node: token, kind: z.literal("reconciliation"), metric: z.enum(["selectedUltimate", "paid", "case", "reportedIncurred", "bookedUnpaid", "selectedUnpaid", "additionalIncurredDevelopment", "paidPlusCase", "reportedIncurredDifference", "indicatedVersusBooked"]), coordinate: token.nullable() }).strict(), z.object({ id: token, node: token, kind: z.literal("forecast"), metric: z.enum(["expectedEmergence", "projectedCumulative"]), origin: token }).strict(), z.object({ id: token, node: token, kind: z.literal("trend-application"), origin: token }).strict(), ]); export const reviewSensitivityRequestSchema: z.ZodType = z.object({ id: token, version: token, baseline: reviewPlanSchema, changes: z.array(reviewSensitivityChangeSchema).min(1).max(REVIEW_SENSITIVITY_LIMITS.maximumChanges), attribution: z.enum(["none", "ordered-bridge", "shapley"]), observations: z.array(reviewSensitivityObservationSchema).min(1).max(REVIEW_SENSITIVITY_LIMITS.maximumObservations), reconciliationTolerance: z.number().finite().nonnegative(), limits: z.object({ maximumChanges: z.number().int().positive().max(REVIEW_SENSITIVITY_LIMITS.maximumChanges), maximumRuns: z.number().int().positive().max(REVIEW_SENSITIVITY_LIMITS.maximumRuns), maximumObservations: z.number().int().positive().max(REVIEW_SENSITIVITY_LIMITS.maximumObservations), maximumCompatibilityChecks: z.number().int().positive().max(REVIEW_SENSITIVITY_LIMITS.maximumCompatibilityChecks), maximumInputCharacters: z.number().int().positive().max(REVIEW_SENSITIVITY_LIMITS.maximumInputCharacters), maximumOutputCharacters: z.number().int().positive().max(REVIEW_SENSITIVITY_LIMITS.maximumOutputCharacters) }).strict(), }).strict(); export function parseReviewSensitivityRequest(input: unknown): ReviewSensitivityRequest { return validateReviewSensitivityRequest(parseReviewBoundary(reviewSensitivityRequestSchema, input)); } export function runReviewSensitivityFromInput(input: unknown): ReviewSensitivity { return runReviewSensitivity(parseReviewSensitivityRequest(input)); } const replayInput = z.custom(isDiagnosticPlainRecord, "Expected a saved sensitivity object"); /** Preserve every saved field; core validates the complete envelope by exact execution replay. */ export function replayReviewSensitivityFromInput(input: unknown): ReviewSensitivity { return replayReviewSensitivity(parseReviewBoundary(replayInput, input)); }