@actuarial-ts/core 0.10.0 → 0.11.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 (87) hide show
  1. package/README.md +9 -7
  2. package/dist/canonical.d.ts.map +1 -1
  3. package/dist/canonical.js +64 -5
  4. package/dist/canonical.js.map +1 -1
  5. package/dist/customizationContracts.d.ts +10 -1
  6. package/dist/customizationContracts.d.ts.map +1 -1
  7. package/dist/descriptiveStatistics.js +1 -1
  8. package/dist/descriptiveStatistics.js.map +1 -1
  9. package/dist/diagnosticAggregation.d.ts +26 -0
  10. package/dist/diagnosticAggregation.d.ts.map +1 -1
  11. package/dist/diagnosticAggregation.js +125 -11
  12. package/dist/diagnosticAggregation.js.map +1 -1
  13. package/dist/diagnosticAuditText.d.ts +11 -0
  14. package/dist/diagnosticAuditText.d.ts.map +1 -0
  15. package/dist/diagnosticAuditText.js +179 -0
  16. package/dist/diagnosticAuditText.js.map +1 -0
  17. package/dist/diagnosticFormulas.d.ts.map +1 -1
  18. package/dist/diagnosticFormulas.js +71 -12
  19. package/dist/diagnosticFormulas.js.map +1 -1
  20. package/dist/diagnosticFreeze.d.ts +15 -1
  21. package/dist/diagnosticFreeze.d.ts.map +1 -1
  22. package/dist/diagnosticFreeze.js +82 -31
  23. package/dist/diagnosticFreeze.js.map +1 -1
  24. package/dist/diagnosticKeys.d.ts +9 -0
  25. package/dist/diagnosticKeys.d.ts.map +1 -0
  26. package/dist/diagnosticKeys.js +20 -0
  27. package/dist/diagnosticKeys.js.map +1 -0
  28. package/dist/diagnosticOrdering.d.ts.map +1 -1
  29. package/dist/diagnosticOrdering.js +9 -2
  30. package/dist/diagnosticOrdering.js.map +1 -1
  31. package/dist/diagnosticPeriods.d.ts.map +1 -1
  32. package/dist/diagnosticPeriods.js +19 -1
  33. package/dist/diagnosticPeriods.js.map +1 -1
  34. package/dist/diagnosticPreparation.d.ts +19 -0
  35. package/dist/diagnosticPreparation.d.ts.map +1 -1
  36. package/dist/diagnosticPreparation.js +544 -231
  37. package/dist/diagnosticPreparation.js.map +1 -1
  38. package/dist/diagnosticReview.d.ts.map +1 -1
  39. package/dist/diagnosticReview.js +419 -186
  40. package/dist/diagnosticReview.js.map +1 -1
  41. package/dist/diagnosticReviewSources.d.ts +6 -1
  42. package/dist/diagnosticReviewSources.d.ts.map +1 -1
  43. package/dist/diagnosticReviewSources.js +59 -14
  44. package/dist/diagnosticReviewSources.js.map +1 -1
  45. package/dist/diagnosticReviewStore.d.ts.map +1 -1
  46. package/dist/diagnosticReviewStore.js +48 -7
  47. package/dist/diagnosticReviewStore.js.map +1 -1
  48. package/dist/diagnosticRunner.d.ts.map +1 -1
  49. package/dist/diagnosticRunner.js +112 -37
  50. package/dist/diagnosticRunner.js.map +1 -1
  51. package/dist/diagnosticRuntime.d.ts.map +1 -1
  52. package/dist/diagnosticRuntime.js +11 -1
  53. package/dist/diagnosticRuntime.js.map +1 -1
  54. package/dist/diagnosticSourceOrdering.d.ts +27 -0
  55. package/dist/diagnosticSourceOrdering.d.ts.map +1 -1
  56. package/dist/diagnosticSourceOrdering.js +98 -12
  57. package/dist/diagnosticSourceOrdering.js.map +1 -1
  58. package/dist/mack.d.ts.map +1 -1
  59. package/dist/mack.js +35 -0
  60. package/dist/mack.js.map +1 -1
  61. package/dist/types.d.ts +1 -1
  62. package/dist/types.d.ts.map +1 -1
  63. package/dist/types.js +1 -0
  64. package/dist/types.js.map +1 -1
  65. package/dist/version.d.ts +1 -1
  66. package/dist/version.js +1 -1
  67. package/package.json +1 -1
  68. package/src/canonical.ts +63 -5
  69. package/src/customizationContracts.ts +10 -1
  70. package/src/descriptiveStatistics.ts +1 -1
  71. package/src/diagnosticAggregation.ts +173 -9
  72. package/src/diagnosticAuditText.ts +187 -0
  73. package/src/diagnosticFormulas.ts +77 -27
  74. package/src/diagnosticFreeze.ts +75 -30
  75. package/src/diagnosticKeys.ts +22 -0
  76. package/src/diagnosticOrdering.ts +9 -8
  77. package/src/diagnosticPeriods.ts +21 -1
  78. package/src/diagnosticPreparation.ts +624 -272
  79. package/src/diagnosticReview.ts +566 -268
  80. package/src/diagnosticReviewSources.ts +69 -18
  81. package/src/diagnosticReviewStore.ts +48 -6
  82. package/src/diagnosticRunner.ts +133 -54
  83. package/src/diagnosticRuntime.ts +11 -1
  84. package/src/diagnosticSourceOrdering.ts +115 -11
  85. package/src/mack.ts +41 -0
  86. package/src/types.ts +1 -0
  87. package/src/version.ts +1 -1
@@ -1,11 +1,12 @@
1
1
  import { canonicalJson, fnv1a64 } from "./canonical.js";
2
- import { freezeDiagnosticGraph as deepFreeze } from "./diagnosticFreeze.js";
3
- import { createDiagnosticEvidenceInterner } from "./diagnosticEvidenceIntern.js";
2
+ import { freezeDiagnosticGraph as deepFreeze, freezeOwnedDiagnosticGraph as deepFreezeOwned, } from "./diagnosticFreeze.js";
3
+ import { auditRecordCanonicalJson } from "./diagnosticAuditText.js";
4
+ import { canonicalScalarArrayJson } from "./diagnosticKeys.js";
4
5
  import { createDiagnosticIdentityArray, createDiagnosticIdentityObject, createDiagnosticIdentityValue, fingerprintDiagnosticIdentity, } from "./diagnosticIdentityStream.js";
5
6
  import { normalizeDiagnosticsFilterIdentity, projectDiagnosticIdentity, } from "./diagnosticIdentity.js";
6
7
  import { compareDiagnosticBlockers, compareDiagnosticContributions, compareDiagnosticFindings, } from "./diagnosticOrdering.js";
7
8
  import { assertCompiledDiagnosticDefinition, getCompiledDiagnosticDefinitionInternals, } from "./diagnosticDefinitions.js";
8
- import { auditedDiagnosticContribution, finalizeDiagnosticContributions, } from "./diagnosticAggregation.js";
9
+ import { auditedDiagnosticContribution, createDiagnosticStatsInterner, finalizeDiagnosticContributionsInterned, ownedDiagnosticContribution, } from "./diagnosticAggregation.js";
9
10
  import { deriveDiagnosticClaimMeasuresWithAudit, } from "./diagnosticDerivations.js";
10
11
  import { compareDiagnosticPeriods, normalizeDiagnosticPeriod, } from "./diagnosticPeriods.js";
11
12
  import { auditDiagnosticNumber, reconcileDiagnosticExposures, } from "./diagnosticExposure.js";
@@ -39,6 +40,9 @@ export function reselectCompactDiagnosticMetrics(prepared, filter) {
39
40
  return undefined;
40
41
  const selected = Object.freeze({ ...prepared, filter: deepFreeze(normalized) });
41
42
  authenticCompact.add(selected);
43
+ const recordedGroups = preparedSourceGroups.get(prepared);
44
+ if (recordedGroups !== undefined)
45
+ preparedSourceGroups.set(selected, recordedGroups);
42
46
  const parent = sourceSelectionParents.get(prepared);
43
47
  const coordinateParent = coordinateSelectionParents.get(prepared);
44
48
  if (coordinateParent)
@@ -65,7 +69,7 @@ export function selectCompactDiagnosticCoordinates(prepared, filter) {
65
69
  }
66
70
  function selectCompactSourceScope(prepared, filter, allowOrigins, allowCoordinates = false) {
67
71
  assertCompactPreparedDiagnosticData(prepared);
68
- const metadata = sourceSelectionMetadata.get(prepared);
72
+ const selection = sourceSelectionMetadata.get(prepared);
69
73
  const boundaryIssues = preparationBoundaryIssues({
70
74
  definition: prepared.definition, losses: [], exposures: [],
71
75
  ...(filter === undefined ? {} : { filter }),
@@ -73,12 +77,13 @@ function selectCompactSourceScope(prepared, filter, allowOrigins, allowCoordinat
73
77
  if (boundaryIssues.length)
74
78
  throw new DiagnosticValidationError(boundaryIssues);
75
79
  const issues = [];
76
- const normalizedFilter = normalizeFilter(prepared.definition, filter, metadata?.groups ?? new Set(prepared.inputAudit.map((item) => item.record.sourceGroup)), issues);
80
+ const normalizedFilter = normalizeFilter(prepared.definition, filter, selection?.groups ?? new Set(prepared.inputAudit.map((item) => item.record.sourceGroup)), issues);
77
81
  const normalized = normalizedFilter.value;
78
82
  if (issues.length)
79
83
  throw new DiagnosticValidationError(issues);
80
- if (!metadata)
84
+ if (!selection)
81
85
  return undefined;
86
+ const metadata = selection.load();
82
87
  if (allowOrigins && !metadata.allOriginsSelected)
83
88
  return undefined;
84
89
  if (allowCoordinates && !metadata.allCoordinatesSelected)
@@ -118,13 +123,13 @@ function selectCompactSourceScope(prepared, filter, allowOrigins, allowCoordinat
118
123
  const cells = Object.freeze(prepared.cells.filter((cell) => includes(cell.sourceGroup) && includesOrigin(cell.origin) && includesDate(cell.origin, cell.valuation)));
119
124
  const exposures = Object.freeze(prepared.exposures.filter((exposure) => exposure.status === "valid" && includes(exposure.sourceGroup) && includesOrigin(exposure.origin) && includesDate(exposure.origin, exposure.valuation)));
120
125
  const coordinateKeys = allowCoordinates ? new Set(cells.map((cell) => cellKey(cell.sourceGroup, cell.origin, cell.valuation))) : undefined;
121
- const sourceOrigins = allowCoordinates ? new Set(cells.map((cell) => canonicalJson([cell.sourceGroup, cell.origin]))) : undefined;
126
+ const sourceOrigins = allowCoordinates ? new Set(cells.map((cell) => sourceOriginKey(cell.sourceGroup, cell.origin))) : undefined;
122
127
  const internals = getCompiledDiagnosticDefinitionInternals(prepared.definition);
123
128
  const orphanFindings = allowCoordinates ? exposures.flatMap((exposure) => {
124
129
  if (exposure.status !== "valid")
125
130
  return [];
126
131
  const matches = internals.measuresById.get(exposure.measureId).exposureTiming === "origin-static"
127
- ? sourceOrigins.has(canonicalJson([exposure.sourceGroup, exposure.origin]))
132
+ ? sourceOrigins.has(sourceOriginKey(exposure.sourceGroup, exposure.origin))
128
133
  : exposure.valuation !== undefined && coordinateKeys.has(cellKey(exposure.sourceGroup, exposure.origin, exposure.valuation));
129
134
  return matches ? [] : [exposureWithoutLossFinding(exposure)];
130
135
  }) : [];
@@ -138,6 +143,9 @@ function selectCompactSourceScope(prepared, filter, allowOrigins, allowCoordinat
138
143
  expectedCells: Object.freeze(prepared.expectedCells.filter((cell) => includes(cell.sourceGroup) && includesOrigin(cell.origin) && includesDate(cell.origin, cell.valuation))),
139
144
  });
140
145
  authenticCompact.add(selected);
146
+ const knownGroups = preparedSourceGroups.get(prepared);
147
+ if (knownGroups !== undefined)
148
+ preparedSourceGroups.set(selected, knownGroups);
141
149
  if (allowCoordinates)
142
150
  coordinateSelectionParents.set(selected, prepared);
143
151
  else
@@ -158,7 +166,36 @@ const authentic = new WeakSet();
158
166
  const authenticCompact = new WeakSet();
159
167
  const sourceSelectionParents = new WeakMap();
160
168
  const coordinateSelectionParents = new WeakMap();
169
+ /**
170
+ * Selection ranks for every included/excluded audit variant are derived only
171
+ * from the preparation's own owned snapshots, so they are computed on the
172
+ * first source/origin/date query rather than for every preparation. The
173
+ * captured preparation state is released once the ranks exist.
174
+ */
175
+ class LazySelectionMetadata {
176
+ groups;
177
+ #compute;
178
+ #value;
179
+ constructor(groups, compute) {
180
+ this.groups = groups;
181
+ this.#compute = compute;
182
+ }
183
+ load() {
184
+ if (this.#value === undefined) {
185
+ this.#value = this.#compute();
186
+ this.#compute = null;
187
+ }
188
+ return this.#value;
189
+ }
190
+ }
161
191
  const sourceSelectionMetadata = new WeakMap();
192
+ // Every source group present in a preparation's input rows, hence in its audit.
193
+ // Selections keep the same audit records, so they share their parent's set.
194
+ const preparedSourceGroups = new WeakMap();
195
+ /** Source groups audited by an authentic preparation, when it recorded them. */
196
+ export function getPreparedSourceGroups(prepared) {
197
+ return preparedSourceGroups.get(prepared);
198
+ }
162
199
  const compactIdentityDocuments = new WeakMap();
163
200
  const compactFingerprints = new WeakMap();
164
201
  const identities = new WeakMap();
@@ -235,6 +272,71 @@ function own(record, key) {
235
272
  function uniqueSorted(values) {
236
273
  return [...new Set(values)].sort(codeUnit);
237
274
  }
275
+ /** Own keys in code-unit order; skips the sort when insertion order already is. */
276
+ function sortedKeys(record) {
277
+ const keys = Object.keys(record);
278
+ for (let index = 1; index < keys.length; index++)
279
+ if (keys[index - 1] > keys[index])
280
+ return keys.sort(codeUnit);
281
+ return keys;
282
+ }
283
+ /**
284
+ * Own-key copy in code-unit key order with each value mapped, built without
285
+ * entry pairs. A literal "__proto__" key stays own data, as Object.fromEntries
286
+ * would keep it.
287
+ */
288
+ function sortedRecordCopy(record, map) {
289
+ const keys = sortedKeys(record);
290
+ const out = {};
291
+ for (let index = 0; index < keys.length; index++) {
292
+ const key = keys[index];
293
+ const value = map(record[key]);
294
+ if (key === "__proto__")
295
+ Object.defineProperty(out, key, {
296
+ value,
297
+ enumerable: true,
298
+ writable: true,
299
+ configurable: true,
300
+ });
301
+ else
302
+ out[key] = value;
303
+ }
304
+ return out;
305
+ }
306
+ /** Assigns own data even for a literal "__proto__" measure id. */
307
+ function setMeasureEntry(record, id, value) {
308
+ if (id === "__proto__")
309
+ Object.defineProperty(record, id, {
310
+ value,
311
+ enumerable: true,
312
+ writable: true,
313
+ configurable: true,
314
+ });
315
+ else
316
+ record[id] = value;
317
+ }
318
+ /** A fresh empty list per measure id, in id order; a literal "__proto__" id stays own data. */
319
+ function emptyListsByMeasure(measureIds) {
320
+ const out = {};
321
+ for (let index = 0; index < measureIds.length; index++)
322
+ setMeasureEntry(out, measureIds[index], []);
323
+ return out;
324
+ }
325
+ /**
326
+ * One shared immutable empty list per measure id. A caller that needs to append
327
+ * must first replace the entry, so the shared list is never mutated; most cells
328
+ * never append at all and keep every entry pointing at it.
329
+ */
330
+ function sharedEmptyListsByMeasure(measureIds, empty) {
331
+ const out = {};
332
+ for (let index = 0; index < measureIds.length; index++)
333
+ setMeasureEntry(out, measureIds[index], empty);
334
+ return out;
335
+ }
336
+ /** Internal coordinate keys: validated tokens never contain U+0000. */
337
+ function sourceOriginKey(sourceGroup, origin) {
338
+ return `${sourceGroup}${origin}`;
339
+ }
238
340
  function normalizeSources(values) {
239
341
  return normalizeDiagnosticSourceLocations(values);
240
342
  }
@@ -293,13 +395,8 @@ function mergeFindings(values) {
293
395
  }
294
396
  return [...merged.values()].sort(compareDiagnosticFindings);
295
397
  }
296
- function snapshotLoss(row) {
297
- const measures = Object.fromEntries(Object.keys(row.measures)
298
- .sort(codeUnit)
299
- .map((measureId) => [
300
- measureId,
301
- auditDiagnosticNumber(row.measures[measureId] ?? null),
302
- ]));
398
+ function snapshotLoss(row, audited = auditDiagnosticNumber) {
399
+ const measures = sortedRecordCopy(row.measures, (value) => audited(value ?? null));
303
400
  return {
304
401
  recordId: row.recordId,
305
402
  rowType: row.rowType,
@@ -312,14 +409,14 @@ function snapshotLoss(row) {
312
409
  source: row.source === undefined ? null : { ...row.source },
313
410
  };
314
411
  }
315
- function snapshotExposure(row) {
412
+ function snapshotExposure(row, audited = auditDiagnosticNumber) {
316
413
  return {
317
414
  key: row.key,
318
415
  sourceGroup: row.sourceGroup,
319
416
  origin: row.origin,
320
417
  valuation: row.valuation ?? null,
321
418
  measureId: row.measureId,
322
- value: auditDiagnosticNumber(row.value),
419
+ value: audited(row.value),
323
420
  complete: row.complete,
324
421
  source: row.source === undefined ? null : { ...row.source },
325
422
  };
@@ -557,11 +654,9 @@ function coordinateFor(definition, originLabel, valuationLabel) {
557
654
  issue: null,
558
655
  };
559
656
  }
657
+ /** Internal cell key: validated tokens and period labels never contain U+0000. */
560
658
  function cellKey(sourceGroup, origin, valuation) {
561
- return canonicalJson([sourceGroup, origin, valuation]);
562
- }
563
- function parseCellKey(key) {
564
- return JSON.parse(key);
659
+ return `${sourceGroup}${origin}${valuation}`;
565
660
  }
566
661
  function coordinateContext(candidate) {
567
662
  if (candidate.coordinate === null)
@@ -628,7 +723,7 @@ function commonCoordinate(candidates) {
628
723
  return keys.length === 1 ? coordinateContext(candidates[0]) : {};
629
724
  }
630
725
  function exposureIdentity(candidate) {
631
- return canonicalJson(candidate.timing === "valuation-specific"
726
+ return canonicalScalarArrayJson(candidate.timing === "valuation-specific"
632
727
  ? [
633
728
  candidate.row.measureId,
634
729
  candidate.row.key,
@@ -657,33 +752,179 @@ function exposureCoordinateContext(candidates) {
657
752
  }
658
753
  return { sourceGroup: coherentSource[0], origin: coherentOrigin[0] };
659
754
  }
755
+ const AUDIT_KIND_RANK = { loss: 0, exposure: 1, "expected-cell": 2 };
756
+ const AUDIT_DISPOSITION_RANK = {
757
+ invalid: 0,
758
+ "complete-period-cutoff": 1,
759
+ filter: 2,
760
+ retained: 3,
761
+ };
660
762
  function createAuditComparator() {
661
- const rank = { loss: 0, exposure: 1, "expected-cell": 2 };
662
- const disposition = {
663
- invalid: 0,
664
- "complete-period-cutoff": 1,
665
- filter: 2,
666
- retained: 3,
667
- };
668
763
  // Audit snapshots are already owned and complete before sorting. Preserve the
669
764
  // exact canonical-text ordering, but serialize each compared record once.
670
765
  // Keep this memo local to the sort; it must not retain keys with the result.
671
- const keys = new WeakMap();
766
+ // A strong table is released with the comparator and looks up faster than a
767
+ // weak one for the millions of comparisons a large audit sort performs.
768
+ const keys = new Map();
672
769
  const key = (record) => {
673
770
  const previous = keys.get(record);
674
771
  if (previous !== undefined)
675
772
  return previous;
676
- const text = canonicalJson(record);
773
+ const text = auditRecordCanonicalJson(record);
677
774
  keys.set(record, text);
678
775
  return text;
679
776
  };
680
- return (left, right) => rank[left.kind] - rank[right.kind] ||
681
- (left.record === right.record ? 0 : codeUnit(key(left.record), key(right.record))) ||
682
- disposition[left.disposition] - disposition[right.disposition];
777
+ return {
778
+ key,
779
+ compare: (left, right) => AUDIT_KIND_RANK[left.kind] - AUDIT_KIND_RANK[right.kind] ||
780
+ (left.record === right.record ? 0 : codeUnit(key(left.record), key(right.record))) ||
781
+ AUDIT_DISPOSITION_RANK[left.disposition] - AUDIT_DISPOSITION_RANK[right.disposition],
782
+ };
783
+ }
784
+ /**
785
+ * Sorts audit records in place by the comparator's order, comparing texts that
786
+ * were resolved once per record instead of looking each up on every comparison.
787
+ * The stable sort over the decorated rows yields exactly `audit.sort(compare)`.
788
+ */
789
+ function sortAuditRecords(audit, order) {
790
+ const decorated = audit.map((item) => ({
791
+ item,
792
+ kind: AUDIT_KIND_RANK[item.kind],
793
+ disposition: AUDIT_DISPOSITION_RANK[item.disposition],
794
+ key: undefined,
795
+ }));
796
+ const keyOf = (row) => row.key ?? (row.key = order.key(row.item.record));
797
+ decorated.sort((left, right) => left.kind - right.kind ||
798
+ (left.item.record === right.item.record ? 0 : codeUnit(keyOf(left), keyOf(right))) ||
799
+ left.disposition - right.disposition);
800
+ for (let index = 0; index < decorated.length; index++)
801
+ audit[index] = decorated[index].item;
802
+ }
803
+ /**
804
+ * Every included/excluded audit variant in the comparator's order, built from
805
+ * the already sorted audit instead of re-sorting three copies of it. The audit
806
+ * holds each record once in comparator order, so when no two distinct records
807
+ * of one kind share a canonical text, the sorted variants are exactly: each
808
+ * audit record's variants in disposition order (stable for equal dispositions:
809
+ * the audit item, then its source-excluded, then its origin-excluded variant),
810
+ * with variants whose record is a re-labelled raw snapshot merged in by the
811
+ * same comparator. Any canonical-text tie falls back to the full stable sort.
812
+ * Exported for the equivalence test only.
813
+ */
814
+ export function orderAuditVariants(audit, excluded, originExcluded, order) {
815
+ const everything = () => [...audit, ...excluded.values(), ...originExcluded.values()].sort(order.compare);
816
+ for (let index = 1; index < audit.length; index++) {
817
+ const previous = audit[index - 1];
818
+ const current = audit[index];
819
+ if (previous.kind === current.kind &&
820
+ previous.record !== current.record &&
821
+ order.key(previous.record) === order.key(current.record))
822
+ return everything();
823
+ }
824
+ const auditRecords = new Set();
825
+ for (const item of audit)
826
+ auditRecords.add(item.record);
827
+ const excludedByRecord = new Map();
828
+ const detached = [];
829
+ for (const variant of excluded.values()) {
830
+ if (auditRecords.has(variant.record))
831
+ excludedByRecord.set(variant.record, variant);
832
+ else
833
+ detached.push(variant);
834
+ }
835
+ if (detached.length > 0) {
836
+ const auditKeys = new Set(audit.map((item) => `${AUDIT_KIND_RANK[item.kind]}:${order.key(item.record)}`));
837
+ for (const variant of detached)
838
+ if (auditKeys.has(`${AUDIT_KIND_RANK[variant.kind]}:${order.key(variant.record)}`))
839
+ return everything();
840
+ detached.sort(order.compare);
841
+ }
842
+ const ordered = [];
843
+ let next = 0;
844
+ for (const item of audit) {
845
+ while (next < detached.length && order.compare(detached[next], item) < 0)
846
+ ordered.push(detached[next++]);
847
+ const variants = [item];
848
+ const excludedVariant = excludedByRecord.get(item.record);
849
+ if (excludedVariant !== undefined)
850
+ variants.push(excludedVariant);
851
+ const originVariant = originExcluded.get(item.record);
852
+ if (originVariant !== undefined)
853
+ variants.push(originVariant);
854
+ variants.sort((left, right) => AUDIT_DISPOSITION_RANK[left.disposition] - AUDIT_DISPOSITION_RANK[right.disposition]);
855
+ for (const variant of variants)
856
+ ordered.push(variant);
857
+ }
858
+ while (next < detached.length)
859
+ ordered.push(detached[next++]);
860
+ return ordered;
683
861
  }
684
862
  function isPlainRecord(value) {
685
863
  return isDiagnosticPlainRecord(value);
686
864
  }
865
+ // Allowed-key sets and the exact path suffixes each row check reports under.
866
+ // Both are constants, so a clean row allocates no path text at all: every
867
+ // string below is built inside the branch that reports an issue.
868
+ const PREPARATION_INPUT_KEYS = new Set([
869
+ "definition",
870
+ "losses",
871
+ "exposures",
872
+ "filter",
873
+ "completePeriodCutoffs",
874
+ "expectedCells",
875
+ ]);
876
+ const LOSS_ROW_KEYS = new Set([
877
+ "rowType",
878
+ "recordId",
879
+ "claimId",
880
+ "sourceGroup",
881
+ "origin",
882
+ "valuation",
883
+ "complete",
884
+ "source",
885
+ "measures",
886
+ ]);
887
+ const EXPOSURE_ROW_KEYS = new Set([
888
+ "key",
889
+ "sourceGroup",
890
+ "origin",
891
+ "valuation",
892
+ "measureId",
893
+ "value",
894
+ "complete",
895
+ "source",
896
+ ]);
897
+ const SOURCE_LOCATION_KEYS = new Set([
898
+ "artifactId",
899
+ "sourceFile",
900
+ "sourceSheet",
901
+ "sourceRow",
902
+ "sourceCell",
903
+ ]);
904
+ const FILTER_KEYS = new Set([
905
+ "sourceGroups",
906
+ "outputGroups",
907
+ "origins",
908
+ "originFrom",
909
+ "originThrough",
910
+ "valuations",
911
+ "valuationFrom",
912
+ "valuationThrough",
913
+ "minDevelopmentAge",
914
+ "maxDevelopmentAge",
915
+ "instanceIds",
916
+ ]);
917
+ const CUTOFF_KEYS = new Set([
918
+ "sourceGroup",
919
+ "originThrough",
920
+ "valuationThrough",
921
+ ]);
922
+ const EXPECTED_CELL_KEYS = new Set([
923
+ "sourceGroup",
924
+ "origin",
925
+ "valuation",
926
+ "source",
927
+ ]);
687
928
  function preparationBoundaryIssues(value) {
688
929
  const issues = [];
689
930
  if (!isPlainRecord(value))
@@ -696,27 +937,65 @@ function preparationBoundaryIssues(value) {
696
937
  },
697
938
  ];
698
939
  const issue = (domain, code, path, message) => issues.push({ domain, code, path, message });
940
+ /** Row path with a constant suffix, built only when an issue cites it. */
941
+ const at = (prefix, index, suffix) => `${prefix}[${index}]${suffix}`;
699
942
  const token = (item, path, domain = "input") => {
700
943
  if (typeof item !== "string")
701
944
  issue(domain, "invalid-type", path, "Expected a string");
702
945
  else if (!isDiagnosticToken(item))
703
946
  issue(domain, "invalid-string", path, "Expected a nonempty token with valid Unicode and no U+0000");
704
947
  };
948
+ /** `token` for a row field; the path text exists only for a reported issue. */
949
+ const tokenAt = (item, prefix, index, suffix, domain = "input") => {
950
+ if (typeof item === "string" && isDiagnosticToken(item))
951
+ return;
952
+ token(item, at(prefix, index, suffix), domain);
953
+ };
705
954
  const exactKeys = (item, allowed, path, domain) => {
706
955
  for (const key of Object.keys(item)) {
707
- const keyPath = `${path}.${key}`;
708
- if (!allowed.includes(key))
709
- issue(domain, "unknown-key", keyPath, `Unknown key ${key}`);
956
+ if (!allowed.has(key))
957
+ issue(domain, "unknown-key", `${path}.${key}`, `Unknown key ${key}`);
710
958
  else if (item[key] === undefined)
711
- issue(domain, "invalid-type", keyPath, "Explicit undefined is not allowed");
959
+ issue(domain, "invalid-type", `${path}.${key}`, "Explicit undefined is not allowed");
712
960
  }
713
961
  };
962
+ /** `exactKeys` for a row, keeping the same own-key order and messages. */
963
+ const exactKeysAt = (item, allowed, prefix, index, suffix, domain) => {
964
+ for (const key in item) {
965
+ if (!Object.hasOwn(item, key))
966
+ continue;
967
+ if (!allowed.has(key))
968
+ issue(domain, "unknown-key", `${at(prefix, index, suffix)}.${key}`, `Unknown key ${key}`);
969
+ else if (item[key] === undefined)
970
+ issue(domain, "invalid-type", `${at(prefix, index, suffix)}.${key}`, "Explicit undefined is not allowed");
971
+ }
972
+ };
973
+ /** Source of a row; every reported path lies under the row's `.source`. */
974
+ const sourceAt = (item, prefix, index) => {
975
+ if (!isPlainRecord(item)) {
976
+ issue("input", "invalid-type", at(prefix, index, ".source"), "Source location must be an object");
977
+ return;
978
+ }
979
+ exactKeysAt(item, SOURCE_LOCATION_KEYS, prefix, index, ".source", "input");
980
+ tokenAt(item.artifactId, prefix, index, ".source.artifactId");
981
+ if (item.sourceFile !== undefined)
982
+ tokenAt(item.sourceFile, prefix, index, ".source.sourceFile");
983
+ if (item.sourceSheet !== undefined)
984
+ tokenAt(item.sourceSheet, prefix, index, ".source.sourceSheet");
985
+ if (item.sourceCell !== undefined)
986
+ tokenAt(item.sourceCell, prefix, index, ".source.sourceCell");
987
+ if (item.sourceRow !== undefined &&
988
+ (typeof item.sourceRow !== "number" ||
989
+ !Number.isSafeInteger(item.sourceRow) ||
990
+ item.sourceRow < 0))
991
+ issue("input", "invalid-number", at(prefix, index, ".source.sourceRow"), "Source row must be a nonnegative safe integer");
992
+ };
714
993
  const source = (item, path) => {
715
994
  if (!isPlainRecord(item)) {
716
995
  issue("input", "invalid-type", path, "Source location must be an object");
717
996
  return;
718
997
  }
719
- exactKeys(item, ["artifactId", "sourceFile", "sourceSheet", "sourceRow", "sourceCell"], path, "input");
998
+ exactKeys(item, SOURCE_LOCATION_KEYS, path, "input");
720
999
  token(item.artifactId, `${path}.artifactId`);
721
1000
  for (const key of ["sourceFile", "sourceSheet", "sourceCell"])
722
1001
  if (item[key] !== undefined)
@@ -734,101 +1013,68 @@ function preparationBoundaryIssues(value) {
734
1013
  }
735
1014
  return item;
736
1015
  };
737
- exactKeys(value, [
738
- "definition",
739
- "losses",
740
- "exposures",
741
- "filter",
742
- "completePeriodCutoffs",
743
- "expectedCells",
744
- ], "$", "configuration");
745
- for (const [index, raw] of array(value.losses, "$.losses", "input").entries()) {
746
- const path = `$.losses[${index}]`;
1016
+ exactKeys(value, PREPARATION_INPUT_KEYS, "$", "configuration");
1017
+ const losses = array(value.losses, "$.losses", "input");
1018
+ for (let index = 0; index < losses.length; index++) {
1019
+ const raw = losses[index];
747
1020
  if (!isPlainRecord(raw)) {
748
- issue("input", "invalid-type", path, "Loss record must be an object");
1021
+ issue("input", "invalid-type", at("$.losses", index, ""), "Loss record must be an object");
749
1022
  continue;
750
1023
  }
751
- exactKeys(raw, [
752
- "rowType",
753
- "recordId",
754
- "claimId",
755
- "sourceGroup",
756
- "origin",
757
- "valuation",
758
- "complete",
759
- "source",
760
- "measures",
761
- ], path, "input");
1024
+ exactKeysAt(raw, LOSS_ROW_KEYS, "$.losses", index, "", "input");
762
1025
  if (raw.rowType !== "claim" && raw.rowType !== "aggregate")
763
- issue("input", "invalid-type", `${path}.rowType`, "Loss row type must be claim or aggregate");
764
- token(raw.recordId, `${path}.recordId`);
765
- token(raw.sourceGroup, `${path}.sourceGroup`);
766
- token(raw.origin, `${path}.origin`);
767
- token(raw.valuation, `${path}.valuation`);
1026
+ issue("input", "invalid-type", at("$.losses", index, ".rowType"), "Loss row type must be claim or aggregate");
1027
+ tokenAt(raw.recordId, "$.losses", index, ".recordId");
1028
+ tokenAt(raw.sourceGroup, "$.losses", index, ".sourceGroup");
1029
+ tokenAt(raw.origin, "$.losses", index, ".origin");
1030
+ tokenAt(raw.valuation, "$.losses", index, ".valuation");
768
1031
  if (raw.rowType === "claim")
769
- token(raw.claimId, `${path}.claimId`);
1032
+ tokenAt(raw.claimId, "$.losses", index, ".claimId");
770
1033
  else if (raw.claimId !== undefined)
771
- issue("input", "unknown-key", `${path}.claimId`, "Aggregate loss rows cannot contain claimId");
1034
+ issue("input", "unknown-key", at("$.losses", index, ".claimId"), "Aggregate loss rows cannot contain claimId");
772
1035
  if (typeof raw.complete !== "boolean")
773
- issue("input", "invalid-type", `${path}.complete`, "Loss completeness must be boolean");
1036
+ issue("input", "invalid-type", at("$.losses", index, ".complete"), "Loss completeness must be boolean");
774
1037
  if (raw.source !== undefined)
775
- source(raw.source, `${path}.source`);
776
- if (!isPlainRecord(raw.measures))
777
- issue("input", "invalid-type", `${path}.measures`, "Measures must be an object");
1038
+ sourceAt(raw.source, "$.losses", index);
1039
+ const measures = raw.measures;
1040
+ if (!isPlainRecord(measures))
1041
+ issue("input", "invalid-type", at("$.losses", index, ".measures"), "Measures must be an object");
778
1042
  else
779
- for (const [measureId, amount] of Object.entries(raw.measures)) {
780
- token(measureId, `${path}.measures`);
1043
+ for (const measureId in measures) {
1044
+ if (!Object.hasOwn(measures, measureId))
1045
+ continue;
1046
+ tokenAt(measureId, "$.losses", index, ".measures");
1047
+ const amount = measures[measureId];
781
1048
  if (amount !== null && typeof amount !== "number")
782
- issue("input", "invalid-type", `${path}.measures.${measureId}`, "Measure value must be a number or null");
1049
+ issue("input", "invalid-type", `${at("$.losses", index, ".measures")}.${measureId}`, "Measure value must be a number or null");
783
1050
  }
784
1051
  }
785
- for (const [index, raw] of array(value.exposures, "$.exposures", "input").entries()) {
786
- const path = `$.exposures[${index}]`;
1052
+ const exposures = array(value.exposures, "$.exposures", "input");
1053
+ for (let index = 0; index < exposures.length; index++) {
1054
+ const raw = exposures[index];
787
1055
  if (!isPlainRecord(raw)) {
788
- issue("input", "invalid-type", path, "Exposure observation must be an object");
1056
+ issue("input", "invalid-type", at("$.exposures", index, ""), "Exposure observation must be an object");
789
1057
  continue;
790
1058
  }
791
- exactKeys(raw, [
792
- "key",
793
- "sourceGroup",
794
- "origin",
795
- "valuation",
796
- "measureId",
797
- "value",
798
- "complete",
799
- "source",
800
- ], path, "input");
801
- token(raw.key, `${path}.key`);
802
- token(raw.sourceGroup, `${path}.sourceGroup`);
803
- token(raw.origin, `${path}.origin`);
804
- token(raw.measureId, `${path}.measureId`);
1059
+ exactKeysAt(raw, EXPOSURE_ROW_KEYS, "$.exposures", index, "", "input");
1060
+ tokenAt(raw.key, "$.exposures", index, ".key");
1061
+ tokenAt(raw.sourceGroup, "$.exposures", index, ".sourceGroup");
1062
+ tokenAt(raw.origin, "$.exposures", index, ".origin");
1063
+ tokenAt(raw.measureId, "$.exposures", index, ".measureId");
805
1064
  if (raw.valuation !== undefined)
806
- token(raw.valuation, `${path}.valuation`);
1065
+ tokenAt(raw.valuation, "$.exposures", index, ".valuation");
807
1066
  if (raw.value !== null && typeof raw.value !== "number")
808
- issue("input", "invalid-type", `${path}.value`, "Exposure value must be a number or null");
1067
+ issue("input", "invalid-type", at("$.exposures", index, ".value"), "Exposure value must be a number or null");
809
1068
  if (typeof raw.complete !== "boolean")
810
- issue("input", "invalid-type", `${path}.complete`, "Exposure completeness must be boolean");
1069
+ issue("input", "invalid-type", at("$.exposures", index, ".complete"), "Exposure completeness must be boolean");
811
1070
  if (raw.source !== undefined)
812
- source(raw.source, `${path}.source`);
1071
+ sourceAt(raw.source, "$.exposures", index);
813
1072
  }
814
1073
  if (value.filter !== undefined && !isPlainRecord(value.filter))
815
1074
  issue("configuration", "invalid-type", "$.filter", "Filter must be an object");
816
1075
  if (isPlainRecord(value.filter)) {
817
1076
  const filter = value.filter;
818
- const keys = [
819
- "sourceGroups",
820
- "outputGroups",
821
- "origins",
822
- "originFrom",
823
- "originThrough",
824
- "valuations",
825
- "valuationFrom",
826
- "valuationThrough",
827
- "minDevelopmentAge",
828
- "maxDevelopmentAge",
829
- "instanceIds",
830
- ];
831
- exactKeys(filter, keys, "$.filter", "configuration");
1077
+ exactKeys(filter, FILTER_KEYS, "$.filter", "configuration");
832
1078
  for (const key of [
833
1079
  "sourceGroups",
834
1080
  "outputGroups",
@@ -854,7 +1100,7 @@ function preparationBoundaryIssues(value) {
854
1100
  issue("configuration", "invalid-type", path, "Cutoff must be an object");
855
1101
  continue;
856
1102
  }
857
- exactKeys(raw, ["sourceGroup", "originThrough", "valuationThrough"], path, "configuration");
1103
+ exactKeys(raw, CUTOFF_KEYS, path, "configuration");
858
1104
  token(raw.sourceGroup, `${path}.sourceGroup`, "configuration");
859
1105
  for (const key of ["originThrough", "valuationThrough"])
860
1106
  if (raw[key] !== null)
@@ -867,7 +1113,7 @@ function preparationBoundaryIssues(value) {
867
1113
  issue("configuration", "invalid-type", path, "Expected cell must be an object");
868
1114
  continue;
869
1115
  }
870
- exactKeys(raw, ["sourceGroup", "origin", "valuation", "source"], path, "configuration");
1116
+ exactKeys(raw, EXPECTED_CELL_KEYS, path, "configuration");
871
1117
  token(raw.sourceGroup, `${path}.sourceGroup`, "configuration");
872
1118
  token(raw.origin, `${path}.origin`, "configuration");
873
1119
  token(raw.valuation, `${path}.valuation`, "configuration");
@@ -974,17 +1220,28 @@ function prepareDiagnosticDataInMode(input, mode) {
974
1220
  const filter = normalizedFilter.value;
975
1221
  const findings = [];
976
1222
  const pendingBlockers = [];
1223
+ // Audited numbers are immutable scalar records repeated across rows; one
1224
+ // owned instance per distinct value serves every snapshot of this preparation.
1225
+ const auditedNumbers = new Map();
1226
+ const auditedNumber = (value) => {
1227
+ let audited = auditedNumbers.get(value);
1228
+ if (audited === undefined) {
1229
+ audited = auditDiagnosticNumber(value);
1230
+ auditedNumbers.set(value, audited);
1231
+ }
1232
+ return audited;
1233
+ };
1234
+ // Source clones are complete once copied; freezing them here lets the owned
1235
+ // freeze walk treat every citation list attached below as finished.
977
1236
  const lossCandidates = input.losses.map((inputRow) => ({
978
1237
  row: {
979
1238
  ...inputRow,
980
1239
  ...(inputRow.source === undefined
981
1240
  ? {}
982
- : { source: { ...inputRow.source } }),
983
- measures: Object.fromEntries(Object.keys(inputRow.measures)
984
- .sort(codeUnit)
985
- .map((key) => [key, inputRow.measures[key]])),
1241
+ : { source: Object.freeze({ ...inputRow.source }) }),
1242
+ measures: sortedRecordCopy(inputRow.measures, (value) => value),
986
1243
  },
987
- snapshot: snapshotLoss(inputRow),
1244
+ snapshot: snapshotLoss(inputRow, auditedNumber),
988
1245
  disposition: selectedBySource(filter, inputRow.sourceGroup)
989
1246
  ? "retained"
990
1247
  : "filter",
@@ -1009,8 +1266,6 @@ function prepareDiagnosticDataInMode(input, mode) {
1009
1266
  sources: candidate.row.source ? [candidate.row.source] : [],
1010
1267
  }));
1011
1268
  }
1012
- if (origin !== null)
1013
- candidate.snapshot = { ...candidate.snapshot, origin: origin.label };
1014
1269
  try {
1015
1270
  valuation = normalizeDiagnosticPeriod(definition, "valuation", candidate.row.valuation);
1016
1271
  }
@@ -1023,7 +1278,20 @@ function prepareDiagnosticDataInMode(input, mode) {
1023
1278
  sources: candidate.row.source ? [candidate.row.source] : [],
1024
1279
  }));
1025
1280
  }
1026
- if (valuation !== null)
1281
+ // One snapshot copy carries every normalized label; overriding existing
1282
+ // keys keeps the original key order exactly as successive copies did, and
1283
+ // a snapshot whose labels were canonical already stays the same record.
1284
+ const originChanged = origin !== null && candidate.snapshot.origin !== origin.label;
1285
+ const valuationChanged = valuation !== null && candidate.snapshot.valuation !== valuation.label;
1286
+ if (originChanged && valuationChanged)
1287
+ candidate.snapshot = {
1288
+ ...candidate.snapshot,
1289
+ origin: origin.label,
1290
+ valuation: valuation.label,
1291
+ };
1292
+ else if (originChanged)
1293
+ candidate.snapshot = { ...candidate.snapshot, origin: origin.label };
1294
+ else if (valuationChanged)
1027
1295
  candidate.snapshot = {
1028
1296
  ...candidate.snapshot,
1029
1297
  valuation: valuation.label,
@@ -1057,16 +1325,13 @@ function prepareDiagnosticDataInMode(input, mode) {
1057
1325
  developmentAge,
1058
1326
  ageUnit: definition.definition.periodAxis.ageUnit,
1059
1327
  };
1060
- candidate.row = {
1061
- ...candidate.row,
1062
- origin: origin.label,
1063
- valuation: valuation.label,
1064
- };
1065
- candidate.snapshot = {
1066
- ...candidate.snapshot,
1067
- origin: candidate.row.origin,
1068
- valuation: candidate.row.valuation,
1069
- };
1328
+ // Canonical labels are the common case; copy only when a label changes.
1329
+ if (candidate.row.origin !== origin.label || candidate.row.valuation !== valuation.label)
1330
+ candidate.row = {
1331
+ ...candidate.row,
1332
+ origin: origin.label,
1333
+ valuation: valuation.label,
1334
+ };
1070
1335
  if (beyondCutoff(candidate, cutoffByGroup))
1071
1336
  candidate.disposition = "complete-period-cutoff";
1072
1337
  else if (!selectedByOrigin(filter, origin, normalizedFilter.originCoordinates) ||
@@ -1081,7 +1346,7 @@ function prepareDiagnosticDataInMode(input, mode) {
1081
1346
  ? {}
1082
1347
  : { source: { ...inputRow.source } }),
1083
1348
  },
1084
- snapshot: snapshotExposure(inputRow),
1349
+ snapshot: snapshotExposure(inputRow, auditedNumber),
1085
1350
  disposition: selectedBySource(filter, inputRow.sourceGroup)
1086
1351
  ? "retained"
1087
1352
  : "filter",
@@ -1275,7 +1540,7 @@ function prepareDiagnosticDataInMode(input, mode) {
1275
1540
  const byClaim = new Map();
1276
1541
  for (const candidate of provisionalLosses) {
1277
1542
  const claimId = candidate.row.claimId;
1278
- addToMap(bySnapshot, canonicalJson([
1543
+ addToMap(bySnapshot, canonicalScalarArrayJson([
1279
1544
  claimId,
1280
1545
  candidate.row.sourceGroup,
1281
1546
  candidate.row.origin,
@@ -1289,7 +1554,7 @@ function prepareDiagnosticDataInMode(input, mode) {
1289
1554
  claimId: cohort[0].row.claimId,
1290
1555
  });
1291
1556
  for (const [claimId, cohort] of byClaim)
1292
- if (uniqueSorted(cohort.map((candidate) => canonicalJson([candidate.row.sourceGroup, candidate.row.origin]))).length > 1)
1557
+ if (uniqueSorted(cohort.map((candidate) => canonicalScalarArrayJson([candidate.row.sourceGroup, candidate.row.origin]))).length > 1)
1293
1558
  lossFindings("claim-identity-conflict", cohort, { claimId });
1294
1559
  }
1295
1560
  for (const candidate of provisionalLosses)
@@ -1332,7 +1597,7 @@ function prepareDiagnosticDataInMode(input, mode) {
1332
1597
  const invalidExposureBlockers = [];
1333
1598
  for (const exposure of reconciled) {
1334
1599
  const timing = internals.measuresById.get(exposure.measureId)?.exposureTiming;
1335
- const identity = canonicalJson(timing === "valuation-specific"
1600
+ const identity = canonicalScalarArrayJson(timing === "valuation-specific"
1336
1601
  ? [
1337
1602
  exposure.measureId,
1338
1603
  exposure.key,
@@ -1381,14 +1646,24 @@ function prepareDiagnosticDataInMode(input, mode) {
1381
1646
  item,
1382
1647
  ]));
1383
1648
  const cellRows = new Map();
1384
- for (const candidate of retainedCandidates)
1385
- addToMap(cellRows, cellKey(candidate.row.sourceGroup, candidate.row.origin, candidate.row.valuation), derivedByRecord.get(candidate.row.recordId));
1649
+ // The coordinate behind each cell key, so it is never parsed back from text.
1650
+ const cellCoordinates = new Map();
1651
+ for (const candidate of retainedCandidates) {
1652
+ const key = cellKey(candidate.row.sourceGroup, candidate.row.origin, candidate.row.valuation);
1653
+ if (!cellCoordinates.has(key))
1654
+ cellCoordinates.set(key, [
1655
+ candidate.row.sourceGroup,
1656
+ candidate.row.origin,
1657
+ candidate.row.valuation,
1658
+ ]);
1659
+ addToMap(cellRows, key, derivedByRecord.get(candidate.row.recordId));
1660
+ }
1386
1661
  // Exposure attachment is a coordinate join. Index the established cell order
1387
1662
  // once; scanning every cell for each origin-static exposure is quadratic.
1388
1663
  const cellKeysBySourceOrigin = new Map();
1389
1664
  for (const key of cellRows.keys()) {
1390
- const [sourceGroup, origin] = parseCellKey(key);
1391
- addToMap(cellKeysBySourceOrigin, canonicalJson([sourceGroup, origin]), key);
1665
+ const [sourceGroup, origin] = cellCoordinates.get(key);
1666
+ addToMap(cellKeysBySourceOrigin, sourceOriginKey(sourceGroup, origin), key);
1392
1667
  }
1393
1668
  const allMeasureIds = definition.definition.measures
1394
1669
  .map((measure) => measure.id)
@@ -1409,7 +1684,7 @@ function prepareDiagnosticDataInMode(input, mode) {
1409
1684
  if (candidate.timing === "valuation-specific" && candidate.valuation)
1410
1685
  targets.add(cellKey(candidate.row.sourceGroup, candidate.origin.label, candidate.valuation.label));
1411
1686
  else
1412
- for (const key of cellKeysBySourceOrigin.get(canonicalJson([candidate.row.sourceGroup, candidate.origin.label])) ?? [])
1687
+ for (const key of cellKeysBySourceOrigin.get(sourceOriginKey(candidate.row.sourceGroup, candidate.origin.label)) ?? [])
1413
1688
  targets.add(key);
1414
1689
  }
1415
1690
  for (const key of targets) {
@@ -1433,7 +1708,7 @@ function prepareDiagnosticDataInMode(input, mode) {
1433
1708
  ? null
1434
1709
  : cellKey(exposure.sourceGroup, exposure.origin, exposure.valuation);
1435
1710
  const keys = timing === "origin-static"
1436
- ? (cellKeysBySourceOrigin.get(canonicalJson([exposure.sourceGroup, exposure.origin])) ?? [])
1711
+ ? (cellKeysBySourceOrigin.get(sourceOriginKey(exposure.sourceGroup, exposure.origin)) ?? [])
1437
1712
  : coordinateKey !== null && cellRows.has(coordinateKey)
1438
1713
  ? [coordinateKey]
1439
1714
  : [];
@@ -1450,18 +1725,27 @@ function prepareDiagnosticDataInMode(input, mode) {
1450
1725
  // and cells. Share only freshly computed owned values, never contributions,
1451
1726
  // source identities, prepared-cell envelopes, or authenticated SDK handles.
1452
1727
  // The invocation-scoped pool is bounded and released after preparation.
1453
- const componentSharing = createDiagnosticEvidenceInterner();
1728
+ const componentSharing = createDiagnosticStatsInterner();
1729
+ // Clean cells share immutable empties; readers only iterate these lists.
1730
+ const noBlockers = Object.freeze([]);
1731
+ const noFindings = Object.freeze([]);
1454
1732
  for (const [key, rows] of cellRows) {
1455
- const [sourceGroup, origin, valuation] = parseCellKey(key);
1733
+ const coordinates = cellCoordinates.get(key);
1734
+ const sourceGroup = coordinates[0];
1735
+ const origin = coordinates[1];
1736
+ const valuation = coordinates[2];
1456
1737
  const coordinate = coordinateFor(definition, origin, valuation).coordinate;
1457
- const contributions = Object.fromEntries(allMeasureIds.map((measureId) => [
1458
- measureId,
1459
- [],
1460
- ]));
1461
- const blockers = Object.fromEntries(allMeasureIds.map((measureId) => [
1462
- measureId,
1463
- [],
1464
- ]));
1738
+ const contributions = emptyListsByMeasure(allMeasureIds);
1739
+ // Structural blockers are rare; every measure starts on the shared empty
1740
+ // list and only a measure that actually gets one allocates its own.
1741
+ const blockers = sharedEmptyListsByMeasure(allMeasureIds, noBlockers);
1742
+ const addBlocker = (measureId, blocker) => {
1743
+ const existing = blockers[measureId];
1744
+ if (existing === noBlockers)
1745
+ setMeasureEntry(blockers, measureId, [blocker]);
1746
+ else
1747
+ existing.push(blocker);
1748
+ };
1465
1749
  const cellFindings = [];
1466
1750
  const derivedContribution = (row, missingPolicy, state) => {
1467
1751
  const base = {
@@ -1482,8 +1766,12 @@ function prepareDiagnosticDataInMode(input, mode) {
1482
1766
  ? { ...base, status: "imputed-zero", value: 0 }
1483
1767
  : { ...base, status: "missing", value: null };
1484
1768
  };
1485
- for (const row of rows)
1486
- for (const measure of nonExposureMeasures) {
1769
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
1770
+ const row = rows[rowIndex];
1771
+ // One frozen citation list per row serves every measure it contributes to.
1772
+ const rowSources = Object.freeze(row.source ? [row.source] : []);
1773
+ for (let at = 0; at < nonExposureMeasures.length; at++) {
1774
+ const measure = nonExposureMeasures[at];
1487
1775
  if (measure.source === "derived") {
1488
1776
  const state = derivedAuditByRecord.get(row.recordId).derived[measure.id];
1489
1777
  contributions[measure.id].push(derivedContribution(row, measure.missing, state));
@@ -1507,8 +1795,9 @@ function prepareDiagnosticDataInMode(input, mode) {
1507
1795
  }
1508
1796
  }
1509
1797
  else
1510
- contributions[measure.id].push(auditedDiagnosticContribution(row.recordId, own(row.measures, measure.id), measure.missing, row.source ? [row.source] : []));
1798
+ contributions[measure.id].push(ownedDiagnosticContribution(row.recordId, own(row.measures, measure.id), measure.missing, rowSources));
1511
1799
  }
1800
+ }
1512
1801
  const attachedExposures = validExposuresByCell.get(key) ?? [];
1513
1802
  for (const exposure of attachedExposures)
1514
1803
  contributions[exposure.measureId].push(auditedDiagnosticContribution(exposure.key, exposure.value, "unknown", exposure.sources, exposure.deduplicated));
@@ -1522,7 +1811,7 @@ function prepareDiagnosticDataInMode(input, mode) {
1522
1811
  finding: pending.finding,
1523
1812
  };
1524
1813
  for (const measureId of pending.measureIds)
1525
- blockers[measureId].push(blocker);
1814
+ addBlocker(measureId, blocker);
1526
1815
  }
1527
1816
  for (const measure of exposureMeasures) {
1528
1817
  const attempted = attachedExposures.some((exposure) => exposure.measureId === measure.id) || invalidExposureMeasuresByCell.get(key)?.has(measure.id);
@@ -1539,7 +1828,7 @@ function prepareDiagnosticDataInMode(input, mode) {
1539
1828
  });
1540
1829
  findings.push(finding);
1541
1830
  cellFindings.push(finding);
1542
- blockers[measure.id].push({
1831
+ addBlocker(measure.id, {
1543
1832
  code: finding.code,
1544
1833
  message: finding.message,
1545
1834
  sourceIds: uniqueSorted(rows.map((row) => row.recordId)),
@@ -1549,21 +1838,29 @@ function prepareDiagnosticDataInMode(input, mode) {
1549
1838
  }
1550
1839
  }
1551
1840
  for (const measureId of allMeasureIds) {
1552
- blockers[measureId] = [
1553
- ...new Map(blockers[measureId].map((blocker) => [
1554
- canonicalJson(blocker),
1555
- blocker,
1556
- ])).values(),
1557
- ].sort(compareDiagnosticBlockers);
1558
- contributions[measureId].sort(compareDiagnosticContributions);
1841
+ const measureBlockers = blockers[measureId];
1842
+ if (measureBlockers.length > 0)
1843
+ setMeasureEntry(blockers, measureId, [
1844
+ ...new Map(measureBlockers.map((blocker) => [canonicalJson(blocker), blocker])).values(),
1845
+ ].sort(compareDiagnosticBlockers));
1846
+ if (contributions[measureId].length > 1)
1847
+ contributions[measureId].sort(compareDiagnosticContributions);
1559
1848
  }
1560
- const components = Object.fromEntries(allMeasureIds.map((measureId) => {
1849
+ const components = {};
1850
+ for (let at = 0; at < allMeasureIds.length; at++) {
1851
+ const measureId = allMeasureIds[at];
1561
1852
  const measure = internals.measuresById.get(measureId);
1562
- return [
1563
- measureId,
1564
- componentSharing.internOwned(finalizeDiagnosticContributions(contributions[measureId], measure.missing, blockers[measureId]), "plain"),
1565
- ];
1566
- }));
1853
+ const stats = finalizeDiagnosticContributionsInterned(contributions[measureId], measure.missing, blockers[measureId], componentSharing);
1854
+ if (measureId === "__proto__")
1855
+ Object.defineProperty(components, measureId, {
1856
+ value: stats,
1857
+ enumerable: true,
1858
+ writable: true,
1859
+ configurable: true,
1860
+ });
1861
+ else
1862
+ components[measureId] = stats;
1863
+ }
1567
1864
  for (const measureId of allMeasureIds) {
1568
1865
  const stats = components[measureId];
1569
1866
  // The finalized counts already prove whether any per-status finding is
@@ -1614,11 +1911,13 @@ function prepareDiagnosticDataInMode(input, mode) {
1614
1911
  valuation,
1615
1912
  developmentAge: coordinate.developmentAge,
1616
1913
  ageUnit: coordinate.ageUnit,
1617
- lossRecordIds: uniqueSorted(rows.map((row) => row.recordId)),
1914
+ lossRecordIds: rows.length === 1
1915
+ ? [rows[0].recordId]
1916
+ : uniqueSorted(rows.map((row) => row.recordId)),
1618
1917
  contributions,
1619
1918
  components,
1620
1919
  structuralBlockers: blockers,
1621
- findings: mergeFindings(cellFindings),
1920
+ findings: cellFindings.length === 0 ? noFindings : mergeFindings(cellFindings),
1622
1921
  });
1623
1922
  }
1624
1923
  for (const candidate of expectedCandidates.filter((item) => item.disposition === "retained")) {
@@ -1653,7 +1952,7 @@ function prepareDiagnosticDataInMode(input, mode) {
1653
1952
  compareDiagnosticPeriods(left.coordinate.origin, right.coordinate.origin) ||
1654
1953
  compareDiagnosticPeriods(left.coordinate.valuation, right.coordinate.valuation))
1655
1954
  .map((candidate) => candidate.row);
1656
- const compareAudits = createAuditComparator();
1955
+ const auditOrder = createAuditComparator();
1657
1956
  const audit = [
1658
1957
  ...lossCandidates.map((candidate) => ({
1659
1958
  kind: "loss",
@@ -1670,20 +1969,26 @@ function prepareDiagnosticDataInMode(input, mode) {
1670
1969
  disposition: candidate.disposition,
1671
1970
  record: candidate.snapshot,
1672
1971
  })),
1673
- ].sort(compareAudits);
1972
+ ];
1973
+ sortAuditRecords(audit, auditOrder);
1674
1974
  const normalizedFindings = mergeFindings(findings);
1675
- const frozenCells = preparedCells.map((cell) => deepFreeze(cell));
1975
+ // Freeze each owned graph exactly once. Cells dominate large preparations;
1976
+ // walking them a second time from the envelope would double the cost, and
1977
+ // their shared children (contributions, statistics, citation lists, audited
1978
+ // numbers, empty records) were attached frozen and complete, so the owned
1979
+ // walk skips them instead of re-visiting each per reference.
1980
+ const frozenCells = Object.freeze(preparedCells.map((cell) => deepFreezeOwned(cell)));
1676
1981
  const cutoffValues = normalizedCutoffs.map((cutoff) => cutoff.value);
1677
- const ownedData = deepFreeze({
1982
+ const ownedData = Object.freeze({
1678
1983
  definitionIntegrity: definition.definitionIntegrity,
1679
- filter,
1680
- completePeriodCutoffs: cutoffValues,
1681
- inputAudit: audit,
1984
+ filter: deepFreeze(filter),
1985
+ completePeriodCutoffs: deepFreeze(cutoffValues),
1986
+ inputAudit: deepFreezeOwned(audit),
1682
1987
  cells: frozenCells,
1683
- exposures: reconciled,
1988
+ exposures: deepFreeze(reconciled),
1684
1989
  expectedCellsProvided: input.expectedCells !== undefined,
1685
- expectedCells,
1686
- findings: normalizedFindings,
1990
+ expectedCells: deepFreeze(expectedCells),
1991
+ findings: deepFreeze(normalizedFindings),
1687
1992
  });
1688
1993
  if (mode === "compact") {
1689
1994
  // Every child was frozen above (the compiled definition owns its snapshot).
@@ -1692,64 +1997,72 @@ function prepareDiagnosticDataInMode(input, mode) {
1692
1997
  ...ownedData,
1693
1998
  });
1694
1999
  authenticCompact.add(prepared);
2000
+ preparedSourceGroups.set(prepared, knownSourceGroups);
1695
2001
  if (definition.definition.lossRowGrain === "aggregate" && [...knownSourceGroups].every((group) => selectedBySource(filter, group)) &&
1696
2002
  normalizedFindings.length === 0 && reconciled.every((exposure) => exposure.status === "valid")) {
1697
- const excluded = new Map();
1698
- lossCandidates.forEach((candidate, index) => {
1699
- const raw = rawLossSnapshots[index];
1700
- // Normalization changes only the coordinate labels in these snapshots.
1701
- // When those were canonical already, reuse the same complete record.
1702
- const record = raw.origin === candidate.snapshot.origin && raw.valuation === candidate.snapshot.valuation
1703
- ? candidate.snapshot : raw;
1704
- excluded.set(candidate.snapshot, deepFreeze({ kind: "loss", disposition: "filter", record }));
1705
- });
1706
- exposureCandidates.forEach((candidate, index) => {
1707
- const raw = rawExposureSnapshots[index];
1708
- const record = raw.origin === candidate.snapshot.origin && raw.valuation === candidate.snapshot.valuation
1709
- ? candidate.snapshot : raw;
1710
- excluded.set(candidate.snapshot, deepFreeze({ kind: "exposure", disposition: "filter", record }));
1711
- });
1712
- expectedCandidates.forEach((candidate) => excluded.set(candidate.snapshot, deepFreeze({
1713
- kind: "expected-cell", disposition: "filter", record: candidate.snapshot,
1714
- })));
1715
- // Rank every possible included/excluded audit variant once. Query sorts
1716
- // then use only numbers; canonical record strings live only in this
1717
- // preparation's comparator and are not retained by the context.
1718
- const originExcluded = new Map(audit.map((item) => [item.record,
1719
- deepFreeze({ ...item, disposition: "filter" })]));
1720
- const origins = new Map();
1721
- for (const candidate of lossCandidates)
1722
- if (candidate.coordinate)
2003
+ const computeSelectionMetadata = () => {
2004
+ const excluded = new Map();
2005
+ lossCandidates.forEach((candidate, index) => {
2006
+ const raw = rawLossSnapshots[index];
2007
+ // Normalization changes only the coordinate labels in these snapshots.
2008
+ // When those were canonical already, reuse the same complete record.
2009
+ // Raw snapshots were never attached to the audit, so freeze them here.
2010
+ const record = raw.origin === candidate.snapshot.origin && raw.valuation === candidate.snapshot.valuation
2011
+ ? candidate.snapshot : deepFreeze(raw);
2012
+ excluded.set(candidate.snapshot, Object.freeze({ kind: "loss", disposition: "filter", record }));
2013
+ });
2014
+ exposureCandidates.forEach((candidate, index) => {
2015
+ const raw = rawExposureSnapshots[index];
2016
+ const record = raw.origin === candidate.snapshot.origin && raw.valuation === candidate.snapshot.valuation
2017
+ ? candidate.snapshot : deepFreeze(raw);
2018
+ excluded.set(candidate.snapshot, Object.freeze({ kind: "exposure", disposition: "filter", record }));
2019
+ });
2020
+ expectedCandidates.forEach((candidate) => excluded.set(candidate.snapshot, Object.freeze({
2021
+ kind: "expected-cell", disposition: "filter", record: candidate.snapshot,
2022
+ })));
2023
+ // Rank every possible included/excluded audit variant once. Query sorts
2024
+ // then use only numbers; canonical record strings live only in this
2025
+ // preparation's comparator and are not retained by the context. Every
2026
+ // audit record was deeply frozen with the envelope above.
2027
+ const originExcluded = new Map(audit.map((item) => [item.record,
2028
+ Object.freeze({ ...item, disposition: "filter" })]));
2029
+ const origins = new Map();
2030
+ for (const candidate of lossCandidates)
2031
+ if (candidate.coordinate)
2032
+ origins.set(candidate.coordinate.origin.label, candidate.coordinate.origin);
2033
+ for (const candidate of exposureCandidates)
2034
+ if (candidate.origin)
2035
+ origins.set(candidate.origin.label, candidate.origin);
2036
+ for (const candidate of expectedCandidates)
1723
2037
  origins.set(candidate.coordinate.origin.label, candidate.coordinate.origin);
1724
- for (const candidate of exposureCandidates)
1725
- if (candidate.origin)
1726
- origins.set(candidate.origin.label, candidate.origin);
1727
- for (const candidate of expectedCandidates)
1728
- origins.set(candidate.coordinate.origin.label, candidate.coordinate.origin);
1729
- const orderedVariants = [...audit, ...excluded.values(), ...originExcluded.values()].sort(compareAudits);
1730
- const auditRanks = new Map(orderedVariants.map((item, index) => [item, index]));
1731
- const allOriginsSelected = [...origins.values()].every((origin) => selectedByOrigin(filter, origin, normalizedFilter.originCoordinates));
1732
- const valuations = new Map();
1733
- let allCoordinatesSelected = true;
1734
- const rememberCoordinate = (valuation, age) => {
1735
- valuations.set(valuation.label, valuation);
1736
- if (!selectedByValuation(filter, valuation, normalizedFilter.valuationCoordinates) || !selectedByAge(filter, age))
1737
- allCoordinatesSelected = false;
1738
- };
1739
- for (const candidate of lossCandidates)
1740
- if (candidate.coordinate)
2038
+ const orderedVariants = orderAuditVariants(audit, excluded, originExcluded, auditOrder);
2039
+ const auditRanks = new Map(orderedVariants.map((item, index) => [item, index]));
2040
+ const allOriginsSelected = [...origins.values()].every((origin) => selectedByOrigin(filter, origin, normalizedFilter.originCoordinates));
2041
+ const valuations = new Map();
2042
+ let allCoordinatesSelected = true;
2043
+ const rememberCoordinate = (valuation, age) => {
2044
+ valuations.set(valuation.label, valuation);
2045
+ if (!selectedByValuation(filter, valuation, normalizedFilter.valuationCoordinates) || !selectedByAge(filter, age))
2046
+ allCoordinatesSelected = false;
2047
+ };
2048
+ for (const candidate of lossCandidates)
2049
+ if (candidate.coordinate)
2050
+ rememberCoordinate(candidate.coordinate.valuation, candidate.coordinate.developmentAge);
2051
+ for (const candidate of expectedCandidates)
1741
2052
  rememberCoordinate(candidate.coordinate.valuation, candidate.coordinate.developmentAge);
1742
- for (const candidate of expectedCandidates)
1743
- rememberCoordinate(candidate.coordinate.valuation, candidate.coordinate.developmentAge);
1744
- for (const candidate of exposureCandidates)
1745
- if (candidate.valuation !== null)
1746
- rememberCoordinate(candidate.valuation, candidate.developmentAge);
1747
- sourceSelectionMetadata.set(prepared, { groups: knownSourceGroups, excluded, originExcluded,
1748
- origins, valuations, allOriginsSelected, allCoordinatesSelected, auditRanks });
2053
+ for (const candidate of exposureCandidates)
2054
+ if (candidate.valuation !== null)
2055
+ rememberCoordinate(candidate.valuation, candidate.developmentAge);
2056
+ return { excluded, originExcluded, origins, valuations, allOriginsSelected,
2057
+ allCoordinatesSelected, auditRanks };
2058
+ };
2059
+ sourceSelectionMetadata.set(prepared, new LazySelectionMetadata(knownSourceGroups, computeSelectionMetadata));
1749
2060
  }
1750
2061
  return prepared;
1751
2062
  }
1752
- return createEagerPreparedDiagnosticData(definition, ownedData);
2063
+ const eager = createEagerPreparedDiagnosticData(definition, ownedData);
2064
+ preparedSourceGroups.set(eager, knownSourceGroups);
2065
+ return eager;
1753
2066
  }
1754
2067
  function createEagerPreparedDiagnosticData(definition, ownedData) {
1755
2068
  const identity = projectDiagnosticIdentity({