@actuarial-ts/interchange 0.2.0 → 0.3.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 (42) hide show
  1. package/README.md +28 -1
  2. package/dist/convert/result.d.ts +4 -4
  3. package/dist/convert/result.js +3 -3
  4. package/dist/convert/result.js.map +1 -1
  5. package/dist/envelope.d.ts +1 -1
  6. package/dist/envelope.js +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/parse.d.ts.map +1 -1
  12. package/dist/parse.js +49 -0
  13. package/dist/parse.js.map +1 -1
  14. package/dist/referee/crosscheck.d.ts +12 -0
  15. package/dist/referee/crosscheck.d.ts.map +1 -1
  16. package/dist/referee/crosscheck.js +109 -9
  17. package/dist/referee/crosscheck.js.map +1 -1
  18. package/dist/referee/crosscheckStochastic.d.ts +24 -0
  19. package/dist/referee/crosscheckStochastic.d.ts.map +1 -0
  20. package/dist/referee/crosscheckStochastic.js +367 -0
  21. package/dist/referee/crosscheckStochastic.js.map +1 -0
  22. package/dist/schemas/result.d.ts +552 -0
  23. package/dist/schemas/result.d.ts.map +1 -1
  24. package/dist/schemas/result.js +52 -0
  25. package/dist/schemas/result.js.map +1 -1
  26. package/package.json +4 -2
  27. package/src/convert/result.ts +212 -0
  28. package/src/convert/selection.ts +565 -0
  29. package/src/convert/triangle.ts +208 -0
  30. package/src/envelope.ts +193 -0
  31. package/src/index.ts +15 -0
  32. package/src/parse.ts +175 -0
  33. package/src/referee/crosscheck.ts +459 -0
  34. package/src/referee/crosscheckStochastic.ts +488 -0
  35. package/src/referee/profiles.ts +113 -0
  36. package/src/schemas/bundle.ts +64 -0
  37. package/src/schemas/crosscheck.ts +84 -0
  38. package/src/schemas/manifest.ts +75 -0
  39. package/src/schemas/result.ts +180 -0
  40. package/src/schemas/selection.ts +175 -0
  41. package/src/schemas/study.ts +98 -0
  42. package/src/schemas/triangle.ts +138 -0
@@ -0,0 +1,138 @@
1
+ import { z } from "zod";
2
+ import { envelopeShape } from "../envelope.js";
3
+
4
+ /**
5
+ * TriangleDoc (spec 3.2).
6
+ *
7
+ * - Measure vocabulary is closed with an escape hatch: the seven core
8
+ * TriangleKind values plus `earnedPremium` plus `custom:<label>`.
9
+ * - Cadence is the integer `originLengthMonths` (12 | 6 | 3 | 1), not an
10
+ * enum; `origins[].start` disambiguates fiscal/trailing calendars.
11
+ * - Null means unobserved, everywhere. Adapters must never let NaN→0 or
12
+ * 0→missing conversions leak through.
13
+ * - `valuesRef` is the bulk lane (spec 3.3): v1 defines the field; every
14
+ * v1 conformance fixture uses inline JSON, and this TS reader's
15
+ * converters require inline values in Phase A.
16
+ */
17
+
18
+ export const CORE_MEASURES = [
19
+ "paid",
20
+ "incurred",
21
+ "caseReserve",
22
+ "reportedCount",
23
+ "openCount",
24
+ "closedCount",
25
+ "closedWithPayCount",
26
+ ] as const;
27
+
28
+ export type CoreMeasure = (typeof CORE_MEASURES)[number];
29
+
30
+ export const measureSchema = z.union([
31
+ z.enum([...CORE_MEASURES, "earnedPremium"]),
32
+ z.string().regex(/^custom:.+$/, 'custom measures must be namespaced as "custom:<label>"'),
33
+ ]);
34
+
35
+ export type Measure = z.infer<typeof measureSchema>;
36
+
37
+ export const originLengthMonthsSchema = z.union([
38
+ z.literal(12),
39
+ z.literal(6),
40
+ z.literal(3),
41
+ z.literal(1),
42
+ ]);
43
+
44
+ export type OriginLengthMonths = z.infer<typeof originLengthMonthsSchema>;
45
+
46
+ const isoDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected an ISO yyyy-mm-dd date");
47
+
48
+ export const triangleOriginSchema = z
49
+ .object({ label: z.string().min(1), start: isoDateSchema })
50
+ .passthrough();
51
+
52
+ /** Bulk-lane reference (spec 3.3). */
53
+ export const valuesRefSchema = z
54
+ .object({ format: z.literal("arrow"), path: z.string().min(1), sha256: z.string().min(1) })
55
+ .passthrough();
56
+
57
+ export const triangleBodySchema = z
58
+ .object({
59
+ measure: measureSchema,
60
+ cumulative: z.boolean(),
61
+ originLengthMonths: originLengthMonthsSchema,
62
+ origins: z.array(triangleOriginSchema).min(1),
63
+ agesMonths: z.array(z.number().int().positive()).min(1),
64
+ /** values[originIndex][ageIndex]; null = not yet observable / missing. */
65
+ values: z.array(z.array(z.number().finite().nullable())).optional(),
66
+ valuesRef: valuesRefSchema.optional(),
67
+ valuationDate: isoDateSchema,
68
+ basis: z
69
+ .object({
70
+ grossNet: z.enum(["gross", "net"]).optional(),
71
+ laeTreatment: z.string().optional(),
72
+ })
73
+ .passthrough()
74
+ .optional(),
75
+ units: z
76
+ .object({ currency: z.string().optional(), scale: z.number().positive().optional() })
77
+ .passthrough()
78
+ .optional(),
79
+ segment: z.object({ labels: z.record(z.string()) }).passthrough().optional(),
80
+ })
81
+ .passthrough()
82
+ .superRefine((body, ctx) => {
83
+ if (body.values === undefined && body.valuesRef === undefined) {
84
+ ctx.addIssue({
85
+ code: z.ZodIssueCode.custom,
86
+ message: "a triangle needs inline `values` or a bulk-lane `valuesRef`",
87
+ });
88
+ }
89
+ if (body.values !== undefined) {
90
+ if (body.values.length !== body.origins.length) {
91
+ ctx.addIssue({
92
+ code: z.ZodIssueCode.custom,
93
+ message: `values has ${body.values.length} row(s) but origins has ${body.origins.length}`,
94
+ path: ["values"],
95
+ });
96
+ }
97
+ body.values.forEach((row, i) => {
98
+ if (row.length !== body.agesMonths.length) {
99
+ ctx.addIssue({
100
+ code: z.ZodIssueCode.custom,
101
+ message: `values[${i}] has ${row.length} column(s) but agesMonths has ${body.agesMonths.length}`,
102
+ path: ["values", i],
103
+ });
104
+ }
105
+ });
106
+ }
107
+ for (let j = 1; j < body.agesMonths.length; j++) {
108
+ if (body.agesMonths[j]! <= body.agesMonths[j - 1]!) {
109
+ ctx.addIssue({
110
+ code: z.ZodIssueCode.custom,
111
+ message: "agesMonths must be strictly ascending",
112
+ path: ["agesMonths", j],
113
+ });
114
+ break;
115
+ }
116
+ }
117
+ for (let i = 1; i < body.origins.length; i++) {
118
+ if (body.origins[i]!.start <= body.origins[i - 1]!.start) {
119
+ ctx.addIssue({
120
+ code: z.ZodIssueCode.custom,
121
+ message: "origins must have strictly ascending start dates",
122
+ path: ["origins", i],
123
+ });
124
+ break;
125
+ }
126
+ }
127
+ });
128
+
129
+ export type TriangleBody = z.infer<typeof triangleBodySchema>;
130
+
131
+ export const triangleDocSchema = z
132
+ .object({
133
+ ...envelopeShape("triangle"),
134
+ triangle: triangleBodySchema,
135
+ })
136
+ .passthrough();
137
+
138
+ export type TriangleDoc = z.infer<typeof triangleDocSchema>;