@claritylabs/cl-sdk 3.0.10 → 3.0.12
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 +265 -44
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +265 -44
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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 === "
|
|
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;
|
|
@@ -2662,14 +2746,24 @@ function normalizeDocumentSourceTreePaths(nodes) {
|
|
|
2662
2746
|
group.sort((left, right) => left.order - right.order || left.id.localeCompare(right.id));
|
|
2663
2747
|
}
|
|
2664
2748
|
const result = [];
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2749
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2750
|
+
const visit = (node, path, ancestors, parentId) => {
|
|
2751
|
+
if (visited.has(node.id) || ancestors.has(node.id)) return;
|
|
2752
|
+
visited.add(node.id);
|
|
2753
|
+
const next = { ...node, parentId, path };
|
|
2667
2754
|
result.push(next);
|
|
2668
2755
|
const children = byParent.get(node.id) ?? [];
|
|
2669
|
-
|
|
2756
|
+
const nextAncestors = new Set(ancestors);
|
|
2757
|
+
nextAncestors.add(node.id);
|
|
2758
|
+
children.forEach((child, index) => visit(child, `${path}.${index + 1}`, nextAncestors, node.id));
|
|
2670
2759
|
};
|
|
2671
2760
|
const roots = byParent.get(void 0) ?? [];
|
|
2672
|
-
roots.forEach((root, index) => visit(root, String(index + 1)));
|
|
2761
|
+
roots.forEach((root, index) => visit(root, String(index + 1), /* @__PURE__ */ new Set(), void 0));
|
|
2762
|
+
for (const node of nodes) {
|
|
2763
|
+
if (!visited.has(node.id)) {
|
|
2764
|
+
visit(node, String(result.length + 1), /* @__PURE__ */ new Set(), void 0);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2673
2767
|
return result;
|
|
2674
2768
|
}
|
|
2675
2769
|
function buildDocumentSourceTree(sourceSpans, documentId) {
|
|
@@ -2803,7 +2897,7 @@ function buildDocumentSourceTree(sourceSpans, documentId) {
|
|
|
2803
2897
|
}
|
|
2804
2898
|
addStandaloneSpanNode(span, pageParentId);
|
|
2805
2899
|
}
|
|
2806
|
-
return normalizeDocumentSourceTreePaths([...nodes.values()]);
|
|
2900
|
+
return normalizeDocumentSourceTreePaths(groupPageContentByTitles([...nodes.values()]));
|
|
2807
2901
|
}
|
|
2808
2902
|
|
|
2809
2903
|
// src/source/operational-profile.ts
|
|
@@ -8999,7 +9093,9 @@ var ORGANIZABLE_KINDS = [
|
|
|
8999
9093
|
"clause"
|
|
9000
9094
|
];
|
|
9001
9095
|
var ORGANIZATION_TOP_LEVEL_BATCH_SIZE = 80;
|
|
9002
|
-
var
|
|
9096
|
+
var ORGANIZER_MAX_SOURCE_SPANS = 400;
|
|
9097
|
+
var ORGANIZER_MAX_TOP_LEVEL_NODES = 18;
|
|
9098
|
+
var OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES = 80;
|
|
9003
9099
|
var SourceTreeOrganizationSchema = z41.object({
|
|
9004
9100
|
labels: z41.array(z41.object({
|
|
9005
9101
|
nodeId: z41.string(),
|
|
@@ -9116,6 +9212,22 @@ function endorsementDescription(title, node) {
|
|
|
9116
9212
|
title
|
|
9117
9213
|
);
|
|
9118
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
|
+
}
|
|
9119
9231
|
function semanticGroupNodeId(documentId, kind, title, childNodeIds) {
|
|
9120
9232
|
return [
|
|
9121
9233
|
documentId.replace(/[^a-zA-Z0-9_.:-]/g, "_"),
|
|
@@ -9126,7 +9238,12 @@ function semanticGroupNodeId(documentId, kind, title, childNodeIds) {
|
|
|
9126
9238
|
].join(":");
|
|
9127
9239
|
}
|
|
9128
9240
|
function looksLikeDeclarationsStart(node) {
|
|
9129
|
-
|
|
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, ""));
|
|
9130
9247
|
}
|
|
9131
9248
|
function looksLikeDeclarationsContinuation(node) {
|
|
9132
9249
|
const text = sourceNodeText(node);
|
|
@@ -9134,7 +9251,8 @@ function looksLikeDeclarationsContinuation(node) {
|
|
|
9134
9251
|
}
|
|
9135
9252
|
function looksLikePolicyFormStart(node) {
|
|
9136
9253
|
const text = sourceNodeText(node);
|
|
9137
|
-
|
|
9254
|
+
const excerpt = cleanText(node.textExcerpt, "");
|
|
9255
|
+
return /\bpolicy form\b/i.test(node.title) || /^policy\s+form\b/i.test(excerpt) || /\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);
|
|
9138
9256
|
}
|
|
9139
9257
|
function looksLikePolicyFormContinuation(node) {
|
|
9140
9258
|
const text = sourceNodeText(node);
|
|
@@ -9160,7 +9278,7 @@ function groupAdjacentChildren(params) {
|
|
|
9160
9278
|
parentId,
|
|
9161
9279
|
kind: params.kind,
|
|
9162
9280
|
title: params.title,
|
|
9163
|
-
description: params.description,
|
|
9281
|
+
description: descriptionWithPages(params.description, children),
|
|
9164
9282
|
textExcerpt: children.map((child) => child.textExcerpt ?? child.description).filter(Boolean).join("\n\n").slice(0, 1600),
|
|
9165
9283
|
sourceSpanIds,
|
|
9166
9284
|
pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
|
|
@@ -9213,6 +9331,22 @@ function applySemanticPageGrouping(sourceTree) {
|
|
|
9213
9331
|
const children = (nodesByParent(relabeled).get(rootId) ?? []).filter((node) => node.kind !== "document").sort((left, right) => left.order - right.order);
|
|
9214
9332
|
let nextTree = relabeled;
|
|
9215
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
|
+
}
|
|
9216
9350
|
if (declarationsStartIndex >= 0) {
|
|
9217
9351
|
const declarationIds = [];
|
|
9218
9352
|
for (let index = declarationsStartIndex; index < children.length; index += 1) {
|
|
@@ -9381,17 +9515,19 @@ function normalizeContainerEvidenceFromChildren(sourceTree) {
|
|
|
9381
9515
|
return sourceTree.map((node) => byId.get(node.id) ?? node);
|
|
9382
9516
|
}
|
|
9383
9517
|
function normalizeSemanticHierarchy(sourceTree) {
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
normalizePolicyFormStructure(
|
|
9388
|
-
normalizeDocumentSourceTreePaths(sourceTree)
|
|
9389
|
-
)
|
|
9390
|
-
)
|
|
9518
|
+
const normalized = normalizeDocumentSourceTreePaths(
|
|
9519
|
+
normalizePolicyFormStructure(
|
|
9520
|
+
normalizeDocumentSourceTreePaths(sourceTree)
|
|
9391
9521
|
)
|
|
9392
9522
|
);
|
|
9523
|
+
const nested = normalizeDocumentSourceTreePaths(nestEndorsementContinuationPages(normalized));
|
|
9524
|
+
const withEvidence = normalizeContainerEvidenceFromChildren(nested);
|
|
9525
|
+
return normalizeDocumentSourceTreePaths(
|
|
9526
|
+
normalizeContainerEvidenceFromChildren(withEvidence)
|
|
9527
|
+
);
|
|
9393
9528
|
}
|
|
9394
9529
|
function applyEndorsementGrouping(sourceTree) {
|
|
9530
|
+
const rootId = sourceTreeRootId(sourceTree);
|
|
9395
9531
|
const relabeledTree = sourceTree.map((node) => {
|
|
9396
9532
|
if (node.kind === "document" || isEndorsementGroup(node)) return node;
|
|
9397
9533
|
const title = endorsementStartTitle(node);
|
|
@@ -9429,7 +9565,7 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
9429
9565
|
...node,
|
|
9430
9566
|
kind: "page_group",
|
|
9431
9567
|
title: "Endorsements",
|
|
9432
|
-
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]),
|
|
9433
9569
|
metadata: {
|
|
9434
9570
|
...node.metadata,
|
|
9435
9571
|
sourceTreeVersion: "v3",
|
|
@@ -9456,9 +9592,10 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
9456
9592
|
};
|
|
9457
9593
|
});
|
|
9458
9594
|
for (const [parentId, children] of byParent) {
|
|
9595
|
+
if (parentId !== rootId) continue;
|
|
9459
9596
|
if (endorsementGroupIds.has(parentId ?? "")) continue;
|
|
9460
9597
|
const endorsementChildren = children.filter((child) => child.kind === "endorsement" && !isEndorsementGroup(child));
|
|
9461
|
-
if (endorsementChildren.length <
|
|
9598
|
+
if (endorsementChildren.length < 1) continue;
|
|
9462
9599
|
const endorsementGroupChildren = [];
|
|
9463
9600
|
let hasSeenEndorsementStart = false;
|
|
9464
9601
|
for (const child of children) {
|
|
@@ -9471,6 +9608,7 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
9471
9608
|
endorsementGroupChildren.push(child);
|
|
9472
9609
|
}
|
|
9473
9610
|
}
|
|
9611
|
+
if (endorsementGroupChildren.length < 1) continue;
|
|
9474
9612
|
const documentId = endorsementChildren[0].documentId;
|
|
9475
9613
|
const pageStarts = endorsementGroupChildren.map((child) => child.pageStart).filter((page) => typeof page === "number");
|
|
9476
9614
|
const pageEnds = endorsementGroupChildren.map((child) => child.pageEnd ?? child.pageStart).filter((page) => typeof page === "number");
|
|
@@ -9483,7 +9621,7 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
9483
9621
|
parentId,
|
|
9484
9622
|
kind: "page_group",
|
|
9485
9623
|
title: "Endorsements",
|
|
9486
|
-
description: "Endorsement forms grouped by source order",
|
|
9624
|
+
description: descriptionWithPages("Endorsement forms grouped by source order", endorsementGroupChildren),
|
|
9487
9625
|
textExcerpt: void 0,
|
|
9488
9626
|
sourceSpanIds: [],
|
|
9489
9627
|
pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
|
|
@@ -9494,11 +9632,13 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
9494
9632
|
metadata: { sourceTreeVersion: "v3", organizer: "endorsement_grouping" }
|
|
9495
9633
|
};
|
|
9496
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;
|
|
9497
9637
|
const normalizedGroup = {
|
|
9498
9638
|
...groupNode,
|
|
9499
9639
|
sourceSpanIds: groupNode.sourceSpanIds.length ? groupNode.sourceSpanIds : childSpanIds,
|
|
9500
|
-
pageStart: groupNode.pageStart
|
|
9501
|
-
pageEnd: groupNode.pageEnd
|
|
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),
|
|
9502
9642
|
order
|
|
9503
9643
|
};
|
|
9504
9644
|
groupsByParent.set(parentId, normalizedGroup);
|
|
@@ -9538,6 +9678,24 @@ function nodesByParent(sourceTree) {
|
|
|
9538
9678
|
function sourceTreeRootId(sourceTree) {
|
|
9539
9679
|
return sourceTree.find((node) => node.kind === "document")?.id;
|
|
9540
9680
|
}
|
|
9681
|
+
function rootChildren(sourceTree) {
|
|
9682
|
+
const byParent = nodesByParent(sourceTree);
|
|
9683
|
+
const rootId = sourceTreeRootId(sourceTree);
|
|
9684
|
+
return (byParent.get(rootId) ?? []).filter((node) => node.kind !== "document");
|
|
9685
|
+
}
|
|
9686
|
+
function hasDeterministicSemanticOutline(sourceTree) {
|
|
9687
|
+
const children = rootChildren(sourceTree);
|
|
9688
|
+
const semanticCount = children.filter(
|
|
9689
|
+
(node) => node.kind === "page_group" || node.kind === "form" || node.kind === "endorsement" || node.kind === "section" || node.kind === "schedule"
|
|
9690
|
+
).length;
|
|
9691
|
+
return semanticCount >= 2 || children.some((node) => node.title === "Declarations") || children.some((node) => node.title === "Policy Form") || children.some((node) => node.title === "Endorsements");
|
|
9692
|
+
}
|
|
9693
|
+
function shouldRunSourceTreeOrganizer(sourceTree, sourceSpans) {
|
|
9694
|
+
const topLevelCount = rootChildren(sourceTree).length;
|
|
9695
|
+
if (sourceSpans.length > ORGANIZER_MAX_SOURCE_SPANS) return false;
|
|
9696
|
+
if (topLevelCount > ORGANIZER_MAX_TOP_LEVEL_NODES) return false;
|
|
9697
|
+
return !hasDeterministicSemanticOutline(sourceTree);
|
|
9698
|
+
}
|
|
9541
9699
|
function organizationBatches(sourceTree) {
|
|
9542
9700
|
const byParent = nodesByParent(sourceTree);
|
|
9543
9701
|
const rootId = sourceTreeRootId(sourceTree);
|
|
@@ -9556,10 +9714,6 @@ function organizationBatches(sourceTree) {
|
|
|
9556
9714
|
const candidates = /* @__PURE__ */ new Map();
|
|
9557
9715
|
for (const node of topLevelBatch) {
|
|
9558
9716
|
candidates.set(node.id, node);
|
|
9559
|
-
const childContext = (byParent.get(node.id) ?? []).filter((child) => child.kind !== "text" && child.kind !== "table_cell").slice(0, ORGANIZATION_CHILD_CONTEXT_LIMIT);
|
|
9560
|
-
for (const child of childContext) {
|
|
9561
|
-
candidates.set(child.id, child);
|
|
9562
|
-
}
|
|
9563
9717
|
}
|
|
9564
9718
|
batches.push({
|
|
9565
9719
|
label: `top-level nodes ${index + 1}-${index + topLevelBatch.length} of ${topLevelNodes.length}`,
|
|
@@ -9610,6 +9764,41 @@ Rules:
|
|
|
9610
9764
|
Source nodes:
|
|
9611
9765
|
${JSON.stringify(nodes, null, 2)}
|
|
9612
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
|
+
return genericPages.length > 0 || topLevel.length > 6 || !hasDeclarations || !hasPolicyForm || !hasEndorsements;
|
|
9777
|
+
}
|
|
9778
|
+
function buildOutlineCleanupPrompt(sourceTree) {
|
|
9779
|
+
const topLevel = rootChildren(sourceTree).slice(0, OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES);
|
|
9780
|
+
const nodes = topLevel.map((node) => compactNode(node, 900));
|
|
9781
|
+
return `You clean a top-level source outline for an insurance policy.
|
|
9782
|
+
|
|
9783
|
+
Expected product-facing order:
|
|
9784
|
+
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.
|
|
9785
|
+
2. Declarations: declarations page(s), schedules, named insured/policy period/premium rows, coverage limit schedules, forms-and-endorsements schedules.
|
|
9786
|
+
3. Policy Form: the main policy wording, insuring agreements, definitions, exclusions, conditions, claim provisions, and general policy terms.
|
|
9787
|
+
4. Endorsements: one generic "Endorsements" page_group containing each separately numbered endorsement as its own child.
|
|
9788
|
+
|
|
9789
|
+
Rules:
|
|
9790
|
+
- Use only node IDs from this top-level list: ${JSON.stringify(topLevel.map((node) => node.id))}
|
|
9791
|
+
- Group only adjacent top-level nodes.
|
|
9792
|
+
- Do not invent text, pages, source spans, limits, or policy facts.
|
|
9793
|
+
- Do not merge individually numbered endorsements into one endorsement node; use the generic "Endorsements" parent for the series.
|
|
9794
|
+
- Use canonical terse titles: "Notices and Jacket", "Declarations", "Policy Form", "Endorsements", or the printed endorsement number.
|
|
9795
|
+
- Keep page_group descriptions short and include the page range when pages are known, for example "Declarations pages 6-8".
|
|
9796
|
+
- 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.
|
|
9797
|
+
- If the existing deterministic outline is already correct, return empty labels and groups.
|
|
9798
|
+
|
|
9799
|
+
Top-level source nodes:
|
|
9800
|
+
${JSON.stringify(nodes, null, 2)}
|
|
9801
|
+
|
|
9613
9802
|
Return JSON with labels and groups only.`;
|
|
9614
9803
|
}
|
|
9615
9804
|
function buildOperationalProfilePrompt(sourceTree, fallback) {
|
|
@@ -9664,7 +9853,7 @@ function applyOrganization(sourceTree, organization) {
|
|
|
9664
9853
|
if (!children.every((child) => child.parentId === parentId)) continue;
|
|
9665
9854
|
const documentId = children[0].documentId;
|
|
9666
9855
|
const title = simplifyOrganizerTitle(group.title, group.title, group.kind);
|
|
9667
|
-
const description = cleanText(group.description, title);
|
|
9856
|
+
const description = descriptionWithPages(cleanText(group.description, title), children);
|
|
9668
9857
|
const id = groupNodeId(documentId, { ...group, title });
|
|
9669
9858
|
if (byId.has(id)) continue;
|
|
9670
9859
|
const sourceSpanIds = [...new Set(children.flatMap((child) => child.sourceSpanIds))];
|
|
@@ -9819,7 +10008,7 @@ function materializeDocument(params) {
|
|
|
9819
10008
|
}
|
|
9820
10009
|
async function runSourceTreeExtraction(params) {
|
|
9821
10010
|
const sourceSpans = normalizeSourceSpans(params.sourceSpans);
|
|
9822
|
-
let sourceTree = buildDocumentSourceTree(sourceSpans, params.id);
|
|
10011
|
+
let sourceTree = applySemanticPageGrouping(buildDocumentSourceTree(sourceSpans, params.id));
|
|
9823
10012
|
const warnings = [];
|
|
9824
10013
|
let modelCalls = 0;
|
|
9825
10014
|
let callsWithUsage = 0;
|
|
@@ -9841,21 +10030,55 @@ async function runSourceTreeExtraction(params) {
|
|
|
9841
10030
|
}
|
|
9842
10031
|
params.trackUsage(usage, report);
|
|
9843
10032
|
};
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
const
|
|
10033
|
+
if (shouldRunSourceTreeOrganizer(sourceTree, sourceSpans)) {
|
|
10034
|
+
try {
|
|
10035
|
+
const organizations = [];
|
|
10036
|
+
const batches = organizationBatches(sourceTree);
|
|
10037
|
+
for (const [batchIndex, batch] of batches.entries()) {
|
|
10038
|
+
const budget = params.resolveBudget("extraction_source_tree", 4096);
|
|
10039
|
+
const startedAt = Date.now();
|
|
10040
|
+
const response = await safeGenerateObject(
|
|
10041
|
+
params.generateObject,
|
|
10042
|
+
{
|
|
10043
|
+
prompt: buildOrganizationPrompt(batch),
|
|
10044
|
+
schema: SourceTreeOrganizationSchema,
|
|
10045
|
+
maxTokens: budget.maxTokens,
|
|
10046
|
+
taskKind: "extraction_source_tree",
|
|
10047
|
+
budgetDiagnostics: budget
|
|
10048
|
+
},
|
|
10049
|
+
{
|
|
10050
|
+
fallback: { labels: [], groups: [] },
|
|
10051
|
+
log: params.log
|
|
10052
|
+
}
|
|
10053
|
+
);
|
|
10054
|
+
localTrack(response.usage, {
|
|
10055
|
+
taskKind: "extraction_source_tree",
|
|
10056
|
+
label: batches.length > 1 ? `source_tree_organizer_${batchIndex + 1}` : "source_tree_organizer",
|
|
10057
|
+
maxTokens: budget.maxTokens,
|
|
10058
|
+
durationMs: Date.now() - startedAt
|
|
10059
|
+
});
|
|
10060
|
+
organizations.push(response.object);
|
|
10061
|
+
}
|
|
10062
|
+
sourceTree = applyOrganization(sourceTree, mergeOrganizationResults(organizations));
|
|
10063
|
+
} catch (error) {
|
|
10064
|
+
warnings.push(`Source-tree organizer failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
|
|
10065
|
+
}
|
|
10066
|
+
} else {
|
|
10067
|
+
await params.log?.("Deterministic source tree ready; skipped model organizer");
|
|
10068
|
+
}
|
|
10069
|
+
if (shouldRunOutlineCleanup(sourceTree)) {
|
|
10070
|
+
try {
|
|
10071
|
+
const budget = params.resolveBudget("extraction_source_tree", 1600);
|
|
10072
|
+
const maxTokens = Math.min(budget.maxTokens, 1600);
|
|
9849
10073
|
const startedAt = Date.now();
|
|
9850
10074
|
const response = await safeGenerateObject(
|
|
9851
10075
|
params.generateObject,
|
|
9852
10076
|
{
|
|
9853
|
-
prompt:
|
|
10077
|
+
prompt: buildOutlineCleanupPrompt(sourceTree),
|
|
9854
10078
|
schema: SourceTreeOrganizationSchema,
|
|
9855
|
-
maxTokens
|
|
10079
|
+
maxTokens,
|
|
9856
10080
|
taskKind: "extraction_source_tree",
|
|
9857
|
-
budgetDiagnostics: budget,
|
|
9858
|
-
providerOptions: { ...params.providerOptions, sourceSpans }
|
|
10081
|
+
budgetDiagnostics: { ...budget, maxTokens }
|
|
9859
10082
|
},
|
|
9860
10083
|
{
|
|
9861
10084
|
fallback: { labels: [], groups: [] },
|
|
@@ -9864,17 +10087,15 @@ async function runSourceTreeExtraction(params) {
|
|
|
9864
10087
|
);
|
|
9865
10088
|
localTrack(response.usage, {
|
|
9866
10089
|
taskKind: "extraction_source_tree",
|
|
9867
|
-
label:
|
|
9868
|
-
maxTokens
|
|
10090
|
+
label: "source_tree_outline_cleanup",
|
|
10091
|
+
maxTokens,
|
|
9869
10092
|
durationMs: Date.now() - startedAt
|
|
9870
10093
|
});
|
|
9871
|
-
|
|
10094
|
+
sourceTree = applyOrganization(sourceTree, response.object);
|
|
10095
|
+
} catch (error) {
|
|
10096
|
+
warnings.push(`Source-tree outline cleanup failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
|
|
9872
10097
|
}
|
|
9873
|
-
sourceTree = applyOrganization(sourceTree, mergeOrganizationResults(organizations));
|
|
9874
|
-
} catch (error) {
|
|
9875
|
-
warnings.push(`Source-tree organizer failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
|
|
9876
10098
|
}
|
|
9877
|
-
sourceTree = applySemanticPageGrouping(sourceTree);
|
|
9878
10099
|
const deterministicProfile = buildDeterministicOperationalProfile({
|
|
9879
10100
|
sourceTree,
|
|
9880
10101
|
sourceSpans
|