@claritylabs/cl-sdk 3.0.11 → 3.0.13

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.mjs CHANGED
@@ -2602,7 +2602,7 @@ function normalizeNodeKind(span) {
2602
2602
  if (unit === "table_cell") return "table_cell";
2603
2603
  if (unit === "key_value") return "schedule";
2604
2604
  if (unit === "section") return "section";
2605
- if (element === "title" || element === "section_candidate") {
2605
+ if (element === "section_candidate") {
2606
2606
  const text = span.text.toLowerCase();
2607
2607
  if (/endorsement/.test(text)) return "endorsement";
2608
2608
  if (/schedule|declarations?/.test(text)) return "schedule";
@@ -2638,6 +2638,90 @@ function makeNode(params) {
2638
2638
  metadata: params.metadata
2639
2639
  };
2640
2640
  }
2641
+ function metadataString(metadata, key) {
2642
+ const value = metadata?.[key];
2643
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
2644
+ }
2645
+ function isTitleContentNode(node) {
2646
+ if (node.kind !== "text") return false;
2647
+ return metadataString(node.metadata, "elementType") === "title" || metadataString(node.metadata, "sourceUnit") === "title";
2648
+ }
2649
+ function nodePageEnd(node) {
2650
+ return node.pageEnd ?? node.pageStart;
2651
+ }
2652
+ function groupPageContentByTitles(nodes) {
2653
+ const byParent = /* @__PURE__ */ new Map();
2654
+ for (const node of nodes) {
2655
+ const children = byParent.get(node.parentId) ?? [];
2656
+ children.push(node);
2657
+ byParent.set(node.parentId, children);
2658
+ }
2659
+ for (const children of byParent.values()) {
2660
+ children.sort((left, right) => left.order - right.order || left.id.localeCompare(right.id));
2661
+ }
2662
+ const byId = new Map(nodes.map((node) => [node.id, node]));
2663
+ for (const pageNode of nodes.filter((node) => node.kind === "page")) {
2664
+ const children = (byParent.get(pageNode.id) ?? []).filter((child) => child.kind !== "table_row" && child.kind !== "table_cell");
2665
+ let activeTitle;
2666
+ let activeContent = [];
2667
+ const flush = () => {
2668
+ if (!activeTitle || activeContent.length === 0) {
2669
+ activeTitle = void 0;
2670
+ activeContent = [];
2671
+ return;
2672
+ }
2673
+ const contentNodes = activeContent.map((node) => byId.get(node.id) ?? node).filter((node) => node.parentId === pageNode.id);
2674
+ if (contentNodes.length === 0) {
2675
+ activeTitle = void 0;
2676
+ activeContent = [];
2677
+ return;
2678
+ }
2679
+ const evidenceNodes = [activeTitle, ...contentNodes];
2680
+ const title = truncate(activeTitle.textExcerpt ?? activeTitle.title, 120);
2681
+ const pageStarts = evidenceNodes.map((node) => node.pageStart).filter((page) => typeof page === "number");
2682
+ const pageEnds = evidenceNodes.map(nodePageEnd).filter((page) => typeof page === "number");
2683
+ const sourceSpanIds = [...new Set(evidenceNodes.flatMap((node) => node.sourceSpanIds))];
2684
+ const bbox = evidenceNodes.flatMap((node) => node.bbox ?? []).slice(0, 12);
2685
+ byId.set(activeTitle.id, {
2686
+ ...activeTitle,
2687
+ kind: "section",
2688
+ title,
2689
+ description: nodeTextDescription({
2690
+ kind: "section",
2691
+ title,
2692
+ text: evidenceNodes.map((node) => node.textExcerpt).filter(Boolean).join("\n\n"),
2693
+ page: activeTitle.pageStart
2694
+ }),
2695
+ textExcerpt: evidenceNodes.map((node) => node.textExcerpt).filter(Boolean).join("\n\n").slice(0, 1600),
2696
+ sourceSpanIds,
2697
+ pageStart: pageStarts.length ? Math.min(...pageStarts) : activeTitle.pageStart,
2698
+ pageEnd: pageEnds.length ? Math.max(...pageEnds) : activeTitle.pageEnd,
2699
+ bbox,
2700
+ metadata: {
2701
+ ...activeTitle.metadata,
2702
+ sourceTreeVersion: "v3",
2703
+ organizer: "title_block"
2704
+ }
2705
+ });
2706
+ for (const node of contentNodes) {
2707
+ byId.set(node.id, { ...node, parentId: activeTitle.id });
2708
+ }
2709
+ activeTitle = void 0;
2710
+ activeContent = [];
2711
+ };
2712
+ for (const child of children) {
2713
+ if (isTitleContentNode(child)) {
2714
+ flush();
2715
+ activeTitle = child;
2716
+ activeContent = [];
2717
+ continue;
2718
+ }
2719
+ if (activeTitle) activeContent.push(child);
2720
+ }
2721
+ flush();
2722
+ }
2723
+ return nodes.map((node) => byId.get(node.id) ?? node);
2724
+ }
2641
2725
  function sortSpans(left, right) {
2642
2726
  const leftPage = pageStart(left) ?? 0;
2643
2727
  const rightPage = pageStart(right) ?? 0;
@@ -2813,7 +2897,7 @@ function buildDocumentSourceTree(sourceSpans, documentId) {
2813
2897
  }
2814
2898
  addStandaloneSpanNode(span, pageParentId);
2815
2899
  }
2816
- return normalizeDocumentSourceTreePaths([...nodes.values()]);
2900
+ return normalizeDocumentSourceTreePaths(groupPageContentByTitles([...nodes.values()]));
2817
2901
  }
2818
2902
 
2819
2903
  // src/source/operational-profile.ts
@@ -9009,9 +9093,9 @@ var ORGANIZABLE_KINDS = [
9009
9093
  "clause"
9010
9094
  ];
9011
9095
  var ORGANIZATION_TOP_LEVEL_BATCH_SIZE = 80;
9012
- var ORGANIZATION_CHILD_CONTEXT_LIMIT = 4;
9013
9096
  var ORGANIZER_MAX_SOURCE_SPANS = 400;
9014
9097
  var ORGANIZER_MAX_TOP_LEVEL_NODES = 18;
9098
+ var OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES = 80;
9015
9099
  var SourceTreeOrganizationSchema = z41.object({
9016
9100
  labels: z41.array(z41.object({
9017
9101
  nodeId: z41.string(),
@@ -9128,6 +9212,22 @@ function endorsementDescription(title, node) {
9128
9212
  title
9129
9213
  );
9130
9214
  }
9215
+ function nodePageEnd2(node) {
9216
+ return node.pageEnd ?? node.pageStart;
9217
+ }
9218
+ function pageRangeForNodes(nodes) {
9219
+ const pageStarts = nodes.map((node) => node.pageStart).filter((page) => typeof page === "number");
9220
+ const pageEnds = nodes.map(nodePageEnd2).filter((page) => typeof page === "number");
9221
+ if (pageStarts.length === 0 || pageEnds.length === 0) return void 0;
9222
+ const start = Math.min(...pageStarts);
9223
+ const end = Math.max(...pageEnds);
9224
+ return start === end ? `page ${start}` : `pages ${start}-${end}`;
9225
+ }
9226
+ function descriptionWithPages(description, nodes) {
9227
+ const range = pageRangeForNodes(nodes);
9228
+ if (!range || new RegExp(`\\b${range.replace("-", "\\-")}\\b`, "i").test(description)) return description;
9229
+ return `${description}; ${range}`;
9230
+ }
9131
9231
  function semanticGroupNodeId(documentId, kind, title, childNodeIds) {
9132
9232
  return [
9133
9233
  documentId.replace(/[^a-zA-Z0-9_.:-]/g, "_"),
@@ -9138,7 +9238,12 @@ function semanticGroupNodeId(documentId, kind, title, childNodeIds) {
9138
9238
  ].join(":");
9139
9239
  }
9140
9240
  function looksLikeDeclarationsStart(node) {
9141
- return /(^|\b)declarations?\b/i.test(sourceNodeText(node)) && !/\bforms?\s+and\s+endorsements?\b/i.test(sourceNodeText(node));
9241
+ const title = cleanText(node.title, "");
9242
+ const text = sourceNodeText(node);
9243
+ if (/\b(important notice|privacy notice|ofac advisory|terrorism risk insurance act|how to report a claim)\b/i.test(text)) {
9244
+ return false;
9245
+ }
9246
+ return /^declarations?$/i.test(title) || /\bdeclarations?\s+(page|schedule|section)\b/i.test(text) || /^declarations?\b/i.test(cleanText(node.textExcerpt, ""));
9142
9247
  }
9143
9248
  function looksLikeDeclarationsContinuation(node) {
9144
9249
  const text = sourceNodeText(node);
@@ -9173,7 +9278,7 @@ function groupAdjacentChildren(params) {
9173
9278
  parentId,
9174
9279
  kind: params.kind,
9175
9280
  title: params.title,
9176
- description: params.description,
9281
+ description: descriptionWithPages(params.description, children),
9177
9282
  textExcerpt: children.map((child) => child.textExcerpt ?? child.description).filter(Boolean).join("\n\n").slice(0, 1600),
9178
9283
  sourceSpanIds,
9179
9284
  pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
@@ -9226,6 +9331,22 @@ function applySemanticPageGrouping(sourceTree) {
9226
9331
  const children = (nodesByParent(relabeled).get(rootId) ?? []).filter((node) => node.kind !== "document").sort((left, right) => left.order - right.order);
9227
9332
  let nextTree = relabeled;
9228
9333
  const declarationsStartIndex = children.findIndex(looksLikeDeclarationsStart);
9334
+ const firstCoreIndex = children.findIndex(
9335
+ (child) => looksLikeDeclarationsStart(child) || looksLikePolicyFormStart(child) || looksLikeEndorsementStart(child)
9336
+ );
9337
+ const frontMatterBoundary = declarationsStartIndex >= 0 ? declarationsStartIndex : firstCoreIndex;
9338
+ if (frontMatterBoundary > 0) {
9339
+ const frontMatterIds = children.slice(0, frontMatterBoundary).map((child) => child.id);
9340
+ nextTree = groupAdjacentChildren({
9341
+ sourceTree: nextTree,
9342
+ children,
9343
+ childIds: frontMatterIds,
9344
+ kind: "page_group",
9345
+ title: "Notices and Jacket",
9346
+ description: "Policy jacket, notices, and administrative pages grouped by source order",
9347
+ organizer: "semantic_front_matter_grouping"
9348
+ });
9349
+ }
9229
9350
  if (declarationsStartIndex >= 0) {
9230
9351
  const declarationIds = [];
9231
9352
  for (let index = declarationsStartIndex; index < children.length; index += 1) {
@@ -9394,17 +9515,19 @@ function normalizeContainerEvidenceFromChildren(sourceTree) {
9394
9515
  return sourceTree.map((node) => byId.get(node.id) ?? node);
9395
9516
  }
9396
9517
  function normalizeSemanticHierarchy(sourceTree) {
9397
- return normalizeDocumentSourceTreePaths(
9398
- normalizeContainerEvidenceFromChildren(
9399
- nestEndorsementContinuationPages(
9400
- normalizePolicyFormStructure(
9401
- normalizeDocumentSourceTreePaths(sourceTree)
9402
- )
9403
- )
9518
+ const normalized = normalizeDocumentSourceTreePaths(
9519
+ normalizePolicyFormStructure(
9520
+ normalizeDocumentSourceTreePaths(sourceTree)
9404
9521
  )
9405
9522
  );
9523
+ const nested = normalizeDocumentSourceTreePaths(nestEndorsementContinuationPages(normalized));
9524
+ const withEvidence = normalizeContainerEvidenceFromChildren(nested);
9525
+ return normalizeDocumentSourceTreePaths(
9526
+ normalizeContainerEvidenceFromChildren(withEvidence)
9527
+ );
9406
9528
  }
9407
9529
  function applyEndorsementGrouping(sourceTree) {
9530
+ const rootId = sourceTreeRootId(sourceTree);
9408
9531
  const relabeledTree = sourceTree.map((node) => {
9409
9532
  if (node.kind === "document" || isEndorsementGroup(node)) return node;
9410
9533
  const title = endorsementStartTitle(node);
@@ -9442,7 +9565,7 @@ function applyEndorsementGrouping(sourceTree) {
9442
9565
  ...node,
9443
9566
  kind: "page_group",
9444
9567
  title: "Endorsements",
9445
- description: cleanText(node.description, "Endorsement forms grouped by source order"),
9568
+ description: descriptionWithPages(cleanText(node.description, "Endorsement forms grouped by source order"), byParent.get(node.id) ?? [node]),
9446
9569
  metadata: {
9447
9570
  ...node.metadata,
9448
9571
  sourceTreeVersion: "v3",
@@ -9469,9 +9592,10 @@ function applyEndorsementGrouping(sourceTree) {
9469
9592
  };
9470
9593
  });
9471
9594
  for (const [parentId, children] of byParent) {
9595
+ if (parentId !== rootId) continue;
9472
9596
  if (endorsementGroupIds.has(parentId ?? "")) continue;
9473
9597
  const endorsementChildren = children.filter((child) => child.kind === "endorsement" && !isEndorsementGroup(child));
9474
- if (endorsementChildren.length < 2) continue;
9598
+ if (endorsementChildren.length < 1) continue;
9475
9599
  const endorsementGroupChildren = [];
9476
9600
  let hasSeenEndorsementStart = false;
9477
9601
  for (const child of children) {
@@ -9484,6 +9608,7 @@ function applyEndorsementGrouping(sourceTree) {
9484
9608
  endorsementGroupChildren.push(child);
9485
9609
  }
9486
9610
  }
9611
+ if (endorsementGroupChildren.length < 1) continue;
9487
9612
  const documentId = endorsementChildren[0].documentId;
9488
9613
  const pageStarts = endorsementGroupChildren.map((child) => child.pageStart).filter((page) => typeof page === "number");
9489
9614
  const pageEnds = endorsementGroupChildren.map((child) => child.pageEnd ?? child.pageStart).filter((page) => typeof page === "number");
@@ -9496,7 +9621,7 @@ function applyEndorsementGrouping(sourceTree) {
9496
9621
  parentId,
9497
9622
  kind: "page_group",
9498
9623
  title: "Endorsements",
9499
- description: "Endorsement forms grouped by source order",
9624
+ description: descriptionWithPages("Endorsement forms grouped by source order", endorsementGroupChildren),
9500
9625
  textExcerpt: void 0,
9501
9626
  sourceSpanIds: [],
9502
9627
  pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
@@ -9507,11 +9632,13 @@ function applyEndorsementGrouping(sourceTree) {
9507
9632
  metadata: { sourceTreeVersion: "v3", organizer: "endorsement_grouping" }
9508
9633
  };
9509
9634
  const childSpanIds = [...new Set(endorsementGroupChildren.flatMap((child) => child.sourceSpanIds))];
9635
+ const childPageStart = pageStarts.length ? Math.min(...pageStarts) : void 0;
9636
+ const childPageEnd = pageEnds.length ? Math.max(...pageEnds) : void 0;
9510
9637
  const normalizedGroup = {
9511
9638
  ...groupNode,
9512
9639
  sourceSpanIds: groupNode.sourceSpanIds.length ? groupNode.sourceSpanIds : childSpanIds,
9513
- pageStart: groupNode.pageStart ?? (pageStarts.length ? Math.min(...pageStarts) : void 0),
9514
- pageEnd: groupNode.pageEnd ?? (pageEnds.length ? Math.max(...pageEnds) : void 0),
9640
+ pageStart: childPageStart === void 0 ? groupNode.pageStart : groupNode.pageStart === void 0 ? childPageStart : Math.min(groupNode.pageStart, childPageStart),
9641
+ pageEnd: childPageEnd === void 0 ? groupNode.pageEnd : groupNode.pageEnd === void 0 ? childPageEnd : Math.max(groupNode.pageEnd, childPageEnd),
9515
9642
  order
9516
9643
  };
9517
9644
  groupsByParent.set(parentId, normalizedGroup);
@@ -9587,10 +9714,6 @@ function organizationBatches(sourceTree) {
9587
9714
  const candidates = /* @__PURE__ */ new Map();
9588
9715
  for (const node of topLevelBatch) {
9589
9716
  candidates.set(node.id, node);
9590
- const childContext = (byParent.get(node.id) ?? []).filter((child) => child.kind !== "text" && child.kind !== "table_cell").slice(0, ORGANIZATION_CHILD_CONTEXT_LIMIT);
9591
- for (const child of childContext) {
9592
- candidates.set(child.id, child);
9593
- }
9594
9717
  }
9595
9718
  batches.push({
9596
9719
  label: `top-level nodes ${index + 1}-${index + topLevelBatch.length} of ${topLevelNodes.length}`,
@@ -9641,6 +9764,42 @@ Rules:
9641
9764
  Source nodes:
9642
9765
  ${JSON.stringify(nodes, null, 2)}
9643
9766
 
9767
+ Return JSON with labels and groups only.`;
9768
+ }
9769
+ function shouldRunOutlineCleanup(sourceTree) {
9770
+ const topLevel = rootChildren(sourceTree);
9771
+ if (topLevel.length === 0 || topLevel.length > OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES) return false;
9772
+ const genericPages = topLevel.filter((node) => node.kind === "page" && /^Page\s+\d+$/i.test(node.title));
9773
+ const hasDeclarations = topLevel.some((node) => node.title === "Declarations");
9774
+ const hasPolicyForm = topLevel.some((node) => node.title === "Policy Form");
9775
+ const hasEndorsements = topLevel.some((node) => node.title === "Endorsements");
9776
+ if (hasDeclarations && hasPolicyForm && hasEndorsements) return false;
9777
+ return genericPages.length > 0 || topLevel.length > 6 || !hasDeclarations || !hasPolicyForm || !hasEndorsements;
9778
+ }
9779
+ function buildOutlineCleanupPrompt(sourceTree) {
9780
+ const topLevel = rootChildren(sourceTree).slice(0, OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES);
9781
+ const nodes = topLevel.map((node) => compactNode(node, 900));
9782
+ return `You clean a top-level source outline for an insurance policy.
9783
+
9784
+ Expected product-facing order:
9785
+ 1. Optional front matter: policy jacket, important notices, privacy notices, OFAC notices, TRIA/terrorism notices, marketing/admin pages, signatures, countersignatures, or other pages that are not the declarations, policy wording, or endorsements.
9786
+ 2. Declarations: declarations page(s), schedules, named insured/policy period/premium rows, coverage limit schedules, forms-and-endorsements schedules.
9787
+ 3. Policy Form: the main policy wording, insuring agreements, definitions, exclusions, conditions, claim provisions, and general policy terms.
9788
+ 4. Endorsements: one generic "Endorsements" page_group containing each separately numbered endorsement as its own child.
9789
+
9790
+ Rules:
9791
+ - Use only node IDs from this top-level list: ${JSON.stringify(topLevel.map((node) => node.id))}
9792
+ - Group only adjacent top-level nodes.
9793
+ - Do not invent text, pages, source spans, limits, or policy facts.
9794
+ - Do not merge individually numbered endorsements into one endorsement node; use the generic "Endorsements" parent for the series.
9795
+ - Use canonical terse titles: "Notices and Jacket", "Declarations", "Policy Form", "Endorsements", or the printed endorsement number.
9796
+ - Keep page_group descriptions short and include the page range when pages are known, for example "Declarations pages 6-8".
9797
+ - If a page is an OFAC, privacy, terrorism/TRIA, claim-reporting notice, signature page, or jacket, do not label it as declarations or policy form.
9798
+ - If the existing deterministic outline is already correct, return empty labels and groups.
9799
+
9800
+ Top-level source nodes:
9801
+ ${JSON.stringify(nodes, null, 2)}
9802
+
9644
9803
  Return JSON with labels and groups only.`;
9645
9804
  }
9646
9805
  function buildOperationalProfilePrompt(sourceTree, fallback) {
@@ -9695,7 +9854,7 @@ function applyOrganization(sourceTree, organization) {
9695
9854
  if (!children.every((child) => child.parentId === parentId)) continue;
9696
9855
  const documentId = children[0].documentId;
9697
9856
  const title = simplifyOrganizerTitle(group.title, group.title, group.kind);
9698
- const description = cleanText(group.description, title);
9857
+ const description = descriptionWithPages(cleanText(group.description, title), children);
9699
9858
  const id = groupNodeId(documentId, { ...group, title });
9700
9859
  if (byId.has(id)) continue;
9701
9860
  const sourceSpanIds = [...new Set(children.flatMap((child) => child.sourceSpanIds))];
@@ -9886,8 +10045,7 @@ async function runSourceTreeExtraction(params) {
9886
10045
  schema: SourceTreeOrganizationSchema,
9887
10046
  maxTokens: budget.maxTokens,
9888
10047
  taskKind: "extraction_source_tree",
9889
- budgetDiagnostics: budget,
9890
- providerOptions: params.providerOptions
10048
+ budgetDiagnostics: budget
9891
10049
  },
9892
10050
  {
9893
10051
  fallback: { labels: [], groups: [] },
@@ -9909,6 +10067,36 @@ async function runSourceTreeExtraction(params) {
9909
10067
  } else {
9910
10068
  await params.log?.("Deterministic source tree ready; skipped model organizer");
9911
10069
  }
10070
+ if (shouldRunOutlineCleanup(sourceTree)) {
10071
+ try {
10072
+ const budget = params.resolveBudget("extraction_source_tree", 1600);
10073
+ const maxTokens = Math.min(budget.maxTokens, 1600);
10074
+ const startedAt = Date.now();
10075
+ const response = await safeGenerateObject(
10076
+ params.generateObject,
10077
+ {
10078
+ prompt: buildOutlineCleanupPrompt(sourceTree),
10079
+ schema: SourceTreeOrganizationSchema,
10080
+ maxTokens,
10081
+ taskKind: "extraction_source_tree",
10082
+ budgetDiagnostics: { ...budget, maxTokens }
10083
+ },
10084
+ {
10085
+ fallback: { labels: [], groups: [] },
10086
+ log: params.log
10087
+ }
10088
+ );
10089
+ localTrack(response.usage, {
10090
+ taskKind: "extraction_source_tree",
10091
+ label: "source_tree_outline_cleanup",
10092
+ maxTokens,
10093
+ durationMs: Date.now() - startedAt
10094
+ });
10095
+ sourceTree = applyOrganization(sourceTree, response.object);
10096
+ } catch (error) {
10097
+ warnings.push(`Source-tree outline cleanup failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
10098
+ }
10099
+ }
9912
10100
  const deterministicProfile = buildDeterministicOperationalProfile({
9913
10101
  sourceTree,
9914
10102
  sourceSpans