@actuarial-ts/data 0.2.0 → 0.3.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/dist/csv.d.ts +14 -2
- package/dist/csv.d.ts.map +1 -1
- package/dist/csv.js +18 -3
- package/dist/csv.js.map +1 -1
- package/dist/lossRun.d.ts.map +1 -1
- package/dist/lossRun.js +27 -4
- package/dist/lossRun.js.map +1 -1
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +49 -3
- package/dist/review.js.map +1 -1
- package/package.json +3 -2
- package/src/csv.ts +116 -0
- package/src/index.ts +4 -0
- package/src/longFormat.ts +0 -0
- package/src/lossRun.ts +171 -0
- package/src/review.ts +374 -0
package/dist/csv.d.ts
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
* validation. Ragged rows are preserved as-is — validation is the caller's
|
|
11
11
|
* job (see lossRun.ts).
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
export interface CsvParseResult {
|
|
14
|
+
/** The parsed grid (rows x columns). Ragged rows preserved as-is. */
|
|
15
|
+
rows: string[][];
|
|
16
|
+
/**
|
|
17
|
+
* Structural problems the parser recovered from. Content is still parsed
|
|
18
|
+
* leniently — a warning here means the OUTPUT may not mean what the file's
|
|
19
|
+
* author intended, which the caller must surface rather than swallow: an
|
|
20
|
+
* unterminated quote consumes the remainder of the input into one field,
|
|
21
|
+
* and five good rows silently becoming one is how claims vanish.
|
|
22
|
+
*/
|
|
23
|
+
warnings: string[];
|
|
24
|
+
}
|
|
25
|
+
/** Parses CSV text into a grid of string fields plus structural warnings. */
|
|
26
|
+
export declare function parseCsv(text: string): CsvParseResult;
|
|
15
27
|
//# sourceMappingURL=csv.d.ts.map
|
package/dist/csv.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,qEAAqE;
|
|
1
|
+
{"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;IACjB;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAwFrD"}
|
package/dist/csv.js
CHANGED
|
@@ -10,16 +10,19 @@
|
|
|
10
10
|
* validation. Ragged rows are preserved as-is — validation is the caller's
|
|
11
11
|
* job (see lossRun.ts).
|
|
12
12
|
*/
|
|
13
|
-
/** Parses CSV text into a grid of string fields
|
|
13
|
+
/** Parses CSV text into a grid of string fields plus structural warnings. */
|
|
14
14
|
export function parseCsv(text) {
|
|
15
15
|
let s = text;
|
|
16
16
|
if (s.length > 0 && s.charCodeAt(0) === 0xfeff)
|
|
17
17
|
s = s.slice(1);
|
|
18
18
|
const rows = [];
|
|
19
|
+
const warnings = [];
|
|
19
20
|
let row = [];
|
|
20
21
|
let field = "";
|
|
21
22
|
let inQuotes = false;
|
|
22
23
|
let fieldWasQuoted = false;
|
|
24
|
+
let line = 1;
|
|
25
|
+
let quoteOpenedAtLine = 0;
|
|
23
26
|
const endField = () => {
|
|
24
27
|
row.push(field);
|
|
25
28
|
field = "";
|
|
@@ -48,12 +51,15 @@ export function parseCsv(text) {
|
|
|
48
51
|
i++;
|
|
49
52
|
continue;
|
|
50
53
|
}
|
|
54
|
+
if (ch === "\n")
|
|
55
|
+
line++;
|
|
51
56
|
field += ch;
|
|
52
57
|
i++;
|
|
53
58
|
continue;
|
|
54
59
|
}
|
|
55
60
|
if (ch === '"' && field === "" && !fieldWasQuoted) {
|
|
56
61
|
inQuotes = true;
|
|
62
|
+
quoteOpenedAtLine = line;
|
|
57
63
|
fieldWasQuoted = true;
|
|
58
64
|
i++;
|
|
59
65
|
continue;
|
|
@@ -67,11 +73,13 @@ export function parseCsv(text) {
|
|
|
67
73
|
if (s[i + 1] === "\n")
|
|
68
74
|
i++;
|
|
69
75
|
endRow();
|
|
76
|
+
line++;
|
|
70
77
|
i++;
|
|
71
78
|
continue;
|
|
72
79
|
}
|
|
73
80
|
if (ch === "\n") {
|
|
74
81
|
endRow();
|
|
82
|
+
line++;
|
|
75
83
|
i++;
|
|
76
84
|
continue;
|
|
77
85
|
}
|
|
@@ -79,8 +87,15 @@ export function parseCsv(text) {
|
|
|
79
87
|
i++;
|
|
80
88
|
}
|
|
81
89
|
// Flush a final row that has no trailing newline. An unterminated quoted
|
|
82
|
-
// field flushes with whatever
|
|
90
|
+
// field still flushes with whatever it accumulated — content stays lenient —
|
|
91
|
+
// but the structural problem is REPORTED: everything after the stray quote
|
|
92
|
+
// was consumed into one field, and the caller must not present that output
|
|
93
|
+
// as a faithful read of the file.
|
|
94
|
+
if (inQuotes) {
|
|
95
|
+
warnings.push(`unterminated quoted field starting at line ${quoteOpenedAtLine}: the remainder of the ` +
|
|
96
|
+
"input was consumed into a single field; check the file for a stray or unescaped quote");
|
|
97
|
+
}
|
|
83
98
|
endRow();
|
|
84
|
-
return rows;
|
|
99
|
+
return { rows, warnings };
|
|
85
100
|
}
|
|
86
101
|
//# sourceMappingURL=csv.js.map
|
package/dist/csv.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csv.js","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"csv.js","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"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"}
|
package/dist/lossRun.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lossRun.d.ts","sourceRoot":"","sources":["../src/lossRun.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAmCxD,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"lossRun.d.ts","sourceRoot":"","sources":["../src/lossRun.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAmCxD,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAwBD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAqGhE"}
|
package/dist/lossRun.js
CHANGED
|
@@ -42,12 +42,18 @@ function isValidIsoDate(value) {
|
|
|
42
42
|
const d = Number(match[3]);
|
|
43
43
|
if (m < 1 || m > 12 || d < 1)
|
|
44
44
|
return false;
|
|
45
|
-
|
|
45
|
+
// Arithmetic rule, mirroring compliance/src/metadata.ts (this package cannot
|
|
46
|
+
// import compliance — the dependency points the other way). The previous
|
|
47
|
+
// Date.UTC form mapped years 0-99 into the 1900s; the mapping happens to
|
|
48
|
+
// preserve leapness for every year except 0000, so this is a correctness-of-
|
|
49
|
+
// form consolidation rather than a live-bug fix.
|
|
50
|
+
const leap = (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
|
|
51
|
+
const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m - 1];
|
|
46
52
|
return d <= daysInMonth;
|
|
47
53
|
}
|
|
48
54
|
/** Parses a loss-run CSV into ClaimSnapshots plus per-row validation errors. */
|
|
49
55
|
export function parseLossRunCsv(text) {
|
|
50
|
-
const grid = parseCsv(text);
|
|
56
|
+
const { rows: grid, warnings: csvWarnings } = parseCsv(text);
|
|
51
57
|
const headers = (grid[0] ?? []).map(normalizeHeader);
|
|
52
58
|
const missing = REQUIRED_COLUMNS.filter((c) => !headers.includes(c));
|
|
53
59
|
if (missing.length > 0) {
|
|
@@ -60,6 +66,12 @@ export function parseLossRunCsv(text) {
|
|
|
60
66
|
});
|
|
61
67
|
const claims = [];
|
|
62
68
|
const errors = [];
|
|
69
|
+
for (const warning of csvWarnings) {
|
|
70
|
+
// Structural CSV problems surface through the same channel as row errors:
|
|
71
|
+
// partial loading is intended behavior here, silent partial loading is not.
|
|
72
|
+
const lineMatch = warning.match(/line (\d+)/);
|
|
73
|
+
errors.push({ row: lineMatch ? Number(lineMatch[1]) : 1, message: `CSV structure: ${warning}` });
|
|
74
|
+
}
|
|
63
75
|
for (let r = 1; r < grid.length; r++) {
|
|
64
76
|
const rowNumber = r + 1; // 1-based including the header row
|
|
65
77
|
const cells = grid[r];
|
|
@@ -81,9 +93,20 @@ export function parseLossRunCsv(text) {
|
|
|
81
93
|
const evaluationDate = date("evaluation_date");
|
|
82
94
|
const amount = (name) => {
|
|
83
95
|
const value = cell(name);
|
|
84
|
-
|
|
96
|
+
// Currency is decimal digits with an optional sign and fraction —
|
|
97
|
+
// Number() also accepts hex (0x2710 -> 10000), binary, octal and
|
|
98
|
+
// scientific notation, which silently changes magnitudes instead of
|
|
99
|
+
// erroring. Formatted amounts are rejected with a pointed message
|
|
100
|
+
// rather than guessed at: "1,234" could be one thousand or 1.234
|
|
101
|
+
// depending on locale, and a loss run is no place to guess.
|
|
102
|
+
if (/[,()]|\s/.test(value.trim()) && value.trim() !== "") {
|
|
103
|
+
rowErrors.push(`${name} must be an unformatted decimal (got "${value}"); remove thousands separators, ` +
|
|
104
|
+
"parentheses and spaces");
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const n = /^-?\d+(\.\d+)?$/.test(value.trim()) ? Number(value.trim()) : NaN;
|
|
85
108
|
if (!Number.isFinite(n)) {
|
|
86
|
-
rowErrors.push(`${name} must be a finite number (got "${value}")`);
|
|
109
|
+
rowErrors.push(`${name} must be a finite decimal number (got "${value}")`);
|
|
87
110
|
return null;
|
|
88
111
|
}
|
|
89
112
|
return n;
|
package/dist/lossRun.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lossRun.js","sourceRoot":"","sources":["../src/lossRun.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,gBAAgB,GAAG;IACvB,UAAU;IACV,eAAe;IACf,aAAa;IACb,iBAAiB;IACjB,cAAc;IACd,cAAc;IACd,QAAQ;CACA,CAAC;AAaX,SAAS,eAAe,CAAC,CAAS;IAChC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACrD,CAAC;AAED,uEAAuE;AACvE,SAAS,cAAc,CAAC,KAAa;IACnC,MAAM,KAAK,GAAG,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3C,MAAM,
|
|
1
|
+
{"version":3,"file":"lossRun.js","sourceRoot":"","sources":["../src/lossRun.ts"],"names":[],"mappings":"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"}
|
package/dist/review.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,eAAe,CAAC;AAE5E,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAChF;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,eAAe,CAAC;AAE5E,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAChF;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAmDD,8EAA8E;AAC9E,wBAAgB,eAAe,CAC7B,MAAM,EAAE,aAAa,EAAE,EACvB,IAAI,GAAE,sBAA2B,GAChC,gBAAgB,CAsGlB;AA+ED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAG,gBAAgB,CA+FpF"}
|
package/dist/review.js
CHANGED
|
@@ -29,6 +29,7 @@ function summarize(checks) {
|
|
|
29
29
|
return { checks, summary };
|
|
30
30
|
}
|
|
31
31
|
const CLAIM_DESCRIPTIONS = {
|
|
32
|
+
"non-finite-value": "Every money field is a finite number (no NaN or Infinity)",
|
|
32
33
|
"negative-paid": "Cumulative paid amounts are non-negative",
|
|
33
34
|
"negative-case": "Case reserves are non-negative (negative case is legitimate but rare)",
|
|
34
35
|
"paid-decreasing": "Cumulative paid never decreases across a claim's snapshots ordered by evaluation date",
|
|
@@ -44,8 +45,14 @@ export function reviewClaimData(claims, opts = {}) {
|
|
|
44
45
|
const dateOrder = [];
|
|
45
46
|
const futureDated = [];
|
|
46
47
|
const closedWithCase = [];
|
|
47
|
-
claims.forEach((c
|
|
48
|
-
|
|
48
|
+
claims.forEach((c) => {
|
|
49
|
+
// Identified by claimId + evaluation date, which the DATA carries. The old
|
|
50
|
+
// label fabricated "row N" from this array's index — but this function
|
|
51
|
+
// receives parsed claims, not the file, and parseLossRunCsv numbers real
|
|
52
|
+
// CSV rows (header-inclusive) in its own errors. Two different "row"
|
|
53
|
+
// meanings pointed auditors at the wrong line; an identifier we cannot
|
|
54
|
+
// compute is one we must not print.
|
|
55
|
+
const where = `claim ${c.claimId} (eval ${c.evaluationDate})`;
|
|
49
56
|
if (c.paidToDate < 0) {
|
|
50
57
|
negativePaid.push(`${where}: paid_to_date ${c.paidToDate}`);
|
|
51
58
|
}
|
|
@@ -105,10 +112,20 @@ export function reviewClaimData(claims, opts = {}) {
|
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
114
|
}
|
|
115
|
+
const nonFinite = [];
|
|
116
|
+
for (const c of claims) {
|
|
117
|
+
const where = `claim ${c.claimId}`;
|
|
118
|
+
if (!Number.isFinite(c.paidToDate))
|
|
119
|
+
nonFinite.push(`${where}: paid_to_date ${String(c.paidToDate)}`);
|
|
120
|
+
if (!Number.isFinite(c.caseReserve))
|
|
121
|
+
nonFinite.push(`${where}: case_reserve ${String(c.caseReserve)}`);
|
|
122
|
+
}
|
|
108
123
|
const futureCheck = opts.asOfDate === undefined
|
|
109
124
|
? notEvaluated("future-dated", CLAIM_DESCRIPTIONS["future-dated"], "no asOfDate provided")
|
|
110
125
|
: makeCheck("future-dated", CLAIM_DESCRIPTIONS["future-dated"], "fail", futureDated);
|
|
111
126
|
return summarize([
|
|
127
|
+
// First: if the numbers are not numbers, the other verdicts are noise.
|
|
128
|
+
makeCheck("non-finite-value", CLAIM_DESCRIPTIONS["non-finite-value"], "fail", nonFinite),
|
|
112
129
|
makeCheck("negative-paid", CLAIM_DESCRIPTIONS["negative-paid"], "fail", negativePaid),
|
|
113
130
|
makeCheck("negative-case", CLAIM_DESCRIPTIONS["negative-case"], "warning", negativeCase),
|
|
114
131
|
makeCheck("paid-decreasing", CLAIM_DESCRIPTIONS["paid-decreasing"], "fail", paidDecreasing),
|
|
@@ -119,12 +136,35 @@ export function reviewClaimData(claims, opts = {}) {
|
|
|
119
136
|
]);
|
|
120
137
|
}
|
|
121
138
|
const TRIANGLE_DESCRIPTIONS = {
|
|
139
|
+
"non-finite-value": "Every observed cell is a finite number (no NaN or Infinity)",
|
|
122
140
|
"shape-mismatch": "Paid and incurred triangles share the same origins and ages",
|
|
123
141
|
"paid-exceeds-incurred": "Paid never exceeds incurred in any cell (1e-9 relative tolerance)",
|
|
124
142
|
"negative-incremental-paid": "Cumulative paid is non-decreasing along each origin row (salvage/subrogation can legitimately violate this)",
|
|
125
143
|
"negative-incremental-incurred": "Cumulative incurred is non-decreasing along each origin row (case takedowns can legitimately violate this)",
|
|
126
144
|
"interior-missing": "No row has a missing cell between observed cells",
|
|
127
145
|
};
|
|
146
|
+
/**
|
|
147
|
+
* Findings for NaN/Infinity in observed cells. This check exists because every
|
|
148
|
+
* OTHER check is a relational comparison, and every relational comparison is
|
|
149
|
+
* false for NaN — so without it, a triangle of NaN cells passes the entire
|
|
150
|
+
* review and renders into disclosure Section 3 as clean data. It runs FIRST:
|
|
151
|
+
* if the numbers are not numbers, the other verdicts are noise.
|
|
152
|
+
*/
|
|
153
|
+
function nonFiniteTriangleFindings(tri) {
|
|
154
|
+
const out = [];
|
|
155
|
+
for (let i = 0; i < tri.values.length; i++) {
|
|
156
|
+
const row = tri.values[i];
|
|
157
|
+
for (let j = 0; j < row.length; j++) {
|
|
158
|
+
const v = row[j];
|
|
159
|
+
if (v === null || v === undefined)
|
|
160
|
+
continue;
|
|
161
|
+
if (!Number.isFinite(v)) {
|
|
162
|
+
out.push(`${tri.kind} ${tri.origins[i]} age ${tri.ages[j]}: ${String(v)}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
128
168
|
/** Findings where a row's cumulative values decrease between observed cells. */
|
|
129
169
|
function negativeIncrementalFindings(tri) {
|
|
130
170
|
const out = [];
|
|
@@ -151,7 +191,10 @@ function interiorMissingFindings(tri) {
|
|
|
151
191
|
const out = [];
|
|
152
192
|
for (let i = 0; i < tri.values.length; i++) {
|
|
153
193
|
const row = tri.values[i];
|
|
154
|
-
|
|
194
|
+
// undefined and null are both absences (a JSON round-trip can produce
|
|
195
|
+
// either); the negative-incremental walk above already treats them
|
|
196
|
+
// identically, and a gap must not change meaning between checks.
|
|
197
|
+
const observed = row.map((v) => v !== null && v !== undefined);
|
|
155
198
|
const first = observed.indexOf(true);
|
|
156
199
|
const last = observed.lastIndexOf(true);
|
|
157
200
|
if (first === -1)
|
|
@@ -177,12 +220,14 @@ export function reviewTriangles(paid, incurred) {
|
|
|
177
220
|
if (!sameAges) {
|
|
178
221
|
shapeFindings.push(`ages differ: paid [${paid.ages.join(", ")}] vs incurred [${incurred.ages.join(", ")}]`);
|
|
179
222
|
}
|
|
223
|
+
const nonFiniteCheck = makeCheck("non-finite-value", TRIANGLE_DESCRIPTIONS["non-finite-value"], "fail", [...nonFiniteTriangleFindings(paid), ...nonFiniteTriangleFindings(incurred)]);
|
|
180
224
|
const shapeCheck = makeCheck("shape-mismatch", TRIANGLE_DESCRIPTIONS["shape-mismatch"], "fail", shapeFindings);
|
|
181
225
|
if (shapeFindings.length > 0) {
|
|
182
226
|
// Cell-level comparisons are meaningless across mismatched grids; the
|
|
183
227
|
// remaining checks stay listed (disclosure) but are not evaluated.
|
|
184
228
|
const reason = "triangle shapes differ";
|
|
185
229
|
return summarize([
|
|
230
|
+
nonFiniteCheck,
|
|
186
231
|
shapeCheck,
|
|
187
232
|
notEvaluated("paid-exceeds-incurred", TRIANGLE_DESCRIPTIONS["paid-exceeds-incurred"], reason),
|
|
188
233
|
notEvaluated("negative-incremental-paid", TRIANGLE_DESCRIPTIONS["negative-incremental-paid"], reason),
|
|
@@ -206,6 +251,7 @@ export function reviewTriangles(paid, incurred) {
|
|
|
206
251
|
}
|
|
207
252
|
}
|
|
208
253
|
return summarize([
|
|
254
|
+
nonFiniteCheck,
|
|
209
255
|
shapeCheck,
|
|
210
256
|
makeCheck("paid-exceeds-incurred", TRIANGLE_DESCRIPTIONS["paid-exceeds-incurred"], "fail", paidExceeds),
|
|
211
257
|
makeCheck("negative-incremental-paid", TRIANGLE_DESCRIPTIONS["negative-incremental-paid"], "warning", negativeIncrementalFindings(paid)),
|
package/dist/review.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.js","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuarial-ts/data",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Data ingestion and ASOP No. 23 data-quality review for the actuarial-ts SDK: RFC 4180 CSV parsing, loss-run import, long-format triangle assembly, and claim/triangle review checks.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
|
+
"src",
|
|
18
19
|
"README.md",
|
|
19
20
|
"LICENSE",
|
|
20
21
|
"NOTICE"
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
"prepack": "tsc -p tsconfig.build.json"
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
50
|
-
"@actuarial-ts/core": "^0.
|
|
51
|
+
"@actuarial-ts/core": "^0.3.0"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"typescript": "^5.7.3",
|
package/src/csv.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal RFC 4180-subset CSV parser.
|
|
3
|
+
*
|
|
4
|
+
* Supported: comma delimiter, quoted fields, doubled quotes ("") as escaped
|
|
5
|
+
* quotes inside quoted fields, commas and newlines inside quoted fields,
|
|
6
|
+
* CRLF and LF row endings, a leading UTF-8 BOM, and a tolerated trailing
|
|
7
|
+
* newline. Lines that are completely empty outside quotes are skipped.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately NOT here: header handling, type coercion, and shape
|
|
10
|
+
* validation. Ragged rows are preserved as-is — validation is the caller's
|
|
11
|
+
* job (see lossRun.ts).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface CsvParseResult {
|
|
15
|
+
/** The parsed grid (rows x columns). Ragged rows preserved as-is. */
|
|
16
|
+
rows: string[][];
|
|
17
|
+
/**
|
|
18
|
+
* Structural problems the parser recovered from. Content is still parsed
|
|
19
|
+
* leniently — a warning here means the OUTPUT may not mean what the file's
|
|
20
|
+
* author intended, which the caller must surface rather than swallow: an
|
|
21
|
+
* unterminated quote consumes the remainder of the input into one field,
|
|
22
|
+
* and five good rows silently becoming one is how claims vanish.
|
|
23
|
+
*/
|
|
24
|
+
warnings: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Parses CSV text into a grid of string fields plus structural warnings. */
|
|
28
|
+
export function parseCsv(text: string): CsvParseResult {
|
|
29
|
+
let s = text;
|
|
30
|
+
if (s.length > 0 && s.charCodeAt(0) === 0xfeff) s = s.slice(1);
|
|
31
|
+
|
|
32
|
+
const rows: string[][] = [];
|
|
33
|
+
const warnings: string[] = [];
|
|
34
|
+
let row: string[] = [];
|
|
35
|
+
let field = "";
|
|
36
|
+
let inQuotes = false;
|
|
37
|
+
let fieldWasQuoted = false;
|
|
38
|
+
let line = 1;
|
|
39
|
+
let quoteOpenedAtLine = 0;
|
|
40
|
+
|
|
41
|
+
const endField = (): void => {
|
|
42
|
+
row.push(field);
|
|
43
|
+
field = "";
|
|
44
|
+
fieldWasQuoted = false;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const endRow = (): void => {
|
|
48
|
+
// A line with zero characters outside quotes is skipped; a line like
|
|
49
|
+
// `""` or `,` still produces a row.
|
|
50
|
+
if (row.length === 0 && field === "" && !fieldWasQuoted) return;
|
|
51
|
+
endField();
|
|
52
|
+
rows.push(row);
|
|
53
|
+
row = [];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
let i = 0;
|
|
57
|
+
while (i < s.length) {
|
|
58
|
+
const ch = s[i]!;
|
|
59
|
+
if (inQuotes) {
|
|
60
|
+
if (ch === '"') {
|
|
61
|
+
if (s[i + 1] === '"') {
|
|
62
|
+
field += '"';
|
|
63
|
+
i += 2;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
inQuotes = false;
|
|
67
|
+
i++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (ch === "\n") line++;
|
|
71
|
+
field += ch;
|
|
72
|
+
i++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (ch === '"' && field === "" && !fieldWasQuoted) {
|
|
76
|
+
inQuotes = true;
|
|
77
|
+
quoteOpenedAtLine = line;
|
|
78
|
+
fieldWasQuoted = true;
|
|
79
|
+
i++;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (ch === ",") {
|
|
83
|
+
endField();
|
|
84
|
+
i++;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (ch === "\r") {
|
|
88
|
+
if (s[i + 1] === "\n") i++;
|
|
89
|
+
endRow();
|
|
90
|
+
line++;
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (ch === "\n") {
|
|
95
|
+
endRow();
|
|
96
|
+
line++;
|
|
97
|
+
i++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
field += ch;
|
|
101
|
+
i++;
|
|
102
|
+
}
|
|
103
|
+
// Flush a final row that has no trailing newline. An unterminated quoted
|
|
104
|
+
// field still flushes with whatever it accumulated — content stays lenient —
|
|
105
|
+
// but the structural problem is REPORTED: everything after the stray quote
|
|
106
|
+
// was consumed into one field, and the caller must not present that output
|
|
107
|
+
// as a faithful read of the file.
|
|
108
|
+
if (inQuotes) {
|
|
109
|
+
warnings.push(
|
|
110
|
+
`unterminated quoted field starting at line ${quoteOpenedAtLine}: the remainder of the ` +
|
|
111
|
+
"input was consumed into a single field; check the file for a stray or unescaped quote",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
endRow();
|
|
115
|
+
return { rows, warnings };
|
|
116
|
+
}
|
package/src/index.ts
ADDED
|
Binary file
|
package/src/lossRun.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { ClaimSnapshot } from "@actuarial-ts/core";
|
|
2
|
+
import { ReservingError } from "@actuarial-ts/core";
|
|
3
|
+
import { parseCsv } from "./csv.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Loss-run CSV import. Mirrors the workbench import contract:
|
|
7
|
+
*
|
|
8
|
+
* Required columns (one row per claim per evaluation snapshot):
|
|
9
|
+
* claim_id, accident_date, report_date, evaluation_date,
|
|
10
|
+
* paid_to_date, case_reserve, status
|
|
11
|
+
*
|
|
12
|
+
* Headers are normalized (trimmed, lower-cased, whitespace -> underscores);
|
|
13
|
+
* extra columns are ignored. Missing required columns make the file
|
|
14
|
+
* unparseable and throw ReservingError("SHAPE", ...). Row-level failures are
|
|
15
|
+
* collected — not thrown — so the caller decides whether to abort or load
|
|
16
|
+
* the clean rows; a row with any error contributes no claim.
|
|
17
|
+
*
|
|
18
|
+
* Row numbers in errors are 1-based INCLUDING the header row, so the first
|
|
19
|
+
* data row is row 2.
|
|
20
|
+
*
|
|
21
|
+
* Unlike the workbench importer, negative paid/case amounts are accepted
|
|
22
|
+
* here: the ASOP 23 review layer (reviewClaimData) flags them, keeping the
|
|
23
|
+
* signal visible instead of silently rejecting salvage/subrogation rows.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const REQUIRED_COLUMNS = [
|
|
27
|
+
"claim_id",
|
|
28
|
+
"accident_date",
|
|
29
|
+
"report_date",
|
|
30
|
+
"evaluation_date",
|
|
31
|
+
"paid_to_date",
|
|
32
|
+
"case_reserve",
|
|
33
|
+
"status",
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
export interface LossRunRowError {
|
|
37
|
+
/** 1-based row number including the header (first data row = 2). */
|
|
38
|
+
row: number;
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface LossRunParseResult {
|
|
43
|
+
claims: ClaimSnapshot[];
|
|
44
|
+
errors: LossRunRowError[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeHeader(h: string): string {
|
|
48
|
+
return h.trim().toLowerCase().replace(/\s+/g, "_");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** True when the string is a real calendar date in yyyy-mm-dd form. */
|
|
52
|
+
function isValidIsoDate(value: string): boolean {
|
|
53
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
54
|
+
if (!match) return false;
|
|
55
|
+
const y = Number(match[1]);
|
|
56
|
+
const m = Number(match[2]);
|
|
57
|
+
const d = Number(match[3]);
|
|
58
|
+
if (m < 1 || m > 12 || d < 1) return false;
|
|
59
|
+
// Arithmetic rule, mirroring compliance/src/metadata.ts (this package cannot
|
|
60
|
+
// import compliance — the dependency points the other way). The previous
|
|
61
|
+
// Date.UTC form mapped years 0-99 into the 1900s; the mapping happens to
|
|
62
|
+
// preserve leapness for every year except 0000, so this is a correctness-of-
|
|
63
|
+
// form consolidation rather than a live-bug fix.
|
|
64
|
+
const leap = (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
|
|
65
|
+
const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m - 1]!;
|
|
66
|
+
return d <= daysInMonth;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Parses a loss-run CSV into ClaimSnapshots plus per-row validation errors. */
|
|
70
|
+
export function parseLossRunCsv(text: string): LossRunParseResult {
|
|
71
|
+
const { rows: grid, warnings: csvWarnings } = parseCsv(text);
|
|
72
|
+
const headers = (grid[0] ?? []).map(normalizeHeader);
|
|
73
|
+
const missing = REQUIRED_COLUMNS.filter((c) => !headers.includes(c));
|
|
74
|
+
if (missing.length > 0) {
|
|
75
|
+
throw new ReservingError(
|
|
76
|
+
"SHAPE",
|
|
77
|
+
`Missing required column(s): ${missing.join(", ")}. Found: ${
|
|
78
|
+
headers.filter(Boolean).join(", ") || "(none)"
|
|
79
|
+
}`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const columnIndex = new Map<string, number>();
|
|
83
|
+
headers.forEach((h, idx) => {
|
|
84
|
+
if (!columnIndex.has(h)) columnIndex.set(h, idx);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const claims: ClaimSnapshot[] = [];
|
|
88
|
+
const errors: LossRunRowError[] = [];
|
|
89
|
+
for (const warning of csvWarnings) {
|
|
90
|
+
// Structural CSV problems surface through the same channel as row errors:
|
|
91
|
+
// partial loading is intended behavior here, silent partial loading is not.
|
|
92
|
+
const lineMatch = warning.match(/line (\d+)/);
|
|
93
|
+
errors.push({ row: lineMatch ? Number(lineMatch[1]) : 1, message: `CSV structure: ${warning}` });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (let r = 1; r < grid.length; r++) {
|
|
97
|
+
const rowNumber = r + 1; // 1-based including the header row
|
|
98
|
+
const cells = grid[r]!;
|
|
99
|
+
const cell = (name: string): string => (cells[columnIndex.get(name)!] ?? "").trim();
|
|
100
|
+
const rowErrors: string[] = [];
|
|
101
|
+
|
|
102
|
+
const claimId = cell("claim_id");
|
|
103
|
+
if (claimId === "") rowErrors.push("claim_id is required");
|
|
104
|
+
|
|
105
|
+
const date = (name: string): string | null => {
|
|
106
|
+
const value = cell(name);
|
|
107
|
+
if (!isValidIsoDate(value)) {
|
|
108
|
+
rowErrors.push(`${name} must be a real calendar date in yyyy-mm-dd form (got "${value}")`);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
};
|
|
113
|
+
const accidentDate = date("accident_date");
|
|
114
|
+
const reportDate = date("report_date");
|
|
115
|
+
const evaluationDate = date("evaluation_date");
|
|
116
|
+
|
|
117
|
+
const amount = (name: string): number | null => {
|
|
118
|
+
const value = cell(name);
|
|
119
|
+
// Currency is decimal digits with an optional sign and fraction —
|
|
120
|
+
// Number() also accepts hex (0x2710 -> 10000), binary, octal and
|
|
121
|
+
// scientific notation, which silently changes magnitudes instead of
|
|
122
|
+
// erroring. Formatted amounts are rejected with a pointed message
|
|
123
|
+
// rather than guessed at: "1,234" could be one thousand or 1.234
|
|
124
|
+
// depending on locale, and a loss run is no place to guess.
|
|
125
|
+
if (/[,()]|\s/.test(value.trim()) && value.trim() !== "") {
|
|
126
|
+
rowErrors.push(
|
|
127
|
+
`${name} must be an unformatted decimal (got "${value}"); remove thousands separators, ` +
|
|
128
|
+
"parentheses and spaces",
|
|
129
|
+
);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const n = /^-?\d+(\.\d+)?$/.test(value.trim()) ? Number(value.trim()) : NaN;
|
|
133
|
+
if (!Number.isFinite(n)) {
|
|
134
|
+
rowErrors.push(`${name} must be a finite decimal number (got "${value}")`);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return n;
|
|
138
|
+
};
|
|
139
|
+
const paidToDate = amount("paid_to_date");
|
|
140
|
+
const caseReserve = amount("case_reserve");
|
|
141
|
+
|
|
142
|
+
const statusRaw = cell("status").toLowerCase();
|
|
143
|
+
const status: "open" | "closed" | null =
|
|
144
|
+
statusRaw === "open" || statusRaw === "closed" ? statusRaw : null;
|
|
145
|
+
if (status === null) {
|
|
146
|
+
rowErrors.push(`status must be "open" or "closed" (got "${cell("status")}")`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Cross-field checks need all three dates to be individually valid.
|
|
150
|
+
if (accidentDate !== null && reportDate !== null && evaluationDate !== null) {
|
|
151
|
+
if (reportDate < accidentDate) rowErrors.push("report_date precedes accident_date");
|
|
152
|
+
if (evaluationDate < reportDate) rowErrors.push("evaluation_date precedes report_date");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (rowErrors.length > 0) {
|
|
156
|
+
for (const message of rowErrors) errors.push({ row: rowNumber, message });
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
claims.push({
|
|
160
|
+
claimId,
|
|
161
|
+
accidentDate: accidentDate!,
|
|
162
|
+
reportDate: reportDate!,
|
|
163
|
+
evaluationDate: evaluationDate!,
|
|
164
|
+
paidToDate: paidToDate!,
|
|
165
|
+
caseReserve: caseReserve!,
|
|
166
|
+
status: status!,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { claims, errors };
|
|
171
|
+
}
|
package/src/review.ts
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import type { ClaimSnapshot, Triangle } from "@actuarial-ts/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ASOP No. 23 (Data Quality)-oriented data review.
|
|
5
|
+
*
|
|
6
|
+
* The report lists every check PERFORMED, not just the ones that found
|
|
7
|
+
* something — the actuary's disclosure needs "what was reviewed" as much as
|
|
8
|
+
* "what was found". A check that could not be evaluated (no as-of date; a
|
|
9
|
+
* triangle shape mismatch blocking cell comparisons) stays in the report
|
|
10
|
+
* with an explicit "not evaluated" detail so the disclosure never overstates
|
|
11
|
+
* the review.
|
|
12
|
+
*
|
|
13
|
+
* Severity philosophy:
|
|
14
|
+
* - "fail" = the data is wrong or internally inconsistent (negative paid,
|
|
15
|
+
* dates out of order, duplicate snapshots, paid > incurred).
|
|
16
|
+
* - "warning" = legitimate but rare/reportable (negative case reserves,
|
|
17
|
+
* salvage-driven negative incremental paid, closed claims
|
|
18
|
+
* still carrying case).
|
|
19
|
+
*
|
|
20
|
+
* These utilities are designed to support the actuary's compliance with
|
|
21
|
+
* ASOP No. 23; responsibility for compliance remains with the credentialed
|
|
22
|
+
* actuary.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export type DataCheckStatus = "pass" | "warning" | "fail" | "not-evaluated";
|
|
26
|
+
|
|
27
|
+
export interface DataCheck {
|
|
28
|
+
id: string;
|
|
29
|
+
description: string;
|
|
30
|
+
status: DataCheckStatus;
|
|
31
|
+
details: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DataReviewReport {
|
|
35
|
+
checks: DataCheck[];
|
|
36
|
+
summary: { pass: number; warning: number; fail: number; notEvaluated: number };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ReviewClaimDataOptions {
|
|
40
|
+
/** ISO date; when given, any claim date after it fails "future-dated". */
|
|
41
|
+
asOfDate?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** At most this many offending items are listed per check, then "+N more". */
|
|
45
|
+
const MAX_DETAILS = 20;
|
|
46
|
+
|
|
47
|
+
function capDetails(items: string[]): string[] {
|
|
48
|
+
if (items.length <= MAX_DETAILS) return items;
|
|
49
|
+
return [...items.slice(0, MAX_DETAILS), `+${items.length - MAX_DETAILS} more`];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function makeCheck(
|
|
53
|
+
id: string,
|
|
54
|
+
description: string,
|
|
55
|
+
statusWhenFound: "warning" | "fail",
|
|
56
|
+
findings: string[],
|
|
57
|
+
): DataCheck {
|
|
58
|
+
return {
|
|
59
|
+
id,
|
|
60
|
+
description,
|
|
61
|
+
status: findings.length > 0 ? statusWhenFound : "pass",
|
|
62
|
+
details: capDetails(findings),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function notEvaluated(id: string, description: string, reason: string): DataCheck {
|
|
67
|
+
// A check that could not run is REPORTED as such - counting it as "pass"
|
|
68
|
+
// would overstate the review in the very disclosure this feeds.
|
|
69
|
+
return { id, description, status: "not-evaluated", details: [`not evaluated: ${reason}`] };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function summarize(checks: DataCheck[]): DataReviewReport {
|
|
73
|
+
const summary = { pass: 0, warning: 0, fail: 0, notEvaluated: 0 };
|
|
74
|
+
for (const c of checks) {
|
|
75
|
+
if (c.status === "not-evaluated") summary.notEvaluated++;
|
|
76
|
+
else summary[c.status]++;
|
|
77
|
+
}
|
|
78
|
+
return { checks, summary };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const CLAIM_DESCRIPTIONS = {
|
|
82
|
+
"non-finite-value": "Every money field is a finite number (no NaN or Infinity)",
|
|
83
|
+
"negative-paid": "Cumulative paid amounts are non-negative",
|
|
84
|
+
"negative-case": "Case reserves are non-negative (negative case is legitimate but rare)",
|
|
85
|
+
"paid-decreasing":
|
|
86
|
+
"Cumulative paid never decreases across a claim's snapshots ordered by evaluation date",
|
|
87
|
+
"date-order": "accident_date <= report_date <= evaluation_date on every snapshot",
|
|
88
|
+
"duplicate-snapshot": "No claim has two snapshots at the same evaluation date",
|
|
89
|
+
"future-dated": "No claim date exceeds the as-of date",
|
|
90
|
+
"closed-with-case": "Closed claims carry no outstanding case reserve",
|
|
91
|
+
} as const;
|
|
92
|
+
|
|
93
|
+
/** Reviews claim-level snapshots against the ASOP 23-oriented check suite. */
|
|
94
|
+
export function reviewClaimData(
|
|
95
|
+
claims: ClaimSnapshot[],
|
|
96
|
+
opts: ReviewClaimDataOptions = {},
|
|
97
|
+
): DataReviewReport {
|
|
98
|
+
const negativePaid: string[] = [];
|
|
99
|
+
const negativeCase: string[] = [];
|
|
100
|
+
const dateOrder: string[] = [];
|
|
101
|
+
const futureDated: string[] = [];
|
|
102
|
+
const closedWithCase: string[] = [];
|
|
103
|
+
|
|
104
|
+
claims.forEach((c) => {
|
|
105
|
+
// Identified by claimId + evaluation date, which the DATA carries. The old
|
|
106
|
+
// label fabricated "row N" from this array's index — but this function
|
|
107
|
+
// receives parsed claims, not the file, and parseLossRunCsv numbers real
|
|
108
|
+
// CSV rows (header-inclusive) in its own errors. Two different "row"
|
|
109
|
+
// meanings pointed auditors at the wrong line; an identifier we cannot
|
|
110
|
+
// compute is one we must not print.
|
|
111
|
+
const where = `claim ${c.claimId} (eval ${c.evaluationDate})`;
|
|
112
|
+
if (c.paidToDate < 0) {
|
|
113
|
+
negativePaid.push(`${where}: paid_to_date ${c.paidToDate}`);
|
|
114
|
+
}
|
|
115
|
+
if (c.caseReserve < 0) {
|
|
116
|
+
negativeCase.push(`${where}: case_reserve ${c.caseReserve}`);
|
|
117
|
+
}
|
|
118
|
+
if (c.reportDate < c.accidentDate) {
|
|
119
|
+
dateOrder.push(
|
|
120
|
+
`${where}: report_date ${c.reportDate} precedes accident_date ${c.accidentDate}`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (c.evaluationDate < c.reportDate) {
|
|
124
|
+
dateOrder.push(
|
|
125
|
+
`${where}: evaluation_date ${c.evaluationDate} precedes report_date ${c.reportDate}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (opts.asOfDate !== undefined) {
|
|
129
|
+
const asOf = opts.asOfDate;
|
|
130
|
+
if (c.accidentDate > asOf) {
|
|
131
|
+
futureDated.push(`${where}: accident_date ${c.accidentDate} exceeds as-of ${asOf}`);
|
|
132
|
+
}
|
|
133
|
+
if (c.reportDate > asOf) {
|
|
134
|
+
futureDated.push(`${where}: report_date ${c.reportDate} exceeds as-of ${asOf}`);
|
|
135
|
+
}
|
|
136
|
+
if (c.evaluationDate > asOf) {
|
|
137
|
+
futureDated.push(`${where}: evaluation_date ${c.evaluationDate} exceeds as-of ${asOf}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (c.status === "closed" && c.caseReserve > 0) {
|
|
141
|
+
closedWithCase.push(`${where}: case_reserve ${c.caseReserve} on a closed claim`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Per-claim timeline checks: duplicates and decreasing cumulative paid.
|
|
146
|
+
const byClaim = new Map<string, ClaimSnapshot[]>();
|
|
147
|
+
for (const c of claims) {
|
|
148
|
+
const list = byClaim.get(c.claimId);
|
|
149
|
+
if (list) list.push(c);
|
|
150
|
+
else byClaim.set(c.claimId, [c]);
|
|
151
|
+
}
|
|
152
|
+
const paidDecreasing: string[] = [];
|
|
153
|
+
const duplicates: string[] = [];
|
|
154
|
+
for (const [claimId, snaps] of byClaim) {
|
|
155
|
+
const sorted = [...snaps].sort((a, b) => a.evaluationDate.localeCompare(b.evaluationDate));
|
|
156
|
+
const seenEvals = new Set<string>();
|
|
157
|
+
for (const s of sorted) {
|
|
158
|
+
if (seenEvals.has(s.evaluationDate)) {
|
|
159
|
+
duplicates.push(`claim ${claimId}: duplicate snapshot at ${s.evaluationDate}`);
|
|
160
|
+
}
|
|
161
|
+
seenEvals.add(s.evaluationDate);
|
|
162
|
+
}
|
|
163
|
+
for (let k = 1; k < sorted.length; k++) {
|
|
164
|
+
const prev = sorted[k - 1]!;
|
|
165
|
+
const cur = sorted[k]!;
|
|
166
|
+
// Same-date pairs are the duplicate check's finding, not this one's.
|
|
167
|
+
if (cur.evaluationDate === prev.evaluationDate) continue;
|
|
168
|
+
if (cur.paidToDate < prev.paidToDate) {
|
|
169
|
+
paidDecreasing.push(
|
|
170
|
+
`claim ${claimId}: paid_to_date ${prev.paidToDate} -> ${cur.paidToDate} between ${prev.evaluationDate} and ${cur.evaluationDate}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const nonFinite: string[] = [];
|
|
177
|
+
for (const c of claims) {
|
|
178
|
+
const where = `claim ${c.claimId}`;
|
|
179
|
+
if (!Number.isFinite(c.paidToDate)) nonFinite.push(`${where}: paid_to_date ${String(c.paidToDate)}`);
|
|
180
|
+
if (!Number.isFinite(c.caseReserve)) nonFinite.push(`${where}: case_reserve ${String(c.caseReserve)}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const futureCheck =
|
|
184
|
+
opts.asOfDate === undefined
|
|
185
|
+
? notEvaluated("future-dated", CLAIM_DESCRIPTIONS["future-dated"], "no asOfDate provided")
|
|
186
|
+
: makeCheck("future-dated", CLAIM_DESCRIPTIONS["future-dated"], "fail", futureDated);
|
|
187
|
+
|
|
188
|
+
return summarize([
|
|
189
|
+
// First: if the numbers are not numbers, the other verdicts are noise.
|
|
190
|
+
makeCheck("non-finite-value", CLAIM_DESCRIPTIONS["non-finite-value"], "fail", nonFinite),
|
|
191
|
+
makeCheck("negative-paid", CLAIM_DESCRIPTIONS["negative-paid"], "fail", negativePaid),
|
|
192
|
+
makeCheck("negative-case", CLAIM_DESCRIPTIONS["negative-case"], "warning", negativeCase),
|
|
193
|
+
makeCheck("paid-decreasing", CLAIM_DESCRIPTIONS["paid-decreasing"], "fail", paidDecreasing),
|
|
194
|
+
makeCheck("date-order", CLAIM_DESCRIPTIONS["date-order"], "fail", dateOrder),
|
|
195
|
+
makeCheck("duplicate-snapshot", CLAIM_DESCRIPTIONS["duplicate-snapshot"], "fail", duplicates),
|
|
196
|
+
futureCheck,
|
|
197
|
+
makeCheck("closed-with-case", CLAIM_DESCRIPTIONS["closed-with-case"], "warning", closedWithCase),
|
|
198
|
+
]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const TRIANGLE_DESCRIPTIONS = {
|
|
202
|
+
"non-finite-value": "Every observed cell is a finite number (no NaN or Infinity)",
|
|
203
|
+
"shape-mismatch": "Paid and incurred triangles share the same origins and ages",
|
|
204
|
+
"paid-exceeds-incurred": "Paid never exceeds incurred in any cell (1e-9 relative tolerance)",
|
|
205
|
+
"negative-incremental-paid":
|
|
206
|
+
"Cumulative paid is non-decreasing along each origin row (salvage/subrogation can legitimately violate this)",
|
|
207
|
+
"negative-incremental-incurred":
|
|
208
|
+
"Cumulative incurred is non-decreasing along each origin row (case takedowns can legitimately violate this)",
|
|
209
|
+
"interior-missing": "No row has a missing cell between observed cells",
|
|
210
|
+
} as const;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Findings for NaN/Infinity in observed cells. This check exists because every
|
|
214
|
+
* OTHER check is a relational comparison, and every relational comparison is
|
|
215
|
+
* false for NaN — so without it, a triangle of NaN cells passes the entire
|
|
216
|
+
* review and renders into disclosure Section 3 as clean data. It runs FIRST:
|
|
217
|
+
* if the numbers are not numbers, the other verdicts are noise.
|
|
218
|
+
*/
|
|
219
|
+
function nonFiniteTriangleFindings(tri: Triangle): string[] {
|
|
220
|
+
const out: string[] = [];
|
|
221
|
+
for (let i = 0; i < tri.values.length; i++) {
|
|
222
|
+
const row = tri.values[i]!;
|
|
223
|
+
for (let j = 0; j < row.length; j++) {
|
|
224
|
+
const v = row[j];
|
|
225
|
+
if (v === null || v === undefined) continue;
|
|
226
|
+
if (!Number.isFinite(v)) {
|
|
227
|
+
out.push(`${tri.kind} ${tri.origins[i]} age ${tri.ages[j]}: ${String(v)}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Findings where a row's cumulative values decrease between observed cells. */
|
|
235
|
+
function negativeIncrementalFindings(tri: Triangle): string[] {
|
|
236
|
+
const out: string[] = [];
|
|
237
|
+
for (let i = 0; i < tri.values.length; i++) {
|
|
238
|
+
const row = tri.values[i]!;
|
|
239
|
+
let prev: number | null = null;
|
|
240
|
+
let prevAge: number | null = null;
|
|
241
|
+
for (let j = 0; j < row.length; j++) {
|
|
242
|
+
const v = row[j];
|
|
243
|
+
if (v === null || v === undefined) continue;
|
|
244
|
+
const age = tri.ages[j]!;
|
|
245
|
+
if (prev !== null && v < prev) {
|
|
246
|
+
out.push(
|
|
247
|
+
`${tri.kind} ${tri.origins[i]} age ${prevAge} -> ${age}: ${prev} -> ${v}`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
prev = v;
|
|
251
|
+
prevAge = age;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Findings for null cells with observed cells both before and after in the row. */
|
|
258
|
+
function interiorMissingFindings(tri: Triangle): string[] {
|
|
259
|
+
const out: string[] = [];
|
|
260
|
+
for (let i = 0; i < tri.values.length; i++) {
|
|
261
|
+
const row = tri.values[i]!;
|
|
262
|
+
// undefined and null are both absences (a JSON round-trip can produce
|
|
263
|
+
// either); the negative-incremental walk above already treats them
|
|
264
|
+
// identically, and a gap must not change meaning between checks.
|
|
265
|
+
const observed = row.map((v) => v !== null && v !== undefined);
|
|
266
|
+
const first = observed.indexOf(true);
|
|
267
|
+
const last = observed.lastIndexOf(true);
|
|
268
|
+
if (first === -1) continue;
|
|
269
|
+
for (let j = first + 1; j < last; j++) {
|
|
270
|
+
if (!observed[j]) {
|
|
271
|
+
out.push(`${tri.kind} ${tri.origins[i]} age ${tri.ages[j]}: interior cell missing`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Reviews a paid/incurred triangle pair for cross-triangle consistency. */
|
|
279
|
+
export function reviewTriangles(paid: Triangle, incurred: Triangle): DataReviewReport {
|
|
280
|
+
const shapeFindings: string[] = [];
|
|
281
|
+
const sameOrigins =
|
|
282
|
+
paid.origins.length === incurred.origins.length &&
|
|
283
|
+
paid.origins.every((o, i) => o === incurred.origins[i]);
|
|
284
|
+
const sameAges =
|
|
285
|
+
paid.ages.length === incurred.ages.length &&
|
|
286
|
+
paid.ages.every((a, j) => a === incurred.ages[j]);
|
|
287
|
+
if (!sameOrigins) {
|
|
288
|
+
shapeFindings.push(
|
|
289
|
+
`origins differ: paid [${paid.origins.join(", ")}] vs incurred [${incurred.origins.join(", ")}]`,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
if (!sameAges) {
|
|
293
|
+
shapeFindings.push(
|
|
294
|
+
`ages differ: paid [${paid.ages.join(", ")}] vs incurred [${incurred.ages.join(", ")}]`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
const nonFiniteCheck = makeCheck(
|
|
298
|
+
"non-finite-value",
|
|
299
|
+
TRIANGLE_DESCRIPTIONS["non-finite-value"],
|
|
300
|
+
"fail",
|
|
301
|
+
[...nonFiniteTriangleFindings(paid), ...nonFiniteTriangleFindings(incurred)],
|
|
302
|
+
);
|
|
303
|
+
const shapeCheck = makeCheck(
|
|
304
|
+
"shape-mismatch",
|
|
305
|
+
TRIANGLE_DESCRIPTIONS["shape-mismatch"],
|
|
306
|
+
"fail",
|
|
307
|
+
shapeFindings,
|
|
308
|
+
);
|
|
309
|
+
if (shapeFindings.length > 0) {
|
|
310
|
+
// Cell-level comparisons are meaningless across mismatched grids; the
|
|
311
|
+
// remaining checks stay listed (disclosure) but are not evaluated.
|
|
312
|
+
const reason = "triangle shapes differ";
|
|
313
|
+
return summarize([
|
|
314
|
+
nonFiniteCheck,
|
|
315
|
+
shapeCheck,
|
|
316
|
+
notEvaluated("paid-exceeds-incurred", TRIANGLE_DESCRIPTIONS["paid-exceeds-incurred"], reason),
|
|
317
|
+
notEvaluated(
|
|
318
|
+
"negative-incremental-paid",
|
|
319
|
+
TRIANGLE_DESCRIPTIONS["negative-incremental-paid"],
|
|
320
|
+
reason,
|
|
321
|
+
),
|
|
322
|
+
notEvaluated(
|
|
323
|
+
"negative-incremental-incurred",
|
|
324
|
+
TRIANGLE_DESCRIPTIONS["negative-incremental-incurred"],
|
|
325
|
+
reason,
|
|
326
|
+
),
|
|
327
|
+
notEvaluated("interior-missing", TRIANGLE_DESCRIPTIONS["interior-missing"], reason),
|
|
328
|
+
]);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const paidExceeds: string[] = [];
|
|
332
|
+
for (let i = 0; i < paid.values.length; i++) {
|
|
333
|
+
const paidRow = paid.values[i]!;
|
|
334
|
+
const incRow = incurred.values[i]!;
|
|
335
|
+
for (let j = 0; j < paidRow.length; j++) {
|
|
336
|
+
const p = paidRow[j];
|
|
337
|
+
const inc = incRow[j];
|
|
338
|
+
if (p === null || p === undefined || inc === null || inc === undefined) continue;
|
|
339
|
+
const tolerance = 1e-9 * Math.max(1, Math.abs(p), Math.abs(inc));
|
|
340
|
+
if (p - inc > tolerance) {
|
|
341
|
+
paidExceeds.push(`${paid.origins[i]} age ${paid.ages[j]}: paid ${p} > incurred ${inc}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return summarize([
|
|
347
|
+
nonFiniteCheck,
|
|
348
|
+
shapeCheck,
|
|
349
|
+
makeCheck(
|
|
350
|
+
"paid-exceeds-incurred",
|
|
351
|
+
TRIANGLE_DESCRIPTIONS["paid-exceeds-incurred"],
|
|
352
|
+
"fail",
|
|
353
|
+
paidExceeds,
|
|
354
|
+
),
|
|
355
|
+
makeCheck(
|
|
356
|
+
"negative-incremental-paid",
|
|
357
|
+
TRIANGLE_DESCRIPTIONS["negative-incremental-paid"],
|
|
358
|
+
"warning",
|
|
359
|
+
negativeIncrementalFindings(paid),
|
|
360
|
+
),
|
|
361
|
+
makeCheck(
|
|
362
|
+
"negative-incremental-incurred",
|
|
363
|
+
TRIANGLE_DESCRIPTIONS["negative-incremental-incurred"],
|
|
364
|
+
"warning",
|
|
365
|
+
negativeIncrementalFindings(incurred),
|
|
366
|
+
),
|
|
367
|
+
makeCheck(
|
|
368
|
+
"interior-missing",
|
|
369
|
+
TRIANGLE_DESCRIPTIONS["interior-missing"],
|
|
370
|
+
"warning",
|
|
371
|
+
[...interiorMissingFindings(paid), ...interiorMissingFindings(incurred)],
|
|
372
|
+
),
|
|
373
|
+
]);
|
|
374
|
+
}
|