@claritylabs/cl-sdk 3.0.3 → 3.0.5
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.d.mts +12 -11
- package/dist/index.d.ts +12 -11
- package/dist/index.js +157 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +156 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2301,9 +2301,117 @@ function chunkSourceSpans(spans, options = {}) {
|
|
|
2301
2301
|
flush();
|
|
2302
2302
|
return chunks;
|
|
2303
2303
|
}
|
|
2304
|
+
function normalizeSourceSpans(spans) {
|
|
2305
|
+
const droppedParentSpanIds = /* @__PURE__ */ new Set();
|
|
2306
|
+
const cleaned = [];
|
|
2307
|
+
for (const [index, span] of spans.entries()) {
|
|
2308
|
+
if (span.parentSpanId && droppedParentSpanIds.has(span.parentSpanId)) continue;
|
|
2309
|
+
const normalized = normalizeSourceSpanText(span);
|
|
2310
|
+
if (!normalized) {
|
|
2311
|
+
droppedParentSpanIds.add(span.id);
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
cleaned.push({ ...normalized, __originalIndex: index });
|
|
2315
|
+
}
|
|
2316
|
+
return mergeTextRuns(cleaned).map(({ __originalIndex: _index, ...span }) => span);
|
|
2317
|
+
}
|
|
2304
2318
|
function sourceUnit(span) {
|
|
2305
2319
|
return span.sourceUnit ?? span.metadata?.sourceUnit;
|
|
2306
2320
|
}
|
|
2321
|
+
function spanPage(span) {
|
|
2322
|
+
return span.pageStart ?? span.location?.page ?? span.location?.startPage;
|
|
2323
|
+
}
|
|
2324
|
+
function normalizeSourceSpanText(span) {
|
|
2325
|
+
const unit = sourceUnit(span);
|
|
2326
|
+
const text = normalizeWhitespace(span.text);
|
|
2327
|
+
if (!text) return void 0;
|
|
2328
|
+
if (isDiscardableBoilerplate(text, unit)) return void 0;
|
|
2329
|
+
const cleanedText = cleanBoilerplateLines(text);
|
|
2330
|
+
if (!cleanedText) return void 0;
|
|
2331
|
+
if (cleanedText === text) return span;
|
|
2332
|
+
return retextSpan(span, cleanedText, {
|
|
2333
|
+
boilerplateRemoved: "true",
|
|
2334
|
+
removedBoilerplateText: removedBoilerplateLines(text).join(" | ").slice(0, 500)
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
function isDiscardableBoilerplate(text, unit) {
|
|
2338
|
+
const cleaned = normalizeWhitespace(text.replace(/\bColumn\s+\d+:\s*/gi, ""));
|
|
2339
|
+
if (/^SPECIMEN POLICY\s+[-—]\s+FOR TESTING ONLY$/i.test(cleaned)) return true;
|
|
2340
|
+
if (/^Page\s+\d+\s+of\s+\d+$/i.test(cleaned)) return true;
|
|
2341
|
+
if (/^[A-Z]{2,}(?:-[A-Z0-9]{2,})+\s+\d{2}\s+\d{2}$/i.test(cleaned)) return true;
|
|
2342
|
+
if (/^[A-Z]{2,}(?:-[A-Z0-9]{2,})+\s+\d{2}\s+\d{2}\s*\|\s*Page\s+\d+\s+of\s+\d+$/i.test(cleaned)) return true;
|
|
2343
|
+
if (unit === "table_row" && /^[^|]{0,40}\|\s*Page\s+\d+\s+of\s+\d+$/i.test(cleaned)) return true;
|
|
2344
|
+
return false;
|
|
2345
|
+
}
|
|
2346
|
+
function isBoilerplateLine(line) {
|
|
2347
|
+
const cleaned = normalizeWhitespace(line.replace(/\bColumn\s+\d+:\s*/gi, ""));
|
|
2348
|
+
return isDiscardableBoilerplate(cleaned) || /^IMPORTANT NOTICE\s*[-—]\s*/i.test(cleaned) || /^THIS IS A CLAIMS-MADE AND REPORTED POLICY\.? PLEASE READ IT CAREFULLY\.?$/i.test(cleaned);
|
|
2349
|
+
}
|
|
2350
|
+
function removedBoilerplateLines(text) {
|
|
2351
|
+
return text.split(/\s{2,}|\r?\n/).map(normalizeWhitespace).filter((line) => line && isBoilerplateLine(line));
|
|
2352
|
+
}
|
|
2353
|
+
function cleanBoilerplateLines(text) {
|
|
2354
|
+
const withoutInlineBoilerplate = text.replace(/\b(?:Column\s+\d+:\s*)?[A-Z]{2,}(?:-[A-Z0-9]{2,})+\s+\d{2}\s+\d{2}\s+(?:\|\s*)?(?:Column\s+\d+:\s*)?Page\s+\d+\s+of\s+\d+\b/gi, " ").replace(/\bSPECIMEN POLICY\s+[-—]\s+FOR TESTING ONLY\b/gi, " ").replace(/\bPage\s+\d+\s+of\s+\d+\b/gi, " ");
|
|
2355
|
+
const lines = withoutInlineBoilerplate.split(/\r?\n/);
|
|
2356
|
+
const filtered = lines.map(normalizeWhitespace).filter((line) => line && !isBoilerplateLine(line));
|
|
2357
|
+
return normalizeWhitespace(filtered.join(" "));
|
|
2358
|
+
}
|
|
2359
|
+
function shouldMergeTextSpan(left, right) {
|
|
2360
|
+
if (sourceUnit(left) !== "text" || sourceUnit(right) !== "text") return false;
|
|
2361
|
+
if (spanPage(left) !== spanPage(right)) return false;
|
|
2362
|
+
if (left.metadata?.elementType === "title" || right.metadata?.elementType === "title") return false;
|
|
2363
|
+
const leftText = normalizeWhitespace(left.text);
|
|
2364
|
+
const rightText = normalizeWhitespace(right.text);
|
|
2365
|
+
if (!leftText || !rightText) return false;
|
|
2366
|
+
if (/[:.;!?)]$/.test(leftText)) return false;
|
|
2367
|
+
if (/^(?:[A-Z][A-Z0-9 &/(),.-]{8,}|Item\s+\d+|Section\s+\d+|Part\s+[A-Z]\b)/.test(rightText)) return false;
|
|
2368
|
+
return /^[a-z(]/.test(rightText) || /\b(?:a|an|and|any|as|at|by|for|from|in|into|may|must|of|or|that|the|this|to|with|within|you|your)$/i.test(leftText);
|
|
2369
|
+
}
|
|
2370
|
+
function mergeTextRuns(spans) {
|
|
2371
|
+
const result = [];
|
|
2372
|
+
let current;
|
|
2373
|
+
for (const span of spans) {
|
|
2374
|
+
if (current && shouldMergeTextSpan(current, span)) {
|
|
2375
|
+
current = mergeTextSpanPair(current, span);
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
if (current) result.push(current);
|
|
2379
|
+
current = span;
|
|
2380
|
+
}
|
|
2381
|
+
if (current) result.push(current);
|
|
2382
|
+
return result;
|
|
2383
|
+
}
|
|
2384
|
+
function mergeTextSpanPair(left, right) {
|
|
2385
|
+
const text = normalizeWhitespace(`${left.text} ${right.text}`);
|
|
2386
|
+
const merged = retextSpan(left, text, {
|
|
2387
|
+
mergedSourceSpanIds: [left.metadata?.mergedSourceSpanIds, left.id, right.id, right.metadata?.mergedSourceSpanIds].filter(Boolean).join(","),
|
|
2388
|
+
sourceSpanNormalization: "merged_text_run"
|
|
2389
|
+
});
|
|
2390
|
+
return {
|
|
2391
|
+
...merged,
|
|
2392
|
+
bbox: [...left.bbox ?? [], ...right.bbox ?? []],
|
|
2393
|
+
pageEnd: right.pageEnd ?? left.pageEnd,
|
|
2394
|
+
location: {
|
|
2395
|
+
...left.location,
|
|
2396
|
+
endPage: right.location?.endPage ?? right.pageEnd ?? left.location?.endPage
|
|
2397
|
+
},
|
|
2398
|
+
__originalIndex: left.__originalIndex
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
function retextSpan(span, text, metadata) {
|
|
2402
|
+
const textHash = sourceSpanTextHash(text);
|
|
2403
|
+
return SourceSpanSchema.parse({
|
|
2404
|
+
...span,
|
|
2405
|
+
id: `${span.id.split(":").slice(0, -1).join(":")}:${textHash.slice(0, 12)}`,
|
|
2406
|
+
text,
|
|
2407
|
+
hash: textHash,
|
|
2408
|
+
textHash,
|
|
2409
|
+
metadata: {
|
|
2410
|
+
...span.metadata ?? {},
|
|
2411
|
+
...metadata
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2307
2415
|
function filterChunkableSourceSpans(spans) {
|
|
2308
2416
|
const rowIds = new Set(
|
|
2309
2417
|
spans.filter((span) => sourceUnit(span) === "table_row").map((span) => span.id)
|
|
@@ -4060,14 +4168,14 @@ function stringArray(value) {
|
|
|
4060
4168
|
function sourceUnit4(span) {
|
|
4061
4169
|
return span.sourceUnit ?? span.metadata?.sourceUnit;
|
|
4062
4170
|
}
|
|
4063
|
-
function
|
|
4171
|
+
function spanPage2(span) {
|
|
4064
4172
|
return span.pageStart ?? span.location?.page ?? span.location?.startPage;
|
|
4065
4173
|
}
|
|
4066
4174
|
function spanPageEnd(span) {
|
|
4067
|
-
return span.pageEnd ?? span.location?.endPage ??
|
|
4175
|
+
return span.pageEnd ?? span.location?.endPage ?? spanPage2(span);
|
|
4068
4176
|
}
|
|
4069
4177
|
function sourceSpansForPage(sourceSpans, page) {
|
|
4070
|
-
return sourceSpans.filter((span) =>
|
|
4178
|
+
return sourceSpans.filter((span) => spanPage2(span) === page && sourceUnit4(span) === "page").map((span) => span.id);
|
|
4071
4179
|
}
|
|
4072
4180
|
function nodePageOverlaps(node, record) {
|
|
4073
4181
|
const recordStart = numberValue(record, "pageNumber", "pageStart", "resolvedFromPage");
|
|
@@ -4168,8 +4276,8 @@ function buildNodesFromSourceSpans(sourceSpans) {
|
|
|
4168
4276
|
const unit = sourceUnit4(span);
|
|
4169
4277
|
return unit === "section" || unit === "section_candidate" || unit === "page";
|
|
4170
4278
|
});
|
|
4171
|
-
return candidates.sort((left, right) => (
|
|
4172
|
-
const title = span.sectionId ?? span.formNumber ?? (sourceUnit4(span) === "page" &&
|
|
4279
|
+
return candidates.sort((left, right) => (spanPage2(left) ?? 0) - (spanPage2(right) ?? 0) || left.id.localeCompare(right.id)).map((span, index) => {
|
|
4280
|
+
const title = span.sectionId ?? span.formNumber ?? (sourceUnit4(span) === "page" && spanPage2(span) ? `Page ${spanPage2(span)}` : `Source unit ${index + 1}`);
|
|
4173
4281
|
return {
|
|
4174
4282
|
id: `source:${index}:${slugPart(span.id)}`,
|
|
4175
4283
|
title,
|
|
@@ -4177,7 +4285,7 @@ function buildNodesFromSourceSpans(sourceSpans) {
|
|
|
4177
4285
|
type: sourceUnit4(span),
|
|
4178
4286
|
label: sourceUnit4(span),
|
|
4179
4287
|
level: 1,
|
|
4180
|
-
pageStart:
|
|
4288
|
+
pageStart: spanPage2(span),
|
|
4181
4289
|
pageEnd: spanPageEnd(span),
|
|
4182
4290
|
formNumber: span.formNumber,
|
|
4183
4291
|
excerpt: span.text.slice(0, 500),
|
|
@@ -8956,6 +9064,29 @@ function cleanText(value, fallback) {
|
|
|
8956
9064
|
const text = value?.replace(/\s+/g, " ").trim();
|
|
8957
9065
|
return text || fallback;
|
|
8958
9066
|
}
|
|
9067
|
+
function simplifyOrganizerTitle(value, fallback, kind) {
|
|
9068
|
+
const title = cleanText(value, fallback);
|
|
9069
|
+
if (/^declarations\b/i.test(title)) return "Declarations";
|
|
9070
|
+
if (/^policy\s+form\b/i.test(title)) return "Policy Form";
|
|
9071
|
+
if (/^definitions\b/i.test(title)) return "Definitions";
|
|
9072
|
+
const endorsementNumber = title.match(/^endorsement\s+(?:no\.?|number|#)?\s*([A-Z0-9][A-Z0-9.-]*)\b/i)?.[1];
|
|
9073
|
+
if (endorsementNumber) return `Endorsement No. ${endorsementNumber}`;
|
|
9074
|
+
if (kind === "endorsement" && /^endorsements?\s+\d+\s*[–-]\s*\d+\b/i.test(title)) {
|
|
9075
|
+
return title.replace(/[–—]/g, "-").replace(/\s*\(.*/, "").trim();
|
|
9076
|
+
}
|
|
9077
|
+
return title;
|
|
9078
|
+
}
|
|
9079
|
+
function endorsementReference(value) {
|
|
9080
|
+
return value?.match(/\bendorsement\s+(?:no\.?|number|#)?\s*([A-Z0-9][A-Z0-9.-]*)\b/i)?.[1]?.toUpperCase();
|
|
9081
|
+
}
|
|
9082
|
+
function rejectsOrganizerGroup(group, children) {
|
|
9083
|
+
if (group.kind !== "endorsement") return false;
|
|
9084
|
+
if (/^endorsements?\s+\d+\s*[–-]\s*\d+\b/i.test(group.title)) return true;
|
|
9085
|
+
const childNumbers = new Set(
|
|
9086
|
+
children.map((child) => endorsementReference([child.title, child.description, child.textExcerpt].filter(Boolean).join(" "))).filter((value) => Boolean(value))
|
|
9087
|
+
);
|
|
9088
|
+
return childNumbers.size > 1;
|
|
9089
|
+
}
|
|
8959
9090
|
function compactNode(node, maxText = 700) {
|
|
8960
9091
|
return {
|
|
8961
9092
|
id: node.id,
|
|
@@ -9043,8 +9174,10 @@ Scope:
|
|
|
9043
9174
|
Rules:
|
|
9044
9175
|
- Use only node IDs from the provided list.
|
|
9045
9176
|
- Do not invent text, page numbers, source spans, limits, or policy facts.
|
|
9046
|
-
- You may relabel existing nodes and group adjacent top-level/page nodes from this batch when they are clearly one form,
|
|
9177
|
+
- 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.
|
|
9178
|
+
- Do not group separately numbered endorsements into one parent. Never create rollup titles such as "Endorsements 1-3 (...)".
|
|
9047
9179
|
- Add concise, human-readable titles to generic text, table, row, and cell nodes when the text makes their role clear.
|
|
9180
|
+
- 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.
|
|
9048
9181
|
- Groups must list existing childNodeIds only.
|
|
9049
9182
|
- Keep descriptions short and useful for search.
|
|
9050
9183
|
- Prefer the document's own form titles, endorsement titles, schedules, declarations headings, and page order over keyword-only guessing.
|
|
@@ -9094,17 +9227,20 @@ function applyOrganization(sourceTree, organization) {
|
|
|
9094
9227
|
return {
|
|
9095
9228
|
...node,
|
|
9096
9229
|
kind: label.kind ?? node.kind,
|
|
9097
|
-
title:
|
|
9230
|
+
title: simplifyOrganizerTitle(label.title, node.title, label.kind ?? node.kind),
|
|
9098
9231
|
description: cleanText(label.description, node.description)
|
|
9099
9232
|
};
|
|
9100
9233
|
});
|
|
9101
9234
|
for (const group of organization.groups) {
|
|
9102
9235
|
const children = group.childNodeIds.map((id2) => byId.get(id2)).filter((node2) => Boolean(node2));
|
|
9103
9236
|
if (children.length === 0) continue;
|
|
9237
|
+
if (rejectsOrganizerGroup(group, children)) continue;
|
|
9104
9238
|
const parentId = children[0].parentId;
|
|
9105
9239
|
if (!children.every((child) => child.parentId === parentId)) continue;
|
|
9106
9240
|
const documentId = children[0].documentId;
|
|
9107
|
-
const
|
|
9241
|
+
const title = simplifyOrganizerTitle(group.title, group.title, group.kind);
|
|
9242
|
+
const description = cleanText(group.description, title);
|
|
9243
|
+
const id = groupNodeId(documentId, { ...group, title });
|
|
9108
9244
|
if (byId.has(id)) continue;
|
|
9109
9245
|
const sourceSpanIds = [...new Set(children.flatMap((child) => child.sourceSpanIds))];
|
|
9110
9246
|
const pageStarts = children.map((child) => child.pageStart).filter((page) => typeof page === "number");
|
|
@@ -9115,8 +9251,8 @@ function applyOrganization(sourceTree, organization) {
|
|
|
9115
9251
|
documentId,
|
|
9116
9252
|
parentId,
|
|
9117
9253
|
kind: group.kind,
|
|
9118
|
-
title
|
|
9119
|
-
description
|
|
9254
|
+
title,
|
|
9255
|
+
description,
|
|
9120
9256
|
textExcerpt: children.map((child) => child.textExcerpt ?? child.description).filter(Boolean).join("\n\n").slice(0, 1600),
|
|
9121
9257
|
sourceSpanIds,
|
|
9122
9258
|
pageStart: pageStarts.length ? Math.min(...pageStarts) : void 0,
|
|
@@ -9257,7 +9393,8 @@ function materializeDocument(params) {
|
|
|
9257
9393
|
};
|
|
9258
9394
|
}
|
|
9259
9395
|
async function runSourceTreeExtraction(params) {
|
|
9260
|
-
|
|
9396
|
+
const sourceSpans = normalizeSourceSpans(params.sourceSpans);
|
|
9397
|
+
let sourceTree = buildDocumentSourceTree(sourceSpans, params.id);
|
|
9261
9398
|
const warnings = [];
|
|
9262
9399
|
let modelCalls = 0;
|
|
9263
9400
|
let callsWithUsage = 0;
|
|
@@ -9293,7 +9430,7 @@ async function runSourceTreeExtraction(params) {
|
|
|
9293
9430
|
maxTokens: budget.maxTokens,
|
|
9294
9431
|
taskKind: "extraction_source_tree",
|
|
9295
9432
|
budgetDiagnostics: budget,
|
|
9296
|
-
providerOptions: { ...params.providerOptions, sourceSpans
|
|
9433
|
+
providerOptions: { ...params.providerOptions, sourceSpans }
|
|
9297
9434
|
},
|
|
9298
9435
|
{
|
|
9299
9436
|
fallback: { labels: [], groups: [] },
|
|
@@ -9314,12 +9451,12 @@ async function runSourceTreeExtraction(params) {
|
|
|
9314
9451
|
}
|
|
9315
9452
|
const deterministicProfile = buildDeterministicOperationalProfile({
|
|
9316
9453
|
sourceTree,
|
|
9317
|
-
sourceSpans
|
|
9454
|
+
sourceSpans
|
|
9318
9455
|
});
|
|
9319
9456
|
let operationalProfile = deterministicProfile;
|
|
9320
9457
|
try {
|
|
9321
9458
|
const validNodeIds = new Set(sourceTree.map((node) => node.id));
|
|
9322
|
-
const validSpanIds = new Set(
|
|
9459
|
+
const validSpanIds = new Set(sourceSpans.map((span) => span.id));
|
|
9323
9460
|
const budget = params.resolveBudget("extraction_operational_profile", 8192);
|
|
9324
9461
|
const startedAt = Date.now();
|
|
9325
9462
|
const response = await safeGenerateObject(
|
|
@@ -9330,7 +9467,7 @@ async function runSourceTreeExtraction(params) {
|
|
|
9330
9467
|
maxTokens: budget.maxTokens,
|
|
9331
9468
|
taskKind: "extraction_operational_profile",
|
|
9332
9469
|
budgetDiagnostics: budget,
|
|
9333
|
-
providerOptions: { ...params.providerOptions, sourceSpans
|
|
9470
|
+
providerOptions: { ...params.providerOptions, sourceSpans, sourceTree }
|
|
9334
9471
|
},
|
|
9335
9472
|
{
|
|
9336
9473
|
fallback: deterministicProfile,
|
|
@@ -9359,8 +9496,8 @@ async function runSourceTreeExtraction(params) {
|
|
|
9359
9496
|
});
|
|
9360
9497
|
return {
|
|
9361
9498
|
sourceTree,
|
|
9362
|
-
sourceSpans
|
|
9363
|
-
sourceChunks: chunkSourceSpans(
|
|
9499
|
+
sourceSpans,
|
|
9500
|
+
sourceChunks: chunkSourceSpans(sourceSpans),
|
|
9364
9501
|
operationalProfile,
|
|
9365
9502
|
document,
|
|
9366
9503
|
chunks: [],
|
|
@@ -14374,6 +14511,7 @@ export {
|
|
|
14374
14511
|
normalizeDoclingDocument,
|
|
14375
14512
|
normalizeDocumentSourceTreePaths,
|
|
14376
14513
|
normalizeForMatch,
|
|
14514
|
+
normalizeSourceSpans,
|
|
14377
14515
|
orderSourceEvidence,
|
|
14378
14516
|
overlayTextOnPdf,
|
|
14379
14517
|
pLimit,
|