@actuarial-ts/core 0.6.0 → 0.7.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 +12 -3
- package/dist/canonical.d.ts +11 -0
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +136 -26
- package/dist/canonical.js.map +1 -1
- package/dist/capping.d.ts.map +1 -1
- package/dist/capping.js +39 -6
- package/dist/capping.js.map +1 -1
- package/dist/diagnosticAggregation.d.ts +3 -0
- package/dist/diagnosticAggregation.d.ts.map +1 -1
- package/dist/diagnosticAggregation.js +30 -7
- package/dist/diagnosticAggregation.js.map +1 -1
- package/dist/diagnosticDefinitions.d.ts +3 -0
- package/dist/diagnosticDefinitions.d.ts.map +1 -1
- package/dist/diagnosticDefinitions.js +844 -182
- package/dist/diagnosticDefinitions.js.map +1 -1
- package/dist/diagnosticDerivations.d.ts +23 -0
- package/dist/diagnosticDerivations.d.ts.map +1 -1
- package/dist/diagnosticDerivations.js +187 -43
- package/dist/diagnosticDerivations.js.map +1 -1
- package/dist/diagnosticEvidenceIntern.d.ts +16 -0
- package/dist/diagnosticEvidenceIntern.d.ts.map +1 -0
- package/dist/diagnosticEvidenceIntern.js +139 -0
- package/dist/diagnosticEvidenceIntern.js.map +1 -0
- package/dist/diagnosticExposure.d.ts +2 -2
- package/dist/diagnosticExposure.d.ts.map +1 -1
- package/dist/diagnosticExposure.js +319 -21
- package/dist/diagnosticExposure.js.map +1 -1
- package/dist/diagnosticExpressions.d.ts +1 -1
- package/dist/diagnosticExpressions.d.ts.map +1 -1
- package/dist/diagnosticExpressions.js +35 -15
- package/dist/diagnosticExpressions.js.map +1 -1
- package/dist/diagnosticFormulas.d.ts +3 -0
- package/dist/diagnosticFormulas.d.ts.map +1 -1
- package/dist/diagnosticFormulas.js +160 -26
- package/dist/diagnosticFormulas.js.map +1 -1
- package/dist/diagnosticIdentity.d.ts +15 -1
- package/dist/diagnosticIdentity.d.ts.map +1 -1
- package/dist/diagnosticIdentity.js +321 -51
- package/dist/diagnosticIdentity.js.map +1 -1
- package/dist/diagnosticIdentityStream.d.ts +56 -0
- package/dist/diagnosticIdentityStream.d.ts.map +1 -0
- package/dist/diagnosticIdentityStream.js +472 -0
- package/dist/diagnosticIdentityStream.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 +2 -3
- package/dist/diagnosticPeriods.d.ts.map +1 -1
- package/dist/diagnosticPeriods.js +20 -68
- package/dist/diagnosticPeriods.js.map +1 -1
- package/dist/diagnosticPreparation.d.ts +75 -14
- package/dist/diagnosticPreparation.d.ts.map +1 -1
- package/dist/diagnosticPreparation.js +1606 -112
- package/dist/diagnosticPreparation.js.map +1 -1
- package/dist/diagnosticReview.d.ts +40 -15
- package/dist/diagnosticReview.d.ts.map +1 -1
- package/dist/diagnosticReview.js +600 -42
- package/dist/diagnosticReview.js.map +1 -1
- package/dist/diagnosticReviewSources.d.ts +18 -0
- package/dist/diagnosticReviewSources.d.ts.map +1 -0
- package/dist/diagnosticReviewSources.js +76 -0
- package/dist/diagnosticReviewSources.js.map +1 -0
- package/dist/diagnosticReviewStore.d.ts +88 -0
- package/dist/diagnosticReviewStore.d.ts.map +1 -0
- package/dist/diagnosticReviewStore.js +455 -0
- package/dist/diagnosticReviewStore.js.map +1 -0
- package/dist/diagnosticRunner.d.ts +27 -2
- package/dist/diagnosticRunner.d.ts.map +1 -1
- package/dist/diagnosticRunner.js +764 -70
- package/dist/diagnosticRunner.js.map +1 -1
- 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/fnvAccumulator.d.ts +6 -0
- package/dist/fnvAccumulator.d.ts.map +1 -0
- package/dist/fnvAccumulator.js +21 -0
- package/dist/fnvAccumulator.js.map +1 -0
- package/dist/index.d.ts +15 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- 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 +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +22 -8
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
- package/src/canonical.ts +144 -26
- package/src/capping.ts +109 -18
- package/src/diagnosticAggregation.ts +69 -20
- package/src/diagnosticDefinitions.ts +2675 -426
- package/src/diagnosticDerivations.ts +281 -49
- package/src/diagnosticEvidenceIntern.ts +151 -0
- package/src/diagnosticExposure.ts +550 -50
- package/src/diagnosticExpressions.ts +99 -17
- package/src/diagnosticFormulas.ts +239 -39
- package/src/diagnosticIdentity.ts +700 -191
- package/src/diagnosticIdentityStream.ts +553 -0
- package/src/diagnosticOrdering.ts +106 -0
- package/src/diagnosticPeriodAxis.ts +92 -0
- package/src/diagnosticPeriods.ts +67 -82
- package/src/diagnosticPreparation.ts +2558 -132
- package/src/diagnosticReview.ts +1072 -58
- package/src/diagnosticReviewSources.ts +85 -0
- package/src/diagnosticReviewStore.ts +692 -0
- package/src/diagnosticRunner.ts +1273 -72
- package/src/diagnosticRuntime.ts +264 -0
- package/src/diagnosticSourceOrdering.ts +50 -0
- package/src/fnvAccumulator.ts +20 -0
- package/src/index.ts +69 -4
- package/src/mack.ts +72 -9
- package/src/triangle.ts +103 -13
- package/src/types.ts +55 -20
- package/src/version.ts +1 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DiagnosticValidationError,
|
|
3
|
+
type DiagnosticValidationDomain,
|
|
4
|
+
type DiagnosticValidationIssue,
|
|
5
|
+
} from "./types.js";
|
|
6
|
+
|
|
7
|
+
export const MAX_DIAGNOSTIC_JSON_DEPTH = 256;
|
|
8
|
+
export const MAX_DIAGNOSTIC_JSON_NODES = 1_000_000;
|
|
9
|
+
|
|
10
|
+
/** True only for ordinary data records, including deliberately prototype-free records. */
|
|
11
|
+
export function isDiagnosticPlainRecord(
|
|
12
|
+
value: unknown,
|
|
13
|
+
): value is Record<string, unknown> {
|
|
14
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
15
|
+
return false;
|
|
16
|
+
const prototype: unknown = Object.getPrototypeOf(value);
|
|
17
|
+
return prototype === Object.prototype || prototype === null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Own-property lookup which remains correct for every legal string key. */
|
|
21
|
+
export function hasDiagnosticOwn(value: object, key: PropertyKey): boolean {
|
|
22
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Creates a record in which `__proto__`, `constructor`, and `toString` are ordinary keys. */
|
|
26
|
+
export function diagnosticRecord<T>(): Record<string, T> {
|
|
27
|
+
return Object.create(null) as Record<string, T>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizeDiagnosticNumber(value: number): number {
|
|
31
|
+
return Object.is(value, -0) ? 0 : value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Rejects U+0000 and unpaired UTF-16 surrogate code units. */
|
|
35
|
+
export function isWellFormedDiagnosticString(value: string): boolean {
|
|
36
|
+
for (let index = 0; index < value.length; index++) {
|
|
37
|
+
const code = value.charCodeAt(index);
|
|
38
|
+
if (code === 0) return false;
|
|
39
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
40
|
+
const next = value.charCodeAt(index + 1);
|
|
41
|
+
if (!(next >= 0xdc00 && next <= 0xdfff)) return false;
|
|
42
|
+
index++;
|
|
43
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) return false;
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isDiagnosticToken(value: unknown): value is string {
|
|
49
|
+
return (
|
|
50
|
+
typeof value === "string" &&
|
|
51
|
+
isWellFormedDiagnosticString(value) &&
|
|
52
|
+
value.length > 0 &&
|
|
53
|
+
!/^[\u0009-\u000d\u0020]|[\u0009-\u000d\u0020]$/.test(value)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Strict Gregorian date validation for public SDK date boundaries. */
|
|
58
|
+
export function isRealIsoDate(value: unknown): value is string {
|
|
59
|
+
if (typeof value !== "string" || !isWellFormedDiagnosticString(value))
|
|
60
|
+
return false;
|
|
61
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
62
|
+
if (!match) return false;
|
|
63
|
+
const year = Number(match[1]);
|
|
64
|
+
const month = Number(match[2]);
|
|
65
|
+
const day = Number(match[3]);
|
|
66
|
+
if (year < 1 || year > 9999 || month < 1 || month > 12 || day < 1)
|
|
67
|
+
return false;
|
|
68
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
69
|
+
const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
70
|
+
return day <= days[month - 1]!;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface JsonFrame {
|
|
74
|
+
readonly value: unknown;
|
|
75
|
+
readonly path: string;
|
|
76
|
+
readonly depth: number;
|
|
77
|
+
readonly exiting: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Iterative JSON boundary preflight. It never invokes getters and reports
|
|
82
|
+
* malformed values before recursive normalization or hashing can run.
|
|
83
|
+
*/
|
|
84
|
+
export function diagnosticJsonPreflight(
|
|
85
|
+
value: unknown,
|
|
86
|
+
domain: DiagnosticValidationDomain,
|
|
87
|
+
options: { readonly maxDepth?: number; readonly maxNodes?: number } = {},
|
|
88
|
+
): readonly DiagnosticValidationIssue[] {
|
|
89
|
+
const maxDepth = options.maxDepth ?? MAX_DIAGNOSTIC_JSON_DEPTH;
|
|
90
|
+
const maxNodes = options.maxNodes ?? MAX_DIAGNOSTIC_JSON_NODES;
|
|
91
|
+
const issues: DiagnosticValidationIssue[] = [];
|
|
92
|
+
const active = new WeakSet<object>();
|
|
93
|
+
const stack: JsonFrame[] = [{ value, path: "$", depth: 1, exiting: false }];
|
|
94
|
+
let nodes = 0;
|
|
95
|
+
while (stack.length > 0) {
|
|
96
|
+
const frame = stack.pop()!;
|
|
97
|
+
if (frame.exiting) {
|
|
98
|
+
active.delete(frame.value as object);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
nodes++;
|
|
102
|
+
if (nodes > maxNodes) {
|
|
103
|
+
issues.push({
|
|
104
|
+
domain,
|
|
105
|
+
code: "expression-limit",
|
|
106
|
+
path: "$",
|
|
107
|
+
message: `JSON node count exceeds ${maxNodes}`,
|
|
108
|
+
});
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
if (frame.depth > maxDepth) {
|
|
112
|
+
issues.push({
|
|
113
|
+
domain,
|
|
114
|
+
code: "expression-limit",
|
|
115
|
+
path: frame.path,
|
|
116
|
+
message: `JSON depth exceeds ${maxDepth}`,
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const item = frame.value;
|
|
121
|
+
if (item === null || typeof item === "boolean") continue;
|
|
122
|
+
if (typeof item === "string") {
|
|
123
|
+
if (!isWellFormedDiagnosticString(item))
|
|
124
|
+
issues.push({
|
|
125
|
+
domain,
|
|
126
|
+
code: "invalid-json-value",
|
|
127
|
+
path: frame.path,
|
|
128
|
+
message: "String is not well-formed UTF-16 or contains U+0000",
|
|
129
|
+
});
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (typeof item === "number") {
|
|
133
|
+
if (!Number.isFinite(item))
|
|
134
|
+
issues.push({
|
|
135
|
+
domain,
|
|
136
|
+
code: "invalid-json-value",
|
|
137
|
+
path: frame.path,
|
|
138
|
+
message: "JSON numeric value must be finite",
|
|
139
|
+
});
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (typeof item !== "object") {
|
|
143
|
+
issues.push({
|
|
144
|
+
domain,
|
|
145
|
+
code: "invalid-json-value",
|
|
146
|
+
path: frame.path,
|
|
147
|
+
message: "Value is not plain JSON data",
|
|
148
|
+
});
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const isArray = Array.isArray(item);
|
|
152
|
+
if (
|
|
153
|
+
isArray
|
|
154
|
+
? Object.getPrototypeOf(item) !== Array.prototype
|
|
155
|
+
: !isDiagnosticPlainRecord(item)
|
|
156
|
+
) {
|
|
157
|
+
issues.push({
|
|
158
|
+
domain,
|
|
159
|
+
code: "invalid-json-value",
|
|
160
|
+
path: frame.path,
|
|
161
|
+
message: "Value must use a plain object or array prototype",
|
|
162
|
+
});
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (active.has(item)) {
|
|
166
|
+
issues.push({
|
|
167
|
+
domain,
|
|
168
|
+
code: "cycle",
|
|
169
|
+
path: frame.path,
|
|
170
|
+
message: "JSON value contains a cycle",
|
|
171
|
+
});
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
active.add(item);
|
|
175
|
+
stack.push({ ...frame, exiting: true });
|
|
176
|
+
if (isArray) {
|
|
177
|
+
for (const key of Reflect.ownKeys(item)) {
|
|
178
|
+
if (key === "length") continue;
|
|
179
|
+
const index = typeof key === "string" ? Number(key) : Number.NaN;
|
|
180
|
+
if (
|
|
181
|
+
Number.isInteger(index) && index >= 0 && index < item.length &&
|
|
182
|
+
String(index) === key
|
|
183
|
+
) continue;
|
|
184
|
+
const path = typeof key === "symbol"
|
|
185
|
+
? frame.path
|
|
186
|
+
: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
|
|
187
|
+
? `${frame.path}.${key}`
|
|
188
|
+
: `${frame.path}[${JSON.stringify(key)}]`;
|
|
189
|
+
issues.push({
|
|
190
|
+
domain,
|
|
191
|
+
code: "invalid-json-value",
|
|
192
|
+
path,
|
|
193
|
+
message: "JSON arrays may contain only indexed data properties",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
for (let index = item.length - 1; index >= 0; index--) {
|
|
197
|
+
const path = `${frame.path}[${index}]`;
|
|
198
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, String(index));
|
|
199
|
+
if (!descriptor || !("value" in descriptor))
|
|
200
|
+
issues.push({
|
|
201
|
+
domain,
|
|
202
|
+
code: "invalid-json-value",
|
|
203
|
+
path,
|
|
204
|
+
message: "JSON arrays may contain only indexed data properties",
|
|
205
|
+
});
|
|
206
|
+
else
|
|
207
|
+
stack.push({
|
|
208
|
+
value: descriptor.value,
|
|
209
|
+
path,
|
|
210
|
+
depth: frame.depth + 1,
|
|
211
|
+
exiting: false,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
for (const key of Object.keys(item).sort().reverse()) {
|
|
217
|
+
const path = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
|
|
218
|
+
? `${frame.path}.${key}`
|
|
219
|
+
: `${frame.path}[${JSON.stringify(key)}]`;
|
|
220
|
+
if (!isWellFormedDiagnosticString(key))
|
|
221
|
+
issues.push({
|
|
222
|
+
domain,
|
|
223
|
+
code: "invalid-json-value",
|
|
224
|
+
path,
|
|
225
|
+
message: "Object key is not well-formed UTF-16 or contains U+0000",
|
|
226
|
+
});
|
|
227
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, key);
|
|
228
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
229
|
+
issues.push({
|
|
230
|
+
domain,
|
|
231
|
+
code: "invalid-json-value",
|
|
232
|
+
path,
|
|
233
|
+
message: "JSON objects may contain only data properties",
|
|
234
|
+
});
|
|
235
|
+
} else {
|
|
236
|
+
stack.push({
|
|
237
|
+
value: descriptor.value,
|
|
238
|
+
path,
|
|
239
|
+
depth: frame.depth + 1,
|
|
240
|
+
exiting: false,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return issues;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Snapshot JSON-shaped data into owned, deeply frozen arrays/null-prototype records. */
|
|
249
|
+
export function snapshotDiagnosticJson<T>(value: T): T {
|
|
250
|
+
const issues = diagnosticJsonPreflight(value, "input");
|
|
251
|
+
if (issues.length > 0) throw new DiagnosticValidationError(issues);
|
|
252
|
+
const clone = (item: unknown): unknown => {
|
|
253
|
+
if (item === null || typeof item !== "object") {
|
|
254
|
+
return typeof item === "number" ? normalizeDiagnosticNumber(item) : item;
|
|
255
|
+
}
|
|
256
|
+
if (Array.isArray(item)) return Object.freeze(item.map(clone));
|
|
257
|
+
const result = diagnosticRecord<unknown>();
|
|
258
|
+
for (const key of Object.keys(item).sort()) {
|
|
259
|
+
result[key] = clone((item as Record<string, unknown>)[key]);
|
|
260
|
+
}
|
|
261
|
+
return Object.freeze(result);
|
|
262
|
+
};
|
|
263
|
+
return clone(value) as T;
|
|
264
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { canonicalJson } from "./canonical.js";
|
|
2
|
+
import type { DiagnosticSourceLocation } from "./diagnosticDefinitions.js";
|
|
3
|
+
import { normalizeDiagnosticNumber } from "./diagnosticRuntime.js";
|
|
4
|
+
|
|
5
|
+
function codeUnit(left: string, right: string): number {
|
|
6
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function optional<T>(
|
|
10
|
+
left: T | undefined,
|
|
11
|
+
right: T | undefined,
|
|
12
|
+
compare: (a: T, b: T) => number,
|
|
13
|
+
): number {
|
|
14
|
+
if (left === undefined) return right === undefined ? 0 : -1;
|
|
15
|
+
if (right === undefined) return 1;
|
|
16
|
+
return compare(left, right);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Contract comparator for source provenance; source rows compare numerically. */
|
|
20
|
+
export function compareDiagnosticSourceLocations(
|
|
21
|
+
left: DiagnosticSourceLocation,
|
|
22
|
+
right: DiagnosticSourceLocation,
|
|
23
|
+
): number {
|
|
24
|
+
return (
|
|
25
|
+
codeUnit(left.artifactId, right.artifactId) ||
|
|
26
|
+
optional(left.sourceFile, right.sourceFile, codeUnit) ||
|
|
27
|
+
optional(left.sourceSheet, right.sourceSheet, codeUnit) ||
|
|
28
|
+
optional(left.sourceRow, right.sourceRow, (a, b) => a - b) ||
|
|
29
|
+
optional(left.sourceCell, right.sourceCell, codeUnit)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Exact-deduplicates, snapshots, and contract-sorts source provenance. */
|
|
34
|
+
export function normalizeDiagnosticSourceLocations(
|
|
35
|
+
values: readonly (DiagnosticSourceLocation | null | undefined)[],
|
|
36
|
+
): DiagnosticSourceLocation[] {
|
|
37
|
+
const unique = new Map<string, DiagnosticSourceLocation>();
|
|
38
|
+
for (const value of values)
|
|
39
|
+
if (value !== null && value !== undefined) {
|
|
40
|
+
const normalized = {
|
|
41
|
+
...value,
|
|
42
|
+
...(value.sourceRow === undefined
|
|
43
|
+
? {}
|
|
44
|
+
: { sourceRow: normalizeDiagnosticNumber(value.sourceRow) }),
|
|
45
|
+
};
|
|
46
|
+
const key = canonicalJson(normalized);
|
|
47
|
+
if (!unique.has(key)) unique.set(key, normalized);
|
|
48
|
+
}
|
|
49
|
+
return [...unique.values()].sort(compareDiagnosticSourceLocations);
|
|
50
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** @internal Shared exact FNV-1a state; deliberately not a package export. */
|
|
2
|
+
export function createFnvAccumulator() {
|
|
3
|
+
let high = 0xcbf29ce4;
|
|
4
|
+
let low = 0x84222325;
|
|
5
|
+
return {
|
|
6
|
+
update(bytes: Uint8Array) {
|
|
7
|
+
for (const byte of bytes) {
|
|
8
|
+
low = (low ^ byte) >>> 0;
|
|
9
|
+
// FNV_PRIME = 2^40 + 435. Every intermediate stays below 2^42,
|
|
10
|
+
// preserving the exact modular multiplication of the BigInt form.
|
|
11
|
+
const product = low * 0x1b3;
|
|
12
|
+
high = (high * 0x1b3 + Math.floor(product / 0x100000000) + (low << 8)) >>> 0;
|
|
13
|
+
low = product >>> 0;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
digest() {
|
|
17
|
+
return high.toString(16).padStart(8, "0") + low.toString(16).padStart(8, "0");
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
export * from "./types.js";
|
|
2
2
|
export * from "./util.js";
|
|
3
3
|
export * from "./canonical.js";
|
|
4
|
+
export * from "./diagnosticIdentityStream.js";
|
|
4
5
|
export * from "./version.js";
|
|
6
|
+
export {
|
|
7
|
+
MAX_DIAGNOSTIC_JSON_DEPTH,
|
|
8
|
+
MAX_DIAGNOSTIC_JSON_NODES,
|
|
9
|
+
diagnosticJsonPreflight,
|
|
10
|
+
diagnosticRecord,
|
|
11
|
+
hasDiagnosticOwn,
|
|
12
|
+
isDiagnosticPlainRecord,
|
|
13
|
+
isDiagnosticToken,
|
|
14
|
+
isRealIsoDate,
|
|
15
|
+
isWellFormedDiagnosticString,
|
|
16
|
+
normalizeDiagnosticNumber,
|
|
17
|
+
snapshotDiagnosticJson,
|
|
18
|
+
} from "./diagnosticRuntime.js";
|
|
5
19
|
export {
|
|
6
20
|
MAX_DIAGNOSTIC_DEFINITION_EXPRESSION_NODES,
|
|
7
21
|
MAX_DIAGNOSTIC_EXPRESSION_DEPTH,
|
|
@@ -63,7 +77,10 @@ export type {
|
|
|
63
77
|
NormalizedDiagnosticReviewRuleIdentity,
|
|
64
78
|
NormalizedDiagnosticSourceLocationIdentity,
|
|
65
79
|
NormalizedDiagnosticToleranceIdentity,
|
|
80
|
+
NormalizedDiagnosticsFilterIdentity,
|
|
81
|
+
DiagnosticIdentityProjection,
|
|
66
82
|
} from "./diagnosticIdentity.js";
|
|
83
|
+
export { projectDiagnosticIdentity, normalizeDiagnosticsFilterIdentity } from "./diagnosticIdentity.js";
|
|
67
84
|
export {
|
|
68
85
|
CASUALTY_FORMULA_TEMPLATES,
|
|
69
86
|
applyDiagnosticPresentation,
|
|
@@ -75,9 +92,7 @@ export type {
|
|
|
75
92
|
DiagnosticQuantitySemantics,
|
|
76
93
|
FinalizedDiagnosticMeasure,
|
|
77
94
|
} from "./diagnosticFormulas.js";
|
|
78
|
-
export {
|
|
79
|
-
classifyDiagnosticComparison,
|
|
80
|
-
} from "./diagnosticRules.js";
|
|
95
|
+
export { classifyDiagnosticComparison } from "./diagnosticRules.js";
|
|
81
96
|
export { deriveDiagnosticClaimMeasures } from "./diagnosticDerivations.js";
|
|
82
97
|
export type {
|
|
83
98
|
DiagnosticMeasureValues,
|
|
@@ -89,6 +104,11 @@ export {
|
|
|
89
104
|
normalizeDiagnosticPeriod,
|
|
90
105
|
} from "./diagnosticPeriods.js";
|
|
91
106
|
export type { DiagnosticNormalizedPeriod } from "./diagnosticPeriods.js";
|
|
107
|
+
export {
|
|
108
|
+
compareDiagnosticSourceLocations,
|
|
109
|
+
normalizeDiagnosticSourceLocations,
|
|
110
|
+
} from "./diagnosticSourceOrdering.js";
|
|
111
|
+
export { compareDiagnosticIdentityValues } from "./diagnosticOrdering.js";
|
|
92
112
|
export {
|
|
93
113
|
auditedDiagnosticContribution,
|
|
94
114
|
finalizeDiagnosticContributions,
|
|
@@ -103,47 +123,92 @@ export {
|
|
|
103
123
|
reconcileDiagnosticExposures,
|
|
104
124
|
} from "./diagnosticExposure.js";
|
|
105
125
|
export {
|
|
126
|
+
assertCompactPreparedDiagnosticData,
|
|
106
127
|
assertPreparedDiagnosticData,
|
|
107
128
|
getPreparedDiagnosticDataIdentity,
|
|
129
|
+
getCompactPreparedDiagnosticDataIdentityDocument,
|
|
130
|
+
getCompactPreparedDiagnosticDataFingerprint,
|
|
131
|
+
materializePreparedDiagnosticData,
|
|
108
132
|
prepareDiagnosticData,
|
|
133
|
+
prepareDiagnosticDataCompact,
|
|
109
134
|
verifyPreparedDiagnosticDataIntegrity,
|
|
110
135
|
} from "./diagnosticPreparation.js";
|
|
111
136
|
export type {
|
|
137
|
+
CompactPreparedDiagnosticData,
|
|
112
138
|
DiagnosticClaimObservation,
|
|
113
139
|
DiagnosticCompletePeriodCutoff,
|
|
114
140
|
DiagnosticExpectedCell,
|
|
141
|
+
DiagnosticExpectedCellAuditSnapshot,
|
|
142
|
+
DiagnosticExposureInputAuditSnapshot,
|
|
115
143
|
DiagnosticInputAuditRecord,
|
|
116
144
|
DiagnosticInputDisposition,
|
|
117
145
|
DiagnosticLossInput,
|
|
146
|
+
DiagnosticLossInputAuditSnapshot,
|
|
118
147
|
DiagnosticLossRecordBase,
|
|
119
148
|
DiagnosticLossSnapshot,
|
|
120
149
|
NormalizedDiagnosticPreparationIdentity,
|
|
150
|
+
NormalizedDiagnosticExpectedCellIdentity,
|
|
121
151
|
PrepareDiagnosticDataInput,
|
|
122
152
|
PreparedDiagnosticData,
|
|
153
|
+
PreparedDiagnosticDataContent,
|
|
123
154
|
PreparedDiagnosticSourceCell,
|
|
124
155
|
} from "./diagnosticPreparation.js";
|
|
125
156
|
export {
|
|
157
|
+
assertCompactMetricDiagnosticsResult,
|
|
126
158
|
commonMaturity,
|
|
159
|
+
commonMaturityCompact,
|
|
127
160
|
getMetricDiagnosticsResultIdentity,
|
|
161
|
+
getCompactMetricDiagnosticsResultIdentityDocument,
|
|
128
162
|
runMetricDiagnostics,
|
|
163
|
+
runMetricDiagnosticsCompact,
|
|
164
|
+
materializeMetricDiagnosticsResult,
|
|
129
165
|
sameMaturity,
|
|
166
|
+
sameMaturityCompact,
|
|
130
167
|
validateDiagnosticGroupingConfiguration,
|
|
168
|
+
validateCompactDiagnosticGroupingConfiguration,
|
|
131
169
|
} from "./diagnosticRunner.js";
|
|
132
|
-
export { evaluateDiagnosticReviewRules } from "./diagnosticReview.js";
|
|
170
|
+
export { evaluateDiagnosticReviewRules, evaluateDiagnosticReviewRulesCompact } from "./diagnosticReview.js";
|
|
171
|
+
export {
|
|
172
|
+
assertCompactDiagnosticReviewEvaluations,
|
|
173
|
+
getDiagnosticReviewEvaluation,
|
|
174
|
+
getDiagnosticReviewEvaluationSummary,
|
|
175
|
+
getCompactDiagnosticReviewEvaluationsIdentityDocument,
|
|
176
|
+
iterateDiagnosticReviewEvaluations,
|
|
177
|
+
pageDiagnosticReviewEvaluations,
|
|
178
|
+
pageDiagnosticReviewEvaluationSources,
|
|
179
|
+
} from "./diagnosticReviewStore.js";
|
|
180
|
+
export type {
|
|
181
|
+
CompactDiagnosticReviewEvaluations,
|
|
182
|
+
CompactDiagnosticReviewRuleSummary,
|
|
183
|
+
DiagnosticReviewEffectiveStatus,
|
|
184
|
+
DiagnosticReviewEvaluationQuery,
|
|
185
|
+
DiagnosticReviewEvaluationSummary,
|
|
186
|
+
DiagnosticReviewPage,
|
|
187
|
+
DiagnosticReviewSourceQuery,
|
|
188
|
+
DiagnosticReviewStatusCounts,
|
|
189
|
+
} from "./diagnosticReviewStore.js";
|
|
133
190
|
export type {
|
|
191
|
+
DiagnosticCellReviewScope,
|
|
192
|
+
DiagnosticControlTotalReviewScope,
|
|
134
193
|
DiagnosticReviewCoordinate,
|
|
135
194
|
DiagnosticReviewEvaluationScope,
|
|
195
|
+
DiagnosticReviewExpressionOverflow,
|
|
136
196
|
DiagnosticReviewRuleEvaluation,
|
|
197
|
+
DiagnosticReviewRuleEvaluationBase,
|
|
198
|
+
DiagnosticValuationPairReviewScope,
|
|
137
199
|
} from "./diagnosticReview.js";
|
|
138
200
|
export type {
|
|
139
201
|
CommonMaturityResult,
|
|
202
|
+
CompactMetricDiagnosticsResult,
|
|
140
203
|
DiagnosticEmergencePoint,
|
|
141
204
|
DiagnosticMetricEvaluation,
|
|
142
205
|
DiagnosticMetricTriangle,
|
|
143
206
|
DiagnosticMetricTriangleCell,
|
|
144
207
|
MetricDiagnosticsResult,
|
|
208
|
+
MetricDiagnosticsResultContent,
|
|
145
209
|
NormalizedDiagnosticResultIdentity,
|
|
146
210
|
RunMetricDiagnosticsInput,
|
|
211
|
+
RunMetricDiagnosticsCompactInput,
|
|
147
212
|
} from "./diagnosticRunner.js";
|
|
148
213
|
export type {
|
|
149
214
|
DiagnosticAuditedNumericValue,
|
package/src/mack.ts
CHANGED
|
@@ -11,6 +11,10 @@ export interface MackOptions {
|
|
|
11
11
|
selected?: (number | null)[];
|
|
12
12
|
/** Multiplicative tail factor beyond the last observed age (1 = none). */
|
|
13
13
|
tailFactor?: number;
|
|
14
|
+
/** Standard error of the selected tail-factor estimate (Mack 1999). */
|
|
15
|
+
tailStandardError?: number;
|
|
16
|
+
/** Tail random-error scale sigma, applied as sigma / sqrt(C) (Mack 1999). */
|
|
17
|
+
tailSigma?: number;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
/**
|
|
@@ -46,7 +50,10 @@ export function mackEstimators(tri: Triangle): {
|
|
|
46
50
|
const n = tri.origins.length;
|
|
47
51
|
const K = tri.ages.length;
|
|
48
52
|
if (K < 2) {
|
|
49
|
-
throw new ReservingError(
|
|
53
|
+
throw new ReservingError(
|
|
54
|
+
"TOO_SMALL",
|
|
55
|
+
"Mack requires at least two development ages",
|
|
56
|
+
);
|
|
50
57
|
}
|
|
51
58
|
const f: number[] = [];
|
|
52
59
|
const denomSums: number[] = [];
|
|
@@ -169,7 +176,45 @@ export function runMack(tri: Triangle, options: MackOptions = {}): MackResult {
|
|
|
169
176
|
}
|
|
170
177
|
const tail = options.tailFactor ?? 1;
|
|
171
178
|
if (!isNum(tail) || tail <= 0) {
|
|
172
|
-
throw new ReservingError(
|
|
179
|
+
throw new ReservingError(
|
|
180
|
+
"BAD_TAIL",
|
|
181
|
+
"Tail factor must be a positive number",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const hasPublishedTailInputs =
|
|
185
|
+
options.tailStandardError !== undefined || options.tailSigma !== undefined;
|
|
186
|
+
if (
|
|
187
|
+
hasPublishedTailInputs &&
|
|
188
|
+
(options.tailStandardError === undefined || options.tailSigma === undefined)
|
|
189
|
+
) {
|
|
190
|
+
throw new ReservingError(
|
|
191
|
+
"BAD_TAIL",
|
|
192
|
+
"tailStandardError and tailSigma must be supplied together",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (
|
|
196
|
+
options.tailStandardError !== undefined &&
|
|
197
|
+
(!isNum(options.tailStandardError) || options.tailStandardError < 0)
|
|
198
|
+
) {
|
|
199
|
+
throw new ReservingError(
|
|
200
|
+
"BAD_TAIL",
|
|
201
|
+
"Tail standard error must be a finite nonnegative number",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (
|
|
205
|
+
options.tailSigma !== undefined &&
|
|
206
|
+
(!isNum(options.tailSigma) || options.tailSigma < 0)
|
|
207
|
+
) {
|
|
208
|
+
throw new ReservingError(
|
|
209
|
+
"BAD_TAIL",
|
|
210
|
+
"Tail sigma must be a finite nonnegative number",
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (tail === 1 && hasPublishedTailInputs) {
|
|
214
|
+
throw new ReservingError(
|
|
215
|
+
"BAD_TAIL",
|
|
216
|
+
"Tail uncertainty inputs require a tailFactor other than 1",
|
|
217
|
+
);
|
|
173
218
|
}
|
|
174
219
|
|
|
175
220
|
// sigma^2_k estimates: data-estimated where possible; Mack's extrapolation
|
|
@@ -189,9 +234,10 @@ export function runMack(tri: Triangle, options: MackOptions = {}): MackResult {
|
|
|
189
234
|
sigma2Tail = s2b;
|
|
190
235
|
}
|
|
191
236
|
denomTail = denomSums[K - 2]!;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
237
|
+
if (!hasPublishedTailInputs)
|
|
238
|
+
warnings.push(
|
|
239
|
+
"The tail step's standard-error contribution extrapolates sigma^2 beyond the observed columns and reuses the final column's volume; supply tailStandardError and tailSigma for the explicit Mack (1999) treatment",
|
|
240
|
+
);
|
|
195
241
|
}
|
|
196
242
|
|
|
197
243
|
// Project the full rectangle with the projection factors.
|
|
@@ -233,7 +279,12 @@ export function runMack(tri: Triangle, options: MackOptions = {}): MackResult {
|
|
|
233
279
|
);
|
|
234
280
|
}
|
|
235
281
|
if (tail !== 1 && projected[i]![K - 1]! > 0) {
|
|
236
|
-
|
|
282
|
+
const finalCumulative = projected[i]![K - 1]!;
|
|
283
|
+
mse += hasPublishedTailInputs
|
|
284
|
+
? (options.tailSigma! ** 2 / finalCumulative +
|
|
285
|
+
options.tailStandardError! ** 2) /
|
|
286
|
+
tail ** 2
|
|
287
|
+
: (sigma2Tail / tail ** 2) * (1 / finalCumulative + 1 / denomTail);
|
|
237
288
|
}
|
|
238
289
|
mse *= ultimate ** 2;
|
|
239
290
|
mseByRow[i] = mse;
|
|
@@ -283,7 +334,10 @@ export function runMack(tri: Triangle, options: MackOptions = {}): MackResult {
|
|
|
283
334
|
const uj = ultimateOf(j);
|
|
284
335
|
if (!(uj > 0)) continue;
|
|
285
336
|
const floor = Math.max(maturity[i]!, maturity[j]!);
|
|
286
|
-
partnerUltimatesByFloor.set(
|
|
337
|
+
partnerUltimatesByFloor.set(
|
|
338
|
+
floor,
|
|
339
|
+
(partnerUltimatesByFloor.get(floor) ?? 0) + uj,
|
|
340
|
+
);
|
|
287
341
|
}
|
|
288
342
|
|
|
289
343
|
for (const [floor, partnerUltimates] of partnerUltimatesByFloor) {
|
|
@@ -295,7 +349,9 @@ export function runMack(tri: Triangle, options: MackOptions = {}): MackResult {
|
|
|
295
349
|
shared += (2 * sigma2[k]!) / fEff[k]! ** 2 / denomSums[k]!;
|
|
296
350
|
}
|
|
297
351
|
if (tail !== 1) {
|
|
298
|
-
shared +=
|
|
352
|
+
shared += hasPublishedTailInputs
|
|
353
|
+
? (2 * options.tailStandardError! ** 2) / tail ** 2
|
|
354
|
+
: (2 * sigma2Tail) / tail ** 2 / denomTail;
|
|
299
355
|
}
|
|
300
356
|
|
|
301
357
|
totalMse += ui * partnerUltimates * shared;
|
|
@@ -317,7 +373,14 @@ export function runMack(tri: Triangle, options: MackOptions = {}): MackResult {
|
|
|
317
373
|
developmentFactors: fEff,
|
|
318
374
|
sigmaSquared: sigma2,
|
|
319
375
|
tailFactor: tail,
|
|
320
|
-
sigmaSquaredTail:
|
|
376
|
+
sigmaSquaredTail:
|
|
377
|
+
tail !== 1 && !hasPublishedTailInputs ? sigma2Tail : undefined,
|
|
378
|
+
tailStandardError:
|
|
379
|
+
tail !== 1 && hasPublishedTailInputs
|
|
380
|
+
? options.tailStandardError
|
|
381
|
+
: undefined,
|
|
382
|
+
tailSigma:
|
|
383
|
+
tail !== 1 && hasPublishedTailInputs ? options.tailSigma : undefined,
|
|
321
384
|
rows,
|
|
322
385
|
totals: {
|
|
323
386
|
...totals,
|