@actuarial-ts/core 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- 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/diagnosticExposure.d.ts +2 -2
- package/dist/diagnosticExposure.d.ts.map +1 -1
- package/dist/diagnosticExposure.js +228 -20
- 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 +318 -51
- package/dist/diagnosticIdentity.js.map +1 -1
- 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 +47 -14
- package/dist/diagnosticPreparation.d.ts.map +1 -1
- package/dist/diagnosticPreparation.js +1482 -112
- package/dist/diagnosticPreparation.js.map +1 -1
- package/dist/diagnosticReview.d.ts +36 -14
- package/dist/diagnosticReview.d.ts.map +1 -1
- package/dist/diagnosticReview.js +448 -42
- package/dist/diagnosticReview.js.map +1 -1
- package/dist/diagnosticRunner.d.ts +2 -1
- package/dist/diagnosticRunner.d.ts.map +1 -1
- package/dist/diagnosticRunner.js +656 -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/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/mack.d.ts +4 -0
- package/dist/mack.d.ts.map +1 -1
- package/dist/mack.js +32 -4
- package/dist/mack.js.map +1 -1
- package/dist/triangle.d.ts.map +1 -1
- package/dist/triangle.js +59 -9
- package/dist/triangle.js.map +1 -1
- package/dist/types.d.ts +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/capping.ts +109 -18
- package/src/diagnosticAggregation.ts +69 -20
- package/src/diagnosticDefinitions.ts +2675 -426
- package/src/diagnosticDerivations.ts +281 -49
- package/src/diagnosticExposure.ts +441 -50
- package/src/diagnosticExpressions.ts +99 -17
- package/src/diagnosticFormulas.ts +239 -39
- package/src/diagnosticIdentity.ts +691 -191
- package/src/diagnosticOrdering.ts +106 -0
- package/src/diagnosticPeriodAxis.ts +92 -0
- package/src/diagnosticPeriods.ts +67 -82
- package/src/diagnosticPreparation.ts +2369 -127
- package/src/diagnosticReview.ts +864 -58
- package/src/diagnosticRunner.ts +1076 -72
- package/src/diagnosticRuntime.ts +264 -0
- package/src/diagnosticSourceOrdering.ts +50 -0
- package/src/index.ts +31 -3
- package/src/mack.ts +72 -9
- package/src/triangle.ts +103 -13
- package/src/types.ts +55 -20
- package/src/version.ts +1 -1
package/dist/diagnosticRunner.js
CHANGED
|
@@ -1,117 +1,703 @@
|
|
|
1
1
|
import { getCompiledDiagnosticDefinitionInternals } from "./diagnosticDefinitions.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
import { finalizeDiagnosticContributions } from "./diagnosticAggregation.js";
|
|
3
|
+
import { applyDiagnosticPresentation, diagnosticRawRatio, evaluateDiagnosticMeasureExpression, evaluateDiagnosticRoleExpression, } from "./diagnosticFormulas.js";
|
|
4
|
+
import { classifyDiagnosticComparison, diagnosticPredicateMatches, } from "./diagnosticRules.js";
|
|
5
|
+
import { assertPreparedDiagnosticData, } from "./diagnosticPreparation.js";
|
|
6
|
+
import { DiagnosticValidationError, } from "./types.js";
|
|
7
|
+
import { canonicalJson } from "./canonical.js";
|
|
8
|
+
import { projectDiagnosticIdentity, } from "./diagnosticIdentity.js";
|
|
9
|
+
import { compareDiagnosticFindings } from "./diagnosticOrdering.js";
|
|
10
|
+
import { normalizeDiagnosticPeriod } from "./diagnosticPeriods.js";
|
|
11
|
+
import { normalizeDiagnosticSourceLocations } from "./diagnosticSourceOrdering.js";
|
|
12
|
+
import { diagnosticJsonPreflight, hasDiagnosticOwn, isDiagnosticPlainRecord, isDiagnosticToken, } from "./diagnosticRuntime.js";
|
|
13
|
+
function codeUnit(a, b) {
|
|
14
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
15
|
+
}
|
|
16
|
+
function pointer(path, segment) {
|
|
17
|
+
return `${path}/${String(segment).replaceAll("~", "~0").replaceAll("/", "~1")}`;
|
|
18
|
+
}
|
|
19
|
+
function deepFreeze(value, seen = new WeakSet()) {
|
|
20
|
+
if (value === null || typeof value !== "object" || seen.has(value))
|
|
21
|
+
return value;
|
|
22
|
+
seen.add(value);
|
|
23
|
+
for (const child of Object.values(value))
|
|
24
|
+
deepFreeze(child, seen);
|
|
25
|
+
return Object.freeze(value);
|
|
26
|
+
}
|
|
27
|
+
function quantity(measure, value) {
|
|
28
|
+
return {
|
|
29
|
+
kind: measure.kind,
|
|
30
|
+
unit: measure.unit,
|
|
31
|
+
...(measure.basisId ? { basisId: measure.basisId } : {}),
|
|
32
|
+
...(measure.countPopulationId
|
|
33
|
+
? { countPopulationId: measure.countPopulationId }
|
|
34
|
+
: {}),
|
|
35
|
+
...(measure.exposureBasisId
|
|
36
|
+
? { exposureBasisId: measure.exposureBasisId }
|
|
37
|
+
: {}),
|
|
38
|
+
value,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const READINESS_ORDER = [
|
|
42
|
+
"missing",
|
|
43
|
+
"imputed",
|
|
44
|
+
"non-finite",
|
|
45
|
+
"structural-ambiguity",
|
|
46
|
+
"aggregation-overflow",
|
|
47
|
+
"expression-overflow",
|
|
48
|
+
"tolerance-overflow",
|
|
49
|
+
];
|
|
50
|
+
function readiness(values) {
|
|
51
|
+
return READINESS_ORDER.filter((value) => values.includes(value));
|
|
52
|
+
}
|
|
53
|
+
function expressionMeasureIds(expression) {
|
|
54
|
+
if (expression.op === "measure")
|
|
55
|
+
return [expression.measureId];
|
|
56
|
+
return [
|
|
57
|
+
...new Set((expression.op === "add"
|
|
58
|
+
? expression.terms
|
|
59
|
+
: [expression.left, expression.right]).flatMap(expressionMeasureIds)),
|
|
60
|
+
].sort(codeUnit);
|
|
61
|
+
}
|
|
62
|
+
function unionSources(values) {
|
|
63
|
+
return normalizeDiagnosticSourceLocations(values);
|
|
64
|
+
}
|
|
65
|
+
function mergeFindings(values) {
|
|
66
|
+
const merged = new Map();
|
|
67
|
+
for (const value of values) {
|
|
68
|
+
const key = canonicalJson({ ...value, sources: [] });
|
|
69
|
+
const previous = merged.get(key);
|
|
70
|
+
const sources = normalizeDiagnosticSourceLocations([
|
|
71
|
+
...(previous?.sources ?? []),
|
|
72
|
+
...value.sources,
|
|
73
|
+
]);
|
|
74
|
+
merged.set(key, { ...(previous ?? value), sources });
|
|
75
|
+
}
|
|
76
|
+
return [...merged.values()].sort(compareDiagnosticFindings);
|
|
77
|
+
}
|
|
11
78
|
export function validateDiagnosticGroupingConfiguration(input) {
|
|
12
79
|
assertPreparedDiagnosticData(input.prepared);
|
|
13
|
-
const
|
|
80
|
+
const boundaryIssues = [];
|
|
81
|
+
for (const [value, path] of [
|
|
82
|
+
[input.groupMap, "$.groupMap"],
|
|
83
|
+
[input.groupDimensions, "$.groupDimensions"],
|
|
84
|
+
]) {
|
|
85
|
+
if (value === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
if (!isDiagnosticPlainRecord(value))
|
|
88
|
+
boundaryIssues.push({
|
|
89
|
+
domain: "configuration",
|
|
90
|
+
code: "invalid-type",
|
|
91
|
+
path,
|
|
92
|
+
message: "Grouping configuration must be a plain object",
|
|
93
|
+
});
|
|
94
|
+
else
|
|
95
|
+
boundaryIssues.push(...diagnosticJsonPreflight(value, "configuration").map((issue) => ({
|
|
96
|
+
...issue,
|
|
97
|
+
path: issue.path === "$" ? path : `${path}${issue.path.slice(1)}`,
|
|
98
|
+
})));
|
|
99
|
+
}
|
|
100
|
+
if (boundaryIssues.length > 0)
|
|
101
|
+
throw new DiagnosticValidationError(boundaryIssues);
|
|
102
|
+
const auditedSourceGroups = input.prepared.inputAudit.flatMap((item) => {
|
|
103
|
+
return input.prepared.filter?.sourceGroups === undefined ||
|
|
104
|
+
input.prepared.filter.sourceGroups.includes(item.record.sourceGroup)
|
|
105
|
+
? [item.record.sourceGroup]
|
|
106
|
+
: [];
|
|
107
|
+
});
|
|
108
|
+
const sourceGroups = new Set([
|
|
109
|
+
...auditedSourceGroups,
|
|
110
|
+
...input.prepared.cells.map((cell) => cell.sourceGroup),
|
|
111
|
+
]);
|
|
14
112
|
for (const [source, target] of Object.entries(input.groupMap ?? {})) {
|
|
15
113
|
if (!sourceGroups.has(source))
|
|
16
|
-
throw new DiagnosticValidationError([
|
|
17
|
-
|
|
18
|
-
|
|
114
|
+
throw new DiagnosticValidationError([
|
|
115
|
+
{
|
|
116
|
+
domain: "configuration",
|
|
117
|
+
code: "invalid-configuration",
|
|
118
|
+
path: `$.groupMap[${JSON.stringify(source)}]`,
|
|
119
|
+
message: "Group map contains an unused source group",
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
if (!isDiagnosticToken(source))
|
|
123
|
+
throw new DiagnosticValidationError([
|
|
124
|
+
{
|
|
125
|
+
domain: "configuration",
|
|
126
|
+
code: "invalid-string",
|
|
127
|
+
path: `$.groupMap[${JSON.stringify(source)}]`,
|
|
128
|
+
message: "Source group must be a nonempty token",
|
|
129
|
+
},
|
|
130
|
+
]);
|
|
131
|
+
if (!isDiagnosticToken(target))
|
|
132
|
+
throw new DiagnosticValidationError([
|
|
133
|
+
{
|
|
134
|
+
domain: "configuration",
|
|
135
|
+
code: "invalid-string",
|
|
136
|
+
path: `$.groupMap[${JSON.stringify(source)}]`,
|
|
137
|
+
message: "Output group must be a nonempty token",
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
19
140
|
}
|
|
20
|
-
const outputGroups = new Set([...sourceGroups].map((source) => input.groupMap
|
|
141
|
+
const outputGroups = new Set([...sourceGroups].map((source) => input.groupMap !== undefined && hasDiagnosticOwn(input.groupMap, source)
|
|
142
|
+
? input.groupMap[source]
|
|
143
|
+
: source));
|
|
21
144
|
for (const group of Object.keys(input.groupDimensions ?? {}))
|
|
22
145
|
if (!outputGroups.has(group))
|
|
23
|
-
throw new DiagnosticValidationError([
|
|
146
|
+
throw new DiagnosticValidationError([
|
|
147
|
+
{
|
|
148
|
+
domain: "configuration",
|
|
149
|
+
code: "invalid-configuration",
|
|
150
|
+
path: `$.groupDimensions[${JSON.stringify(group)}]`,
|
|
151
|
+
message: "Dimensions contain an unused output group",
|
|
152
|
+
},
|
|
153
|
+
]);
|
|
24
154
|
for (const group of input.prepared.filter?.outputGroups ?? [])
|
|
25
155
|
if (!outputGroups.has(group))
|
|
26
|
-
throw new DiagnosticValidationError([
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
156
|
+
throw new DiagnosticValidationError([
|
|
157
|
+
{
|
|
158
|
+
domain: "configuration",
|
|
159
|
+
code: "unknown-reference",
|
|
160
|
+
path: "$.prepared.filter.outputGroups",
|
|
161
|
+
message: `Unknown output group ${group}`,
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
165
|
+
function mergeStats(cells, measure) {
|
|
166
|
+
const contributions = cells.flatMap((cell) => cell.contributions[measure.id] ?? []);
|
|
167
|
+
const blockers = [
|
|
168
|
+
...new Map(cells
|
|
169
|
+
.flatMap((cell) => cell.structuralBlockers[measure.id] ?? [])
|
|
170
|
+
.map((blocker) => [canonicalJson(blocker), blocker])).entries(),
|
|
171
|
+
]
|
|
172
|
+
.sort(([left], [right]) => codeUnit(left, right))
|
|
173
|
+
.map(([, blocker]) => blocker);
|
|
174
|
+
return finalizeDiagnosticContributions(contributions, measure.missing, blockers);
|
|
37
175
|
}
|
|
38
176
|
function inferExpressionMeasure(expression, measures) {
|
|
39
|
-
const id = expression.op === "measure"
|
|
177
|
+
const id = expression.op === "measure"
|
|
178
|
+
? expression.measureId
|
|
179
|
+
: expression.op === "add"
|
|
180
|
+
? inferExpressionMeasure(expression.terms[0], measures).id
|
|
181
|
+
: inferExpressionMeasure(expression.left, measures).id;
|
|
40
182
|
return measures.get(id);
|
|
41
183
|
}
|
|
42
|
-
function
|
|
184
|
+
function inferRoleExpressionMeasure(expression, bindings, measures) {
|
|
185
|
+
const role = expression.op === "role"
|
|
186
|
+
? expression.role
|
|
187
|
+
: inferRoleExpressionMeasure(expression.op === "add" ? expression.terms[0] : expression.left, bindings, measures).id;
|
|
188
|
+
if (expression.op !== "role")
|
|
189
|
+
return measures.get(role);
|
|
190
|
+
return inferExpressionMeasure(bindings[role], measures);
|
|
191
|
+
}
|
|
192
|
+
function evaluatePoint(prepared, components, instanceId, context, preparedFindings, blockerFindingsByMeasure, sourcesByMeasure) {
|
|
43
193
|
const definition = prepared.definition;
|
|
44
194
|
const internals = getCompiledDiagnosticDefinitionInternals(definition);
|
|
45
195
|
const instance = definition.definition.instances.find((item) => item.id === instanceId);
|
|
46
196
|
const formula = definition.definition.formulas.find((item) => item.id === instance.formulaId);
|
|
197
|
+
const instanceIndex = definition.definition.instances.findIndex((item) => item.id === instance.id);
|
|
198
|
+
const formulaIndex = definition.definition.formulas.findIndex((item) => item.id === formula.id);
|
|
47
199
|
const measureStates = Object.create(null);
|
|
48
200
|
for (const [measureId, stats] of Object.entries(components)) {
|
|
49
201
|
const measure = internals.measuresById.get(measureId);
|
|
50
|
-
const readiness = [
|
|
51
|
-
|
|
202
|
+
const readiness = [
|
|
203
|
+
...(stats.missing > 0
|
|
204
|
+
? [stats.imputedZero > 0 ? "imputed" : "missing"]
|
|
205
|
+
: []),
|
|
206
|
+
...(stats.nonFinite > 0 ? ["non-finite"] : []),
|
|
207
|
+
...(stats.structural > 0 ? ["structural-ambiguity"] : []),
|
|
208
|
+
...(stats.sum === null && stats.nonFinite === 0 && stats.structural === 0
|
|
209
|
+
? ["aggregation-overflow"]
|
|
210
|
+
: []),
|
|
211
|
+
];
|
|
212
|
+
measureStates[measureId] = {
|
|
213
|
+
quantity: quantity(measure, stats.value),
|
|
214
|
+
stats,
|
|
215
|
+
readiness,
|
|
216
|
+
sources: sourcesByMeasure[measureId] ?? [],
|
|
217
|
+
};
|
|
52
218
|
}
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
219
|
+
const expressionSources = (expression) => unionSources(expressionMeasureIds(expression).flatMap((measureId) => sourcesByMeasure[measureId] ?? []));
|
|
220
|
+
const withOverflowSources = (result, sources) => ({
|
|
221
|
+
...result,
|
|
222
|
+
overflows: result.overflows.map((overflow) => ({
|
|
223
|
+
...overflow,
|
|
224
|
+
sources: overflow.sources.length > 0 ? overflow.sources : sources,
|
|
225
|
+
})),
|
|
226
|
+
});
|
|
227
|
+
const bindings = Object.fromEntries(Object.entries(instance.bindings).map(([role, expression]) => {
|
|
228
|
+
const sources = expressionSources(expression);
|
|
229
|
+
const path = pointer(pointer(pointer(pointer("", "instances"), instanceIndex), "bindings"), role);
|
|
230
|
+
return [
|
|
231
|
+
role,
|
|
232
|
+
withOverflowSources(evaluateDiagnosticMeasureExpression(expression, measureStates, path), sources),
|
|
233
|
+
];
|
|
234
|
+
}));
|
|
235
|
+
const calculationSources = unionSources((internals.calculationDependenciesByInstanceId.get(instance.id) ?? []).flatMap((measureId) => sourcesByMeasure[measureId] ?? []));
|
|
236
|
+
const roleExpressionSources = (expression) => {
|
|
237
|
+
if (expression.op === "role")
|
|
238
|
+
return expressionSources(instance.bindings[expression.role]);
|
|
239
|
+
return unionSources((expression.op === "add"
|
|
240
|
+
? expression.terms
|
|
241
|
+
: [expression.left, expression.right]).flatMap(roleExpressionSources));
|
|
242
|
+
};
|
|
243
|
+
const numeratorSources = roleExpressionSources(formula.numerator);
|
|
244
|
+
const denominatorSources = roleExpressionSources(formula.denominator);
|
|
245
|
+
const formulaPath = pointer(pointer("", "formulas"), formulaIndex);
|
|
246
|
+
const numeratorResult = withOverflowSources(evaluateDiagnosticRoleExpression(formula.numerator, bindings, pointer(formulaPath, "numerator")), numeratorSources);
|
|
247
|
+
const denominatorResult = withOverflowSources(evaluateDiagnosticRoleExpression(formula.denominator, bindings, pointer(formulaPath, "denominator")), denominatorSources);
|
|
56
248
|
const raw = diagnosticRawRatio(numeratorResult.value, denominatorResult.value);
|
|
57
249
|
const presented = applyDiagnosticPresentation(raw, instance.presentation);
|
|
58
|
-
const numeratorMeasure =
|
|
59
|
-
const denominatorMeasure =
|
|
250
|
+
const numeratorMeasure = inferRoleExpressionMeasure(formula.numerator, instance.bindings, internals.measuresById);
|
|
251
|
+
const denominatorMeasure = inferRoleExpressionMeasure(formula.denominator, instance.bindings, internals.measuresById);
|
|
60
252
|
const ruleEvaluations = [];
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
253
|
+
const deps = internals.evaluationDependenciesByInstanceId.get(instance.id) ?? [];
|
|
254
|
+
const findingContext = {
|
|
255
|
+
instanceId: instance.id,
|
|
256
|
+
...context,
|
|
257
|
+
sources: calculationSources,
|
|
258
|
+
};
|
|
259
|
+
const findings = mergeFindings([
|
|
260
|
+
...preparedFindings.filter((finding) => finding.category !== "structural" &&
|
|
261
|
+
(finding.measureId === undefined || deps.includes(finding.measureId))),
|
|
262
|
+
...deps.flatMap((measureId) => blockerFindingsByMeasure[measureId] ?? []),
|
|
263
|
+
]);
|
|
264
|
+
for (const overflow of [
|
|
265
|
+
...numeratorResult.overflows,
|
|
266
|
+
...denominatorResult.overflows,
|
|
267
|
+
])
|
|
268
|
+
findings.push({
|
|
269
|
+
code: "diagnostic-expression-overflow",
|
|
270
|
+
message: "Measure expression overflowed",
|
|
271
|
+
severity: "fail",
|
|
272
|
+
category: "aggregation",
|
|
273
|
+
expressionPath: overflow.expressionPath,
|
|
274
|
+
...findingContext,
|
|
275
|
+
sources: overflow.sources,
|
|
276
|
+
});
|
|
277
|
+
if (numeratorResult.value === null)
|
|
278
|
+
findings.push({
|
|
279
|
+
code: "diagnostic-numerator-unavailable",
|
|
280
|
+
message: "Calculation numerator is unavailable",
|
|
281
|
+
severity: "warning",
|
|
282
|
+
category: "calculation",
|
|
283
|
+
...findingContext,
|
|
284
|
+
});
|
|
285
|
+
if (denominatorResult.value === null)
|
|
286
|
+
findings.push({
|
|
287
|
+
code: "diagnostic-denominator-unavailable",
|
|
288
|
+
message: "Calculation denominator is unavailable",
|
|
289
|
+
severity: "warning",
|
|
290
|
+
category: "calculation",
|
|
291
|
+
...findingContext,
|
|
292
|
+
});
|
|
293
|
+
else if (denominatorResult.value <= 0)
|
|
294
|
+
findings.push({
|
|
295
|
+
code: "diagnostic-denominator-not-positive",
|
|
296
|
+
message: "Calculation denominator is not strictly positive",
|
|
297
|
+
severity: "warning",
|
|
298
|
+
category: "calculation",
|
|
299
|
+
...findingContext,
|
|
300
|
+
});
|
|
301
|
+
else if (numeratorResult.value !== null && raw === null)
|
|
302
|
+
findings.push({
|
|
303
|
+
code: "diagnostic-calculation-overflow",
|
|
304
|
+
message: "Calculation result overflowed",
|
|
305
|
+
severity: "fail",
|
|
306
|
+
category: "calculation",
|
|
307
|
+
...findingContext,
|
|
308
|
+
});
|
|
309
|
+
if (presented.finding)
|
|
310
|
+
findings.push({ ...presented.finding, ...findingContext });
|
|
311
|
+
const operand = (item, path) => {
|
|
312
|
+
if (item.source === "constant")
|
|
313
|
+
return { value: item.value, reasons: [], overflows: [], sources: [] };
|
|
314
|
+
if (item.source === "calculation") {
|
|
315
|
+
const result = item.field === "numerator" ? numeratorResult : denominatorResult;
|
|
316
|
+
return {
|
|
317
|
+
...result,
|
|
318
|
+
sources: item.field === "numerator" ? numeratorSources : denominatorSources,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const sources = expressionSources(item.expression);
|
|
322
|
+
return {
|
|
323
|
+
...withOverflowSources(evaluateDiagnosticMeasureExpression(item.expression, measureStates, pointer(path, "expression")), sources),
|
|
324
|
+
sources,
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
for (const [ruleIndex, rule] of instance.rules.entries()) {
|
|
328
|
+
const rulePath = pointer(pointer(pointer(pointer(pointer("", "instances"), instanceIndex), "rules"), ruleIndex), "when");
|
|
329
|
+
const left = operand(rule.when.left, pointer(rulePath, "left"));
|
|
330
|
+
const right = operand(rule.when.right, pointer(rulePath, "right"));
|
|
331
|
+
const ruleSources = unionSources([...left.sources, ...right.sources]);
|
|
332
|
+
const expressionOverflows = [
|
|
333
|
+
...new Map([...left.overflows, ...right.overflows].map((overflow) => [
|
|
334
|
+
canonicalJson(overflow),
|
|
335
|
+
overflow,
|
|
336
|
+
])).entries(),
|
|
337
|
+
]
|
|
338
|
+
.sort(([a], [b]) => codeUnit(a, b))
|
|
339
|
+
.map(([, overflow]) => overflow);
|
|
340
|
+
const reasons = readiness([
|
|
341
|
+
...left.reasons,
|
|
342
|
+
...right.reasons,
|
|
343
|
+
...(expressionOverflows.length > 0
|
|
344
|
+
? ["expression-overflow"]
|
|
345
|
+
: []),
|
|
346
|
+
]);
|
|
347
|
+
const classified = reasons.length > 0
|
|
348
|
+
? null
|
|
349
|
+
: classifyDiagnosticComparison(left.value, right.value, rule.when.tolerance);
|
|
350
|
+
if (classified === null || classified.status === "not-evaluated") {
|
|
351
|
+
const notEvaluatedReasons = readiness([
|
|
352
|
+
...reasons,
|
|
353
|
+
...(classified?.status === "not-evaluated" ? [classified.reason] : []),
|
|
354
|
+
...((left.value === null || right.value === null) &&
|
|
355
|
+
reasons.length === 0
|
|
356
|
+
? ["missing"]
|
|
357
|
+
: []),
|
|
358
|
+
]);
|
|
359
|
+
ruleEvaluations.push({
|
|
360
|
+
ruleId: rule.id,
|
|
361
|
+
status: "not-evaluated",
|
|
362
|
+
severity: rule.severity,
|
|
363
|
+
left: left.value,
|
|
364
|
+
right: right.value,
|
|
365
|
+
relation: null,
|
|
366
|
+
notEvaluatedReasons,
|
|
367
|
+
expressionOverflows,
|
|
368
|
+
code: "diagnostic-rule-not-evaluated",
|
|
369
|
+
message: "Diagnostic metric rule was not evaluated",
|
|
370
|
+
});
|
|
371
|
+
for (const overflow of expressionOverflows)
|
|
372
|
+
findings.push({
|
|
373
|
+
code: "diagnostic-expression-overflow",
|
|
374
|
+
message: "Measure expression overflowed",
|
|
375
|
+
severity: "fail",
|
|
376
|
+
category: "aggregation",
|
|
377
|
+
ruleId: rule.id,
|
|
378
|
+
expressionPath: overflow.expressionPath,
|
|
379
|
+
...findingContext,
|
|
380
|
+
sources: overflow.sources,
|
|
381
|
+
});
|
|
382
|
+
findings.push({
|
|
383
|
+
code: "diagnostic-rule-not-evaluated",
|
|
384
|
+
message: "Diagnostic metric rule was not evaluated",
|
|
385
|
+
severity: "info",
|
|
386
|
+
category: "rule",
|
|
387
|
+
ruleId: rule.id,
|
|
388
|
+
...findingContext,
|
|
389
|
+
sources: ruleSources,
|
|
390
|
+
});
|
|
391
|
+
continue;
|
|
74
392
|
}
|
|
393
|
+
const triggered = diagnosticPredicateMatches(rule.when.operator, classified.relation);
|
|
394
|
+
ruleEvaluations.push({
|
|
395
|
+
ruleId: rule.id,
|
|
396
|
+
status: triggered ? "triggered" : "pass",
|
|
397
|
+
severity: rule.severity,
|
|
398
|
+
left: left.value,
|
|
399
|
+
right: right.value,
|
|
400
|
+
relation: classified.relation,
|
|
401
|
+
notEvaluatedReasons: [],
|
|
402
|
+
expressionOverflows: [],
|
|
403
|
+
code: triggered ? rule.code : null,
|
|
404
|
+
message: triggered ? rule.message : null,
|
|
405
|
+
});
|
|
406
|
+
if (triggered)
|
|
407
|
+
findings.push({
|
|
408
|
+
code: rule.code,
|
|
409
|
+
message: rule.message,
|
|
410
|
+
severity: rule.severity,
|
|
411
|
+
category: "rule",
|
|
412
|
+
ruleId: rule.id,
|
|
413
|
+
...findingContext,
|
|
414
|
+
sources: ruleSources,
|
|
415
|
+
});
|
|
75
416
|
}
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
417
|
+
const refs = {
|
|
418
|
+
amountBasisIds: [
|
|
419
|
+
...new Set(deps.flatMap((id) => internals.measuresById.get(id)?.basisId
|
|
420
|
+
? [internals.measuresById.get(id).basisId]
|
|
421
|
+
: [])),
|
|
422
|
+
].sort(codeUnit),
|
|
423
|
+
countPopulationIds: [
|
|
424
|
+
...new Set(deps.flatMap((id) => internals.measuresById.get(id)?.countPopulationId
|
|
425
|
+
? [internals.measuresById.get(id).countPopulationId]
|
|
426
|
+
: [])),
|
|
427
|
+
].sort(codeUnit),
|
|
428
|
+
exposureBasisIds: [
|
|
429
|
+
...new Set(deps.flatMap((id) => internals.measuresById.get(id)?.exposureBasisId
|
|
430
|
+
? [internals.measuresById.get(id).exposureBasisId]
|
|
431
|
+
: [])),
|
|
432
|
+
].sort(codeUnit),
|
|
433
|
+
};
|
|
434
|
+
return deepFreeze({
|
|
435
|
+
instanceId: instance.id,
|
|
436
|
+
instanceVersion: instance.version,
|
|
437
|
+
formulaId: formula.id,
|
|
438
|
+
formulaVersion: formula.version,
|
|
439
|
+
semanticReferences: refs,
|
|
440
|
+
formulaFingerprint: definition.formulaFingerprints[formula.id],
|
|
441
|
+
calculationFingerprint: definition.calculationFingerprints[instance.id],
|
|
442
|
+
definitionIntegrity: definition.definitionIntegrity,
|
|
443
|
+
calculation: {
|
|
444
|
+
numerator: quantity(numeratorMeasure, numeratorResult.value),
|
|
445
|
+
denominator: quantity(denominatorMeasure, denominatorResult.value),
|
|
446
|
+
value: raw,
|
|
447
|
+
},
|
|
448
|
+
presentation: { ...instance.presentation, value: presented.value },
|
|
449
|
+
components: Object.fromEntries(deps.map((id) => [id, components[id]])),
|
|
450
|
+
rules: ruleEvaluations,
|
|
451
|
+
findings: mergeFindings(findings),
|
|
452
|
+
});
|
|
79
453
|
}
|
|
80
454
|
export function runMetricDiagnostics(input) {
|
|
81
455
|
validateDiagnosticGroupingConfiguration(input);
|
|
82
456
|
const prepared = input.prepared;
|
|
83
457
|
const buckets = new Map();
|
|
84
458
|
for (const cell of prepared.cells) {
|
|
85
|
-
const group = input.groupMap
|
|
86
|
-
|
|
459
|
+
const group = input.groupMap !== undefined &&
|
|
460
|
+
hasDiagnosticOwn(input.groupMap, cell.sourceGroup)
|
|
461
|
+
? input.groupMap[cell.sourceGroup]
|
|
462
|
+
: cell.sourceGroup;
|
|
463
|
+
if (prepared.filter?.outputGroups !== undefined &&
|
|
464
|
+
!prepared.filter.outputGroups.includes(group))
|
|
87
465
|
continue;
|
|
88
|
-
const key =
|
|
466
|
+
const key = canonicalJson([group, cell.origin, cell.valuation]);
|
|
89
467
|
buckets.set(key, [...(buckets.get(key) ?? []), cell]);
|
|
90
468
|
}
|
|
91
|
-
const selectedInstances = prepared.definition.definition.instances.filter((instance) => prepared.filter?.instanceIds === undefined ||
|
|
469
|
+
const selectedInstances = prepared.definition.definition.instances.filter((instance) => prepared.filter?.instanceIds === undefined ||
|
|
470
|
+
prepared.filter.instanceIds.includes(instance.id));
|
|
92
471
|
const emergence = [];
|
|
93
472
|
for (const [key, cells] of buckets) {
|
|
94
|
-
const [group, origin, valuation] =
|
|
95
|
-
const components = Object.fromEntries(prepared.definition.definition.measures.map((measure) => [
|
|
96
|
-
|
|
97
|
-
|
|
473
|
+
const [group, origin, valuation] = JSON.parse(key);
|
|
474
|
+
const components = Object.fromEntries(prepared.definition.definition.measures.map((measure) => [
|
|
475
|
+
measure.id,
|
|
476
|
+
mergeStats(cells, measure),
|
|
477
|
+
]));
|
|
478
|
+
const sourcesByMeasure = Object.fromEntries(prepared.definition.definition.measures.map((measure) => [
|
|
479
|
+
measure.id,
|
|
480
|
+
unionSources(cells.flatMap((cell) => [
|
|
481
|
+
...(cell.contributions[measure.id] ?? []).flatMap((item) => item.sources),
|
|
482
|
+
...(cell.structuralBlockers[measure.id] ?? []).flatMap((item) => item.sources),
|
|
483
|
+
])),
|
|
484
|
+
]));
|
|
485
|
+
const context = {
|
|
486
|
+
group,
|
|
487
|
+
origin,
|
|
488
|
+
valuation,
|
|
489
|
+
developmentAge: cells[0].developmentAge,
|
|
490
|
+
ageUnit: cells[0].ageUnit,
|
|
491
|
+
};
|
|
492
|
+
const mappedOverflowFindings = prepared.definition.definition.measures.flatMap((measure) => {
|
|
493
|
+
const stats = components[measure.id];
|
|
494
|
+
return stats.sum === null &&
|
|
495
|
+
stats.nonFinite === 0 &&
|
|
496
|
+
stats.structural === 0
|
|
497
|
+
? [
|
|
498
|
+
{
|
|
499
|
+
code: "diagnostic-measure-overflow",
|
|
500
|
+
message: "Measure aggregation overflowed",
|
|
501
|
+
severity: "fail",
|
|
502
|
+
category: "aggregation",
|
|
503
|
+
measureId: measure.id,
|
|
504
|
+
...context,
|
|
505
|
+
sources: sourcesByMeasure[measure.id] ?? [],
|
|
506
|
+
},
|
|
507
|
+
]
|
|
508
|
+
: [];
|
|
509
|
+
});
|
|
510
|
+
const mappedDeduplicationFindings = prepared.definition.definition.measures.flatMap((measure) => {
|
|
511
|
+
const stats = components[measure.id];
|
|
512
|
+
return stats.deduplicated > 0
|
|
513
|
+
? [
|
|
514
|
+
{
|
|
515
|
+
code: "diagnostic-exposure-deduplicated",
|
|
516
|
+
message: `${stats.deduplicated} repeated exposure observation(s) were counted once by stable key`,
|
|
517
|
+
severity: "info",
|
|
518
|
+
category: "aggregation",
|
|
519
|
+
measureId: measure.id,
|
|
520
|
+
...context,
|
|
521
|
+
sources: sourcesByMeasure[measure.id] ?? [],
|
|
522
|
+
},
|
|
523
|
+
]
|
|
524
|
+
: [];
|
|
525
|
+
});
|
|
526
|
+
const mappedFindings = mergeFindings([
|
|
527
|
+
...cells.flatMap((cell) => cell.findings.map((finding) => ({ ...finding, group }))),
|
|
528
|
+
...mappedOverflowFindings,
|
|
529
|
+
...mappedDeduplicationFindings,
|
|
530
|
+
]);
|
|
531
|
+
const blockerFindingsByMeasure = Object.fromEntries(prepared.definition.definition.measures.map((measure) => [
|
|
532
|
+
measure.id,
|
|
533
|
+
mergeFindings(cells.flatMap((cell) => (cell.structuralBlockers[measure.id] ?? []).flatMap((blocker) => blocker.finding ? [{ ...blocker.finding, group }] : []))),
|
|
534
|
+
]));
|
|
535
|
+
const metrics = Object.fromEntries(selectedInstances.map((instance) => [
|
|
536
|
+
instance.id,
|
|
537
|
+
evaluatePoint(prepared, components, instance.id, context, mappedFindings, blockerFindingsByMeasure, sourcesByMeasure),
|
|
538
|
+
]));
|
|
539
|
+
emergence.push({
|
|
540
|
+
group,
|
|
541
|
+
sourceGroups: [...new Set(cells.map((cell) => cell.sourceGroup))].sort(codeUnit),
|
|
542
|
+
...(input.groupDimensions &&
|
|
543
|
+
Object.prototype.hasOwnProperty.call(input.groupDimensions, group)
|
|
544
|
+
? { dimensions: input.groupDimensions[group] }
|
|
545
|
+
: {}),
|
|
546
|
+
origin,
|
|
547
|
+
valuation,
|
|
548
|
+
developmentAge: cells[0].developmentAge,
|
|
549
|
+
ageUnit: cells[0].ageUnit,
|
|
550
|
+
components,
|
|
551
|
+
metrics,
|
|
552
|
+
findings: mergeFindings([
|
|
553
|
+
...mappedFindings,
|
|
554
|
+
...Object.values(metrics).flatMap((metric) => metric.findings),
|
|
555
|
+
]),
|
|
556
|
+
});
|
|
98
557
|
}
|
|
99
|
-
|
|
558
|
+
const periodCoordinate = (side, label) => normalizeDiagnosticPeriod(prepared.definition, side, label).coordinate;
|
|
559
|
+
emergence.sort((a, b) => codeUnit(a.group, b.group) ||
|
|
560
|
+
periodCoordinate("origin", a.origin) -
|
|
561
|
+
periodCoordinate("origin", b.origin) ||
|
|
562
|
+
periodCoordinate("valuation", a.valuation) -
|
|
563
|
+
periodCoordinate("valuation", b.valuation) ||
|
|
564
|
+
codeUnit(a.origin, b.origin) ||
|
|
565
|
+
codeUnit(a.valuation, b.valuation));
|
|
100
566
|
const groups = [...new Set(emergence.map((point) => point.group))].sort(codeUnit);
|
|
101
567
|
const triangles = [];
|
|
102
568
|
for (const group of groups)
|
|
103
569
|
for (const instance of selectedInstances) {
|
|
104
570
|
const points = emergence.filter((point) => point.group === group);
|
|
105
|
-
const origins = [...new Set(points.map((point) => point.origin))].sort(
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
571
|
+
const origins = [...new Set(points.map((point) => point.origin))].sort((a, b) => periodCoordinate("origin", a) - periodCoordinate("origin", b) ||
|
|
572
|
+
codeUnit(a, b));
|
|
573
|
+
const ages = [
|
|
574
|
+
...new Set(points.map((point) => point.developmentAge)),
|
|
575
|
+
].sort((a, b) => a - b);
|
|
576
|
+
const cells = origins.map((origin) => ages.map((age) => {
|
|
577
|
+
const point = points.find((item) => item.origin === origin && item.developmentAge === age);
|
|
578
|
+
return point
|
|
579
|
+
? {
|
|
580
|
+
origin: point.origin,
|
|
581
|
+
valuation: point.valuation,
|
|
582
|
+
developmentAge: point.developmentAge,
|
|
583
|
+
ageUnit: point.ageUnit,
|
|
584
|
+
evaluation: point.metrics[instance.id],
|
|
585
|
+
}
|
|
586
|
+
: null;
|
|
587
|
+
}));
|
|
588
|
+
triangles.push({
|
|
589
|
+
group,
|
|
590
|
+
instanceId: instance.id,
|
|
591
|
+
origins,
|
|
592
|
+
developmentAges: ages,
|
|
593
|
+
ageUnit: prepared.definition.definition.periodAxis.ageUnit,
|
|
594
|
+
calculationValues: cells.map((row) => row.map((cell) => cell?.evaluation.calculation.value ?? null)),
|
|
595
|
+
presentationValues: cells.map((row) => row.map((cell) => cell?.evaluation.presentation.value ?? null)),
|
|
596
|
+
cells,
|
|
597
|
+
});
|
|
109
598
|
}
|
|
110
|
-
const latestDiagonal = groups.flatMap((group) => {
|
|
111
|
-
|
|
599
|
+
const latestDiagonal = groups.flatMap((group) => {
|
|
600
|
+
const groupPoints = emergence.filter((point) => point.group === group);
|
|
601
|
+
return [...new Set(groupPoints.map((point) => point.origin))].flatMap((origin) => {
|
|
602
|
+
const points = groupPoints.filter((point) => point.origin === origin);
|
|
603
|
+
return points.length
|
|
604
|
+
? [
|
|
605
|
+
points.reduce((latest, point) => point.developmentAge > latest.developmentAge ? point : latest),
|
|
606
|
+
]
|
|
607
|
+
: [];
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
const attachedPreparationFindings = new Set(prepared.cells
|
|
611
|
+
.flatMap((cell) => cell.findings)
|
|
612
|
+
.map((finding) => canonicalJson(finding)));
|
|
613
|
+
const unattachedPreparationFindings = prepared.findings.filter((finding) => !attachedPreparationFindings.has(canonicalJson(finding)));
|
|
614
|
+
return deepFreeze({
|
|
615
|
+
definitionIntegrity: prepared.definition.definitionIntegrity,
|
|
616
|
+
preparationFingerprint: prepared.preparationFingerprint,
|
|
617
|
+
ageUnit: prepared.definition.definition.periodAxis.ageUnit,
|
|
618
|
+
emergence,
|
|
619
|
+
triangles,
|
|
620
|
+
latestDiagonal,
|
|
621
|
+
findings: mergeFindings([
|
|
622
|
+
...unattachedPreparationFindings,
|
|
623
|
+
...emergence.flatMap((point) => point.findings),
|
|
624
|
+
]),
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
function viewGroups(result, outputGroups, path) {
|
|
628
|
+
if (!Array.isArray(outputGroups))
|
|
629
|
+
throw new DiagnosticValidationError([
|
|
630
|
+
{
|
|
631
|
+
domain: "view",
|
|
632
|
+
code: "invalid-type",
|
|
633
|
+
path,
|
|
634
|
+
message: "Output groups must be an array",
|
|
635
|
+
},
|
|
636
|
+
]);
|
|
637
|
+
const produced = new Set(result.emergence.map((point) => point.group));
|
|
638
|
+
const issues = [];
|
|
639
|
+
outputGroups.forEach((group, index) => {
|
|
640
|
+
const itemPath = `${path}[${index}]`;
|
|
641
|
+
if (!isDiagnosticToken(group))
|
|
642
|
+
issues.push({
|
|
643
|
+
domain: "view",
|
|
644
|
+
code: "invalid-string",
|
|
645
|
+
path: itemPath,
|
|
646
|
+
message: "Output group must be a nonempty token",
|
|
647
|
+
});
|
|
648
|
+
else if (!produced.has(group))
|
|
649
|
+
issues.push({
|
|
650
|
+
domain: "view",
|
|
651
|
+
code: "unknown-reference",
|
|
652
|
+
path: itemPath,
|
|
653
|
+
message: `Unknown output group ${group}`,
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
if (issues.length > 0)
|
|
657
|
+
throw new DiagnosticValidationError(issues);
|
|
658
|
+
return [...new Set(outputGroups)].sort(codeUnit);
|
|
659
|
+
}
|
|
660
|
+
export function sameMaturity(result, developmentAge, outputGroups) {
|
|
661
|
+
if (!Number.isSafeInteger(developmentAge) || developmentAge < 0)
|
|
662
|
+
throw new DiagnosticValidationError([
|
|
663
|
+
{
|
|
664
|
+
domain: "view",
|
|
665
|
+
code: "invalid-number",
|
|
666
|
+
path: "$.developmentAge",
|
|
667
|
+
message: "Development age must be a nonnegative safe integer",
|
|
668
|
+
},
|
|
669
|
+
]);
|
|
670
|
+
const groups = outputGroups === undefined
|
|
671
|
+
? null
|
|
672
|
+
: new Set(viewGroups(result, outputGroups, "$.outputGroups"));
|
|
673
|
+
return result.emergence.filter((point) => point.developmentAge === developmentAge &&
|
|
674
|
+
(groups === null || groups.has(point.group)));
|
|
675
|
+
}
|
|
676
|
+
export function commonMaturity(result, outputGroups) {
|
|
677
|
+
const groups = viewGroups(result, outputGroups, "$.outputGroups");
|
|
678
|
+
if (groups.length === 0)
|
|
679
|
+
return deepFreeze({
|
|
680
|
+
developmentAge: null,
|
|
681
|
+
ageUnit: result.ageUnit,
|
|
682
|
+
points: [],
|
|
683
|
+
});
|
|
684
|
+
const common = [
|
|
685
|
+
...new Set(result.emergence
|
|
686
|
+
.filter((point) => point.group === groups[0])
|
|
687
|
+
.map((point) => point.developmentAge)),
|
|
688
|
+
]
|
|
689
|
+
.filter((age) => groups.every((group) => result.emergence.some((point) => point.group === group && point.developmentAge === age)))
|
|
690
|
+
.sort((left, right) => right - left);
|
|
691
|
+
const developmentAge = common[0] ?? null;
|
|
692
|
+
return deepFreeze({
|
|
693
|
+
developmentAge,
|
|
694
|
+
ageUnit: result.ageUnit,
|
|
695
|
+
points: developmentAge === null
|
|
696
|
+
? []
|
|
697
|
+
: sameMaturity(result, developmentAge, groups),
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
export function getMetricDiagnosticsResultIdentity(result) {
|
|
701
|
+
return projectDiagnosticIdentity(result);
|
|
112
702
|
}
|
|
113
|
-
export function sameMaturity(result, developmentAge, outputGroups) { if (!Number.isSafeInteger(developmentAge) || developmentAge < 0)
|
|
114
|
-
throw new DiagnosticValidationError([{ domain: "view", code: "invalid-number", path: "$.developmentAge", message: "Development age must be a nonnegative safe integer" }]); const groups = outputGroups === undefined ? null : new Set(outputGroups); return result.emergence.filter((point) => point.developmentAge === developmentAge && (groups === null || groups.has(point.group))); }
|
|
115
|
-
export function commonMaturity(result, outputGroups) { const groups = [...new Set(outputGroups)].sort(codeUnit); const common = [...new Set(result.emergence.filter((p) => p.group === groups[0]).map((p) => p.developmentAge))].filter((age) => groups.every((group) => result.emergence.some((point) => point.group === group && point.developmentAge === age))).sort((a, b) => b - a); const developmentAge = common[0] ?? null; return deepFreeze({ developmentAge, ageUnit: result.ageUnit, points: developmentAge === null ? [] : sameMaturity(result, developmentAge, groups) }); }
|
|
116
|
-
export function getMetricDiagnosticsResultIdentity(result) { return deepFreeze(result); }
|
|
117
703
|
//# sourceMappingURL=diagnosticRunner.js.map
|