@actuarial-ts/data 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +20 -4
  2. package/dist/customizationContracts.d.ts +2473 -0
  3. package/dist/customizationContracts.d.ts.map +1 -0
  4. package/dist/customizationContracts.js +845 -0
  5. package/dist/customizationContracts.js.map +1 -0
  6. package/dist/customizationExternalState.d.ts +42 -0
  7. package/dist/customizationExternalState.d.ts.map +1 -0
  8. package/dist/customizationExternalState.js +121 -0
  9. package/dist/customizationExternalState.js.map +1 -0
  10. package/dist/customizationHistoryStream.d.ts +26 -0
  11. package/dist/customizationHistoryStream.d.ts.map +1 -0
  12. package/dist/customizationHistoryStream.js +66 -0
  13. package/dist/customizationHistoryStream.js.map +1 -0
  14. package/dist/diagnosticInput.d.ts +33 -0
  15. package/dist/diagnosticInput.d.ts.map +1 -1
  16. package/dist/diagnosticInput.js +71 -1
  17. package/dist/diagnosticInput.js.map +1 -1
  18. package/dist/diagnosticPreparedReview.d.ts.map +1 -1
  19. package/dist/diagnosticPreparedReview.js +38 -0
  20. package/dist/diagnosticPreparedReview.js.map +1 -1
  21. package/dist/historyMapping.d.ts +898 -0
  22. package/dist/historyMapping.d.ts.map +1 -0
  23. package/dist/historyMapping.js +395 -0
  24. package/dist/historyMapping.js.map +1 -0
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/version.d.ts +1 -1
  30. package/dist/version.js +1 -1
  31. package/package.json +3 -3
  32. package/src/customizationContracts.ts +963 -0
  33. package/src/customizationExternalState.ts +208 -0
  34. package/src/customizationHistoryStream.ts +90 -0
  35. package/src/diagnosticInput.ts +101 -0
  36. package/src/diagnosticPreparedReview.ts +48 -0
  37. package/src/historyMapping.ts +514 -0
  38. package/src/index.ts +4 -0
  39. package/src/version.ts +1 -1
@@ -0,0 +1,845 @@
1
+ import { ANALYSIS_DEFINITION_CONTRACT_VERSION, ANALYSIS_RECIPE_CONTRACT_VERSION, ANALYSIS_RESULT_CONTRACT_VERSION, CUSTOMIZATION_CAPABILITIES, HISTORICAL_DATASET_CONTRACT_VERSION, DiagnosticValidationError, diagnosticJsonPreflight, isDiagnosticToken, isRealIsoDate, } from "@actuarial-ts/core";
2
+ import { z } from "zod";
3
+ const token = z
4
+ .string()
5
+ .refine(isDiagnosticToken, "Expected a nonempty token with valid Unicode and no U+0000");
6
+ const date = z.string().refine(isRealIsoDate, "Expected a real Gregorian yyyy-mm-dd date");
7
+ const timestamp = z.string().datetime({ offset: true });
8
+ const finite = z.number().finite();
9
+ const scalar = z.union([z.string(), finite, z.boolean(), z.null()]);
10
+ const positiveSafeInteger = z.number().int().positive().safe();
11
+ /** Complete, closed resource policy for every customization streaming boundary. */
12
+ export const customizationResourceLimitsSchema = z
13
+ .object({
14
+ maximumInputRecords: positiveSafeInteger,
15
+ maximumInMemoryBytes: positiveSafeInteger,
16
+ maximumRecordBytes: positiveSafeInteger,
17
+ maximumPartitionRecords: positiveSafeInteger,
18
+ maximumOutputValues: positiveSafeInteger,
19
+ maximumActiveScenarios: positiveSafeInteger,
20
+ })
21
+ .strict();
22
+ const jsonValue = z.lazy(() => z.union([z.null(), z.boolean(), finite, z.string(), z.array(jsonValue), z.record(jsonValue)]));
23
+ const predicate = z.lazy(() => z.union([
24
+ z.object({ op: z.literal("true") }).strict(),
25
+ z.object({ op: z.literal("eq"), dimensionId: token, value: scalar }).strict(),
26
+ z.object({ op: z.literal("in"), dimensionId: token, values: z.array(scalar) }).strict(),
27
+ z
28
+ .object({
29
+ op: z.literal("range"),
30
+ dimensionId: token,
31
+ minimum: z.union([finite, date]).optional(),
32
+ maximum: z.union([finite, date]).optional(),
33
+ includeMinimum: z.boolean(),
34
+ includeMaximum: z.boolean(),
35
+ })
36
+ .strict()
37
+ .superRefine((value, context) => {
38
+ if (value.minimum === undefined && value.maximum === undefined)
39
+ context.addIssue({
40
+ code: z.ZodIssueCode.custom,
41
+ path: ["minimum"],
42
+ message: "Range requires a minimum, maximum, or both",
43
+ });
44
+ if (value.minimum !== undefined &&
45
+ value.maximum !== undefined &&
46
+ typeof value.minimum !== typeof value.maximum)
47
+ context.addIssue({
48
+ code: z.ZodIssueCode.custom,
49
+ path: ["maximum"],
50
+ message: "Range endpoints must have the same type",
51
+ });
52
+ }),
53
+ z.object({ op: z.literal("missing"), dimensionId: token, isMissing: z.boolean() }).strict(),
54
+ z.object({ op: z.enum(["and", "or"]), terms: z.array(predicate).min(1) }).strict(),
55
+ z.object({ op: z.literal("not"), term: predicate }).strict(),
56
+ ]));
57
+ const sourceLocation = z
58
+ .object({
59
+ artifactId: token,
60
+ sourceFile: token.optional(),
61
+ sourceSheet: token.optional(),
62
+ sourceRow: z.number().int().positive().safe().optional(),
63
+ })
64
+ .strict();
65
+ export const customizationExposureObservationSchema = z
66
+ .object({
67
+ id: token,
68
+ measureId: token,
69
+ value: finite.nullable(),
70
+ startDate: date.optional(),
71
+ endDate: date.optional(),
72
+ pointDate: date.optional(),
73
+ evaluationDate: date.optional(),
74
+ dimensions: z.record(token, scalar),
75
+ revisionId: token,
76
+ revision: z.object({
77
+ action: z.enum(["upsert", "delete"]),
78
+ sequence: z.number().int().nonnegative().safe().optional(),
79
+ correctedAt: timestamp.optional(),
80
+ }).strict().optional(),
81
+ source: sourceLocation,
82
+ })
83
+ .strict()
84
+ .superRefine((value, context) => {
85
+ if ((value.startDate === undefined) !== (value.endDate === undefined))
86
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["endDate"], message: "Exposure interval requires both startDate and endDate" });
87
+ if (value.startDate !== undefined && value.endDate !== undefined && value.startDate >= value.endDate)
88
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["endDate"], message: "Exposure endDate must be after startDate" });
89
+ });
90
+ export const exposureAllocationTargetSchema = z
91
+ .object({
92
+ id: token,
93
+ startDate: date,
94
+ endDate: date,
95
+ dimensions: z.record(token, scalar),
96
+ weights: z.record(token, finite.nonnegative()).optional(),
97
+ })
98
+ .strict()
99
+ .refine((value) => value.startDate < value.endDate, { path: ["endDate"], message: "Exposure target endDate must be after startDate" });
100
+ export const exposureRevisionSelectionSchema = z.object({
101
+ revisionView: z.discriminatedUnion("kind", [
102
+ z.object({ kind: z.literal("as-known"), knowledgeCutoff: timestamp }).strict(),
103
+ z.object({ kind: z.literal("restated") }).strict(),
104
+ ]),
105
+ revisionPrecedence: z.enum(["sequence", "corrected-at"]),
106
+ }).strict();
107
+ export const exposureLevelScheduleSchema = z.object({
108
+ id: token,
109
+ measureId: token,
110
+ startDate: date,
111
+ endDate: date,
112
+ initialDailyLevel: finite.nonnegative(),
113
+ movements: z.array(z.discriminatedUnion("kind", [
114
+ z.object({ id: token, effectiveDate: date, kind: z.literal("set-daily-level"), dailyLevel: finite.nonnegative() }).strict(),
115
+ z.object({ id: token, effectiveDate: date, kind: z.literal("cancel") }).strict(),
116
+ ])),
117
+ dimensions: z.record(token, scalar),
118
+ evaluationDate: date.optional(),
119
+ revisionId: token,
120
+ source: sourceLocation,
121
+ }).strict().superRefine((schedule, context) => {
122
+ if (schedule.startDate >= schedule.endDate)
123
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["endDate"], message: "endDate must be after startDate" });
124
+ duplicatePaths(schedule.movements, "movements", context);
125
+ });
126
+ export const historicalRecordSchema = z
127
+ .object({
128
+ recordId: token,
129
+ sourceNamespace: token,
130
+ claimId: token,
131
+ grain: z.enum(["claim-snapshot", "claim-component-snapshot", "transaction"]),
132
+ componentId: token.optional(),
133
+ accidentDate: date.optional(),
134
+ reportDate: date.optional(),
135
+ evaluationDate: date.optional(),
136
+ effectiveDate: date.optional(),
137
+ measures: z.record(token, z.union([finite, z.null()])),
138
+ status: token.optional(),
139
+ dimensions: z.record(token, z.union([scalar, z.array(scalar)])).optional(),
140
+ relationships: z
141
+ .object({
142
+ claimantIds: z.array(token).optional(),
143
+ occurrenceId: token.optional(),
144
+ policyIds: z.array(token).optional(),
145
+ coverageIds: z.array(token).optional(),
146
+ })
147
+ .strict()
148
+ .optional(),
149
+ completeness: z.enum(["complete-snapshot", "partial-snapshot", "change-only"]),
150
+ revision: z
151
+ .object({
152
+ id: token,
153
+ action: z.enum(["upsert", "delete"]),
154
+ sequence: z.number().int().nonnegative().safe().optional(),
155
+ correctedAt: timestamp.optional(),
156
+ })
157
+ .strict(),
158
+ source: sourceLocation,
159
+ })
160
+ .strict()
161
+ .superRefine((record, context) => {
162
+ if (record.grain === "claim-component-snapshot" && record.componentId === undefined)
163
+ context.addIssue({
164
+ code: z.ZodIssueCode.custom,
165
+ path: ["componentId"],
166
+ message: "Claim-component snapshots require componentId",
167
+ });
168
+ if (record.grain !== "claim-component-snapshot" && record.componentId !== undefined)
169
+ context.addIssue({
170
+ code: z.ZodIssueCode.custom,
171
+ path: ["componentId"],
172
+ message: "componentId is only valid for claim-component snapshots",
173
+ });
174
+ if (record.grain === "transaction" && record.effectiveDate === undefined)
175
+ context.addIssue({
176
+ code: z.ZodIssueCode.custom,
177
+ path: ["effectiveDate"],
178
+ message: "Transactions require effectiveDate",
179
+ });
180
+ if (record.grain !== "transaction" && record.evaluationDate === undefined)
181
+ context.addIssue({
182
+ code: z.ZodIssueCode.custom,
183
+ path: ["evaluationDate"],
184
+ message: "Snapshots require evaluationDate",
185
+ });
186
+ if (record.revision.action === "delete" && Object.keys(record.measures).length > 0)
187
+ context.addIssue({
188
+ code: z.ZodIssueCode.custom,
189
+ path: ["measures"],
190
+ message: "Deletion tombstones cannot carry measures",
191
+ });
192
+ });
193
+ export const historicalDatasetInputSchema = z
194
+ .object({
195
+ historyContractVersion: z.literal(HISTORICAL_DATASET_CONTRACT_VERSION),
196
+ datasetId: token,
197
+ historyMode: z.enum(["full-snapshots", "changes", "mixed"]),
198
+ revisionView: z.discriminatedUnion("kind", [
199
+ z.object({ kind: z.literal("as-known"), knowledgeCutoff: timestamp }).strict(),
200
+ z.object({ kind: z.literal("restated") }).strict(),
201
+ ]),
202
+ revisionPrecedence: z.enum(["sequence", "corrected-at"]),
203
+ missingSnapshotRecord: z.enum(["unknown", "carried-forward"]),
204
+ sources: z
205
+ .array(z
206
+ .object({ namespace: token, artifactId: token, mappingVersion: token })
207
+ .strict())
208
+ .min(1),
209
+ evaluationDates: z.array(date).optional(),
210
+ openingBalances: z
211
+ .array(z
212
+ .object({
213
+ sourceNamespace: token,
214
+ claimId: token,
215
+ asOfDate: date,
216
+ measures: z.record(token, finite),
217
+ source: sourceLocation,
218
+ })
219
+ .strict())
220
+ .optional(),
221
+ records: z.array(historicalRecordSchema),
222
+ })
223
+ .strict()
224
+ .superRefine((input, context) => {
225
+ const namespaces = new Set();
226
+ const artifactByNamespace = new Map();
227
+ input.sources.forEach((source, index) => {
228
+ if (namespaces.has(source.namespace))
229
+ context.addIssue({
230
+ code: z.ZodIssueCode.custom,
231
+ path: ["sources", index, "namespace"],
232
+ message: "Source namespace is duplicated",
233
+ });
234
+ namespaces.add(source.namespace);
235
+ artifactByNamespace.set(source.namespace, source.artifactId);
236
+ });
237
+ input.records.forEach((record, index) => {
238
+ if (!namespaces.has(record.sourceNamespace))
239
+ context.addIssue({
240
+ code: z.ZodIssueCode.custom,
241
+ path: ["records", index, "sourceNamespace"],
242
+ message: "Record references an unknown source namespace",
243
+ });
244
+ if (artifactByNamespace.has(record.sourceNamespace) &&
245
+ artifactByNamespace.get(record.sourceNamespace) !== record.source.artifactId)
246
+ context.addIssue({
247
+ code: z.ZodIssueCode.custom,
248
+ path: ["records", index, "source", "artifactId"],
249
+ message: "Record artifact does not match its source namespace",
250
+ });
251
+ if (input.revisionPrecedence === "sequence" && record.revision.sequence === undefined)
252
+ context.addIssue({
253
+ code: z.ZodIssueCode.custom,
254
+ path: ["records", index, "revision", "sequence"],
255
+ message: "Sequence precedence requires a sequence on every revision",
256
+ });
257
+ if (input.revisionPrecedence === "corrected-at" && record.revision.correctedAt === undefined)
258
+ context.addIssue({
259
+ code: z.ZodIssueCode.custom,
260
+ path: ["records", index, "revision", "correctedAt"],
261
+ message: "Corrected-at precedence requires correctedAt on every revision",
262
+ });
263
+ if (input.revisionView.kind === "as-known" &&
264
+ record.revision.correctedAt === undefined)
265
+ context.addIssue({
266
+ code: z.ZodIssueCode.custom,
267
+ path: ["records", index, "revision", "correctedAt"],
268
+ message: "As-known revision views require correctedAt on every revision",
269
+ });
270
+ if (record.accidentDate !== undefined &&
271
+ record.reportDate !== undefined &&
272
+ record.reportDate < record.accidentDate)
273
+ context.addIssue({
274
+ code: z.ZodIssueCode.custom,
275
+ path: ["records", index, "reportDate"],
276
+ message: "reportDate must not precede accidentDate",
277
+ });
278
+ for (const [field, observationDate] of [
279
+ ["evaluationDate", record.evaluationDate],
280
+ ["effectiveDate", record.effectiveDate],
281
+ ])
282
+ if (record.reportDate !== undefined &&
283
+ observationDate !== undefined &&
284
+ observationDate < record.reportDate)
285
+ context.addIssue({
286
+ code: z.ZodIssueCode.custom,
287
+ path: ["records", index, field],
288
+ message: "Observation date must not precede reportDate",
289
+ });
290
+ if (input.historyMode === "full-snapshots" &&
291
+ (record.grain === "transaction" || record.completeness === "change-only"))
292
+ context.addIssue({
293
+ code: z.ZodIssueCode.custom,
294
+ path: ["records", index, "grain"],
295
+ message: "Full-snapshot history cannot contain transaction/change-only records",
296
+ });
297
+ if (input.historyMode === "changes" &&
298
+ (record.grain !== "transaction" || record.completeness !== "change-only"))
299
+ context.addIssue({
300
+ code: z.ZodIssueCode.custom,
301
+ path: ["records", index, "grain"],
302
+ message: "Changes history requires transaction grain and change-only completeness",
303
+ });
304
+ });
305
+ if (input.historyMode !== "full-snapshots" && input.evaluationDates === undefined)
306
+ context.addIssue({
307
+ code: z.ZodIssueCode.custom,
308
+ path: ["evaluationDates"],
309
+ message: "Changes or mixed history requires explicit evaluationDates",
310
+ });
311
+ if (input.evaluationDates !== undefined) {
312
+ const ordered = [...input.evaluationDates].sort();
313
+ if (new Set(input.evaluationDates).size !== input.evaluationDates.length ||
314
+ ordered.some((value, index) => value !== input.evaluationDates[index]))
315
+ context.addIssue({
316
+ code: z.ZodIssueCode.custom,
317
+ path: ["evaluationDates"],
318
+ message: "evaluationDates must be unique and ascending",
319
+ });
320
+ }
321
+ input.openingBalances?.forEach((balance, index) => {
322
+ if (!namespaces.has(balance.sourceNamespace))
323
+ context.addIssue({
324
+ code: z.ZodIssueCode.custom,
325
+ path: ["openingBalances", index, "sourceNamespace"],
326
+ message: "Opening balance references an unknown source namespace",
327
+ });
328
+ if (artifactByNamespace.has(balance.sourceNamespace) &&
329
+ artifactByNamespace.get(balance.sourceNamespace) !== balance.source.artifactId)
330
+ context.addIssue({
331
+ code: z.ZodIssueCode.custom,
332
+ path: ["openingBalances", index, "source", "artifactId"],
333
+ message: "Opening balance artifact does not match its source namespace",
334
+ });
335
+ });
336
+ });
337
+ const scope = z
338
+ .object({
339
+ id: token,
340
+ subject: z.enum(["claim", "claimant", "occurrence", "policy", "coverage"]),
341
+ predicate,
342
+ classification: z.discriminatedUnion("kind", [
343
+ z.object({ kind: z.literal("as-observed") }).strict(),
344
+ z.object({ kind: z.literal("latest-as-of-analysis"), asOfDate: date }).strict(),
345
+ z.object({ kind: z.literal("frozen-cohort"), asOfDate: date }).strict(),
346
+ ]),
347
+ })
348
+ .strict();
349
+ const statistic = z.discriminatedUnion("kind", [
350
+ z.object({ kind: z.literal("sum"), measureId: token }).strict(),
351
+ z
352
+ .object({
353
+ kind: z.literal("ratio"),
354
+ numeratorMeasureId: token,
355
+ denominatorMeasureId: token,
356
+ scale: finite,
357
+ denominatorRule: z.enum(["positive", "nonzero"]),
358
+ })
359
+ .strict(),
360
+ z
361
+ .object({
362
+ kind: z.literal("distinct-count"),
363
+ entity: z.enum(["claim", "claimant", "occurrence", "policy", "coverage"]),
364
+ })
365
+ .strict(),
366
+ z
367
+ .object({ kind: z.literal("weighted-mean"), measureId: token, weightMeasureId: token })
368
+ .strict(),
369
+ z.object({ kind: z.enum(["minimum", "maximum"]), measureId: token }).strict(),
370
+ z
371
+ .object({
372
+ kind: z.literal("quantile"),
373
+ measureId: token,
374
+ probability: finite.min(0).max(1),
375
+ method: z.literal("hf-type-7"),
376
+ })
377
+ .strict(),
378
+ z
379
+ .object({
380
+ kind: z.literal("duration"),
381
+ start: z.enum(["accident-date", "report-date"]),
382
+ end: z.enum(["report-date", "closed-date", "evaluation-date"]),
383
+ unit: z.literal("days"),
384
+ population: z.enum(["all-with-observed-endpoint", "closed-only"]),
385
+ })
386
+ .strict(),
387
+ z
388
+ .object({
389
+ kind: z.literal("survival-quantile"),
390
+ start: z.enum(["accident-date", "report-date"]),
391
+ event: z.literal("closed-date"),
392
+ censor: z.literal("evaluation-date"),
393
+ unit: z.literal("days"),
394
+ probability: finite.min(0).max(1),
395
+ method: z.literal("kaplan-meier-product-limit"),
396
+ })
397
+ .strict(),
398
+ ]);
399
+ const financialTerm = z.discriminatedUnion("kind", [
400
+ z
401
+ .object({
402
+ kind: z.enum(["claim-layer", "occurrence-layer"]),
403
+ id: token,
404
+ measureId: token,
405
+ attachment: finite.nonnegative(),
406
+ limit: finite.positive().nullable(),
407
+ allocation: z.literal("proportional"),
408
+ })
409
+ .strict(),
410
+ z
411
+ .object({
412
+ kind: z.literal("policy-aggregate"),
413
+ id: token,
414
+ measureId: token,
415
+ deductible: finite.nonnegative(),
416
+ limit: finite.positive().nullable(),
417
+ allocation: z.enum(["proportional", "chronological"]),
418
+ })
419
+ .strict(),
420
+ ]);
421
+ const financialAdjustment = z.discriminatedUnion("kind", [
422
+ z.object({ kind: z.literal("scale"), id: token, purpose: z.enum(["trend", "index"]), factor: finite.positive() }).strict(),
423
+ z.object({ kind: z.literal("currency"), id: token, fromUnit: token, toUnit: token, rate: finite.positive(), rateDate: date }).strict(),
424
+ z.object({ kind: z.enum(["include-expense", "exclude-expense"]), id: token, expenseMeasureId: token }).strict(),
425
+ z.object({ kind: z.literal("net-recovery"), id: token, recoveryMeasureId: token }).strict(),
426
+ ]);
427
+ function predicateDimensions(value) {
428
+ switch (value.op) {
429
+ case "true":
430
+ return [];
431
+ case "eq":
432
+ case "in":
433
+ case "range":
434
+ case "missing":
435
+ return [value.dimensionId];
436
+ case "not":
437
+ return predicateDimensions(value.term);
438
+ case "and":
439
+ case "or":
440
+ return value.terms.flatMap(predicateDimensions);
441
+ }
442
+ }
443
+ function duplicatePaths(values, base, context) {
444
+ const seen = new Set();
445
+ values.forEach((value, index) => {
446
+ if (seen.has(value.id))
447
+ context.addIssue({
448
+ code: z.ZodIssueCode.custom,
449
+ path: [base, index, "id"],
450
+ message: `${base} ID is duplicated`,
451
+ });
452
+ seen.add(value.id);
453
+ });
454
+ }
455
+ export const analysisDefinitionSchema = z
456
+ .object({
457
+ analysisContractVersion: z.literal(ANALYSIS_DEFINITION_CONTRACT_VERSION),
458
+ id: token,
459
+ version: token,
460
+ dimensions: z.array(z
461
+ .object({
462
+ id: token,
463
+ type: z.enum(["string", "number", "boolean", "date", "entity-reference"]),
464
+ multiple: z.boolean(),
465
+ })
466
+ .strict()),
467
+ scopes: z
468
+ .object({
469
+ financial: scope,
470
+ reporting: scope,
471
+ exposure: z.array(z
472
+ .object({
473
+ id: token,
474
+ populationKeys: z.array(token),
475
+ timeBasis: z.enum(["interval", "point-in-time", "already-earned"]),
476
+ reportingFilterEffect: z.enum(["none", "matching-exposure-dimensions"]),
477
+ valuationSelection: z.enum(["origin-static", "exact", "latest-on-or-before"]).optional(),
478
+ })
479
+ .strict()),
480
+ })
481
+ .strict(),
482
+ policySchedule: z.array(z
483
+ .object({
484
+ id: token,
485
+ label: token,
486
+ scope: z.record(token, scalar),
487
+ startDate: date,
488
+ endDate: date,
489
+ revisionId: token,
490
+ })
491
+ .strict()
492
+ .refine((period) => period.startDate < period.endDate, {
493
+ path: ["endDate"],
494
+ message: "Policy period endDate must be after startDate",
495
+ })),
496
+ period: z
497
+ .object({
498
+ originBasis: z.enum(["accident", "report", "policy"]),
499
+ grouping: z.discriminatedUnion("kind", [
500
+ z.object({ kind: z.literal("calendar"), cadence: z.enum(["month", "quarter", "year"]) }).strict(),
501
+ z
502
+ .object({
503
+ kind: z.literal("fiscal"),
504
+ cadence: z.enum(["quarter", "year"]),
505
+ startMonth: z.number().int().min(1).max(12),
506
+ })
507
+ .strict(),
508
+ z.object({ kind: z.literal("policy-schedule"), periodIds: z.array(token) }).strict(),
509
+ ]),
510
+ comparisonClock: z.enum([
511
+ "common-evaluation-date",
512
+ "since-period-start",
513
+ "since-period-end",
514
+ "matched-accident-cohorts",
515
+ ]),
516
+ ageConvention: z.enum(["exact-days", "calendar-months"]),
517
+ observationSelection: z.enum(["exact", "latest-on-or-before"]),
518
+ })
519
+ .strict(),
520
+ exposures: z.array(z
521
+ .object({
522
+ id: token,
523
+ unit: token,
524
+ sourceMeaning: z.enum([
525
+ "already-earned",
526
+ "written-over-interval",
527
+ "flow-over-interval",
528
+ "point-in-time",
529
+ ]),
530
+ earning: z.discriminatedUnion("kind", [
531
+ z.object({ kind: z.literal("supplied") }).strict(),
532
+ z.object({ kind: z.literal("uniform-daily"), dayCount: z.literal("actual-days") }).strict(),
533
+ z.object({ kind: z.literal("weighted"), scheduleId: token }).strict(),
534
+ ]),
535
+ allocation: z.discriminatedUnion("kind", [
536
+ z.object({ kind: z.literal("exact-match") }).strict(),
537
+ z.object({ kind: z.literal("time-overlap") }).strict(),
538
+ z.object({ kind: z.literal("supplied-weights"), weightMeasureId: token }).strict(),
539
+ ]),
540
+ scopeId: token,
541
+ })
542
+ .strict()),
543
+ measures: z.array(z
544
+ .object({
545
+ id: token,
546
+ unit: token,
547
+ developmentSemantics: z.enum(["cumulative", "incremental", "point-in-time"]),
548
+ statistic,
549
+ populationScopeId: token,
550
+ condition: predicate.optional(),
551
+ })
552
+ .strict()),
553
+ financialTerms: z.array(financialTerm),
554
+ financialAdjustments: z.array(financialAdjustment).optional(),
555
+ })
556
+ .strict()
557
+ .superRefine((definition, context) => {
558
+ duplicatePaths(definition.dimensions, "dimensions", context);
559
+ duplicatePaths(definition.scopes.exposure, "scopes.exposure", context);
560
+ duplicatePaths(definition.policySchedule, "policySchedule", context);
561
+ duplicatePaths(definition.exposures, "exposures", context);
562
+ duplicatePaths(definition.measures, "measures", context);
563
+ duplicatePaths(definition.financialTerms, "financialTerms", context);
564
+ duplicatePaths(definition.financialAdjustments ?? [], "financialAdjustments", context);
565
+ const financialStageIds = [...definition.financialTerms.map((item) => item.id), ...(definition.financialAdjustments ?? []).map((item) => item.id)];
566
+ if (new Set(financialStageIds).size !== financialStageIds.length)
567
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["financialAdjustments"], message: "Financial term and adjustment IDs must be distinct" });
568
+ const scopeIds = new Set([
569
+ definition.scopes.financial.id,
570
+ definition.scopes.reporting.id,
571
+ ...definition.scopes.exposure.map((scope) => scope.id),
572
+ ]);
573
+ if (scopeIds.size !== definition.scopes.exposure.length + 2)
574
+ context.addIssue({
575
+ code: z.ZodIssueCode.custom,
576
+ path: ["scopes"],
577
+ message: "Financial, reporting, and exposure scope IDs must be distinct",
578
+ });
579
+ const dimensions = new Set(definition.dimensions.map((dimension) => dimension.id));
580
+ for (const [scopeName, selectedScope] of [
581
+ ["financial", definition.scopes.financial],
582
+ ["reporting", definition.scopes.reporting],
583
+ ])
584
+ for (const dimensionId of predicateDimensions(selectedScope.predicate))
585
+ if (!dimensions.has(dimensionId))
586
+ context.addIssue({
587
+ code: z.ZodIssueCode.custom,
588
+ path: ["scopes", scopeName, "predicate"],
589
+ message: `Predicate references unknown dimension ${dimensionId}`,
590
+ });
591
+ definition.exposures.forEach((exposure, index) => {
592
+ if (!definition.scopes.exposure.some((scope) => scope.id === exposure.scopeId))
593
+ context.addIssue({
594
+ code: z.ZodIssueCode.custom,
595
+ path: ["exposures", index, "scopeId"],
596
+ message: "Exposure references an unknown exposure scope",
597
+ });
598
+ });
599
+ definition.measures.forEach((measure, index) => {
600
+ if (measure.populationScopeId !== definition.scopes.financial.id &&
601
+ measure.populationScopeId !== definition.scopes.reporting.id)
602
+ context.addIssue({
603
+ code: z.ZodIssueCode.custom,
604
+ path: ["measures", index, "populationScopeId"],
605
+ message: "Measure references an unknown population scope",
606
+ });
607
+ if (measure.developmentSemantics === "cumulative" &&
608
+ measure.statistic.kind !== "sum")
609
+ context.addIssue({
610
+ code: z.ZodIssueCode.custom,
611
+ path: ["measures", index, "statistic"],
612
+ message: "Cumulative measures initially support explicit-evaluation sums only",
613
+ });
614
+ for (const dimensionId of measure.condition === undefined ? [] : predicateDimensions(measure.condition))
615
+ if (!dimensions.has(dimensionId))
616
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["measures", index, "condition"], message: `Measure condition references unknown dimension ${dimensionId}` });
617
+ });
618
+ const measureIds = new Set(definition.measures.map((measure) => measure.id));
619
+ definition.financialTerms.forEach((term, index) => {
620
+ if (!measureIds.has(term.measureId))
621
+ context.addIssue({
622
+ code: z.ZodIssueCode.custom,
623
+ path: ["financialTerms", index, "measureId"],
624
+ message: "Financial term references an unknown measure",
625
+ });
626
+ });
627
+ if (definition.period.grouping.kind === "policy-schedule") {
628
+ const periodIds = new Set(definition.policySchedule.map((period) => period.id));
629
+ definition.period.grouping.periodIds.forEach((id, index) => {
630
+ if (!periodIds.has(id))
631
+ context.addIssue({
632
+ code: z.ZodIssueCode.custom,
633
+ path: ["period", "grouping", "periodIds", index],
634
+ message: "Policy grouping references an unknown policy period",
635
+ });
636
+ });
637
+ }
638
+ });
639
+ const recipeStep = z.discriminatedUnion("kind", [
640
+ z.object({ kind: z.literal("prepare-history"), historyContractId: token }).strict(),
641
+ z.object({ kind: z.literal("assign-periods"), analysisDefinitionId: token }).strict(),
642
+ z.object({ kind: z.literal("earn-exposure"), exposureMeasureIds: z.array(token) }).strict(),
643
+ z.object({ kind: z.literal("apply-financial-terms"), termIds: z.array(token) }).strict(),
644
+ z.object({ kind: z.literal("apply-financial-pipeline"), amountMeasureId: token, stageIds: z.array(token).min(1) }).strict(),
645
+ z.object({ kind: z.literal("calculate"), measureIds: z.array(token) }).strict(),
646
+ z.object({ kind: z.literal("adapt-diagnostics"), diagnosticDefinitionId: token }).strict(),
647
+ ]);
648
+ export const analysisRecipeSchema = z
649
+ .object({
650
+ recipeContractVersion: z.literal(ANALYSIS_RECIPE_CONTRACT_VERSION),
651
+ id: token,
652
+ version: token,
653
+ analysisDefinitionId: token,
654
+ steps: z.array(recipeStep).min(1),
655
+ requiredCapabilities: z.array(z.enum(CUSTOMIZATION_CAPABILITIES)),
656
+ parameters: z.record(token, jsonValue),
657
+ })
658
+ .strict()
659
+ .superRefine((recipe, context) => {
660
+ if (new Set(recipe.requiredCapabilities).size !== recipe.requiredCapabilities.length)
661
+ context.addIssue({
662
+ code: z.ZodIssueCode.custom,
663
+ path: ["requiredCapabilities"],
664
+ message: "Required capabilities must be unique",
665
+ });
666
+ const firstPrepare = recipe.steps.findIndex((step) => step.kind === "prepare-history");
667
+ const firstAssign = recipe.steps.findIndex((step) => step.kind === "assign-periods");
668
+ const firstCalculate = recipe.steps.findIndex((step) => step.kind === "calculate");
669
+ if (firstPrepare !== 0)
670
+ context.addIssue({
671
+ code: z.ZodIssueCode.custom,
672
+ path: ["steps"],
673
+ message: "Recipe must begin with prepare-history",
674
+ });
675
+ if (firstAssign < 0 || (firstPrepare >= 0 && firstAssign < firstPrepare))
676
+ context.addIssue({
677
+ code: z.ZodIssueCode.custom,
678
+ path: ["steps"],
679
+ message: "Recipe must assign periods after preparing history",
680
+ });
681
+ if (firstCalculate < 0 || (firstAssign >= 0 && firstCalculate < firstAssign))
682
+ context.addIssue({
683
+ code: z.ZodIssueCode.custom,
684
+ path: ["steps"],
685
+ message: "Recipe must calculate after assigning periods",
686
+ });
687
+ recipe.steps.forEach((step, index) => {
688
+ if (step.kind === "assign-periods" && step.analysisDefinitionId !== recipe.analysisDefinitionId)
689
+ context.addIssue({
690
+ code: z.ZodIssueCode.custom,
691
+ path: ["steps", index, "analysisDefinitionId"],
692
+ message: "assign-periods must reference the recipe analysis definition",
693
+ });
694
+ });
695
+ });
696
+ const capabilityFinding = z.object({
697
+ code: z.enum([
698
+ "missing-prerequisite",
699
+ "unsupported-operation",
700
+ "incompatible-scope",
701
+ "resource-limit",
702
+ "ambiguous-attribution",
703
+ "incomplete-financial-population",
704
+ "incompatible-semantics",
705
+ "invalid-configuration",
706
+ ]),
707
+ capability: z.enum(CUSTOMIZATION_CAPABILITIES),
708
+ path: z.string(),
709
+ message: z.string(),
710
+ requiredFields: z.array(z.string()).optional(),
711
+ }).strict();
712
+ const identity = z.string().regex(/^fnv1a64-jcs-v1:[0-9a-f]{16}$/);
713
+ export const analysisResultSchema = z.object({
714
+ resultContractVersion: z.literal(ANALYSIS_RESULT_CONTRACT_VERSION),
715
+ datasetRevisionId: identity,
716
+ analysisDefinitionId: token,
717
+ recipeId: token,
718
+ calculationIdentity: identity,
719
+ lineageIdentity: identity,
720
+ values: z.array(z.object({
721
+ measureId: token,
722
+ unit: token,
723
+ coordinates: z.record(token, scalar),
724
+ value: finite.nullable(),
725
+ status: z.enum(["available", "unavailable"]),
726
+ contributingObservations: z.number().int().nonnegative(),
727
+ excludedObservations: z.number().int().nonnegative(),
728
+ exact: z.literal(true),
729
+ numerator: finite.nullable(),
730
+ denominator: finite.nullable(),
731
+ findings: z.array(capabilityFinding),
732
+ }).strict()),
733
+ }).strict();
734
+ const scenarioChange = z.discriminatedUnion("kind", [
735
+ z.object({ kind: z.literal("financial-term-limit"), termId: token, limit: finite.positive().nullable() }).strict(),
736
+ z.object({ kind: z.literal("financial-term-attachment"), termId: token, amount: finite.nonnegative() }).strict(),
737
+ z.object({ kind: z.literal("reporting-predicate"), predicate }).strict(),
738
+ ]);
739
+ export const analysisScenarioSchema = z.object({
740
+ id: token,
741
+ changes: z.array(scenarioChange).min(1).max(100),
742
+ }).strict().superRefine((scenario, context) => {
743
+ const keys = scenario.changes.map((change) => change.kind === "reporting-predicate" ? change.kind : `${change.kind}:${change.termId}`);
744
+ if (new Set(keys).size !== keys.length)
745
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["changes"], message: "Scenario changes must target each parameter at most once" });
746
+ });
747
+ export const analysisScenarioGridSchema = z
748
+ .object({
749
+ id: token,
750
+ axes: z
751
+ .array(z
752
+ .object({
753
+ id: token,
754
+ options: z
755
+ .array(z.object({ id: token, change: scenarioChange.nullable() }).strict())
756
+ .min(1),
757
+ })
758
+ .strict()
759
+ .superRefine((axis, context) => {
760
+ if (new Set(axis.options.map((option) => option.id)).size !== axis.options.length)
761
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["options"], message: "Scenario grid option IDs must be unique within an axis" });
762
+ }))
763
+ .min(1),
764
+ })
765
+ .strict()
766
+ .superRefine((grid, context) => {
767
+ if (new Set(grid.axes.map((axis) => axis.id)).size !== grid.axes.length)
768
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["axes"], message: "Scenario grid axis IDs must be unique" });
769
+ });
770
+ export const analysisClassificationSchema = z.discriminatedUnion("kind", [
771
+ z.object({
772
+ kind: z.literal("numeric-bands"), id: token, dimensionId: token,
773
+ groups: z.array(z.object({ id: token, minimum: finite.optional(), maximum: finite.optional(), includeMinimum: z.boolean(), includeMaximum: z.boolean() }).strict()),
774
+ overlapPolicy: z.enum(["reject", "allow"]), gapPolicy: z.enum(["unclassified", "reject"]),
775
+ }).strict(),
776
+ z.object({
777
+ kind: z.literal("lookup"), id: token, dimensionId: token,
778
+ entries: z.array(z.object({ value: scalar, groupId: token }).strict()), unmappedPolicy: z.enum(["unclassified", "reject"]),
779
+ }).strict(),
780
+ z.object({
781
+ kind: z.literal("hierarchy"), id: token, dimensionId: token,
782
+ entries: z.array(z.object({ value: scalar, groupIds: z.array(token).min(1) }).strict()), unmappedPolicy: z.enum(["unclassified", "reject"]),
783
+ }).strict(),
784
+ z.object({
785
+ kind: z.literal("rules"), id: token,
786
+ groups: z.array(z.object({ id: token, predicate }).strict()), overlapPolicy: z.enum(["reject", "allow"]), gapPolicy: z.enum(["unclassified", "reject"]),
787
+ }).strict(),
788
+ ]);
789
+ function zodIssues(error, domain) {
790
+ return error.issues.map((issue) => ({
791
+ domain,
792
+ code: issue.code === z.ZodIssueCode.unrecognized_keys ? "unknown-key" : "invalid-configuration",
793
+ path: `$${issue.path.map((part) => (typeof part === "number" ? `[${part}]` : `.${part}`)).join("")}`,
794
+ message: issue.message,
795
+ }));
796
+ }
797
+ function parseContract(value, schema, domain) {
798
+ const preflight = diagnosticJsonPreflight(value, domain);
799
+ if (preflight.length > 0)
800
+ throw new DiagnosticValidationError(preflight);
801
+ const parsed = schema.safeParse(value);
802
+ if (!parsed.success)
803
+ throw new DiagnosticValidationError(zodIssues(parsed.error, domain));
804
+ return parsed.data;
805
+ }
806
+ export function parseHistoricalDatasetInput(value) {
807
+ return parseContract(value, historicalDatasetInputSchema, "input");
808
+ }
809
+ export function parseCustomizationResourceLimits(value) {
810
+ return parseContract(value, customizationResourceLimitsSchema, "configuration");
811
+ }
812
+ export function parseHistoricalObservationRecord(value) {
813
+ return parseContract(value, historicalRecordSchema, "input");
814
+ }
815
+ export function parseAnalysisDefinition(value) {
816
+ return parseContract(value, analysisDefinitionSchema, "definition");
817
+ }
818
+ export function parseAnalysisRecipe(value) {
819
+ return parseContract(value, analysisRecipeSchema, "configuration");
820
+ }
821
+ export function parseAnalysisResult(value) {
822
+ return parseContract(value, analysisResultSchema, "input");
823
+ }
824
+ export function parseAnalysisScenario(value) {
825
+ return parseContract(value, analysisScenarioSchema, "configuration");
826
+ }
827
+ export function parseAnalysisScenarioGrid(value) {
828
+ return parseContract(value, analysisScenarioGridSchema, "configuration");
829
+ }
830
+ export function parseCustomizationExposureObservations(value) {
831
+ return parseContract(value, z.array(customizationExposureObservationSchema), "input");
832
+ }
833
+ export function parseExposureAllocationTargets(value) {
834
+ return parseContract(value, z.array(exposureAllocationTargetSchema), "input");
835
+ }
836
+ export function parseExposureRevisionSelection(value) {
837
+ return parseContract(value, exposureRevisionSelectionSchema, "configuration");
838
+ }
839
+ export function parseExposureLevelSchedule(value) {
840
+ return parseContract(value, exposureLevelScheduleSchema, "input");
841
+ }
842
+ export function parseAnalysisClassification(value) {
843
+ return parseContract(value, analysisClassificationSchema, "configuration");
844
+ }
845
+ //# sourceMappingURL=customizationContracts.js.map