@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.
- package/README.md +9 -7
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +64 -5
- package/dist/canonical.js.map +1 -1
- package/dist/customizationContracts.d.ts +10 -1
- package/dist/customizationContracts.d.ts.map +1 -1
- package/dist/descriptiveStatistics.js +1 -1
- package/dist/descriptiveStatistics.js.map +1 -1
- package/dist/diagnosticAggregation.d.ts +26 -0
- package/dist/diagnosticAggregation.d.ts.map +1 -1
- package/dist/diagnosticAggregation.js +125 -11
- package/dist/diagnosticAggregation.js.map +1 -1
- package/dist/diagnosticAuditText.d.ts +11 -0
- package/dist/diagnosticAuditText.d.ts.map +1 -0
- package/dist/diagnosticAuditText.js +179 -0
- package/dist/diagnosticAuditText.js.map +1 -0
- package/dist/diagnosticFormulas.d.ts.map +1 -1
- package/dist/diagnosticFormulas.js +71 -12
- package/dist/diagnosticFormulas.js.map +1 -1
- package/dist/diagnosticFreeze.d.ts +15 -1
- package/dist/diagnosticFreeze.d.ts.map +1 -1
- package/dist/diagnosticFreeze.js +82 -31
- package/dist/diagnosticFreeze.js.map +1 -1
- package/dist/diagnosticKeys.d.ts +9 -0
- package/dist/diagnosticKeys.d.ts.map +1 -0
- package/dist/diagnosticKeys.js +20 -0
- package/dist/diagnosticKeys.js.map +1 -0
- package/dist/diagnosticOrdering.d.ts.map +1 -1
- package/dist/diagnosticOrdering.js +9 -2
- package/dist/diagnosticOrdering.js.map +1 -1
- package/dist/diagnosticPeriods.d.ts.map +1 -1
- package/dist/diagnosticPeriods.js +19 -1
- package/dist/diagnosticPeriods.js.map +1 -1
- package/dist/diagnosticPreparation.d.ts +19 -0
- package/dist/diagnosticPreparation.d.ts.map +1 -1
- package/dist/diagnosticPreparation.js +544 -231
- package/dist/diagnosticPreparation.js.map +1 -1
- package/dist/diagnosticReview.d.ts.map +1 -1
- package/dist/diagnosticReview.js +419 -186
- package/dist/diagnosticReview.js.map +1 -1
- package/dist/diagnosticReviewSources.d.ts +6 -1
- package/dist/diagnosticReviewSources.d.ts.map +1 -1
- package/dist/diagnosticReviewSources.js +59 -14
- package/dist/diagnosticReviewSources.js.map +1 -1
- package/dist/diagnosticReviewStore.d.ts.map +1 -1
- package/dist/diagnosticReviewStore.js +48 -7
- package/dist/diagnosticReviewStore.js.map +1 -1
- package/dist/diagnosticRunner.d.ts.map +1 -1
- package/dist/diagnosticRunner.js +112 -37
- package/dist/diagnosticRunner.js.map +1 -1
- package/dist/diagnosticRuntime.d.ts.map +1 -1
- package/dist/diagnosticRuntime.js +11 -1
- package/dist/diagnosticRuntime.js.map +1 -1
- package/dist/diagnosticSourceOrdering.d.ts +27 -0
- package/dist/diagnosticSourceOrdering.d.ts.map +1 -1
- package/dist/diagnosticSourceOrdering.js +98 -12
- package/dist/diagnosticSourceOrdering.js.map +1 -1
- package/dist/mack.d.ts.map +1 -1
- package/dist/mack.js +35 -0
- package/dist/mack.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/canonical.ts +63 -5
- package/src/customizationContracts.ts +10 -1
- package/src/descriptiveStatistics.ts +1 -1
- package/src/diagnosticAggregation.ts +173 -9
- package/src/diagnosticAuditText.ts +187 -0
- package/src/diagnosticFormulas.ts +77 -27
- package/src/diagnosticFreeze.ts +75 -30
- package/src/diagnosticKeys.ts +22 -0
- package/src/diagnosticOrdering.ts +9 -8
- package/src/diagnosticPeriods.ts +21 -1
- package/src/diagnosticPreparation.ts +624 -272
- package/src/diagnosticReview.ts +566 -268
- package/src/diagnosticReviewSources.ts +69 -18
- package/src/diagnosticReviewStore.ts +48 -6
- package/src/diagnosticRunner.ts +133 -54
- package/src/diagnosticRuntime.ts +11 -1
- package/src/diagnosticSourceOrdering.ts +115 -11
- package/src/mack.ts +41 -0
- package/src/types.ts +1 -0
- 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 {
|
|
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,
|
|
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
|
|
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,
|
|
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 (!
|
|
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) =>
|
|
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(
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
773
|
+
const text = auditRecordCanonicalJson(record);
|
|
677
774
|
keys.set(record, text);
|
|
678
775
|
return text;
|
|
679
776
|
};
|
|
680
|
-
return
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
708
|
-
|
|
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",
|
|
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,
|
|
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
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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",
|
|
1021
|
+
issue("input", "invalid-type", at("$.losses", index, ""), "Loss record must be an object");
|
|
749
1022
|
continue;
|
|
750
1023
|
}
|
|
751
|
-
|
|
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",
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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
|
-
|
|
1032
|
+
tokenAt(raw.claimId, "$.losses", index, ".claimId");
|
|
770
1033
|
else if (raw.claimId !== undefined)
|
|
771
|
-
issue("input", "unknown-key",
|
|
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",
|
|
1036
|
+
issue("input", "invalid-type", at("$.losses", index, ".complete"), "Loss completeness must be boolean");
|
|
774
1037
|
if (raw.source !== undefined)
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
|
780
|
-
|
|
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", `${
|
|
1049
|
+
issue("input", "invalid-type", `${at("$.losses", index, ".measures")}.${measureId}`, "Measure value must be a number or null");
|
|
783
1050
|
}
|
|
784
1051
|
}
|
|
785
|
-
|
|
786
|
-
|
|
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",
|
|
1056
|
+
issue("input", "invalid-type", at("$.exposures", index, ""), "Exposure observation must be an object");
|
|
789
1057
|
continue;
|
|
790
1058
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
|
|
1065
|
+
tokenAt(raw.valuation, "$.exposures", index, ".valuation");
|
|
807
1066
|
if (raw.value !== null && typeof raw.value !== "number")
|
|
808
|
-
issue("input", "invalid-type",
|
|
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",
|
|
1069
|
+
issue("input", "invalid-type", at("$.exposures", index, ".complete"), "Exposure completeness must be boolean");
|
|
811
1070
|
if (raw.source !== undefined)
|
|
812
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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,
|
|
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) =>
|
|
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 =
|
|
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
|
-
|
|
1385
|
-
|
|
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] =
|
|
1391
|
-
addToMap(cellKeysBySourceOrigin,
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
const
|
|
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 (
|
|
1486
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1554
|
-
|
|
1555
|
-
blocker,
|
|
1556
|
-
]
|
|
1557
|
-
].
|
|
1558
|
-
|
|
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 =
|
|
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
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
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:
|
|
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
|
|
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
|
-
]
|
|
1972
|
+
];
|
|
1973
|
+
sortAuditRecords(audit, auditOrder);
|
|
1674
1974
|
const normalizedFindings = mergeFindings(findings);
|
|
1675
|
-
|
|
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 =
|
|
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
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
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
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
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
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
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
|
-
|
|
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({
|