@actuarial-ts/core 0.5.0 → 0.6.1
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.
- package/README.md +65 -226
- package/dist/capping.d.ts.map +1 -1
- package/dist/capping.js +39 -6
- package/dist/capping.js.map +1 -1
- package/dist/casualtyDiagnostics.d.ts +19 -49
- package/dist/casualtyDiagnostics.d.ts.map +1 -1
- package/dist/casualtyDiagnostics.js +84 -116
- package/dist/casualtyDiagnostics.js.map +1 -1
- package/dist/diagnosticAggregation.d.ts +32 -0
- package/dist/diagnosticAggregation.d.ts.map +1 -0
- package/dist/diagnosticAggregation.js +73 -0
- package/dist/diagnosticAggregation.js.map +1 -0
- package/dist/diagnosticDefinitions.d.ts +289 -0
- package/dist/diagnosticDefinitions.d.ts.map +1 -0
- package/dist/diagnosticDefinitions.js +1688 -0
- package/dist/diagnosticDefinitions.js.map +1 -0
- package/dist/diagnosticDerivations.d.ts +35 -0
- package/dist/diagnosticDerivations.d.ts.map +1 -0
- package/dist/diagnosticDerivations.js +224 -0
- package/dist/diagnosticDerivations.js.map +1 -0
- package/dist/diagnosticExposure.d.ts +51 -0
- package/dist/diagnosticExposure.d.ts.map +1 -0
- package/dist/diagnosticExposure.js +273 -0
- package/dist/diagnosticExposure.js.map +1 -0
- package/dist/diagnosticExpressions.d.ts +56 -0
- package/dist/diagnosticExpressions.d.ts.map +1 -0
- package/dist/diagnosticExpressions.js +124 -0
- package/dist/diagnosticExpressions.js.map +1 -0
- package/dist/diagnosticFormulas.d.ts +198 -0
- package/dist/diagnosticFormulas.d.ts.map +1 -0
- package/dist/diagnosticFormulas.js +219 -0
- package/dist/diagnosticFormulas.js.map +1 -0
- package/dist/diagnosticIdentity.d.ts +251 -0
- package/dist/diagnosticIdentity.d.ts.map +1 -0
- package/dist/diagnosticIdentity.js +721 -0
- package/dist/diagnosticIdentity.js.map +1 -0
- package/dist/diagnosticOrdering.d.ts +8 -0
- package/dist/diagnosticOrdering.d.ts.map +1 -0
- package/dist/diagnosticOrdering.js +73 -0
- package/dist/diagnosticOrdering.js.map +1 -0
- package/dist/diagnosticPeriodAxis.d.ts +9 -0
- package/dist/diagnosticPeriodAxis.d.ts.map +1 -0
- package/dist/diagnosticPeriodAxis.js +69 -0
- package/dist/diagnosticPeriodAxis.js.map +1 -0
- package/dist/diagnosticPeriods.d.ts +15 -0
- package/dist/diagnosticPeriods.d.ts.map +1 -0
- package/dist/diagnosticPeriods.js +50 -0
- package/dist/diagnosticPeriods.js.map +1 -0
- package/dist/diagnosticPreparation.d.ts +131 -0
- package/dist/diagnosticPreparation.d.ts.map +1 -0
- package/dist/diagnosticPreparation.js +1539 -0
- package/dist/diagnosticPreparation.js.map +1 -0
- package/dist/diagnosticReview.d.ts +66 -0
- package/dist/diagnosticReview.d.ts.map +1 -0
- package/dist/diagnosticReview.js +490 -0
- package/dist/diagnosticReview.js.map +1 -0
- package/dist/diagnosticRules.d.ts +32 -0
- package/dist/diagnosticRules.d.ts.map +1 -0
- package/dist/diagnosticRules.js +32 -0
- package/dist/diagnosticRules.js.map +1 -0
- package/dist/diagnosticRunner.d.ts +86 -0
- package/dist/diagnosticRunner.d.ts.map +1 -0
- package/dist/diagnosticRunner.js +703 -0
- package/dist/diagnosticRunner.js.map +1 -0
- package/dist/diagnosticRuntime.d.ts +26 -0
- package/dist/diagnosticRuntime.d.ts.map +1 -0
- package/dist/diagnosticRuntime.js +243 -0
- package/dist/diagnosticRuntime.js.map +1 -0
- package/dist/diagnosticSourceOrdering.d.ts +6 -0
- package/dist/diagnosticSourceOrdering.d.ts.map +1 -0
- package/dist/diagnosticSourceOrdering.js +38 -0
- package/dist/diagnosticSourceOrdering.js.map +1 -0
- package/dist/index.d.ts +28 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -1
- package/dist/index.js.map +1 -1
- package/dist/mack.d.ts +4 -0
- package/dist/mack.d.ts.map +1 -1
- package/dist/mack.js +32 -4
- package/dist/mack.js.map +1 -1
- package/dist/triangle.d.ts.map +1 -1
- package/dist/triangle.js +59 -9
- package/dist/triangle.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +128 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +4 -3
- package/src/capping.ts +109 -18
- package/src/casualtyDiagnostics.ts +79 -189
- package/src/diagnosticAggregation.ts +128 -0
- package/src/diagnosticDefinitions.ts +3624 -0
- package/src/diagnosticDerivations.ts +321 -0
- package/src/diagnosticExposure.ts +482 -0
- package/src/diagnosticExpressions.ts +255 -0
- package/src/diagnosticFormulas.ts +350 -0
- package/src/diagnosticIdentity.ts +1267 -0
- package/src/diagnosticOrdering.ts +106 -0
- package/src/diagnosticPeriodAxis.ts +92 -0
- package/src/diagnosticPeriods.ts +102 -0
- package/src/diagnosticPreparation.ts +2475 -0
- package/src/diagnosticReview.ts +897 -0
- package/src/diagnosticRules.ts +69 -0
- package/src/diagnosticRunner.ts +1114 -0
- package/src/diagnosticRuntime.ts +264 -0
- package/src/diagnosticSourceOrdering.ts +50 -0
- package/src/index.ts +184 -1
- package/src/mack.ts +72 -9
- package/src/triangle.ts +103 -13
- package/src/types.ts +196 -0
- package/src/version.ts +2 -0
- package/dist/metricDiagnostics.d.ts +0 -212
- package/dist/metricDiagnostics.d.ts.map +0 -1
- package/dist/metricDiagnostics.js +0 -627
- package/dist/metricDiagnostics.js.map +0 -1
- package/src/metricDiagnostics.ts +0 -876
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertCompiledDiagnosticDefinition,
|
|
3
|
+
getCompiledDiagnosticDefinitionInternals,
|
|
4
|
+
type CompiledDiagnosticDefinition,
|
|
5
|
+
} from "./diagnosticDefinitions.js";
|
|
6
|
+
import type { DiagnosticClaimExpression } from "./diagnosticExpressions.js";
|
|
7
|
+
import type { DiagnosticExpressionOverflow } from "./diagnosticRules.js";
|
|
8
|
+
import {
|
|
9
|
+
DiagnosticValidationError,
|
|
10
|
+
type DiagnosticValidationIssue,
|
|
11
|
+
} from "./types.js";
|
|
12
|
+
import {
|
|
13
|
+
diagnosticRecord,
|
|
14
|
+
hasDiagnosticOwn,
|
|
15
|
+
isDiagnosticPlainRecord,
|
|
16
|
+
normalizeDiagnosticNumber,
|
|
17
|
+
} from "./diagnosticRuntime.js";
|
|
18
|
+
|
|
19
|
+
export type DiagnosticMeasureValues = Readonly<Record<string, number | null>>;
|
|
20
|
+
|
|
21
|
+
export type DiagnosticRowWithDerivedMeasures<
|
|
22
|
+
TRow extends { measures: DiagnosticMeasureValues },
|
|
23
|
+
> = Omit<TRow, "measures"> & { readonly measures: DiagnosticMeasureValues };
|
|
24
|
+
|
|
25
|
+
export type DiagnosticDerivedValueAudit =
|
|
26
|
+
| { readonly status: "observed"; readonly value: number }
|
|
27
|
+
| { readonly status: "missing"; readonly value: null }
|
|
28
|
+
| {
|
|
29
|
+
readonly status: "non-finite";
|
|
30
|
+
readonly value: null;
|
|
31
|
+
readonly nonFiniteKind: "nan" | "positive-infinity" | "negative-infinity";
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export interface DiagnosticDerivedRowAudit<
|
|
35
|
+
TRow extends { measures: DiagnosticMeasureValues },
|
|
36
|
+
> {
|
|
37
|
+
readonly row: DiagnosticRowWithDerivedMeasures<TRow>;
|
|
38
|
+
readonly derived: Readonly<Record<string, DiagnosticDerivedValueAudit>>;
|
|
39
|
+
readonly expressionOverflows: Readonly<
|
|
40
|
+
Record<string, readonly DiagnosticExpressionOverflow[]>
|
|
41
|
+
>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ClaimExpressionAudit {
|
|
45
|
+
readonly state: DiagnosticDerivedValueAudit;
|
|
46
|
+
readonly overflows: readonly DiagnosticExpressionOverflow[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function nonFiniteKind(
|
|
50
|
+
value: number,
|
|
51
|
+
): "nan" | "positive-infinity" | "negative-infinity" {
|
|
52
|
+
return Number.isNaN(value)
|
|
53
|
+
? "nan"
|
|
54
|
+
: value > 0
|
|
55
|
+
? "positive-infinity"
|
|
56
|
+
: "negative-infinity";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function mergeNonFiniteKinds(
|
|
60
|
+
values: readonly ("nan" | "positive-infinity" | "negative-infinity")[],
|
|
61
|
+
): "nan" | "positive-infinity" | "negative-infinity" {
|
|
62
|
+
const unique = new Set(values);
|
|
63
|
+
return unique.size === 1 ? values[0]! : "nan";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function neumaier(values: readonly number[]): {
|
|
67
|
+
readonly value: number | null;
|
|
68
|
+
readonly nonFiniteKind?: "nan" | "positive-infinity" | "negative-infinity";
|
|
69
|
+
} {
|
|
70
|
+
let sum = 0;
|
|
71
|
+
let correction = 0;
|
|
72
|
+
for (const value of values) {
|
|
73
|
+
const next = sum + value;
|
|
74
|
+
if (!Number.isFinite(next))
|
|
75
|
+
return { value: null, nonFiniteKind: nonFiniteKind(next) };
|
|
76
|
+
const nextCorrection =
|
|
77
|
+
correction +
|
|
78
|
+
(Math.abs(sum) >= Math.abs(value)
|
|
79
|
+
? sum - next + value
|
|
80
|
+
: value - next + sum);
|
|
81
|
+
if (!Number.isFinite(nextCorrection))
|
|
82
|
+
return { value: null, nonFiniteKind: nonFiniteKind(nextCorrection) };
|
|
83
|
+
correction = nextCorrection;
|
|
84
|
+
sum = next;
|
|
85
|
+
}
|
|
86
|
+
const result = sum + correction;
|
|
87
|
+
return Number.isFinite(result)
|
|
88
|
+
? { value: Object.is(result, -0) ? 0 : result }
|
|
89
|
+
: { value: null, nonFiniteKind: nonFiniteKind(result) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function evaluateClaimExpressionAudit(
|
|
93
|
+
expression: DiagnosticClaimExpression,
|
|
94
|
+
states: Readonly<Record<string, DiagnosticDerivedValueAudit>>,
|
|
95
|
+
path: string,
|
|
96
|
+
): ClaimExpressionAudit {
|
|
97
|
+
if (expression.op === "measure")
|
|
98
|
+
return {
|
|
99
|
+
state: hasDiagnosticOwn(states, expression.measureId)
|
|
100
|
+
? states[expression.measureId]!
|
|
101
|
+
: { status: "missing", value: null },
|
|
102
|
+
overflows: [],
|
|
103
|
+
};
|
|
104
|
+
if (expression.op === "claim-layer") {
|
|
105
|
+
const source = hasDiagnosticOwn(states, expression.measureId)
|
|
106
|
+
? states[expression.measureId]!
|
|
107
|
+
: { status: "missing" as const, value: null };
|
|
108
|
+
if (source.status !== "observed") return { state: source, overflows: [] };
|
|
109
|
+
const value =
|
|
110
|
+
source.value <= expression.attachment
|
|
111
|
+
? 0
|
|
112
|
+
: expression.limit === null
|
|
113
|
+
? source.value - expression.attachment
|
|
114
|
+
: Math.min(source.value - expression.attachment, expression.limit);
|
|
115
|
+
if (Number.isFinite(value))
|
|
116
|
+
return {
|
|
117
|
+
state: { status: "observed", value: normalizeDiagnosticNumber(value) },
|
|
118
|
+
overflows: [],
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
state: {
|
|
122
|
+
status: "non-finite",
|
|
123
|
+
value: null,
|
|
124
|
+
nonFiniteKind: nonFiniteKind(value),
|
|
125
|
+
},
|
|
126
|
+
overflows: [{ expressionPath: path, sources: [] }],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const children =
|
|
130
|
+
expression.op === "add"
|
|
131
|
+
? expression.terms.map((term, index) =>
|
|
132
|
+
evaluateClaimExpressionAudit(term, states, `${path}/terms/${index}`),
|
|
133
|
+
)
|
|
134
|
+
: [
|
|
135
|
+
evaluateClaimExpressionAudit(expression.left, states, `${path}/left`),
|
|
136
|
+
evaluateClaimExpressionAudit(
|
|
137
|
+
expression.right,
|
|
138
|
+
states,
|
|
139
|
+
`${path}/right`,
|
|
140
|
+
),
|
|
141
|
+
];
|
|
142
|
+
const overflows = children.flatMap((child) => child.overflows);
|
|
143
|
+
const nonFinite = children.flatMap((child) =>
|
|
144
|
+
child.state.status === "non-finite" ? [child.state.nonFiniteKind] : [],
|
|
145
|
+
);
|
|
146
|
+
if (nonFinite.length > 0)
|
|
147
|
+
return {
|
|
148
|
+
state: {
|
|
149
|
+
status: "non-finite",
|
|
150
|
+
value: null,
|
|
151
|
+
nonFiniteKind: mergeNonFiniteKinds(nonFinite),
|
|
152
|
+
},
|
|
153
|
+
overflows,
|
|
154
|
+
};
|
|
155
|
+
if (children.some((child) => child.state.status === "missing"))
|
|
156
|
+
return { state: { status: "missing", value: null }, overflows };
|
|
157
|
+
const values = children.map((child) => child.state.value!);
|
|
158
|
+
const calculation =
|
|
159
|
+
expression.op === "add"
|
|
160
|
+
? neumaier(values)
|
|
161
|
+
: { value: values[0]! - values[1]! };
|
|
162
|
+
if (calculation.value !== null && Number.isFinite(calculation.value)) {
|
|
163
|
+
return {
|
|
164
|
+
state: {
|
|
165
|
+
status: "observed",
|
|
166
|
+
value: normalizeDiagnosticNumber(calculation.value),
|
|
167
|
+
},
|
|
168
|
+
overflows,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const failedKind =
|
|
172
|
+
calculation.nonFiniteKind ?? nonFiniteKind(calculation.value ?? Number.NaN);
|
|
173
|
+
return {
|
|
174
|
+
state: { status: "non-finite", value: null, nonFiniteKind: failedKind },
|
|
175
|
+
overflows: [...overflows, { expressionPath: path, sources: [] }],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function validateRows<TRow extends { measures: DiagnosticMeasureValues }>(
|
|
180
|
+
rows: readonly TRow[],
|
|
181
|
+
definition: CompiledDiagnosticDefinition,
|
|
182
|
+
): void {
|
|
183
|
+
const internals = getCompiledDiagnosticDefinitionInternals(definition);
|
|
184
|
+
const issues: DiagnosticValidationIssue[] = [];
|
|
185
|
+
if (definition.definition.lossRowGrain !== "claim") {
|
|
186
|
+
issues.push({
|
|
187
|
+
domain: "input",
|
|
188
|
+
code: "invalid-input-relationship",
|
|
189
|
+
path: "$.definition.lossRowGrain",
|
|
190
|
+
message: "Claim derivation requires a claim-grain definition",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (!Array.isArray(rows)) {
|
|
194
|
+
issues.push({
|
|
195
|
+
domain: "input",
|
|
196
|
+
code: "invalid-type",
|
|
197
|
+
path: "$.rows",
|
|
198
|
+
message: "Claim rows must be an array",
|
|
199
|
+
});
|
|
200
|
+
} else {
|
|
201
|
+
rows.forEach((row, rowIndex) => {
|
|
202
|
+
const measures = row?.measures;
|
|
203
|
+
if (!isDiagnosticPlainRecord(measures)) {
|
|
204
|
+
issues.push({
|
|
205
|
+
domain: "input",
|
|
206
|
+
code: "invalid-type",
|
|
207
|
+
path: `$.rows[${rowIndex}].measures`,
|
|
208
|
+
message: "Row measures must be a plain record",
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
for (const [measureId, value] of Object.entries(measures)) {
|
|
213
|
+
const measure = internals.measuresById.get(measureId);
|
|
214
|
+
const path = `$.rows[${rowIndex}].measures[${JSON.stringify(measureId)}]`;
|
|
215
|
+
if (!measure)
|
|
216
|
+
issues.push({
|
|
217
|
+
domain: "input",
|
|
218
|
+
code: "unknown-reference",
|
|
219
|
+
path,
|
|
220
|
+
message: `Unknown measure ${measureId}`,
|
|
221
|
+
});
|
|
222
|
+
else if (measure.source !== "loss")
|
|
223
|
+
issues.push({
|
|
224
|
+
domain: "input",
|
|
225
|
+
code: "invalid-input-relationship",
|
|
226
|
+
path,
|
|
227
|
+
message: `Caller rows cannot supply ${measure.source} measure ${measureId}`,
|
|
228
|
+
});
|
|
229
|
+
if (value !== null && typeof value !== "number")
|
|
230
|
+
issues.push({
|
|
231
|
+
domain: "input",
|
|
232
|
+
code: "invalid-type",
|
|
233
|
+
path,
|
|
234
|
+
message: "Measure value must be a number or null",
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (issues.length > 0) throw new DiagnosticValidationError(issues);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** @internal Shared with diagnostic preparation to preserve derivation quality. */
|
|
243
|
+
export function deriveDiagnosticClaimMeasuresWithAudit<
|
|
244
|
+
TRow extends { measures: DiagnosticMeasureValues },
|
|
245
|
+
>(
|
|
246
|
+
rows: readonly TRow[],
|
|
247
|
+
definition: CompiledDiagnosticDefinition,
|
|
248
|
+
): readonly DiagnosticDerivedRowAudit<TRow>[] {
|
|
249
|
+
assertCompiledDiagnosticDefinition(definition);
|
|
250
|
+
validateRows(rows, definition);
|
|
251
|
+
const internals = getCompiledDiagnosticDefinitionInternals(definition);
|
|
252
|
+
const plan = [...internals.derivationsByOutputMeasureId.values()];
|
|
253
|
+
const normalizedIndex = new Map(
|
|
254
|
+
definition.definition.derivedMeasures.map((derivation, index) => [
|
|
255
|
+
derivation.outputMeasureId,
|
|
256
|
+
index,
|
|
257
|
+
]),
|
|
258
|
+
);
|
|
259
|
+
return Object.freeze(
|
|
260
|
+
rows.map((row) => {
|
|
261
|
+
const measures = diagnosticRecord<number | null>();
|
|
262
|
+
const states = diagnosticRecord<DiagnosticDerivedValueAudit>();
|
|
263
|
+
for (const [id, value] of Object.entries(row.measures)) {
|
|
264
|
+
measures[id] = value;
|
|
265
|
+
states[id] =
|
|
266
|
+
value === null || value === undefined
|
|
267
|
+
? { status: "missing" as const, value: null }
|
|
268
|
+
: Number.isFinite(value)
|
|
269
|
+
? {
|
|
270
|
+
status: "observed" as const,
|
|
271
|
+
value: normalizeDiagnosticNumber(value),
|
|
272
|
+
}
|
|
273
|
+
: {
|
|
274
|
+
status: "non-finite" as const,
|
|
275
|
+
value: null,
|
|
276
|
+
nonFiniteKind: nonFiniteKind(value),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
const derived = diagnosticRecord<DiagnosticDerivedValueAudit>();
|
|
280
|
+
const expressionOverflows =
|
|
281
|
+
diagnosticRecord<readonly DiagnosticExpressionOverflow[]>();
|
|
282
|
+
for (const derivation of plan) {
|
|
283
|
+
const index = normalizedIndex.get(derivation.outputMeasureId)!;
|
|
284
|
+
const evaluated = evaluateClaimExpressionAudit(
|
|
285
|
+
derivation.expression,
|
|
286
|
+
states,
|
|
287
|
+
`/derivedMeasures/${index}/expression`,
|
|
288
|
+
);
|
|
289
|
+
states[derivation.outputMeasureId] = evaluated.state;
|
|
290
|
+
derived[derivation.outputMeasureId] = evaluated.state;
|
|
291
|
+
expressionOverflows[derivation.outputMeasureId] = Object.freeze([
|
|
292
|
+
...evaluated.overflows,
|
|
293
|
+
]);
|
|
294
|
+
measures[derivation.outputMeasureId] = evaluated.state.value;
|
|
295
|
+
}
|
|
296
|
+
return Object.freeze({
|
|
297
|
+
row: Object.freeze({
|
|
298
|
+
...row,
|
|
299
|
+
measures: Object.freeze(measures),
|
|
300
|
+
}) as DiagnosticRowWithDerivedMeasures<TRow>,
|
|
301
|
+
derived: Object.freeze(derived),
|
|
302
|
+
expressionOverflows: Object.freeze(expressionOverflows),
|
|
303
|
+
});
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Materializes compiler-approved claim-level measures without mutating caller rows. */
|
|
309
|
+
export function deriveDiagnosticClaimMeasures<
|
|
310
|
+
TRow extends { measures: DiagnosticMeasureValues },
|
|
311
|
+
>(
|
|
312
|
+
rows: readonly TRow[],
|
|
313
|
+
definition: CompiledDiagnosticDefinition,
|
|
314
|
+
): readonly DiagnosticRowWithDerivedMeasures<TRow>[] {
|
|
315
|
+
assertCompiledDiagnosticDefinition(definition);
|
|
316
|
+
return Object.freeze(
|
|
317
|
+
deriveDiagnosticClaimMeasuresWithAudit(rows, definition).map(
|
|
318
|
+
(item) => item.row,
|
|
319
|
+
),
|
|
320
|
+
);
|
|
321
|
+
}
|