@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,721 @@
|
|
|
1
|
+
import { canonicalJson, fnv1a64 } from "./canonical.js";
|
|
2
|
+
import { normalizeDiagnosticPeriodWithAxis } from "./diagnosticPeriodAxis.js";
|
|
3
|
+
import { diagnosticJsonPreflight, isDiagnosticPlainRecord, isDiagnosticToken, normalizeDiagnosticNumber, } from "./diagnosticRuntime.js";
|
|
4
|
+
import { DiagnosticValidationError, } from "./types.js";
|
|
5
|
+
/** Validates selector shapes without interpreting periods against a definition. */
|
|
6
|
+
function validateFilterIdentityInput(filter) {
|
|
7
|
+
const issues = [
|
|
8
|
+
...diagnosticJsonPreflight(filter, "configuration"),
|
|
9
|
+
];
|
|
10
|
+
if (issues.length)
|
|
11
|
+
throw new DiagnosticValidationError(issues);
|
|
12
|
+
if (!isDiagnosticPlainRecord(filter))
|
|
13
|
+
throw new DiagnosticValidationError([
|
|
14
|
+
{
|
|
15
|
+
domain: "configuration",
|
|
16
|
+
code: "invalid-type",
|
|
17
|
+
path: "$",
|
|
18
|
+
message: "Filter must be a plain object or null",
|
|
19
|
+
},
|
|
20
|
+
]);
|
|
21
|
+
const lists = [
|
|
22
|
+
"sourceGroups", "outputGroups", "origins", "valuations", "instanceIds",
|
|
23
|
+
];
|
|
24
|
+
const labels = [
|
|
25
|
+
"originFrom", "originThrough", "valuationFrom", "valuationThrough",
|
|
26
|
+
];
|
|
27
|
+
const bounds = ["minDevelopmentAge", "maxDevelopmentAge"];
|
|
28
|
+
const checkToken = (value, path) => {
|
|
29
|
+
if (!isDiagnosticToken(value))
|
|
30
|
+
issues.push({
|
|
31
|
+
domain: "configuration",
|
|
32
|
+
code: "invalid-string",
|
|
33
|
+
path,
|
|
34
|
+
message: "Filter selectors must use nonempty token strings",
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
for (const key of Object.keys(filter).sort()) {
|
|
38
|
+
const value = filter[key];
|
|
39
|
+
const path = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
|
|
40
|
+
? `$.${key}`
|
|
41
|
+
: `$[${JSON.stringify(key)}]`;
|
|
42
|
+
if (lists.includes(key)) {
|
|
43
|
+
if (!Array.isArray(value))
|
|
44
|
+
issues.push({
|
|
45
|
+
domain: "configuration",
|
|
46
|
+
code: "invalid-type",
|
|
47
|
+
path,
|
|
48
|
+
message: "Filter selector must be an array",
|
|
49
|
+
});
|
|
50
|
+
else
|
|
51
|
+
value.forEach((item, index) => checkToken(item, `${path}[${index}]`));
|
|
52
|
+
}
|
|
53
|
+
else if (labels.includes(key))
|
|
54
|
+
checkToken(value, path);
|
|
55
|
+
else if (bounds.includes(key)) {
|
|
56
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
57
|
+
issues.push({
|
|
58
|
+
domain: "configuration",
|
|
59
|
+
code: "invalid-number",
|
|
60
|
+
path,
|
|
61
|
+
message: "Development-age bounds must be nonnegative safe integers",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
issues.push({
|
|
66
|
+
domain: "configuration",
|
|
67
|
+
code: "unknown-key",
|
|
68
|
+
path,
|
|
69
|
+
message: `Unknown filter key ${key}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (typeof filter.minDevelopmentAge === "number" &&
|
|
73
|
+
typeof filter.maxDevelopmentAge === "number" &&
|
|
74
|
+
filter.minDevelopmentAge > filter.maxDevelopmentAge)
|
|
75
|
+
issues.push({
|
|
76
|
+
domain: "configuration",
|
|
77
|
+
code: "invalid-configuration",
|
|
78
|
+
path: "$",
|
|
79
|
+
message: "Minimum development age exceeds maximum",
|
|
80
|
+
});
|
|
81
|
+
if (issues.length)
|
|
82
|
+
throw new DiagnosticValidationError(issues);
|
|
83
|
+
}
|
|
84
|
+
/** Materializes optional selectors; period aliases must already be canonical. */
|
|
85
|
+
export function normalizeDiagnosticsFilterIdentity(filter) {
|
|
86
|
+
if (filter === null)
|
|
87
|
+
return null;
|
|
88
|
+
validateFilterIdentityInput(filter);
|
|
89
|
+
return deepFreeze({
|
|
90
|
+
sourceGroups: filter.sourceGroups === undefined
|
|
91
|
+
? null
|
|
92
|
+
: sortedUnique(filter.sourceGroups),
|
|
93
|
+
outputGroups: filter.outputGroups === undefined
|
|
94
|
+
? null
|
|
95
|
+
: sortedUnique(filter.outputGroups),
|
|
96
|
+
origins: filter.origins === undefined ? null : sortedUnique(filter.origins),
|
|
97
|
+
originFrom: filter.originFrom ?? null,
|
|
98
|
+
originThrough: filter.originThrough ?? null,
|
|
99
|
+
valuations: filter.valuations === undefined ? null : sortedUnique(filter.valuations),
|
|
100
|
+
valuationFrom: filter.valuationFrom ?? null,
|
|
101
|
+
valuationThrough: filter.valuationThrough ?? null,
|
|
102
|
+
minDevelopmentAge: filter.minDevelopmentAge === undefined
|
|
103
|
+
? null
|
|
104
|
+
: normalizeDiagnosticNumber(filter.minDevelopmentAge),
|
|
105
|
+
maxDevelopmentAge: filter.maxDevelopmentAge === undefined
|
|
106
|
+
? null
|
|
107
|
+
: normalizeDiagnosticNumber(filter.maxDevelopmentAge),
|
|
108
|
+
instanceIds: filter.instanceIds === undefined ? null : sortedUnique(filter.instanceIds),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Owned identity copy. Source slots are schema-defined; free JSON dimensions
|
|
113
|
+
* must not be interpreted as source evidence just because a key looks similar.
|
|
114
|
+
*/
|
|
115
|
+
export function projectDiagnosticIdentity(value) {
|
|
116
|
+
const issues = diagnosticJsonPreflight(value, "input", {
|
|
117
|
+
maxNodes: Number.MAX_SAFE_INTEGER,
|
|
118
|
+
});
|
|
119
|
+
if (issues.length)
|
|
120
|
+
throw new DiagnosticValidationError(issues);
|
|
121
|
+
const clone = (item, sourceSlot = false, freeJson = false, path = "$") => {
|
|
122
|
+
if (item === null || typeof item !== "object")
|
|
123
|
+
return typeof item === "number" && Object.is(item, -0) ? 0 : item;
|
|
124
|
+
if (Array.isArray(item))
|
|
125
|
+
return Object.freeze(item.map((child, index) => clone(child, sourceSlot, freeJson, `${path}[${index}]`)));
|
|
126
|
+
const record = item;
|
|
127
|
+
if (sourceSlot && typeof record.artifactId === "string") {
|
|
128
|
+
const sourceIssues = [];
|
|
129
|
+
for (const key of Object.keys(record)) {
|
|
130
|
+
const fieldPath = `${path}.${key}`;
|
|
131
|
+
if (![
|
|
132
|
+
"artifactId",
|
|
133
|
+
"sourceFile",
|
|
134
|
+
"sourceSheet",
|
|
135
|
+
"sourceRow",
|
|
136
|
+
"sourceCell",
|
|
137
|
+
].includes(key))
|
|
138
|
+
sourceIssues.push({
|
|
139
|
+
domain: "input",
|
|
140
|
+
code: "unknown-key",
|
|
141
|
+
path: fieldPath,
|
|
142
|
+
message: `Unknown source-location key ${key}`,
|
|
143
|
+
});
|
|
144
|
+
else if (key === "sourceRow") {
|
|
145
|
+
if (record[key] !== null &&
|
|
146
|
+
!(typeof record[key] === "number" &&
|
|
147
|
+
Number.isSafeInteger(record[key]) &&
|
|
148
|
+
record[key] >= 0))
|
|
149
|
+
sourceIssues.push({
|
|
150
|
+
domain: "input",
|
|
151
|
+
code: "invalid-number",
|
|
152
|
+
path: fieldPath,
|
|
153
|
+
message: "Source row must be a nonnegative safe integer or null",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else if (!(key !== "artifactId" && record[key] === null) &&
|
|
157
|
+
!isDiagnosticToken(record[key]))
|
|
158
|
+
sourceIssues.push({
|
|
159
|
+
domain: "input",
|
|
160
|
+
code: "invalid-string",
|
|
161
|
+
path: fieldPath,
|
|
162
|
+
message: "Source location must use nonempty token strings",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (sourceIssues.length)
|
|
166
|
+
throw new DiagnosticValidationError(sourceIssues);
|
|
167
|
+
return normalizeDiagnosticSourceLocation(record);
|
|
168
|
+
}
|
|
169
|
+
const result = Object.create(null);
|
|
170
|
+
for (const key of Object.keys(record).sort()) {
|
|
171
|
+
const opaque = freeJson || key === "groupDimensions" || key === "dimensions";
|
|
172
|
+
result[key] = clone(record[key], !opaque && (key === "source" || key === "sources"), opaque, `${path}.${key}`);
|
|
173
|
+
}
|
|
174
|
+
return Object.freeze(result);
|
|
175
|
+
};
|
|
176
|
+
return clone(value);
|
|
177
|
+
}
|
|
178
|
+
function compareCodeUnits(left, right) {
|
|
179
|
+
return left === right ? 0 : left < right ? -1 : 1;
|
|
180
|
+
}
|
|
181
|
+
function sortedUnique(values) {
|
|
182
|
+
return [...new Set(values)].sort(compareCodeUnits);
|
|
183
|
+
}
|
|
184
|
+
function sortedRecord(entries) {
|
|
185
|
+
const result = Object.create(null);
|
|
186
|
+
for (const [key, value] of [...entries].sort(([left], [right]) => compareCodeUnits(left, right))) {
|
|
187
|
+
result[key] = value;
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
function normalizeJsonValue(value) {
|
|
192
|
+
if (typeof value === "number" && Object.is(value, -0))
|
|
193
|
+
return 0;
|
|
194
|
+
if (Array.isArray(value))
|
|
195
|
+
return value.map((item) => normalizeJsonValue(item));
|
|
196
|
+
if (value !== null && typeof value === "object") {
|
|
197
|
+
return sortedRecord(Object.entries(value).map(([key, item]) => [key, normalizeJsonValue(item)]));
|
|
198
|
+
}
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
function deepFreeze(value, seen = new WeakSet()) {
|
|
202
|
+
if (value === null || typeof value !== "object")
|
|
203
|
+
return value;
|
|
204
|
+
if (seen.has(value))
|
|
205
|
+
return value;
|
|
206
|
+
seen.add(value);
|
|
207
|
+
for (const child of Object.values(value))
|
|
208
|
+
deepFreeze(child, seen);
|
|
209
|
+
return Object.freeze(value);
|
|
210
|
+
}
|
|
211
|
+
function attributes(value) {
|
|
212
|
+
return sortedRecord(Object.entries(value ?? {}).map(([key, item]) => [key, normalizeJsonValue(item)]));
|
|
213
|
+
}
|
|
214
|
+
function normalizeLimitation(limitation) {
|
|
215
|
+
if (limitation.kind === "unlimited")
|
|
216
|
+
return { kind: "unlimited" };
|
|
217
|
+
if (limitation.kind === "unknown") {
|
|
218
|
+
return { kind: "unknown", description: limitation.description ?? null };
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
kind: limitation.kind,
|
|
222
|
+
attachment: Object.is(limitation.attachment, -0)
|
|
223
|
+
? 0
|
|
224
|
+
: limitation.attachment,
|
|
225
|
+
limit: limitation.limit !== null && Object.is(limitation.limit, -0)
|
|
226
|
+
? 0
|
|
227
|
+
: limitation.limit,
|
|
228
|
+
application: limitation.application,
|
|
229
|
+
derivation: limitation.derivation.kind === "sdk"
|
|
230
|
+
? { kind: "sdk" }
|
|
231
|
+
: {
|
|
232
|
+
kind: "external",
|
|
233
|
+
actor: limitation.derivation.actor,
|
|
234
|
+
transformationRef: limitation.derivation.transformationRef,
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function normalizeMeasureExpression(expression) {
|
|
239
|
+
if (expression.op === "measure")
|
|
240
|
+
return {
|
|
241
|
+
op: "measure",
|
|
242
|
+
measureId: expression.measureId,
|
|
243
|
+
};
|
|
244
|
+
if (expression.op === "claim-layer") {
|
|
245
|
+
return {
|
|
246
|
+
op: "claim-layer",
|
|
247
|
+
measureId: expression.measureId,
|
|
248
|
+
attachment: Object.is(expression.attachment, -0)
|
|
249
|
+
? 0
|
|
250
|
+
: expression.attachment,
|
|
251
|
+
limit: expression.limit !== null && Object.is(expression.limit, -0)
|
|
252
|
+
? 0
|
|
253
|
+
: expression.limit,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (expression.op === "subtract") {
|
|
257
|
+
return {
|
|
258
|
+
op: "subtract",
|
|
259
|
+
left: normalizeMeasureExpression(expression.left),
|
|
260
|
+
right: normalizeMeasureExpression(expression.right),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
op: "add",
|
|
265
|
+
terms: expression.terms.map((term) => normalizeMeasureExpression(term)),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function normalizeRoleExpression(expression) {
|
|
269
|
+
if (expression.op === "role")
|
|
270
|
+
return { op: "role", role: expression.role };
|
|
271
|
+
if (expression.op === "subtract") {
|
|
272
|
+
return {
|
|
273
|
+
op: "subtract",
|
|
274
|
+
left: normalizeRoleExpression(expression.left),
|
|
275
|
+
right: normalizeRoleExpression(expression.right),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return { op: "add", terms: expression.terms.map(normalizeRoleExpression) };
|
|
279
|
+
}
|
|
280
|
+
function normalizeTolerance(tolerance) {
|
|
281
|
+
return {
|
|
282
|
+
absolute: Object.is(tolerance?.absolute ?? 0, -0)
|
|
283
|
+
? 0
|
|
284
|
+
: (tolerance?.absolute ?? 0),
|
|
285
|
+
relative: Object.is(tolerance?.relative ?? 0, -0)
|
|
286
|
+
? 0
|
|
287
|
+
: (tolerance?.relative ?? 0),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function normalizeRuleOperand(operand) {
|
|
291
|
+
if (operand.source === "measure") {
|
|
292
|
+
return {
|
|
293
|
+
source: "measure",
|
|
294
|
+
expression: normalizeMeasureExpression(operand.expression),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (operand.source === "calculation")
|
|
298
|
+
return { source: "calculation", field: operand.field };
|
|
299
|
+
return {
|
|
300
|
+
source: "constant",
|
|
301
|
+
value: Object.is(operand.value, -0) ? 0 : operand.value,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function normalizeReviewOperand(operand) {
|
|
305
|
+
if (operand.op === "constant")
|
|
306
|
+
return {
|
|
307
|
+
op: "constant",
|
|
308
|
+
value: Object.is(operand.value, -0) ? 0 : operand.value,
|
|
309
|
+
};
|
|
310
|
+
return normalizeMeasureExpression(operand);
|
|
311
|
+
}
|
|
312
|
+
function normalizeReviewFilter(filter, axis) {
|
|
313
|
+
const normalizeMany = (side, values) => values === undefined
|
|
314
|
+
? null
|
|
315
|
+
: sortedUnique(values.map((value) => normalizeDiagnosticPeriodWithAxis(axis, side, value).label));
|
|
316
|
+
const normalizeOne = (side, value) => value === undefined
|
|
317
|
+
? null
|
|
318
|
+
: normalizeDiagnosticPeriodWithAxis(axis, side, value).label;
|
|
319
|
+
return {
|
|
320
|
+
sourceGroups: filter.sourceGroups === undefined
|
|
321
|
+
? null
|
|
322
|
+
: sortedUnique(filter.sourceGroups),
|
|
323
|
+
origins: normalizeMany("origin", filter.origins),
|
|
324
|
+
originFrom: normalizeOne("origin", filter.originFrom),
|
|
325
|
+
originThrough: normalizeOne("origin", filter.originThrough),
|
|
326
|
+
valuations: normalizeMany("valuation", filter.valuations),
|
|
327
|
+
valuationFrom: normalizeOne("valuation", filter.valuationFrom),
|
|
328
|
+
valuationThrough: normalizeOne("valuation", filter.valuationThrough),
|
|
329
|
+
minDevelopmentAge: filter.minDevelopmentAge === undefined
|
|
330
|
+
? null
|
|
331
|
+
: Object.is(filter.minDevelopmentAge, -0)
|
|
332
|
+
? 0
|
|
333
|
+
: filter.minDevelopmentAge,
|
|
334
|
+
maxDevelopmentAge: filter.maxDevelopmentAge === undefined
|
|
335
|
+
? null
|
|
336
|
+
: Object.is(filter.maxDevelopmentAge, -0)
|
|
337
|
+
? 0
|
|
338
|
+
: filter.maxDevelopmentAge,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function normalizePeriodAxis(axis) {
|
|
342
|
+
if (axis.kind === "calendar") {
|
|
343
|
+
return {
|
|
344
|
+
kind: "calendar",
|
|
345
|
+
originCadence: axis.originCadence,
|
|
346
|
+
valuationCadence: axis.valuationCadence,
|
|
347
|
+
originAnchor: axis.originAnchor,
|
|
348
|
+
valuationAnchor: axis.valuationAnchor,
|
|
349
|
+
ageUnit: "month",
|
|
350
|
+
ageOffset: Object.is(axis.ageOffset, -0) ? 0 : axis.ageOffset,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const coordinates = (values) => values
|
|
354
|
+
.map((coordinate) => ({
|
|
355
|
+
label: coordinate.label,
|
|
356
|
+
aliases: sortedUnique(coordinate.aliases ?? []),
|
|
357
|
+
coordinate: Object.is(coordinate.coordinate, -0)
|
|
358
|
+
? 0
|
|
359
|
+
: coordinate.coordinate,
|
|
360
|
+
}))
|
|
361
|
+
.sort((left, right) => left.coordinate - right.coordinate ||
|
|
362
|
+
compareCodeUnits(left.label, right.label));
|
|
363
|
+
return {
|
|
364
|
+
kind: "ordered",
|
|
365
|
+
id: axis.id,
|
|
366
|
+
version: axis.version,
|
|
367
|
+
ageUnit: axis.ageUnit,
|
|
368
|
+
ageOffset: Object.is(axis.ageOffset, -0) ? 0 : axis.ageOffset,
|
|
369
|
+
origins: coordinates(axis.origins),
|
|
370
|
+
valuations: coordinates(axis.valuations),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function topologicalDerivations(derivations) {
|
|
374
|
+
const byOutput = new Map(derivations.map((derivation) => [derivation.outputMeasureId, derivation]));
|
|
375
|
+
const remaining = new Map(derivations.map((derivation) => [derivation.id, derivation]));
|
|
376
|
+
const emittedOutputs = new Set();
|
|
377
|
+
const ordered = [];
|
|
378
|
+
while (remaining.size > 0) {
|
|
379
|
+
const ready = [...remaining.values()]
|
|
380
|
+
.filter((derivation) => expressionDependencies(derivation.expression)
|
|
381
|
+
.filter((id) => byOutput.has(id))
|
|
382
|
+
.every((id) => emittedOutputs.has(id)))
|
|
383
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id));
|
|
384
|
+
if (ready.length === 0)
|
|
385
|
+
return [...derivations].sort((left, right) => compareCodeUnits(left.id, right.id));
|
|
386
|
+
for (const derivation of ready) {
|
|
387
|
+
ordered.push(derivation);
|
|
388
|
+
emittedOutputs.add(derivation.outputMeasureId);
|
|
389
|
+
remaining.delete(derivation.id);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return ordered;
|
|
393
|
+
}
|
|
394
|
+
function normalizeReviewRule(rule, axis) {
|
|
395
|
+
const base = {
|
|
396
|
+
id: rule.id,
|
|
397
|
+
code: rule.code,
|
|
398
|
+
description: rule.description,
|
|
399
|
+
severity: rule.severity,
|
|
400
|
+
missingInput: rule.missingInput,
|
|
401
|
+
tolerance: normalizeTolerance(rule.tolerance),
|
|
402
|
+
};
|
|
403
|
+
if (rule.kind === "compare") {
|
|
404
|
+
return {
|
|
405
|
+
...base,
|
|
406
|
+
kind: "compare",
|
|
407
|
+
when: {
|
|
408
|
+
left: normalizeReviewOperand(rule.when.left),
|
|
409
|
+
operator: rule.when.operator,
|
|
410
|
+
right: normalizeReviewOperand(rule.when.right),
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
if (rule.kind === "reconcile") {
|
|
415
|
+
return {
|
|
416
|
+
...base,
|
|
417
|
+
kind: "reconcile",
|
|
418
|
+
actual: normalizeMeasureExpression(rule.actual),
|
|
419
|
+
expected: normalizeReviewOperand(rule.expected),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (rule.kind === "monotonic") {
|
|
423
|
+
return {
|
|
424
|
+
...base,
|
|
425
|
+
kind: "monotonic",
|
|
426
|
+
expression: normalizeMeasureExpression(rule.expression),
|
|
427
|
+
direction: rule.direction,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
if (rule.kind === "layer-order") {
|
|
431
|
+
return {
|
|
432
|
+
...base,
|
|
433
|
+
kind: "layer-order",
|
|
434
|
+
narrower: normalizeMeasureExpression(rule.narrower),
|
|
435
|
+
broader: normalizeMeasureExpression(rule.broader),
|
|
436
|
+
comparability: normalizeJsonValue(rule.comparability),
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
...base,
|
|
441
|
+
kind: "control-total",
|
|
442
|
+
expression: normalizeMeasureExpression(rule.expression),
|
|
443
|
+
expected: Object.is(rule.expected, -0) ? 0 : rule.expected,
|
|
444
|
+
filter: rule.filter === undefined
|
|
445
|
+
? null
|
|
446
|
+
: normalizeReviewFilter(rule.filter, axis),
|
|
447
|
+
projection: rule.projection.kind === "valuation"
|
|
448
|
+
? {
|
|
449
|
+
kind: "valuation",
|
|
450
|
+
valuation: normalizeDiagnosticPeriodWithAxis(axis, "valuation", rule.projection.valuation).label,
|
|
451
|
+
}
|
|
452
|
+
: normalizeJsonValue(rule.projection),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
export function normalizeDiagnosticDefinition(definition) {
|
|
456
|
+
const normalized = {
|
|
457
|
+
diagnosticDefinitionVersion: "1.0.0",
|
|
458
|
+
id: definition.id,
|
|
459
|
+
version: definition.version,
|
|
460
|
+
lossRowGrain: definition.lossRowGrain,
|
|
461
|
+
measures: definition.measures
|
|
462
|
+
.map((measure) => ({
|
|
463
|
+
id: measure.id,
|
|
464
|
+
displayName: measure.displayName,
|
|
465
|
+
description: measure.description,
|
|
466
|
+
source: measure.source,
|
|
467
|
+
kind: measure.kind,
|
|
468
|
+
unit: measure.unit,
|
|
469
|
+
developmentSemantics: measure.developmentSemantics,
|
|
470
|
+
aggregation: "sum",
|
|
471
|
+
missing: measure.missing,
|
|
472
|
+
basisId: measure.basisId ?? null,
|
|
473
|
+
countPopulationId: measure.countPopulationId ?? null,
|
|
474
|
+
exposureBasisId: measure.exposureBasisId ?? null,
|
|
475
|
+
exposureTiming: measure.exposureTiming ?? null,
|
|
476
|
+
}))
|
|
477
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
478
|
+
countPopulations: definition.countPopulations
|
|
479
|
+
.map((population) => ({
|
|
480
|
+
id: population.id,
|
|
481
|
+
displayName: population.displayName,
|
|
482
|
+
subject: population.subject,
|
|
483
|
+
unit: population.unit,
|
|
484
|
+
description: population.description,
|
|
485
|
+
attributes: attributes(population.attributes),
|
|
486
|
+
}))
|
|
487
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
488
|
+
exposureBases: definition.exposureBases
|
|
489
|
+
.map((basis) => ({
|
|
490
|
+
id: basis.id,
|
|
491
|
+
displayName: basis.displayName,
|
|
492
|
+
basis: basis.basis,
|
|
493
|
+
unit: basis.unit,
|
|
494
|
+
description: basis.description,
|
|
495
|
+
sourceDescription: basis.sourceDescription ?? null,
|
|
496
|
+
attributes: attributes(basis.attributes),
|
|
497
|
+
}))
|
|
498
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
499
|
+
amountBases: definition.amountBases
|
|
500
|
+
.map((basis) => ({
|
|
501
|
+
id: basis.id,
|
|
502
|
+
displayName: basis.displayName,
|
|
503
|
+
currency: basis.currency,
|
|
504
|
+
perspective: basis.perspective,
|
|
505
|
+
components: basis.components
|
|
506
|
+
.map((component) => ({
|
|
507
|
+
id: component.id,
|
|
508
|
+
treatment: component.treatment,
|
|
509
|
+
limitation: normalizeLimitation(component.limitation),
|
|
510
|
+
}))
|
|
511
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
512
|
+
sourceDescription: basis.sourceDescription ?? null,
|
|
513
|
+
attributes: attributes(basis.attributes),
|
|
514
|
+
}))
|
|
515
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
516
|
+
derivedMeasures: topologicalDerivations(definition.derivedMeasures).map((derivation) => ({
|
|
517
|
+
id: derivation.id,
|
|
518
|
+
outputMeasureId: derivation.outputMeasureId,
|
|
519
|
+
expression: normalizeMeasureExpression(derivation.expression),
|
|
520
|
+
})),
|
|
521
|
+
formulas: definition.formulas
|
|
522
|
+
.map((formula) => ({
|
|
523
|
+
id: formula.id,
|
|
524
|
+
version: formula.version,
|
|
525
|
+
roles: sortedRecord(Object.entries(formula.roles).map(([name, role]) => [
|
|
526
|
+
name,
|
|
527
|
+
{
|
|
528
|
+
kind: role.kind,
|
|
529
|
+
compatibilityGroup: role.compatibilityGroup ?? null,
|
|
530
|
+
developmentSemantics: role.developmentSemantics ?? null,
|
|
531
|
+
},
|
|
532
|
+
])),
|
|
533
|
+
numerator: normalizeRoleExpression(formula.numerator),
|
|
534
|
+
denominator: normalizeRoleExpression(formula.denominator),
|
|
535
|
+
denominatorPolicy: "positive-or-null",
|
|
536
|
+
}))
|
|
537
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
538
|
+
instances: definition.instances.map((instance) => ({
|
|
539
|
+
id: instance.id,
|
|
540
|
+
version: instance.version,
|
|
541
|
+
formulaId: instance.formulaId,
|
|
542
|
+
bindings: sortedRecord(Object.entries(instance.bindings).map(([role, expression]) => [role, normalizeMeasureExpression(expression)])),
|
|
543
|
+
presentation: {
|
|
544
|
+
displayName: instance.presentation.displayName,
|
|
545
|
+
description: instance.presentation.description,
|
|
546
|
+
displayUnit: instance.presentation.displayUnit,
|
|
547
|
+
scale: Object.is(instance.presentation.scale, -0)
|
|
548
|
+
? 0
|
|
549
|
+
: instance.presentation.scale,
|
|
550
|
+
numeratorLabel: instance.presentation.numeratorLabel,
|
|
551
|
+
denominatorLabel: instance.presentation.denominatorLabel,
|
|
552
|
+
},
|
|
553
|
+
rules: instance.rules.map((rule) => ({
|
|
554
|
+
id: rule.id,
|
|
555
|
+
code: rule.code,
|
|
556
|
+
message: rule.message,
|
|
557
|
+
severity: rule.severity,
|
|
558
|
+
when: {
|
|
559
|
+
left: normalizeRuleOperand(rule.when.left),
|
|
560
|
+
operator: rule.when.operator,
|
|
561
|
+
right: normalizeRuleOperand(rule.when.right),
|
|
562
|
+
tolerance: normalizeTolerance(rule.when.tolerance),
|
|
563
|
+
},
|
|
564
|
+
})),
|
|
565
|
+
})),
|
|
566
|
+
reviewRules: definition.reviewRules.map((rule) => normalizeReviewRule(rule, definition.periodAxis)),
|
|
567
|
+
periodAxis: normalizePeriodAxis(definition.periodAxis),
|
|
568
|
+
};
|
|
569
|
+
return deepFreeze(normalized);
|
|
570
|
+
}
|
|
571
|
+
export function normalizeDiagnosticSourceLocation(source) {
|
|
572
|
+
return deepFreeze({
|
|
573
|
+
artifactId: source.artifactId,
|
|
574
|
+
sourceFile: source.sourceFile ?? null,
|
|
575
|
+
sourceSheet: source.sourceSheet ?? null,
|
|
576
|
+
sourceRow: source.sourceRow === undefined
|
|
577
|
+
? null
|
|
578
|
+
: Object.is(source.sourceRow, -0)
|
|
579
|
+
? 0
|
|
580
|
+
: source.sourceRow,
|
|
581
|
+
sourceCell: source.sourceCell ?? null,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
function expressionDependencies(expression) {
|
|
585
|
+
if (expression.op === "measure" || expression.op === "claim-layer")
|
|
586
|
+
return [expression.measureId];
|
|
587
|
+
if (expression.op === "subtract")
|
|
588
|
+
return sortedUnique([
|
|
589
|
+
...expressionDependencies(expression.left),
|
|
590
|
+
...expressionDependencies(expression.right),
|
|
591
|
+
]);
|
|
592
|
+
return sortedUnique(expression.terms.flatMap(expressionDependencies));
|
|
593
|
+
}
|
|
594
|
+
function ruleMeasureDependencies(operand) {
|
|
595
|
+
return operand.source === "measure"
|
|
596
|
+
? expressionDependencies(operand.expression)
|
|
597
|
+
: [];
|
|
598
|
+
}
|
|
599
|
+
function tag(kind, property, value) {
|
|
600
|
+
return `fnv1a64-jcs-v1:${fnv1a64(canonicalJson({
|
|
601
|
+
identityVersion: 1,
|
|
602
|
+
kind,
|
|
603
|
+
[property]: value,
|
|
604
|
+
}))}`;
|
|
605
|
+
}
|
|
606
|
+
export function buildDiagnosticIdentities(definition) {
|
|
607
|
+
const formulas = new Map(definition.formulas.map((formula) => [formula.id, formula]));
|
|
608
|
+
const measures = new Map(definition.measures.map((measure) => [measure.id, measure]));
|
|
609
|
+
const derivations = new Map(definition.derivedMeasures.map((derivation) => [
|
|
610
|
+
derivation.outputMeasureId,
|
|
611
|
+
derivation,
|
|
612
|
+
]));
|
|
613
|
+
const populations = new Map(definition.countPopulations.map((population) => [
|
|
614
|
+
population.id,
|
|
615
|
+
population,
|
|
616
|
+
]));
|
|
617
|
+
const exposureBases = new Map(definition.exposureBases.map((basis) => [basis.id, basis]));
|
|
618
|
+
const amountBases = new Map(definition.amountBases.map((basis) => [basis.id, basis]));
|
|
619
|
+
const formulaFingerprints = sortedRecord(definition.formulas.map((formula) => [formula.id, tag("diagnostic-formula", "formula", formula)]));
|
|
620
|
+
const calculationFingerprints = Object.create(null);
|
|
621
|
+
const calculationScopesByInstanceId = new Map();
|
|
622
|
+
const calculationDependenciesByInstanceId = new Map();
|
|
623
|
+
const evaluationDependenciesByInstanceId = new Map();
|
|
624
|
+
const transitiveDependencies = (roots) => {
|
|
625
|
+
const found = new Set();
|
|
626
|
+
const stack = [...roots];
|
|
627
|
+
while (stack.length > 0) {
|
|
628
|
+
const id = stack.pop();
|
|
629
|
+
if (found.has(id))
|
|
630
|
+
continue;
|
|
631
|
+
found.add(id);
|
|
632
|
+
const derivation = derivations.get(id);
|
|
633
|
+
if (derivation)
|
|
634
|
+
stack.push(...expressionDependencies(derivation.expression));
|
|
635
|
+
}
|
|
636
|
+
return [...found].sort(compareCodeUnits);
|
|
637
|
+
};
|
|
638
|
+
for (const instance of definition.instances) {
|
|
639
|
+
const calculationDependencies = transitiveDependencies(Object.values(instance.bindings).flatMap(expressionDependencies));
|
|
640
|
+
const evaluationDependencies = transitiveDependencies([
|
|
641
|
+
...calculationDependencies,
|
|
642
|
+
...instance.rules.flatMap((rule) => [
|
|
643
|
+
...ruleMeasureDependencies(rule.when.left),
|
|
644
|
+
...ruleMeasureDependencies(rule.when.right),
|
|
645
|
+
]),
|
|
646
|
+
]);
|
|
647
|
+
calculationDependenciesByInstanceId.set(instance.id, calculationDependencies);
|
|
648
|
+
evaluationDependenciesByInstanceId.set(instance.id, evaluationDependencies);
|
|
649
|
+
const selectedMeasures = calculationDependencies
|
|
650
|
+
.map((id) => measures.get(id))
|
|
651
|
+
.filter(Boolean);
|
|
652
|
+
const selectedDerivations = definition.derivedMeasures.filter((derivation) => calculationDependencies.includes(derivation.outputMeasureId));
|
|
653
|
+
const countPopulationIds = sortedUnique(selectedMeasures.flatMap((measure) => measure.countPopulationId === null ? [] : [measure.countPopulationId]));
|
|
654
|
+
const exposureBasisIds = sortedUnique(selectedMeasures.flatMap((measure) => measure.exposureBasisId === null ? [] : [measure.exposureBasisId]));
|
|
655
|
+
const amountBasisIds = sortedUnique(selectedMeasures.flatMap((measure) => measure.basisId === null ? [] : [measure.basisId]));
|
|
656
|
+
const scope = {
|
|
657
|
+
formulaFingerprint: formulaFingerprints[instance.formulaId],
|
|
658
|
+
instance: {
|
|
659
|
+
id: instance.id,
|
|
660
|
+
version: instance.version,
|
|
661
|
+
formulaId: instance.formulaId,
|
|
662
|
+
bindings: instance.bindings,
|
|
663
|
+
},
|
|
664
|
+
lossRowGrain: definition.lossRowGrain,
|
|
665
|
+
measures: selectedMeasures.map((measure) => ({
|
|
666
|
+
id: measure.id,
|
|
667
|
+
source: measure.source,
|
|
668
|
+
kind: measure.kind,
|
|
669
|
+
unit: measure.unit,
|
|
670
|
+
developmentSemantics: measure.developmentSemantics,
|
|
671
|
+
aggregation: "sum",
|
|
672
|
+
missing: measure.missing,
|
|
673
|
+
basisId: measure.basisId,
|
|
674
|
+
countPopulationId: measure.countPopulationId,
|
|
675
|
+
exposureBasisId: measure.exposureBasisId,
|
|
676
|
+
exposureTiming: measure.exposureTiming,
|
|
677
|
+
})),
|
|
678
|
+
countPopulations: countPopulationIds.map((id) => {
|
|
679
|
+
const population = populations.get(id);
|
|
680
|
+
return {
|
|
681
|
+
id,
|
|
682
|
+
subject: population.subject,
|
|
683
|
+
unit: population.unit,
|
|
684
|
+
attributes: population.attributes,
|
|
685
|
+
};
|
|
686
|
+
}),
|
|
687
|
+
exposureBases: exposureBasisIds.map((id) => {
|
|
688
|
+
const basis = exposureBases.get(id);
|
|
689
|
+
return {
|
|
690
|
+
id,
|
|
691
|
+
basis: basis.basis,
|
|
692
|
+
unit: basis.unit,
|
|
693
|
+
attributes: basis.attributes,
|
|
694
|
+
};
|
|
695
|
+
}),
|
|
696
|
+
amountBases: amountBasisIds.map((id) => {
|
|
697
|
+
const basis = amountBases.get(id);
|
|
698
|
+
return {
|
|
699
|
+
id,
|
|
700
|
+
currency: basis.currency,
|
|
701
|
+
perspective: basis.perspective,
|
|
702
|
+
components: basis.components,
|
|
703
|
+
attributes: basis.attributes,
|
|
704
|
+
};
|
|
705
|
+
}),
|
|
706
|
+
derivedMeasures: selectedDerivations,
|
|
707
|
+
};
|
|
708
|
+
const frozenScope = deepFreeze(scope);
|
|
709
|
+
calculationScopesByInstanceId.set(instance.id, frozenScope);
|
|
710
|
+
calculationFingerprints[instance.id] = tag("diagnostic-calculation", "calculation", frozenScope);
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
formulaFingerprints: deepFreeze(formulaFingerprints),
|
|
714
|
+
calculationFingerprints: deepFreeze(sortedRecord(Object.entries(calculationFingerprints))),
|
|
715
|
+
definitionIntegrity: tag("diagnostic-definition", "definition", definition),
|
|
716
|
+
calculationScopesByInstanceId,
|
|
717
|
+
calculationDependenciesByInstanceId,
|
|
718
|
+
evaluationDependenciesByInstanceId,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
//# sourceMappingURL=diagnosticIdentity.js.map
|