@actuarial-ts/core 0.5.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 -226
- 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 +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 -189
- 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
|
@@ -0,0 +1,1026 @@
|
|
|
1
|
+
import { MAX_DIAGNOSTIC_DEFINITION_EXPRESSION_NODES, walkDiagnosticExpression, } from "./diagnosticExpressions.js";
|
|
2
|
+
import { buildDiagnosticIdentities, normalizeDiagnosticDefinition, } from "./diagnosticIdentity.js";
|
|
3
|
+
import { DiagnosticValidationError, } from "./types.js";
|
|
4
|
+
import { canonicalJson } from "./canonical.js";
|
|
5
|
+
const compiledDiagnosticDefinitionBrand = Symbol("compiled-diagnostic-definition");
|
|
6
|
+
const authenticCompiledDefinitions = new WeakSet();
|
|
7
|
+
const compiledInternals = new WeakMap();
|
|
8
|
+
function isPlainRecord(value) {
|
|
9
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
10
|
+
return false;
|
|
11
|
+
const prototype = Object.getPrototypeOf(value);
|
|
12
|
+
return prototype === Object.prototype || prototype === null;
|
|
13
|
+
}
|
|
14
|
+
function propertyPath(parent, key) {
|
|
15
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
|
|
16
|
+
? `${parent}.${key}`
|
|
17
|
+
: `${parent}[${JSON.stringify(key)}]`;
|
|
18
|
+
}
|
|
19
|
+
function pushIssue(issues, code, path, message) {
|
|
20
|
+
issues.push({ domain: "definition", code, path, message });
|
|
21
|
+
}
|
|
22
|
+
function hasWellFormedUtf16(value) {
|
|
23
|
+
for (let index = 0; index < value.length; index++) {
|
|
24
|
+
const code = value.charCodeAt(index);
|
|
25
|
+
if (code === 0)
|
|
26
|
+
return false;
|
|
27
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
28
|
+
const next = value.charCodeAt(index + 1);
|
|
29
|
+
if (!(next >= 0xdc00 && next <= 0xdfff))
|
|
30
|
+
return false;
|
|
31
|
+
index++;
|
|
32
|
+
}
|
|
33
|
+
else if (code >= 0xdc00 && code <= 0xdfff)
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
function validateToken(value, path, label, issues) {
|
|
39
|
+
if (typeof value !== "string") {
|
|
40
|
+
pushIssue(issues, "invalid-type", path, `${label} must be a string`);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (!hasWellFormedUtf16(value)) {
|
|
44
|
+
pushIssue(issues, "invalid-string", path, `${label} contains an invalid Unicode string`);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (value.length === 0 || /^[\u0009-\u000d\u0020]|[\u0009-\u000d\u0020]$/.test(value)) {
|
|
48
|
+
pushIssue(issues, "invalid-string", path, `${label} must be nonempty without surrounding ASCII whitespace`);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
function validateFinite(value, path, label, issues) {
|
|
54
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
55
|
+
pushIssue(issues, "invalid-number", path, `${label} must be finite`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
function validateJsonValue(value, issues) {
|
|
61
|
+
const active = new WeakSet();
|
|
62
|
+
const stack = [{ value, path: "$", exiting: false }];
|
|
63
|
+
while (stack.length > 0) {
|
|
64
|
+
const frame = stack.pop();
|
|
65
|
+
if (frame.value === null || typeof frame.value === "boolean")
|
|
66
|
+
continue;
|
|
67
|
+
if (typeof frame.value === "string") {
|
|
68
|
+
if (!hasWellFormedUtf16(frame.value)) {
|
|
69
|
+
pushIssue(issues, "invalid-json-value", frame.path, "String is not well-formed UTF-16 or contains U+0000");
|
|
70
|
+
}
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (typeof frame.value === "number") {
|
|
74
|
+
if (!Number.isFinite(frame.value)) {
|
|
75
|
+
pushIssue(issues, "invalid-json-value", frame.path, "JSON numeric value must be finite");
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (typeof frame.value !== "object" || frame.value === undefined) {
|
|
80
|
+
pushIssue(issues, "invalid-json-value", frame.path, "Value is not plain JSON data");
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const object = frame.value;
|
|
84
|
+
if (frame.exiting) {
|
|
85
|
+
active.delete(object);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (active.has(object)) {
|
|
89
|
+
pushIssue(issues, "cycle", frame.path, "JSON value contains a cycle");
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!Array.isArray(object) && !isPlainRecord(object)) {
|
|
93
|
+
pushIssue(issues, "invalid-json-value", frame.path, "Value must use a plain object or array prototype");
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
active.add(object);
|
|
97
|
+
stack.push({ value: object, path: frame.path, exiting: true });
|
|
98
|
+
if (Array.isArray(object)) {
|
|
99
|
+
for (let index = object.length - 1; index >= 0; index--) {
|
|
100
|
+
stack.push({ value: object[index], path: `${frame.path}[${index}]`, exiting: false });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
for (const key of Object.keys(object).sort().reverse()) {
|
|
105
|
+
if (!hasWellFormedUtf16(key)) {
|
|
106
|
+
pushIssue(issues, "invalid-json-value", propertyPath(frame.path, key), "Object key is not well-formed UTF-16 or contains U+0000");
|
|
107
|
+
}
|
|
108
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
109
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
110
|
+
pushIssue(issues, "invalid-json-value", propertyPath(frame.path, key), "JSON objects may contain only data properties");
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
stack.push({ value: descriptor.value, path: propertyPath(frame.path, key), exiting: false });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function requireArray(value, path, issues) {
|
|
119
|
+
if (!Array.isArray(value)) {
|
|
120
|
+
pushIssue(issues, "invalid-type", path, "Expected an array");
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
function collectCatalog(values, path, issues) {
|
|
126
|
+
const result = new Map();
|
|
127
|
+
values.forEach((value, index) => {
|
|
128
|
+
const idPath = `${path}[${index}].id`;
|
|
129
|
+
if (!validateToken(value?.id, idPath, "ID", issues))
|
|
130
|
+
return;
|
|
131
|
+
if (result.has(value.id)) {
|
|
132
|
+
pushIssue(issues, "duplicate-id", idPath, `Duplicate ID ${value.id}`);
|
|
133
|
+
}
|
|
134
|
+
else
|
|
135
|
+
result.set(value.id, value);
|
|
136
|
+
});
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
function semanticReference(measure) {
|
|
140
|
+
if (measure.kind === "amount")
|
|
141
|
+
return `amount:${measure.basisId ?? ""}`;
|
|
142
|
+
if (measure.kind === "count")
|
|
143
|
+
return `count:${measure.countPopulationId ?? ""}`;
|
|
144
|
+
return `exposure:${measure.exposureBasisId ?? ""}`;
|
|
145
|
+
}
|
|
146
|
+
function transitiveMeasureIds(roots, derivations) {
|
|
147
|
+
const result = new Set();
|
|
148
|
+
const stack = [...roots];
|
|
149
|
+
while (stack.length > 0) {
|
|
150
|
+
const id = stack.pop();
|
|
151
|
+
if (result.has(id))
|
|
152
|
+
continue;
|
|
153
|
+
result.add(id);
|
|
154
|
+
const derivation = derivations.get(id);
|
|
155
|
+
if (derivation) {
|
|
156
|
+
stack.push(...walkDiagnosticExpression(derivation.expression, "claim", "$", []).dependencies);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return [...result].sort();
|
|
160
|
+
}
|
|
161
|
+
function projectedAmountBasis(basis) {
|
|
162
|
+
return {
|
|
163
|
+
currency: basis.currency,
|
|
164
|
+
perspective: basis.perspective,
|
|
165
|
+
components: [...basis.components].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function projectedBasisKey(basis) {
|
|
169
|
+
return canonicalJson(basis);
|
|
170
|
+
}
|
|
171
|
+
function projectClaimExpression(expression, path, measures, amountBases, issues) {
|
|
172
|
+
if (expression.op === "measure") {
|
|
173
|
+
const measure = measures.get(expression.measureId);
|
|
174
|
+
if (!measure)
|
|
175
|
+
return null;
|
|
176
|
+
if (measure.kind !== "amount") {
|
|
177
|
+
return { kind: measure.kind, unit: measure.unit, reference: semanticReference(measure) };
|
|
178
|
+
}
|
|
179
|
+
const basis = measure.basisId ? amountBases.get(measure.basisId) : undefined;
|
|
180
|
+
return basis
|
|
181
|
+
? { kind: "amount", unit: measure.unit, reference: `amount:${measure.basisId}`, amountBasis: projectedAmountBasis(basis) }
|
|
182
|
+
: null;
|
|
183
|
+
}
|
|
184
|
+
if (expression.op === "claim-layer") {
|
|
185
|
+
const measure = measures.get(expression.measureId);
|
|
186
|
+
if (!measure || measure.kind !== "amount" || !measure.basisId) {
|
|
187
|
+
pushIssue(issues, "incompatible-semantics", path, "Claim layers require an amount measure");
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const basis = amountBases.get(measure.basisId);
|
|
191
|
+
const component = basis?.components[0];
|
|
192
|
+
if (!basis || basis.components.length !== 1 || component?.treatment !== "included" || component.limitation.kind !== "unlimited") {
|
|
193
|
+
pushIssue(issues, "incompatible-semantics", path, "Claim layers require one included unlimited source component");
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
kind: "amount",
|
|
198
|
+
unit: measure.unit,
|
|
199
|
+
reference: "amount:projected",
|
|
200
|
+
amountBasis: {
|
|
201
|
+
currency: basis.currency,
|
|
202
|
+
perspective: basis.perspective,
|
|
203
|
+
components: [{
|
|
204
|
+
id: component.id,
|
|
205
|
+
treatment: "included",
|
|
206
|
+
limitation: {
|
|
207
|
+
kind: "layer",
|
|
208
|
+
attachment: expression.attachment,
|
|
209
|
+
limit: expression.limit,
|
|
210
|
+
application: "claim",
|
|
211
|
+
derivation: { kind: "sdk" },
|
|
212
|
+
},
|
|
213
|
+
}],
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const children = expression.op === "add" ? expression.terms : [expression.left, expression.right];
|
|
218
|
+
const projected = children.map((child, index) => projectClaimExpression(child, expression.op === "add" ? `${path}.terms[${index}]` : `${path}.${index === 0 ? "left" : "right"}`, measures, amountBases, issues));
|
|
219
|
+
if (projected.some((item) => item === null))
|
|
220
|
+
return null;
|
|
221
|
+
const present = projected;
|
|
222
|
+
if (expression.op === "subtract") {
|
|
223
|
+
const [left, right] = present;
|
|
224
|
+
if (left.kind !== right.kind || left.unit !== right.unit ||
|
|
225
|
+
(left.kind === "amount"
|
|
226
|
+
? projectedBasisKey(left.amountBasis) !== projectedBasisKey(right.amountBasis)
|
|
227
|
+
: left.reference !== right.reference)) {
|
|
228
|
+
pushIssue(issues, "incompatible-semantics", path, "Claim subtraction requires one exact semantic basis");
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
return left;
|
|
232
|
+
}
|
|
233
|
+
const first = present[0];
|
|
234
|
+
if (present.some((item) => item.kind !== first.kind || item.unit !== first.unit)) {
|
|
235
|
+
pushIssue(issues, "incompatible-semantics", path, "Claim addition requires one kind and unit");
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
if (first.kind !== "amount") {
|
|
239
|
+
if (present.some((item) => item.reference !== first.reference)) {
|
|
240
|
+
pushIssue(issues, "incompatible-semantics", path, "Claim addition requires one exact semantic basis");
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
return first;
|
|
244
|
+
}
|
|
245
|
+
const bases = present.map((item) => item.amountBasis);
|
|
246
|
+
if (bases.some((basis) => basis.currency !== bases[0].currency || basis.perspective !== bases[0].perspective)) {
|
|
247
|
+
pushIssue(issues, "incompatible-semantics", path, "Amount addition cannot mix currency or perspective");
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
const componentIds = new Set();
|
|
251
|
+
const components = [];
|
|
252
|
+
for (const basis of bases) {
|
|
253
|
+
for (const component of basis.components) {
|
|
254
|
+
if (componentIds.has(component.id)) {
|
|
255
|
+
pushIssue(issues, "incompatible-semantics", path, `Amount addition overlaps component ${component.id}`);
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
componentIds.add(component.id);
|
|
259
|
+
components.push(component);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const amountBasis = {
|
|
263
|
+
currency: bases[0].currency,
|
|
264
|
+
perspective: bases[0].perspective,
|
|
265
|
+
components: components.sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
266
|
+
};
|
|
267
|
+
return { kind: "amount", unit: first.unit, reference: "amount:projected", amountBasis };
|
|
268
|
+
}
|
|
269
|
+
function expressionSemantics(expression, path, measures, issues) {
|
|
270
|
+
const walked = walkDiagnosticExpression(expression, "measure", path, issues);
|
|
271
|
+
const found = walked.dependencies
|
|
272
|
+
.map((id) => measures.get(id))
|
|
273
|
+
.filter((value) => value !== undefined);
|
|
274
|
+
for (const id of walked.dependencies) {
|
|
275
|
+
if (!measures.has(id)) {
|
|
276
|
+
pushIssue(issues, "unknown-reference", path, `Unknown measure ${id}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const signatures = new Set(found.map((measure) => `${measure.kind}\u0000${measure.unit}\u0000${semanticReference(measure)}`));
|
|
280
|
+
if (signatures.size > 1) {
|
|
281
|
+
pushIssue(issues, "incompatible-semantics", path, "Expression combines incompatible measure semantics");
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
measureIds: walked.dependencies,
|
|
285
|
+
signature: signatures.size === 1 ? [...signatures][0] : null,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function validateTolerance(value, path, issues) {
|
|
289
|
+
for (const key of ["absolute", "relative"]) {
|
|
290
|
+
const component = value?.[key];
|
|
291
|
+
if (component === undefined)
|
|
292
|
+
continue;
|
|
293
|
+
if (!validateFinite(component, `${path}.${key}`, `${key} tolerance`, issues))
|
|
294
|
+
continue;
|
|
295
|
+
if (component < 0) {
|
|
296
|
+
pushIssue(issues, "invalid-number", `${path}.${key}`, `${key} tolerance must be nonnegative`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function validatePeriodAxis(axis, issues) {
|
|
301
|
+
if (!isPlainRecord(axis)) {
|
|
302
|
+
pushIssue(issues, "invalid-type", "$.periodAxis", "Period axis must be an object");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (!validateFinite(axis.ageOffset, "$.periodAxis.ageOffset", "Period age offset", issues) ||
|
|
306
|
+
!Number.isSafeInteger(axis.ageOffset)) {
|
|
307
|
+
pushIssue(issues, "invalid-number", "$.periodAxis.ageOffset", "Period age offset must be a safe integer");
|
|
308
|
+
}
|
|
309
|
+
if (axis.kind === "calendar") {
|
|
310
|
+
if (!["month", "quarter", "year"].includes(axis.originCadence)) {
|
|
311
|
+
pushIssue(issues, "invalid-period", "$.periodAxis.originCadence", "Unknown origin cadence");
|
|
312
|
+
}
|
|
313
|
+
if (!["month", "quarter", "year"].includes(axis.valuationCadence)) {
|
|
314
|
+
pushIssue(issues, "invalid-period", "$.periodAxis.valuationCadence", "Unknown valuation cadence");
|
|
315
|
+
}
|
|
316
|
+
if (axis.ageUnit !== "month") {
|
|
317
|
+
pushIssue(issues, "invalid-period", "$.periodAxis.ageUnit", "Calendar axes use month age units");
|
|
318
|
+
}
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (axis.kind !== "ordered") {
|
|
322
|
+
pushIssue(issues, "invalid-period", "$.periodAxis.kind", "Unknown period-axis kind");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
validateToken(axis.id, "$.periodAxis.id", "Ordered-axis ID", issues);
|
|
326
|
+
validateToken(axis.version, "$.periodAxis.version", "Ordered-axis version", issues);
|
|
327
|
+
validateToken(axis.ageUnit, "$.periodAxis.ageUnit", "Ordered-axis age unit", issues);
|
|
328
|
+
for (const side of ["origins", "valuations"]) {
|
|
329
|
+
const coordinates = requireArray(axis[side], `$.periodAxis.${side}`, issues);
|
|
330
|
+
const names = new Set();
|
|
331
|
+
const coordinateValues = new Set();
|
|
332
|
+
coordinates.forEach((coordinate, index) => {
|
|
333
|
+
const base = `$.periodAxis.${side}[${index}]`;
|
|
334
|
+
if (validateToken(coordinate?.label, `${base}.label`, "Period label", issues)) {
|
|
335
|
+
if (names.has(coordinate.label))
|
|
336
|
+
pushIssue(issues, "duplicate-id", `${base}.label`, `Duplicate period label or alias ${coordinate.label}`);
|
|
337
|
+
names.add(coordinate.label);
|
|
338
|
+
}
|
|
339
|
+
if (!validateFinite(coordinate?.coordinate, `${base}.coordinate`, "Period coordinate", issues) ||
|
|
340
|
+
!Number.isSafeInteger(coordinate.coordinate)) {
|
|
341
|
+
pushIssue(issues, "invalid-number", `${base}.coordinate`, "Period coordinate must be a safe integer");
|
|
342
|
+
}
|
|
343
|
+
else if (coordinateValues.has(coordinate.coordinate)) {
|
|
344
|
+
pushIssue(issues, "duplicate-id", `${base}.coordinate`, `Duplicate period coordinate ${coordinate.coordinate}`);
|
|
345
|
+
}
|
|
346
|
+
else
|
|
347
|
+
coordinateValues.add(coordinate.coordinate);
|
|
348
|
+
for (const [aliasIndex, alias] of (coordinate?.aliases ?? []).entries()) {
|
|
349
|
+
if (!validateToken(alias, `${base}.aliases[${aliasIndex}]`, "Period alias", issues))
|
|
350
|
+
continue;
|
|
351
|
+
if (names.has(alias))
|
|
352
|
+
pushIssue(issues, "duplicate-id", `${base}.aliases[${aliasIndex}]`, `Duplicate period label or alias ${alias}`);
|
|
353
|
+
names.add(alias);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function validateDefinition(value) {
|
|
359
|
+
const issues = [];
|
|
360
|
+
validateJsonValue(value, issues);
|
|
361
|
+
if (!isPlainRecord(value)) {
|
|
362
|
+
pushIssue(issues, "invalid-type", "$", "Diagnostic definition must be a plain object");
|
|
363
|
+
throw new DiagnosticValidationError(issues);
|
|
364
|
+
}
|
|
365
|
+
const definition = value;
|
|
366
|
+
if (definition.diagnosticDefinitionVersion !== "1.0.0") {
|
|
367
|
+
pushIssue(issues, "invalid-type", "$.diagnosticDefinitionVersion", "Diagnostic definition version must be 1.0.0");
|
|
368
|
+
}
|
|
369
|
+
validateToken(definition.id, "$.id", "Definition ID", issues);
|
|
370
|
+
validateToken(definition.version, "$.version", "Definition version", issues);
|
|
371
|
+
if (definition.lossRowGrain !== "claim" && definition.lossRowGrain !== "aggregate") {
|
|
372
|
+
pushIssue(issues, "invalid-type", "$.lossRowGrain", "Loss row grain must be claim or aggregate");
|
|
373
|
+
}
|
|
374
|
+
const measures = requireArray(definition.measures, "$.measures", issues);
|
|
375
|
+
const populations = requireArray(definition.countPopulations, "$.countPopulations", issues);
|
|
376
|
+
const exposureBases = requireArray(definition.exposureBases, "$.exposureBases", issues);
|
|
377
|
+
const amountBases = requireArray(definition.amountBases, "$.amountBases", issues);
|
|
378
|
+
const derivations = requireArray(definition.derivedMeasures, "$.derivedMeasures", issues);
|
|
379
|
+
const formulas = requireArray(definition.formulas, "$.formulas", issues);
|
|
380
|
+
const instances = requireArray(definition.instances, "$.instances", issues);
|
|
381
|
+
const reviewRules = requireArray(definition.reviewRules, "$.reviewRules", issues);
|
|
382
|
+
const measureMap = collectCatalog(measures, "$.measures", issues);
|
|
383
|
+
const populationMap = collectCatalog(populations, "$.countPopulations", issues);
|
|
384
|
+
const exposureBasisMap = collectCatalog(exposureBases, "$.exposureBases", issues);
|
|
385
|
+
const amountBasisMap = collectCatalog(amountBases, "$.amountBases", issues);
|
|
386
|
+
collectCatalog(derivations, "$.derivedMeasures", issues);
|
|
387
|
+
const derivationsByOutput = new Map();
|
|
388
|
+
for (const derivation of derivations) {
|
|
389
|
+
if (isPlainRecord(derivation) && typeof derivation.outputMeasureId === "string" &&
|
|
390
|
+
!derivationsByOutput.has(derivation.outputMeasureId)) {
|
|
391
|
+
derivationsByOutput.set(derivation.outputMeasureId, derivation);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const formulaMap = collectCatalog(formulas, "$.formulas", issues);
|
|
395
|
+
collectCatalog(instances, "$.instances", issues);
|
|
396
|
+
const reviewRuleMap = collectCatalog(reviewRules, "$.reviewRules", issues);
|
|
397
|
+
populations.forEach((population, index) => {
|
|
398
|
+
const base = `$.countPopulations[${index}]`;
|
|
399
|
+
validateToken(population.displayName, `${base}.displayName`, "Population display name", issues);
|
|
400
|
+
validateToken(population.description, `${base}.description`, "Population description", issues);
|
|
401
|
+
validateToken(population.unit, `${base}.unit`, "Population unit", issues);
|
|
402
|
+
if (!["claim", "claimant", "policy", "occurrence", "other", "unknown"].includes(population.subject)) {
|
|
403
|
+
pushIssue(issues, "invalid-type", `${base}.subject`, "Unknown count-population subject");
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
exposureBases.forEach((basis, index) => {
|
|
407
|
+
const base = `$.exposureBases[${index}]`;
|
|
408
|
+
validateToken(basis.displayName, `${base}.displayName`, "Exposure-basis display name", issues);
|
|
409
|
+
validateToken(basis.description, `${base}.description`, "Exposure-basis description", issues);
|
|
410
|
+
validateToken(basis.unit, `${base}.unit`, "Exposure-basis unit", issues);
|
|
411
|
+
if (basis.sourceDescription !== undefined)
|
|
412
|
+
validateToken(basis.sourceDescription, `${base}.sourceDescription`, "Exposure source description", issues);
|
|
413
|
+
if (!["earned", "written", "in-force", "other", "unknown"].includes(basis.basis)) {
|
|
414
|
+
pushIssue(issues, "invalid-type", `${base}.basis`, "Unknown exposure basis");
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
amountBases.forEach((basis, index) => {
|
|
418
|
+
const base = `$.amountBases[${index}]`;
|
|
419
|
+
validateToken(basis.displayName, `${base}.displayName`, "Amount-basis display name", issues);
|
|
420
|
+
validateToken(basis.currency, `${base}.currency`, "Amount-basis currency", issues);
|
|
421
|
+
if (basis.sourceDescription !== undefined)
|
|
422
|
+
validateToken(basis.sourceDescription, `${base}.sourceDescription`, "Amount source description", issues);
|
|
423
|
+
if (!["gross", "net", "ceded", "other", "unknown"].includes(basis.perspective)) {
|
|
424
|
+
pushIssue(issues, "invalid-type", `${base}.perspective`, "Unknown amount perspective");
|
|
425
|
+
}
|
|
426
|
+
const components = requireArray(basis.components, `${base}.components`, issues);
|
|
427
|
+
if (components.length === 0)
|
|
428
|
+
pushIssue(issues, "missing-required", `${base}.components`, "Amount basis requires at least one component");
|
|
429
|
+
const componentIds = collectCatalog(components, `${base}.components`, issues);
|
|
430
|
+
if (componentIds.size > 0 && components.every((component) => component.treatment === "excluded")) {
|
|
431
|
+
pushIssue(issues, "incompatible-semantics", `${base}.components`, "Amount basis cannot exclude every component");
|
|
432
|
+
}
|
|
433
|
+
components.forEach((component, componentIndex) => {
|
|
434
|
+
const componentBase = `${base}.components[${componentIndex}]`;
|
|
435
|
+
if (!isPlainRecord(component)) {
|
|
436
|
+
pushIssue(issues, "invalid-type", componentBase, "Amount-basis component must be an object");
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (!["included", "excluded", "unknown"].includes(component.treatment)) {
|
|
440
|
+
pushIssue(issues, "invalid-type", `${componentBase}.treatment`, "Unknown component treatment");
|
|
441
|
+
}
|
|
442
|
+
const limitation = component.limitation;
|
|
443
|
+
if (!isPlainRecord(limitation)) {
|
|
444
|
+
pushIssue(issues, "invalid-type", `${componentBase}.limitation`, "Amount limitation must be an object");
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (limitation.kind === "unknown") {
|
|
448
|
+
if (limitation.description !== undefined)
|
|
449
|
+
validateToken(limitation.description, `${componentBase}.limitation.description`, "Unknown limitation description", issues);
|
|
450
|
+
}
|
|
451
|
+
else if (limitation.kind === "layer" || limitation.kind === "pre-limited") {
|
|
452
|
+
if (validateFinite(limitation.attachment, `${componentBase}.limitation.attachment`, "Attachment", issues) && limitation.attachment < 0) {
|
|
453
|
+
pushIssue(issues, "invalid-number", `${componentBase}.limitation.attachment`, "Attachment must be nonnegative");
|
|
454
|
+
}
|
|
455
|
+
if (limitation.limit !== null) {
|
|
456
|
+
if (validateFinite(limitation.limit, `${componentBase}.limitation.limit`, "Layer width", issues) && limitation.limit <= 0) {
|
|
457
|
+
pushIssue(issues, "invalid-number", `${componentBase}.limitation.limit`, "Layer width must be positive");
|
|
458
|
+
}
|
|
459
|
+
if (Number.isFinite(limitation.attachment) && Number.isFinite(limitation.limit) && !Number.isFinite(limitation.attachment + limitation.limit)) {
|
|
460
|
+
pushIssue(issues, "invalid-number", `${componentBase}.limitation.limit`, "Attachment plus layer width must be finite");
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (limitation.derivation?.kind === "sdk") {
|
|
464
|
+
if (limitation.kind !== "layer" || limitation.application !== "claim") {
|
|
465
|
+
pushIssue(issues, "incompatible-semantics", `${componentBase}.limitation.derivation`, "SDK limitations require a claim layer");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else if (limitation.derivation?.kind === "external") {
|
|
469
|
+
validateToken(limitation.derivation.transformationRef, `${componentBase}.limitation.derivation.transformationRef`, "Transformation reference", issues);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
pushIssue(issues, "invalid-type", `${componentBase}.limitation.derivation`, "Limited basis requires a derivation record");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else if (limitation.kind !== "unlimited") {
|
|
476
|
+
pushIssue(issues, "invalid-type", `${componentBase}.limitation.kind`, "Unknown amount limitation kind");
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
measures.forEach((measure, index) => {
|
|
481
|
+
const base = `$.measures[${index}]`;
|
|
482
|
+
validateToken(measure.displayName, `${base}.displayName`, "Measure display name", issues);
|
|
483
|
+
validateToken(measure.description, `${base}.description`, "Measure description", issues);
|
|
484
|
+
validateToken(measure.unit, `${base}.unit`, "Measure unit", issues);
|
|
485
|
+
if (measure.aggregation !== "sum")
|
|
486
|
+
pushIssue(issues, "incompatible-semantics", `${base}.aggregation`, "Only sum aggregation is supported");
|
|
487
|
+
if (measure.missing !== "unknown" && measure.missing !== "zero")
|
|
488
|
+
pushIssue(issues, "invalid-type", `${base}.missing`, "Unknown missing-value policy");
|
|
489
|
+
if (!["cumulative", "incremental", "point-in-time", "unknown"].includes(measure.developmentSemantics)) {
|
|
490
|
+
pushIssue(issues, "invalid-type", `${base}.developmentSemantics`, "Unknown development semantics");
|
|
491
|
+
}
|
|
492
|
+
if (measure.kind === "amount") {
|
|
493
|
+
const basis = measure.basisId ? amountBasisMap.get(measure.basisId) : undefined;
|
|
494
|
+
if (!basis)
|
|
495
|
+
pushIssue(issues, "unknown-reference", `${base}.basisId`, "Amount measure requires an existing amount basis");
|
|
496
|
+
else if (measure.unit !== basis.currency)
|
|
497
|
+
pushIssue(issues, "incompatible-semantics", `${base}.unit`, "Amount measure unit must equal basis currency");
|
|
498
|
+
if (measure.countPopulationId !== undefined || measure.exposureBasisId !== undefined || measure.exposureTiming !== undefined) {
|
|
499
|
+
pushIssue(issues, "incompatible-semantics", base, "Amount measure has inapplicable population or exposure semantics");
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else if (measure.kind === "count") {
|
|
503
|
+
const population = measure.countPopulationId ? populationMap.get(measure.countPopulationId) : undefined;
|
|
504
|
+
if (!population)
|
|
505
|
+
pushIssue(issues, "unknown-reference", `${base}.countPopulationId`, "Count measure requires an existing count population");
|
|
506
|
+
else if (measure.unit !== population.unit)
|
|
507
|
+
pushIssue(issues, "incompatible-semantics", `${base}.unit`, "Count measure unit must equal population unit");
|
|
508
|
+
if (measure.basisId !== undefined || measure.exposureBasisId !== undefined || measure.exposureTiming !== undefined) {
|
|
509
|
+
pushIssue(issues, "incompatible-semantics", base, "Count measure has inapplicable amount or exposure semantics");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
else if (measure.kind === "exposure") {
|
|
513
|
+
const basis = measure.exposureBasisId ? exposureBasisMap.get(measure.exposureBasisId) : undefined;
|
|
514
|
+
if (!basis)
|
|
515
|
+
pushIssue(issues, "unknown-reference", `${base}.exposureBasisId`, "Exposure measure requires an existing exposure basis");
|
|
516
|
+
else if (measure.unit !== basis.unit)
|
|
517
|
+
pushIssue(issues, "incompatible-semantics", `${base}.unit`, "Exposure measure unit must equal exposure-basis unit");
|
|
518
|
+
if (measure.source !== "exposure")
|
|
519
|
+
pushIssue(issues, "incompatible-semantics", `${base}.source`, "Exposure measure source must be exposure");
|
|
520
|
+
if (measure.exposureTiming !== "origin-static" && measure.exposureTiming !== "valuation-specific") {
|
|
521
|
+
pushIssue(issues, "missing-required", `${base}.exposureTiming`, "Exposure measure requires timing");
|
|
522
|
+
}
|
|
523
|
+
if (measure.missing !== "unknown")
|
|
524
|
+
pushIssue(issues, "incompatible-semantics", `${base}.missing`, "Exposure missingness must remain unknown");
|
|
525
|
+
if (measure.basisId !== undefined || measure.countPopulationId !== undefined) {
|
|
526
|
+
pushIssue(issues, "incompatible-semantics", base, "Exposure measure has inapplicable amount or population semantics");
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
pushIssue(issues, "invalid-type", `${base}.kind`, "Unknown measure kind");
|
|
531
|
+
}
|
|
532
|
+
if (measure.kind !== "exposure" && measure.source !== "loss" && measure.source !== "derived") {
|
|
533
|
+
pushIssue(issues, "invalid-type", `${base}.source`, "Non-exposure measure source must be loss or derived");
|
|
534
|
+
}
|
|
535
|
+
if (measure.kind !== "exposure" && measure.source === "exposure") {
|
|
536
|
+
pushIssue(issues, "incompatible-semantics", `${base}.source`, "Only exposure measures may use exposure source");
|
|
537
|
+
}
|
|
538
|
+
if (measure.kind === "amount" && measure.basisId) {
|
|
539
|
+
const basis = amountBasisMap.get(measure.basisId);
|
|
540
|
+
const hasSdkLimitation = basis?.components.some((component) => (component.limitation.kind === "layer" || component.limitation.kind === "pre-limited") &&
|
|
541
|
+
component.limitation.derivation.kind === "sdk") ?? false;
|
|
542
|
+
if (hasSdkLimitation && measure.source !== "derived") {
|
|
543
|
+
pushIssue(issues, "incompatible-semantics", `${base}.source`, "SDK-limited amount measures must be derived");
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
let definitionExpressionNodes = 0;
|
|
548
|
+
const formulaRoleUsage = new Map();
|
|
549
|
+
formulas.forEach((formula, index) => {
|
|
550
|
+
const base = `$.formulas[${index}]`;
|
|
551
|
+
validateToken(formula.version, `${base}.version`, "Formula version", issues);
|
|
552
|
+
if (formula.denominatorPolicy !== "positive-or-null") {
|
|
553
|
+
pushIssue(issues, "incompatible-semantics", `${base}.denominatorPolicy`, "Unknown denominator policy");
|
|
554
|
+
}
|
|
555
|
+
if (!isPlainRecord(formula.roles)) {
|
|
556
|
+
pushIssue(issues, "invalid-type", `${base}.roles`, "Formula roles must be an object");
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
for (const [roleName, role] of Object.entries(formula.roles)) {
|
|
560
|
+
validateToken(roleName, propertyPath(`${base}.roles`, roleName), "Formula role", issues);
|
|
561
|
+
if (!isPlainRecord(role) || !["count", "amount", "exposure"].includes(role.kind)) {
|
|
562
|
+
pushIssue(issues, "invalid-type", propertyPath(`${base}.roles`, roleName), "Formula role has an invalid kind");
|
|
563
|
+
}
|
|
564
|
+
if (role.compatibilityGroup !== undefined)
|
|
565
|
+
validateToken(role.compatibilityGroup, `${propertyPath(`${base}.roles`, roleName)}.compatibilityGroup`, "Compatibility group", issues);
|
|
566
|
+
}
|
|
567
|
+
const numerator = walkDiagnosticExpression(formula.numerator, "role", `${base}.numerator`, issues);
|
|
568
|
+
const denominator = walkDiagnosticExpression(formula.denominator, "role", `${base}.denominator`, issues);
|
|
569
|
+
definitionExpressionNodes += numerator.nodeCount + denominator.nodeCount;
|
|
570
|
+
const used = [...new Set([...numerator.dependencies, ...denominator.dependencies])].sort();
|
|
571
|
+
formulaRoleUsage.set(formula.id, used);
|
|
572
|
+
for (const role of used) {
|
|
573
|
+
if (!Object.prototype.hasOwnProperty.call(formula.roles, role)) {
|
|
574
|
+
pushIssue(issues, "unknown-reference", base, `Formula ${formula.id} references unknown role ${role}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
for (const role of Object.keys(formula.roles)) {
|
|
578
|
+
if (!used.includes(role))
|
|
579
|
+
pushIssue(issues, "incompatible-semantics", propertyPath(`${base}.roles`, role), `Formula role ${role} is unused`);
|
|
580
|
+
}
|
|
581
|
+
for (const [side, walked] of [["numerator", numerator], ["denominator", denominator]]) {
|
|
582
|
+
const kinds = new Set(walked.dependencies.map((role) => formula.roles[role]?.kind).filter(Boolean));
|
|
583
|
+
if (kinds.size > 1)
|
|
584
|
+
pushIssue(issues, "incompatible-semantics", `${base}.${side}`, "Formula arithmetic combines different role kinds");
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
const instanceRuleIds = new Set();
|
|
588
|
+
instances.forEach((instance, index) => {
|
|
589
|
+
const base = `$.instances[${index}]`;
|
|
590
|
+
validateToken(instance.version, `${base}.version`, "Instance version", issues);
|
|
591
|
+
validateToken(instance.formulaId, `${base}.formulaId`, "Formula reference", issues);
|
|
592
|
+
const formula = formulaMap.get(instance.formulaId);
|
|
593
|
+
if (!formula) {
|
|
594
|
+
pushIssue(issues, "unknown-reference", `${base}.formulaId`, `Unknown formula ${instance.formulaId}`);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (!isPlainRecord(instance.bindings)) {
|
|
598
|
+
pushIssue(issues, "invalid-type", `${base}.bindings`, "Instance bindings must be an object");
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const usedRoles = formulaRoleUsage.get(formula.id) ?? [];
|
|
602
|
+
const bindingSemantics = new Map();
|
|
603
|
+
for (const roleName of usedRoles) {
|
|
604
|
+
if (!Object.prototype.hasOwnProperty.call(instance.bindings, roleName)) {
|
|
605
|
+
pushIssue(issues, "missing-required", propertyPath(`${base}.bindings`, roleName), `Missing binding for role ${roleName}`);
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const expression = instance.bindings[roleName];
|
|
609
|
+
const semantics = expressionSemantics(expression, propertyPath(`${base}.bindings`, roleName), measureMap, issues);
|
|
610
|
+
bindingSemantics.set(roleName, semantics);
|
|
611
|
+
definitionExpressionNodes += walkDiagnosticExpression(expression, "measure", propertyPath(`${base}.bindings`, roleName), []).nodeCount;
|
|
612
|
+
const role = formula.roles[roleName];
|
|
613
|
+
const boundMeasures = semantics.measureIds.map((id) => measureMap.get(id)).filter((item) => item !== undefined);
|
|
614
|
+
if (boundMeasures.some((measure) => measure.kind !== role.kind)) {
|
|
615
|
+
pushIssue(issues, "incompatible-semantics", propertyPath(`${base}.bindings`, roleName), `Binding kind does not match role ${roleName}`);
|
|
616
|
+
}
|
|
617
|
+
if (role.developmentSemantics !== undefined) {
|
|
618
|
+
const leaves = transitiveMeasureIds(semantics.measureIds, derivationsByOutput)
|
|
619
|
+
.map((id) => measureMap.get(id))
|
|
620
|
+
.filter((measure) => measure !== undefined);
|
|
621
|
+
if (leaves.some((measure) => measure.developmentSemantics !== role.developmentSemantics)) {
|
|
622
|
+
pushIssue(issues, "incompatible-semantics", propertyPath(`${base}.bindings`, roleName), `Transitive binding development semantics do not match role ${roleName}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
for (const [side, roleExpression] of [["numerator", formula.numerator], ["denominator", formula.denominator]]) {
|
|
627
|
+
const roleIds = walkDiagnosticExpression(roleExpression, "role", `${base}.${side}`, []).dependencies;
|
|
628
|
+
const signatures = new Set(roleIds.map((roleId) => bindingSemantics.get(roleId)?.signature).filter((signature) => signature !== null && signature !== undefined));
|
|
629
|
+
if (signatures.size > 1) {
|
|
630
|
+
pushIssue(issues, "incompatible-semantics", `${base}.bindings`, `Bound ${side} arithmetic combines incompatible semantics`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
for (const binding of Object.keys(instance.bindings)) {
|
|
634
|
+
if (!usedRoles.includes(binding)) {
|
|
635
|
+
pushIssue(issues, "unknown-reference", propertyPath(`${base}.bindings`, binding), `Unexpected binding for role ${binding}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const compatibility = new Map();
|
|
639
|
+
for (const roleName of usedRoles) {
|
|
640
|
+
const role = formula.roles[roleName];
|
|
641
|
+
const binding = instance.bindings[roleName];
|
|
642
|
+
if (!role.compatibilityGroup || !binding)
|
|
643
|
+
continue;
|
|
644
|
+
const semantics = expressionSemantics(binding, propertyPath(`${base}.bindings`, roleName), measureMap, []);
|
|
645
|
+
if (semantics.signature === null)
|
|
646
|
+
continue;
|
|
647
|
+
const previous = compatibility.get(role.compatibilityGroup);
|
|
648
|
+
if (previous !== undefined && previous !== semantics.signature) {
|
|
649
|
+
pushIssue(issues, "incompatible-semantics", propertyPath(`${base}.bindings`, roleName), `Compatibility group ${role.compatibilityGroup} binds different semantics`);
|
|
650
|
+
}
|
|
651
|
+
else
|
|
652
|
+
compatibility.set(role.compatibilityGroup, semantics.signature);
|
|
653
|
+
}
|
|
654
|
+
for (const [key, text] of [
|
|
655
|
+
["displayName", instance.presentation?.displayName],
|
|
656
|
+
["description", instance.presentation?.description],
|
|
657
|
+
["displayUnit", instance.presentation?.displayUnit],
|
|
658
|
+
["numeratorLabel", instance.presentation?.numeratorLabel],
|
|
659
|
+
["denominatorLabel", instance.presentation?.denominatorLabel],
|
|
660
|
+
])
|
|
661
|
+
validateToken(text, `${base}.presentation.${key}`, `Presentation ${key}`, issues);
|
|
662
|
+
if (!validateFinite(instance.presentation?.scale, `${base}.presentation.scale`, "Presentation scale", issues) || instance.presentation.scale <= 0) {
|
|
663
|
+
pushIssue(issues, "invalid-number", `${base}.presentation.scale`, "Presentation scale must be positive");
|
|
664
|
+
}
|
|
665
|
+
requireArray(instance.rules, `${base}.rules`, issues).forEach((rawRule, ruleIndex) => {
|
|
666
|
+
const rule = rawRule;
|
|
667
|
+
const ruleBase = `${base}.rules[${ruleIndex}]`;
|
|
668
|
+
if (validateToken(rule.id, `${ruleBase}.id`, "Metric rule ID", issues)) {
|
|
669
|
+
if (rule.id.startsWith("diagnostic/structural/"))
|
|
670
|
+
pushIssue(issues, "incompatible-semantics", `${ruleBase}.id`, "Metric rule ID uses the reserved structural prefix");
|
|
671
|
+
if (instanceRuleIds.has(rule.id) || reviewRuleMap.has(rule.id))
|
|
672
|
+
pushIssue(issues, "duplicate-id", `${ruleBase}.id`, `Duplicate rule ID ${rule.id}`);
|
|
673
|
+
instanceRuleIds.add(rule.id);
|
|
674
|
+
}
|
|
675
|
+
validateToken(rule.code, `${ruleBase}.code`, "Metric rule code", issues);
|
|
676
|
+
validateToken(rule.message, `${ruleBase}.message`, "Metric rule message", issues);
|
|
677
|
+
validateTolerance(rule.when?.tolerance, `${ruleBase}.when.tolerance`, issues);
|
|
678
|
+
for (const [side, operand] of [["left", rule.when?.left], ["right", rule.when?.right]]) {
|
|
679
|
+
const operandPath = `${ruleBase}.when.${side}`;
|
|
680
|
+
if (operand?.source === "measure") {
|
|
681
|
+
const semantic = expressionSemantics(operand.expression, `${operandPath}.expression`, measureMap, issues);
|
|
682
|
+
definitionExpressionNodes += 1 + walkDiagnosticExpression(operand.expression, "measure", `${operandPath}.expression`, []).nodeCount;
|
|
683
|
+
void semantic;
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
definitionExpressionNodes += 1;
|
|
687
|
+
if (operand?.source === "constant")
|
|
688
|
+
validateFinite(operand.value, `${operandPath}.value`, "Rule constant", issues);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
const derivedOutputs = new Map();
|
|
694
|
+
derivations.forEach((derivation, index) => {
|
|
695
|
+
const base = `$.derivedMeasures[${index}]`;
|
|
696
|
+
validateToken(derivation.outputMeasureId, `${base}.outputMeasureId`, "Derived output measure", issues);
|
|
697
|
+
const output = measureMap.get(derivation.outputMeasureId);
|
|
698
|
+
if (!output)
|
|
699
|
+
pushIssue(issues, "unknown-reference", `${base}.outputMeasureId`, `Unknown derived output ${derivation.outputMeasureId}`);
|
|
700
|
+
else if (output.source !== "derived")
|
|
701
|
+
pushIssue(issues, "incompatible-semantics", `${base}.outputMeasureId`, "Derivation output must declare source derived");
|
|
702
|
+
if (derivedOutputs.has(derivation.outputMeasureId))
|
|
703
|
+
pushIssue(issues, "duplicate-id", `${base}.outputMeasureId`, `Duplicate derived output ${derivation.outputMeasureId}`);
|
|
704
|
+
derivedOutputs.set(derivation.outputMeasureId, derivation);
|
|
705
|
+
const walked = walkDiagnosticExpression(derivation.expression, "claim", `${base}.expression`, issues);
|
|
706
|
+
definitionExpressionNodes += walked.nodeCount;
|
|
707
|
+
for (const id of walked.dependencies)
|
|
708
|
+
if (!measureMap.has(id))
|
|
709
|
+
pushIssue(issues, "unknown-reference", `${base}.expression`, `Unknown derivation measure ${id}`);
|
|
710
|
+
const transitiveIds = transitiveMeasureIds(walked.dependencies, derivationsByOutput);
|
|
711
|
+
const transitiveInputs = transitiveIds.map((id) => measureMap.get(id)).filter((item) => item !== undefined);
|
|
712
|
+
if (output && transitiveInputs.some((input) => input.developmentSemantics !== output.developmentSemantics)) {
|
|
713
|
+
pushIssue(issues, "incompatible-semantics", `${base}.expression`, "Derived output and inputs must share development semantics");
|
|
714
|
+
}
|
|
715
|
+
if (definition.lossRowGrain !== "claim") {
|
|
716
|
+
pushIssue(issues, "incompatible-semantics", base, "Derived measures require claim-grain input");
|
|
717
|
+
}
|
|
718
|
+
const expressionStack = [derivation.expression];
|
|
719
|
+
while (expressionStack.length > 0) {
|
|
720
|
+
const current = expressionStack.pop();
|
|
721
|
+
if (!isPlainRecord(current))
|
|
722
|
+
continue;
|
|
723
|
+
if (current.op === "claim-layer") {
|
|
724
|
+
const attachment = current.attachment;
|
|
725
|
+
const limit = current.limit;
|
|
726
|
+
const attachmentOk = validateFinite(attachment, `${base}.expression.attachment`, "Claim-layer attachment", issues);
|
|
727
|
+
if (attachmentOk && attachment < 0)
|
|
728
|
+
pushIssue(issues, "invalid-number", `${base}.expression.attachment`, "Claim-layer attachment must be nonnegative");
|
|
729
|
+
if (limit !== null) {
|
|
730
|
+
const limitOk = validateFinite(limit, `${base}.expression.limit`, "Claim-layer width", issues);
|
|
731
|
+
if (limitOk && limit <= 0)
|
|
732
|
+
pushIssue(issues, "invalid-number", `${base}.expression.limit`, "Claim-layer width must be positive");
|
|
733
|
+
if (attachmentOk && limitOk && !Number.isFinite(attachment + limit)) {
|
|
734
|
+
pushIssue(issues, "invalid-number", `${base}.expression.limit`, "Attachment plus claim-layer width must be finite");
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
else if (current.op === "add" && Array.isArray(current.terms))
|
|
739
|
+
expressionStack.push(...current.terms);
|
|
740
|
+
else if (current.op === "subtract")
|
|
741
|
+
expressionStack.push(current.left, current.right);
|
|
742
|
+
}
|
|
743
|
+
const projection = projectClaimExpression(derivation.expression, `${base}.expression`, measureMap, amountBasisMap, issues);
|
|
744
|
+
if (output && projection) {
|
|
745
|
+
const expected = output.kind === "amount" && output.basisId
|
|
746
|
+
? amountBasisMap.get(output.basisId)
|
|
747
|
+
: undefined;
|
|
748
|
+
const matches = output.kind === projection.kind && output.unit === projection.unit &&
|
|
749
|
+
(output.kind === "amount"
|
|
750
|
+
? expected !== undefined && projectedBasisKey(projectedAmountBasis(expected)) === projectedBasisKey(projection.amountBasis)
|
|
751
|
+
: semanticReference(output) === projection.reference);
|
|
752
|
+
if (!matches)
|
|
753
|
+
pushIssue(issues, "incompatible-semantics", `${base}.expression`, "Derived expression does not project the output measure semantics");
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
for (const [measureId, measure] of measureMap) {
|
|
757
|
+
if (measure.source === "derived" && !derivedOutputs.has(measureId)) {
|
|
758
|
+
pushIssue(issues, "missing-required", "$.derivedMeasures", `Derived measure ${measureId} requires exactly one derivation`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
const visiting = new Set();
|
|
762
|
+
const visited = new Set();
|
|
763
|
+
const visit = (id) => {
|
|
764
|
+
if (visited.has(id))
|
|
765
|
+
return;
|
|
766
|
+
if (visiting.has(id)) {
|
|
767
|
+
pushIssue(issues, "cycle", "$.derivedMeasures", `Derived measure graph contains a cycle at ${id}`);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
visiting.add(id);
|
|
771
|
+
const derivation = derivedOutputs.get(id);
|
|
772
|
+
if (derivation) {
|
|
773
|
+
const deps = walkDiagnosticExpression(derivation.expression, "claim", "$.derivedMeasures", []).dependencies;
|
|
774
|
+
for (const dependency of deps)
|
|
775
|
+
if (derivedOutputs.has(dependency))
|
|
776
|
+
visit(dependency);
|
|
777
|
+
}
|
|
778
|
+
visiting.delete(id);
|
|
779
|
+
visited.add(id);
|
|
780
|
+
};
|
|
781
|
+
for (const id of derivedOutputs.keys())
|
|
782
|
+
visit(id);
|
|
783
|
+
reviewRules.forEach((rule, index) => {
|
|
784
|
+
const base = `$.reviewRules[${index}]`;
|
|
785
|
+
validateToken(rule.code, `${base}.code`, "Review rule code", issues);
|
|
786
|
+
validateToken(rule.description, `${base}.description`, "Review rule description", issues);
|
|
787
|
+
if (rule.id.startsWith("diagnostic/structural/"))
|
|
788
|
+
pushIssue(issues, "incompatible-semantics", `${base}.id`, "Review rule ID uses the reserved structural prefix");
|
|
789
|
+
validateTolerance(rule.tolerance, `${base}.tolerance`, issues);
|
|
790
|
+
const roots = [];
|
|
791
|
+
if (rule.kind === "compare") {
|
|
792
|
+
if (rule.when.left.op !== "constant")
|
|
793
|
+
roots.push(rule.when.left);
|
|
794
|
+
else
|
|
795
|
+
definitionExpressionNodes++;
|
|
796
|
+
if (rule.when.right.op !== "constant")
|
|
797
|
+
roots.push(rule.when.right);
|
|
798
|
+
else
|
|
799
|
+
definitionExpressionNodes++;
|
|
800
|
+
}
|
|
801
|
+
else if (rule.kind === "reconcile") {
|
|
802
|
+
roots.push(rule.actual);
|
|
803
|
+
if (rule.expected.op !== "constant")
|
|
804
|
+
roots.push(rule.expected);
|
|
805
|
+
else
|
|
806
|
+
definitionExpressionNodes++;
|
|
807
|
+
}
|
|
808
|
+
else if (rule.kind === "monotonic")
|
|
809
|
+
roots.push(rule.expression);
|
|
810
|
+
else if (rule.kind === "layer-order")
|
|
811
|
+
roots.push(rule.narrower, rule.broader);
|
|
812
|
+
else if (rule.kind === "control-total") {
|
|
813
|
+
roots.push(rule.expression);
|
|
814
|
+
validateFinite(rule.expected, `${base}.expected`, "Control total", issues);
|
|
815
|
+
}
|
|
816
|
+
else
|
|
817
|
+
pushIssue(issues, "invalid-type", `${base}.kind`, "Unknown review-rule kind");
|
|
818
|
+
roots.forEach((root, rootIndex) => {
|
|
819
|
+
expressionSemantics(root, `${base}.expression[${rootIndex}]`, measureMap, issues);
|
|
820
|
+
definitionExpressionNodes += walkDiagnosticExpression(root, "measure", `${base}.expression[${rootIndex}]`, []).nodeCount;
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
if (definitionExpressionNodes > MAX_DIAGNOSTIC_DEFINITION_EXPRESSION_NODES) {
|
|
824
|
+
pushIssue(issues, "expression-limit", "$", `Definition expression node count exceeds ${MAX_DIAGNOSTIC_DEFINITION_EXPRESSION_NODES}`);
|
|
825
|
+
}
|
|
826
|
+
validatePeriodAxis(definition.periodAxis, issues);
|
|
827
|
+
if (issues.length > 0)
|
|
828
|
+
throw new DiagnosticValidationError(issues);
|
|
829
|
+
return definition;
|
|
830
|
+
}
|
|
831
|
+
function optionalFromNullable(value) {
|
|
832
|
+
return value === null || value === undefined ? undefined : value;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* The normalized wire projection materializes optional semantic fields as
|
|
836
|
+
* null. Convert only those documented defaults back to the authored view so
|
|
837
|
+
* both accepted inputs travel through the exact same semantic compiler.
|
|
838
|
+
*/
|
|
839
|
+
function authoredView(value) {
|
|
840
|
+
const definition = value;
|
|
841
|
+
const reviewRules = definition.reviewRules.map((rule) => {
|
|
842
|
+
const tolerance = { ...rule.tolerance };
|
|
843
|
+
if (rule.kind !== "control-total")
|
|
844
|
+
return { ...rule, tolerance };
|
|
845
|
+
const filter = rule.filter === null
|
|
846
|
+
? undefined
|
|
847
|
+
: {
|
|
848
|
+
sourceGroups: optionalFromNullable(rule.filter.sourceGroups),
|
|
849
|
+
origins: optionalFromNullable(rule.filter.origins),
|
|
850
|
+
originFrom: optionalFromNullable(rule.filter.originFrom),
|
|
851
|
+
originThrough: optionalFromNullable(rule.filter.originThrough),
|
|
852
|
+
valuations: optionalFromNullable(rule.filter.valuations),
|
|
853
|
+
valuationFrom: optionalFromNullable(rule.filter.valuationFrom),
|
|
854
|
+
valuationThrough: optionalFromNullable(rule.filter.valuationThrough),
|
|
855
|
+
minDevelopmentAge: optionalFromNullable(rule.filter.minDevelopmentAge),
|
|
856
|
+
maxDevelopmentAge: optionalFromNullable(rule.filter.maxDevelopmentAge),
|
|
857
|
+
};
|
|
858
|
+
return { ...rule, tolerance, ...(filter === undefined ? {} : { filter }) };
|
|
859
|
+
});
|
|
860
|
+
return {
|
|
861
|
+
diagnosticDefinitionVersion: definition.diagnosticDefinitionVersion,
|
|
862
|
+
id: definition.id,
|
|
863
|
+
version: definition.version,
|
|
864
|
+
lossRowGrain: definition.lossRowGrain,
|
|
865
|
+
measures: definition.measures.map((measure) => ({
|
|
866
|
+
id: measure.id,
|
|
867
|
+
displayName: measure.displayName,
|
|
868
|
+
description: measure.description,
|
|
869
|
+
source: measure.source,
|
|
870
|
+
kind: measure.kind,
|
|
871
|
+
unit: measure.unit,
|
|
872
|
+
developmentSemantics: measure.developmentSemantics,
|
|
873
|
+
aggregation: measure.aggregation,
|
|
874
|
+
missing: measure.missing,
|
|
875
|
+
...(measure.basisId === null || measure.basisId === undefined ? {} : { basisId: measure.basisId }),
|
|
876
|
+
...(measure.countPopulationId === null || measure.countPopulationId === undefined
|
|
877
|
+
? {}
|
|
878
|
+
: { countPopulationId: measure.countPopulationId }),
|
|
879
|
+
...(measure.exposureBasisId === null || measure.exposureBasisId === undefined
|
|
880
|
+
? {}
|
|
881
|
+
: { exposureBasisId: measure.exposureBasisId }),
|
|
882
|
+
...(measure.exposureTiming === null || measure.exposureTiming === undefined
|
|
883
|
+
? {}
|
|
884
|
+
: { exposureTiming: measure.exposureTiming }),
|
|
885
|
+
})),
|
|
886
|
+
countPopulations: definition.countPopulations.map((population) => ({ ...population })),
|
|
887
|
+
exposureBases: definition.exposureBases.map((basis) => ({
|
|
888
|
+
id: basis.id,
|
|
889
|
+
displayName: basis.displayName,
|
|
890
|
+
basis: basis.basis,
|
|
891
|
+
unit: basis.unit,
|
|
892
|
+
description: basis.description,
|
|
893
|
+
...(basis.sourceDescription === null || basis.sourceDescription === undefined
|
|
894
|
+
? {}
|
|
895
|
+
: { sourceDescription: basis.sourceDescription }),
|
|
896
|
+
...(basis.attributes === undefined ? {} : { attributes: basis.attributes }),
|
|
897
|
+
})),
|
|
898
|
+
amountBases: definition.amountBases.map((basis) => ({
|
|
899
|
+
id: basis.id,
|
|
900
|
+
displayName: basis.displayName,
|
|
901
|
+
currency: basis.currency,
|
|
902
|
+
perspective: basis.perspective,
|
|
903
|
+
components: basis.components.map((component) => ({
|
|
904
|
+
id: component.id,
|
|
905
|
+
treatment: component.treatment,
|
|
906
|
+
limitation: component.limitation.kind === "unknown"
|
|
907
|
+
? {
|
|
908
|
+
kind: "unknown",
|
|
909
|
+
...(component.limitation.description === null || component.limitation.description === undefined
|
|
910
|
+
? {}
|
|
911
|
+
: { description: component.limitation.description }),
|
|
912
|
+
}
|
|
913
|
+
: component.limitation,
|
|
914
|
+
})),
|
|
915
|
+
...(basis.sourceDescription === null || basis.sourceDescription === undefined
|
|
916
|
+
? {}
|
|
917
|
+
: { sourceDescription: basis.sourceDescription }),
|
|
918
|
+
...(basis.attributes === undefined ? {} : { attributes: basis.attributes }),
|
|
919
|
+
})),
|
|
920
|
+
derivedMeasures: definition.derivedMeasures.map((derivation) => ({ ...derivation })),
|
|
921
|
+
formulas: definition.formulas.map((formula) => ({
|
|
922
|
+
id: formula.id,
|
|
923
|
+
version: formula.version,
|
|
924
|
+
roles: Object.fromEntries(Object.entries(formula.roles).map(([name, role]) => [name, {
|
|
925
|
+
kind: role.kind,
|
|
926
|
+
...(role.compatibilityGroup === null || role.compatibilityGroup === undefined
|
|
927
|
+
? {}
|
|
928
|
+
: { compatibilityGroup: role.compatibilityGroup }),
|
|
929
|
+
...(role.developmentSemantics === null || role.developmentSemantics === undefined
|
|
930
|
+
? {}
|
|
931
|
+
: { developmentSemantics: role.developmentSemantics }),
|
|
932
|
+
}])),
|
|
933
|
+
numerator: formula.numerator,
|
|
934
|
+
denominator: formula.denominator,
|
|
935
|
+
denominatorPolicy: formula.denominatorPolicy,
|
|
936
|
+
})),
|
|
937
|
+
instances: definition.instances.map((instance) => ({
|
|
938
|
+
id: instance.id,
|
|
939
|
+
version: instance.version,
|
|
940
|
+
formulaId: instance.formulaId,
|
|
941
|
+
bindings: instance.bindings,
|
|
942
|
+
presentation: instance.presentation,
|
|
943
|
+
rules: instance.rules.map((rule) => ({
|
|
944
|
+
...rule,
|
|
945
|
+
when: { ...rule.when, tolerance: { ...rule.when.tolerance } },
|
|
946
|
+
})),
|
|
947
|
+
})),
|
|
948
|
+
reviewRules,
|
|
949
|
+
periodAxis: definition.periodAxis,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
/** Compiles, validates, normalizes, fingerprints, and freezes a diagnostic definition. */
|
|
953
|
+
export function compileDiagnosticDefinition(value) {
|
|
954
|
+
const boundaryIssues = [];
|
|
955
|
+
validateJsonValue(value, boundaryIssues);
|
|
956
|
+
if (boundaryIssues.length > 0)
|
|
957
|
+
throw new DiagnosticValidationError(boundaryIssues);
|
|
958
|
+
let authored;
|
|
959
|
+
try {
|
|
960
|
+
authored = authoredView(value);
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
throw new DiagnosticValidationError([{
|
|
964
|
+
domain: "definition",
|
|
965
|
+
code: "invalid-type",
|
|
966
|
+
path: "$",
|
|
967
|
+
message: "Diagnostic definition does not have the required object and array structure",
|
|
968
|
+
}]);
|
|
969
|
+
}
|
|
970
|
+
let definition;
|
|
971
|
+
try {
|
|
972
|
+
definition = validateDefinition(authored);
|
|
973
|
+
}
|
|
974
|
+
catch (error) {
|
|
975
|
+
if (error instanceof DiagnosticValidationError)
|
|
976
|
+
throw error;
|
|
977
|
+
throw new DiagnosticValidationError([{
|
|
978
|
+
domain: "definition",
|
|
979
|
+
code: "invalid-type",
|
|
980
|
+
path: "$",
|
|
981
|
+
message: "Diagnostic definition contains a malformed nested value",
|
|
982
|
+
}]);
|
|
983
|
+
}
|
|
984
|
+
const normalized = normalizeDiagnosticDefinition(definition);
|
|
985
|
+
const identities = buildDiagnosticIdentities(normalized);
|
|
986
|
+
const compiledBase = {
|
|
987
|
+
definition: normalized,
|
|
988
|
+
formulaFingerprints: identities.formulaFingerprints,
|
|
989
|
+
calculationFingerprints: identities.calculationFingerprints,
|
|
990
|
+
definitionIntegrity: identities.definitionIntegrity,
|
|
991
|
+
};
|
|
992
|
+
Object.defineProperty(compiledBase, compiledDiagnosticDefinitionBrand, {
|
|
993
|
+
value: true,
|
|
994
|
+
enumerable: false,
|
|
995
|
+
configurable: false,
|
|
996
|
+
writable: false,
|
|
997
|
+
});
|
|
998
|
+
const compiled = compiledBase;
|
|
999
|
+
Object.freeze(compiled);
|
|
1000
|
+
authenticCompiledDefinitions.add(compiled);
|
|
1001
|
+
compiledInternals.set(compiled, {
|
|
1002
|
+
formulasById: new Map(normalized.formulas.map((formula) => [formula.id, formula])),
|
|
1003
|
+
measuresById: new Map(definition.measures.map((measure) => [measure.id, measure])),
|
|
1004
|
+
calculationScopesByInstanceId: identities.calculationScopesByInstanceId,
|
|
1005
|
+
calculationDependenciesByInstanceId: identities.calculationDependenciesByInstanceId,
|
|
1006
|
+
evaluationDependenciesByInstanceId: identities.evaluationDependenciesByInstanceId,
|
|
1007
|
+
derivationsByOutputMeasureId: new Map(normalized.derivedMeasures.map((derivation) => [derivation.outputMeasureId, derivation])),
|
|
1008
|
+
});
|
|
1009
|
+
return compiled;
|
|
1010
|
+
}
|
|
1011
|
+
export function assertCompiledDiagnosticDefinition(value) {
|
|
1012
|
+
if ((typeof value !== "object" && typeof value !== "function") || value === null || !authenticCompiledDefinitions.has(value)) {
|
|
1013
|
+
throw new DiagnosticValidationError([{
|
|
1014
|
+
domain: "definition",
|
|
1015
|
+
code: "invalid-input-relationship",
|
|
1016
|
+
path: "$",
|
|
1017
|
+
message: "Value is not an authentic compiled diagnostic definition",
|
|
1018
|
+
}]);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
/** @internal Shared only by core diagnostic runtime modules; not re-exported. */
|
|
1022
|
+
export function getCompiledDiagnosticDefinitionInternals(value) {
|
|
1023
|
+
assertCompiledDiagnosticDefinition(value);
|
|
1024
|
+
return compiledInternals.get(value);
|
|
1025
|
+
}
|
|
1026
|
+
//# sourceMappingURL=diagnosticDefinitions.js.map
|