import { z } from "zod"; import { REVIEW_DISCOUNTING_LIMITS, calculateReviewDiscounting, isDiagnosticPlainRecord, isDiagnosticToken, isRealIsoDate, replayReviewDiscounting, validateReviewDiscountingRequest, type ReviewDiscountingRequest, type ReviewDiscountingResult, } from "@actuarial-ts/core"; import { analysisReferenceSchema, analysisSnapshotSchema, assumptionSnapshotSchema, quantitySemanticsSchema } 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 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)); }); const cashflow = z.object({ fromMonths: z.number().finite().nonnegative(), toMonths: z.number().finite().nonnegative(), amount: z.number().finite() }).strict(); const rates = z.discriminatedUnion("kind", [ z.object({ kind: z.literal("flat"), rate: assumptionSnapshotSchema }).strict(), z.object({ kind: z.literal("curve"), points: z.array(z.object({ year: z.number().int().safe().positive(), rate: assumptionSnapshotSchema }).strict()).min(1) }).strict(), ]); const limits = z.object({ maximumOrigins: z.number().int().positive().max(REVIEW_DISCOUNTING_LIMITS.maximumOrigins), maximumCashflows: z.number().int().positive().max(REVIEW_DISCOUNTING_LIMITS.maximumCashflows), maximumInputCharacters: z.number().int().positive().max(REVIEW_DISCOUNTING_LIMITS.maximumInputCharacters), maximumOutputCharacters: z.number().int().positive().max(REVIEW_DISCOUNTING_LIMITS.maximumOutputCharacters) }).strict(); export const reviewDiscountingRequestSchema = z.object({ id: token, version: token, intendedPurpose: token, discountDate: date, nominal: retainedSeries, schedules: z.array(z.object({ origin: token, cashflows: z.array(cashflow) }).strict()).min(1).max(REVIEW_DISCOUNTING_LIMITS.maximumOrigins), cashflowProvenance: z.object({ source: analysisReferenceSchema, asOfDate: date, rationale: token, unbiasedRationale: token, futureConditions: token, sensitivityRationale: token }).strict(), rates, rateProvenance: z.object({ source: analysisReferenceSchema, asOfDate: date, rationale: token, approach: z.enum(["risk-free", "portfolio", "other", "provided-by-another-party"]), provider: token.nullable(), reasonablenessAssessment: token, investmentExpenseTreatment: token, economicConditions: token }).strict(), convention: z.enum(["mid-period", "end-period"]), riskMargin: z.discriminatedUnion("kind", [z.object({ kind: z.literal("none"), rationale: token }).strict(), z.object({ kind: z.literal("explicit-amount"), amount: z.number().finite().nonnegative(), source: analysisReferenceSchema, rationale: token, uncertaintyBasis: token }).strict()]), discountedQuantity: quantitySemanticsSchema, policies: z.object({ unadoptedAssumption: z.enum(["reject", "allow-qualified"]), curveHorizon: z.enum(["reject", "use-last-rate-qualified"]), tieOut: z.object({ absoluteTolerance: z.number().finite().nonnegative(), relativeTolerance: z.number().finite().min(0).max(1) }).strict() }).strict(), declaration: z.object({ actorId: token, knownDate: date, rationale: token }).strict(), snapshots: z.array(analysisSnapshotSchema), limits, }).strict(); export function parseReviewDiscountingRequest(input: unknown): ReviewDiscountingRequest { return validateReviewDiscountingRequest(parseReviewBoundary(reviewDiscountingRequestSchema, input) as ReviewDiscountingRequest).request; } export function calculateReviewDiscountingFromInput(input: unknown): ReviewDiscountingResult { return calculateReviewDiscounting(parseReviewDiscountingRequest(input)); } export function replayReviewDiscountingFromInput(input: unknown): ReviewDiscountingResult { return replayReviewDiscounting(parseReviewBoundary(z.custom(isDiagnosticPlainRecord), input)); }