@claritylabs/cl-sdk 3.1.4 → 3.1.6
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/application.d.mts +1 -1
- package/dist/application.d.ts +1 -1
- package/dist/{index-BbDPEnG9.d.mts → index-C-jIqJru.d.mts} +163 -163
- package/dist/{index-BbDPEnG9.d.ts → index-C-jIqJru.d.ts} +163 -163
- package/dist/index.d.mts +59 -58
- package/dist/index.d.ts +59 -58
- package/dist/index.js +417 -46
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +417 -46
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -80,53 +80,70 @@ function sanitizeNulls(obj) {
|
|
|
80
80
|
|
|
81
81
|
// src/core/strict-schema.ts
|
|
82
82
|
import { z } from "zod";
|
|
83
|
+
function schemaDef(schema) {
|
|
84
|
+
return schema._zod?.def ?? schema._def ?? {};
|
|
85
|
+
}
|
|
86
|
+
function schemaKind(schema) {
|
|
87
|
+
const def = schemaDef(schema);
|
|
88
|
+
const raw = typeof def.type === "string" ? def.type : typeof def.typeName === "string" ? def.typeName : typeof schema.type === "string" ? schema.type : void 0;
|
|
89
|
+
return raw?.replace(/^Zod/, "").toLowerCase();
|
|
90
|
+
}
|
|
91
|
+
function schemaDescription(schema) {
|
|
92
|
+
const def = schemaDef(schema);
|
|
93
|
+
return schema.description ?? def.description;
|
|
94
|
+
}
|
|
95
|
+
function objectShape(schema) {
|
|
96
|
+
const def = schemaDef(schema);
|
|
97
|
+
const shape = schema.shape ?? def.shape;
|
|
98
|
+
return typeof shape === "function" ? shape() : shape;
|
|
99
|
+
}
|
|
100
|
+
function withDescription(schema, description) {
|
|
101
|
+
return description ? schema.describe(description) : schema;
|
|
102
|
+
}
|
|
83
103
|
function toStrictSchema(schema) {
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
const shape = schema
|
|
104
|
+
const kind = schemaKind(schema);
|
|
105
|
+
const def = schemaDef(schema);
|
|
106
|
+
if (kind === "object") {
|
|
107
|
+
const shape = objectShape(schema);
|
|
88
108
|
if (!shape) return schema;
|
|
89
109
|
const newShape = {};
|
|
90
110
|
for (const [key, value] of Object.entries(shape)) {
|
|
91
111
|
const field = value;
|
|
92
|
-
const fieldDef = field
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
112
|
+
const fieldDef = schemaDef(field);
|
|
113
|
+
const fieldKind = schemaKind(field);
|
|
114
|
+
if (fieldKind === "optional") {
|
|
95
115
|
const innerType = fieldDef?.innerType;
|
|
96
|
-
const description = field
|
|
116
|
+
const description = schemaDescription(field);
|
|
97
117
|
if (innerType) {
|
|
98
118
|
const transformed = toStrictSchema(innerType);
|
|
99
|
-
|
|
100
|
-
if (description) nullable = nullable.describe(description);
|
|
101
|
-
newShape[key] = nullable;
|
|
119
|
+
newShape[key] = withDescription(z.nullable(transformed), description);
|
|
102
120
|
} else {
|
|
103
|
-
|
|
104
|
-
if (description) nullable = nullable.describe(description);
|
|
105
|
-
newShape[key] = nullable;
|
|
121
|
+
newShape[key] = withDescription(z.nullable(field), description);
|
|
106
122
|
}
|
|
107
123
|
} else {
|
|
108
124
|
newShape[key] = toStrictSchema(field);
|
|
109
125
|
}
|
|
110
126
|
}
|
|
111
|
-
|
|
112
|
-
const result = z.object(newShape);
|
|
113
|
-
return objDesc ? result.describe(objDesc) : result;
|
|
127
|
+
return withDescription(z.object(newShape), schemaDescription(schema));
|
|
114
128
|
}
|
|
115
|
-
if (
|
|
116
|
-
const element = def
|
|
129
|
+
if (kind === "array") {
|
|
130
|
+
const element = def.element ?? def.type ?? schema.element;
|
|
117
131
|
if (element) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
132
|
+
return withDescription(z.array(toStrictSchema(element)), schemaDescription(schema));
|
|
133
|
+
}
|
|
134
|
+
return schema;
|
|
135
|
+
}
|
|
136
|
+
if (kind === "nullable") {
|
|
137
|
+
const innerType = def?.innerType;
|
|
138
|
+
if (innerType) {
|
|
139
|
+
return withDescription(z.nullable(toStrictSchema(innerType)), schemaDescription(schema));
|
|
121
140
|
}
|
|
122
141
|
return schema;
|
|
123
142
|
}
|
|
124
|
-
if (
|
|
143
|
+
if (kind === "default") {
|
|
125
144
|
const innerType = def?.innerType;
|
|
126
145
|
if (innerType) {
|
|
127
|
-
|
|
128
|
-
const result = z.nullable(toStrictSchema(innerType));
|
|
129
|
-
return nullDesc ? result.describe(nullDesc) : result;
|
|
146
|
+
return withDescription(z.nullable(toStrictSchema(innerType)), schemaDescription(schema));
|
|
130
147
|
}
|
|
131
148
|
return schema;
|
|
132
149
|
}
|
|
@@ -2779,8 +2796,30 @@ function sortSpans(left, right) {
|
|
|
2779
2796
|
const leftCol = left.table?.columnIndex ?? Number(left.metadata?.columnIndex ?? 0);
|
|
2780
2797
|
const rightCol = right.table?.columnIndex ?? Number(right.metadata?.columnIndex ?? 0);
|
|
2781
2798
|
if (leftCol !== rightCol) return leftCol - rightCol;
|
|
2799
|
+
if (tableId(left) && tableId(left) === tableId(right)) {
|
|
2800
|
+
const leftUnitRank = sourceUnitSortRank(left);
|
|
2801
|
+
const rightUnitRank = sourceUnitSortRank(right);
|
|
2802
|
+
if (leftUnitRank !== rightUnitRank) return leftUnitRank - rightUnitRank;
|
|
2803
|
+
}
|
|
2782
2804
|
return left.id.localeCompare(right.id);
|
|
2783
2805
|
}
|
|
2806
|
+
function sourceUnitSortRank(span) {
|
|
2807
|
+
switch (sourceUnit2(span)) {
|
|
2808
|
+
case "page":
|
|
2809
|
+
return 0;
|
|
2810
|
+
case "table":
|
|
2811
|
+
return 1;
|
|
2812
|
+
case "table_row":
|
|
2813
|
+
return 2;
|
|
2814
|
+
case "table_cell":
|
|
2815
|
+
return 3;
|
|
2816
|
+
case "section":
|
|
2817
|
+
case "key_value":
|
|
2818
|
+
case "text":
|
|
2819
|
+
default:
|
|
2820
|
+
return 4;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2784
2823
|
function normalizeDocumentSourceTreePaths(nodes) {
|
|
2785
2824
|
const byParent = /* @__PURE__ */ new Map();
|
|
2786
2825
|
for (const node of nodes) {
|
|
@@ -9849,6 +9888,8 @@ Rules:
|
|
|
9849
9888
|
- Keep a coverage when it is a real operational coverage/benefit even if only one term needs cleanup.
|
|
9850
9889
|
- When changing a term's semantic meaning, set kind to the corrected normalized term kind.
|
|
9851
9890
|
- Do not add new coverage rows or new terms; this pass cleans the existing projection.
|
|
9891
|
+
- Include every JSON key in each decision. Use null for scalar fields you are not changing and [] for source ID lists you are not changing.
|
|
9892
|
+
- For each coverage decision, always include termDecisions. Use [] when no terms need cleanup.
|
|
9852
9893
|
- Keep reasons concise and factual.
|
|
9853
9894
|
|
|
9854
9895
|
Candidate projection:
|
|
@@ -9859,9 +9900,6 @@ ${JSON.stringify(nodes, null, 2)}
|
|
|
9859
9900
|
|
|
9860
9901
|
Return JSON with coverageDecisions and warnings only.`;
|
|
9861
9902
|
}
|
|
9862
|
-
function hasOwn(object, key) {
|
|
9863
|
-
return Object.prototype.hasOwnProperty.call(object, key);
|
|
9864
|
-
}
|
|
9865
9903
|
function uniqueStrings(values) {
|
|
9866
9904
|
return [...new Set(values.filter((value) => value.length > 0))];
|
|
9867
9905
|
}
|
|
@@ -9936,18 +9974,16 @@ function applyTermCleanupDecision(term, decision, validNodeIds, validSpanIds) {
|
|
|
9936
9974
|
sourceSpanIds: cleanupIds(decision.sourceSpanIds, validSpanIds, term.sourceSpanIds)
|
|
9937
9975
|
};
|
|
9938
9976
|
if (next.sourceNodeIds.length === 0 && next.sourceSpanIds.length === 0) return term;
|
|
9939
|
-
if (
|
|
9940
|
-
|
|
9941
|
-
else delete next.amount;
|
|
9977
|
+
if (typeof decision.amount === "number" && Number.isFinite(decision.amount)) {
|
|
9978
|
+
next.amount = decision.amount;
|
|
9942
9979
|
} else if (decision.value || decision.kind) {
|
|
9943
9980
|
const amount = next.kind === "retroactive_date" ? void 0 : amountFromOperationalValue(next.value);
|
|
9944
9981
|
if (amount === void 0) delete next.amount;
|
|
9945
9982
|
else next.amount = amount;
|
|
9946
9983
|
}
|
|
9947
|
-
if (
|
|
9984
|
+
if (decision.appliesTo != null) {
|
|
9948
9985
|
const appliesTo = cleanProfileValue(decision.appliesTo);
|
|
9949
9986
|
if (appliesTo) next.appliesTo = appliesTo;
|
|
9950
|
-
else delete next.appliesTo;
|
|
9951
9987
|
}
|
|
9952
9988
|
return next;
|
|
9953
9989
|
}
|
|
@@ -9966,46 +10002,42 @@ function applyCoverageCleanupDecision(coverage, decision, validNodeIds, validSpa
|
|
|
9966
10002
|
const name = cleanProfileValue(decision.name);
|
|
9967
10003
|
if (name) next.name = name;
|
|
9968
10004
|
if (decision.coverageOrigin) next.coverageOrigin = decision.coverageOrigin;
|
|
9969
|
-
if (
|
|
10005
|
+
if (decision.limit != null) {
|
|
9970
10006
|
const value = cleanProfileValue(decision.limit);
|
|
9971
10007
|
if (value) next.limit = value;
|
|
9972
|
-
else delete next.limit;
|
|
9973
10008
|
}
|
|
9974
|
-
if (
|
|
10009
|
+
if (decision.deductible != null) {
|
|
9975
10010
|
const value = cleanProfileValue(decision.deductible);
|
|
9976
10011
|
if (value) next.deductible = value;
|
|
9977
|
-
else delete next.deductible;
|
|
9978
10012
|
}
|
|
9979
|
-
if (
|
|
10013
|
+
if (decision.premium != null) {
|
|
9980
10014
|
const value = cleanProfileValue(decision.premium);
|
|
9981
10015
|
if (value) next.premium = value;
|
|
9982
|
-
else delete next.premium;
|
|
9983
10016
|
}
|
|
9984
|
-
if (
|
|
10017
|
+
if (decision.retroactiveDate != null) {
|
|
9985
10018
|
const value = cleanProfileValue(decision.retroactiveDate);
|
|
9986
10019
|
if (value) next.retroactiveDate = value;
|
|
9987
|
-
else delete next.retroactiveDate;
|
|
9988
10020
|
}
|
|
9989
10021
|
const termDecisions = (decision.termDecisions ?? []).filter((termDecision) => termDecision.termIndex < coverage.limits.length);
|
|
9990
10022
|
const termDecisionByIndex = new Map(termDecisions.map((termDecision) => [termDecision.termIndex, termDecision]));
|
|
9991
10023
|
next.limits = coverage.limits.map((term, index) => applyTermCleanupDecision(term, termDecisionByIndex.get(index), validNodeIds, validSpanIds)).filter((term) => Boolean(term));
|
|
9992
10024
|
if (termDecisions.length > 0) {
|
|
9993
|
-
if (
|
|
10025
|
+
if (decision.limit == null && termDecisionsTouch(coverage, termDecisions, isLimitTerm)) {
|
|
9994
10026
|
const value = primaryLimitFromTerms(next.limits);
|
|
9995
10027
|
if (value) next.limit = value;
|
|
9996
10028
|
else delete next.limit;
|
|
9997
10029
|
}
|
|
9998
|
-
if (
|
|
10030
|
+
if (decision.deductible == null && termDecisionsTouch(coverage, termDecisions, isDeductibleTerm)) {
|
|
9999
10031
|
const value = deductibleFromTerms(next.limits);
|
|
10000
10032
|
if (value) next.deductible = value;
|
|
10001
10033
|
else delete next.deductible;
|
|
10002
10034
|
}
|
|
10003
|
-
if (
|
|
10035
|
+
if (decision.premium == null && termDecisionsTouch(coverage, termDecisions, isPremiumTerm)) {
|
|
10004
10036
|
const value = premiumFromTerms(next.limits);
|
|
10005
10037
|
if (value) next.premium = value;
|
|
10006
10038
|
else delete next.premium;
|
|
10007
10039
|
}
|
|
10008
|
-
if (
|
|
10040
|
+
if (decision.retroactiveDate == null && termDecisionsTouch(coverage, termDecisions, isRetroactiveDateTerm)) {
|
|
10009
10041
|
const value = retroactiveDateFromTerms(next.limits);
|
|
10010
10042
|
if (value) next.retroactiveDate = value;
|
|
10011
10043
|
else delete next.retroactiveDate;
|
|
@@ -10113,6 +10145,23 @@ var SourceTreeOrganizationSchema = z42.object({
|
|
|
10113
10145
|
childNodeIds: z42.array(z42.string()).min(1)
|
|
10114
10146
|
}))
|
|
10115
10147
|
});
|
|
10148
|
+
var SourceTreeVisualTableRepairSchema = z42.object({
|
|
10149
|
+
tables: z42.array(z42.object({
|
|
10150
|
+
tableNodeId: z42.string(),
|
|
10151
|
+
columnLabels: z42.array(z42.object({
|
|
10152
|
+
columnIndex: z42.number().int().nonnegative(),
|
|
10153
|
+
label: z42.string()
|
|
10154
|
+
})).default([]),
|
|
10155
|
+
continuationRows: z42.array(z42.object({
|
|
10156
|
+
sourceRowNodeId: z42.string(),
|
|
10157
|
+
targetRowNodeId: z42.string(),
|
|
10158
|
+
targetColumnIndex: z42.number().int().nonnegative().optional(),
|
|
10159
|
+
targetColumnLabel: z42.string().optional(),
|
|
10160
|
+
reason: z42.string().optional()
|
|
10161
|
+
})).default([])
|
|
10162
|
+
})).default([]),
|
|
10163
|
+
warnings: z42.array(z42.string()).default([])
|
|
10164
|
+
});
|
|
10116
10165
|
var SourceBackedValueForPromptSchema = z42.object({
|
|
10117
10166
|
value: z42.string(),
|
|
10118
10167
|
normalizedValue: z42.string().optional(),
|
|
@@ -11335,6 +11384,319 @@ ${JSON.stringify(nodes, null, 2)}
|
|
|
11335
11384
|
|
|
11336
11385
|
Return JSON for the operational profile.`;
|
|
11337
11386
|
}
|
|
11387
|
+
var VISUAL_TABLE_REPAIR_MAX_TABLES = 4;
|
|
11388
|
+
var VISUAL_TABLE_REPAIR_MAX_ROWS = 28;
|
|
11389
|
+
var VISUAL_TABLE_REPAIR_MAX_CELLS = 140;
|
|
11390
|
+
var VISUAL_TABLE_REPAIR_KEYWORDS = /\b(coverage|limit|limits?|deductible|retroactive|premium|tax|fee|sublimit|sub-limit|aggregate|claim|occurrence|retention|declarations?)\b/i;
|
|
11391
|
+
function isSourceTreeHeaderRow(row) {
|
|
11392
|
+
return row.metadata?.isHeader === true || row.metadata?.isHeader === "true";
|
|
11393
|
+
}
|
|
11394
|
+
function tableCellText(cell) {
|
|
11395
|
+
return cleanText(cell.textExcerpt ?? cell.description ?? cell.title, "");
|
|
11396
|
+
}
|
|
11397
|
+
function tableRowTextForPrompt(row, cells) {
|
|
11398
|
+
return cleanText(
|
|
11399
|
+
cells.length ? cells.map(tableCellText).filter(Boolean).join(" | ") : row.textExcerpt ?? row.description ?? row.title,
|
|
11400
|
+
row.title
|
|
11401
|
+
);
|
|
11402
|
+
}
|
|
11403
|
+
function tableCellColumnIndex(cell, fallbackIndex) {
|
|
11404
|
+
const metadataIndex = cell.metadata?.columnIndex;
|
|
11405
|
+
return typeof metadataIndex === "number" && Number.isInteger(metadataIndex) ? metadataIndex : fallbackIndex;
|
|
11406
|
+
}
|
|
11407
|
+
function isGenericColumnTitle(value) {
|
|
11408
|
+
const title = cleanText(value, "");
|
|
11409
|
+
return !title || /^(?:column\s+\d+|table cell|value)$/i.test(title);
|
|
11410
|
+
}
|
|
11411
|
+
function bboxSummary(node) {
|
|
11412
|
+
const box = node.bbox?.[0];
|
|
11413
|
+
if (!box) return void 0;
|
|
11414
|
+
const round = (value) => Math.round(value * 10) / 10;
|
|
11415
|
+
return {
|
|
11416
|
+
page: box.page,
|
|
11417
|
+
x: round(box.x),
|
|
11418
|
+
y: round(box.y),
|
|
11419
|
+
width: round(box.width),
|
|
11420
|
+
height: round(box.height)
|
|
11421
|
+
};
|
|
11422
|
+
}
|
|
11423
|
+
function tableRowsWithCells(table, byParent) {
|
|
11424
|
+
return (byParent.get(table.id) ?? []).filter((node) => node.kind === "table_row").map((row) => ({
|
|
11425
|
+
row,
|
|
11426
|
+
cells: (byParent.get(row.id) ?? []).filter((child) => child.kind === "table_cell").sort(
|
|
11427
|
+
(left, right) => tableCellColumnIndex(left, 0) - tableCellColumnIndex(right, 0) || left.order - right.order || left.id.localeCompare(right.id)
|
|
11428
|
+
)
|
|
11429
|
+
})).sort((left, right) => left.row.order - right.row.order || left.row.id.localeCompare(right.row.id));
|
|
11430
|
+
}
|
|
11431
|
+
function primaryHeaderColumnCount(rows) {
|
|
11432
|
+
const header = rows.find(({ row, cells }) => isSourceTreeHeaderRow(row) && cells.length > 1);
|
|
11433
|
+
if (header) return header.cells.length;
|
|
11434
|
+
return Math.max(0, ...rows.map(({ cells }) => cells.length));
|
|
11435
|
+
}
|
|
11436
|
+
function shouldRepairVisualTable(candidate) {
|
|
11437
|
+
const rows = candidate.rows;
|
|
11438
|
+
if (rows.length < 3) return false;
|
|
11439
|
+
const tableText = rows.map(({ row, cells }) => tableRowTextForPrompt(row, cells)).join(" ");
|
|
11440
|
+
if (!VISUAL_TABLE_REPAIR_KEYWORDS.test(tableText)) return false;
|
|
11441
|
+
const headerCount = primaryHeaderColumnCount(rows);
|
|
11442
|
+
if (headerCount < 2) return false;
|
|
11443
|
+
const firstHeaderIndex = rows.findIndex(({ row, cells }) => isSourceTreeHeaderRow(row) && cells.length > 1);
|
|
11444
|
+
const repeatedHeader = firstHeaderIndex >= 0 && rows.some(({ row }, index) => index > firstHeaderIndex && isSourceTreeHeaderRow(row));
|
|
11445
|
+
const shortContinuation = rows.some(
|
|
11446
|
+
({ row, cells }, index) => index > 0 && !isSourceTreeHeaderRow(row) && cells.length > 0 && cells.length < headerCount && !/^(?:item\s+\d+|[A-Z]\.)\b/i.test(tableCellText(cells[0]))
|
|
11447
|
+
);
|
|
11448
|
+
const genericDataLabels = rows.some(
|
|
11449
|
+
({ row, cells }) => !isSourceTreeHeaderRow(row) && cells.length >= 2 && cells.some((cell) => isGenericColumnTitle(cell.title))
|
|
11450
|
+
);
|
|
11451
|
+
const danglingSlash = rows.some(
|
|
11452
|
+
({ row, cells }) => !isSourceTreeHeaderRow(row) && /\/\s*$/.test(tableRowTextForPrompt(row, cells))
|
|
11453
|
+
);
|
|
11454
|
+
return repeatedHeader || shortContinuation || genericDataLabels || danglingSlash;
|
|
11455
|
+
}
|
|
11456
|
+
function visualTableCandidates(sourceTree) {
|
|
11457
|
+
const byParent = nodesByParent(sourceTree);
|
|
11458
|
+
return sourceTree.filter((node) => node.kind === "table" && typeof node.pageStart === "number").map((table) => ({
|
|
11459
|
+
table,
|
|
11460
|
+
page: table.pageStart,
|
|
11461
|
+
rows: tableRowsWithCells(table, byParent)
|
|
11462
|
+
})).filter(shouldRepairVisualTable).sort(
|
|
11463
|
+
(left, right) => left.page - right.page || left.table.order - right.table.order || left.table.id.localeCompare(right.table.id)
|
|
11464
|
+
).slice(0, VISUAL_TABLE_REPAIR_MAX_TABLES);
|
|
11465
|
+
}
|
|
11466
|
+
function compactVisualTableCandidate(candidate) {
|
|
11467
|
+
let cellCount = 0;
|
|
11468
|
+
const rows = candidate.rows.slice(0, VISUAL_TABLE_REPAIR_MAX_ROWS).map(({ row, cells }) => {
|
|
11469
|
+
const compactCells = cells.slice(0, Math.max(0, VISUAL_TABLE_REPAIR_MAX_CELLS - cellCount)).map((cell, index) => ({
|
|
11470
|
+
cellNodeId: cell.id,
|
|
11471
|
+
columnIndex: tableCellColumnIndex(cell, index),
|
|
11472
|
+
currentColumnName: cell.title,
|
|
11473
|
+
text: tableCellText(cell),
|
|
11474
|
+
bbox: bboxSummary(cell)
|
|
11475
|
+
}));
|
|
11476
|
+
cellCount += compactCells.length;
|
|
11477
|
+
return {
|
|
11478
|
+
rowNodeId: row.id,
|
|
11479
|
+
order: row.order,
|
|
11480
|
+
isHeader: isSourceTreeHeaderRow(row),
|
|
11481
|
+
text: tableRowTextForPrompt(row, cells),
|
|
11482
|
+
bbox: bboxSummary(row),
|
|
11483
|
+
cells: compactCells
|
|
11484
|
+
};
|
|
11485
|
+
});
|
|
11486
|
+
return {
|
|
11487
|
+
tableNodeId: candidate.table.id,
|
|
11488
|
+
page: candidate.page,
|
|
11489
|
+
title: candidate.table.title,
|
|
11490
|
+
bbox: bboxSummary(candidate.table),
|
|
11491
|
+
rows
|
|
11492
|
+
};
|
|
11493
|
+
}
|
|
11494
|
+
function buildVisualTableRepairPrompt(candidate) {
|
|
11495
|
+
return `Compare a parsed insurance source table against the original page visual layout.
|
|
11496
|
+
|
|
11497
|
+
If a page image is attached, use it as the primary reference. If no image is available, use the bbox coordinates below as the visual layout reference.
|
|
11498
|
+
|
|
11499
|
+
Task:
|
|
11500
|
+
- Identify rows that are not real standalone rows because they are visually wrapped continuation text for a nearby row.
|
|
11501
|
+
- Identify the primary printed column labels for the table.
|
|
11502
|
+
|
|
11503
|
+
Rules:
|
|
11504
|
+
- Return only high-confidence repairs.
|
|
11505
|
+
- Use only rowNodeId/tableNodeId/cellNodeId values from the provided JSON.
|
|
11506
|
+
- Do not invent policy facts, values, row text, source spans, or page numbers.
|
|
11507
|
+
- continuationRows.sourceRowNodeId must be a parsed row that should be removed as a standalone row.
|
|
11508
|
+
- continuationRows.targetRowNodeId must be the row that visually owns that wrapped text, usually the immediately previous non-header row.
|
|
11509
|
+
- targetColumnIndex/targetColumnLabel should point to the visual column that owns the wrapped text, usually the limit/amount/value column.
|
|
11510
|
+
- Do not mark actual data rows as continuations when they begin a new item, coverage part, endorsement, form, location, person, or premium/tax row.
|
|
11511
|
+
- columnLabels should be the primary visual header labels, not later wrapped cell text that the parser misread as a header.
|
|
11512
|
+
|
|
11513
|
+
Parsed table with visual coordinates:
|
|
11514
|
+
${JSON.stringify(compactVisualTableCandidate(candidate), null, 2)}
|
|
11515
|
+
|
|
11516
|
+
Return JSON with tables[].columnLabels and tables[].continuationRows. Return empty arrays if no repair is needed.`;
|
|
11517
|
+
}
|
|
11518
|
+
function sourceSpanIdsForNodes(nodes) {
|
|
11519
|
+
return [...new Set(nodes.flatMap((node) => node.sourceSpanIds))];
|
|
11520
|
+
}
|
|
11521
|
+
function appendDistinctText(base, addition) {
|
|
11522
|
+
const current = cleanText(base, "");
|
|
11523
|
+
const next = cleanText(addition, "");
|
|
11524
|
+
if (!current) return next || void 0;
|
|
11525
|
+
if (!next) return current;
|
|
11526
|
+
if (current.toLowerCase().includes(next.toLowerCase())) return current;
|
|
11527
|
+
const delimiter = /(?:[/(:;-]|,\s*)$/.test(current) ? " " : " / ";
|
|
11528
|
+
return cleanText(`${current}${delimiter}${next}`, current);
|
|
11529
|
+
}
|
|
11530
|
+
function mergedBbox(nodes) {
|
|
11531
|
+
const boxes = nodes.flatMap((node) => node.bbox ?? []);
|
|
11532
|
+
return boxes.length ? boxes.slice(0, 12) : void 0;
|
|
11533
|
+
}
|
|
11534
|
+
function normalizedRepairLabel(value) {
|
|
11535
|
+
const label = cleanText(value, "");
|
|
11536
|
+
if (!label || label.length > 80) return void 0;
|
|
11537
|
+
if (/^(?:source|page|row|table)$/i.test(label)) return void 0;
|
|
11538
|
+
return label;
|
|
11539
|
+
}
|
|
11540
|
+
function findCellForContinuation(params) {
|
|
11541
|
+
if (typeof params.targetColumnIndex === "number") {
|
|
11542
|
+
const byIndex = params.cells.find(
|
|
11543
|
+
(cell, index) => tableCellColumnIndex(cell, index) === params.targetColumnIndex
|
|
11544
|
+
);
|
|
11545
|
+
if (byIndex) return byIndex;
|
|
11546
|
+
}
|
|
11547
|
+
const label = normalizedRepairLabel(params.targetColumnLabel);
|
|
11548
|
+
if (label) {
|
|
11549
|
+
const byLabel = params.cells.find(
|
|
11550
|
+
(cell) => cleanText(cell.title, "").toLowerCase() === label.toLowerCase()
|
|
11551
|
+
);
|
|
11552
|
+
if (byLabel) return byLabel;
|
|
11553
|
+
}
|
|
11554
|
+
return params.cells[1] ?? params.cells[params.cells.length - 1];
|
|
11555
|
+
}
|
|
11556
|
+
function applyVisualTableRepair(sourceTree, repair) {
|
|
11557
|
+
if (repair.tables.length === 0) return sourceTree;
|
|
11558
|
+
const byId = new Map(sourceTree.map((node) => [node.id, node]));
|
|
11559
|
+
const byParent = nodesByParent(sourceTree);
|
|
11560
|
+
const updates = /* @__PURE__ */ new Map();
|
|
11561
|
+
const removeIds = /* @__PURE__ */ new Set();
|
|
11562
|
+
const currentNode = (id) => updates.get(id) ?? byId.get(id);
|
|
11563
|
+
for (const tableRepair of repair.tables) {
|
|
11564
|
+
const table = byId.get(tableRepair.tableNodeId);
|
|
11565
|
+
if (!table || table.kind !== "table") continue;
|
|
11566
|
+
const rows = tableRowsWithCells(table, byParent);
|
|
11567
|
+
const rowIds = new Set(rows.map(({ row }) => row.id));
|
|
11568
|
+
const rowOrder = new Map(rows.map(({ row }, index) => [row.id, index]));
|
|
11569
|
+
const columnLabels = /* @__PURE__ */ new Map();
|
|
11570
|
+
for (const label of tableRepair.columnLabels) {
|
|
11571
|
+
const normalized = normalizedRepairLabel(label.label);
|
|
11572
|
+
if (normalized) columnLabels.set(label.columnIndex, normalized);
|
|
11573
|
+
}
|
|
11574
|
+
if (columnLabels.size > 0) {
|
|
11575
|
+
for (const { row, cells } of rows) {
|
|
11576
|
+
if (removeIds.has(row.id)) continue;
|
|
11577
|
+
for (const [fallbackIndex, cell] of cells.entries()) {
|
|
11578
|
+
const columnIndex = tableCellColumnIndex(cell, fallbackIndex);
|
|
11579
|
+
const label = columnLabels.get(columnIndex);
|
|
11580
|
+
if (!label || cell.title === label) continue;
|
|
11581
|
+
updates.set(cell.id, {
|
|
11582
|
+
...currentNode(cell.id) ?? cell,
|
|
11583
|
+
title: label,
|
|
11584
|
+
metadata: {
|
|
11585
|
+
...cell.metadata ?? {},
|
|
11586
|
+
visualTableRepairColumnLabel: label
|
|
11587
|
+
}
|
|
11588
|
+
});
|
|
11589
|
+
}
|
|
11590
|
+
}
|
|
11591
|
+
}
|
|
11592
|
+
for (const continuation of tableRepair.continuationRows) {
|
|
11593
|
+
if (!rowIds.has(continuation.sourceRowNodeId) || !rowIds.has(continuation.targetRowNodeId)) continue;
|
|
11594
|
+
if (continuation.sourceRowNodeId === continuation.targetRowNodeId) continue;
|
|
11595
|
+
const sourceIndex = rowOrder.get(continuation.sourceRowNodeId);
|
|
11596
|
+
const targetIndex = rowOrder.get(continuation.targetRowNodeId);
|
|
11597
|
+
if (sourceIndex === void 0 || targetIndex === void 0) continue;
|
|
11598
|
+
if (Math.abs(sourceIndex - targetIndex) > 3) continue;
|
|
11599
|
+
const sourceRow = currentNode(continuation.sourceRowNodeId);
|
|
11600
|
+
const targetRow = currentNode(continuation.targetRowNodeId);
|
|
11601
|
+
if (!sourceRow || !targetRow || sourceRow.kind !== "table_row" || targetRow.kind !== "table_row") continue;
|
|
11602
|
+
if (isSourceTreeHeaderRow(targetRow)) continue;
|
|
11603
|
+
const sourceCells = (byParent.get(sourceRow.id) ?? []).filter((node) => node.kind === "table_cell").map((node) => currentNode(node.id) ?? node);
|
|
11604
|
+
const targetCells = (byParent.get(targetRow.id) ?? []).filter((node) => node.kind === "table_cell").map((node) => currentNode(node.id) ?? node);
|
|
11605
|
+
const sourceText = tableRowTextForPrompt(sourceRow, sourceCells);
|
|
11606
|
+
if (!sourceText) continue;
|
|
11607
|
+
const targetCell = findCellForContinuation({
|
|
11608
|
+
cells: targetCells,
|
|
11609
|
+
targetColumnIndex: continuation.targetColumnIndex,
|
|
11610
|
+
targetColumnLabel: continuation.targetColumnLabel
|
|
11611
|
+
});
|
|
11612
|
+
const sourceNodes = [sourceRow, ...sourceCells];
|
|
11613
|
+
const sourceSpanIds = sourceSpanIdsForNodes(sourceNodes);
|
|
11614
|
+
if (targetCell) {
|
|
11615
|
+
const nextCellText = appendDistinctText(tableCellText(targetCell), sourceText);
|
|
11616
|
+
if (nextCellText) {
|
|
11617
|
+
const mergedNodes = [targetCell, ...sourceNodes];
|
|
11618
|
+
updates.set(targetCell.id, {
|
|
11619
|
+
...currentNode(targetCell.id) ?? targetCell,
|
|
11620
|
+
textExcerpt: nextCellText,
|
|
11621
|
+
description: cleanText([targetCell.title, nextCellText].filter(Boolean).join(" | "), targetCell.description),
|
|
11622
|
+
sourceSpanIds: [.../* @__PURE__ */ new Set([...targetCell.sourceSpanIds, ...sourceSpanIds])],
|
|
11623
|
+
bbox: mergedBbox(mergedNodes),
|
|
11624
|
+
metadata: {
|
|
11625
|
+
...targetCell.metadata ?? {},
|
|
11626
|
+
visualTableRepair: "merged_continuation"
|
|
11627
|
+
}
|
|
11628
|
+
});
|
|
11629
|
+
}
|
|
11630
|
+
}
|
|
11631
|
+
const nextRowText = appendDistinctText(targetRow.textExcerpt ?? targetRow.description, sourceText);
|
|
11632
|
+
updates.set(targetRow.id, {
|
|
11633
|
+
...currentNode(targetRow.id) ?? targetRow,
|
|
11634
|
+
textExcerpt: nextRowText,
|
|
11635
|
+
description: cleanText([targetRow.title, nextRowText].filter(Boolean).join(" | "), targetRow.description),
|
|
11636
|
+
sourceSpanIds: [.../* @__PURE__ */ new Set([...targetRow.sourceSpanIds, ...sourceSpanIds])],
|
|
11637
|
+
bbox: mergedBbox([targetRow, ...sourceNodes]),
|
|
11638
|
+
metadata: {
|
|
11639
|
+
...targetRow.metadata ?? {},
|
|
11640
|
+
visualTableRepair: "merged_continuation"
|
|
11641
|
+
}
|
|
11642
|
+
});
|
|
11643
|
+
removeIds.add(sourceRow.id);
|
|
11644
|
+
for (const sourceCell of sourceCells) removeIds.add(sourceCell.id);
|
|
11645
|
+
}
|
|
11646
|
+
}
|
|
11647
|
+
if (updates.size === 0 && removeIds.size === 0) return sourceTree;
|
|
11648
|
+
const repaired = sourceTree.filter((node) => !removeIds.has(node.id)).map((node) => updates.get(node.id) ?? node);
|
|
11649
|
+
return normalizeDocumentSourceTreePaths(normalizeContainerEvidenceFromChildren(repaired));
|
|
11650
|
+
}
|
|
11651
|
+
async function runVisualTableRepair(params) {
|
|
11652
|
+
const candidates = visualTableCandidates(params.sourceTree);
|
|
11653
|
+
if (candidates.length === 0) return { sourceTree: params.sourceTree, warnings: [] };
|
|
11654
|
+
let sourceTree = params.sourceTree;
|
|
11655
|
+
const warnings = [];
|
|
11656
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
11657
|
+
try {
|
|
11658
|
+
const budget = params.resolveBudget("extraction_source_tree", 1800);
|
|
11659
|
+
const maxTokens = Math.min(budget.maxTokens, 2400);
|
|
11660
|
+
const startedAt = Date.now();
|
|
11661
|
+
const response = await safeGenerateObject(
|
|
11662
|
+
params.generateObject,
|
|
11663
|
+
{
|
|
11664
|
+
prompt: buildVisualTableRepairPrompt(candidate),
|
|
11665
|
+
schema: SourceTreeVisualTableRepairSchema,
|
|
11666
|
+
maxTokens,
|
|
11667
|
+
taskKind: "extraction_source_tree",
|
|
11668
|
+
budgetDiagnostics: { ...budget, maxTokens },
|
|
11669
|
+
trace: {
|
|
11670
|
+
label: `source_tree_visual_table_repair_p${candidate.page}`,
|
|
11671
|
+
startPage: candidate.page,
|
|
11672
|
+
endPage: candidate.page,
|
|
11673
|
+
batchIndex: index + 1,
|
|
11674
|
+
batchCount: candidates.length,
|
|
11675
|
+
sourceBacked: true
|
|
11676
|
+
}
|
|
11677
|
+
},
|
|
11678
|
+
{
|
|
11679
|
+
fallback: { tables: [], warnings: [] },
|
|
11680
|
+
log: params.log
|
|
11681
|
+
}
|
|
11682
|
+
);
|
|
11683
|
+
params.trackUsage(response.usage, {
|
|
11684
|
+
taskKind: "extraction_source_tree",
|
|
11685
|
+
label: `source_tree_visual_table_repair_p${candidate.page}`,
|
|
11686
|
+
maxTokens,
|
|
11687
|
+
durationMs: Date.now() - startedAt
|
|
11688
|
+
});
|
|
11689
|
+
const repair = response.object;
|
|
11690
|
+
sourceTree = applyVisualTableRepair(sourceTree, repair);
|
|
11691
|
+
warnings.push(...repair.warnings.map(
|
|
11692
|
+
(warning) => `Visual table repair warning on page ${candidate.page}: ${warning}`
|
|
11693
|
+
));
|
|
11694
|
+
} catch (error) {
|
|
11695
|
+
warnings.push(`Visual table repair skipped on page ${candidate.page}; parsed table kept (${error instanceof Error ? error.message : String(error)})`);
|
|
11696
|
+
}
|
|
11697
|
+
}
|
|
11698
|
+
return { sourceTree, warnings };
|
|
11699
|
+
}
|
|
11338
11700
|
function groupNodeId(documentId, group) {
|
|
11339
11701
|
return [
|
|
11340
11702
|
documentId.replace(/[^a-zA-Z0-9_.:-]/g, "_"),
|
|
@@ -11648,6 +12010,15 @@ async function runSourceTreeExtraction(params) {
|
|
|
11648
12010
|
warnings.push(`Source-tree outline cleanup failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
|
|
11649
12011
|
}
|
|
11650
12012
|
}
|
|
12013
|
+
const visualTableRepair = await runVisualTableRepair({
|
|
12014
|
+
sourceTree,
|
|
12015
|
+
generateObject: params.generateObject,
|
|
12016
|
+
resolveBudget: params.resolveBudget,
|
|
12017
|
+
trackUsage: localTrack,
|
|
12018
|
+
log: params.log
|
|
12019
|
+
});
|
|
12020
|
+
sourceTree = visualTableRepair.sourceTree;
|
|
12021
|
+
warnings.push(...visualTableRepair.warnings);
|
|
11651
12022
|
const deterministicProfile = buildDeterministicOperationalProfile({
|
|
11652
12023
|
sourceTree,
|
|
11653
12024
|
sourceSpans
|