@actuarial-ts/core 0.4.0 → 0.6.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.
- package/README.md +65 -215
- package/dist/casualtyDiagnostics.d.ts +19 -49
- package/dist/casualtyDiagnostics.d.ts.map +1 -1
- package/dist/casualtyDiagnostics.js +84 -114
- package/dist/casualtyDiagnostics.js.map +1 -1
- package/dist/diagnosticAggregation.d.ts +29 -0
- package/dist/diagnosticAggregation.d.ts.map +1 -0
- package/dist/diagnosticAggregation.js +50 -0
- package/dist/diagnosticAggregation.js.map +1 -0
- package/dist/diagnosticDefinitions.d.ts +286 -0
- package/dist/diagnosticDefinitions.d.ts.map +1 -0
- package/dist/diagnosticDefinitions.js +1026 -0
- package/dist/diagnosticDefinitions.js.map +1 -0
- package/dist/diagnosticDerivations.d.ts +12 -0
- package/dist/diagnosticDerivations.d.ts.map +1 -0
- package/dist/diagnosticDerivations.js +80 -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 +65 -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 +104 -0
- package/dist/diagnosticExpressions.js.map +1 -0
- package/dist/diagnosticFormulas.d.ts +195 -0
- package/dist/diagnosticFormulas.d.ts.map +1 -0
- package/dist/diagnosticFormulas.js +85 -0
- package/dist/diagnosticFormulas.js.map +1 -0
- package/dist/diagnosticIdentity.d.ts +237 -0
- package/dist/diagnosticIdentity.d.ts.map +1 -0
- package/dist/diagnosticIdentity.js +454 -0
- package/dist/diagnosticIdentity.js.map +1 -0
- package/dist/diagnosticPeriods.d.ts +16 -0
- package/dist/diagnosticPeriods.d.ts.map +1 -0
- package/dist/diagnosticPeriods.js +98 -0
- package/dist/diagnosticPeriods.js.map +1 -0
- package/dist/diagnosticPreparation.d.ts +98 -0
- package/dist/diagnosticPreparation.d.ts.map +1 -0
- package/dist/diagnosticPreparation.js +169 -0
- package/dist/diagnosticPreparation.js.map +1 -0
- package/dist/diagnosticReview.d.ts +44 -0
- package/dist/diagnosticReview.d.ts.map +1 -0
- package/dist/diagnosticReview.js +84 -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 +85 -0
- package/dist/diagnosticRunner.d.ts.map +1 -0
- package/dist/diagnosticRunner.js +117 -0
- package/dist/diagnosticRunner.js.map +1 -0
- package/dist/index.d.ts +24 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +114 -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 +2 -2
- package/src/casualtyDiagnostics.ts +79 -187
- package/src/diagnosticAggregation.ts +79 -0
- package/src/diagnosticDefinitions.ts +1375 -0
- package/src/diagnosticDerivations.ts +89 -0
- package/src/diagnosticExposure.ts +91 -0
- package/src/diagnosticExpressions.ts +173 -0
- package/src/diagnosticFormulas.ts +150 -0
- package/src/diagnosticIdentity.ts +767 -0
- package/src/diagnosticPeriods.ts +117 -0
- package/src/diagnosticPreparation.ts +233 -0
- package/src/diagnosticReview.ts +91 -0
- package/src/diagnosticRules.ts +69 -0
- package/src/diagnosticRunner.ts +110 -0
- package/src/index.ts +156 -1
- package/src/types.ts +161 -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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"metricDiagnostics.d.ts","sourceRoot":"","sources":["../src/metricDiagnostics.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,627 +0,0 @@
|
|
|
1
|
-
import { compareQuarterPeriods, parseQuarterPeriod } from "./periods.js";
|
|
2
|
-
import { ReservingError } from "./types.js";
|
|
3
|
-
import { safeRatio } from "./util.js";
|
|
4
|
-
function nullRecord() {
|
|
5
|
-
return Object.create(null);
|
|
6
|
-
}
|
|
7
|
-
function copyOwnRecord(source) {
|
|
8
|
-
const copy = nullRecord();
|
|
9
|
-
for (const key of Object.keys(source))
|
|
10
|
-
copy[key] = source[key];
|
|
11
|
-
return copy;
|
|
12
|
-
}
|
|
13
|
-
function nullRecordForKeys(keys, value) {
|
|
14
|
-
const record = nullRecord();
|
|
15
|
-
for (const key of keys)
|
|
16
|
-
record[key] = value;
|
|
17
|
-
return record;
|
|
18
|
-
}
|
|
19
|
-
function ownRecordValue(record, key) {
|
|
20
|
-
return record !== undefined && Object.prototype.hasOwnProperty.call(record, key)
|
|
21
|
-
? record[key]
|
|
22
|
-
: undefined;
|
|
23
|
-
}
|
|
24
|
-
function compareCodeUnits(a, b) {
|
|
25
|
-
if (a === b)
|
|
26
|
-
return 0;
|
|
27
|
-
return a < b ? -1 : 1;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Stable natural ordering without the host's locale or ICU data. ASCII digit
|
|
31
|
-
* runs compare by numeric magnitude; all other text compares by UTF-16 code
|
|
32
|
-
* units, with the original spelling as a total-order tie breaker.
|
|
33
|
-
*/
|
|
34
|
-
function compareDeterministicStrings(a, b) {
|
|
35
|
-
if (a === b)
|
|
36
|
-
return 0;
|
|
37
|
-
const left = a.match(/\d+|\D+/g) ?? [""];
|
|
38
|
-
const right = b.match(/\d+|\D+/g) ?? [""];
|
|
39
|
-
const count = Math.min(left.length, right.length);
|
|
40
|
-
for (let index = 0; index < count; index++) {
|
|
41
|
-
const leftPart = left[index];
|
|
42
|
-
const rightPart = right[index];
|
|
43
|
-
const leftNumeric = /^\d+$/.test(leftPart);
|
|
44
|
-
const rightNumeric = /^\d+$/.test(rightPart);
|
|
45
|
-
if (leftNumeric && rightNumeric) {
|
|
46
|
-
const leftSignificant = leftPart.replace(/^0+(?=\d)/, "");
|
|
47
|
-
const rightSignificant = rightPart.replace(/^0+(?=\d)/, "");
|
|
48
|
-
if (leftSignificant.length !== rightSignificant.length) {
|
|
49
|
-
return leftSignificant.length - rightSignificant.length;
|
|
50
|
-
}
|
|
51
|
-
const magnitude = compareCodeUnits(leftSignificant, rightSignificant);
|
|
52
|
-
if (magnitude !== 0)
|
|
53
|
-
return magnitude;
|
|
54
|
-
if (leftPart.length !== rightPart.length)
|
|
55
|
-
return leftPart.length - rightPart.length;
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
const text = compareCodeUnits(leftPart, rightPart);
|
|
59
|
-
if (text !== 0)
|
|
60
|
-
return text;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (left.length !== right.length)
|
|
64
|
-
return left.length - right.length;
|
|
65
|
-
return compareCodeUnits(a, b);
|
|
66
|
-
}
|
|
67
|
-
/** Adapts structured metric warnings to the existing core diagnostic finding vocabulary. */
|
|
68
|
-
export function diagnosticWarningToFinding(warning) {
|
|
69
|
-
return { severity: "warning", code: warning.code, message: warning.message };
|
|
70
|
-
}
|
|
71
|
-
export function aggregateMeasures(rows) {
|
|
72
|
-
const components = nullRecord();
|
|
73
|
-
const names = new Set(rows.flatMap((row) => Object.keys(row)));
|
|
74
|
-
for (const name of names)
|
|
75
|
-
components[name] = { sum: 0, observed: 0, missing: 0, nonFinite: 0 };
|
|
76
|
-
for (const row of rows) {
|
|
77
|
-
for (const name of names) {
|
|
78
|
-
const cell = components[name];
|
|
79
|
-
const value = ownRecordValue(row, name);
|
|
80
|
-
if (value === null || value === undefined)
|
|
81
|
-
cell.missing++;
|
|
82
|
-
else if (!Number.isFinite(value))
|
|
83
|
-
cell.nonFinite++;
|
|
84
|
-
else {
|
|
85
|
-
cell.sum += value;
|
|
86
|
-
cell.observed++;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return { components };
|
|
91
|
-
}
|
|
92
|
-
export function mergeMeasureAggregates(aggregates) {
|
|
93
|
-
const components = nullRecord();
|
|
94
|
-
for (const aggregate of aggregates) {
|
|
95
|
-
for (const [name, value] of Object.entries(aggregate.components)) {
|
|
96
|
-
const target = components[name] ?? (components[name] = { sum: 0, observed: 0, missing: 0, nonFinite: 0 });
|
|
97
|
-
target.sum += value.sum;
|
|
98
|
-
target.observed += value.observed;
|
|
99
|
-
target.missing += value.missing;
|
|
100
|
-
target.nonFinite += value.nonFinite;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return { components };
|
|
104
|
-
}
|
|
105
|
-
export function finalizeMeasureAggregate(aggregate, sparsePolicy = "preserve-null") {
|
|
106
|
-
const measures = nullRecord();
|
|
107
|
-
const warnings = [];
|
|
108
|
-
for (const [name, cell] of Object.entries(aggregate.components)) {
|
|
109
|
-
if (cell.nonFinite > 0) {
|
|
110
|
-
measures[name] = null;
|
|
111
|
-
warnings.push({
|
|
112
|
-
code: "NON_FINITE_COMPONENT",
|
|
113
|
-
component: name,
|
|
114
|
-
message: `${name} contains ${cell.nonFinite} non-finite value(s)`,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
else if (!Number.isFinite(cell.sum)) {
|
|
118
|
-
measures[name] = null;
|
|
119
|
-
warnings.push({
|
|
120
|
-
code: "NON_FINITE_COMPONENT",
|
|
121
|
-
component: name,
|
|
122
|
-
message: `${name} aggregate overflowed or is non-finite`,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
else if (cell.missing > 0 && sparsePolicy === "preserve-null") {
|
|
126
|
-
measures[name] = null;
|
|
127
|
-
warnings.push({
|
|
128
|
-
code: "MISSING_COMPONENT",
|
|
129
|
-
component: name,
|
|
130
|
-
message: `${name} is incomplete: ${cell.missing} contributing value(s) are missing`,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
measures[name] = cell.sum;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return { measures, warnings };
|
|
138
|
-
}
|
|
139
|
-
export function measureExpressionComponents(expression) {
|
|
140
|
-
if (expression.op === "measure")
|
|
141
|
-
return [expression.measure];
|
|
142
|
-
if (expression.op === "subtract") {
|
|
143
|
-
return [...measureExpressionComponents(expression.left), ...measureExpressionComponents(expression.right)];
|
|
144
|
-
}
|
|
145
|
-
return expression.terms.flatMap(measureExpressionComponents);
|
|
146
|
-
}
|
|
147
|
-
function evaluateExpression(expression, measures) {
|
|
148
|
-
if (expression.op === "measure") {
|
|
149
|
-
const value = ownRecordValue(measures, expression.measure);
|
|
150
|
-
return value !== null && value !== undefined && Number.isFinite(value) ? value : null;
|
|
151
|
-
}
|
|
152
|
-
if (expression.op === "subtract") {
|
|
153
|
-
const left = evaluateExpression(expression.left, measures);
|
|
154
|
-
const right = evaluateExpression(expression.right, measures);
|
|
155
|
-
return left === null || right === null ? null : left - right;
|
|
156
|
-
}
|
|
157
|
-
let total = 0;
|
|
158
|
-
for (const term of expression.terms) {
|
|
159
|
-
const value = evaluateExpression(term, measures);
|
|
160
|
-
if (value === null)
|
|
161
|
-
return null;
|
|
162
|
-
total += value;
|
|
163
|
-
}
|
|
164
|
-
return total;
|
|
165
|
-
}
|
|
166
|
-
export function evaluateMetric(definition, components, inheritedWarnings = []) {
|
|
167
|
-
if (!Number.isFinite(definition.scale)) {
|
|
168
|
-
throw new ReservingError("BAD_RATIO", `Metric ${definition.id} scale must be finite`);
|
|
169
|
-
}
|
|
170
|
-
const required = new Set([
|
|
171
|
-
...definition.requiredComponents,
|
|
172
|
-
...measureExpressionComponents(definition.numerator),
|
|
173
|
-
...measureExpressionComponents(definition.denominator),
|
|
174
|
-
]);
|
|
175
|
-
const rawComponents = nullRecord();
|
|
176
|
-
const warnings = inheritedWarnings.filter((warning) => warning.component === undefined ||
|
|
177
|
-
required.has(warning.component) ||
|
|
178
|
-
warning.code === "INCOMPLETE_EXPOSURE" ||
|
|
179
|
-
warning.code === "CONFLICTING_EXPOSURE");
|
|
180
|
-
for (const name of required) {
|
|
181
|
-
const value = ownRecordValue(components, name);
|
|
182
|
-
rawComponents[name] = value !== null && value !== undefined && Number.isFinite(value) ? value : null;
|
|
183
|
-
if (value === null || value === undefined) {
|
|
184
|
-
if (!warnings.some((w) => w.code === "MISSING_COMPONENT" && w.component === name)) {
|
|
185
|
-
warnings.push({ code: "MISSING_COMPONENT", component: name, message: `${name} is missing` });
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
else if (!Number.isFinite(value)) {
|
|
189
|
-
if (!warnings.some((w) => w.code === "NON_FINITE_COMPONENT" && w.component === name)) {
|
|
190
|
-
warnings.push({ code: "NON_FINITE_COMPONENT", component: name, message: `${name} is not finite` });
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
const evaluatedNumerator = evaluateExpression(definition.numerator, components);
|
|
195
|
-
const numerator = evaluatedNumerator !== null && Number.isFinite(evaluatedNumerator)
|
|
196
|
-
? evaluatedNumerator
|
|
197
|
-
: null;
|
|
198
|
-
if (evaluatedNumerator !== null && !Number.isFinite(evaluatedNumerator)) {
|
|
199
|
-
warnings.push({
|
|
200
|
-
code: "NON_FINITE_NUMERATOR",
|
|
201
|
-
message: `${definition.numeratorLabel} overflowed or is non-finite`,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
const evaluatedDenominator = evaluateExpression(definition.denominator, components);
|
|
205
|
-
const denominator = evaluatedDenominator !== null && Number.isFinite(evaluatedDenominator)
|
|
206
|
-
? evaluatedDenominator
|
|
207
|
-
: null;
|
|
208
|
-
let value = null;
|
|
209
|
-
if (denominator === null || !Number.isFinite(denominator) || denominator <= 0) {
|
|
210
|
-
warnings.push({
|
|
211
|
-
code: "INVALID_DENOMINATOR",
|
|
212
|
-
message: `${definition.denominatorLabel} must be finite and greater than zero`,
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
else if (numerator !== null && Number.isFinite(numerator)) {
|
|
216
|
-
const ratio = safeRatio(numerator, denominator);
|
|
217
|
-
if (ratio === null) {
|
|
218
|
-
warnings.push({
|
|
219
|
-
code: "NON_FINITE_RESULT",
|
|
220
|
-
message: `${definition.displayName} ratio overflowed or is non-finite`,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
const scaled = ratio * definition.scale;
|
|
225
|
-
if (Number.isFinite(scaled))
|
|
226
|
-
value = scaled;
|
|
227
|
-
else {
|
|
228
|
-
warnings.push({
|
|
229
|
-
code: "NON_FINITE_RESULT",
|
|
230
|
-
message: `${definition.displayName} scaled result overflowed or is non-finite`,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
for (const rule of definition.warningRules ?? []) {
|
|
236
|
-
if (rule.when === "numerator-greater-than-denominator" && numerator !== null && denominator !== null) {
|
|
237
|
-
const tolerance = rule.tolerance ?? 0;
|
|
238
|
-
if (numerator - denominator > tolerance * Math.max(1, Math.abs(numerator), Math.abs(denominator))) {
|
|
239
|
-
warnings.push({ code: rule.code, message: rule.message });
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (definition.evaluateWarnings) {
|
|
244
|
-
warnings.push(...definition.evaluateWarnings({
|
|
245
|
-
definition,
|
|
246
|
-
components: rawComponents,
|
|
247
|
-
rawNumerator: numerator,
|
|
248
|
-
rawDenominator: denominator,
|
|
249
|
-
value,
|
|
250
|
-
}));
|
|
251
|
-
}
|
|
252
|
-
return {
|
|
253
|
-
metricId: definition.id,
|
|
254
|
-
metricVersion: definition.version,
|
|
255
|
-
value,
|
|
256
|
-
rawNumerator: numerator,
|
|
257
|
-
rawDenominator: denominator,
|
|
258
|
-
numeratorLabel: definition.numeratorLabel,
|
|
259
|
-
denominatorLabel: definition.denominatorLabel,
|
|
260
|
-
unit: definition.unit,
|
|
261
|
-
scale: definition.scale,
|
|
262
|
-
basis: definition.basis,
|
|
263
|
-
rawComponents,
|
|
264
|
-
warnings,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
function evaluateLayerExpression(expression, measures) {
|
|
268
|
-
if (expression.op === "measure") {
|
|
269
|
-
const value = ownRecordValue(measures, expression.measure);
|
|
270
|
-
return value !== null && value !== undefined && Number.isFinite(value) ? value : null;
|
|
271
|
-
}
|
|
272
|
-
if (expression.op === "claim-cap") {
|
|
273
|
-
if (!Number.isFinite(expression.limit) || expression.limit <= 0) {
|
|
274
|
-
throw new ReservingError("BAD_CAP", `Claim-level layer cap must be positive; got ${expression.limit}`);
|
|
275
|
-
}
|
|
276
|
-
const value = ownRecordValue(measures, expression.measure);
|
|
277
|
-
return value !== null && value !== undefined && Number.isFinite(value)
|
|
278
|
-
? Math.min(value, expression.limit)
|
|
279
|
-
: null;
|
|
280
|
-
}
|
|
281
|
-
let total = 0;
|
|
282
|
-
for (const term of expression.terms) {
|
|
283
|
-
const value = evaluateLayerExpression(term, measures);
|
|
284
|
-
if (value === null)
|
|
285
|
-
return null;
|
|
286
|
-
total += value;
|
|
287
|
-
}
|
|
288
|
-
return total;
|
|
289
|
-
}
|
|
290
|
-
/** Applies claim caps before aggregation; this function intentionally accepts claim rows, not aggregates. */
|
|
291
|
-
export function deriveAmountLayers(rows, layers) {
|
|
292
|
-
return rows.map((row) => {
|
|
293
|
-
const measures = copyOwnRecord(row.measures);
|
|
294
|
-
for (const layer of layers) {
|
|
295
|
-
measures[layer.paidMeasure] = evaluateLayerExpression(layer.paid, row.measures);
|
|
296
|
-
measures[layer.incurredMeasure] = evaluateLayerExpression(layer.incurred, row.measures);
|
|
297
|
-
}
|
|
298
|
-
return { dimensions: row.dimensions, measures };
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
function rowIncluded(row, filter) {
|
|
302
|
-
if (!filter)
|
|
303
|
-
return true;
|
|
304
|
-
if (filter.groups && !filter.groups.includes(row.group))
|
|
305
|
-
return false;
|
|
306
|
-
if (filter.origins && !filter.origins.includes(row.origin))
|
|
307
|
-
return false;
|
|
308
|
-
if (filter.originFrom && periodCompare(row.origin, filter.originFrom) < 0)
|
|
309
|
-
return false;
|
|
310
|
-
if (filter.originThrough && periodCompare(row.origin, filter.originThrough) > 0)
|
|
311
|
-
return false;
|
|
312
|
-
if (filter.valuations && !filter.valuations.includes(row.valuation))
|
|
313
|
-
return false;
|
|
314
|
-
if (filter.valuationFrom && periodCompare(row.valuation, filter.valuationFrom) < 0)
|
|
315
|
-
return false;
|
|
316
|
-
if (filter.valuationThrough && periodCompare(row.valuation, filter.valuationThrough) > 0)
|
|
317
|
-
return false;
|
|
318
|
-
if (filter.policyPeriods && (row.policyPeriod === undefined || !filter.policyPeriods.includes(row.policyPeriod)))
|
|
319
|
-
return false;
|
|
320
|
-
if (filter.minAgeMonths !== undefined && row.ageMonths < filter.minAgeMonths)
|
|
321
|
-
return false;
|
|
322
|
-
if (filter.maxAgeMonths !== undefined && row.ageMonths > filter.maxAgeMonths)
|
|
323
|
-
return false;
|
|
324
|
-
return true;
|
|
325
|
-
}
|
|
326
|
-
function sameMeasures(a, b) {
|
|
327
|
-
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
328
|
-
for (const key of keys) {
|
|
329
|
-
if (!Object.is(ownRecordValue(a, key) ?? null, ownRecordValue(b, key) ?? null))
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
return true;
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Reconciles stable exposure keys before aggregation. Identical valuation
|
|
336
|
-
* copies collapse to one row. Conflicting copies become one incomplete/null
|
|
337
|
-
* row so exposure-based metrics fail closed rather than using a partial base.
|
|
338
|
-
*/
|
|
339
|
-
export function reconcileDiagnosticExposureKeys(rows) {
|
|
340
|
-
const seen = new Map();
|
|
341
|
-
const findings = [];
|
|
342
|
-
for (const row of rows) {
|
|
343
|
-
const previous = seen.get(row.key);
|
|
344
|
-
if (!previous) {
|
|
345
|
-
seen.set(row.key, { ...row, measures: copyOwnRecord(row.measures) });
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
const consistent = previous.group === row.group &&
|
|
349
|
-
previous.origin === row.origin &&
|
|
350
|
-
sameMeasures(previous.measures, row.measures) &&
|
|
351
|
-
(previous.complete ?? true) === (row.complete ?? true);
|
|
352
|
-
if (consistent) {
|
|
353
|
-
findings.push({
|
|
354
|
-
code: "DUPLICATE_EXPOSURE_KEY",
|
|
355
|
-
exposureKey: row.key,
|
|
356
|
-
group: row.group,
|
|
357
|
-
origin: row.origin,
|
|
358
|
-
message: `Exposure key ${row.key} repeats; identical copies were counted once`,
|
|
359
|
-
});
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
const names = new Set([...Object.keys(previous.measures), ...Object.keys(row.measures)]);
|
|
363
|
-
seen.set(row.key, {
|
|
364
|
-
...previous,
|
|
365
|
-
complete: false,
|
|
366
|
-
measures: nullRecordForKeys(names, null),
|
|
367
|
-
});
|
|
368
|
-
if (!findings.some((finding) => finding.code === "CONFLICTING_EXPOSURE" && finding.exposureKey === row.key)) {
|
|
369
|
-
findings.push({
|
|
370
|
-
code: "CONFLICTING_EXPOSURE",
|
|
371
|
-
exposureKey: row.key,
|
|
372
|
-
group: previous.group,
|
|
373
|
-
origin: previous.origin,
|
|
374
|
-
message: `Exposure key ${row.key} repeats with inconsistent attributes or measures`,
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return { exposures: [...seen.values()], findings };
|
|
379
|
-
}
|
|
380
|
-
function periodCompare(a, b) {
|
|
381
|
-
try {
|
|
382
|
-
parseQuarterPeriod(a);
|
|
383
|
-
parseQuarterPeriod(b);
|
|
384
|
-
return compareQuarterPeriods(a, b);
|
|
385
|
-
}
|
|
386
|
-
catch {
|
|
387
|
-
return compareDeterministicStrings(a, b);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
function aggregateExposures(rows, filter, groupMap, expectedSources) {
|
|
391
|
-
const eligible = rows.filter((row) => (!filter?.groups || filter.groups.includes(row.group)) &&
|
|
392
|
-
(!filter?.origins || filter.origins.includes(row.origin)) &&
|
|
393
|
-
(!filter?.originFrom || periodCompare(row.origin, filter.originFrom) >= 0) &&
|
|
394
|
-
(!filter?.originThrough || periodCompare(row.origin, filter.originThrough) <= 0) &&
|
|
395
|
-
(row.valuation === undefined || !filter?.valuations || filter.valuations.includes(row.valuation)) &&
|
|
396
|
-
(row.valuation === undefined || !filter?.valuationFrom || periodCompare(row.valuation, filter.valuationFrom) >= 0) &&
|
|
397
|
-
(row.valuation === undefined || !filter?.valuationThrough || periodCompare(row.valuation, filter.valuationThrough) <= 0));
|
|
398
|
-
const reconciled = reconcileDiagnosticExposureKeys(eligible);
|
|
399
|
-
const findingsByKey = new Map();
|
|
400
|
-
for (const finding of reconciled.findings) {
|
|
401
|
-
const list = findingsByKey.get(finding.exposureKey) ?? [];
|
|
402
|
-
list.push(finding);
|
|
403
|
-
findingsByKey.set(finding.exposureKey, list);
|
|
404
|
-
}
|
|
405
|
-
const byCell = new Map();
|
|
406
|
-
const sourceKeysByCell = new Map();
|
|
407
|
-
for (const row of reconciled.exposures) {
|
|
408
|
-
const group = ownRecordValue(groupMap, row.group) ?? row.group;
|
|
409
|
-
const key = `${group}\u0000${row.origin}`;
|
|
410
|
-
const list = byCell.get(key) ?? [];
|
|
411
|
-
list.push(row);
|
|
412
|
-
byCell.set(key, list);
|
|
413
|
-
const sourceKeys = sourceKeysByCell.get(key) ?? new Set();
|
|
414
|
-
sourceKeys.add(`${row.group}\u0000${row.origin}`);
|
|
415
|
-
sourceKeysByCell.set(key, sourceKeys);
|
|
416
|
-
}
|
|
417
|
-
const result = new Map();
|
|
418
|
-
const cellKeys = new Set([...byCell.keys(), ...expectedSources.keys()]);
|
|
419
|
-
for (const key of cellKeys) {
|
|
420
|
-
const cellRows = byCell.get(key) ?? [];
|
|
421
|
-
const warnings = [];
|
|
422
|
-
const nullComponents = new Set();
|
|
423
|
-
const safeRows = cellRows.map((row) => {
|
|
424
|
-
warnings.push(...(findingsByKey.get(row.key) ?? []).map((finding) => ({
|
|
425
|
-
code: finding.code,
|
|
426
|
-
exposureKey: finding.exposureKey,
|
|
427
|
-
message: finding.message,
|
|
428
|
-
})));
|
|
429
|
-
if (row.complete === false) {
|
|
430
|
-
const componentNames = Object.keys(row.measures);
|
|
431
|
-
for (const name of componentNames)
|
|
432
|
-
nullComponents.add(name);
|
|
433
|
-
warnings.push({
|
|
434
|
-
code: "INCOMPLETE_EXPOSURE",
|
|
435
|
-
exposureKey: row.key,
|
|
436
|
-
message: `Exposure key ${row.key} is marked incomplete`,
|
|
437
|
-
});
|
|
438
|
-
return nullRecordForKeys(componentNames, null);
|
|
439
|
-
}
|
|
440
|
-
return row.measures;
|
|
441
|
-
});
|
|
442
|
-
const missingSourceKeys = [...(expectedSources.get(key) ?? [])]
|
|
443
|
-
.filter((sourceKey) => !sourceKeysByCell.get(key)?.has(sourceKey));
|
|
444
|
-
if (missingSourceKeys.length > 0) {
|
|
445
|
-
for (const sourceKey of missingSourceKeys) {
|
|
446
|
-
const [sourceGroup, origin] = sourceKey.split("\u0000");
|
|
447
|
-
warnings.push({
|
|
448
|
-
code: "INCOMPLETE_EXPOSURE",
|
|
449
|
-
message: `No exposure row was supplied for source group ${sourceGroup} and origin ${origin}`,
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
const exposureMeasures = new Set(cellRows.flatMap((row) => Object.keys(row.measures)));
|
|
453
|
-
if (exposureMeasures.size > 0) {
|
|
454
|
-
for (const name of exposureMeasures)
|
|
455
|
-
nullComponents.add(name);
|
|
456
|
-
safeRows.push(nullRecordForKeys(exposureMeasures, null));
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
const aggregate = aggregateMeasures(safeRows);
|
|
460
|
-
for (const [name, cell] of Object.entries(aggregate.components)) {
|
|
461
|
-
if (cell.missing > 0)
|
|
462
|
-
nullComponents.add(name);
|
|
463
|
-
}
|
|
464
|
-
result.set(key, { aggregate, warnings, nullComponents });
|
|
465
|
-
}
|
|
466
|
-
return result;
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Generic quarterly diagnostics engine. Loss rows aggregate by
|
|
470
|
-
* group/origin/valuation; exposure rows aggregate once by stable exposure key;
|
|
471
|
-
* every metric then divides the aggregated expressions exactly once.
|
|
472
|
-
*/
|
|
473
|
-
export function runMetricDiagnostics(input) {
|
|
474
|
-
const sparsePolicy = input.sparsePolicy ?? "preserve-null";
|
|
475
|
-
const lossIds = new Set();
|
|
476
|
-
for (const row of input.losses) {
|
|
477
|
-
if (lossIds.has(row.id))
|
|
478
|
-
throw new ReservingError("BAD_TABLE", `Duplicate loss row id ${row.id}`);
|
|
479
|
-
lossIds.add(row.id);
|
|
480
|
-
}
|
|
481
|
-
const metricIds = new Set();
|
|
482
|
-
for (const metric of input.metrics) {
|
|
483
|
-
if (metricIds.has(metric.id))
|
|
484
|
-
throw new ReservingError("BAD_TABLE", `Duplicate metric id ${metric.id}`);
|
|
485
|
-
metricIds.add(metric.id);
|
|
486
|
-
}
|
|
487
|
-
const lossBuckets = new Map();
|
|
488
|
-
const expectedExposureSources = new Map();
|
|
489
|
-
for (const row of input.losses) {
|
|
490
|
-
if (!rowIncluded(row, input.filter))
|
|
491
|
-
continue;
|
|
492
|
-
if (!Number.isFinite(row.ageMonths) || row.ageMonths < 0) {
|
|
493
|
-
throw new ReservingError("BAD_DATE", `Loss row ${row.id} has invalid development age ${row.ageMonths}`);
|
|
494
|
-
}
|
|
495
|
-
const group = ownRecordValue(input.groupMap, row.group) ?? row.group;
|
|
496
|
-
if (input.exposures !== undefined) {
|
|
497
|
-
const exposureCellKey = `${group}\u0000${row.origin}`;
|
|
498
|
-
const sourceKeys = expectedExposureSources.get(exposureCellKey) ?? new Set();
|
|
499
|
-
sourceKeys.add(`${row.group}\u0000${row.origin}`);
|
|
500
|
-
expectedExposureSources.set(exposureCellKey, sourceKeys);
|
|
501
|
-
}
|
|
502
|
-
const key = `${group}\u0000${row.origin}\u0000${row.valuation}`;
|
|
503
|
-
const bucket = lossBuckets.get(key);
|
|
504
|
-
if (bucket) {
|
|
505
|
-
if (bucket.ageMonths !== row.ageMonths) {
|
|
506
|
-
throw new ReservingError("BAD_DATE", `Aggregate snapshot ${group}/${row.origin}/${row.valuation} has inconsistent ages ${bucket.ageMonths} and ${row.ageMonths}`);
|
|
507
|
-
}
|
|
508
|
-
bucket.rows.push(row.measures);
|
|
509
|
-
}
|
|
510
|
-
else {
|
|
511
|
-
const dimensions = ownRecordValue(input.groupDimensions, group) ?? (group === row.group ? row.dimensions : undefined);
|
|
512
|
-
lossBuckets.set(key, {
|
|
513
|
-
group,
|
|
514
|
-
...(dimensions !== undefined ? { dimensions } : {}),
|
|
515
|
-
origin: row.origin,
|
|
516
|
-
valuation: row.valuation,
|
|
517
|
-
ageMonths: row.ageMonths,
|
|
518
|
-
rows: [row.measures],
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
const exposures = aggregateExposures(input.exposures ?? [], input.filter, input.groupMap, expectedExposureSources);
|
|
523
|
-
const emergence = [];
|
|
524
|
-
for (const bucket of lossBuckets.values()) {
|
|
525
|
-
const loss = aggregateMeasures(bucket.rows);
|
|
526
|
-
const exposure = exposures.get(`${bucket.group}\u0000${bucket.origin}`);
|
|
527
|
-
const aggregate = exposure ? mergeMeasureAggregates([loss, exposure.aggregate]) : loss;
|
|
528
|
-
const finalized = finalizeMeasureAggregate(aggregate, sparsePolicy);
|
|
529
|
-
const componentWarnings = [...finalized.warnings, ...(exposure?.warnings ?? [])];
|
|
530
|
-
for (const name of exposure?.nullComponents ?? []) {
|
|
531
|
-
finalized.measures[name] = null;
|
|
532
|
-
if (!componentWarnings.some((warning) => warning.code === "MISSING_COMPONENT" && warning.component === name)) {
|
|
533
|
-
componentWarnings.push({
|
|
534
|
-
code: "MISSING_COMPONENT",
|
|
535
|
-
component: name,
|
|
536
|
-
message: `${name} exposure is incomplete and remains null`,
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
const metrics = nullRecord();
|
|
541
|
-
for (const definition of input.metrics) {
|
|
542
|
-
metrics[definition.id] = evaluateMetric(definition, finalized.measures, componentWarnings);
|
|
543
|
-
}
|
|
544
|
-
emergence.push({
|
|
545
|
-
group: bucket.group,
|
|
546
|
-
...(bucket.dimensions !== undefined ? { dimensions: bucket.dimensions } : {}),
|
|
547
|
-
origin: bucket.origin,
|
|
548
|
-
valuation: bucket.valuation,
|
|
549
|
-
ageMonths: bucket.ageMonths,
|
|
550
|
-
components: finalized.measures,
|
|
551
|
-
componentWarnings,
|
|
552
|
-
metrics,
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
emergence.sort((a, b) => compareDeterministicStrings(a.group, b.group) || periodCompare(a.origin, b.origin) || a.ageMonths - b.ageMonths || periodCompare(a.valuation, b.valuation));
|
|
556
|
-
const triangles = [];
|
|
557
|
-
for (const group of [...new Set(emergence.map((point) => point.group))].sort(compareDeterministicStrings)) {
|
|
558
|
-
const groupPoints = emergence.filter((point) => point.group === group);
|
|
559
|
-
for (const metric of input.metrics)
|
|
560
|
-
triangles.push(metricTriangleFromEmergence(groupPoints, metric.id));
|
|
561
|
-
}
|
|
562
|
-
const latestByOrigin = new Map();
|
|
563
|
-
for (const point of emergence) {
|
|
564
|
-
const key = `${point.group}\u0000${point.origin}`;
|
|
565
|
-
const previous = latestByOrigin.get(key);
|
|
566
|
-
if (!previous || point.ageMonths > previous.ageMonths)
|
|
567
|
-
latestByOrigin.set(key, point);
|
|
568
|
-
}
|
|
569
|
-
return {
|
|
570
|
-
emergence,
|
|
571
|
-
triangles,
|
|
572
|
-
latestDiagonal: [...latestByOrigin.values()],
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
export function metricTriangleFromEmergence(points, metricId) {
|
|
576
|
-
const groups = new Set(points.map((point) => point.group));
|
|
577
|
-
if (groups.size > 1)
|
|
578
|
-
throw new ReservingError("SHAPE", "A metric triangle can contain only one group");
|
|
579
|
-
const origins = [...new Set(points.map((point) => point.origin))].sort(periodCompare);
|
|
580
|
-
const ages = [...new Set(points.map((point) => point.ageMonths))].sort((a, b) => a - b);
|
|
581
|
-
const originIndex = new Map(origins.map((origin, index) => [origin, index]));
|
|
582
|
-
const ageIndex = new Map(ages.map((age, index) => [age, index]));
|
|
583
|
-
const cells = origins.map(() => ages.map(() => null));
|
|
584
|
-
for (const point of points) {
|
|
585
|
-
const metric = ownRecordValue(point.metrics, metricId);
|
|
586
|
-
if (!metric)
|
|
587
|
-
throw new ReservingError("BAD_TABLE", `Emergence point has no metric ${metricId}`);
|
|
588
|
-
const i = originIndex.get(point.origin);
|
|
589
|
-
const j = ageIndex.get(point.ageMonths);
|
|
590
|
-
if (cells[i][j] !== null) {
|
|
591
|
-
throw new ReservingError("SHAPE", `Duplicate diagnostic cell after aggregation for group ${point.group}, origin ${point.origin}, age ${point.ageMonths}`);
|
|
592
|
-
}
|
|
593
|
-
cells[i][j] = metric;
|
|
594
|
-
}
|
|
595
|
-
return {
|
|
596
|
-
group: points[0]?.group ?? "",
|
|
597
|
-
metricId,
|
|
598
|
-
origins,
|
|
599
|
-
ages,
|
|
600
|
-
values: cells.map((row) => row.map((cell) => cell?.value ?? null)),
|
|
601
|
-
cells,
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
export function sameMaturity(result, ageMonths, groups) {
|
|
605
|
-
return result.emergence.filter((point) => point.ageMonths === ageMonths && (!groups || groups.includes(point.group)));
|
|
606
|
-
}
|
|
607
|
-
/** Latest maturity present in every selected group; selection is caller-defined. */
|
|
608
|
-
export function commonMaturity(result, groups) {
|
|
609
|
-
if (groups.length === 0)
|
|
610
|
-
return { ageMonths: null, points: [] };
|
|
611
|
-
let common = null;
|
|
612
|
-
for (const group of groups) {
|
|
613
|
-
const ages = new Set(result.emergence.filter((point) => point.group === group).map((point) => point.ageMonths));
|
|
614
|
-
if (common === null)
|
|
615
|
-
common = ages;
|
|
616
|
-
else {
|
|
617
|
-
const intersection = new Set();
|
|
618
|
-
for (const age of common)
|
|
619
|
-
if (ages.has(age))
|
|
620
|
-
intersection.add(age);
|
|
621
|
-
common = intersection;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
const ageMonths = common && common.size > 0 ? Math.max(...common) : null;
|
|
625
|
-
return { ageMonths, points: ageMonths === null ? [] : sameMaturity(result, ageMonths, groups) };
|
|
626
|
-
}
|
|
627
|
-
//# sourceMappingURL=metricDiagnostics.js.map
|