@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.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 === "
|
|
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;
|
|
@@ -3017,14 +3101,24 @@ function normalizeDocumentSourceTreePaths(nodes) {
|
|
|
3017
3101
|
group.sort((left, right) => left.order - right.order || left.id.localeCompare(right.id));
|
|
3018
3102
|
}
|
|
3019
3103
|
const result = [];
|
|
3020
|
-
const
|
|
3021
|
-
|
|
3104
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3105
|
+
const visit = (node, path, ancestors, parentId) => {
|
|
3106
|
+
if (visited.has(node.id) || ancestors.has(node.id)) return;
|
|
3107
|
+
visited.add(node.id);
|
|
3108
|
+
const next = { ...node, parentId, path };
|
|
3022
3109
|
result.push(next);
|
|
3023
3110
|
const children = byParent.get(node.id) ?? [];
|
|
3024
|
-
|
|
3111
|
+
const nextAncestors = new Set(ancestors);
|
|
3112
|
+
nextAncestors.add(node.id);
|
|
3113
|
+
children.forEach((child, index) => visit(child, `${path}.${index + 1}`, nextAncestors, node.id));
|
|
3025
3114
|
};
|
|
3026
3115
|
const roots = byParent.get(void 0) ?? [];
|
|
3027
|
-
roots.forEach((root, index) => visit(root, String(index + 1)));
|
|
3116
|
+
roots.forEach((root, index) => visit(root, String(index + 1), /* @__PURE__ */ new Set(), void 0));
|
|
3117
|
+
for (const node of nodes) {
|
|
3118
|
+
if (!visited.has(node.id)) {
|
|
3119
|
+
visit(node, String(result.length + 1), /* @__PURE__ */ new Set(), void 0);
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3028
3122
|
return result;
|
|
3029
3123
|
}
|
|
3030
3124
|
function buildDocumentSourceTree(sourceSpans, documentId) {
|
|
@@ -3158,7 +3252,7 @@ function buildDocumentSourceTree(sourceSpans, documentId) {
|
|
|
3158
3252
|
}
|
|
3159
3253
|
addStandaloneSpanNode(span, pageParentId);
|
|
3160
3254
|
}
|
|
3161
|
-
return normalizeDocumentSourceTreePaths([...nodes.values()]);
|
|
3255
|
+
return normalizeDocumentSourceTreePaths(groupPageContentByTitles([...nodes.values()]));
|
|
3162
3256
|
}
|
|
3163
3257
|
|
|
3164
3258
|
// src/source/operational-profile.ts
|
|
@@ -9346,7 +9440,9 @@ var ORGANIZABLE_KINDS = [
|
|
|
9346
9440
|
"clause"
|
|
9347
9441
|
];
|
|
9348
9442
|
var ORGANIZATION_TOP_LEVEL_BATCH_SIZE = 80;
|
|
9349
|
-
var
|
|
9443
|
+
var ORGANIZER_MAX_SOURCE_SPANS = 400;
|
|
9444
|
+
var ORGANIZER_MAX_TOP_LEVEL_NODES = 18;
|
|
9445
|
+
var OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES = 80;
|
|
9350
9446
|
var SourceTreeOrganizationSchema = import_zod41.z.object({
|
|
9351
9447
|
labels: import_zod41.z.array(import_zod41.z.object({
|
|
9352
9448
|
nodeId: import_zod41.z.string(),
|
|
@@ -9463,6 +9559,22 @@ function endorsementDescription(title, node) {
|
|
|
9463
9559
|
title
|
|
9464
9560
|
);
|
|
9465
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
|
+
}
|
|
9466
9578
|
function semanticGroupNodeId(documentId, kind, title, childNodeIds) {
|
|
9467
9579
|
return [
|
|
9468
9580
|
documentId.replace(/[^a-zA-Z0-9_.:-]/g, "_"),
|
|
@@ -9473,7 +9585,12 @@ function semanticGroupNodeId(documentId, kind, title, childNodeIds) {
|
|
|
9473
9585
|
].join(":");
|
|
9474
9586
|
}
|
|
9475
9587
|
function looksLikeDeclarationsStart(node) {
|
|
9476
|
-
|
|
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, ""));
|
|
9477
9594
|
}
|
|
9478
9595
|
function looksLikeDeclarationsContinuation(node) {
|
|
9479
9596
|
const text = sourceNodeText(node);
|
|
@@ -9481,7 +9598,8 @@ function looksLikeDeclarationsContinuation(node) {
|
|
|
9481
9598
|
}
|
|
9482
9599
|
function looksLikePolicyFormStart(node) {
|
|
9483
9600
|
const text = sourceNodeText(node);
|
|
9484
|
-
|
|
9601
|
+
const excerpt = cleanText(node.textExcerpt, "");
|
|
9602
|
+
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);
|
|
9485
9603
|
}
|
|
9486
9604
|
function looksLikePolicyFormContinuation(node) {
|
|
9487
9605
|
const text = sourceNodeText(node);
|
|
@@ -9507,7 +9625,7 @@ function groupAdjacentChildren(params) {
|
|
|
9507
9625
|
parentId,
|
|
9508
9626
|
kind: params.kind,
|
|
9509
9627
|
title: params.title,
|
|
9510
|
-
description: params.description,
|
|
9628
|
+
description: descriptionWithPages(params.description, children),
|
|
9511
9629
|
textExcerpt: children.map((child) => child.textExcerpt ?? child.description).filter(Boolean).join("\n\n").slice(0, 1600),
|
|
9512
9630
|
sourceSpanIds,
|
|
9513
9631
|
pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
|
|
@@ -9560,6 +9678,22 @@ function applySemanticPageGrouping(sourceTree) {
|
|
|
9560
9678
|
const children = (nodesByParent(relabeled).get(rootId) ?? []).filter((node) => node.kind !== "document").sort((left, right) => left.order - right.order);
|
|
9561
9679
|
let nextTree = relabeled;
|
|
9562
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
|
+
}
|
|
9563
9697
|
if (declarationsStartIndex >= 0) {
|
|
9564
9698
|
const declarationIds = [];
|
|
9565
9699
|
for (let index = declarationsStartIndex; index < children.length; index += 1) {
|
|
@@ -9728,17 +9862,19 @@ function normalizeContainerEvidenceFromChildren(sourceTree) {
|
|
|
9728
9862
|
return sourceTree.map((node) => byId.get(node.id) ?? node);
|
|
9729
9863
|
}
|
|
9730
9864
|
function normalizeSemanticHierarchy(sourceTree) {
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
normalizePolicyFormStructure(
|
|
9735
|
-
normalizeDocumentSourceTreePaths(sourceTree)
|
|
9736
|
-
)
|
|
9737
|
-
)
|
|
9865
|
+
const normalized = normalizeDocumentSourceTreePaths(
|
|
9866
|
+
normalizePolicyFormStructure(
|
|
9867
|
+
normalizeDocumentSourceTreePaths(sourceTree)
|
|
9738
9868
|
)
|
|
9739
9869
|
);
|
|
9870
|
+
const nested = normalizeDocumentSourceTreePaths(nestEndorsementContinuationPages(normalized));
|
|
9871
|
+
const withEvidence = normalizeContainerEvidenceFromChildren(nested);
|
|
9872
|
+
return normalizeDocumentSourceTreePaths(
|
|
9873
|
+
normalizeContainerEvidenceFromChildren(withEvidence)
|
|
9874
|
+
);
|
|
9740
9875
|
}
|
|
9741
9876
|
function applyEndorsementGrouping(sourceTree) {
|
|
9877
|
+
const rootId = sourceTreeRootId(sourceTree);
|
|
9742
9878
|
const relabeledTree = sourceTree.map((node) => {
|
|
9743
9879
|
if (node.kind === "document" || isEndorsementGroup(node)) return node;
|
|
9744
9880
|
const title = endorsementStartTitle(node);
|
|
@@ -9776,7 +9912,7 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
9776
9912
|
...node,
|
|
9777
9913
|
kind: "page_group",
|
|
9778
9914
|
title: "Endorsements",
|
|
9779
|
-
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]),
|
|
9780
9916
|
metadata: {
|
|
9781
9917
|
...node.metadata,
|
|
9782
9918
|
sourceTreeVersion: "v3",
|
|
@@ -9803,9 +9939,10 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
9803
9939
|
};
|
|
9804
9940
|
});
|
|
9805
9941
|
for (const [parentId, children] of byParent) {
|
|
9942
|
+
if (parentId !== rootId) continue;
|
|
9806
9943
|
if (endorsementGroupIds.has(parentId ?? "")) continue;
|
|
9807
9944
|
const endorsementChildren = children.filter((child) => child.kind === "endorsement" && !isEndorsementGroup(child));
|
|
9808
|
-
if (endorsementChildren.length <
|
|
9945
|
+
if (endorsementChildren.length < 1) continue;
|
|
9809
9946
|
const endorsementGroupChildren = [];
|
|
9810
9947
|
let hasSeenEndorsementStart = false;
|
|
9811
9948
|
for (const child of children) {
|
|
@@ -9818,6 +9955,7 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
9818
9955
|
endorsementGroupChildren.push(child);
|
|
9819
9956
|
}
|
|
9820
9957
|
}
|
|
9958
|
+
if (endorsementGroupChildren.length < 1) continue;
|
|
9821
9959
|
const documentId = endorsementChildren[0].documentId;
|
|
9822
9960
|
const pageStarts = endorsementGroupChildren.map((child) => child.pageStart).filter((page) => typeof page === "number");
|
|
9823
9961
|
const pageEnds = endorsementGroupChildren.map((child) => child.pageEnd ?? child.pageStart).filter((page) => typeof page === "number");
|
|
@@ -9830,7 +9968,7 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
9830
9968
|
parentId,
|
|
9831
9969
|
kind: "page_group",
|
|
9832
9970
|
title: "Endorsements",
|
|
9833
|
-
description: "Endorsement forms grouped by source order",
|
|
9971
|
+
description: descriptionWithPages("Endorsement forms grouped by source order", endorsementGroupChildren),
|
|
9834
9972
|
textExcerpt: void 0,
|
|
9835
9973
|
sourceSpanIds: [],
|
|
9836
9974
|
pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
|
|
@@ -9841,11 +9979,13 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
9841
9979
|
metadata: { sourceTreeVersion: "v3", organizer: "endorsement_grouping" }
|
|
9842
9980
|
};
|
|
9843
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;
|
|
9844
9984
|
const normalizedGroup = {
|
|
9845
9985
|
...groupNode,
|
|
9846
9986
|
sourceSpanIds: groupNode.sourceSpanIds.length ? groupNode.sourceSpanIds : childSpanIds,
|
|
9847
|
-
pageStart: groupNode.pageStart
|
|
9848
|
-
pageEnd: groupNode.pageEnd
|
|
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),
|
|
9849
9989
|
order
|
|
9850
9990
|
};
|
|
9851
9991
|
groupsByParent.set(parentId, normalizedGroup);
|
|
@@ -9885,6 +10025,24 @@ function nodesByParent(sourceTree) {
|
|
|
9885
10025
|
function sourceTreeRootId(sourceTree) {
|
|
9886
10026
|
return sourceTree.find((node) => node.kind === "document")?.id;
|
|
9887
10027
|
}
|
|
10028
|
+
function rootChildren(sourceTree) {
|
|
10029
|
+
const byParent = nodesByParent(sourceTree);
|
|
10030
|
+
const rootId = sourceTreeRootId(sourceTree);
|
|
10031
|
+
return (byParent.get(rootId) ?? []).filter((node) => node.kind !== "document");
|
|
10032
|
+
}
|
|
10033
|
+
function hasDeterministicSemanticOutline(sourceTree) {
|
|
10034
|
+
const children = rootChildren(sourceTree);
|
|
10035
|
+
const semanticCount = children.filter(
|
|
10036
|
+
(node) => node.kind === "page_group" || node.kind === "form" || node.kind === "endorsement" || node.kind === "section" || node.kind === "schedule"
|
|
10037
|
+
).length;
|
|
10038
|
+
return semanticCount >= 2 || children.some((node) => node.title === "Declarations") || children.some((node) => node.title === "Policy Form") || children.some((node) => node.title === "Endorsements");
|
|
10039
|
+
}
|
|
10040
|
+
function shouldRunSourceTreeOrganizer(sourceTree, sourceSpans) {
|
|
10041
|
+
const topLevelCount = rootChildren(sourceTree).length;
|
|
10042
|
+
if (sourceSpans.length > ORGANIZER_MAX_SOURCE_SPANS) return false;
|
|
10043
|
+
if (topLevelCount > ORGANIZER_MAX_TOP_LEVEL_NODES) return false;
|
|
10044
|
+
return !hasDeterministicSemanticOutline(sourceTree);
|
|
10045
|
+
}
|
|
9888
10046
|
function organizationBatches(sourceTree) {
|
|
9889
10047
|
const byParent = nodesByParent(sourceTree);
|
|
9890
10048
|
const rootId = sourceTreeRootId(sourceTree);
|
|
@@ -9903,10 +10061,6 @@ function organizationBatches(sourceTree) {
|
|
|
9903
10061
|
const candidates = /* @__PURE__ */ new Map();
|
|
9904
10062
|
for (const node of topLevelBatch) {
|
|
9905
10063
|
candidates.set(node.id, node);
|
|
9906
|
-
const childContext = (byParent.get(node.id) ?? []).filter((child) => child.kind !== "text" && child.kind !== "table_cell").slice(0, ORGANIZATION_CHILD_CONTEXT_LIMIT);
|
|
9907
|
-
for (const child of childContext) {
|
|
9908
|
-
candidates.set(child.id, child);
|
|
9909
|
-
}
|
|
9910
10064
|
}
|
|
9911
10065
|
batches.push({
|
|
9912
10066
|
label: `top-level nodes ${index + 1}-${index + topLevelBatch.length} of ${topLevelNodes.length}`,
|
|
@@ -9957,6 +10111,41 @@ Rules:
|
|
|
9957
10111
|
Source nodes:
|
|
9958
10112
|
${JSON.stringify(nodes, null, 2)}
|
|
9959
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
|
+
return genericPages.length > 0 || topLevel.length > 6 || !hasDeclarations || !hasPolicyForm || !hasEndorsements;
|
|
10124
|
+
}
|
|
10125
|
+
function buildOutlineCleanupPrompt(sourceTree) {
|
|
10126
|
+
const topLevel = rootChildren(sourceTree).slice(0, OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES);
|
|
10127
|
+
const nodes = topLevel.map((node) => compactNode(node, 900));
|
|
10128
|
+
return `You clean a top-level source outline for an insurance policy.
|
|
10129
|
+
|
|
10130
|
+
Expected product-facing order:
|
|
10131
|
+
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.
|
|
10132
|
+
2. Declarations: declarations page(s), schedules, named insured/policy period/premium rows, coverage limit schedules, forms-and-endorsements schedules.
|
|
10133
|
+
3. Policy Form: the main policy wording, insuring agreements, definitions, exclusions, conditions, claim provisions, and general policy terms.
|
|
10134
|
+
4. Endorsements: one generic "Endorsements" page_group containing each separately numbered endorsement as its own child.
|
|
10135
|
+
|
|
10136
|
+
Rules:
|
|
10137
|
+
- Use only node IDs from this top-level list: ${JSON.stringify(topLevel.map((node) => node.id))}
|
|
10138
|
+
- Group only adjacent top-level nodes.
|
|
10139
|
+
- Do not invent text, pages, source spans, limits, or policy facts.
|
|
10140
|
+
- Do not merge individually numbered endorsements into one endorsement node; use the generic "Endorsements" parent for the series.
|
|
10141
|
+
- Use canonical terse titles: "Notices and Jacket", "Declarations", "Policy Form", "Endorsements", or the printed endorsement number.
|
|
10142
|
+
- Keep page_group descriptions short and include the page range when pages are known, for example "Declarations pages 6-8".
|
|
10143
|
+
- 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.
|
|
10144
|
+
- If the existing deterministic outline is already correct, return empty labels and groups.
|
|
10145
|
+
|
|
10146
|
+
Top-level source nodes:
|
|
10147
|
+
${JSON.stringify(nodes, null, 2)}
|
|
10148
|
+
|
|
9960
10149
|
Return JSON with labels and groups only.`;
|
|
9961
10150
|
}
|
|
9962
10151
|
function buildOperationalProfilePrompt(sourceTree, fallback) {
|
|
@@ -10011,7 +10200,7 @@ function applyOrganization(sourceTree, organization) {
|
|
|
10011
10200
|
if (!children.every((child) => child.parentId === parentId)) continue;
|
|
10012
10201
|
const documentId = children[0].documentId;
|
|
10013
10202
|
const title = simplifyOrganizerTitle(group.title, group.title, group.kind);
|
|
10014
|
-
const description = cleanText(group.description, title);
|
|
10203
|
+
const description = descriptionWithPages(cleanText(group.description, title), children);
|
|
10015
10204
|
const id = groupNodeId(documentId, { ...group, title });
|
|
10016
10205
|
if (byId.has(id)) continue;
|
|
10017
10206
|
const sourceSpanIds = [...new Set(children.flatMap((child) => child.sourceSpanIds))];
|
|
@@ -10166,7 +10355,7 @@ function materializeDocument(params) {
|
|
|
10166
10355
|
}
|
|
10167
10356
|
async function runSourceTreeExtraction(params) {
|
|
10168
10357
|
const sourceSpans = normalizeSourceSpans(params.sourceSpans);
|
|
10169
|
-
let sourceTree = buildDocumentSourceTree(sourceSpans, params.id);
|
|
10358
|
+
let sourceTree = applySemanticPageGrouping(buildDocumentSourceTree(sourceSpans, params.id));
|
|
10170
10359
|
const warnings = [];
|
|
10171
10360
|
let modelCalls = 0;
|
|
10172
10361
|
let callsWithUsage = 0;
|
|
@@ -10188,21 +10377,55 @@ async function runSourceTreeExtraction(params) {
|
|
|
10188
10377
|
}
|
|
10189
10378
|
params.trackUsage(usage, report);
|
|
10190
10379
|
};
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
const
|
|
10380
|
+
if (shouldRunSourceTreeOrganizer(sourceTree, sourceSpans)) {
|
|
10381
|
+
try {
|
|
10382
|
+
const organizations = [];
|
|
10383
|
+
const batches = organizationBatches(sourceTree);
|
|
10384
|
+
for (const [batchIndex, batch] of batches.entries()) {
|
|
10385
|
+
const budget = params.resolveBudget("extraction_source_tree", 4096);
|
|
10386
|
+
const startedAt = Date.now();
|
|
10387
|
+
const response = await safeGenerateObject(
|
|
10388
|
+
params.generateObject,
|
|
10389
|
+
{
|
|
10390
|
+
prompt: buildOrganizationPrompt(batch),
|
|
10391
|
+
schema: SourceTreeOrganizationSchema,
|
|
10392
|
+
maxTokens: budget.maxTokens,
|
|
10393
|
+
taskKind: "extraction_source_tree",
|
|
10394
|
+
budgetDiagnostics: budget
|
|
10395
|
+
},
|
|
10396
|
+
{
|
|
10397
|
+
fallback: { labels: [], groups: [] },
|
|
10398
|
+
log: params.log
|
|
10399
|
+
}
|
|
10400
|
+
);
|
|
10401
|
+
localTrack(response.usage, {
|
|
10402
|
+
taskKind: "extraction_source_tree",
|
|
10403
|
+
label: batches.length > 1 ? `source_tree_organizer_${batchIndex + 1}` : "source_tree_organizer",
|
|
10404
|
+
maxTokens: budget.maxTokens,
|
|
10405
|
+
durationMs: Date.now() - startedAt
|
|
10406
|
+
});
|
|
10407
|
+
organizations.push(response.object);
|
|
10408
|
+
}
|
|
10409
|
+
sourceTree = applyOrganization(sourceTree, mergeOrganizationResults(organizations));
|
|
10410
|
+
} catch (error) {
|
|
10411
|
+
warnings.push(`Source-tree organizer failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
|
|
10412
|
+
}
|
|
10413
|
+
} else {
|
|
10414
|
+
await params.log?.("Deterministic source tree ready; skipped model organizer");
|
|
10415
|
+
}
|
|
10416
|
+
if (shouldRunOutlineCleanup(sourceTree)) {
|
|
10417
|
+
try {
|
|
10418
|
+
const budget = params.resolveBudget("extraction_source_tree", 1600);
|
|
10419
|
+
const maxTokens = Math.min(budget.maxTokens, 1600);
|
|
10196
10420
|
const startedAt = Date.now();
|
|
10197
10421
|
const response = await safeGenerateObject(
|
|
10198
10422
|
params.generateObject,
|
|
10199
10423
|
{
|
|
10200
|
-
prompt:
|
|
10424
|
+
prompt: buildOutlineCleanupPrompt(sourceTree),
|
|
10201
10425
|
schema: SourceTreeOrganizationSchema,
|
|
10202
|
-
maxTokens
|
|
10426
|
+
maxTokens,
|
|
10203
10427
|
taskKind: "extraction_source_tree",
|
|
10204
|
-
budgetDiagnostics: budget,
|
|
10205
|
-
providerOptions: { ...params.providerOptions, sourceSpans }
|
|
10428
|
+
budgetDiagnostics: { ...budget, maxTokens }
|
|
10206
10429
|
},
|
|
10207
10430
|
{
|
|
10208
10431
|
fallback: { labels: [], groups: [] },
|
|
@@ -10211,17 +10434,15 @@ async function runSourceTreeExtraction(params) {
|
|
|
10211
10434
|
);
|
|
10212
10435
|
localTrack(response.usage, {
|
|
10213
10436
|
taskKind: "extraction_source_tree",
|
|
10214
|
-
label:
|
|
10215
|
-
maxTokens
|
|
10437
|
+
label: "source_tree_outline_cleanup",
|
|
10438
|
+
maxTokens,
|
|
10216
10439
|
durationMs: Date.now() - startedAt
|
|
10217
10440
|
});
|
|
10218
|
-
|
|
10441
|
+
sourceTree = applyOrganization(sourceTree, response.object);
|
|
10442
|
+
} catch (error) {
|
|
10443
|
+
warnings.push(`Source-tree outline cleanup failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
|
|
10219
10444
|
}
|
|
10220
|
-
sourceTree = applyOrganization(sourceTree, mergeOrganizationResults(organizations));
|
|
10221
|
-
} catch (error) {
|
|
10222
|
-
warnings.push(`Source-tree organizer failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
|
|
10223
10445
|
}
|
|
10224
|
-
sourceTree = applySemanticPageGrouping(sourceTree);
|
|
10225
10446
|
const deterministicProfile = buildDeterministicOperationalProfile({
|
|
10226
10447
|
sourceTree,
|
|
10227
10448
|
sourceSpans
|