@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.js
CHANGED
|
@@ -458,53 +458,70 @@ function sanitizeNulls(obj) {
|
|
|
458
458
|
|
|
459
459
|
// src/core/strict-schema.ts
|
|
460
460
|
var import_zod = require("zod");
|
|
461
|
+
function schemaDef(schema) {
|
|
462
|
+
return schema._zod?.def ?? schema._def ?? {};
|
|
463
|
+
}
|
|
464
|
+
function schemaKind(schema) {
|
|
465
|
+
const def = schemaDef(schema);
|
|
466
|
+
const raw = typeof def.type === "string" ? def.type : typeof def.typeName === "string" ? def.typeName : typeof schema.type === "string" ? schema.type : void 0;
|
|
467
|
+
return raw?.replace(/^Zod/, "").toLowerCase();
|
|
468
|
+
}
|
|
469
|
+
function schemaDescription(schema) {
|
|
470
|
+
const def = schemaDef(schema);
|
|
471
|
+
return schema.description ?? def.description;
|
|
472
|
+
}
|
|
473
|
+
function objectShape(schema) {
|
|
474
|
+
const def = schemaDef(schema);
|
|
475
|
+
const shape = schema.shape ?? def.shape;
|
|
476
|
+
return typeof shape === "function" ? shape() : shape;
|
|
477
|
+
}
|
|
478
|
+
function withDescription(schema, description) {
|
|
479
|
+
return description ? schema.describe(description) : schema;
|
|
480
|
+
}
|
|
461
481
|
function toStrictSchema(schema) {
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
if (
|
|
465
|
-
const shape = schema
|
|
482
|
+
const kind = schemaKind(schema);
|
|
483
|
+
const def = schemaDef(schema);
|
|
484
|
+
if (kind === "object") {
|
|
485
|
+
const shape = objectShape(schema);
|
|
466
486
|
if (!shape) return schema;
|
|
467
487
|
const newShape = {};
|
|
468
488
|
for (const [key, value] of Object.entries(shape)) {
|
|
469
489
|
const field = value;
|
|
470
|
-
const fieldDef = field
|
|
471
|
-
const
|
|
472
|
-
if (
|
|
490
|
+
const fieldDef = schemaDef(field);
|
|
491
|
+
const fieldKind = schemaKind(field);
|
|
492
|
+
if (fieldKind === "optional") {
|
|
473
493
|
const innerType = fieldDef?.innerType;
|
|
474
|
-
const description = field
|
|
494
|
+
const description = schemaDescription(field);
|
|
475
495
|
if (innerType) {
|
|
476
496
|
const transformed = toStrictSchema(innerType);
|
|
477
|
-
|
|
478
|
-
if (description) nullable = nullable.describe(description);
|
|
479
|
-
newShape[key] = nullable;
|
|
497
|
+
newShape[key] = withDescription(import_zod.z.nullable(transformed), description);
|
|
480
498
|
} else {
|
|
481
|
-
|
|
482
|
-
if (description) nullable = nullable.describe(description);
|
|
483
|
-
newShape[key] = nullable;
|
|
499
|
+
newShape[key] = withDescription(import_zod.z.nullable(field), description);
|
|
484
500
|
}
|
|
485
501
|
} else {
|
|
486
502
|
newShape[key] = toStrictSchema(field);
|
|
487
503
|
}
|
|
488
504
|
}
|
|
489
|
-
|
|
490
|
-
const result = import_zod.z.object(newShape);
|
|
491
|
-
return objDesc ? result.describe(objDesc) : result;
|
|
505
|
+
return withDescription(import_zod.z.object(newShape), schemaDescription(schema));
|
|
492
506
|
}
|
|
493
|
-
if (
|
|
494
|
-
const element = def
|
|
507
|
+
if (kind === "array") {
|
|
508
|
+
const element = def.element ?? def.type ?? schema.element;
|
|
495
509
|
if (element) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
510
|
+
return withDescription(import_zod.z.array(toStrictSchema(element)), schemaDescription(schema));
|
|
511
|
+
}
|
|
512
|
+
return schema;
|
|
513
|
+
}
|
|
514
|
+
if (kind === "nullable") {
|
|
515
|
+
const innerType = def?.innerType;
|
|
516
|
+
if (innerType) {
|
|
517
|
+
return withDescription(import_zod.z.nullable(toStrictSchema(innerType)), schemaDescription(schema));
|
|
499
518
|
}
|
|
500
519
|
return schema;
|
|
501
520
|
}
|
|
502
|
-
if (
|
|
521
|
+
if (kind === "default") {
|
|
503
522
|
const innerType = def?.innerType;
|
|
504
523
|
if (innerType) {
|
|
505
|
-
|
|
506
|
-
const result = import_zod.z.nullable(toStrictSchema(innerType));
|
|
507
|
-
return nullDesc ? result.describe(nullDesc) : result;
|
|
524
|
+
return withDescription(import_zod.z.nullable(toStrictSchema(innerType)), schemaDescription(schema));
|
|
508
525
|
}
|
|
509
526
|
return schema;
|
|
510
527
|
}
|
|
@@ -3157,8 +3174,30 @@ function sortSpans(left, right) {
|
|
|
3157
3174
|
const leftCol = left.table?.columnIndex ?? Number(left.metadata?.columnIndex ?? 0);
|
|
3158
3175
|
const rightCol = right.table?.columnIndex ?? Number(right.metadata?.columnIndex ?? 0);
|
|
3159
3176
|
if (leftCol !== rightCol) return leftCol - rightCol;
|
|
3177
|
+
if (tableId(left) && tableId(left) === tableId(right)) {
|
|
3178
|
+
const leftUnitRank = sourceUnitSortRank(left);
|
|
3179
|
+
const rightUnitRank = sourceUnitSortRank(right);
|
|
3180
|
+
if (leftUnitRank !== rightUnitRank) return leftUnitRank - rightUnitRank;
|
|
3181
|
+
}
|
|
3160
3182
|
return left.id.localeCompare(right.id);
|
|
3161
3183
|
}
|
|
3184
|
+
function sourceUnitSortRank(span) {
|
|
3185
|
+
switch (sourceUnit2(span)) {
|
|
3186
|
+
case "page":
|
|
3187
|
+
return 0;
|
|
3188
|
+
case "table":
|
|
3189
|
+
return 1;
|
|
3190
|
+
case "table_row":
|
|
3191
|
+
return 2;
|
|
3192
|
+
case "table_cell":
|
|
3193
|
+
return 3;
|
|
3194
|
+
case "section":
|
|
3195
|
+
case "key_value":
|
|
3196
|
+
case "text":
|
|
3197
|
+
default:
|
|
3198
|
+
return 4;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3162
3201
|
function normalizeDocumentSourceTreePaths(nodes) {
|
|
3163
3202
|
const byParent = /* @__PURE__ */ new Map();
|
|
3164
3203
|
for (const node of nodes) {
|
|
@@ -10219,6 +10258,8 @@ Rules:
|
|
|
10219
10258
|
- Keep a coverage when it is a real operational coverage/benefit even if only one term needs cleanup.
|
|
10220
10259
|
- When changing a term's semantic meaning, set kind to the corrected normalized term kind.
|
|
10221
10260
|
- Do not add new coverage rows or new terms; this pass cleans the existing projection.
|
|
10261
|
+
- 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.
|
|
10262
|
+
- For each coverage decision, always include termDecisions. Use [] when no terms need cleanup.
|
|
10222
10263
|
- Keep reasons concise and factual.
|
|
10223
10264
|
|
|
10224
10265
|
Candidate projection:
|
|
@@ -10229,9 +10270,6 @@ ${JSON.stringify(nodes, null, 2)}
|
|
|
10229
10270
|
|
|
10230
10271
|
Return JSON with coverageDecisions and warnings only.`;
|
|
10231
10272
|
}
|
|
10232
|
-
function hasOwn(object, key) {
|
|
10233
|
-
return Object.prototype.hasOwnProperty.call(object, key);
|
|
10234
|
-
}
|
|
10235
10273
|
function uniqueStrings(values) {
|
|
10236
10274
|
return [...new Set(values.filter((value) => value.length > 0))];
|
|
10237
10275
|
}
|
|
@@ -10306,18 +10344,16 @@ function applyTermCleanupDecision(term, decision, validNodeIds, validSpanIds) {
|
|
|
10306
10344
|
sourceSpanIds: cleanupIds(decision.sourceSpanIds, validSpanIds, term.sourceSpanIds)
|
|
10307
10345
|
};
|
|
10308
10346
|
if (next.sourceNodeIds.length === 0 && next.sourceSpanIds.length === 0) return term;
|
|
10309
|
-
if (
|
|
10310
|
-
|
|
10311
|
-
else delete next.amount;
|
|
10347
|
+
if (typeof decision.amount === "number" && Number.isFinite(decision.amount)) {
|
|
10348
|
+
next.amount = decision.amount;
|
|
10312
10349
|
} else if (decision.value || decision.kind) {
|
|
10313
10350
|
const amount = next.kind === "retroactive_date" ? void 0 : amountFromOperationalValue(next.value);
|
|
10314
10351
|
if (amount === void 0) delete next.amount;
|
|
10315
10352
|
else next.amount = amount;
|
|
10316
10353
|
}
|
|
10317
|
-
if (
|
|
10354
|
+
if (decision.appliesTo != null) {
|
|
10318
10355
|
const appliesTo = cleanProfileValue(decision.appliesTo);
|
|
10319
10356
|
if (appliesTo) next.appliesTo = appliesTo;
|
|
10320
|
-
else delete next.appliesTo;
|
|
10321
10357
|
}
|
|
10322
10358
|
return next;
|
|
10323
10359
|
}
|
|
@@ -10336,46 +10372,42 @@ function applyCoverageCleanupDecision(coverage, decision, validNodeIds, validSpa
|
|
|
10336
10372
|
const name = cleanProfileValue(decision.name);
|
|
10337
10373
|
if (name) next.name = name;
|
|
10338
10374
|
if (decision.coverageOrigin) next.coverageOrigin = decision.coverageOrigin;
|
|
10339
|
-
if (
|
|
10375
|
+
if (decision.limit != null) {
|
|
10340
10376
|
const value = cleanProfileValue(decision.limit);
|
|
10341
10377
|
if (value) next.limit = value;
|
|
10342
|
-
else delete next.limit;
|
|
10343
10378
|
}
|
|
10344
|
-
if (
|
|
10379
|
+
if (decision.deductible != null) {
|
|
10345
10380
|
const value = cleanProfileValue(decision.deductible);
|
|
10346
10381
|
if (value) next.deductible = value;
|
|
10347
|
-
else delete next.deductible;
|
|
10348
10382
|
}
|
|
10349
|
-
if (
|
|
10383
|
+
if (decision.premium != null) {
|
|
10350
10384
|
const value = cleanProfileValue(decision.premium);
|
|
10351
10385
|
if (value) next.premium = value;
|
|
10352
|
-
else delete next.premium;
|
|
10353
10386
|
}
|
|
10354
|
-
if (
|
|
10387
|
+
if (decision.retroactiveDate != null) {
|
|
10355
10388
|
const value = cleanProfileValue(decision.retroactiveDate);
|
|
10356
10389
|
if (value) next.retroactiveDate = value;
|
|
10357
|
-
else delete next.retroactiveDate;
|
|
10358
10390
|
}
|
|
10359
10391
|
const termDecisions = (decision.termDecisions ?? []).filter((termDecision) => termDecision.termIndex < coverage.limits.length);
|
|
10360
10392
|
const termDecisionByIndex = new Map(termDecisions.map((termDecision) => [termDecision.termIndex, termDecision]));
|
|
10361
10393
|
next.limits = coverage.limits.map((term, index) => applyTermCleanupDecision(term, termDecisionByIndex.get(index), validNodeIds, validSpanIds)).filter((term) => Boolean(term));
|
|
10362
10394
|
if (termDecisions.length > 0) {
|
|
10363
|
-
if (
|
|
10395
|
+
if (decision.limit == null && termDecisionsTouch(coverage, termDecisions, isLimitTerm)) {
|
|
10364
10396
|
const value = primaryLimitFromTerms(next.limits);
|
|
10365
10397
|
if (value) next.limit = value;
|
|
10366
10398
|
else delete next.limit;
|
|
10367
10399
|
}
|
|
10368
|
-
if (
|
|
10400
|
+
if (decision.deductible == null && termDecisionsTouch(coverage, termDecisions, isDeductibleTerm)) {
|
|
10369
10401
|
const value = deductibleFromTerms(next.limits);
|
|
10370
10402
|
if (value) next.deductible = value;
|
|
10371
10403
|
else delete next.deductible;
|
|
10372
10404
|
}
|
|
10373
|
-
if (
|
|
10405
|
+
if (decision.premium == null && termDecisionsTouch(coverage, termDecisions, isPremiumTerm)) {
|
|
10374
10406
|
const value = premiumFromTerms(next.limits);
|
|
10375
10407
|
if (value) next.premium = value;
|
|
10376
10408
|
else delete next.premium;
|
|
10377
10409
|
}
|
|
10378
|
-
if (
|
|
10410
|
+
if (decision.retroactiveDate == null && termDecisionsTouch(coverage, termDecisions, isRetroactiveDateTerm)) {
|
|
10379
10411
|
const value = retroactiveDateFromTerms(next.limits);
|
|
10380
10412
|
if (value) next.retroactiveDate = value;
|
|
10381
10413
|
else delete next.retroactiveDate;
|
|
@@ -10483,6 +10515,23 @@ var SourceTreeOrganizationSchema = import_zod42.z.object({
|
|
|
10483
10515
|
childNodeIds: import_zod42.z.array(import_zod42.z.string()).min(1)
|
|
10484
10516
|
}))
|
|
10485
10517
|
});
|
|
10518
|
+
var SourceTreeVisualTableRepairSchema = import_zod42.z.object({
|
|
10519
|
+
tables: import_zod42.z.array(import_zod42.z.object({
|
|
10520
|
+
tableNodeId: import_zod42.z.string(),
|
|
10521
|
+
columnLabels: import_zod42.z.array(import_zod42.z.object({
|
|
10522
|
+
columnIndex: import_zod42.z.number().int().nonnegative(),
|
|
10523
|
+
label: import_zod42.z.string()
|
|
10524
|
+
})).default([]),
|
|
10525
|
+
continuationRows: import_zod42.z.array(import_zod42.z.object({
|
|
10526
|
+
sourceRowNodeId: import_zod42.z.string(),
|
|
10527
|
+
targetRowNodeId: import_zod42.z.string(),
|
|
10528
|
+
targetColumnIndex: import_zod42.z.number().int().nonnegative().optional(),
|
|
10529
|
+
targetColumnLabel: import_zod42.z.string().optional(),
|
|
10530
|
+
reason: import_zod42.z.string().optional()
|
|
10531
|
+
})).default([])
|
|
10532
|
+
})).default([]),
|
|
10533
|
+
warnings: import_zod42.z.array(import_zod42.z.string()).default([])
|
|
10534
|
+
});
|
|
10486
10535
|
var SourceBackedValueForPromptSchema = import_zod42.z.object({
|
|
10487
10536
|
value: import_zod42.z.string(),
|
|
10488
10537
|
normalizedValue: import_zod42.z.string().optional(),
|
|
@@ -11705,6 +11754,319 @@ ${JSON.stringify(nodes, null, 2)}
|
|
|
11705
11754
|
|
|
11706
11755
|
Return JSON for the operational profile.`;
|
|
11707
11756
|
}
|
|
11757
|
+
var VISUAL_TABLE_REPAIR_MAX_TABLES = 4;
|
|
11758
|
+
var VISUAL_TABLE_REPAIR_MAX_ROWS = 28;
|
|
11759
|
+
var VISUAL_TABLE_REPAIR_MAX_CELLS = 140;
|
|
11760
|
+
var VISUAL_TABLE_REPAIR_KEYWORDS = /\b(coverage|limit|limits?|deductible|retroactive|premium|tax|fee|sublimit|sub-limit|aggregate|claim|occurrence|retention|declarations?)\b/i;
|
|
11761
|
+
function isSourceTreeHeaderRow(row) {
|
|
11762
|
+
return row.metadata?.isHeader === true || row.metadata?.isHeader === "true";
|
|
11763
|
+
}
|
|
11764
|
+
function tableCellText(cell) {
|
|
11765
|
+
return cleanText(cell.textExcerpt ?? cell.description ?? cell.title, "");
|
|
11766
|
+
}
|
|
11767
|
+
function tableRowTextForPrompt(row, cells) {
|
|
11768
|
+
return cleanText(
|
|
11769
|
+
cells.length ? cells.map(tableCellText).filter(Boolean).join(" | ") : row.textExcerpt ?? row.description ?? row.title,
|
|
11770
|
+
row.title
|
|
11771
|
+
);
|
|
11772
|
+
}
|
|
11773
|
+
function tableCellColumnIndex(cell, fallbackIndex) {
|
|
11774
|
+
const metadataIndex = cell.metadata?.columnIndex;
|
|
11775
|
+
return typeof metadataIndex === "number" && Number.isInteger(metadataIndex) ? metadataIndex : fallbackIndex;
|
|
11776
|
+
}
|
|
11777
|
+
function isGenericColumnTitle(value) {
|
|
11778
|
+
const title = cleanText(value, "");
|
|
11779
|
+
return !title || /^(?:column\s+\d+|table cell|value)$/i.test(title);
|
|
11780
|
+
}
|
|
11781
|
+
function bboxSummary(node) {
|
|
11782
|
+
const box = node.bbox?.[0];
|
|
11783
|
+
if (!box) return void 0;
|
|
11784
|
+
const round = (value) => Math.round(value * 10) / 10;
|
|
11785
|
+
return {
|
|
11786
|
+
page: box.page,
|
|
11787
|
+
x: round(box.x),
|
|
11788
|
+
y: round(box.y),
|
|
11789
|
+
width: round(box.width),
|
|
11790
|
+
height: round(box.height)
|
|
11791
|
+
};
|
|
11792
|
+
}
|
|
11793
|
+
function tableRowsWithCells(table, byParent) {
|
|
11794
|
+
return (byParent.get(table.id) ?? []).filter((node) => node.kind === "table_row").map((row) => ({
|
|
11795
|
+
row,
|
|
11796
|
+
cells: (byParent.get(row.id) ?? []).filter((child) => child.kind === "table_cell").sort(
|
|
11797
|
+
(left, right) => tableCellColumnIndex(left, 0) - tableCellColumnIndex(right, 0) || left.order - right.order || left.id.localeCompare(right.id)
|
|
11798
|
+
)
|
|
11799
|
+
})).sort((left, right) => left.row.order - right.row.order || left.row.id.localeCompare(right.row.id));
|
|
11800
|
+
}
|
|
11801
|
+
function primaryHeaderColumnCount(rows) {
|
|
11802
|
+
const header = rows.find(({ row, cells }) => isSourceTreeHeaderRow(row) && cells.length > 1);
|
|
11803
|
+
if (header) return header.cells.length;
|
|
11804
|
+
return Math.max(0, ...rows.map(({ cells }) => cells.length));
|
|
11805
|
+
}
|
|
11806
|
+
function shouldRepairVisualTable(candidate) {
|
|
11807
|
+
const rows = candidate.rows;
|
|
11808
|
+
if (rows.length < 3) return false;
|
|
11809
|
+
const tableText = rows.map(({ row, cells }) => tableRowTextForPrompt(row, cells)).join(" ");
|
|
11810
|
+
if (!VISUAL_TABLE_REPAIR_KEYWORDS.test(tableText)) return false;
|
|
11811
|
+
const headerCount = primaryHeaderColumnCount(rows);
|
|
11812
|
+
if (headerCount < 2) return false;
|
|
11813
|
+
const firstHeaderIndex = rows.findIndex(({ row, cells }) => isSourceTreeHeaderRow(row) && cells.length > 1);
|
|
11814
|
+
const repeatedHeader = firstHeaderIndex >= 0 && rows.some(({ row }, index) => index > firstHeaderIndex && isSourceTreeHeaderRow(row));
|
|
11815
|
+
const shortContinuation = rows.some(
|
|
11816
|
+
({ row, cells }, index) => index > 0 && !isSourceTreeHeaderRow(row) && cells.length > 0 && cells.length < headerCount && !/^(?:item\s+\d+|[A-Z]\.)\b/i.test(tableCellText(cells[0]))
|
|
11817
|
+
);
|
|
11818
|
+
const genericDataLabels = rows.some(
|
|
11819
|
+
({ row, cells }) => !isSourceTreeHeaderRow(row) && cells.length >= 2 && cells.some((cell) => isGenericColumnTitle(cell.title))
|
|
11820
|
+
);
|
|
11821
|
+
const danglingSlash = rows.some(
|
|
11822
|
+
({ row, cells }) => !isSourceTreeHeaderRow(row) && /\/\s*$/.test(tableRowTextForPrompt(row, cells))
|
|
11823
|
+
);
|
|
11824
|
+
return repeatedHeader || shortContinuation || genericDataLabels || danglingSlash;
|
|
11825
|
+
}
|
|
11826
|
+
function visualTableCandidates(sourceTree) {
|
|
11827
|
+
const byParent = nodesByParent(sourceTree);
|
|
11828
|
+
return sourceTree.filter((node) => node.kind === "table" && typeof node.pageStart === "number").map((table) => ({
|
|
11829
|
+
table,
|
|
11830
|
+
page: table.pageStart,
|
|
11831
|
+
rows: tableRowsWithCells(table, byParent)
|
|
11832
|
+
})).filter(shouldRepairVisualTable).sort(
|
|
11833
|
+
(left, right) => left.page - right.page || left.table.order - right.table.order || left.table.id.localeCompare(right.table.id)
|
|
11834
|
+
).slice(0, VISUAL_TABLE_REPAIR_MAX_TABLES);
|
|
11835
|
+
}
|
|
11836
|
+
function compactVisualTableCandidate(candidate) {
|
|
11837
|
+
let cellCount = 0;
|
|
11838
|
+
const rows = candidate.rows.slice(0, VISUAL_TABLE_REPAIR_MAX_ROWS).map(({ row, cells }) => {
|
|
11839
|
+
const compactCells = cells.slice(0, Math.max(0, VISUAL_TABLE_REPAIR_MAX_CELLS - cellCount)).map((cell, index) => ({
|
|
11840
|
+
cellNodeId: cell.id,
|
|
11841
|
+
columnIndex: tableCellColumnIndex(cell, index),
|
|
11842
|
+
currentColumnName: cell.title,
|
|
11843
|
+
text: tableCellText(cell),
|
|
11844
|
+
bbox: bboxSummary(cell)
|
|
11845
|
+
}));
|
|
11846
|
+
cellCount += compactCells.length;
|
|
11847
|
+
return {
|
|
11848
|
+
rowNodeId: row.id,
|
|
11849
|
+
order: row.order,
|
|
11850
|
+
isHeader: isSourceTreeHeaderRow(row),
|
|
11851
|
+
text: tableRowTextForPrompt(row, cells),
|
|
11852
|
+
bbox: bboxSummary(row),
|
|
11853
|
+
cells: compactCells
|
|
11854
|
+
};
|
|
11855
|
+
});
|
|
11856
|
+
return {
|
|
11857
|
+
tableNodeId: candidate.table.id,
|
|
11858
|
+
page: candidate.page,
|
|
11859
|
+
title: candidate.table.title,
|
|
11860
|
+
bbox: bboxSummary(candidate.table),
|
|
11861
|
+
rows
|
|
11862
|
+
};
|
|
11863
|
+
}
|
|
11864
|
+
function buildVisualTableRepairPrompt(candidate) {
|
|
11865
|
+
return `Compare a parsed insurance source table against the original page visual layout.
|
|
11866
|
+
|
|
11867
|
+
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.
|
|
11868
|
+
|
|
11869
|
+
Task:
|
|
11870
|
+
- Identify rows that are not real standalone rows because they are visually wrapped continuation text for a nearby row.
|
|
11871
|
+
- Identify the primary printed column labels for the table.
|
|
11872
|
+
|
|
11873
|
+
Rules:
|
|
11874
|
+
- Return only high-confidence repairs.
|
|
11875
|
+
- Use only rowNodeId/tableNodeId/cellNodeId values from the provided JSON.
|
|
11876
|
+
- Do not invent policy facts, values, row text, source spans, or page numbers.
|
|
11877
|
+
- continuationRows.sourceRowNodeId must be a parsed row that should be removed as a standalone row.
|
|
11878
|
+
- continuationRows.targetRowNodeId must be the row that visually owns that wrapped text, usually the immediately previous non-header row.
|
|
11879
|
+
- targetColumnIndex/targetColumnLabel should point to the visual column that owns the wrapped text, usually the limit/amount/value column.
|
|
11880
|
+
- Do not mark actual data rows as continuations when they begin a new item, coverage part, endorsement, form, location, person, or premium/tax row.
|
|
11881
|
+
- columnLabels should be the primary visual header labels, not later wrapped cell text that the parser misread as a header.
|
|
11882
|
+
|
|
11883
|
+
Parsed table with visual coordinates:
|
|
11884
|
+
${JSON.stringify(compactVisualTableCandidate(candidate), null, 2)}
|
|
11885
|
+
|
|
11886
|
+
Return JSON with tables[].columnLabels and tables[].continuationRows. Return empty arrays if no repair is needed.`;
|
|
11887
|
+
}
|
|
11888
|
+
function sourceSpanIdsForNodes(nodes) {
|
|
11889
|
+
return [...new Set(nodes.flatMap((node) => node.sourceSpanIds))];
|
|
11890
|
+
}
|
|
11891
|
+
function appendDistinctText(base, addition) {
|
|
11892
|
+
const current = cleanText(base, "");
|
|
11893
|
+
const next = cleanText(addition, "");
|
|
11894
|
+
if (!current) return next || void 0;
|
|
11895
|
+
if (!next) return current;
|
|
11896
|
+
if (current.toLowerCase().includes(next.toLowerCase())) return current;
|
|
11897
|
+
const delimiter = /(?:[/(:;-]|,\s*)$/.test(current) ? " " : " / ";
|
|
11898
|
+
return cleanText(`${current}${delimiter}${next}`, current);
|
|
11899
|
+
}
|
|
11900
|
+
function mergedBbox(nodes) {
|
|
11901
|
+
const boxes = nodes.flatMap((node) => node.bbox ?? []);
|
|
11902
|
+
return boxes.length ? boxes.slice(0, 12) : void 0;
|
|
11903
|
+
}
|
|
11904
|
+
function normalizedRepairLabel(value) {
|
|
11905
|
+
const label = cleanText(value, "");
|
|
11906
|
+
if (!label || label.length > 80) return void 0;
|
|
11907
|
+
if (/^(?:source|page|row|table)$/i.test(label)) return void 0;
|
|
11908
|
+
return label;
|
|
11909
|
+
}
|
|
11910
|
+
function findCellForContinuation(params) {
|
|
11911
|
+
if (typeof params.targetColumnIndex === "number") {
|
|
11912
|
+
const byIndex = params.cells.find(
|
|
11913
|
+
(cell, index) => tableCellColumnIndex(cell, index) === params.targetColumnIndex
|
|
11914
|
+
);
|
|
11915
|
+
if (byIndex) return byIndex;
|
|
11916
|
+
}
|
|
11917
|
+
const label = normalizedRepairLabel(params.targetColumnLabel);
|
|
11918
|
+
if (label) {
|
|
11919
|
+
const byLabel = params.cells.find(
|
|
11920
|
+
(cell) => cleanText(cell.title, "").toLowerCase() === label.toLowerCase()
|
|
11921
|
+
);
|
|
11922
|
+
if (byLabel) return byLabel;
|
|
11923
|
+
}
|
|
11924
|
+
return params.cells[1] ?? params.cells[params.cells.length - 1];
|
|
11925
|
+
}
|
|
11926
|
+
function applyVisualTableRepair(sourceTree, repair) {
|
|
11927
|
+
if (repair.tables.length === 0) return sourceTree;
|
|
11928
|
+
const byId = new Map(sourceTree.map((node) => [node.id, node]));
|
|
11929
|
+
const byParent = nodesByParent(sourceTree);
|
|
11930
|
+
const updates = /* @__PURE__ */ new Map();
|
|
11931
|
+
const removeIds = /* @__PURE__ */ new Set();
|
|
11932
|
+
const currentNode = (id) => updates.get(id) ?? byId.get(id);
|
|
11933
|
+
for (const tableRepair of repair.tables) {
|
|
11934
|
+
const table = byId.get(tableRepair.tableNodeId);
|
|
11935
|
+
if (!table || table.kind !== "table") continue;
|
|
11936
|
+
const rows = tableRowsWithCells(table, byParent);
|
|
11937
|
+
const rowIds = new Set(rows.map(({ row }) => row.id));
|
|
11938
|
+
const rowOrder = new Map(rows.map(({ row }, index) => [row.id, index]));
|
|
11939
|
+
const columnLabels = /* @__PURE__ */ new Map();
|
|
11940
|
+
for (const label of tableRepair.columnLabels) {
|
|
11941
|
+
const normalized = normalizedRepairLabel(label.label);
|
|
11942
|
+
if (normalized) columnLabels.set(label.columnIndex, normalized);
|
|
11943
|
+
}
|
|
11944
|
+
if (columnLabels.size > 0) {
|
|
11945
|
+
for (const { row, cells } of rows) {
|
|
11946
|
+
if (removeIds.has(row.id)) continue;
|
|
11947
|
+
for (const [fallbackIndex, cell] of cells.entries()) {
|
|
11948
|
+
const columnIndex = tableCellColumnIndex(cell, fallbackIndex);
|
|
11949
|
+
const label = columnLabels.get(columnIndex);
|
|
11950
|
+
if (!label || cell.title === label) continue;
|
|
11951
|
+
updates.set(cell.id, {
|
|
11952
|
+
...currentNode(cell.id) ?? cell,
|
|
11953
|
+
title: label,
|
|
11954
|
+
metadata: {
|
|
11955
|
+
...cell.metadata ?? {},
|
|
11956
|
+
visualTableRepairColumnLabel: label
|
|
11957
|
+
}
|
|
11958
|
+
});
|
|
11959
|
+
}
|
|
11960
|
+
}
|
|
11961
|
+
}
|
|
11962
|
+
for (const continuation of tableRepair.continuationRows) {
|
|
11963
|
+
if (!rowIds.has(continuation.sourceRowNodeId) || !rowIds.has(continuation.targetRowNodeId)) continue;
|
|
11964
|
+
if (continuation.sourceRowNodeId === continuation.targetRowNodeId) continue;
|
|
11965
|
+
const sourceIndex = rowOrder.get(continuation.sourceRowNodeId);
|
|
11966
|
+
const targetIndex = rowOrder.get(continuation.targetRowNodeId);
|
|
11967
|
+
if (sourceIndex === void 0 || targetIndex === void 0) continue;
|
|
11968
|
+
if (Math.abs(sourceIndex - targetIndex) > 3) continue;
|
|
11969
|
+
const sourceRow = currentNode(continuation.sourceRowNodeId);
|
|
11970
|
+
const targetRow = currentNode(continuation.targetRowNodeId);
|
|
11971
|
+
if (!sourceRow || !targetRow || sourceRow.kind !== "table_row" || targetRow.kind !== "table_row") continue;
|
|
11972
|
+
if (isSourceTreeHeaderRow(targetRow)) continue;
|
|
11973
|
+
const sourceCells = (byParent.get(sourceRow.id) ?? []).filter((node) => node.kind === "table_cell").map((node) => currentNode(node.id) ?? node);
|
|
11974
|
+
const targetCells = (byParent.get(targetRow.id) ?? []).filter((node) => node.kind === "table_cell").map((node) => currentNode(node.id) ?? node);
|
|
11975
|
+
const sourceText = tableRowTextForPrompt(sourceRow, sourceCells);
|
|
11976
|
+
if (!sourceText) continue;
|
|
11977
|
+
const targetCell = findCellForContinuation({
|
|
11978
|
+
cells: targetCells,
|
|
11979
|
+
targetColumnIndex: continuation.targetColumnIndex,
|
|
11980
|
+
targetColumnLabel: continuation.targetColumnLabel
|
|
11981
|
+
});
|
|
11982
|
+
const sourceNodes = [sourceRow, ...sourceCells];
|
|
11983
|
+
const sourceSpanIds = sourceSpanIdsForNodes(sourceNodes);
|
|
11984
|
+
if (targetCell) {
|
|
11985
|
+
const nextCellText = appendDistinctText(tableCellText(targetCell), sourceText);
|
|
11986
|
+
if (nextCellText) {
|
|
11987
|
+
const mergedNodes = [targetCell, ...sourceNodes];
|
|
11988
|
+
updates.set(targetCell.id, {
|
|
11989
|
+
...currentNode(targetCell.id) ?? targetCell,
|
|
11990
|
+
textExcerpt: nextCellText,
|
|
11991
|
+
description: cleanText([targetCell.title, nextCellText].filter(Boolean).join(" | "), targetCell.description),
|
|
11992
|
+
sourceSpanIds: [.../* @__PURE__ */ new Set([...targetCell.sourceSpanIds, ...sourceSpanIds])],
|
|
11993
|
+
bbox: mergedBbox(mergedNodes),
|
|
11994
|
+
metadata: {
|
|
11995
|
+
...targetCell.metadata ?? {},
|
|
11996
|
+
visualTableRepair: "merged_continuation"
|
|
11997
|
+
}
|
|
11998
|
+
});
|
|
11999
|
+
}
|
|
12000
|
+
}
|
|
12001
|
+
const nextRowText = appendDistinctText(targetRow.textExcerpt ?? targetRow.description, sourceText);
|
|
12002
|
+
updates.set(targetRow.id, {
|
|
12003
|
+
...currentNode(targetRow.id) ?? targetRow,
|
|
12004
|
+
textExcerpt: nextRowText,
|
|
12005
|
+
description: cleanText([targetRow.title, nextRowText].filter(Boolean).join(" | "), targetRow.description),
|
|
12006
|
+
sourceSpanIds: [.../* @__PURE__ */ new Set([...targetRow.sourceSpanIds, ...sourceSpanIds])],
|
|
12007
|
+
bbox: mergedBbox([targetRow, ...sourceNodes]),
|
|
12008
|
+
metadata: {
|
|
12009
|
+
...targetRow.metadata ?? {},
|
|
12010
|
+
visualTableRepair: "merged_continuation"
|
|
12011
|
+
}
|
|
12012
|
+
});
|
|
12013
|
+
removeIds.add(sourceRow.id);
|
|
12014
|
+
for (const sourceCell of sourceCells) removeIds.add(sourceCell.id);
|
|
12015
|
+
}
|
|
12016
|
+
}
|
|
12017
|
+
if (updates.size === 0 && removeIds.size === 0) return sourceTree;
|
|
12018
|
+
const repaired = sourceTree.filter((node) => !removeIds.has(node.id)).map((node) => updates.get(node.id) ?? node);
|
|
12019
|
+
return normalizeDocumentSourceTreePaths(normalizeContainerEvidenceFromChildren(repaired));
|
|
12020
|
+
}
|
|
12021
|
+
async function runVisualTableRepair(params) {
|
|
12022
|
+
const candidates = visualTableCandidates(params.sourceTree);
|
|
12023
|
+
if (candidates.length === 0) return { sourceTree: params.sourceTree, warnings: [] };
|
|
12024
|
+
let sourceTree = params.sourceTree;
|
|
12025
|
+
const warnings = [];
|
|
12026
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
12027
|
+
try {
|
|
12028
|
+
const budget = params.resolveBudget("extraction_source_tree", 1800);
|
|
12029
|
+
const maxTokens = Math.min(budget.maxTokens, 2400);
|
|
12030
|
+
const startedAt = Date.now();
|
|
12031
|
+
const response = await safeGenerateObject(
|
|
12032
|
+
params.generateObject,
|
|
12033
|
+
{
|
|
12034
|
+
prompt: buildVisualTableRepairPrompt(candidate),
|
|
12035
|
+
schema: SourceTreeVisualTableRepairSchema,
|
|
12036
|
+
maxTokens,
|
|
12037
|
+
taskKind: "extraction_source_tree",
|
|
12038
|
+
budgetDiagnostics: { ...budget, maxTokens },
|
|
12039
|
+
trace: {
|
|
12040
|
+
label: `source_tree_visual_table_repair_p${candidate.page}`,
|
|
12041
|
+
startPage: candidate.page,
|
|
12042
|
+
endPage: candidate.page,
|
|
12043
|
+
batchIndex: index + 1,
|
|
12044
|
+
batchCount: candidates.length,
|
|
12045
|
+
sourceBacked: true
|
|
12046
|
+
}
|
|
12047
|
+
},
|
|
12048
|
+
{
|
|
12049
|
+
fallback: { tables: [], warnings: [] },
|
|
12050
|
+
log: params.log
|
|
12051
|
+
}
|
|
12052
|
+
);
|
|
12053
|
+
params.trackUsage(response.usage, {
|
|
12054
|
+
taskKind: "extraction_source_tree",
|
|
12055
|
+
label: `source_tree_visual_table_repair_p${candidate.page}`,
|
|
12056
|
+
maxTokens,
|
|
12057
|
+
durationMs: Date.now() - startedAt
|
|
12058
|
+
});
|
|
12059
|
+
const repair = response.object;
|
|
12060
|
+
sourceTree = applyVisualTableRepair(sourceTree, repair);
|
|
12061
|
+
warnings.push(...repair.warnings.map(
|
|
12062
|
+
(warning) => `Visual table repair warning on page ${candidate.page}: ${warning}`
|
|
12063
|
+
));
|
|
12064
|
+
} catch (error) {
|
|
12065
|
+
warnings.push(`Visual table repair skipped on page ${candidate.page}; parsed table kept (${error instanceof Error ? error.message : String(error)})`);
|
|
12066
|
+
}
|
|
12067
|
+
}
|
|
12068
|
+
return { sourceTree, warnings };
|
|
12069
|
+
}
|
|
11708
12070
|
function groupNodeId(documentId, group) {
|
|
11709
12071
|
return [
|
|
11710
12072
|
documentId.replace(/[^a-zA-Z0-9_.:-]/g, "_"),
|
|
@@ -12018,6 +12380,15 @@ async function runSourceTreeExtraction(params) {
|
|
|
12018
12380
|
warnings.push(`Source-tree outline cleanup failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
|
|
12019
12381
|
}
|
|
12020
12382
|
}
|
|
12383
|
+
const visualTableRepair = await runVisualTableRepair({
|
|
12384
|
+
sourceTree,
|
|
12385
|
+
generateObject: params.generateObject,
|
|
12386
|
+
resolveBudget: params.resolveBudget,
|
|
12387
|
+
trackUsage: localTrack,
|
|
12388
|
+
log: params.log
|
|
12389
|
+
});
|
|
12390
|
+
sourceTree = visualTableRepair.sourceTree;
|
|
12391
|
+
warnings.push(...visualTableRepair.warnings);
|
|
12021
12392
|
const deterministicProfile = buildDeterministicOperationalProfile({
|
|
12022
12393
|
sourceTree,
|
|
12023
12394
|
sourceSpans
|