@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
@@ -73,11 +73,63 @@ export const stochasticSummarySchema = z
73
73
  percentiles: z.record(z.number().finite()),
74
74
  })
75
75
  .passthrough();
76
+ /**
77
+ * How much a consumer may rely on re-running this result.
78
+ *
79
+ * A seed is NOT the same guarantee as reproducibility: it removes one source
80
+ * of variance, but the engine underneath may still be non-deterministic. This
81
+ * field says which promise the document actually carries, so an actuary knows
82
+ * whether they hold a derivation anyone can regenerate or an observation
83
+ * somebody recorded.
84
+ *
85
+ * - `seeded-reproducible` — re-running with this seed reproduces this document
86
+ * byte-for-byte. @actuarial-ts/core's own stochastic layer is this
87
+ * (packages/core/test/odpBootstrap.test.ts pins it), because it uses a seeded
88
+ * PRNG with no ambient randomness.
89
+ * - `witnessed` — the engine is NOT byte-reproducible even under a fixed seed.
90
+ * The document is an integrity-checked record of what that engine produced on
91
+ * that run, not something a reviewer can regenerate. Still legitimate ASOP
92
+ * No. 56 evidence — it just supports an attestation, not a replay.
93
+ *
94
+ * Absent means unstated (documents written before this field existed); treat
95
+ * an absent value as unknown, never as a reproducibility guarantee.
96
+ *
97
+ * Deterministic methods do not carry this field: kind `method-result` already
98
+ * implies reproducibility, and the frozen conformance corpus proves it across
99
+ * three independent shores.
100
+ */
101
+ export const reproducibilitySchema = z.enum(["seeded-reproducible", "witnessed"]);
102
+ /**
103
+ * The engine's own self-check: it ran the identical seeded request more than
104
+ * once and reports whether the runs agreed. This is what lets a
105
+ * non-reproducible engine be HONEST rather than silently unstable — the
106
+ * instability is measured and disclosed on the document instead of surfacing
107
+ * later as a mysteriously failing test.
108
+ */
109
+ export const stabilityCheckSchema = z
110
+ .object({
111
+ /** How many independent runs of the identical seeded request were compared (>= 2). */
112
+ repeats: z.number().int().min(2),
113
+ /** True when every repeat produced the identical semantic body. */
114
+ byteIdentical: z.boolean(),
115
+ /**
116
+ * Worst relative deviation observed across repeats on the summary mean,
117
+ * using the cross-shore definition |a-b| / max(|a|,|b|). Null when not
118
+ * computable. Zero with byteIdentical=false means the bodies differed
119
+ * somewhere other than the summary mean.
120
+ */
121
+ maxRelativeDeviation: z.number().finite().nonnegative().nullable(),
122
+ })
123
+ .passthrough();
76
124
  export const stochasticResultBodySchema = z
77
125
  .object({
78
126
  ...resultCommonShape,
79
127
  seed: z.number().int().optional(),
80
128
  nSims: z.number().int().positive(),
129
+ /** Which reproducibility promise this document carries; see the schema doc. */
130
+ reproducibility: reproducibilitySchema.optional(),
131
+ /** The engine's repeat-run self-check, when it performed one. */
132
+ stability: stabilityCheckSchema.optional(),
81
133
  summary: stochasticSummarySchema,
82
134
  byOrigin: z.array(z.object({ origin: z.string().min(1) }).passthrough()),
83
135
  samplesRef: samplesRefSchema.optional(),
@@ -1 +1 @@
1
- {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/schemas/result.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/schemas/result.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actuarial-ts/interchange",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "The actuarial-interchange format for the actuarial-ts SDK: language-neutral, versioned interchange documents (triangles, selections, method results, studies, bundles, crosscheck reports) with RFC 8785 canonicalization, semantic-body integrity tags, an intent/value coherence rule, and the cross-implementation referee. Bridges actuarial-ts with chainladder-python and R ChainLadder.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "files": [
17
17
  "dist",
18
+ "src",
18
19
  "README.md",
19
20
  "LICENSE",
20
21
  "NOTICE"
@@ -52,10 +53,11 @@
52
53
  "emit-schema": "npm run build && node --experimental-strip-types scripts/emit-schema.ts"
53
54
  },
54
55
  "dependencies": {
55
- "@actuarial-ts/core": "^0.2.0",
56
+ "@actuarial-ts/core": "^0.3.0",
56
57
  "zod": "^3.25.76"
57
58
  },
58
59
  "devDependencies": {
60
+ "@types/node": "^22.10.5",
59
61
  "typescript": "^5.7.3",
60
62
  "vitest": "^3.0.5",
61
63
  "zod-to-json-schema": "^3.25.2"
@@ -0,0 +1,212 @@
1
+ import {
2
+ type BenktanderResult,
3
+ type BornhuetterFergusonResult,
4
+ type ChainLadderResult,
5
+ type MackResult,
6
+ ReservingError,
7
+ } from "@actuarial-ts/core";
8
+ import {
9
+ DEFAULT_GENERATOR,
10
+ INTERCHANGE_SPEC_VERSION,
11
+ type GeneratorStamp,
12
+ stampIntegrity,
13
+ } from "../envelope.js";
14
+ import type { TriangleDoc } from "../schemas/triangle.js";
15
+ import type { SelectionDoc } from "../schemas/selection.js";
16
+ import {
17
+ type EngineStamp,
18
+ type MethodResultBody,
19
+ type MethodResultDoc,
20
+ type MethodResultRow,
21
+ methodResultDocSchema,
22
+ } from "../schemas/result.js";
23
+
24
+ /**
25
+ * core method results → MethodResultDoc, stamping `appliesTo` (spec 3.2 /
26
+ * 4.1).
27
+ *
28
+ * Converts the deterministic quartet — ChainLadderResult, MackResult,
29
+ * BornhuetterFergusonResult, BenktanderResult. Further methods (Cape Cod,
30
+ * Clark, Munich, the stochastic layer, ...) are NOT yet converted and
31
+ * throw; adding one is a spec-minor change. Method names use the actuarial-ts namespace
32
+ * (unprefixed discriminants); `clpy:`/`rcl:` are reserved for the other
33
+ * shores.
34
+ *
35
+ * Row vocabulary is the spec's: `unpaid` carries what core's Mack calls
36
+ * `reserve` (identical quantity, interchange field name).
37
+ */
38
+
39
+ export type ConvertibleResult =
40
+ | ChainLadderResult
41
+ | MackResult
42
+ | BornhuetterFergusonResult
43
+ | BenktanderResult;
44
+
45
+ /**
46
+ * The engine stamp for results computed by @actuarial-ts/core. A sync test
47
+ * pins the version to the installed core package so it cannot drift.
48
+ */
49
+ export const CORE_ENGINE: EngineStamp = { name: "actuarial-ts", version: "0.3.0" };
50
+
51
+ export interface ResultToDocOptions {
52
+ /** The triangle document the result was computed from. */
53
+ triangleDoc: TriangleDoc;
54
+ /** The selection document used, when one exists; null/omitted = none
55
+ * (e.g. Mack on its own volume-weighted factors). */
56
+ selectionDoc?: SelectionDoc | null;
57
+ /** ISO timestamp for the envelope (caller-supplied; purity rule). */
58
+ createdAt: string;
59
+ /** Convention profile the run aligns to (referee comparability input). */
60
+ conventionProfile?: string;
61
+ /** Requested-parameter echo; defaults to {}. */
62
+ parameters?: Record<string, unknown>;
63
+ /** Effective parameters when the engine deviated from requested. */
64
+ effectiveParameters?: Record<string, unknown>;
65
+ engine?: EngineStamp;
66
+ generator?: GeneratorStamp;
67
+ extensions?: Record<string, unknown>;
68
+ }
69
+
70
+ interface MappedResult {
71
+ method: string;
72
+ rows: MethodResultRow[];
73
+ totals: MethodResultBody["totals"];
74
+ warnings: string[];
75
+ /** Basis to cross-check against the triangle's measure; null = untyped. */
76
+ basis: string | null;
77
+ }
78
+
79
+ function mapResult(result: ConvertibleResult): MappedResult {
80
+ switch (result.method) {
81
+ case "chainLadder":
82
+ return {
83
+ method: "chainLadder",
84
+ rows: result.rows.map((r) => ({
85
+ origin: r.origin,
86
+ ultimate: r.ultimate,
87
+ unpaid: r.unpaid,
88
+ })),
89
+ totals: { ultimate: result.totals.ultimate, unpaid: result.totals.unpaid },
90
+ warnings: [...result.warnings],
91
+ basis: result.basis,
92
+ };
93
+ case "mack":
94
+ return {
95
+ method: "mack",
96
+ rows: result.rows.map((r) => ({
97
+ origin: r.origin,
98
+ ultimate: r.ultimate,
99
+ unpaid: r.reserve,
100
+ standardError: r.standardError,
101
+ })),
102
+ totals: {
103
+ ultimate: result.totals.ultimate,
104
+ unpaid: result.totals.reserve,
105
+ standardError: result.totals.standardError,
106
+ },
107
+ warnings: [...result.warnings],
108
+ basis: null,
109
+ };
110
+ case "bornhuetterFerguson":
111
+ return {
112
+ method: "bornhuetterFerguson",
113
+ rows: result.rows.map((r) => ({
114
+ origin: r.origin,
115
+ ultimate: r.ultimate,
116
+ unpaid: r.unpaid,
117
+ })),
118
+ totals: { ultimate: result.totals.ultimate, unpaid: result.totals.unpaid },
119
+ warnings: [...result.warnings],
120
+ basis: result.basis,
121
+ };
122
+ case "benktander":
123
+ return {
124
+ method: "benktander",
125
+ rows: result.rows.map((r) => ({
126
+ origin: r.origin,
127
+ ultimate: r.ultimate,
128
+ unpaid: r.unpaid,
129
+ })),
130
+ totals: { ultimate: result.totals.ultimate, unpaid: result.totals.unpaid },
131
+ warnings: [...result.warnings],
132
+ basis: result.basis,
133
+ };
134
+ default: {
135
+ const method = (result as { method?: string }).method ?? "(unknown)";
136
+ throw new ReservingError(
137
+ "BAD_INTERCHANGE",
138
+ `resultToDoc does not support method "${method}" yet; it converts chainLadder, ` +
139
+ "mack, bornhuetterFerguson, and benktander",
140
+ );
141
+ }
142
+ }
143
+ }
144
+
145
+ export function resultToDoc(
146
+ result: ConvertibleResult,
147
+ options: ResultToDocOptions,
148
+ ): MethodResultDoc {
149
+ const selectionDoc = options.selectionDoc ?? null;
150
+ if (
151
+ selectionDoc !== null &&
152
+ selectionDoc.selection.appliesTo.triangleIntegrity !== options.triangleDoc.integrity
153
+ ) {
154
+ throw new ReservingError(
155
+ "BAD_INTERCHANGE",
156
+ `The selection document applies to triangle ${selectionDoc.selection.appliesTo.triangleIntegrity} ` +
157
+ `but the supplied triangle document's integrity is ${options.triangleDoc.integrity}`,
158
+ );
159
+ }
160
+
161
+ const mapped = mapResult(result);
162
+ if (mapped.basis !== null && mapped.basis !== options.triangleDoc.triangle.measure) {
163
+ throw new ReservingError(
164
+ "BAD_INTERCHANGE",
165
+ `Result basis "${mapped.basis}" does not match the triangle document's measure ` +
166
+ `"${options.triangleDoc.triangle.measure}"`,
167
+ );
168
+ }
169
+
170
+ const engine: EngineStamp = { ...(options.engine ?? CORE_ENGINE) };
171
+ if (options.conventionProfile !== undefined) {
172
+ engine.conventionProfile = options.conventionProfile;
173
+ }
174
+
175
+ const body: MethodResultBody = {
176
+ appliesTo: {
177
+ triangleIntegrity: options.triangleDoc.integrity,
178
+ selectionIntegrity: selectionDoc?.integrity ?? null,
179
+ },
180
+ engine,
181
+ method: mapped.method,
182
+ parameters: options.parameters ?? {},
183
+ rows: mapped.rows,
184
+ totals: mapped.totals,
185
+ };
186
+ // Authoring convention (shared with the Python adapter): `warnings` is
187
+ // omitted when empty, so a warning-free result hashes identically no
188
+ // matter which shore authored it.
189
+ if (mapped.warnings.length > 0) body.warnings = mapped.warnings;
190
+ if (options.effectiveParameters !== undefined) {
191
+ body.effectiveParameters = options.effectiveParameters;
192
+ }
193
+
194
+ const doc = stampIntegrity<MethodResultDoc>({
195
+ interchangeVersion: INTERCHANGE_SPEC_VERSION,
196
+ kind: "method-result",
197
+ generator: options.generator ?? DEFAULT_GENERATOR,
198
+ createdAt: options.createdAt,
199
+ extensions: options.extensions ?? {},
200
+ result: body,
201
+ });
202
+ const checked = methodResultDocSchema.safeParse(doc);
203
+ if (!checked.success) {
204
+ throw new ReservingError(
205
+ "BAD_INTERCHANGE",
206
+ `resultToDoc produced an invalid document: ${checked.error.issues
207
+ .map((i) => `${i.path.join(".") || "$"}: ${i.message}`)
208
+ .join("; ")}`,
209
+ );
210
+ }
211
+ return checked.data;
212
+ }