@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.
Files changed (103) hide show
  1. package/README.md +3 -3
  2. package/dist/capping.d.ts.map +1 -1
  3. package/dist/capping.js +39 -6
  4. package/dist/capping.js.map +1 -1
  5. package/dist/diagnosticAggregation.d.ts +3 -0
  6. package/dist/diagnosticAggregation.d.ts.map +1 -1
  7. package/dist/diagnosticAggregation.js +30 -7
  8. package/dist/diagnosticAggregation.js.map +1 -1
  9. package/dist/diagnosticDefinitions.d.ts +3 -0
  10. package/dist/diagnosticDefinitions.d.ts.map +1 -1
  11. package/dist/diagnosticDefinitions.js +844 -182
  12. package/dist/diagnosticDefinitions.js.map +1 -1
  13. package/dist/diagnosticDerivations.d.ts +23 -0
  14. package/dist/diagnosticDerivations.d.ts.map +1 -1
  15. package/dist/diagnosticDerivations.js +187 -43
  16. package/dist/diagnosticDerivations.js.map +1 -1
  17. package/dist/diagnosticExposure.d.ts +2 -2
  18. package/dist/diagnosticExposure.d.ts.map +1 -1
  19. package/dist/diagnosticExposure.js +228 -20
  20. package/dist/diagnosticExposure.js.map +1 -1
  21. package/dist/diagnosticExpressions.d.ts +1 -1
  22. package/dist/diagnosticExpressions.d.ts.map +1 -1
  23. package/dist/diagnosticExpressions.js +35 -15
  24. package/dist/diagnosticExpressions.js.map +1 -1
  25. package/dist/diagnosticFormulas.d.ts +3 -0
  26. package/dist/diagnosticFormulas.d.ts.map +1 -1
  27. package/dist/diagnosticFormulas.js +160 -26
  28. package/dist/diagnosticFormulas.js.map +1 -1
  29. package/dist/diagnosticIdentity.d.ts +15 -1
  30. package/dist/diagnosticIdentity.d.ts.map +1 -1
  31. package/dist/diagnosticIdentity.js +318 -51
  32. package/dist/diagnosticIdentity.js.map +1 -1
  33. package/dist/diagnosticOrdering.d.ts +8 -0
  34. package/dist/diagnosticOrdering.d.ts.map +1 -0
  35. package/dist/diagnosticOrdering.js +73 -0
  36. package/dist/diagnosticOrdering.js.map +1 -0
  37. package/dist/diagnosticPeriodAxis.d.ts +9 -0
  38. package/dist/diagnosticPeriodAxis.d.ts.map +1 -0
  39. package/dist/diagnosticPeriodAxis.js +69 -0
  40. package/dist/diagnosticPeriodAxis.js.map +1 -0
  41. package/dist/diagnosticPeriods.d.ts +2 -3
  42. package/dist/diagnosticPeriods.d.ts.map +1 -1
  43. package/dist/diagnosticPeriods.js +20 -68
  44. package/dist/diagnosticPeriods.js.map +1 -1
  45. package/dist/diagnosticPreparation.d.ts +47 -14
  46. package/dist/diagnosticPreparation.d.ts.map +1 -1
  47. package/dist/diagnosticPreparation.js +1482 -112
  48. package/dist/diagnosticPreparation.js.map +1 -1
  49. package/dist/diagnosticReview.d.ts +36 -14
  50. package/dist/diagnosticReview.d.ts.map +1 -1
  51. package/dist/diagnosticReview.js +448 -42
  52. package/dist/diagnosticReview.js.map +1 -1
  53. package/dist/diagnosticRunner.d.ts +2 -1
  54. package/dist/diagnosticRunner.d.ts.map +1 -1
  55. package/dist/diagnosticRunner.js +656 -70
  56. package/dist/diagnosticRunner.js.map +1 -1
  57. package/dist/diagnosticRuntime.d.ts +26 -0
  58. package/dist/diagnosticRuntime.d.ts.map +1 -0
  59. package/dist/diagnosticRuntime.js +243 -0
  60. package/dist/diagnosticRuntime.js.map +1 -0
  61. package/dist/diagnosticSourceOrdering.d.ts +6 -0
  62. package/dist/diagnosticSourceOrdering.d.ts.map +1 -0
  63. package/dist/diagnosticSourceOrdering.js +38 -0
  64. package/dist/diagnosticSourceOrdering.js.map +1 -0
  65. package/dist/index.d.ts +8 -4
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +5 -1
  68. package/dist/index.js.map +1 -1
  69. package/dist/mack.d.ts +4 -0
  70. package/dist/mack.d.ts.map +1 -1
  71. package/dist/mack.js +32 -4
  72. package/dist/mack.js.map +1 -1
  73. package/dist/triangle.d.ts.map +1 -1
  74. package/dist/triangle.js +59 -9
  75. package/dist/triangle.js.map +1 -1
  76. package/dist/types.d.ts +4 -0
  77. package/dist/types.d.ts.map +1 -1
  78. package/dist/types.js +22 -8
  79. package/dist/types.js.map +1 -1
  80. package/dist/version.d.ts +1 -1
  81. package/dist/version.js +1 -1
  82. package/package.json +3 -2
  83. package/src/capping.ts +109 -18
  84. package/src/diagnosticAggregation.ts +69 -20
  85. package/src/diagnosticDefinitions.ts +2675 -426
  86. package/src/diagnosticDerivations.ts +281 -49
  87. package/src/diagnosticExposure.ts +441 -50
  88. package/src/diagnosticExpressions.ts +99 -17
  89. package/src/diagnosticFormulas.ts +239 -39
  90. package/src/diagnosticIdentity.ts +691 -191
  91. package/src/diagnosticOrdering.ts +106 -0
  92. package/src/diagnosticPeriodAxis.ts +92 -0
  93. package/src/diagnosticPeriods.ts +67 -82
  94. package/src/diagnosticPreparation.ts +2369 -127
  95. package/src/diagnosticReview.ts +864 -58
  96. package/src/diagnosticRunner.ts +1076 -72
  97. package/src/diagnosticRuntime.ts +264 -0
  98. package/src/diagnosticSourceOrdering.ts +50 -0
  99. package/src/index.ts +31 -3
  100. package/src/mack.ts +72 -9
  101. package/src/triangle.ts +103 -13
  102. package/src/types.ts +55 -20
  103. package/src/version.ts +1 -1
@@ -1,117 +1,703 @@
1
1
  import { getCompiledDiagnosticDefinitionInternals } from "./diagnosticDefinitions.js";
2
- import { applyDiagnosticPresentation, diagnosticRawRatio, evaluateDiagnosticMeasureExpression, evaluateDiagnosticRoleExpression } from "./diagnosticFormulas.js";
3
- import { classifyDiagnosticComparison, diagnosticPredicateMatches } from "./diagnosticRules.js";
4
- import { assertPreparedDiagnosticData } from "./diagnosticPreparation.js";
5
- import { DiagnosticValidationError } from "./types.js";
6
- function codeUnit(a, b) { return a < b ? -1 : a > b ? 1 : 0; }
7
- function deepFreeze(value, seen = new WeakSet()) { if (value === null || typeof value !== "object" || seen.has(value))
8
- return value; seen.add(value); for (const child of Object.values(value))
9
- deepFreeze(child, seen); return Object.freeze(value); }
10
- function quantity(measure, value) { return { kind: measure.kind, unit: measure.unit, ...(measure.basisId ? { basisId: measure.basisId } : {}), ...(measure.countPopulationId ? { countPopulationId: measure.countPopulationId } : {}), ...(measure.exposureBasisId ? { exposureBasisId: measure.exposureBasisId } : {}), value }; }
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 sourceGroups = new Set(input.prepared.cells.map((cell) => cell.sourceGroup));
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([{ domain: "configuration", code: "invalid-configuration", path: `$.groupMap[${JSON.stringify(source)}]`, message: "Group map contains an unused source group" }]);
17
- if (target.length === 0 || /^[\t-\r ]|[\t-\r ]$/.test(target))
18
- throw new DiagnosticValidationError([{ domain: "configuration", code: "invalid-string", path: `$.groupMap[${JSON.stringify(source)}]`, message: "Output group must be a nonempty token" }]);
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?.[source] ?? source));
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([{ domain: "configuration", code: "invalid-configuration", path: `$.groupDimensions[${JSON.stringify(group)}]`, message: "Dimensions contain an unused output group" }]);
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([{ domain: "configuration", code: "unknown-reference", path: "$.prepared.filter.outputGroups", message: `Unknown output group ${group}` }]);
27
- }
28
- function mergeStats(stats) {
29
- if (stats.length === 0)
30
- return { value: null, sum: 0, observed: 0, missing: 0, nonFinite: 0, imputedZero: 0, deduplicated: 0, structural: 0 };
31
- const count = (key) => stats.reduce((sum, item) => sum + item[key], 0);
32
- const structural = count("structural");
33
- const nonFinite = count("nonFinite");
34
- const sums = stats.map((item) => item.sum);
35
- const sum = structural > 0 || nonFinite > 0 || sums.some((value) => value === null) ? null : sums.reduce((total, value) => total + value, 0);
36
- return { value: sum === null || stats.some((item) => item.value === null) ? null : sum, sum, observed: count("observed"), missing: count("missing"), nonFinite, imputedZero: count("imputedZero"), deduplicated: count("deduplicated"), structural };
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" ? expression.measureId : expression.op === "add" ? inferExpressionMeasure(expression.terms[0], measures).id : inferExpressionMeasure(expression.left, measures).id;
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 evaluatePoint(prepared, components, instanceId) {
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 = [...(stats.missing > 0 ? [stats.imputedZero > 0 ? "imputed" : "missing"] : []), ...(stats.nonFinite > 0 ? ["non-finite"] : []), ...(stats.structural > 0 ? ["structural-ambiguity"] : []), ...(stats.sum === null && stats.nonFinite === 0 && stats.structural === 0 ? ["aggregation-overflow"] : [])];
51
- measureStates[measureId] = { quantity: quantity(measure, stats.value), stats, readiness };
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 bindings = Object.fromEntries(Object.entries(instance.bindings).map(([role, expression]) => [role, evaluateDiagnosticMeasureExpression(expression, measureStates, `$.instances[${JSON.stringify(instance.id)}].bindings[${JSON.stringify(role)}]`)]));
54
- const numeratorResult = evaluateDiagnosticRoleExpression(formula.numerator, bindings, `$.formulas[${JSON.stringify(formula.id)}].numerator`);
55
- const denominatorResult = evaluateDiagnosticRoleExpression(formula.denominator, bindings, `$.formulas[${JSON.stringify(formula.id)}].denominator`);
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 = inferExpressionMeasure(instance.bindings[(formula.numerator.op === "role" ? formula.numerator.role : formula.numerator.op === "subtract" && formula.numerator.left.op === "role" ? formula.numerator.left.role : Object.keys(instance.bindings)[0])], internals.measuresById);
59
- const denominatorMeasure = inferExpressionMeasure(instance.bindings[(formula.denominator.op === "role" ? formula.denominator.role : Object.keys(instance.bindings)[0])], internals.measuresById);
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 findings = presented.finding ? [{ ...presented.finding, instanceId: instance.id }] : [];
62
- const operand = (item) => item.source === "constant" ? item.value : item.source === "calculation" ? (item.field === "numerator" ? numeratorResult.value : denominatorResult.value) : evaluateDiagnosticMeasureExpression(item.expression, measureStates, "$.rule").value;
63
- for (const rule of instance.rules) {
64
- const left = operand(rule.when.left);
65
- const right = operand(rule.when.right);
66
- const classified = classifyDiagnosticComparison(left, right, rule.when.tolerance);
67
- if (classified.status === "not-evaluated")
68
- ruleEvaluations.push({ ruleId: rule.id, status: "not-evaluated", severity: rule.severity, left: Number.isFinite(left) ? left : null, right: Number.isFinite(right) ? right : null, relation: null, notEvaluatedReasons: [classified.reason], expressionOverflows: [], code: "diagnostic-rule-not-evaluated", message: "Diagnostic metric rule was not evaluated" });
69
- else {
70
- const triggered = diagnosticPredicateMatches(rule.when.operator, classified.relation);
71
- ruleEvaluations.push({ ruleId: rule.id, status: triggered ? "triggered" : "pass", severity: rule.severity, left, right, relation: classified.relation, notEvaluatedReasons: [], expressionOverflows: [], code: triggered ? rule.code : null, message: triggered ? rule.message : null });
72
- if (triggered)
73
- findings.push({ code: rule.code, message: rule.message, severity: rule.severity, category: "rule", ruleId: rule.id, instanceId: instance.id, sources: [] });
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 deps = internals.evaluationDependenciesByInstanceId.get(instance.id) ?? [];
77
- const refs = { amountBasisIds: [...new Set(deps.flatMap((id) => internals.measuresById.get(id)?.basisId ? [internals.measuresById.get(id).basisId] : []))].sort(codeUnit), countPopulationIds: [...new Set(deps.flatMap((id) => internals.measuresById.get(id)?.countPopulationId ? [internals.measuresById.get(id).countPopulationId] : []))].sort(codeUnit), exposureBasisIds: [...new Set(deps.flatMap((id) => internals.measuresById.get(id)?.exposureBasisId ? [internals.measuresById.get(id).exposureBasisId] : []))].sort(codeUnit) };
78
- return deepFreeze({ instanceId: instance.id, instanceVersion: instance.version, formulaId: formula.id, formulaVersion: formula.version, semanticReferences: refs, formulaFingerprint: definition.formulaFingerprints[formula.id], calculationFingerprint: definition.calculationFingerprints[instance.id], definitionIntegrity: definition.definitionIntegrity, calculation: { numerator: quantity(numeratorMeasure, numeratorResult.value), denominator: quantity(denominatorMeasure, denominatorResult.value), value: raw }, presentation: { ...instance.presentation, value: presented.value }, components: Object.fromEntries(deps.map((id) => [id, components[id]])), rules: ruleEvaluations, findings });
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?.[cell.sourceGroup] ?? cell.sourceGroup;
86
- if (prepared.filter?.outputGroups !== undefined && !prepared.filter.outputGroups.includes(group))
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 = `${group}\u0000${cell.origin}\u0000${cell.valuation}`;
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 || prepared.filter.instanceIds.includes(instance.id));
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] = key.split("\u0000");
95
- const components = Object.fromEntries(prepared.definition.definition.measures.map((measure) => [measure.id, mergeStats(cells.map((cell) => cell.components[measure.id]))]));
96
- const metrics = Object.fromEntries(selectedInstances.map((instance) => [instance.id, evaluatePoint(prepared, components, instance.id)]));
97
- emergence.push({ group, sourceGroups: [...new Set(cells.map((cell) => cell.sourceGroup))].sort(codeUnit), ...(input.groupDimensions && Object.prototype.hasOwnProperty.call(input.groupDimensions, group) ? { dimensions: input.groupDimensions[group] } : {}), origin, valuation, developmentAge: cells[0].developmentAge, ageUnit: cells[0].ageUnit, components, metrics, findings: Object.values(metrics).flatMap((metric) => metric.findings) });
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
- emergence.sort((a, b) => codeUnit(a.group, b.group) || codeUnit(a.origin, b.origin) || a.developmentAge - b.developmentAge || codeUnit(a.valuation, b.valuation));
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(codeUnit);
106
- const ages = [...new Set(points.map((point) => point.developmentAge))].sort((a, b) => a - b);
107
- const cells = origins.map((origin) => ages.map((age) => { const point = points.find((item) => item.origin === origin && item.developmentAge === age); return point ? { origin: point.origin, valuation: point.valuation, developmentAge: point.developmentAge, ageUnit: point.ageUnit, evaluation: point.metrics[instance.id] } : null; }));
108
- triangles.push({ group, instanceId: instance.id, origins, developmentAges: ages, ageUnit: prepared.definition.definition.periodAxis.ageUnit, calculationValues: cells.map((row) => row.map((cell) => cell?.evaluation.calculation.value ?? null)), presentationValues: cells.map((row) => row.map((cell) => cell?.evaluation.presentation.value ?? null)), cells });
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) => { const groupPoints = emergence.filter((point) => point.group === group); return [...new Set(groupPoints.map((point) => point.origin))].flatMap((origin) => { const points = groupPoints.filter((point) => point.origin === origin); return points.length ? [points.reduce((latest, point) => point.developmentAge > latest.developmentAge ? point : latest)] : []; }); });
111
- return deepFreeze({ definitionIntegrity: prepared.definition.definitionIntegrity, preparationFingerprint: prepared.preparationFingerprint, ageUnit: prepared.definition.definition.periodAxis.ageUnit, emergence, triangles, latestDiagonal, findings: [...prepared.findings, ...emergence.flatMap((point) => point.findings)] });
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