@claritylabs/cl-sdk 3.0.3 → 3.0.4

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.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 spanPage(span) {
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 ?? spanPage(span);
4175
+ return span.pageEnd ?? span.location?.endPage ?? spanPage2(span);
4068
4176
  }
4069
4177
  function sourceSpansForPage(sourceSpans, page) {
4070
- return sourceSpans.filter((span) => spanPage(span) === page && sourceUnit4(span) === "page").map((span) => span.id);
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) => (spanPage(left) ?? 0) - (spanPage(right) ?? 0) || left.id.localeCompare(right.id)).map((span, index) => {
4172
- const title = span.sectionId ?? span.formNumber ?? (sourceUnit4(span) === "page" && spanPage(span) ? `Page ${spanPage(span)}` : `Source unit ${index + 1}`);
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: spanPage(span),
4288
+ pageStart: spanPage2(span),
4181
4289
  pageEnd: spanPageEnd(span),
4182
4290
  formNumber: span.formNumber,
4183
4291
  excerpt: span.text.slice(0, 500),
@@ -9257,7 +9365,8 @@ function materializeDocument(params) {
9257
9365
  };
9258
9366
  }
9259
9367
  async function runSourceTreeExtraction(params) {
9260
- let sourceTree = buildDocumentSourceTree(params.sourceSpans, params.id);
9368
+ const sourceSpans = normalizeSourceSpans(params.sourceSpans);
9369
+ let sourceTree = buildDocumentSourceTree(sourceSpans, params.id);
9261
9370
  const warnings = [];
9262
9371
  let modelCalls = 0;
9263
9372
  let callsWithUsage = 0;
@@ -9293,7 +9402,7 @@ async function runSourceTreeExtraction(params) {
9293
9402
  maxTokens: budget.maxTokens,
9294
9403
  taskKind: "extraction_source_tree",
9295
9404
  budgetDiagnostics: budget,
9296
- providerOptions: { ...params.providerOptions, sourceSpans: params.sourceSpans }
9405
+ providerOptions: { ...params.providerOptions, sourceSpans }
9297
9406
  },
9298
9407
  {
9299
9408
  fallback: { labels: [], groups: [] },
@@ -9314,12 +9423,12 @@ async function runSourceTreeExtraction(params) {
9314
9423
  }
9315
9424
  const deterministicProfile = buildDeterministicOperationalProfile({
9316
9425
  sourceTree,
9317
- sourceSpans: params.sourceSpans
9426
+ sourceSpans
9318
9427
  });
9319
9428
  let operationalProfile = deterministicProfile;
9320
9429
  try {
9321
9430
  const validNodeIds = new Set(sourceTree.map((node) => node.id));
9322
- const validSpanIds = new Set(params.sourceSpans.map((span) => span.id));
9431
+ const validSpanIds = new Set(sourceSpans.map((span) => span.id));
9323
9432
  const budget = params.resolveBudget("extraction_operational_profile", 8192);
9324
9433
  const startedAt = Date.now();
9325
9434
  const response = await safeGenerateObject(
@@ -9330,7 +9439,7 @@ async function runSourceTreeExtraction(params) {
9330
9439
  maxTokens: budget.maxTokens,
9331
9440
  taskKind: "extraction_operational_profile",
9332
9441
  budgetDiagnostics: budget,
9333
- providerOptions: { ...params.providerOptions, sourceSpans: params.sourceSpans, sourceTree }
9442
+ providerOptions: { ...params.providerOptions, sourceSpans, sourceTree }
9334
9443
  },
9335
9444
  {
9336
9445
  fallback: deterministicProfile,
@@ -9359,8 +9468,8 @@ async function runSourceTreeExtraction(params) {
9359
9468
  });
9360
9469
  return {
9361
9470
  sourceTree,
9362
- sourceSpans: params.sourceSpans,
9363
- sourceChunks: chunkSourceSpans(params.sourceSpans),
9471
+ sourceSpans,
9472
+ sourceChunks: chunkSourceSpans(sourceSpans),
9364
9473
  operationalProfile,
9365
9474
  document,
9366
9475
  chunks: [],
@@ -14374,6 +14483,7 @@ export {
14374
14483
  normalizeDoclingDocument,
14375
14484
  normalizeDocumentSourceTreePaths,
14376
14485
  normalizeForMatch,
14486
+ normalizeSourceSpans,
14377
14487
  orderSourceEvidence,
14378
14488
  overlayTextOnPdf,
14379
14489
  pLimit,