@claritylabs/cl-sdk 3.0.5 → 3.0.7

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
@@ -9416,6 +9416,7 @@ function simplifyOrganizerTitle(value, fallback, kind) {
9416
9416
  if (/^declarations\b/i.test(title)) return "Declarations";
9417
9417
  if (/^policy\s+form\b/i.test(title)) return "Policy Form";
9418
9418
  if (/^definitions\b/i.test(title)) return "Definitions";
9419
+ if (kind === "page_group" && /^endorsements?\b/i.test(title)) return "Endorsements";
9419
9420
  const endorsementNumber = title.match(/^endorsement\s+(?:no\.?|number|#)?\s*([A-Z0-9][A-Z0-9.-]*)\b/i)?.[1];
9420
9421
  if (endorsementNumber) return `Endorsement No. ${endorsementNumber}`;
9421
9422
  if (kind === "endorsement" && /^endorsements?\s+\d+\s*[–-]\s*\d+\b/i.test(title)) {
@@ -9424,9 +9425,159 @@ function simplifyOrganizerTitle(value, fallback, kind) {
9424
9425
  return title;
9425
9426
  }
9426
9427
  function endorsementReference(value) {
9427
- return value?.match(/\bendorsement\s+(?:no\.?|number|#)?\s*([A-Z0-9][A-Z0-9.-]*)\b/i)?.[1]?.toUpperCase();
9428
+ const text = cleanText(value, "");
9429
+ const explicit = text.match(/\bendorsement\s+(?:no\.?|number|#)?\s*([A-Z0-9][A-Z0-9.-]*)\b/i)?.[1]?.toUpperCase();
9430
+ if (explicit) return explicit;
9431
+ return text.match(/\b(?:[A-Z]{2,}-)?END\s+0*([0-9]{1,4})\b/i)?.[1]?.toUpperCase();
9432
+ }
9433
+ function endorsementTitle(value) {
9434
+ const text = cleanText(value, "");
9435
+ const explicit = text.match(/\bendorsement\s+(?:no\.?|number|#)\s*([A-Z0-9][A-Z0-9.-]*)\b/i)?.[1]?.toUpperCase();
9436
+ const number = explicit ?? text.match(/\b(?:[A-Z]{2,}-)?END\s+0*([0-9]{1,4})\b/i)?.[1]?.toUpperCase();
9437
+ return number ? `Endorsement No. ${number}` : void 0;
9438
+ }
9439
+ function sourceNodeText(node) {
9440
+ return cleanText([node.title, node.description, node.textExcerpt].filter(Boolean).join(" "), "");
9441
+ }
9442
+ function semanticGroupNodeId(documentId, kind, title, childNodeIds) {
9443
+ return [
9444
+ documentId.replace(/[^a-zA-Z0-9_.:-]/g, "_"),
9445
+ "source_node",
9446
+ kind,
9447
+ title.replace(/[^a-zA-Z0-9_.:-]/g, "_").toLowerCase().slice(0, 48),
9448
+ childNodeIds.join("_").replace(/[^a-zA-Z0-9_.:-]/g, "_").slice(0, 80)
9449
+ ].join(":");
9450
+ }
9451
+ function looksLikeDeclarationsStart(node) {
9452
+ return /\bdeclarations?\s+(page|schedule)\b/i.test(sourceNodeText(node));
9453
+ }
9454
+ function looksLikeDeclarationsContinuation(node) {
9455
+ const text = sourceNodeText(node);
9456
+ return looksLikeDeclarationsStart(node) || /\b(item\s+\d+\.|coverage part|each claim limit|aggregate limit|retroactive date|self-insured retention|premium|payment plan|producer|broker|forms? and endorsements?|extended reporting period|discovery period)\b/i.test(text);
9457
+ }
9458
+ function looksLikePolicyFormStart(node) {
9459
+ const text = sourceNodeText(node);
9460
+ return /\bpolicy form\b/i.test(node.title) || /\btechnology errors?\s*&?\s*omissions\b/i.test(text) && /\bplease read this entire policy carefully\b/i.test(text) || /\bform\s+[A-Z]{2,}-[A-Z0-9-]+\s+\d{2}\s+\d{2}\b/i.test(text);
9461
+ }
9462
+ function looksLikePolicyFormContinuation(node) {
9463
+ const text = sourceNodeText(node);
9464
+ if (looksLikePolicyFormStart(node)) return true;
9465
+ return /\b(insuring agreement|definitions?|exclusions?|conditions?|claim means|insured means|wrongful act means|limits of liability|notice of claim|cancellation by|action against the company)\b/i.test(text);
9466
+ }
9467
+ function groupAdjacentChildren(params) {
9468
+ if (params.childIds.length < 2) return params.sourceTree;
9469
+ const children = params.childIds.map((id2) => params.children.find((child) => child.id === id2)).filter((child) => Boolean(child));
9470
+ if (children.length < 2) return params.sourceTree;
9471
+ const parentId = children[0].parentId;
9472
+ if (!children.every((child) => child.parentId === parentId)) return params.sourceTree;
9473
+ const documentId = children[0].documentId;
9474
+ const id = semanticGroupNodeId(documentId, params.kind, params.title, children.map((child) => child.id));
9475
+ if (params.sourceTree.some((node) => node.id === id)) return params.sourceTree;
9476
+ const pageStarts = children.map((child) => child.pageStart).filter((page) => typeof page === "number");
9477
+ const pageEnds = children.map((child) => child.pageEnd ?? child.pageStart).filter((page) => typeof page === "number");
9478
+ const sourceSpanIds = [...new Set(children.flatMap((child) => child.sourceSpanIds))];
9479
+ const order = Math.min(...children.map((child) => child.order));
9480
+ const groupNode = {
9481
+ id,
9482
+ documentId,
9483
+ parentId,
9484
+ kind: params.kind,
9485
+ title: params.title,
9486
+ description: params.description,
9487
+ textExcerpt: children.map((child) => child.textExcerpt ?? child.description).filter(Boolean).join("\n\n").slice(0, 1600),
9488
+ sourceSpanIds,
9489
+ pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
9490
+ pageEnd: pageEnds.length ? Math.max(...pageEnds) : void 0,
9491
+ bbox: children.flatMap((child) => child.bbox ?? []).slice(0, 12),
9492
+ order,
9493
+ path: "",
9494
+ metadata: { sourceTreeVersion: "v3", organizer: params.organizer }
9495
+ };
9496
+ const wanted = new Set(children.map((child) => child.id));
9497
+ return [
9498
+ ...params.sourceTree.map(
9499
+ (node) => wanted.has(node.id) ? { ...node, parentId: id, order: node.order + 1e-3 } : node
9500
+ ),
9501
+ groupNode
9502
+ ];
9503
+ }
9504
+ function applySemanticPageGrouping(sourceTree) {
9505
+ const relabeled = sourceTree.map((node) => {
9506
+ if (node.kind === "document" || node.kind === "page_group") return node;
9507
+ const text = sourceNodeText(node);
9508
+ const endorsement = endorsementTitle(text);
9509
+ if (endorsement && node.kind === "page") {
9510
+ return {
9511
+ ...node,
9512
+ kind: "endorsement",
9513
+ title: endorsement,
9514
+ description: cleanText([endorsement, "endorsement", node.pageStart ? `page ${node.pageStart}` : void 0, node.textExcerpt].filter(Boolean).join(" | "), endorsement),
9515
+ metadata: { ...node.metadata, organizerRepair: "semantic_page_grouping" }
9516
+ };
9517
+ }
9518
+ if (node.kind === "page" && looksLikeDeclarationsStart(node)) {
9519
+ return {
9520
+ ...node,
9521
+ title: "Declarations",
9522
+ description: cleanText([node.description, "Declarations"].join(" "), "Declarations"),
9523
+ metadata: { ...node.metadata, organizerRepair: "semantic_page_grouping" }
9524
+ };
9525
+ }
9526
+ if (node.kind === "page" && looksLikePolicyFormStart(node)) {
9527
+ return {
9528
+ ...node,
9529
+ title: "Policy Form",
9530
+ description: cleanText([node.description, "Policy Form"].join(" "), "Policy Form"),
9531
+ metadata: { ...node.metadata, organizerRepair: "semantic_page_grouping" }
9532
+ };
9533
+ }
9534
+ return node;
9535
+ });
9536
+ const rootId = sourceTreeRootId(relabeled);
9537
+ const children = (nodesByParent(relabeled).get(rootId) ?? []).filter((node) => node.kind !== "document").sort((left, right) => left.order - right.order);
9538
+ let nextTree = relabeled;
9539
+ const declarationsStartIndex = children.findIndex(looksLikeDeclarationsStart);
9540
+ if (declarationsStartIndex >= 0) {
9541
+ const declarationIds = [];
9542
+ for (let index = declarationsStartIndex; index < children.length; index += 1) {
9543
+ const child = children[index];
9544
+ if (index > declarationsStartIndex && (looksLikePolicyFormStart(child) || endorsementTitle(sourceNodeText(child)))) break;
9545
+ if (!looksLikeDeclarationsContinuation(child)) break;
9546
+ declarationIds.push(child.id);
9547
+ }
9548
+ nextTree = groupAdjacentChildren({
9549
+ sourceTree: nextTree,
9550
+ children,
9551
+ childIds: declarationIds,
9552
+ kind: "page_group",
9553
+ title: "Declarations",
9554
+ description: "Declarations pages and schedules grouped by source order",
9555
+ organizer: "semantic_declarations_grouping"
9556
+ });
9557
+ }
9558
+ const policyStartIndex = children.findIndex(looksLikePolicyFormStart);
9559
+ if (policyStartIndex >= 0) {
9560
+ const policyIds = [];
9561
+ for (let index = policyStartIndex; index < children.length; index += 1) {
9562
+ const child = children[index];
9563
+ if (index > policyStartIndex && endorsementTitle(sourceNodeText(child))) break;
9564
+ if (!looksLikePolicyFormContinuation(child)) break;
9565
+ policyIds.push(child.id);
9566
+ }
9567
+ nextTree = groupAdjacentChildren({
9568
+ sourceTree: nextTree,
9569
+ children,
9570
+ childIds: policyIds,
9571
+ kind: "form",
9572
+ title: "Policy Form",
9573
+ description: "Policy form pages grouped by source order",
9574
+ organizer: "semantic_policy_form_grouping"
9575
+ });
9576
+ }
9577
+ return applyEndorsementGrouping(normalizeDocumentSourceTreePaths(nextTree));
9428
9578
  }
9429
9579
  function rejectsOrganizerGroup(group, children) {
9580
+ if (/^endorsements?\b/i.test(group.title) && group.kind !== "page_group") return true;
9430
9581
  if (group.kind !== "endorsement") return false;
9431
9582
  if (/^endorsements?\s+\d+\s*[–-]\s*\d+\b/i.test(group.title)) return true;
9432
9583
  const childNumbers = new Set(
@@ -9434,6 +9585,120 @@ function rejectsOrganizerGroup(group, children) {
9434
9585
  );
9435
9586
  return childNumbers.size > 1;
9436
9587
  }
9588
+ function isEndorsementGroup(node) {
9589
+ return node.kind === "page_group" && /^endorsements?\b/i.test(node.title);
9590
+ }
9591
+ function endorsementGroupNodeId(documentId, parentId) {
9592
+ return [
9593
+ documentId.replace(/[^a-zA-Z0-9_.:-]/g, "_"),
9594
+ "source_node",
9595
+ "page_group",
9596
+ "endorsements",
9597
+ parentId?.replace(/[^a-zA-Z0-9_.:-]/g, "_").slice(0, 48) ?? "root"
9598
+ ].join(":");
9599
+ }
9600
+ function applyEndorsementGrouping(sourceTree) {
9601
+ const relabeledTree = sourceTree.map((node) => {
9602
+ if (node.kind === "document" || isEndorsementGroup(node) || node.kind === "endorsement") return node;
9603
+ const title = endorsementTitle([node.title, node.description, node.textExcerpt].filter(Boolean).join(" "));
9604
+ if (!title) return node;
9605
+ return {
9606
+ ...node,
9607
+ kind: "endorsement",
9608
+ title,
9609
+ description: cleanText(
9610
+ [title, "endorsement", node.pageStart ? `page ${node.pageStart}` : void 0, node.textExcerpt].filter(Boolean).join(" | "),
9611
+ title
9612
+ ),
9613
+ metadata: {
9614
+ ...node.metadata,
9615
+ organizerRepair: "normalize_endorsement_grouping"
9616
+ }
9617
+ };
9618
+ });
9619
+ const byParent = nodesByParent(relabeledTree);
9620
+ const groupsByParent = /* @__PURE__ */ new Map();
9621
+ const endorsementGroupIds = new Set(
9622
+ relabeledTree.filter(isEndorsementGroup).map((node) => node.id)
9623
+ );
9624
+ let nextTree = relabeledTree.map((node) => {
9625
+ if (!isEndorsementGroup(node)) return node;
9626
+ const normalized = {
9627
+ ...node,
9628
+ kind: "page_group",
9629
+ title: "Endorsements",
9630
+ description: cleanText(node.description, "Endorsement forms grouped by source order"),
9631
+ metadata: {
9632
+ ...node.metadata,
9633
+ sourceTreeVersion: "v3",
9634
+ organizer: node.metadata?.organizer ?? "endorsement_grouping"
9635
+ }
9636
+ };
9637
+ groupsByParent.set(node.parentId, normalized);
9638
+ endorsementGroupIds.add(node.id);
9639
+ return normalized;
9640
+ });
9641
+ nextTree = nextTree.map((node) => {
9642
+ if (!endorsementGroupIds.has(node.parentId ?? "")) return node;
9643
+ const title = endorsementTitle([node.title, node.description, node.textExcerpt].filter(Boolean).join(" "));
9644
+ if (!title) return node;
9645
+ return {
9646
+ ...node,
9647
+ kind: "endorsement",
9648
+ title,
9649
+ description: cleanText(
9650
+ [title, "endorsement", node.pageStart ? `page ${node.pageStart}` : void 0, node.textExcerpt].filter(Boolean).join(" | "),
9651
+ title
9652
+ ),
9653
+ metadata: {
9654
+ ...node.metadata,
9655
+ organizerRepair: "normalize_endorsement_grouping"
9656
+ }
9657
+ };
9658
+ });
9659
+ for (const [parentId, children] of byParent) {
9660
+ if (endorsementGroupIds.has(parentId ?? "")) continue;
9661
+ const endorsementChildren = children.filter((child) => child.kind === "endorsement" && !isEndorsementGroup(child));
9662
+ if (endorsementChildren.length < 2) continue;
9663
+ const documentId = endorsementChildren[0].documentId;
9664
+ const pageStarts = endorsementChildren.map((child) => child.pageStart).filter((page) => typeof page === "number");
9665
+ const pageEnds = endorsementChildren.map((child) => child.pageEnd ?? child.pageStart).filter((page) => typeof page === "number");
9666
+ const order = Math.min(...endorsementChildren.map((child) => child.order));
9667
+ const existingGroup = groupsByParent.get(parentId);
9668
+ const groupId = existingGroup?.id ?? endorsementGroupNodeId(documentId, parentId);
9669
+ const groupNode = existingGroup ?? {
9670
+ id: groupId,
9671
+ documentId,
9672
+ parentId,
9673
+ kind: "page_group",
9674
+ title: "Endorsements",
9675
+ description: "Endorsement forms grouped by source order",
9676
+ textExcerpt: void 0,
9677
+ sourceSpanIds: [],
9678
+ pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
9679
+ pageEnd: pageEnds.length ? Math.max(...pageEnds) : void 0,
9680
+ bbox: endorsementChildren.flatMap((child) => child.bbox ?? []).slice(0, 12),
9681
+ order,
9682
+ path: "",
9683
+ metadata: { sourceTreeVersion: "v3", organizer: "endorsement_grouping" }
9684
+ };
9685
+ const childSpanIds = [...new Set(endorsementChildren.flatMap((child) => child.sourceSpanIds))];
9686
+ const normalizedGroup = {
9687
+ ...groupNode,
9688
+ sourceSpanIds: groupNode.sourceSpanIds.length ? groupNode.sourceSpanIds : childSpanIds,
9689
+ pageStart: groupNode.pageStart ?? (pageStarts.length ? Math.min(...pageStarts) : void 0),
9690
+ pageEnd: groupNode.pageEnd ?? (pageEnds.length ? Math.max(...pageEnds) : void 0),
9691
+ order
9692
+ };
9693
+ groupsByParent.set(parentId, normalizedGroup);
9694
+ if (!existingGroup) nextTree.push(normalizedGroup);
9695
+ else nextTree = nextTree.map((node) => node.id === normalizedGroup.id ? normalizedGroup : node);
9696
+ nextTree = nextTree.map(
9697
+ (node) => endorsementChildren.some((child) => child.id === node.id) ? { ...node, parentId: groupId, order: node.order + 1e-3 } : node
9698
+ );
9699
+ }
9700
+ return normalizeDocumentSourceTreePaths(nextTree);
9701
+ }
9437
9702
  function compactNode(node, maxText = 700) {
9438
9703
  return {
9439
9704
  id: node.id,
@@ -9522,7 +9787,8 @@ Rules:
9522
9787
  - Use only node IDs from the provided list.
9523
9788
  - Do not invent text, page numbers, source spans, limits, or policy facts.
9524
9789
  - You may relabel existing nodes and group adjacent top-level/page nodes from this batch only when they are clearly one continuous form, one declarations set, one schedule, or one clause family.
9525
- - Do not group separately numbered endorsements into one parent. Never create rollup titles such as "Endorsements 1-3 (...)".
9790
+ - Group adjacent separately numbered endorsements under a single generic "Endorsements" page_group parent, with each individual endorsement preserved as its own child node.
9791
+ - Never create rollup titles such as "Endorsements 1-3 (...)" or merge multiple endorsements into one endorsement node.
9526
9792
  - Add concise, human-readable titles to generic text, table, row, and cell nodes when the text makes their role clear.
9527
9793
  - Keep organizer titles terse. Use the printed heading or a compact canonical title such as "Declarations", "Policy Form", "Definitions", or "Endorsement No. 3"; do not add parenthetical summaries.
9528
9794
  - Groups must list existing childNodeIds only.
@@ -9617,7 +9883,7 @@ function applyOrganization(sourceTree, organization) {
9617
9883
  ];
9618
9884
  byId.set(id, node);
9619
9885
  }
9620
- return normalizeDocumentSourceTreePaths(nextTree);
9886
+ return applyEndorsementGrouping(normalizeDocumentSourceTreePaths(nextTree));
9621
9887
  }
9622
9888
  function sourceTreeToOutline(sourceTree) {
9623
9889
  const byParent = /* @__PURE__ */ new Map();
@@ -9796,6 +10062,7 @@ async function runSourceTreeExtraction(params) {
9796
10062
  } catch (error) {
9797
10063
  warnings.push(`Source-tree organizer failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
9798
10064
  }
10065
+ sourceTree = applySemanticPageGrouping(sourceTree);
9799
10066
  const deterministicProfile = buildDeterministicOperationalProfile({
9800
10067
  sourceTree,
9801
10068
  sourceSpans