import { z } from "zod"; import { DEVELOPMENT_SELECTION_LIMITS, isDiagnosticToken, isRealIsoDate, isDiagnosticPlainRecord, validateDevelopmentSelectionRequest, selectDevelopmentPattern, replayDevelopmentSelection, validateTailFittingRequest, fitDevelopmentPatternTail, type DevelopmentSelectionRequest, type DevelopmentSelectionResult, type TailFittingRequest } from "@actuarial-ts/core"; import { analysisReferenceSchema, analysisSnapshotSchema, analysisContextSchema, quantitySemanticsSchema, modelDevelopmentSourceSchema, reviewAssumptionDefinitionSchema, reviewCountDefinitionSchema } from "./reviewContracts.js"; import { parseReviewBoundary } from "./reviewBoundary.js"; const token = z.string().refine(isDiagnosticToken), date = z.string().refine(isRealIsoDate); const chosenValue = { value: z.number().finite().positive().nullable(), rationale: token, evidence: z.array(analysisReferenceSchema) }; const manual = z.object({ kind: z.literal("manual"), ...chosenValue }).strict(), external = z.object({ kind: z.literal("external"), ...chosenValue }).strict(); const averageIntent = z.object({ kind: z.enum(["volume-weighted", "simple", "regression", "geometric", "medial", "judgmental", "external"]), windowOriginPeriods: z.number().int().positive().optional(), excludeHigh: z.number().int().nonnegative().optional(), excludeLow: z.number().int().nonnegative().optional(), exclusions: z.array(z.object({ origin: token, reason: token.optional() }).strict()).optional(), rationale: token.optional() }).strict(); const fittedTail = z.object({ kind: z.literal("fitted"), family: z.enum(["exponential-decay", "inverse-power"]), fitFromAgeMonths: z.number().int().positive().nullable(), maximumHorizon: z.number().int().positive().max(200), convention: z.literal("regular-development-period-index"), rationale: token }).strict(); export const developmentPatternSelectionSchema = z.object({ development: z.array(z.object({ fromAge: z.number().int().positive(), throughAge: z.number().int().positive(), choice: z.discriminatedUnion("kind", [manual, external, z.object({ kind: z.literal("average"), intent: averageIntent, rationale: token }).strict()]) }).strict()), tail: z.discriminatedUnion("kind", [manual, external, fittedTail]), }).strict(); export const developmentSelectionAssumptionSchema = reviewAssumptionDefinitionSchema.omit({ contractVersion: true, value: true, provenance: true }); export function developmentSelectionSchemaForInput() { const subject = z.discriminatedUnion("kind", [ z.object({ kind: z.literal("claim-count"), countDefinition: reviewCountDefinitionSchema }).strict(), z.object({ kind: z.literal("loss-per-count"), counts: z.object({ source: modelDevelopmentSourceSchema, context: analysisContextSchema, quantity: quantitySemanticsSchema, countDefinition: reviewCountDefinitionSchema }).strict(), relationship: z.object({ relationship: z.literal("same-covered-claims"), rationale: token, evidence: z.array(analysisReferenceSchema) }).strict() }).strict(), ]); return z.object({ id: token, version: token, source: modelDevelopmentSourceSchema, context: analysisContextSchema, quantity: quantitySemanticsSchema, subject: subject.optional(), snapshots: z.array(analysisSnapshotSchema), declaration: z.object({ actorId: token, knownDate: date, rationale: token }).strict(), futureObservations: z.enum(["reject", "truncate"]), selection: developmentPatternSelectionSchema, sourceRelationship: z.enum(["current-data", "separate-data"]), assumption: developmentSelectionAssumptionSchema, limits: z.object({ maximumOrigins: z.number().int().positive().max(DEVELOPMENT_SELECTION_LIMITS.maximumOrigins), maximumCells: z.number().int().positive().max(DEVELOPMENT_SELECTION_LIMITS.maximumCells), maximumInputCharacters: z.number().int().positive().max(DEVELOPMENT_SELECTION_LIMITS.maximumInputCharacters), maximumOutputCharacters: z.number().int().positive().max(DEVELOPMENT_SELECTION_LIMITS.maximumOutputCharacters) }).strict() }).strict(); } export const developmentSelectionRequestSchema: z.ZodType = z.lazy(developmentSelectionSchemaForInput); export const tailFittingRequestSchema: z.ZodType = z.lazy(() => developmentSelectionSchemaForInput().extend({ selection: developmentPatternSelectionSchema.extend({ tail: fittedTail }) })); export function parseTailFittingRequest(input: unknown): TailFittingRequest { return validateTailFittingRequest(parseReviewBoundary(tailFittingRequestSchema, input)); } export function fitDevelopmentPatternTailFromInput(input: unknown): DevelopmentSelectionResult { return fitDevelopmentPatternTail(parseTailFittingRequest(input)); } export function parseDevelopmentSelectionRequest(input: unknown): DevelopmentSelectionRequest { return validateDevelopmentSelectionRequest(parseReviewBoundary(developmentSelectionRequestSchema, input)); } export function selectDevelopmentPatternFromInput(input: unknown): DevelopmentSelectionResult { return selectDevelopmentPattern(parseDevelopmentSelectionRequest(input)); } export function replayDevelopmentSelectionFromInput(input: unknown): DevelopmentSelectionResult { return replayDevelopmentSelection(parseReviewBoundary(z.custom(isDiagnosticPlainRecord), input)); }