import { z } from "zod"; import { REVIEW_NET_RECOVERIES_LIMITS, calculateReviewNetRecoveries, createNetRecoveriesPaidBalance, isDiagnosticPlainRecord, isDiagnosticToken, isRealIsoDate, replayReviewNetRecoveries, validateReviewNetRecoveriesRequest, type ReviewNetRecoveriesPaidBalanceRequest, type ReviewNetRecoveriesRequest, type ReviewNetRecoveriesResult, } from "@actuarial-ts/core"; import { analysisReferenceSchema, analysisSnapshotSchema, 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 ids = z.array(token).min(1).refine(values => new Set(values).size === values.length, "Expected distinct component identifiers"); const limits = z.object({ maximumOrigins: z.number().int().positive().max(REVIEW_NET_RECOVERIES_LIMITS.maximumOrigins), maximumInputCharacters: z.number().int().positive().max(REVIEW_NET_RECOVERIES_LIMITS.maximumInputCharacters), maximumOutputCharacters: z.number().int().positive().max(REVIEW_NET_RECOVERIES_LIMITS.maximumOutputCharacters), }).strict(); export const reviewNetRecoveriesRequestSchema = z.object({ id: token, version: token, gross: z.object({ ultimate: retainedSeries, unpaid: retainedSeries }).strict(), recoveries: z.object({ ultimate: retainedSeries, future: retainedSeries }).strict(), targetFinancialBasis: analysisSnapshotSchema, quantities: z.object({ ultimate: quantitySemanticsSchema, unpaid: quantitySemanticsSchema, paidToDate: quantitySemanticsSchema }).strict(), componentIdentity: z.object({ relationship: z.literal("gross-less-recoveries"), grossLossComponentIds: ids, recoveryComponentIds: ids, targetLossComponentIds: ids, rationale: token }).strict(), populationRelationship: z.object({ relationship: z.literal("same-covered-claims"), rationale: token, evidence: z.array(analysisReferenceSchema).min(1) }).strict(), policies: z.object({ maturity: z.enum(["require-same-observed-age", "allow-qualified-different-observed-age"]), incompleteOrigins: z.enum(["retain-unavailable", "reject"]), negativeNet: z.enum(["reject", "allow-qualified"]) }).strict(), declaration: z.object({ actorId: token, knownDate: date, rationale: token }).strict(), snapshots: z.array(analysisSnapshotSchema), limits, }).strict(); export const reviewNetRecoveriesPaidBalanceRequestSchema = z.object({ id: token, version: token, netting: z.custom(isDiagnosticPlainRecord), origin: token, dimensions: z.record(token, token), rationale: token, }).strict(); export function parseReviewNetRecoveriesRequest(input: unknown): ReviewNetRecoveriesRequest { return validateReviewNetRecoveriesRequest(parseReviewBoundary(reviewNetRecoveriesRequestSchema, input) as ReviewNetRecoveriesRequest).request; } export function calculateReviewNetRecoveriesFromInput(input: unknown): ReviewNetRecoveriesResult { return calculateReviewNetRecoveries(parseReviewNetRecoveriesRequest(input)); } export function replayReviewNetRecoveriesFromInput(input: unknown): ReviewNetRecoveriesResult { return replayReviewNetRecoveries(parseReviewBoundary(z.custom(isDiagnosticPlainRecord), input)); } export function createNetRecoveriesPaidBalanceFromInput(input: unknown) { return createNetRecoveriesPaidBalance(parseReviewBoundary(reviewNetRecoveriesPaidBalanceRequestSchema, input) as ReviewNetRecoveriesPaidBalanceRequest); }