@actuarial-ts/core 0.10.0 → 0.11.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 +9 -7
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +64 -5
- package/dist/canonical.js.map +1 -1
- package/dist/customizationContracts.d.ts +10 -1
- package/dist/customizationContracts.d.ts.map +1 -1
- package/dist/descriptiveStatistics.js +1 -1
- package/dist/descriptiveStatistics.js.map +1 -1
- package/dist/diagnosticAggregation.d.ts +26 -0
- package/dist/diagnosticAggregation.d.ts.map +1 -1
- package/dist/diagnosticAggregation.js +125 -11
- package/dist/diagnosticAggregation.js.map +1 -1
- package/dist/diagnosticAuditText.d.ts +11 -0
- package/dist/diagnosticAuditText.d.ts.map +1 -0
- package/dist/diagnosticAuditText.js +179 -0
- package/dist/diagnosticAuditText.js.map +1 -0
- package/dist/diagnosticFormulas.d.ts.map +1 -1
- package/dist/diagnosticFormulas.js +71 -12
- package/dist/diagnosticFormulas.js.map +1 -1
- package/dist/diagnosticFreeze.d.ts +15 -1
- package/dist/diagnosticFreeze.d.ts.map +1 -1
- package/dist/diagnosticFreeze.js +82 -31
- package/dist/diagnosticFreeze.js.map +1 -1
- package/dist/diagnosticKeys.d.ts +9 -0
- package/dist/diagnosticKeys.d.ts.map +1 -0
- package/dist/diagnosticKeys.js +20 -0
- package/dist/diagnosticKeys.js.map +1 -0
- package/dist/diagnosticOrdering.d.ts.map +1 -1
- package/dist/diagnosticOrdering.js +9 -2
- package/dist/diagnosticOrdering.js.map +1 -1
- package/dist/diagnosticPeriods.d.ts.map +1 -1
- package/dist/diagnosticPeriods.js +19 -1
- package/dist/diagnosticPeriods.js.map +1 -1
- package/dist/diagnosticPreparation.d.ts +19 -0
- package/dist/diagnosticPreparation.d.ts.map +1 -1
- package/dist/diagnosticPreparation.js +544 -231
- package/dist/diagnosticPreparation.js.map +1 -1
- package/dist/diagnosticReview.d.ts.map +1 -1
- package/dist/diagnosticReview.js +419 -186
- package/dist/diagnosticReview.js.map +1 -1
- package/dist/diagnosticReviewSources.d.ts +6 -1
- package/dist/diagnosticReviewSources.d.ts.map +1 -1
- package/dist/diagnosticReviewSources.js +59 -14
- package/dist/diagnosticReviewSources.js.map +1 -1
- package/dist/diagnosticReviewStore.d.ts.map +1 -1
- package/dist/diagnosticReviewStore.js +48 -7
- package/dist/diagnosticReviewStore.js.map +1 -1
- package/dist/diagnosticRunner.d.ts.map +1 -1
- package/dist/diagnosticRunner.js +112 -37
- package/dist/diagnosticRunner.js.map +1 -1
- package/dist/diagnosticRuntime.d.ts.map +1 -1
- package/dist/diagnosticRuntime.js +11 -1
- package/dist/diagnosticRuntime.js.map +1 -1
- package/dist/diagnosticSourceOrdering.d.ts +27 -0
- package/dist/diagnosticSourceOrdering.d.ts.map +1 -1
- package/dist/diagnosticSourceOrdering.js +98 -12
- package/dist/diagnosticSourceOrdering.js.map +1 -1
- package/dist/mack.d.ts.map +1 -1
- package/dist/mack.js +35 -0
- package/dist/mack.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/canonical.ts +63 -5
- package/src/customizationContracts.ts +10 -1
- package/src/descriptiveStatistics.ts +1 -1
- package/src/diagnosticAggregation.ts +173 -9
- package/src/diagnosticAuditText.ts +187 -0
- package/src/diagnosticFormulas.ts +77 -27
- package/src/diagnosticFreeze.ts +75 -30
- package/src/diagnosticKeys.ts +22 -0
- package/src/diagnosticOrdering.ts +9 -8
- package/src/diagnosticPeriods.ts +21 -1
- package/src/diagnosticPreparation.ts +624 -272
- package/src/diagnosticReview.ts +566 -268
- package/src/diagnosticReviewSources.ts +69 -18
- package/src/diagnosticReviewStore.ts +48 -6
- package/src/diagnosticRunner.ts +133 -54
- package/src/diagnosticRuntime.ts +11 -1
- package/src/diagnosticSourceOrdering.ts +115 -11
- package/src/mack.ts +41 -0
- package/src/types.ts +1 -0
- package/src/version.ts +1 -1
|
@@ -30,21 +30,125 @@ export function compareDiagnosticSourceLocations(
|
|
|
30
30
|
);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
const SCHEMA_FIELDS = [
|
|
34
|
+
"artifactId",
|
|
35
|
+
"sourceFile",
|
|
36
|
+
"sourceSheet",
|
|
37
|
+
"sourceRow",
|
|
38
|
+
"sourceCell",
|
|
39
|
+
] as const;
|
|
40
|
+
// Canonical JSON of an object always starts with "{"; this prefix cannot.
|
|
41
|
+
const SCHEMA_KEY_PREFIX = "";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Exact dedupe key: two source locations receive the same key exactly when
|
|
45
|
+
* their canonical JSON is identical. A location holding only the declared
|
|
46
|
+
* fields with string or finite-number values takes a cheap length-prefixed
|
|
47
|
+
* encoding; every other shape (extra own data, null or non-string values,
|
|
48
|
+
* foreign prototypes) defers to `canonicalJson`, including its error
|
|
49
|
+
* behaviour. Properties are read exactly as `canonicalJson` reads them. The
|
|
50
|
+
* key is an internal lookup token, never emitted or hashed into an identity.
|
|
51
|
+
*/
|
|
52
|
+
export function diagnosticSourceLocationKey(
|
|
53
|
+
value: DiagnosticSourceLocation,
|
|
54
|
+
): string {
|
|
55
|
+
const record = value as unknown as Record<string, unknown>;
|
|
56
|
+
const prototype: unknown = Object.getPrototypeOf(record);
|
|
57
|
+
if (prototype === Object.prototype || prototype === null) {
|
|
58
|
+
let key = SCHEMA_KEY_PREFIX;
|
|
59
|
+
let matched = 0;
|
|
60
|
+
for (let index = 0; index < SCHEMA_FIELDS.length; index++) {
|
|
61
|
+
const field = SCHEMA_FIELDS[index]!;
|
|
62
|
+
if (!Object.hasOwn(record, field)) {
|
|
63
|
+
key += "-";
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
matched++;
|
|
67
|
+
const item: unknown = record[field];
|
|
68
|
+
if (typeof item === "string") key += `s${item.length}:${item}`;
|
|
69
|
+
else if (
|
|
70
|
+
field === "sourceRow" &&
|
|
71
|
+
typeof item === "number" &&
|
|
72
|
+
Number.isFinite(item)
|
|
73
|
+
)
|
|
74
|
+
key += `n${String(normalizeDiagnosticNumber(item))}`;
|
|
75
|
+
else return canonicalJson(value);
|
|
76
|
+
}
|
|
77
|
+
if (Object.keys(record).length === matched) return key;
|
|
78
|
+
}
|
|
79
|
+
return canonicalJson(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface NormalizedSource {
|
|
83
|
+
readonly key: string;
|
|
84
|
+
readonly value: DiagnosticSourceLocation;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Invocation-scoped memo: one normalized snapshot and key per input object.
|
|
88
|
+
* Holders must release it with the invocation; it must never outlive or be
|
|
89
|
+
* shared across public calls, and inputs must be immutable while it is live.
|
|
90
|
+
*/
|
|
91
|
+
export type DiagnosticSourceLocationMemo = Map<
|
|
92
|
+
DiagnosticSourceLocation,
|
|
93
|
+
NormalizedSource
|
|
94
|
+
>;
|
|
95
|
+
|
|
96
|
+
function normalizedSource(value: DiagnosticSourceLocation): NormalizedSource {
|
|
97
|
+
const normalized = {
|
|
98
|
+
...value,
|
|
99
|
+
...(value.sourceRow === undefined
|
|
100
|
+
? {}
|
|
101
|
+
: { sourceRow: normalizeDiagnosticNumber(value.sourceRow) }),
|
|
102
|
+
};
|
|
103
|
+
return { key: diagnosticSourceLocationKey(normalized), value: normalized };
|
|
104
|
+
}
|
|
105
|
+
|
|
33
106
|
/** Exact-deduplicates, snapshots, and contract-sorts source provenance. */
|
|
34
107
|
export function normalizeDiagnosticSourceLocations(
|
|
35
108
|
values: readonly (DiagnosticSourceLocation | null | undefined)[],
|
|
36
109
|
): DiagnosticSourceLocation[] {
|
|
110
|
+
return normalizeDiagnosticSourceLocationsMemoized(values);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* `normalizeDiagnosticSourceLocations` that reuses one owned snapshot per
|
|
115
|
+
* distinct input object across calls sharing a memo. Results are identical;
|
|
116
|
+
* only the identity of equal snapshots is shared within the invocation.
|
|
117
|
+
*/
|
|
118
|
+
export function normalizeDiagnosticSourceLocationsMemoized(
|
|
119
|
+
values: readonly (DiagnosticSourceLocation | null | undefined)[],
|
|
120
|
+
memo?: DiagnosticSourceLocationMemo,
|
|
121
|
+
): DiagnosticSourceLocation[] {
|
|
122
|
+
if (values.length === 0) return [];
|
|
123
|
+
// One cited source is the overwhelmingly common case: there is nothing to
|
|
124
|
+
// deduplicate or order, so the union map and its iteration are skipped.
|
|
125
|
+
// `normalizedSource` still runs, so an unrepresentable value throws here
|
|
126
|
+
// exactly as it would on the general path.
|
|
127
|
+
if (values.length === 1) {
|
|
128
|
+
const only = values[0];
|
|
129
|
+
if (only === null || only === undefined) return [];
|
|
130
|
+
let entry = memo?.get(only);
|
|
131
|
+
if (entry === undefined) {
|
|
132
|
+
entry = normalizedSource(only);
|
|
133
|
+
memo?.set(only, entry);
|
|
134
|
+
}
|
|
135
|
+
return [entry.value];
|
|
136
|
+
}
|
|
37
137
|
const unique = new Map<string, DiagnosticSourceLocation>();
|
|
38
|
-
for (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
};
|
|
46
|
-
const key = canonicalJson(normalized);
|
|
47
|
-
if (!unique.has(key)) unique.set(key, normalized);
|
|
138
|
+
for (let index = 0; index < values.length; index++) {
|
|
139
|
+
const value = values[index];
|
|
140
|
+
if (value === null || value === undefined) continue;
|
|
141
|
+
let entry = memo?.get(value);
|
|
142
|
+
if (entry === undefined) {
|
|
143
|
+
entry = normalizedSource(value);
|
|
144
|
+
memo?.set(value, entry);
|
|
48
145
|
}
|
|
49
|
-
|
|
146
|
+
if (!unique.has(entry.key)) unique.set(entry.key, entry.value);
|
|
147
|
+
}
|
|
148
|
+
if (unique.size === 1) return [unique.values().next().value!];
|
|
149
|
+
const ordered = [...unique.values()];
|
|
150
|
+
for (let index = 1; index < ordered.length; index++)
|
|
151
|
+
if (compareDiagnosticSourceLocations(ordered[index - 1]!, ordered[index]!) > 0)
|
|
152
|
+
return ordered.sort(compareDiagnosticSourceLocations);
|
|
153
|
+
return ordered;
|
|
50
154
|
}
|
package/src/mack.ts
CHANGED
|
@@ -241,6 +241,27 @@ export function runMack(tri: Triangle, options: MackOptions = {}): MackResult {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
// Project the full rectangle with the projection factors.
|
|
244
|
+
// A non-positive projection factor makes Mack's variance terms undefined:
|
|
245
|
+
// every one of them divides by f_k^2. The SELECTED path above coerces such a
|
|
246
|
+
// factor to 1.000 with a warning; the volume-weighted path must not silently
|
|
247
|
+
// do the same, because f_k there IS the data's own estimate and changing it
|
|
248
|
+
// would change the projection. Instead the affected columns are excluded from
|
|
249
|
+
// the variance and the result says so. Without this the column contributes
|
|
250
|
+
// sigma^2/0 = Infinity, which the ultimate (collapsed to 0 by the same zero
|
|
251
|
+
// factor) then turns into NaN - a standard error that serializes as null.
|
|
252
|
+
const degenerateFactorColumns = new Set<number>();
|
|
253
|
+
for (let k = 0; k < K - 1; k++) if (!(fEff[k]! > 0)) degenerateFactorColumns.add(k);
|
|
254
|
+
if (degenerateFactorColumns.size > 0) {
|
|
255
|
+
const intervals = [...degenerateFactorColumns]
|
|
256
|
+
.map((k) => `${tri.ages[k]}-${tri.ages[k + 1]}`)
|
|
257
|
+
.join(", ");
|
|
258
|
+
warnings.push(
|
|
259
|
+
`Development factor for ${intervals} months is not positive; Mack's variance ` +
|
|
260
|
+
`is undefined there, so those columns are excluded from every standard error, ` +
|
|
261
|
+
`which is therefore understated`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
244
265
|
const projected: number[][] = tri.values.map((row) => {
|
|
245
266
|
const out: number[] = new Array(K).fill(NaN);
|
|
246
267
|
const last = lastObservedIndex(row);
|
|
@@ -260,6 +281,8 @@ export function runMack(tri: Triangle, options: MackOptions = {}): MackResult {
|
|
|
260
281
|
let mse = 0;
|
|
261
282
|
let droppedColumns = 0;
|
|
262
283
|
for (let k = last; k < K - 1; k++) {
|
|
284
|
+
// Excluded globally above, with a warning naming the interval.
|
|
285
|
+
if (degenerateFactorColumns.has(k)) continue;
|
|
263
286
|
const cik = projected[i]![k]!;
|
|
264
287
|
if (!(cik > 0)) {
|
|
265
288
|
// Mack's 1/C_ik is undefined for a non-positive cumulative. Skipping
|
|
@@ -346,6 +369,7 @@ export function runMack(tri: Triangle, options: MackOptions = {}): MackResult {
|
|
|
346
369
|
|
|
347
370
|
let shared = 0;
|
|
348
371
|
for (let k = floor; k < K - 1; k++) {
|
|
372
|
+
if (degenerateFactorColumns.has(k)) continue;
|
|
349
373
|
shared += (2 * sigma2[k]!) / fEff[k]! ** 2 / denomSums[k]!;
|
|
350
374
|
}
|
|
351
375
|
if (tail !== 1) {
|
|
@@ -368,6 +392,23 @@ export function runMack(tri: Triangle, options: MackOptions = {}): MackResult {
|
|
|
368
392
|
);
|
|
369
393
|
const totalSe = Math.sqrt(totalMse);
|
|
370
394
|
|
|
395
|
+
// Safety net, not the primary path: every degeneracy above is handled by
|
|
396
|
+
// excluding a column with a warning, so nothing here should be non-finite.
|
|
397
|
+
// A standard error that escapes as NaN serializes to JSON as `null`, which a
|
|
398
|
+
// reader cannot distinguish from "not computed" - so refuse loudly instead.
|
|
399
|
+
for (const r of rows) {
|
|
400
|
+
if (!Number.isFinite(r.standardError))
|
|
401
|
+
throw new ReservingError(
|
|
402
|
+
"DEGENERATE_TRIANGLE",
|
|
403
|
+
`Standard error for origin ${r.origin} is not finite; the triangle is degenerate for Mack's model`,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (!Number.isFinite(totalSe))
|
|
407
|
+
throw new ReservingError(
|
|
408
|
+
"DEGENERATE_TRIANGLE",
|
|
409
|
+
"Total standard error is not finite; the triangle is degenerate for Mack's model",
|
|
410
|
+
);
|
|
411
|
+
|
|
371
412
|
return {
|
|
372
413
|
method: "mack",
|
|
373
414
|
developmentFactors: fEff,
|
package/src/types.ts
CHANGED
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's runtime version. A test pins it to package.json. */
|
|
2
|
-
export const CORE_PACKAGE_VERSION = "0.
|
|
2
|
+
export const CORE_PACKAGE_VERSION = "0.11.0";
|