@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.js CHANGED
@@ -2957,7 +2957,7 @@ function normalizeNodeKind(span) {
2957
2957
  if (unit === "table_cell") return "table_cell";
2958
2958
  if (unit === "key_value") return "schedule";
2959
2959
  if (unit === "section") return "section";
2960
- if (element === "title" || element === "section_candidate") {
2960
+ if (element === "section_candidate") {
2961
2961
  const text = span.text.toLowerCase();
2962
2962
  if (/endorsement/.test(text)) return "endorsement";
2963
2963
  if (/schedule|declarations?/.test(text)) return "schedule";
@@ -2993,6 +2993,90 @@ function makeNode(params) {
2993
2993
  metadata: params.metadata
2994
2994
  };
2995
2995
  }
2996
+ function metadataString(metadata, key) {
2997
+ const value = metadata?.[key];
2998
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
2999
+ }
3000
+ function isTitleContentNode(node) {
3001
+ if (node.kind !== "text") return false;
3002
+ return metadataString(node.metadata, "elementType") === "title" || metadataString(node.metadata, "sourceUnit") === "title";
3003
+ }
3004
+ function nodePageEnd(node) {
3005
+ return node.pageEnd ?? node.pageStart;
3006
+ }
3007
+ function groupPageContentByTitles(nodes) {
3008
+ const byParent = /* @__PURE__ */ new Map();
3009
+ for (const node of nodes) {
3010
+ const children = byParent.get(node.parentId) ?? [];
3011
+ children.push(node);
3012
+ byParent.set(node.parentId, children);
3013
+ }
3014
+ for (const children of byParent.values()) {
3015
+ children.sort((left, right) => left.order - right.order || left.id.localeCompare(right.id));
3016
+ }
3017
+ const byId = new Map(nodes.map((node) => [node.id, node]));
3018
+ for (const pageNode of nodes.filter((node) => node.kind === "page")) {
3019
+ const children = (byParent.get(pageNode.id) ?? []).filter((child) => child.kind !== "table_row" && child.kind !== "table_cell");
3020
+ let activeTitle;
3021
+ let activeContent = [];
3022
+ const flush = () => {
3023
+ if (!activeTitle || activeContent.length === 0) {
3024
+ activeTitle = void 0;
3025
+ activeContent = [];
3026
+ return;
3027
+ }
3028
+ const contentNodes = activeContent.map((node) => byId.get(node.id) ?? node).filter((node) => node.parentId === pageNode.id);
3029
+ if (contentNodes.length === 0) {
3030
+ activeTitle = void 0;
3031
+ activeContent = [];
3032
+ return;
3033
+ }
3034
+ const evidenceNodes = [activeTitle, ...contentNodes];
3035
+ const title = truncate(activeTitle.textExcerpt ?? activeTitle.title, 120);
3036
+ const pageStarts = evidenceNodes.map((node) => node.pageStart).filter((page) => typeof page === "number");
3037
+ const pageEnds = evidenceNodes.map(nodePageEnd).filter((page) => typeof page === "number");
3038
+ const sourceSpanIds = [...new Set(evidenceNodes.flatMap((node) => node.sourceSpanIds))];
3039
+ const bbox = evidenceNodes.flatMap((node) => node.bbox ?? []).slice(0, 12);
3040
+ byId.set(activeTitle.id, {
3041
+ ...activeTitle,
3042
+ kind: "section",
3043
+ title,
3044
+ description: nodeTextDescription({
3045
+ kind: "section",
3046
+ title,
3047
+ text: evidenceNodes.map((node) => node.textExcerpt).filter(Boolean).join("\n\n"),
3048
+ page: activeTitle.pageStart
3049
+ }),
3050
+ textExcerpt: evidenceNodes.map((node) => node.textExcerpt).filter(Boolean).join("\n\n").slice(0, 1600),
3051
+ sourceSpanIds,
3052
+ pageStart: pageStarts.length ? Math.min(...pageStarts) : activeTitle.pageStart,
3053
+ pageEnd: pageEnds.length ? Math.max(...pageEnds) : activeTitle.pageEnd,
3054
+ bbox,
3055
+ metadata: {
3056
+ ...activeTitle.metadata,
3057
+ sourceTreeVersion: "v3",
3058
+ organizer: "title_block"
3059
+ }
3060
+ });
3061
+ for (const node of contentNodes) {
3062
+ byId.set(node.id, { ...node, parentId: activeTitle.id });
3063
+ }
3064
+ activeTitle = void 0;
3065
+ activeContent = [];
3066
+ };
3067
+ for (const child of children) {
3068
+ if (isTitleContentNode(child)) {
3069
+ flush();
3070
+ activeTitle = child;
3071
+ activeContent = [];
3072
+ continue;
3073
+ }
3074
+ if (activeTitle) activeContent.push(child);
3075
+ }
3076
+ flush();
3077
+ }
3078
+ return nodes.map((node) => byId.get(node.id) ?? node);
3079
+ }
2996
3080
  function sortSpans(left, right) {
2997
3081
  const leftPage = pageStart(left) ?? 0;
2998
3082
  const rightPage = pageStart(right) ?? 0;
@@ -3168,7 +3252,7 @@ function buildDocumentSourceTree(sourceSpans, documentId) {
3168
3252
  }
3169
3253
  addStandaloneSpanNode(span, pageParentId);
3170
3254
  }
3171
- return normalizeDocumentSourceTreePaths([...nodes.values()]);
3255
+ return normalizeDocumentSourceTreePaths(groupPageContentByTitles([...nodes.values()]));
3172
3256
  }
3173
3257
 
3174
3258
  // src/source/operational-profile.ts
@@ -9356,9 +9440,9 @@ var ORGANIZABLE_KINDS = [
9356
9440
  "clause"
9357
9441
  ];
9358
9442
  var ORGANIZATION_TOP_LEVEL_BATCH_SIZE = 80;
9359
- var ORGANIZATION_CHILD_CONTEXT_LIMIT = 4;
9360
9443
  var ORGANIZER_MAX_SOURCE_SPANS = 400;
9361
9444
  var ORGANIZER_MAX_TOP_LEVEL_NODES = 18;
9445
+ var OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES = 80;
9362
9446
  var SourceTreeOrganizationSchema = import_zod41.z.object({
9363
9447
  labels: import_zod41.z.array(import_zod41.z.object({
9364
9448
  nodeId: import_zod41.z.string(),
@@ -9475,6 +9559,22 @@ function endorsementDescription(title, node) {
9475
9559
  title
9476
9560
  );
9477
9561
  }
9562
+ function nodePageEnd2(node) {
9563
+ return node.pageEnd ?? node.pageStart;
9564
+ }
9565
+ function pageRangeForNodes(nodes) {
9566
+ const pageStarts = nodes.map((node) => node.pageStart).filter((page) => typeof page === "number");
9567
+ const pageEnds = nodes.map(nodePageEnd2).filter((page) => typeof page === "number");
9568
+ if (pageStarts.length === 0 || pageEnds.length === 0) return void 0;
9569
+ const start = Math.min(...pageStarts);
9570
+ const end = Math.max(...pageEnds);
9571
+ return start === end ? `page ${start}` : `pages ${start}-${end}`;
9572
+ }
9573
+ function descriptionWithPages(description, nodes) {
9574
+ const range = pageRangeForNodes(nodes);
9575
+ if (!range || new RegExp(`\\b${range.replace("-", "\\-")}\\b`, "i").test(description)) return description;
9576
+ return `${description}; ${range}`;
9577
+ }
9478
9578
  function semanticGroupNodeId(documentId, kind, title, childNodeIds) {
9479
9579
  return [
9480
9580
  documentId.replace(/[^a-zA-Z0-9_.:-]/g, "_"),
@@ -9485,7 +9585,12 @@ function semanticGroupNodeId(documentId, kind, title, childNodeIds) {
9485
9585
  ].join(":");
9486
9586
  }
9487
9587
  function looksLikeDeclarationsStart(node) {
9488
- return /(^|\b)declarations?\b/i.test(sourceNodeText(node)) && !/\bforms?\s+and\s+endorsements?\b/i.test(sourceNodeText(node));
9588
+ const title = cleanText(node.title, "");
9589
+ const text = sourceNodeText(node);
9590
+ if (/\b(important notice|privacy notice|ofac advisory|terrorism risk insurance act|how to report a claim)\b/i.test(text)) {
9591
+ return false;
9592
+ }
9593
+ return /^declarations?$/i.test(title) || /\bdeclarations?\s+(page|schedule|section)\b/i.test(text) || /^declarations?\b/i.test(cleanText(node.textExcerpt, ""));
9489
9594
  }
9490
9595
  function looksLikeDeclarationsContinuation(node) {
9491
9596
  const text = sourceNodeText(node);
@@ -9520,7 +9625,7 @@ function groupAdjacentChildren(params) {
9520
9625
  parentId,
9521
9626
  kind: params.kind,
9522
9627
  title: params.title,
9523
- description: params.description,
9628
+ description: descriptionWithPages(params.description, children),
9524
9629
  textExcerpt: children.map((child) => child.textExcerpt ?? child.description).filter(Boolean).join("\n\n").slice(0, 1600),
9525
9630
  sourceSpanIds,
9526
9631
  pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
@@ -9573,6 +9678,22 @@ function applySemanticPageGrouping(sourceTree) {
9573
9678
  const children = (nodesByParent(relabeled).get(rootId) ?? []).filter((node) => node.kind !== "document").sort((left, right) => left.order - right.order);
9574
9679
  let nextTree = relabeled;
9575
9680
  const declarationsStartIndex = children.findIndex(looksLikeDeclarationsStart);
9681
+ const firstCoreIndex = children.findIndex(
9682
+ (child) => looksLikeDeclarationsStart(child) || looksLikePolicyFormStart(child) || looksLikeEndorsementStart(child)
9683
+ );
9684
+ const frontMatterBoundary = declarationsStartIndex >= 0 ? declarationsStartIndex : firstCoreIndex;
9685
+ if (frontMatterBoundary > 0) {
9686
+ const frontMatterIds = children.slice(0, frontMatterBoundary).map((child) => child.id);
9687
+ nextTree = groupAdjacentChildren({
9688
+ sourceTree: nextTree,
9689
+ children,
9690
+ childIds: frontMatterIds,
9691
+ kind: "page_group",
9692
+ title: "Notices and Jacket",
9693
+ description: "Policy jacket, notices, and administrative pages grouped by source order",
9694
+ organizer: "semantic_front_matter_grouping"
9695
+ });
9696
+ }
9576
9697
  if (declarationsStartIndex >= 0) {
9577
9698
  const declarationIds = [];
9578
9699
  for (let index = declarationsStartIndex; index < children.length; index += 1) {
@@ -9741,17 +9862,19 @@ function normalizeContainerEvidenceFromChildren(sourceTree) {
9741
9862
  return sourceTree.map((node) => byId.get(node.id) ?? node);
9742
9863
  }
9743
9864
  function normalizeSemanticHierarchy(sourceTree) {
9744
- return normalizeDocumentSourceTreePaths(
9745
- normalizeContainerEvidenceFromChildren(
9746
- nestEndorsementContinuationPages(
9747
- normalizePolicyFormStructure(
9748
- normalizeDocumentSourceTreePaths(sourceTree)
9749
- )
9750
- )
9865
+ const normalized = normalizeDocumentSourceTreePaths(
9866
+ normalizePolicyFormStructure(
9867
+ normalizeDocumentSourceTreePaths(sourceTree)
9751
9868
  )
9752
9869
  );
9870
+ const nested = normalizeDocumentSourceTreePaths(nestEndorsementContinuationPages(normalized));
9871
+ const withEvidence = normalizeContainerEvidenceFromChildren(nested);
9872
+ return normalizeDocumentSourceTreePaths(
9873
+ normalizeContainerEvidenceFromChildren(withEvidence)
9874
+ );
9753
9875
  }
9754
9876
  function applyEndorsementGrouping(sourceTree) {
9877
+ const rootId = sourceTreeRootId(sourceTree);
9755
9878
  const relabeledTree = sourceTree.map((node) => {
9756
9879
  if (node.kind === "document" || isEndorsementGroup(node)) return node;
9757
9880
  const title = endorsementStartTitle(node);
@@ -9789,7 +9912,7 @@ function applyEndorsementGrouping(sourceTree) {
9789
9912
  ...node,
9790
9913
  kind: "page_group",
9791
9914
  title: "Endorsements",
9792
- description: cleanText(node.description, "Endorsement forms grouped by source order"),
9915
+ description: descriptionWithPages(cleanText(node.description, "Endorsement forms grouped by source order"), byParent.get(node.id) ?? [node]),
9793
9916
  metadata: {
9794
9917
  ...node.metadata,
9795
9918
  sourceTreeVersion: "v3",
@@ -9816,9 +9939,10 @@ function applyEndorsementGrouping(sourceTree) {
9816
9939
  };
9817
9940
  });
9818
9941
  for (const [parentId, children] of byParent) {
9942
+ if (parentId !== rootId) continue;
9819
9943
  if (endorsementGroupIds.has(parentId ?? "")) continue;
9820
9944
  const endorsementChildren = children.filter((child) => child.kind === "endorsement" && !isEndorsementGroup(child));
9821
- if (endorsementChildren.length < 2) continue;
9945
+ if (endorsementChildren.length < 1) continue;
9822
9946
  const endorsementGroupChildren = [];
9823
9947
  let hasSeenEndorsementStart = false;
9824
9948
  for (const child of children) {
@@ -9831,6 +9955,7 @@ function applyEndorsementGrouping(sourceTree) {
9831
9955
  endorsementGroupChildren.push(child);
9832
9956
  }
9833
9957
  }
9958
+ if (endorsementGroupChildren.length < 1) continue;
9834
9959
  const documentId = endorsementChildren[0].documentId;
9835
9960
  const pageStarts = endorsementGroupChildren.map((child) => child.pageStart).filter((page) => typeof page === "number");
9836
9961
  const pageEnds = endorsementGroupChildren.map((child) => child.pageEnd ?? child.pageStart).filter((page) => typeof page === "number");
@@ -9843,7 +9968,7 @@ function applyEndorsementGrouping(sourceTree) {
9843
9968
  parentId,
9844
9969
  kind: "page_group",
9845
9970
  title: "Endorsements",
9846
- description: "Endorsement forms grouped by source order",
9971
+ description: descriptionWithPages("Endorsement forms grouped by source order", endorsementGroupChildren),
9847
9972
  textExcerpt: void 0,
9848
9973
  sourceSpanIds: [],
9849
9974
  pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
@@ -9854,11 +9979,13 @@ function applyEndorsementGrouping(sourceTree) {
9854
9979
  metadata: { sourceTreeVersion: "v3", organizer: "endorsement_grouping" }
9855
9980
  };
9856
9981
  const childSpanIds = [...new Set(endorsementGroupChildren.flatMap((child) => child.sourceSpanIds))];
9982
+ const childPageStart = pageStarts.length ? Math.min(...pageStarts) : void 0;
9983
+ const childPageEnd = pageEnds.length ? Math.max(...pageEnds) : void 0;
9857
9984
  const normalizedGroup = {
9858
9985
  ...groupNode,
9859
9986
  sourceSpanIds: groupNode.sourceSpanIds.length ? groupNode.sourceSpanIds : childSpanIds,
9860
- pageStart: groupNode.pageStart ?? (pageStarts.length ? Math.min(...pageStarts) : void 0),
9861
- pageEnd: groupNode.pageEnd ?? (pageEnds.length ? Math.max(...pageEnds) : void 0),
9987
+ pageStart: childPageStart === void 0 ? groupNode.pageStart : groupNode.pageStart === void 0 ? childPageStart : Math.min(groupNode.pageStart, childPageStart),
9988
+ pageEnd: childPageEnd === void 0 ? groupNode.pageEnd : groupNode.pageEnd === void 0 ? childPageEnd : Math.max(groupNode.pageEnd, childPageEnd),
9862
9989
  order
9863
9990
  };
9864
9991
  groupsByParent.set(parentId, normalizedGroup);
@@ -9934,10 +10061,6 @@ function organizationBatches(sourceTree) {
9934
10061
  const candidates = /* @__PURE__ */ new Map();
9935
10062
  for (const node of topLevelBatch) {
9936
10063
  candidates.set(node.id, node);
9937
- const childContext = (byParent.get(node.id) ?? []).filter((child) => child.kind !== "text" && child.kind !== "table_cell").slice(0, ORGANIZATION_CHILD_CONTEXT_LIMIT);
9938
- for (const child of childContext) {
9939
- candidates.set(child.id, child);
9940
- }
9941
10064
  }
9942
10065
  batches.push({
9943
10066
  label: `top-level nodes ${index + 1}-${index + topLevelBatch.length} of ${topLevelNodes.length}`,
@@ -9988,6 +10111,42 @@ Rules:
9988
10111
  Source nodes:
9989
10112
  ${JSON.stringify(nodes, null, 2)}
9990
10113
 
10114
+ Return JSON with labels and groups only.`;
10115
+ }
10116
+ function shouldRunOutlineCleanup(sourceTree) {
10117
+ const topLevel = rootChildren(sourceTree);
10118
+ if (topLevel.length === 0 || topLevel.length > OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES) return false;
10119
+ const genericPages = topLevel.filter((node) => node.kind === "page" && /^Page\s+\d+$/i.test(node.title));
10120
+ const hasDeclarations = topLevel.some((node) => node.title === "Declarations");
10121
+ const hasPolicyForm = topLevel.some((node) => node.title === "Policy Form");
10122
+ const hasEndorsements = topLevel.some((node) => node.title === "Endorsements");
10123
+ if (hasDeclarations && hasPolicyForm && hasEndorsements) return false;
10124
+ return genericPages.length > 0 || topLevel.length > 6 || !hasDeclarations || !hasPolicyForm || !hasEndorsements;
10125
+ }
10126
+ function buildOutlineCleanupPrompt(sourceTree) {
10127
+ const topLevel = rootChildren(sourceTree).slice(0, OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES);
10128
+ const nodes = topLevel.map((node) => compactNode(node, 900));
10129
+ return `You clean a top-level source outline for an insurance policy.
10130
+
10131
+ Expected product-facing order:
10132
+ 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.
10133
+ 2. Declarations: declarations page(s), schedules, named insured/policy period/premium rows, coverage limit schedules, forms-and-endorsements schedules.
10134
+ 3. Policy Form: the main policy wording, insuring agreements, definitions, exclusions, conditions, claim provisions, and general policy terms.
10135
+ 4. Endorsements: one generic "Endorsements" page_group containing each separately numbered endorsement as its own child.
10136
+
10137
+ Rules:
10138
+ - Use only node IDs from this top-level list: ${JSON.stringify(topLevel.map((node) => node.id))}
10139
+ - Group only adjacent top-level nodes.
10140
+ - Do not invent text, pages, source spans, limits, or policy facts.
10141
+ - Do not merge individually numbered endorsements into one endorsement node; use the generic "Endorsements" parent for the series.
10142
+ - Use canonical terse titles: "Notices and Jacket", "Declarations", "Policy Form", "Endorsements", or the printed endorsement number.
10143
+ - Keep page_group descriptions short and include the page range when pages are known, for example "Declarations pages 6-8".
10144
+ - 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.
10145
+ - If the existing deterministic outline is already correct, return empty labels and groups.
10146
+
10147
+ Top-level source nodes:
10148
+ ${JSON.stringify(nodes, null, 2)}
10149
+
9991
10150
  Return JSON with labels and groups only.`;
9992
10151
  }
9993
10152
  function buildOperationalProfilePrompt(sourceTree, fallback) {
@@ -10042,7 +10201,7 @@ function applyOrganization(sourceTree, organization) {
10042
10201
  if (!children.every((child) => child.parentId === parentId)) continue;
10043
10202
  const documentId = children[0].documentId;
10044
10203
  const title = simplifyOrganizerTitle(group.title, group.title, group.kind);
10045
- const description = cleanText(group.description, title);
10204
+ const description = descriptionWithPages(cleanText(group.description, title), children);
10046
10205
  const id = groupNodeId(documentId, { ...group, title });
10047
10206
  if (byId.has(id)) continue;
10048
10207
  const sourceSpanIds = [...new Set(children.flatMap((child) => child.sourceSpanIds))];
@@ -10233,8 +10392,7 @@ async function runSourceTreeExtraction(params) {
10233
10392
  schema: SourceTreeOrganizationSchema,
10234
10393
  maxTokens: budget.maxTokens,
10235
10394
  taskKind: "extraction_source_tree",
10236
- budgetDiagnostics: budget,
10237
- providerOptions: params.providerOptions
10395
+ budgetDiagnostics: budget
10238
10396
  },
10239
10397
  {
10240
10398
  fallback: { labels: [], groups: [] },
@@ -10256,6 +10414,36 @@ async function runSourceTreeExtraction(params) {
10256
10414
  } else {
10257
10415
  await params.log?.("Deterministic source tree ready; skipped model organizer");
10258
10416
  }
10417
+ if (shouldRunOutlineCleanup(sourceTree)) {
10418
+ try {
10419
+ const budget = params.resolveBudget("extraction_source_tree", 1600);
10420
+ const maxTokens = Math.min(budget.maxTokens, 1600);
10421
+ const startedAt = Date.now();
10422
+ const response = await safeGenerateObject(
10423
+ params.generateObject,
10424
+ {
10425
+ prompt: buildOutlineCleanupPrompt(sourceTree),
10426
+ schema: SourceTreeOrganizationSchema,
10427
+ maxTokens,
10428
+ taskKind: "extraction_source_tree",
10429
+ budgetDiagnostics: { ...budget, maxTokens }
10430
+ },
10431
+ {
10432
+ fallback: { labels: [], groups: [] },
10433
+ log: params.log
10434
+ }
10435
+ );
10436
+ localTrack(response.usage, {
10437
+ taskKind: "extraction_source_tree",
10438
+ label: "source_tree_outline_cleanup",
10439
+ maxTokens,
10440
+ durationMs: Date.now() - startedAt
10441
+ });
10442
+ sourceTree = applyOrganization(sourceTree, response.object);
10443
+ } catch (error) {
10444
+ warnings.push(`Source-tree outline cleanup failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
10445
+ }
10446
+ }
10259
10447
  const deterministicProfile = buildDeterministicOperationalProfile({
10260
10448
  sourceTree,
10261
10449
  sourceSpans