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