@actuarial-ts/core 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/dist/canonical.d.ts +11 -0
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +136 -26
- package/dist/canonical.js.map +1 -1
- package/dist/capping.d.ts.map +1 -1
- package/dist/capping.js +39 -6
- package/dist/capping.js.map +1 -1
- package/dist/diagnosticAggregation.d.ts +3 -0
- package/dist/diagnosticAggregation.d.ts.map +1 -1
- package/dist/diagnosticAggregation.js +30 -7
- package/dist/diagnosticAggregation.js.map +1 -1
- package/dist/diagnosticDefinitions.d.ts +3 -0
- package/dist/diagnosticDefinitions.d.ts.map +1 -1
- package/dist/diagnosticDefinitions.js +844 -182
- package/dist/diagnosticDefinitions.js.map +1 -1
- package/dist/diagnosticDerivations.d.ts +23 -0
- package/dist/diagnosticDerivations.d.ts.map +1 -1
- package/dist/diagnosticDerivations.js +187 -43
- package/dist/diagnosticDerivations.js.map +1 -1
- package/dist/diagnosticEvidenceIntern.d.ts +16 -0
- package/dist/diagnosticEvidenceIntern.d.ts.map +1 -0
- package/dist/diagnosticEvidenceIntern.js +139 -0
- package/dist/diagnosticEvidenceIntern.js.map +1 -0
- package/dist/diagnosticExposure.d.ts +2 -2
- package/dist/diagnosticExposure.d.ts.map +1 -1
- package/dist/diagnosticExposure.js +319 -21
- package/dist/diagnosticExposure.js.map +1 -1
- package/dist/diagnosticExpressions.d.ts +1 -1
- package/dist/diagnosticExpressions.d.ts.map +1 -1
- package/dist/diagnosticExpressions.js +35 -15
- package/dist/diagnosticExpressions.js.map +1 -1
- package/dist/diagnosticFormulas.d.ts +3 -0
- package/dist/diagnosticFormulas.d.ts.map +1 -1
- package/dist/diagnosticFormulas.js +160 -26
- package/dist/diagnosticFormulas.js.map +1 -1
- package/dist/diagnosticIdentity.d.ts +15 -1
- package/dist/diagnosticIdentity.d.ts.map +1 -1
- package/dist/diagnosticIdentity.js +321 -51
- package/dist/diagnosticIdentity.js.map +1 -1
- package/dist/diagnosticIdentityStream.d.ts +56 -0
- package/dist/diagnosticIdentityStream.d.ts.map +1 -0
- package/dist/diagnosticIdentityStream.js +472 -0
- package/dist/diagnosticIdentityStream.js.map +1 -0
- package/dist/diagnosticOrdering.d.ts +8 -0
- package/dist/diagnosticOrdering.d.ts.map +1 -0
- package/dist/diagnosticOrdering.js +73 -0
- package/dist/diagnosticOrdering.js.map +1 -0
- package/dist/diagnosticPeriodAxis.d.ts +9 -0
- package/dist/diagnosticPeriodAxis.d.ts.map +1 -0
- package/dist/diagnosticPeriodAxis.js +69 -0
- package/dist/diagnosticPeriodAxis.js.map +1 -0
- package/dist/diagnosticPeriods.d.ts +2 -3
- package/dist/diagnosticPeriods.d.ts.map +1 -1
- package/dist/diagnosticPeriods.js +20 -68
- package/dist/diagnosticPeriods.js.map +1 -1
- package/dist/diagnosticPreparation.d.ts +75 -14
- package/dist/diagnosticPreparation.d.ts.map +1 -1
- package/dist/diagnosticPreparation.js +1606 -112
- package/dist/diagnosticPreparation.js.map +1 -1
- package/dist/diagnosticReview.d.ts +40 -15
- package/dist/diagnosticReview.d.ts.map +1 -1
- package/dist/diagnosticReview.js +600 -42
- package/dist/diagnosticReview.js.map +1 -1
- package/dist/diagnosticReviewSources.d.ts +18 -0
- package/dist/diagnosticReviewSources.d.ts.map +1 -0
- package/dist/diagnosticReviewSources.js +76 -0
- package/dist/diagnosticReviewSources.js.map +1 -0
- package/dist/diagnosticReviewStore.d.ts +88 -0
- package/dist/diagnosticReviewStore.d.ts.map +1 -0
- package/dist/diagnosticReviewStore.js +455 -0
- package/dist/diagnosticReviewStore.js.map +1 -0
- package/dist/diagnosticRunner.d.ts +27 -2
- package/dist/diagnosticRunner.d.ts.map +1 -1
- package/dist/diagnosticRunner.js +764 -70
- package/dist/diagnosticRunner.js.map +1 -1
- package/dist/diagnosticRuntime.d.ts +26 -0
- package/dist/diagnosticRuntime.d.ts.map +1 -0
- package/dist/diagnosticRuntime.js +243 -0
- package/dist/diagnosticRuntime.js.map +1 -0
- package/dist/diagnosticSourceOrdering.d.ts +6 -0
- package/dist/diagnosticSourceOrdering.d.ts.map +1 -0
- package/dist/diagnosticSourceOrdering.js +38 -0
- package/dist/diagnosticSourceOrdering.js.map +1 -0
- package/dist/fnvAccumulator.d.ts +6 -0
- package/dist/fnvAccumulator.d.ts.map +1 -0
- package/dist/fnvAccumulator.js +21 -0
- package/dist/fnvAccumulator.js.map +1 -0
- package/dist/index.d.ts +15 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/mack.d.ts +4 -0
- package/dist/mack.d.ts.map +1 -1
- package/dist/mack.js +32 -4
- package/dist/mack.js.map +1 -1
- package/dist/triangle.d.ts.map +1 -1
- package/dist/triangle.js +59 -9
- package/dist/triangle.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +22 -8
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
- package/src/canonical.ts +144 -26
- package/src/capping.ts +109 -18
- package/src/diagnosticAggregation.ts +69 -20
- package/src/diagnosticDefinitions.ts +2675 -426
- package/src/diagnosticDerivations.ts +281 -49
- package/src/diagnosticEvidenceIntern.ts +151 -0
- package/src/diagnosticExposure.ts +550 -50
- package/src/diagnosticExpressions.ts +99 -17
- package/src/diagnosticFormulas.ts +239 -39
- package/src/diagnosticIdentity.ts +700 -191
- package/src/diagnosticIdentityStream.ts +553 -0
- package/src/diagnosticOrdering.ts +106 -0
- package/src/diagnosticPeriodAxis.ts +92 -0
- package/src/diagnosticPeriods.ts +67 -82
- package/src/diagnosticPreparation.ts +2558 -132
- package/src/diagnosticReview.ts +1072 -58
- package/src/diagnosticReviewSources.ts +85 -0
- package/src/diagnosticReviewStore.ts +692 -0
- package/src/diagnosticRunner.ts +1273 -72
- package/src/diagnosticRuntime.ts +264 -0
- package/src/diagnosticSourceOrdering.ts +50 -0
- package/src/fnvAccumulator.ts +20 -0
- package/src/index.ts +69 -4
- package/src/mack.ts +72 -9
- package/src/triangle.ts +103 -13
- package/src/types.ts +55 -20
- package/src/version.ts +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canonicalJson, fnv1a64 } from "./canonical.js";
|
|
2
|
+
import { createDiagnosticEvidenceInterner } from "./diagnosticEvidenceIntern.js";
|
|
2
3
|
import type {
|
|
3
4
|
AmountBasisDefinition,
|
|
4
5
|
AmountLimitation,
|
|
@@ -24,12 +25,252 @@ import type {
|
|
|
24
25
|
DiagnosticReviewRule,
|
|
25
26
|
DiagnosticRuleOperand,
|
|
26
27
|
DiagnosticSourceLocation,
|
|
28
|
+
DiagnosticsFilter,
|
|
27
29
|
} from "./diagnosticDefinitions.js";
|
|
28
30
|
import type {
|
|
29
31
|
DiagnosticClaimExpression,
|
|
30
32
|
DiagnosticMeasureExpression,
|
|
31
33
|
DiagnosticRoleExpression,
|
|
32
34
|
} from "./diagnosticExpressions.js";
|
|
35
|
+
import { normalizeDiagnosticPeriodWithAxis } from "./diagnosticPeriodAxis.js";
|
|
36
|
+
import {
|
|
37
|
+
diagnosticJsonPreflight,
|
|
38
|
+
isDiagnosticPlainRecord,
|
|
39
|
+
isDiagnosticToken,
|
|
40
|
+
normalizeDiagnosticNumber,
|
|
41
|
+
} from "./diagnosticRuntime.js";
|
|
42
|
+
import {
|
|
43
|
+
DiagnosticValidationError,
|
|
44
|
+
type DiagnosticValidationIssue,
|
|
45
|
+
} from "./types.js";
|
|
46
|
+
|
|
47
|
+
export type DiagnosticIdentityProjection<T> = T extends DiagnosticSourceLocation
|
|
48
|
+
? NormalizedDiagnosticSourceLocationIdentity
|
|
49
|
+
: T extends readonly (infer U)[]
|
|
50
|
+
? readonly DiagnosticIdentityProjection<U>[]
|
|
51
|
+
: T extends object
|
|
52
|
+
? { readonly [K in keyof T]: DiagnosticIdentityProjection<T[K]> }
|
|
53
|
+
: T;
|
|
54
|
+
|
|
55
|
+
export interface NormalizedDiagnosticsFilterIdentity
|
|
56
|
+
extends NormalizedDiagnosticReviewFilterIdentity {
|
|
57
|
+
readonly outputGroups: readonly string[] | null;
|
|
58
|
+
readonly instanceIds: readonly string[] | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Validates selector shapes without interpreting periods against a definition. */
|
|
62
|
+
function validateFilterIdentityInput(filter: DiagnosticsFilter): void {
|
|
63
|
+
const issues: DiagnosticValidationIssue[] = [
|
|
64
|
+
...diagnosticJsonPreflight(filter, "configuration"),
|
|
65
|
+
];
|
|
66
|
+
if (issues.length) throw new DiagnosticValidationError(issues);
|
|
67
|
+
if (!isDiagnosticPlainRecord(filter))
|
|
68
|
+
throw new DiagnosticValidationError([
|
|
69
|
+
{
|
|
70
|
+
domain: "configuration",
|
|
71
|
+
code: "invalid-type",
|
|
72
|
+
path: "$",
|
|
73
|
+
message: "Filter must be a plain object or null",
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
const lists = [
|
|
78
|
+
"sourceGroups", "outputGroups", "origins", "valuations", "instanceIds",
|
|
79
|
+
];
|
|
80
|
+
const labels = [
|
|
81
|
+
"originFrom", "originThrough", "valuationFrom", "valuationThrough",
|
|
82
|
+
];
|
|
83
|
+
const bounds = ["minDevelopmentAge", "maxDevelopmentAge"];
|
|
84
|
+
const checkToken = (value: unknown, path: string) => {
|
|
85
|
+
if (!isDiagnosticToken(value))
|
|
86
|
+
issues.push({
|
|
87
|
+
domain: "configuration",
|
|
88
|
+
code: "invalid-string",
|
|
89
|
+
path,
|
|
90
|
+
message: "Filter selectors must use nonempty token strings",
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
for (const key of Object.keys(filter).sort()) {
|
|
94
|
+
const value: unknown = filter[key];
|
|
95
|
+
const path = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
|
|
96
|
+
? `$.${key}`
|
|
97
|
+
: `$[${JSON.stringify(key)}]`;
|
|
98
|
+
if (lists.includes(key)) {
|
|
99
|
+
if (!Array.isArray(value))
|
|
100
|
+
issues.push({
|
|
101
|
+
domain: "configuration",
|
|
102
|
+
code: "invalid-type",
|
|
103
|
+
path,
|
|
104
|
+
message: "Filter selector must be an array",
|
|
105
|
+
});
|
|
106
|
+
else
|
|
107
|
+
value.forEach((item, index) => checkToken(item, `${path}[${index}]`));
|
|
108
|
+
} else if (labels.includes(key)) checkToken(value, path);
|
|
109
|
+
else if (bounds.includes(key)) {
|
|
110
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
111
|
+
issues.push({
|
|
112
|
+
domain: "configuration",
|
|
113
|
+
code: "invalid-number",
|
|
114
|
+
path,
|
|
115
|
+
message: "Development-age bounds must be nonnegative safe integers",
|
|
116
|
+
});
|
|
117
|
+
} else
|
|
118
|
+
issues.push({
|
|
119
|
+
domain: "configuration",
|
|
120
|
+
code: "unknown-key",
|
|
121
|
+
path,
|
|
122
|
+
message: `Unknown filter key ${key}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (
|
|
126
|
+
typeof filter.minDevelopmentAge === "number" &&
|
|
127
|
+
typeof filter.maxDevelopmentAge === "number" &&
|
|
128
|
+
filter.minDevelopmentAge > filter.maxDevelopmentAge
|
|
129
|
+
)
|
|
130
|
+
issues.push({
|
|
131
|
+
domain: "configuration",
|
|
132
|
+
code: "invalid-configuration",
|
|
133
|
+
path: "$",
|
|
134
|
+
message: "Minimum development age exceeds maximum",
|
|
135
|
+
});
|
|
136
|
+
if (issues.length) throw new DiagnosticValidationError(issues);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Materializes optional selectors; period aliases must already be canonical. */
|
|
140
|
+
export function normalizeDiagnosticsFilterIdentity(
|
|
141
|
+
filter: DiagnosticsFilter | null,
|
|
142
|
+
): NormalizedDiagnosticsFilterIdentity | null {
|
|
143
|
+
if (filter === null) return null;
|
|
144
|
+
validateFilterIdentityInput(filter);
|
|
145
|
+
return deepFreeze({
|
|
146
|
+
sourceGroups:
|
|
147
|
+
filter.sourceGroups === undefined
|
|
148
|
+
? null
|
|
149
|
+
: sortedUnique(filter.sourceGroups),
|
|
150
|
+
outputGroups:
|
|
151
|
+
filter.outputGroups === undefined
|
|
152
|
+
? null
|
|
153
|
+
: sortedUnique(filter.outputGroups),
|
|
154
|
+
origins: filter.origins === undefined ? null : sortedUnique(filter.origins),
|
|
155
|
+
originFrom: filter.originFrom ?? null,
|
|
156
|
+
originThrough: filter.originThrough ?? null,
|
|
157
|
+
valuations:
|
|
158
|
+
filter.valuations === undefined ? null : sortedUnique(filter.valuations),
|
|
159
|
+
valuationFrom: filter.valuationFrom ?? null,
|
|
160
|
+
valuationThrough: filter.valuationThrough ?? null,
|
|
161
|
+
minDevelopmentAge:
|
|
162
|
+
filter.minDevelopmentAge === undefined
|
|
163
|
+
? null
|
|
164
|
+
: normalizeDiagnosticNumber(filter.minDevelopmentAge),
|
|
165
|
+
maxDevelopmentAge:
|
|
166
|
+
filter.maxDevelopmentAge === undefined
|
|
167
|
+
? null
|
|
168
|
+
: normalizeDiagnosticNumber(filter.maxDevelopmentAge),
|
|
169
|
+
instanceIds:
|
|
170
|
+
filter.instanceIds === undefined ? null : sortedUnique(filter.instanceIds),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Owned identity copy. Source slots are schema-defined; free JSON dimensions
|
|
176
|
+
* must not be interpreted as source evidence just because a key looks similar.
|
|
177
|
+
*/
|
|
178
|
+
export function projectDiagnosticIdentity<T>(
|
|
179
|
+
value: T,
|
|
180
|
+
): DiagnosticIdentityProjection<T> {
|
|
181
|
+
const issues = diagnosticJsonPreflight(value, "input", {
|
|
182
|
+
maxNodes: Number.MAX_SAFE_INTEGER,
|
|
183
|
+
});
|
|
184
|
+
if (issues.length) throw new DiagnosticValidationError(issues);
|
|
185
|
+
const sharing = createDiagnosticEvidenceInterner();
|
|
186
|
+
const clone = (
|
|
187
|
+
item: unknown,
|
|
188
|
+
sourceSlot = false,
|
|
189
|
+
freeJson = false,
|
|
190
|
+
path = "$",
|
|
191
|
+
): unknown => {
|
|
192
|
+
if (item === null || typeof item !== "object")
|
|
193
|
+
return typeof item === "number" && Object.is(item, -0) ? 0 : item;
|
|
194
|
+
const mode = freeJson ? "free" : sourceSlot ? "source" : "plain";
|
|
195
|
+
if (Array.isArray(item))
|
|
196
|
+
return sharing.internOwned(
|
|
197
|
+
Object.freeze(
|
|
198
|
+
item.map((child, index) =>
|
|
199
|
+
clone(child, sourceSlot, freeJson, `${path}[${index}]`),
|
|
200
|
+
),
|
|
201
|
+
),
|
|
202
|
+
mode,
|
|
203
|
+
);
|
|
204
|
+
const record = item as Record<string, unknown>;
|
|
205
|
+
if (sourceSlot && typeof record.artifactId === "string") {
|
|
206
|
+
const sourceIssues = [];
|
|
207
|
+
for (const key of Object.keys(record)) {
|
|
208
|
+
const fieldPath = `${path}.${key}`;
|
|
209
|
+
if (
|
|
210
|
+
![
|
|
211
|
+
"artifactId",
|
|
212
|
+
"sourceFile",
|
|
213
|
+
"sourceSheet",
|
|
214
|
+
"sourceRow",
|
|
215
|
+
"sourceCell",
|
|
216
|
+
].includes(key)
|
|
217
|
+
)
|
|
218
|
+
sourceIssues.push({
|
|
219
|
+
domain: "input" as const,
|
|
220
|
+
code: "unknown-key" as const,
|
|
221
|
+
path: fieldPath,
|
|
222
|
+
message: `Unknown source-location key ${key}`,
|
|
223
|
+
});
|
|
224
|
+
else if (key === "sourceRow") {
|
|
225
|
+
if (
|
|
226
|
+
record[key] !== null &&
|
|
227
|
+
!(
|
|
228
|
+
typeof record[key] === "number" &&
|
|
229
|
+
Number.isSafeInteger(record[key]) &&
|
|
230
|
+
record[key] >= 0
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
sourceIssues.push({
|
|
234
|
+
domain: "input" as const,
|
|
235
|
+
code: "invalid-number" as const,
|
|
236
|
+
path: fieldPath,
|
|
237
|
+
message: "Source row must be a nonnegative safe integer or null",
|
|
238
|
+
});
|
|
239
|
+
} else if (
|
|
240
|
+
!(key !== "artifactId" && record[key] === null) &&
|
|
241
|
+
!isDiagnosticToken(record[key])
|
|
242
|
+
)
|
|
243
|
+
sourceIssues.push({
|
|
244
|
+
domain: "input" as const,
|
|
245
|
+
code: "invalid-string" as const,
|
|
246
|
+
path: fieldPath,
|
|
247
|
+
message: "Source location must use nonempty token strings",
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (sourceIssues.length)
|
|
251
|
+
throw new DiagnosticValidationError(sourceIssues);
|
|
252
|
+
return sharing.internOwned(
|
|
253
|
+
normalizeDiagnosticSourceLocation(
|
|
254
|
+
record as unknown as DiagnosticSourceLocation,
|
|
255
|
+
),
|
|
256
|
+
mode,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const result = Object.create(null) as Record<string, unknown>;
|
|
260
|
+
for (const key of Object.keys(record).sort()) {
|
|
261
|
+
const opaque =
|
|
262
|
+
freeJson || key === "groupDimensions" || key === "dimensions";
|
|
263
|
+
result[key] = clone(
|
|
264
|
+
record[key],
|
|
265
|
+
!opaque && (key === "source" || key === "sources"),
|
|
266
|
+
opaque,
|
|
267
|
+
`${path}.${key}`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return sharing.internOwned(Object.freeze(result), mode);
|
|
271
|
+
};
|
|
272
|
+
return clone(value) as DiagnosticIdentityProjection<T>;
|
|
273
|
+
}
|
|
33
274
|
|
|
34
275
|
function compareCodeUnits(left: string, right: string): number {
|
|
35
276
|
return left === right ? 0 : left < right ? -1 : 1;
|
|
@@ -39,9 +280,13 @@ function sortedUnique(values: readonly string[]): readonly string[] {
|
|
|
39
280
|
return [...new Set(values)].sort(compareCodeUnits);
|
|
40
281
|
}
|
|
41
282
|
|
|
42
|
-
function sortedRecord<T>(
|
|
283
|
+
function sortedRecord<T>(
|
|
284
|
+
entries: readonly (readonly [string, T])[],
|
|
285
|
+
): Readonly<Record<string, T>> {
|
|
43
286
|
const result = Object.create(null) as Record<string, T>;
|
|
44
|
-
for (const [key, value] of [...entries].sort(([left], [right]) =>
|
|
287
|
+
for (const [key, value] of [...entries].sort(([left], [right]) =>
|
|
288
|
+
compareCodeUnits(left, right),
|
|
289
|
+
)) {
|
|
45
290
|
result[key] = value;
|
|
46
291
|
}
|
|
47
292
|
return result;
|
|
@@ -49,29 +294,39 @@ function sortedRecord<T>(entries: readonly (readonly [string, T])[]): Readonly<R
|
|
|
49
294
|
|
|
50
295
|
function normalizeJsonValue<T>(value: T): T {
|
|
51
296
|
if (typeof value === "number" && Object.is(value, -0)) return 0 as T;
|
|
52
|
-
if (Array.isArray(value))
|
|
297
|
+
if (Array.isArray(value))
|
|
298
|
+
return value.map((item) => normalizeJsonValue(item)) as T;
|
|
53
299
|
if (value !== null && typeof value === "object") {
|
|
54
|
-
return sortedRecord(
|
|
55
|
-
|
|
56
|
-
|
|
300
|
+
return sortedRecord(
|
|
301
|
+
Object.entries(value as Record<string, unknown>).map(
|
|
302
|
+
([key, item]) => [key, normalizeJsonValue(item)] as const,
|
|
303
|
+
),
|
|
304
|
+
) as T;
|
|
57
305
|
}
|
|
58
306
|
return value;
|
|
59
307
|
}
|
|
60
308
|
|
|
61
|
-
function deepFreeze<T>(
|
|
62
|
-
|
|
309
|
+
function deepFreeze<T>(
|
|
310
|
+
value: T,
|
|
311
|
+
seen = new WeakSet<object>(),
|
|
312
|
+
): DiagnosticDeepReadonly<T> {
|
|
313
|
+
if (value === null || typeof value !== "object")
|
|
314
|
+
return value as DiagnosticDeepReadonly<T>;
|
|
63
315
|
if (seen.has(value)) return value as DiagnosticDeepReadonly<T>;
|
|
64
316
|
seen.add(value);
|
|
65
|
-
for (const child of Object.values(value as Record<string, unknown>))
|
|
317
|
+
for (const child of Object.values(value as Record<string, unknown>))
|
|
318
|
+
deepFreeze(child, seen);
|
|
66
319
|
return Object.freeze(value) as DiagnosticDeepReadonly<T>;
|
|
67
320
|
}
|
|
68
321
|
|
|
69
322
|
function attributes(
|
|
70
323
|
value: Readonly<Record<string, string | number | boolean | null>> | undefined,
|
|
71
324
|
): Readonly<Record<string, string | number | boolean | null>> {
|
|
72
|
-
return sortedRecord(
|
|
73
|
-
|
|
74
|
-
|
|
325
|
+
return sortedRecord(
|
|
326
|
+
Object.entries(value ?? {}).map(
|
|
327
|
+
([key, item]) => [key, normalizeJsonValue(item)] as const,
|
|
328
|
+
),
|
|
329
|
+
);
|
|
75
330
|
}
|
|
76
331
|
|
|
77
332
|
export type NormalizedAmountLimitationIdentity =
|
|
@@ -80,7 +335,11 @@ export type NormalizedAmountLimitationIdentity =
|
|
|
80
335
|
readonly kind: "layer" | "pre-limited";
|
|
81
336
|
readonly attachment: number;
|
|
82
337
|
readonly limit: number | null;
|
|
83
|
-
readonly application:
|
|
338
|
+
readonly application:
|
|
339
|
+
| "claim"
|
|
340
|
+
| "occurrence"
|
|
341
|
+
| "policy"
|
|
342
|
+
| "source-defined";
|
|
84
343
|
readonly derivation:
|
|
85
344
|
| { readonly kind: "sdk" }
|
|
86
345
|
| {
|
|
@@ -111,7 +370,9 @@ export interface NormalizedDiagnosticReviewFilterIdentity {
|
|
|
111
370
|
export type NormalizedDiagnosticPeriodAxisIdentity =
|
|
112
371
|
| DiagnosticDeepReadonly<Extract<DiagnosticPeriodAxis, { kind: "calendar" }>>
|
|
113
372
|
| (Omit<
|
|
114
|
-
DiagnosticDeepReadonly<
|
|
373
|
+
DiagnosticDeepReadonly<
|
|
374
|
+
Extract<DiagnosticPeriodAxis, { kind: "ordered" }>
|
|
375
|
+
>,
|
|
115
376
|
"origins" | "valuations"
|
|
116
377
|
> & {
|
|
117
378
|
readonly origins: readonly {
|
|
@@ -129,11 +390,16 @@ export type NormalizedDiagnosticPeriodAxisIdentity =
|
|
|
129
390
|
export interface NormalizedDiagnosticFormulaIdentity {
|
|
130
391
|
readonly id: string;
|
|
131
392
|
readonly version: string;
|
|
132
|
-
readonly roles: Readonly<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
393
|
+
readonly roles: Readonly<
|
|
394
|
+
Record<
|
|
395
|
+
string,
|
|
396
|
+
{
|
|
397
|
+
readonly kind: DiagnosticMeasureKind;
|
|
398
|
+
readonly compatibilityGroup: string | null;
|
|
399
|
+
readonly developmentSemantics: DiagnosticDevelopmentSemantics | null;
|
|
400
|
+
}
|
|
401
|
+
>
|
|
402
|
+
>;
|
|
137
403
|
readonly numerator: DiagnosticDeepReadonly<DiagnosticRoleExpression>;
|
|
138
404
|
readonly denominator: DiagnosticDeepReadonly<DiagnosticRoleExpression>;
|
|
139
405
|
readonly denominatorPolicy: "positive-or-null";
|
|
@@ -205,7 +471,9 @@ export interface NormalizedDiagnosticDefinitionIdentity {
|
|
|
205
471
|
readonly subject: DiagnosticCountPopulationDefinition["subject"];
|
|
206
472
|
readonly unit: string;
|
|
207
473
|
readonly description: string;
|
|
208
|
-
readonly attributes: Readonly<
|
|
474
|
+
readonly attributes: Readonly<
|
|
475
|
+
Record<string, string | number | boolean | null>
|
|
476
|
+
>;
|
|
209
477
|
}[];
|
|
210
478
|
readonly exposureBases: readonly {
|
|
211
479
|
readonly id: string;
|
|
@@ -214,7 +482,9 @@ export interface NormalizedDiagnosticDefinitionIdentity {
|
|
|
214
482
|
readonly unit: string;
|
|
215
483
|
readonly description: string;
|
|
216
484
|
readonly sourceDescription: string | null;
|
|
217
|
-
readonly attributes: Readonly<
|
|
485
|
+
readonly attributes: Readonly<
|
|
486
|
+
Record<string, string | number | boolean | null>
|
|
487
|
+
>;
|
|
218
488
|
}[];
|
|
219
489
|
readonly amountBases: readonly {
|
|
220
490
|
readonly id: string;
|
|
@@ -227,7 +497,9 @@ export interface NormalizedDiagnosticDefinitionIdentity {
|
|
|
227
497
|
readonly limitation: NormalizedAmountLimitationIdentity;
|
|
228
498
|
}[];
|
|
229
499
|
readonly sourceDescription: string | null;
|
|
230
|
-
readonly attributes: Readonly<
|
|
500
|
+
readonly attributes: Readonly<
|
|
501
|
+
Record<string, string | number | boolean | null>
|
|
502
|
+
>;
|
|
231
503
|
}[];
|
|
232
504
|
readonly derivedMeasures: readonly DiagnosticDeepReadonly<DiagnosticDerivedMeasureDefinition>[];
|
|
233
505
|
readonly formulas: readonly NormalizedDiagnosticFormulaIdentity[];
|
|
@@ -235,7 +507,9 @@ export interface NormalizedDiagnosticDefinitionIdentity {
|
|
|
235
507
|
readonly id: string;
|
|
236
508
|
readonly version: string;
|
|
237
509
|
readonly formulaId: string;
|
|
238
|
-
readonly bindings: Readonly<
|
|
510
|
+
readonly bindings: Readonly<
|
|
511
|
+
Record<string, DiagnosticDeepReadonly<DiagnosticMeasureExpression>>
|
|
512
|
+
>;
|
|
239
513
|
readonly presentation: DiagnosticDeepReadonly<DiagnosticMetricPresentation>;
|
|
240
514
|
readonly rules: readonly {
|
|
241
515
|
readonly id: string;
|
|
@@ -260,7 +534,9 @@ export interface NormalizedDiagnosticCalculationScope {
|
|
|
260
534
|
readonly id: string;
|
|
261
535
|
readonly version: string;
|
|
262
536
|
readonly formulaId: string;
|
|
263
|
-
readonly bindings: Readonly<
|
|
537
|
+
readonly bindings: Readonly<
|
|
538
|
+
Record<string, DiagnosticDeepReadonly<DiagnosticMeasureExpression>>
|
|
539
|
+
>;
|
|
264
540
|
};
|
|
265
541
|
readonly lossRowGrain: "claim" | "aggregate";
|
|
266
542
|
readonly measures: readonly {
|
|
@@ -280,13 +556,17 @@ export interface NormalizedDiagnosticCalculationScope {
|
|
|
280
556
|
readonly id: string;
|
|
281
557
|
readonly subject: DiagnosticCountPopulationDefinition["subject"];
|
|
282
558
|
readonly unit: string;
|
|
283
|
-
readonly attributes: Readonly<
|
|
559
|
+
readonly attributes: Readonly<
|
|
560
|
+
Record<string, string | number | boolean | null>
|
|
561
|
+
>;
|
|
284
562
|
}[];
|
|
285
563
|
readonly exposureBases: readonly {
|
|
286
564
|
readonly id: string;
|
|
287
565
|
readonly basis: DiagnosticExposureBasisDefinition["basis"];
|
|
288
566
|
readonly unit: string;
|
|
289
|
-
readonly attributes: Readonly<
|
|
567
|
+
readonly attributes: Readonly<
|
|
568
|
+
Record<string, string | number | boolean | null>
|
|
569
|
+
>;
|
|
290
570
|
}[];
|
|
291
571
|
readonly amountBases: readonly {
|
|
292
572
|
readonly id: string;
|
|
@@ -297,7 +577,9 @@ export interface NormalizedDiagnosticCalculationScope {
|
|
|
297
577
|
readonly treatment: "included" | "excluded" | "unknown";
|
|
298
578
|
readonly limitation: NormalizedAmountLimitationIdentity;
|
|
299
579
|
}[];
|
|
300
|
-
readonly attributes: Readonly<
|
|
580
|
+
readonly attributes: Readonly<
|
|
581
|
+
Record<string, string | number | boolean | null>
|
|
582
|
+
>;
|
|
301
583
|
}[];
|
|
302
584
|
readonly derivedMeasures: readonly DiagnosticDeepReadonly<DiagnosticDerivedMeasureDefinition>[];
|
|
303
585
|
}
|
|
@@ -310,36 +592,53 @@ export interface NormalizedDiagnosticSourceLocationIdentity {
|
|
|
310
592
|
readonly sourceCell: string | null;
|
|
311
593
|
}
|
|
312
594
|
|
|
313
|
-
function normalizeLimitation(
|
|
595
|
+
function normalizeLimitation(
|
|
596
|
+
limitation: AmountLimitation,
|
|
597
|
+
): NormalizedAmountLimitationIdentity {
|
|
314
598
|
if (limitation.kind === "unlimited") return { kind: "unlimited" };
|
|
315
599
|
if (limitation.kind === "unknown") {
|
|
316
600
|
return { kind: "unknown", description: limitation.description ?? null };
|
|
317
601
|
}
|
|
318
602
|
return {
|
|
319
603
|
kind: limitation.kind,
|
|
320
|
-
attachment: Object.is(limitation.attachment, -0)
|
|
321
|
-
|
|
604
|
+
attachment: Object.is(limitation.attachment, -0)
|
|
605
|
+
? 0
|
|
606
|
+
: limitation.attachment,
|
|
607
|
+
limit:
|
|
608
|
+
limitation.limit !== null && Object.is(limitation.limit, -0)
|
|
609
|
+
? 0
|
|
610
|
+
: limitation.limit,
|
|
322
611
|
application: limitation.application,
|
|
323
|
-
derivation:
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
612
|
+
derivation:
|
|
613
|
+
limitation.derivation.kind === "sdk"
|
|
614
|
+
? { kind: "sdk" }
|
|
615
|
+
: {
|
|
616
|
+
kind: "external",
|
|
617
|
+
actor: limitation.derivation.actor,
|
|
618
|
+
transformationRef: limitation.derivation.transformationRef,
|
|
619
|
+
},
|
|
330
620
|
};
|
|
331
621
|
}
|
|
332
622
|
|
|
333
|
-
function normalizeMeasureExpression<
|
|
334
|
-
|
|
335
|
-
): DiagnosticDeepReadonly<T> {
|
|
336
|
-
if (expression.op === "measure")
|
|
623
|
+
function normalizeMeasureExpression<
|
|
624
|
+
T extends DiagnosticMeasureExpression | DiagnosticClaimExpression,
|
|
625
|
+
>(expression: T): DiagnosticDeepReadonly<T> {
|
|
626
|
+
if (expression.op === "measure")
|
|
627
|
+
return {
|
|
628
|
+
op: "measure",
|
|
629
|
+
measureId: expression.measureId,
|
|
630
|
+
} as DiagnosticDeepReadonly<T>;
|
|
337
631
|
if (expression.op === "claim-layer") {
|
|
338
632
|
return {
|
|
339
633
|
op: "claim-layer",
|
|
340
634
|
measureId: expression.measureId,
|
|
341
|
-
attachment: Object.is(expression.attachment, -0)
|
|
342
|
-
|
|
635
|
+
attachment: Object.is(expression.attachment, -0)
|
|
636
|
+
? 0
|
|
637
|
+
: expression.attachment,
|
|
638
|
+
limit:
|
|
639
|
+
expression.limit !== null && Object.is(expression.limit, -0)
|
|
640
|
+
? 0
|
|
641
|
+
: expression.limit,
|
|
343
642
|
} as DiagnosticDeepReadonly<T>;
|
|
344
643
|
}
|
|
345
644
|
if (expression.op === "subtract") {
|
|
@@ -355,7 +654,9 @@ function normalizeMeasureExpression<T extends DiagnosticMeasureExpression | Diag
|
|
|
355
654
|
} as unknown as DiagnosticDeepReadonly<T>;
|
|
356
655
|
}
|
|
357
656
|
|
|
358
|
-
function normalizeRoleExpression(
|
|
657
|
+
function normalizeRoleExpression(
|
|
658
|
+
expression: DiagnosticRoleExpression,
|
|
659
|
+
): DiagnosticDeepReadonly<DiagnosticRoleExpression> {
|
|
359
660
|
if (expression.op === "role") return { op: "role", role: expression.role };
|
|
360
661
|
if (expression.op === "subtract") {
|
|
361
662
|
return {
|
|
@@ -371,43 +672,95 @@ function normalizeTolerance(
|
|
|
371
672
|
tolerance: { absolute?: number; relative?: number } | undefined,
|
|
372
673
|
): NormalizedDiagnosticToleranceIdentity {
|
|
373
674
|
return {
|
|
374
|
-
absolute: Object.is(tolerance?.absolute ?? 0, -0)
|
|
375
|
-
|
|
675
|
+
absolute: Object.is(tolerance?.absolute ?? 0, -0)
|
|
676
|
+
? 0
|
|
677
|
+
: (tolerance?.absolute ?? 0),
|
|
678
|
+
relative: Object.is(tolerance?.relative ?? 0, -0)
|
|
679
|
+
? 0
|
|
680
|
+
: (tolerance?.relative ?? 0),
|
|
376
681
|
};
|
|
377
682
|
}
|
|
378
683
|
|
|
379
|
-
function normalizeRuleOperand(
|
|
684
|
+
function normalizeRuleOperand(
|
|
685
|
+
operand: DiagnosticRuleOperand,
|
|
686
|
+
): DiagnosticDeepReadonly<DiagnosticRuleOperand> {
|
|
380
687
|
if (operand.source === "measure") {
|
|
381
|
-
return {
|
|
688
|
+
return {
|
|
689
|
+
source: "measure",
|
|
690
|
+
expression: normalizeMeasureExpression(operand.expression),
|
|
691
|
+
};
|
|
382
692
|
}
|
|
383
|
-
if (operand.source === "calculation")
|
|
384
|
-
|
|
693
|
+
if (operand.source === "calculation")
|
|
694
|
+
return { source: "calculation", field: operand.field };
|
|
695
|
+
return {
|
|
696
|
+
source: "constant",
|
|
697
|
+
value: Object.is(operand.value, -0) ? 0 : operand.value,
|
|
698
|
+
};
|
|
385
699
|
}
|
|
386
700
|
|
|
387
|
-
function normalizeReviewOperand(
|
|
388
|
-
|
|
701
|
+
function normalizeReviewOperand(
|
|
702
|
+
operand: DiagnosticReviewOperand,
|
|
703
|
+
): DiagnosticDeepReadonly<DiagnosticReviewOperand> {
|
|
704
|
+
if (operand.op === "constant")
|
|
705
|
+
return {
|
|
706
|
+
op: "constant",
|
|
707
|
+
value: Object.is(operand.value, -0) ? 0 : operand.value,
|
|
708
|
+
};
|
|
389
709
|
return normalizeMeasureExpression(operand);
|
|
390
710
|
}
|
|
391
711
|
|
|
392
|
-
function normalizeReviewFilter(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
minDevelopmentAge: filter.minDevelopmentAge === undefined
|
|
712
|
+
function normalizeReviewFilter(
|
|
713
|
+
filter: DiagnosticReviewFilter,
|
|
714
|
+
axis: DiagnosticPeriodAxis,
|
|
715
|
+
): NormalizedDiagnosticReviewFilterIdentity {
|
|
716
|
+
const normalizeMany = (
|
|
717
|
+
side: "origin" | "valuation",
|
|
718
|
+
values: readonly string[] | undefined,
|
|
719
|
+
) =>
|
|
720
|
+
values === undefined
|
|
402
721
|
? null
|
|
403
|
-
:
|
|
404
|
-
|
|
722
|
+
: sortedUnique(
|
|
723
|
+
values.map(
|
|
724
|
+
(value) =>
|
|
725
|
+
normalizeDiagnosticPeriodWithAxis(axis, side, value)!.label,
|
|
726
|
+
),
|
|
727
|
+
);
|
|
728
|
+
const normalizeOne = (
|
|
729
|
+
side: "origin" | "valuation",
|
|
730
|
+
value: string | undefined,
|
|
731
|
+
) =>
|
|
732
|
+
value === undefined
|
|
405
733
|
? null
|
|
406
|
-
:
|
|
734
|
+
: normalizeDiagnosticPeriodWithAxis(axis, side, value)!.label;
|
|
735
|
+
return {
|
|
736
|
+
sourceGroups:
|
|
737
|
+
filter.sourceGroups === undefined
|
|
738
|
+
? null
|
|
739
|
+
: sortedUnique(filter.sourceGroups),
|
|
740
|
+
origins: normalizeMany("origin", filter.origins),
|
|
741
|
+
originFrom: normalizeOne("origin", filter.originFrom),
|
|
742
|
+
originThrough: normalizeOne("origin", filter.originThrough),
|
|
743
|
+
valuations: normalizeMany("valuation", filter.valuations),
|
|
744
|
+
valuationFrom: normalizeOne("valuation", filter.valuationFrom),
|
|
745
|
+
valuationThrough: normalizeOne("valuation", filter.valuationThrough),
|
|
746
|
+
minDevelopmentAge:
|
|
747
|
+
filter.minDevelopmentAge === undefined
|
|
748
|
+
? null
|
|
749
|
+
: Object.is(filter.minDevelopmentAge, -0)
|
|
750
|
+
? 0
|
|
751
|
+
: filter.minDevelopmentAge,
|
|
752
|
+
maxDevelopmentAge:
|
|
753
|
+
filter.maxDevelopmentAge === undefined
|
|
754
|
+
? null
|
|
755
|
+
: Object.is(filter.maxDevelopmentAge, -0)
|
|
756
|
+
? 0
|
|
757
|
+
: filter.maxDevelopmentAge,
|
|
407
758
|
};
|
|
408
759
|
}
|
|
409
760
|
|
|
410
|
-
function normalizePeriodAxis(
|
|
761
|
+
function normalizePeriodAxis(
|
|
762
|
+
axis: DiagnosticPeriodAxis,
|
|
763
|
+
): NormalizedDiagnosticPeriodAxisIdentity {
|
|
411
764
|
if (axis.kind === "calendar") {
|
|
412
765
|
return {
|
|
413
766
|
kind: "calendar",
|
|
@@ -419,11 +772,20 @@ function normalizePeriodAxis(axis: DiagnosticPeriodAxis): NormalizedDiagnosticPe
|
|
|
419
772
|
ageOffset: Object.is(axis.ageOffset, -0) ? 0 : axis.ageOffset,
|
|
420
773
|
};
|
|
421
774
|
}
|
|
422
|
-
const coordinates = (values: typeof axis.origins) =>
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
775
|
+
const coordinates = (values: typeof axis.origins) =>
|
|
776
|
+
values
|
|
777
|
+
.map((coordinate) => ({
|
|
778
|
+
label: coordinate.label,
|
|
779
|
+
aliases: sortedUnique(coordinate.aliases ?? []),
|
|
780
|
+
coordinate: Object.is(coordinate.coordinate, -0)
|
|
781
|
+
? 0
|
|
782
|
+
: coordinate.coordinate,
|
|
783
|
+
}))
|
|
784
|
+
.sort(
|
|
785
|
+
(left, right) =>
|
|
786
|
+
left.coordinate - right.coordinate ||
|
|
787
|
+
compareCodeUnits(left.label, right.label),
|
|
788
|
+
);
|
|
427
789
|
return {
|
|
428
790
|
kind: "ordered",
|
|
429
791
|
id: axis.id,
|
|
@@ -438,17 +800,26 @@ function normalizePeriodAxis(axis: DiagnosticPeriodAxis): NormalizedDiagnosticPe
|
|
|
438
800
|
function topologicalDerivations(
|
|
439
801
|
derivations: readonly DiagnosticDerivedMeasureDefinition[],
|
|
440
802
|
): readonly DiagnosticDerivedMeasureDefinition[] {
|
|
441
|
-
const byOutput = new Map(
|
|
442
|
-
|
|
803
|
+
const byOutput = new Map(
|
|
804
|
+
derivations.map((derivation) => [derivation.outputMeasureId, derivation]),
|
|
805
|
+
);
|
|
806
|
+
const remaining = new Map(
|
|
807
|
+
derivations.map((derivation) => [derivation.id, derivation]),
|
|
808
|
+
);
|
|
443
809
|
const emittedOutputs = new Set<string>();
|
|
444
810
|
const ordered: DiagnosticDerivedMeasureDefinition[] = [];
|
|
445
811
|
while (remaining.size > 0) {
|
|
446
|
-
const ready = [...remaining.values()]
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
812
|
+
const ready = [...remaining.values()]
|
|
813
|
+
.filter((derivation) =>
|
|
814
|
+
expressionDependencies(derivation.expression)
|
|
815
|
+
.filter((id) => byOutput.has(id))
|
|
816
|
+
.every((id) => emittedOutputs.has(id)),
|
|
817
|
+
)
|
|
818
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id));
|
|
819
|
+
if (ready.length === 0)
|
|
820
|
+
return [...derivations].sort((left, right) =>
|
|
821
|
+
compareCodeUnits(left.id, right.id),
|
|
822
|
+
);
|
|
452
823
|
for (const derivation of ready) {
|
|
453
824
|
ordered.push(derivation);
|
|
454
825
|
emittedOutputs.add(derivation.outputMeasureId);
|
|
@@ -458,7 +829,10 @@ function topologicalDerivations(
|
|
|
458
829
|
return ordered;
|
|
459
830
|
}
|
|
460
831
|
|
|
461
|
-
function normalizeReviewRule(
|
|
832
|
+
function normalizeReviewRule(
|
|
833
|
+
rule: DiagnosticReviewRule,
|
|
834
|
+
axis: DiagnosticPeriodAxis,
|
|
835
|
+
): NormalizedDiagnosticReviewRuleIdentity {
|
|
462
836
|
const base: NormalizedDiagnosticReviewRuleBase = {
|
|
463
837
|
id: rule.id,
|
|
464
838
|
code: rule.code,
|
|
@@ -508,8 +882,21 @@ function normalizeReviewRule(rule: DiagnosticReviewRule): NormalizedDiagnosticRe
|
|
|
508
882
|
kind: "control-total",
|
|
509
883
|
expression: normalizeMeasureExpression(rule.expression),
|
|
510
884
|
expected: Object.is(rule.expected, -0) ? 0 : rule.expected,
|
|
511
|
-
filter:
|
|
512
|
-
|
|
885
|
+
filter:
|
|
886
|
+
rule.filter === undefined
|
|
887
|
+
? null
|
|
888
|
+
: normalizeReviewFilter(rule.filter, axis),
|
|
889
|
+
projection:
|
|
890
|
+
rule.projection.kind === "valuation"
|
|
891
|
+
? {
|
|
892
|
+
kind: "valuation",
|
|
893
|
+
valuation: normalizeDiagnosticPeriodWithAxis(
|
|
894
|
+
axis,
|
|
895
|
+
"valuation",
|
|
896
|
+
rule.projection.valuation,
|
|
897
|
+
)!.label,
|
|
898
|
+
}
|
|
899
|
+
: normalizeJsonValue(rule.projection),
|
|
513
900
|
};
|
|
514
901
|
}
|
|
515
902
|
|
|
@@ -521,80 +908,107 @@ export function normalizeDiagnosticDefinition(
|
|
|
521
908
|
id: definition.id,
|
|
522
909
|
version: definition.version,
|
|
523
910
|
lossRowGrain: definition.lossRowGrain,
|
|
524
|
-
measures: definition.measures
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
id
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
911
|
+
measures: definition.measures
|
|
912
|
+
.map((measure) => ({
|
|
913
|
+
id: measure.id,
|
|
914
|
+
displayName: measure.displayName,
|
|
915
|
+
description: measure.description,
|
|
916
|
+
source: measure.source,
|
|
917
|
+
kind: measure.kind,
|
|
918
|
+
unit: measure.unit,
|
|
919
|
+
developmentSemantics: measure.developmentSemantics,
|
|
920
|
+
aggregation: "sum" as const,
|
|
921
|
+
missing: measure.missing,
|
|
922
|
+
basisId: measure.basisId ?? null,
|
|
923
|
+
countPopulationId: measure.countPopulationId ?? null,
|
|
924
|
+
exposureBasisId: measure.exposureBasisId ?? null,
|
|
925
|
+
exposureTiming: measure.exposureTiming ?? null,
|
|
926
|
+
}))
|
|
927
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
928
|
+
countPopulations: definition.countPopulations
|
|
929
|
+
.map((population) => ({
|
|
930
|
+
id: population.id,
|
|
931
|
+
displayName: population.displayName,
|
|
932
|
+
subject: population.subject,
|
|
933
|
+
unit: population.unit,
|
|
934
|
+
description: population.description,
|
|
935
|
+
attributes: attributes(population.attributes),
|
|
936
|
+
}))
|
|
937
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
938
|
+
exposureBases: definition.exposureBases
|
|
939
|
+
.map((basis) => ({
|
|
940
|
+
id: basis.id,
|
|
941
|
+
displayName: basis.displayName,
|
|
942
|
+
basis: basis.basis,
|
|
943
|
+
unit: basis.unit,
|
|
944
|
+
description: basis.description,
|
|
945
|
+
sourceDescription: basis.sourceDescription ?? null,
|
|
946
|
+
attributes: attributes(basis.attributes),
|
|
947
|
+
}))
|
|
948
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
949
|
+
amountBases: definition.amountBases
|
|
950
|
+
.map((basis) => ({
|
|
951
|
+
id: basis.id,
|
|
952
|
+
displayName: basis.displayName,
|
|
953
|
+
currency: basis.currency,
|
|
954
|
+
perspective: basis.perspective,
|
|
955
|
+
components: basis.components
|
|
956
|
+
.map((component) => ({
|
|
957
|
+
id: component.id,
|
|
958
|
+
treatment: component.treatment,
|
|
959
|
+
limitation: normalizeLimitation(component.limitation),
|
|
960
|
+
}))
|
|
961
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
962
|
+
sourceDescription: basis.sourceDescription ?? null,
|
|
963
|
+
attributes: attributes(basis.attributes),
|
|
964
|
+
}))
|
|
965
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
966
|
+
derivedMeasures: topologicalDerivations(definition.derivedMeasures).map(
|
|
967
|
+
(derivation) => ({
|
|
968
|
+
id: derivation.id,
|
|
969
|
+
outputMeasureId: derivation.outputMeasureId,
|
|
970
|
+
expression: normalizeMeasureExpression(derivation.expression),
|
|
971
|
+
}),
|
|
972
|
+
),
|
|
973
|
+
formulas: definition.formulas
|
|
974
|
+
.map((formula) => ({
|
|
975
|
+
id: formula.id,
|
|
976
|
+
version: formula.version,
|
|
977
|
+
roles: sortedRecord(
|
|
978
|
+
Object.entries(formula.roles).map(
|
|
979
|
+
([name, role]) =>
|
|
980
|
+
[
|
|
981
|
+
name,
|
|
982
|
+
{
|
|
983
|
+
kind: role.kind,
|
|
984
|
+
compatibilityGroup: role.compatibilityGroup ?? null,
|
|
985
|
+
developmentSemantics: role.developmentSemantics ?? null,
|
|
986
|
+
},
|
|
987
|
+
] as const,
|
|
988
|
+
),
|
|
989
|
+
),
|
|
990
|
+
numerator: normalizeRoleExpression(formula.numerator),
|
|
991
|
+
denominator: normalizeRoleExpression(formula.denominator),
|
|
992
|
+
denominatorPolicy: "positive-or-null" as const,
|
|
993
|
+
}))
|
|
994
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
586
995
|
instances: definition.instances.map((instance) => ({
|
|
587
996
|
id: instance.id,
|
|
588
997
|
version: instance.version,
|
|
589
998
|
formulaId: instance.formulaId,
|
|
590
|
-
bindings: sortedRecord(
|
|
591
|
-
|
|
592
|
-
|
|
999
|
+
bindings: sortedRecord(
|
|
1000
|
+
Object.entries(instance.bindings).map(
|
|
1001
|
+
([role, expression]) =>
|
|
1002
|
+
[role, normalizeMeasureExpression(expression)] as const,
|
|
1003
|
+
),
|
|
1004
|
+
),
|
|
593
1005
|
presentation: {
|
|
594
1006
|
displayName: instance.presentation.displayName,
|
|
595
1007
|
description: instance.presentation.description,
|
|
596
1008
|
displayUnit: instance.presentation.displayUnit,
|
|
597
|
-
scale: Object.is(instance.presentation.scale, -0)
|
|
1009
|
+
scale: Object.is(instance.presentation.scale, -0)
|
|
1010
|
+
? 0
|
|
1011
|
+
: instance.presentation.scale,
|
|
598
1012
|
numeratorLabel: instance.presentation.numeratorLabel,
|
|
599
1013
|
denominatorLabel: instance.presentation.denominatorLabel,
|
|
600
1014
|
},
|
|
@@ -611,7 +1025,9 @@ export function normalizeDiagnosticDefinition(
|
|
|
611
1025
|
},
|
|
612
1026
|
})),
|
|
613
1027
|
})),
|
|
614
|
-
reviewRules: definition.reviewRules.map(
|
|
1028
|
+
reviewRules: definition.reviewRules.map((rule) =>
|
|
1029
|
+
normalizeReviewRule(rule, definition.periodAxis),
|
|
1030
|
+
),
|
|
615
1031
|
periodAxis: normalizePeriodAxis(definition.periodAxis),
|
|
616
1032
|
};
|
|
617
1033
|
return deepFreeze(normalized);
|
|
@@ -624,60 +1040,116 @@ export function normalizeDiagnosticSourceLocation(
|
|
|
624
1040
|
artifactId: source.artifactId,
|
|
625
1041
|
sourceFile: source.sourceFile ?? null,
|
|
626
1042
|
sourceSheet: source.sourceSheet ?? null,
|
|
627
|
-
sourceRow:
|
|
1043
|
+
sourceRow:
|
|
1044
|
+
source.sourceRow === undefined
|
|
1045
|
+
? null
|
|
1046
|
+
: Object.is(source.sourceRow, -0)
|
|
1047
|
+
? 0
|
|
1048
|
+
: source.sourceRow,
|
|
628
1049
|
sourceCell: source.sourceCell ?? null,
|
|
629
1050
|
});
|
|
630
1051
|
}
|
|
631
1052
|
|
|
632
|
-
function expressionDependencies(
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
1053
|
+
function expressionDependencies(
|
|
1054
|
+
expression: DiagnosticMeasureExpression | DiagnosticClaimExpression,
|
|
1055
|
+
): readonly string[] {
|
|
1056
|
+
if (expression.op === "measure" || expression.op === "claim-layer")
|
|
1057
|
+
return [expression.measureId];
|
|
1058
|
+
if (expression.op === "subtract")
|
|
1059
|
+
return sortedUnique([
|
|
1060
|
+
...expressionDependencies(expression.left),
|
|
1061
|
+
...expressionDependencies(expression.right),
|
|
1062
|
+
]);
|
|
638
1063
|
return sortedUnique(expression.terms.flatMap(expressionDependencies));
|
|
639
1064
|
}
|
|
640
1065
|
|
|
641
|
-
function ruleMeasureDependencies(
|
|
642
|
-
|
|
1066
|
+
function ruleMeasureDependencies(
|
|
1067
|
+
operand: DiagnosticDeepReadonly<DiagnosticRuleOperand>,
|
|
1068
|
+
): readonly string[] {
|
|
1069
|
+
return operand.source === "measure"
|
|
1070
|
+
? expressionDependencies(operand.expression)
|
|
1071
|
+
: [];
|
|
643
1072
|
}
|
|
644
1073
|
|
|
645
1074
|
function tag<K extends string, T>(kind: K, property: string, value: T): string {
|
|
646
|
-
return `fnv1a64-jcs-v1:${fnv1a64(
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
1075
|
+
return `fnv1a64-jcs-v1:${fnv1a64(
|
|
1076
|
+
canonicalJson({
|
|
1077
|
+
identityVersion: 1,
|
|
1078
|
+
kind,
|
|
1079
|
+
[property]: value,
|
|
1080
|
+
}),
|
|
1081
|
+
)}`;
|
|
651
1082
|
}
|
|
652
1083
|
|
|
653
1084
|
export interface BuiltDiagnosticIdentities {
|
|
654
1085
|
readonly formulaFingerprints: Readonly<Record<string, string>>;
|
|
655
1086
|
readonly calculationFingerprints: Readonly<Record<string, string>>;
|
|
656
1087
|
readonly definitionIntegrity: string;
|
|
657
|
-
readonly calculationScopesByInstanceId: ReadonlyMap<
|
|
658
|
-
|
|
659
|
-
|
|
1088
|
+
readonly calculationScopesByInstanceId: ReadonlyMap<
|
|
1089
|
+
string,
|
|
1090
|
+
NormalizedDiagnosticCalculationScope
|
|
1091
|
+
>;
|
|
1092
|
+
readonly calculationDependenciesByInstanceId: ReadonlyMap<
|
|
1093
|
+
string,
|
|
1094
|
+
readonly string[]
|
|
1095
|
+
>;
|
|
1096
|
+
readonly evaluationDependenciesByInstanceId: ReadonlyMap<
|
|
1097
|
+
string,
|
|
1098
|
+
readonly string[]
|
|
1099
|
+
>;
|
|
660
1100
|
}
|
|
661
1101
|
|
|
662
1102
|
export function buildDiagnosticIdentities(
|
|
663
1103
|
definition: DiagnosticDeepReadonly<NormalizedDiagnosticDefinitionIdentity>,
|
|
664
1104
|
): BuiltDiagnosticIdentities {
|
|
665
|
-
const formulas = new Map(
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1105
|
+
const formulas = new Map(
|
|
1106
|
+
definition.formulas.map((formula) => [formula.id, formula]),
|
|
1107
|
+
);
|
|
1108
|
+
const measures = new Map(
|
|
1109
|
+
definition.measures.map((measure) => [measure.id, measure]),
|
|
1110
|
+
);
|
|
1111
|
+
const derivations = new Map(
|
|
1112
|
+
definition.derivedMeasures.map((derivation) => [
|
|
1113
|
+
derivation.outputMeasureId,
|
|
1114
|
+
derivation,
|
|
1115
|
+
]),
|
|
1116
|
+
);
|
|
1117
|
+
const populations = new Map(
|
|
1118
|
+
definition.countPopulations.map((population) => [
|
|
1119
|
+
population.id,
|
|
1120
|
+
population,
|
|
1121
|
+
]),
|
|
1122
|
+
);
|
|
1123
|
+
const exposureBases = new Map(
|
|
1124
|
+
definition.exposureBases.map((basis) => [basis.id, basis]),
|
|
1125
|
+
);
|
|
1126
|
+
const amountBases = new Map(
|
|
1127
|
+
definition.amountBases.map((basis) => [basis.id, basis]),
|
|
1128
|
+
);
|
|
1129
|
+
|
|
1130
|
+
const formulaFingerprints = sortedRecord(
|
|
1131
|
+
definition.formulas.map(
|
|
1132
|
+
(formula) =>
|
|
1133
|
+
[formula.id, tag("diagnostic-formula", "formula", formula)] as const,
|
|
1134
|
+
),
|
|
1135
|
+
);
|
|
675
1136
|
const calculationFingerprints = Object.create(null) as Record<string, string>;
|
|
676
|
-
const calculationScopesByInstanceId = new Map<
|
|
677
|
-
|
|
678
|
-
|
|
1137
|
+
const calculationScopesByInstanceId = new Map<
|
|
1138
|
+
string,
|
|
1139
|
+
NormalizedDiagnosticCalculationScope
|
|
1140
|
+
>();
|
|
1141
|
+
const calculationDependenciesByInstanceId = new Map<
|
|
1142
|
+
string,
|
|
1143
|
+
readonly string[]
|
|
1144
|
+
>();
|
|
1145
|
+
const evaluationDependenciesByInstanceId = new Map<
|
|
1146
|
+
string,
|
|
1147
|
+
readonly string[]
|
|
1148
|
+
>();
|
|
679
1149
|
|
|
680
|
-
const transitiveDependencies = (
|
|
1150
|
+
const transitiveDependencies = (
|
|
1151
|
+
roots: readonly string[],
|
|
1152
|
+
): readonly string[] => {
|
|
681
1153
|
const found = new Set<string>();
|
|
682
1154
|
const stack = [...roots];
|
|
683
1155
|
while (stack.length > 0) {
|
|
@@ -685,7 +1157,8 @@ export function buildDiagnosticIdentities(
|
|
|
685
1157
|
if (found.has(id)) continue;
|
|
686
1158
|
found.add(id);
|
|
687
1159
|
const derivation = derivations.get(id);
|
|
688
|
-
if (derivation)
|
|
1160
|
+
if (derivation)
|
|
1161
|
+
stack.push(...expressionDependencies(derivation.expression));
|
|
689
1162
|
}
|
|
690
1163
|
return [...found].sort(compareCodeUnits);
|
|
691
1164
|
};
|
|
@@ -701,14 +1174,34 @@ export function buildDiagnosticIdentities(
|
|
|
701
1174
|
...ruleMeasureDependencies(rule.when.right),
|
|
702
1175
|
]),
|
|
703
1176
|
]);
|
|
704
|
-
calculationDependenciesByInstanceId.set(
|
|
1177
|
+
calculationDependenciesByInstanceId.set(
|
|
1178
|
+
instance.id,
|
|
1179
|
+
calculationDependencies,
|
|
1180
|
+
);
|
|
705
1181
|
evaluationDependenciesByInstanceId.set(instance.id, evaluationDependencies);
|
|
706
1182
|
|
|
707
|
-
const selectedMeasures = calculationDependencies
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
1183
|
+
const selectedMeasures = calculationDependencies
|
|
1184
|
+
.map((id) => measures.get(id)!)
|
|
1185
|
+
.filter(Boolean);
|
|
1186
|
+
const selectedDerivations = definition.derivedMeasures.filter(
|
|
1187
|
+
(derivation) =>
|
|
1188
|
+
calculationDependencies.includes(derivation.outputMeasureId),
|
|
1189
|
+
);
|
|
1190
|
+
const countPopulationIds = sortedUnique(
|
|
1191
|
+
selectedMeasures.flatMap((measure) =>
|
|
1192
|
+
measure.countPopulationId === null ? [] : [measure.countPopulationId],
|
|
1193
|
+
),
|
|
1194
|
+
);
|
|
1195
|
+
const exposureBasisIds = sortedUnique(
|
|
1196
|
+
selectedMeasures.flatMap((measure) =>
|
|
1197
|
+
measure.exposureBasisId === null ? [] : [measure.exposureBasisId],
|
|
1198
|
+
),
|
|
1199
|
+
);
|
|
1200
|
+
const amountBasisIds = sortedUnique(
|
|
1201
|
+
selectedMeasures.flatMap((measure) =>
|
|
1202
|
+
measure.basisId === null ? [] : [measure.basisId],
|
|
1203
|
+
),
|
|
1204
|
+
);
|
|
712
1205
|
const scope: NormalizedDiagnosticCalculationScope = {
|
|
713
1206
|
formulaFingerprint: formulaFingerprints[instance.formulaId]!,
|
|
714
1207
|
instance: {
|
|
@@ -733,11 +1226,21 @@ export function buildDiagnosticIdentities(
|
|
|
733
1226
|
})),
|
|
734
1227
|
countPopulations: countPopulationIds.map((id) => {
|
|
735
1228
|
const population = populations.get(id)!;
|
|
736
|
-
return {
|
|
1229
|
+
return {
|
|
1230
|
+
id,
|
|
1231
|
+
subject: population.subject,
|
|
1232
|
+
unit: population.unit,
|
|
1233
|
+
attributes: population.attributes,
|
|
1234
|
+
};
|
|
737
1235
|
}),
|
|
738
1236
|
exposureBases: exposureBasisIds.map((id) => {
|
|
739
1237
|
const basis = exposureBases.get(id)!;
|
|
740
|
-
return {
|
|
1238
|
+
return {
|
|
1239
|
+
id,
|
|
1240
|
+
basis: basis.basis,
|
|
1241
|
+
unit: basis.unit,
|
|
1242
|
+
attributes: basis.attributes,
|
|
1243
|
+
};
|
|
741
1244
|
}),
|
|
742
1245
|
amountBases: amountBasisIds.map((id) => {
|
|
743
1246
|
const basis = amountBases.get(id)!;
|
|
@@ -753,12 +1256,18 @@ export function buildDiagnosticIdentities(
|
|
|
753
1256
|
};
|
|
754
1257
|
const frozenScope = deepFreeze(scope);
|
|
755
1258
|
calculationScopesByInstanceId.set(instance.id, frozenScope);
|
|
756
|
-
calculationFingerprints[instance.id] = tag(
|
|
1259
|
+
calculationFingerprints[instance.id] = tag(
|
|
1260
|
+
"diagnostic-calculation",
|
|
1261
|
+
"calculation",
|
|
1262
|
+
frozenScope,
|
|
1263
|
+
);
|
|
757
1264
|
}
|
|
758
1265
|
|
|
759
1266
|
return {
|
|
760
1267
|
formulaFingerprints: deepFreeze(formulaFingerprints),
|
|
761
|
-
calculationFingerprints: deepFreeze(
|
|
1268
|
+
calculationFingerprints: deepFreeze(
|
|
1269
|
+
sortedRecord(Object.entries(calculationFingerprints)),
|
|
1270
|
+
),
|
|
762
1271
|
definitionIntegrity: tag("diagnostic-definition", "definition", definition),
|
|
763
1272
|
calculationScopesByInstanceId,
|
|
764
1273
|
calculationDependenciesByInstanceId,
|