@actuarial-ts/core 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -2
- package/dist/canonical.d.ts +11 -0
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +136 -26
- package/dist/canonical.js.map +1 -1
- package/dist/diagnosticEvidenceIntern.d.ts +16 -0
- package/dist/diagnosticEvidenceIntern.d.ts.map +1 -0
- package/dist/diagnosticEvidenceIntern.js +139 -0
- package/dist/diagnosticEvidenceIntern.js.map +1 -0
- package/dist/diagnosticExposure.d.ts.map +1 -1
- package/dist/diagnosticExposure.js +97 -7
- package/dist/diagnosticExposure.js.map +1 -1
- package/dist/diagnosticIdentity.d.ts.map +1 -1
- package/dist/diagnosticIdentity.js +6 -3
- package/dist/diagnosticIdentity.js.map +1 -1
- package/dist/diagnosticIdentityStream.d.ts +56 -0
- package/dist/diagnosticIdentityStream.d.ts.map +1 -0
- package/dist/diagnosticIdentityStream.js +472 -0
- package/dist/diagnosticIdentityStream.js.map +1 -0
- package/dist/diagnosticPreparation.d.ts +28 -0
- package/dist/diagnosticPreparation.d.ts.map +1 -1
- package/dist/diagnosticPreparation.js +160 -36
- package/dist/diagnosticPreparation.js.map +1 -1
- package/dist/diagnosticReview.d.ts +4 -1
- package/dist/diagnosticReview.d.ts.map +1 -1
- package/dist/diagnosticReview.js +203 -51
- package/dist/diagnosticReview.js.map +1 -1
- package/dist/diagnosticReviewSources.d.ts +18 -0
- package/dist/diagnosticReviewSources.d.ts.map +1 -0
- package/dist/diagnosticReviewSources.js +76 -0
- package/dist/diagnosticReviewSources.js.map +1 -0
- package/dist/diagnosticReviewStore.d.ts +88 -0
- package/dist/diagnosticReviewStore.d.ts.map +1 -0
- package/dist/diagnosticReviewStore.js +455 -0
- package/dist/diagnosticReviewStore.js.map +1 -0
- package/dist/diagnosticRunner.d.ts +25 -1
- package/dist/diagnosticRunner.d.ts.map +1 -1
- package/dist/diagnosticRunner.js +115 -7
- package/dist/diagnosticRunner.js.map +1 -1
- package/dist/fnvAccumulator.d.ts +6 -0
- package/dist/fnvAccumulator.d.ts.map +1 -0
- package/dist/fnvAccumulator.js +21 -0
- package/dist/fnvAccumulator.js.map +1 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.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 +144 -26
- package/src/diagnosticEvidenceIntern.ts +151 -0
- package/src/diagnosticExposure.ts +126 -17
- package/src/diagnosticIdentity.ts +15 -6
- package/src/diagnosticIdentityStream.ts +553 -0
- package/src/diagnosticPreparation.ts +268 -84
- package/src/diagnosticReview.ts +293 -85
- package/src/diagnosticReviewSources.ts +85 -0
- package/src/diagnosticReviewStore.ts +692 -0
- package/src/diagnosticRunner.ts +206 -9
- package/src/fnvAccumulator.ts +20 -0
- package/src/index.ts +38 -1
- package/src/version.ts +1 -1
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { canonicalJson, fnv1a64 } from "./canonical.js";
|
|
2
|
+
import {
|
|
3
|
+
createDiagnosticIdentityArray,
|
|
4
|
+
createDiagnosticIdentityObject,
|
|
5
|
+
createDiagnosticIdentityValue,
|
|
6
|
+
fingerprintDiagnosticIdentity,
|
|
7
|
+
type DiagnosticIdentityDocument,
|
|
8
|
+
} from "./diagnosticIdentityStream.js";
|
|
2
9
|
import {
|
|
3
10
|
normalizeDiagnosticsFilterIdentity,
|
|
4
11
|
projectDiagnosticIdentity,
|
|
@@ -75,8 +82,7 @@ export interface DiagnosticLossSnapshot extends DiagnosticLossRecordBase {
|
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
export type DiagnosticLossInput =
|
|
78
|
-
|
|
|
79
|
-
| DiagnosticLossSnapshot;
|
|
85
|
+
DiagnosticClaimObservation | DiagnosticLossSnapshot;
|
|
80
86
|
|
|
81
87
|
export interface DiagnosticCompletePeriodCutoff {
|
|
82
88
|
readonly sourceGroup: string;
|
|
@@ -101,10 +107,7 @@ export interface PrepareDiagnosticDataInput {
|
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
export type DiagnosticInputDisposition =
|
|
104
|
-
| "
|
|
105
|
-
| "complete-period-cutoff"
|
|
106
|
-
| "filter"
|
|
107
|
-
| "retained";
|
|
110
|
+
"invalid" | "complete-period-cutoff" | "filter" | "retained";
|
|
108
111
|
|
|
109
112
|
export interface DiagnosticLossInputAuditSnapshot {
|
|
110
113
|
readonly recordId: string;
|
|
@@ -185,6 +188,22 @@ export interface PreparedDiagnosticData {
|
|
|
185
188
|
readonly findings: readonly DiagnosticMetricFinding[];
|
|
186
189
|
}
|
|
187
190
|
|
|
191
|
+
/** Complete prepared content shared by the eager and compact execution paths. */
|
|
192
|
+
export type PreparedDiagnosticDataContent = Omit<
|
|
193
|
+
PreparedDiagnosticData,
|
|
194
|
+
typeof preparedDiagnosticDataBrand | "preparationFingerprint"
|
|
195
|
+
>;
|
|
196
|
+
|
|
197
|
+
declare const compactPreparedDiagnosticDataBrand: unique symbol;
|
|
198
|
+
/**
|
|
199
|
+
* Owned, frozen preparation without an eagerly materialized identity or hash.
|
|
200
|
+
* This is a distinct authenticated value, not an eager preparation with a
|
|
201
|
+
* missing or placeholder fingerprint.
|
|
202
|
+
*/
|
|
203
|
+
export interface CompactPreparedDiagnosticData extends PreparedDiagnosticDataContent {
|
|
204
|
+
readonly [compactPreparedDiagnosticDataBrand]: true;
|
|
205
|
+
}
|
|
206
|
+
|
|
188
207
|
export interface NormalizedDiagnosticPreparationIdentity {
|
|
189
208
|
readonly definitionIntegrity: string;
|
|
190
209
|
readonly filter: NormalizedDiagnosticsFilterIdentity | null;
|
|
@@ -252,6 +271,12 @@ interface PendingBlocker {
|
|
|
252
271
|
}
|
|
253
272
|
|
|
254
273
|
const authentic = new WeakSet<object>();
|
|
274
|
+
const authenticCompact = new WeakSet<object>();
|
|
275
|
+
const compactIdentityDocuments = new WeakMap<
|
|
276
|
+
object,
|
|
277
|
+
DiagnosticIdentityDocument
|
|
278
|
+
>();
|
|
279
|
+
const compactFingerprints = new WeakMap<object, string>();
|
|
255
280
|
const identities = new WeakMap<
|
|
256
281
|
object,
|
|
257
282
|
DiagnosticDeepReadonly<NormalizedDiagnosticPreparationIdentity>
|
|
@@ -992,10 +1017,10 @@ function exposureCoordinateContext(
|
|
|
992
1017
|
return { sourceGroup: coherentSource[0], origin: coherentOrigin[0] };
|
|
993
1018
|
}
|
|
994
1019
|
|
|
995
|
-
function
|
|
1020
|
+
function createAuditComparator(): (
|
|
996
1021
|
left: DiagnosticInputAuditRecord,
|
|
997
1022
|
right: DiagnosticInputAuditRecord,
|
|
998
|
-
)
|
|
1023
|
+
) => number {
|
|
999
1024
|
const rank = { loss: 0, exposure: 1, "expected-cell": 2 } as const;
|
|
1000
1025
|
const disposition = {
|
|
1001
1026
|
invalid: 0,
|
|
@@ -1003,11 +1028,21 @@ function auditSort(
|
|
|
1003
1028
|
filter: 2,
|
|
1004
1029
|
retained: 3,
|
|
1005
1030
|
} as const;
|
|
1006
|
-
|
|
1031
|
+
// Audit snapshots are already owned and complete before sorting. Preserve the
|
|
1032
|
+
// exact canonical-text ordering, but serialize each compared record once.
|
|
1033
|
+
// Keep this memo local to the sort; it must not retain keys with the result.
|
|
1034
|
+
const keys = new WeakMap<DiagnosticInputAuditRecord["record"], string>();
|
|
1035
|
+
const key = (record: DiagnosticInputAuditRecord["record"]): string => {
|
|
1036
|
+
const previous = keys.get(record);
|
|
1037
|
+
if (previous !== undefined) return previous;
|
|
1038
|
+
const text = canonicalJson(record);
|
|
1039
|
+
keys.set(record, text);
|
|
1040
|
+
return text;
|
|
1041
|
+
};
|
|
1042
|
+
return (left, right) =>
|
|
1007
1043
|
rank[left.kind] - rank[right.kind] ||
|
|
1008
|
-
codeUnit(
|
|
1009
|
-
disposition[left.disposition] - disposition[right.disposition]
|
|
1010
|
-
);
|
|
1044
|
+
codeUnit(key(left.record), key(right.record)) ||
|
|
1045
|
+
disposition[left.disposition] - disposition[right.disposition];
|
|
1011
1046
|
}
|
|
1012
1047
|
|
|
1013
1048
|
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -1345,6 +1380,22 @@ function preparationBoundaryIssues(
|
|
|
1345
1380
|
export function prepareDiagnosticData(
|
|
1346
1381
|
input: PrepareDiagnosticDataInput,
|
|
1347
1382
|
): PreparedDiagnosticData {
|
|
1383
|
+
return prepareDiagnosticDataInMode(input, "eager") as PreparedDiagnosticData;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
export function prepareDiagnosticDataCompact(
|
|
1387
|
+
input: PrepareDiagnosticDataInput,
|
|
1388
|
+
): CompactPreparedDiagnosticData {
|
|
1389
|
+
return prepareDiagnosticDataInMode(
|
|
1390
|
+
input,
|
|
1391
|
+
"compact",
|
|
1392
|
+
) as CompactPreparedDiagnosticData;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
function prepareDiagnosticDataInMode(
|
|
1396
|
+
input: PrepareDiagnosticDataInput,
|
|
1397
|
+
mode: "eager" | "compact",
|
|
1398
|
+
): PreparedDiagnosticData | CompactPreparedDiagnosticData {
|
|
1348
1399
|
assertCompiledDiagnosticDefinition(
|
|
1349
1400
|
(input as unknown as { readonly definition?: unknown } | null)?.definition,
|
|
1350
1401
|
);
|
|
@@ -1432,6 +1483,7 @@ export function prepareDiagnosticData(
|
|
|
1432
1483
|
}
|
|
1433
1484
|
const normalized = {
|
|
1434
1485
|
...row,
|
|
1486
|
+
...(row.source === undefined ? {} : { source: { ...row.source } }),
|
|
1435
1487
|
origin: origin.label,
|
|
1436
1488
|
valuation: valuation.label,
|
|
1437
1489
|
};
|
|
@@ -1470,6 +1522,9 @@ export function prepareDiagnosticData(
|
|
|
1470
1522
|
const lossCandidates: LossCandidate[] = input.losses.map((inputRow) => ({
|
|
1471
1523
|
row: {
|
|
1472
1524
|
...inputRow,
|
|
1525
|
+
...(inputRow.source === undefined
|
|
1526
|
+
? {}
|
|
1527
|
+
: { source: { ...inputRow.source } }),
|
|
1473
1528
|
measures: Object.fromEntries(
|
|
1474
1529
|
Object.keys(inputRow.measures)
|
|
1475
1530
|
.sort(codeUnit)
|
|
@@ -1592,7 +1647,12 @@ export function prepareDiagnosticData(
|
|
|
1592
1647
|
|
|
1593
1648
|
const exposureCandidates: ExposureCandidate[] = input.exposures.map(
|
|
1594
1649
|
(inputRow) => ({
|
|
1595
|
-
row: {
|
|
1650
|
+
row: {
|
|
1651
|
+
...inputRow,
|
|
1652
|
+
...(inputRow.source === undefined
|
|
1653
|
+
? {}
|
|
1654
|
+
: { source: { ...inputRow.source } }),
|
|
1655
|
+
},
|
|
1596
1656
|
snapshot: snapshotExposure(inputRow),
|
|
1597
1657
|
disposition: selectedBySource(filter, inputRow.sourceGroup)
|
|
1598
1658
|
? "retained"
|
|
@@ -2015,6 +2075,14 @@ export function prepareDiagnosticData(
|
|
|
2015
2075
|
derivedByRecord.get(candidate.row.recordId)!,
|
|
2016
2076
|
);
|
|
2017
2077
|
|
|
2078
|
+
// Exposure attachment is a coordinate join. Index the established cell order
|
|
2079
|
+
// once; scanning every cell for each origin-static exposure is quadratic.
|
|
2080
|
+
const cellKeysBySourceOrigin = new Map<string, string[]>();
|
|
2081
|
+
for (const key of cellRows.keys()) {
|
|
2082
|
+
const [sourceGroup, origin] = parseCellKey(key);
|
|
2083
|
+
addToMap(cellKeysBySourceOrigin, canonicalJson([sourceGroup, origin]), key);
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2018
2086
|
const allMeasureIds = definition.definition.measures
|
|
2019
2087
|
.map((measure) => measure.id)
|
|
2020
2088
|
.sort(codeUnit);
|
|
@@ -2025,7 +2093,7 @@ export function prepareDiagnosticData(
|
|
|
2025
2093
|
.filter((measure) => measure.source === "exposure")
|
|
2026
2094
|
.sort((left, right) => codeUnit(left.id, right.id));
|
|
2027
2095
|
const preparedCells: PreparedDiagnosticSourceCell[] = [];
|
|
2028
|
-
const
|
|
2096
|
+
const invalidExposureMeasuresByCell = new Map<string, Set<string>>();
|
|
2029
2097
|
for (const item of invalidExposureBlockers) {
|
|
2030
2098
|
const targets = new Set<string>();
|
|
2031
2099
|
for (const candidate of item.candidates) {
|
|
@@ -2039,16 +2107,17 @@ export function prepareDiagnosticData(
|
|
|
2039
2107
|
),
|
|
2040
2108
|
);
|
|
2041
2109
|
else
|
|
2042
|
-
for (const key of
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2110
|
+
for (const key of cellKeysBySourceOrigin.get(
|
|
2111
|
+
canonicalJson([candidate.row.sourceGroup, candidate.origin.label]),
|
|
2112
|
+
) ?? [])
|
|
2113
|
+
targets.add(key);
|
|
2114
|
+
}
|
|
2115
|
+
for (const key of targets) {
|
|
2116
|
+
const measures =
|
|
2117
|
+
invalidExposureMeasuresByCell.get(key) ?? new Set<string>();
|
|
2118
|
+
measures.add(item.finding.measureId!);
|
|
2119
|
+
invalidExposureMeasuresByCell.set(key, measures);
|
|
2050
2120
|
}
|
|
2051
|
-
invalidExposureTargets.set(canonicalJson(item.finding), targets);
|
|
2052
2121
|
pendingBlockers.push({
|
|
2053
2122
|
finding: item.finding,
|
|
2054
2123
|
measureIds: [item.finding.measureId!],
|
|
@@ -2057,21 +2126,28 @@ export function prepareDiagnosticData(
|
|
|
2057
2126
|
});
|
|
2058
2127
|
}
|
|
2059
2128
|
|
|
2060
|
-
const
|
|
2129
|
+
const validExposuresByCell = new Map<
|
|
2130
|
+
string,
|
|
2131
|
+
Extract<ReconciledDiagnosticExposure, { status: "valid" }>[]
|
|
2132
|
+
>();
|
|
2061
2133
|
for (const exposure of reconciled) {
|
|
2062
2134
|
if (exposure.status !== "valid") continue;
|
|
2063
2135
|
const timing = internals.measuresById.get(
|
|
2064
2136
|
exposure.measureId,
|
|
2065
2137
|
)!.exposureTiming;
|
|
2066
|
-
const
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
sourceGroup
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2138
|
+
const coordinateKey =
|
|
2139
|
+
exposure.valuation === undefined
|
|
2140
|
+
? null
|
|
2141
|
+
: cellKey(exposure.sourceGroup, exposure.origin, exposure.valuation);
|
|
2142
|
+
const keys =
|
|
2143
|
+
timing === "origin-static"
|
|
2144
|
+
? (cellKeysBySourceOrigin.get(
|
|
2145
|
+
canonicalJson([exposure.sourceGroup, exposure.origin]),
|
|
2146
|
+
) ?? [])
|
|
2147
|
+
: coordinateKey !== null && cellRows.has(coordinateKey)
|
|
2148
|
+
? [coordinateKey]
|
|
2149
|
+
: [];
|
|
2150
|
+
for (const key of keys) addToMap(validExposuresByCell, key, exposure);
|
|
2075
2151
|
if (keys.length === 0)
|
|
2076
2152
|
findings.push(
|
|
2077
2153
|
structuralFinding("exposure-without-loss", {
|
|
@@ -2085,6 +2161,14 @@ export function prepareDiagnosticData(
|
|
|
2085
2161
|
);
|
|
2086
2162
|
}
|
|
2087
2163
|
|
|
2164
|
+
const pendingBlockersByCell = new Map<
|
|
2165
|
+
string,
|
|
2166
|
+
(typeof pendingBlockers)[number][]
|
|
2167
|
+
>();
|
|
2168
|
+
for (const pending of pendingBlockers)
|
|
2169
|
+
for (const key of new Set(pending.cellKeys))
|
|
2170
|
+
addToMap(pendingBlockersByCell, key, pending);
|
|
2171
|
+
|
|
2088
2172
|
for (const [key, rows] of cellRows) {
|
|
2089
2173
|
const [sourceGroup, origin, valuation] = parseCellKey(key);
|
|
2090
2174
|
const coordinate = coordinateFor(definition, origin, valuation).coordinate!;
|
|
@@ -2161,22 +2245,18 @@ export function prepareDiagnosticData(
|
|
|
2161
2245
|
),
|
|
2162
2246
|
);
|
|
2163
2247
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
),
|
|
2177
|
-
);
|
|
2178
|
-
for (const pending of pendingBlockers) {
|
|
2179
|
-
if (!pending.cellKeys.includes(key)) continue;
|
|
2248
|
+
const attachedExposures = validExposuresByCell.get(key) ?? [];
|
|
2249
|
+
for (const exposure of attachedExposures)
|
|
2250
|
+
contributions[exposure.measureId]!.push(
|
|
2251
|
+
auditedDiagnosticContribution(
|
|
2252
|
+
exposure.key,
|
|
2253
|
+
exposure.value,
|
|
2254
|
+
"unknown",
|
|
2255
|
+
exposure.sources,
|
|
2256
|
+
exposure.deduplicated,
|
|
2257
|
+
),
|
|
2258
|
+
);
|
|
2259
|
+
for (const pending of pendingBlockersByCell.get(key) ?? []) {
|
|
2180
2260
|
cellFindings.push(pending.finding);
|
|
2181
2261
|
const blocker = {
|
|
2182
2262
|
code: pending.finding.code,
|
|
@@ -2190,17 +2270,9 @@ export function prepareDiagnosticData(
|
|
|
2190
2270
|
}
|
|
2191
2271
|
for (const measure of exposureMeasures) {
|
|
2192
2272
|
const attempted =
|
|
2193
|
-
|
|
2194
|
-
(
|
|
2195
|
-
|
|
2196
|
-
exposure.measureId === measure.id &&
|
|
2197
|
-
keys.includes(key),
|
|
2198
|
-
) ||
|
|
2199
|
-
invalidExposureBlockers.some(
|
|
2200
|
-
(item) =>
|
|
2201
|
-
item.finding.measureId === measure.id &&
|
|
2202
|
-
invalidExposureTargets.get(canonicalJson(item.finding))?.has(key),
|
|
2203
|
-
);
|
|
2273
|
+
attachedExposures.some(
|
|
2274
|
+
(exposure) => exposure.measureId === measure.id,
|
|
2275
|
+
) || invalidExposureMeasuresByCell.get(key)?.has(measure.id);
|
|
2204
2276
|
if (contributions[measure.id]!.length === 0 && !attempted) {
|
|
2205
2277
|
const sources = normalizeSources(rows.map((row) => row.source));
|
|
2206
2278
|
const finding = structuralFinding("loss-without-exposure", {
|
|
@@ -2374,28 +2446,22 @@ export function prepareDiagnosticData(
|
|
|
2374
2446
|
)
|
|
2375
2447
|
.map((candidate) => candidate.row);
|
|
2376
2448
|
const audit: DiagnosticInputAuditRecord[] = [
|
|
2377
|
-
...lossCandidates.map(
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
kind: "expected-cell",
|
|
2394
|
-
disposition: candidate.disposition,
|
|
2395
|
-
record: candidate.snapshot,
|
|
2396
|
-
}),
|
|
2397
|
-
),
|
|
2398
|
-
].sort(auditSort);
|
|
2449
|
+
...lossCandidates.map((candidate): DiagnosticInputAuditRecord => ({
|
|
2450
|
+
kind: "loss",
|
|
2451
|
+
disposition: candidate.disposition,
|
|
2452
|
+
record: candidate.snapshot,
|
|
2453
|
+
})),
|
|
2454
|
+
...exposureCandidates.map((candidate): DiagnosticInputAuditRecord => ({
|
|
2455
|
+
kind: "exposure",
|
|
2456
|
+
disposition: candidate.disposition,
|
|
2457
|
+
record: candidate.snapshot,
|
|
2458
|
+
})),
|
|
2459
|
+
...expectedCandidates.map((candidate): DiagnosticInputAuditRecord => ({
|
|
2460
|
+
kind: "expected-cell",
|
|
2461
|
+
disposition: candidate.disposition,
|
|
2462
|
+
record: candidate.snapshot,
|
|
2463
|
+
})),
|
|
2464
|
+
].sort(createAuditComparator());
|
|
2399
2465
|
const normalizedFindings = mergeFindings(findings);
|
|
2400
2466
|
const frozenCells = preparedCells.map((cell) => deepFreeze(cell));
|
|
2401
2467
|
const cutoffValues = normalizedCutoffs.map((cutoff) => cutoff.value);
|
|
@@ -2410,11 +2476,29 @@ export function prepareDiagnosticData(
|
|
|
2410
2476
|
expectedCells,
|
|
2411
2477
|
findings: normalizedFindings,
|
|
2412
2478
|
});
|
|
2479
|
+
if (mode === "compact") {
|
|
2480
|
+
// Every child was frozen above (the compiled definition owns its snapshot).
|
|
2481
|
+
const prepared = Object.freeze({
|
|
2482
|
+
definition,
|
|
2483
|
+
...ownedData,
|
|
2484
|
+
}) as unknown as CompactPreparedDiagnosticData;
|
|
2485
|
+
authenticCompact.add(prepared);
|
|
2486
|
+
return prepared;
|
|
2487
|
+
}
|
|
2488
|
+
return createEagerPreparedDiagnosticData(definition, ownedData);
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
function createEagerPreparedDiagnosticData(
|
|
2492
|
+
definition: CompiledDiagnosticDefinition,
|
|
2493
|
+
ownedData: Omit<PreparedDiagnosticDataContent, "definition"> & {
|
|
2494
|
+
readonly definitionIntegrity: string;
|
|
2495
|
+
},
|
|
2496
|
+
): PreparedDiagnosticData {
|
|
2413
2497
|
const identity: NormalizedDiagnosticPreparationIdentity =
|
|
2414
2498
|
projectDiagnosticIdentity({
|
|
2415
2499
|
...ownedData,
|
|
2416
|
-
filter: normalizeDiagnosticsFilterIdentity(filter),
|
|
2417
|
-
expectedCells: expectedCells.map((cell) => ({
|
|
2500
|
+
filter: normalizeDiagnosticsFilterIdentity(ownedData.filter),
|
|
2501
|
+
expectedCells: ownedData.expectedCells.map((cell) => ({
|
|
2418
2502
|
...cell,
|
|
2419
2503
|
source: cell.source ?? null,
|
|
2420
2504
|
})),
|
|
@@ -2430,6 +2514,106 @@ export function prepareDiagnosticData(
|
|
|
2430
2514
|
return prepared;
|
|
2431
2515
|
}
|
|
2432
2516
|
|
|
2517
|
+
export function assertCompactPreparedDiagnosticData(
|
|
2518
|
+
value: unknown,
|
|
2519
|
+
): asserts value is CompactPreparedDiagnosticData {
|
|
2520
|
+
if (
|
|
2521
|
+
value === null ||
|
|
2522
|
+
typeof value !== "object" ||
|
|
2523
|
+
!authenticCompact.has(value)
|
|
2524
|
+
)
|
|
2525
|
+
throw new DiagnosticValidationError([
|
|
2526
|
+
{
|
|
2527
|
+
domain: "input",
|
|
2528
|
+
code: "invalid-input-relationship",
|
|
2529
|
+
path: "$.prepared",
|
|
2530
|
+
message: "Compact prepared diagnostic data is not authentic",
|
|
2531
|
+
},
|
|
2532
|
+
]);
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
/**
|
|
2536
|
+
* Explicit eager bridge for callers that need the legacy complete JSON shape.
|
|
2537
|
+
* This materializes and hashes the full identity; compact execution never calls
|
|
2538
|
+
* it implicitly, and the compact value does not retain the resulting graph.
|
|
2539
|
+
*/
|
|
2540
|
+
export function materializePreparedDiagnosticData(
|
|
2541
|
+
value: CompactPreparedDiagnosticData,
|
|
2542
|
+
): PreparedDiagnosticData {
|
|
2543
|
+
assertCompactPreparedDiagnosticData(value);
|
|
2544
|
+
return createEagerPreparedDiagnosticData(value.definition, {
|
|
2545
|
+
definitionIntegrity: value.definition.definitionIntegrity,
|
|
2546
|
+
filter: value.filter,
|
|
2547
|
+
completePeriodCutoffs: value.completePeriodCutoffs,
|
|
2548
|
+
inputAudit: value.inputAudit,
|
|
2549
|
+
cells: value.cells,
|
|
2550
|
+
exposures: value.exposures,
|
|
2551
|
+
expectedCellsProvided: value.expectedCellsProvided,
|
|
2552
|
+
expectedCells: value.expectedCells,
|
|
2553
|
+
findings: value.findings,
|
|
2554
|
+
});
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
/**
|
|
2558
|
+
* Core-owned, complete identity projection, emitted without a second retained
|
|
2559
|
+
* preparation graph. A structural document is not itself actuarial authority:
|
|
2560
|
+
* this factory authenticates the frozen preparation before creating one.
|
|
2561
|
+
*/
|
|
2562
|
+
export function getCompactPreparedDiagnosticDataIdentityDocument(
|
|
2563
|
+
value: CompactPreparedDiagnosticData,
|
|
2564
|
+
): DiagnosticIdentityDocument {
|
|
2565
|
+
assertCompactPreparedDiagnosticData(value);
|
|
2566
|
+
const previous = compactIdentityDocuments.get(value);
|
|
2567
|
+
if (previous) return previous;
|
|
2568
|
+
const document = createDiagnosticIdentityObject({
|
|
2569
|
+
definitionIntegrity: createDiagnosticIdentityValue(
|
|
2570
|
+
value.definition.definitionIntegrity,
|
|
2571
|
+
),
|
|
2572
|
+
filter: createDiagnosticIdentityValue(
|
|
2573
|
+
normalizeDiagnosticsFilterIdentity(value.filter),
|
|
2574
|
+
),
|
|
2575
|
+
completePeriodCutoffs: createDiagnosticIdentityValue(
|
|
2576
|
+
value.completePeriodCutoffs,
|
|
2577
|
+
),
|
|
2578
|
+
inputAudit: createDiagnosticIdentityValue(value.inputAudit),
|
|
2579
|
+
cells: createDiagnosticIdentityValue(value.cells),
|
|
2580
|
+
exposures: createDiagnosticIdentityValue(value.exposures),
|
|
2581
|
+
expectedCellsProvided: createDiagnosticIdentityValue(
|
|
2582
|
+
value.expectedCellsProvided,
|
|
2583
|
+
),
|
|
2584
|
+
expectedCells: createDiagnosticIdentityArray(
|
|
2585
|
+
value.expectedCells.length,
|
|
2586
|
+
(index) => {
|
|
2587
|
+
const cell = value.expectedCells[index]!;
|
|
2588
|
+
return createDiagnosticIdentityObject({
|
|
2589
|
+
sourceGroup: createDiagnosticIdentityValue(cell.sourceGroup),
|
|
2590
|
+
origin: createDiagnosticIdentityValue(cell.origin),
|
|
2591
|
+
valuation: createDiagnosticIdentityValue(cell.valuation),
|
|
2592
|
+
source: createDiagnosticIdentityValue(cell.source ?? null),
|
|
2593
|
+
});
|
|
2594
|
+
},
|
|
2595
|
+
),
|
|
2596
|
+
findings: createDiagnosticIdentityValue(value.findings),
|
|
2597
|
+
});
|
|
2598
|
+
compactIdentityDocuments.set(value, document);
|
|
2599
|
+
return document;
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
/** Compute the exact legacy preparation tag on explicit evidence access only. */
|
|
2603
|
+
export function getCompactPreparedDiagnosticDataFingerprint(
|
|
2604
|
+
value: CompactPreparedDiagnosticData,
|
|
2605
|
+
): string {
|
|
2606
|
+
assertCompactPreparedDiagnosticData(value);
|
|
2607
|
+
const previous = compactFingerprints.get(value);
|
|
2608
|
+
if (previous) return previous;
|
|
2609
|
+
const tag = fingerprintDiagnosticIdentity(
|
|
2610
|
+
getCompactPreparedDiagnosticDataIdentityDocument(value),
|
|
2611
|
+
{ kind: "diagnostic-preparation", property: "preparation" },
|
|
2612
|
+
);
|
|
2613
|
+
compactFingerprints.set(value, tag);
|
|
2614
|
+
return tag;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2433
2617
|
export function assertPreparedDiagnosticData(
|
|
2434
2618
|
value: unknown,
|
|
2435
2619
|
): asserts value is PreparedDiagnosticData {
|