@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/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 def = schema._zod?.def;
463
- const typeName = def?.type ?? schema.type;
464
- if (typeName === "object") {
465
- const shape = schema.shape;
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._zod?.def;
471
- const fieldType = fieldDef?.type ?? field.type;
472
- if (fieldType === "optional") {
490
+ const fieldDef = schemaDef(field);
491
+ const fieldKind = schemaKind(field);
492
+ if (fieldKind === "optional") {
473
493
  const innerType = fieldDef?.innerType;
474
- const description = field.description ?? fieldDef?.description ?? field._zod?.def?.description;
494
+ const description = schemaDescription(field);
475
495
  if (innerType) {
476
496
  const transformed = toStrictSchema(innerType);
477
- let nullable = import_zod.z.nullable(transformed);
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
- let nullable = import_zod.z.nullable(field);
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
- const objDesc = schema.description ?? def?.description ?? schema._zod?.def?.description;
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 (typeName === "array") {
494
- const element = def?.element ?? schema.element;
507
+ if (kind === "array") {
508
+ const element = def.element ?? def.type ?? schema.element;
495
509
  if (element) {
496
- const arrDesc = schema.description ?? def?.description ?? schema._zod?.def?.description;
497
- const result = import_zod.z.array(toStrictSchema(element));
498
- return arrDesc ? result.describe(arrDesc) : result;
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 (typeName === "nullable") {
521
+ if (kind === "default") {
503
522
  const innerType = def?.innerType;
504
523
  if (innerType) {
505
- const nullDesc = schema.description ?? def?.description ?? schema._zod?.def?.description;
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 (hasOwn(decision, "amount")) {
10310
- if (typeof decision.amount === "number" && Number.isFinite(decision.amount)) next.amount = decision.amount;
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 (hasOwn(decision, "appliesTo")) {
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 (hasOwn(decision, "limit")) {
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 (hasOwn(decision, "deductible")) {
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 (hasOwn(decision, "premium")) {
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 (hasOwn(decision, "retroactiveDate")) {
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 (!hasOwn(decision, "limit") && termDecisionsTouch(coverage, termDecisions, isLimitTerm)) {
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 (!hasOwn(decision, "deductible") && termDecisionsTouch(coverage, termDecisions, isDeductibleTerm)) {
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 (!hasOwn(decision, "premium") && termDecisionsTouch(coverage, termDecisions, isPremiumTerm)) {
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 (!hasOwn(decision, "retroactiveDate") && termDecisionsTouch(coverage, termDecisions, isRetroactiveDateTerm)) {
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