@claritylabs/cl-sdk 1.3.2 → 1.3.3

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
@@ -7857,7 +7857,7 @@ function normalizePageAssignments(pageAssignments, formInventory) {
7857
7857
  }
7858
7858
  return pageAssignments.map((assignment) => {
7859
7859
  let extractorNames = [...new Set(
7860
- (assignment.extractorNames.length > 0 ? assignment.extractorNames : ["sections"]).filter(Boolean)
7860
+ assignment.extractorNames.filter(Boolean)
7861
7861
  )];
7862
7862
  const hasDeclarations = extractorNames.includes("declarations");
7863
7863
  const hasConditions = extractorNames.includes("conditions");
@@ -7876,9 +7876,6 @@ function normalizePageAssignments(pageAssignments, formInventory) {
7876
7876
  if (inventoryTypes?.has("endorsement") && !extractorNames.includes("endorsements")) {
7877
7877
  extractorNames = [...extractorNames, "endorsements"];
7878
7878
  }
7879
- if (extractorNames.length === 0) {
7880
- extractorNames = ["sections"];
7881
- }
7882
7879
  return {
7883
7880
  ...assignment,
7884
7881
  extractorNames
@@ -7915,20 +7912,12 @@ function groupContiguousPages(pages) {
7915
7912
  function buildPlanFromPageAssignments(pageAssignments, pageCount, formInventory) {
7916
7913
  const extractorPages = /* @__PURE__ */ new Map();
7917
7914
  for (const assignment of pageAssignments) {
7918
- const extractors = assignment.extractorNames.length > 0 ? assignment.extractorNames : ["sections"];
7915
+ const focusedExtractors = assignment.extractorNames.filter((name) => name !== "sections");
7916
+ const extractors = focusedExtractors.length > 0 ? focusedExtractors : assignment.extractorNames;
7919
7917
  for (const extractorName of extractors) {
7920
7918
  extractorPages.set(extractorName, [...extractorPages.get(extractorName) ?? [], assignment.localPageNumber]);
7921
7919
  }
7922
7920
  }
7923
- const coveredPages = /* @__PURE__ */ new Set();
7924
- for (const pages of extractorPages.values()) {
7925
- for (const page of pages) coveredPages.add(page);
7926
- }
7927
- for (let page = 1; page <= pageCount; page += 1) {
7928
- if (!coveredPages.has(page)) {
7929
- extractorPages.set("sections", [...extractorPages.get("sections") ?? [], page]);
7930
- }
7931
- }
7932
7921
  const contextualExtractors = /* @__PURE__ */ new Set(["conditions", "covered_reasons", "definitions", "exclusions", "endorsements"]);
7933
7922
  const contextualForms = (formInventory?.forms ?? []).filter(
7934
7923
  (form) => form.pageStart != null && (form.pageEnd ?? form.pageStart) != null
@@ -8215,6 +8204,139 @@ function createExtractor(config) {
8215
8204
  ranges.push({ startPage, endPage: previousPage });
8216
8205
  return ranges;
8217
8206
  }
8207
+ function pageNumberForSpan(span) {
8208
+ return span.pageStart ?? span.location?.startPage ?? span.location?.page;
8209
+ }
8210
+ function spansForPageRange(spans, startPage, endPage) {
8211
+ return spans.filter((span) => {
8212
+ const start = span.pageStart ?? span.location?.startPage ?? span.location?.page;
8213
+ const end = span.pageEnd ?? span.location?.endPage ?? start;
8214
+ return typeof start === "number" && typeof end === "number" && start <= endPage && end >= startPage;
8215
+ });
8216
+ }
8217
+ function inferSectionType(title, text) {
8218
+ const value = `${title} ${text.slice(0, 500)}`.toLowerCase();
8219
+ if (/\bdefinition|defined terms?\b/.test(value)) return "definition";
8220
+ if (/\bexclusion|not covered|does not apply\b/.test(value)) return "exclusion";
8221
+ if (/\bcondition|duties|loss condition|general condition\b/.test(value)) return "condition";
8222
+ if (/\bendorsement|amend|additional insured\b/.test(value)) return "endorsement";
8223
+ if (/\bcovered cause|covered reason|covered peril|cause of loss|perils insured\b/.test(value)) return "covered_reason";
8224
+ if (/\bdeclaration|schedule\b/.test(value)) return "declarations";
8225
+ return "policy_form";
8226
+ }
8227
+ function buildSourceBackedSectionIndex(spans, startPage, endPage) {
8228
+ const candidateSpans = spansForPageRange(spans, startPage, endPage).filter((span) => span.text.trim().length > 0).filter((span) => span.metadata?.sourceUnit === "section_candidate" || span.sectionId || span.text.length >= 160);
8229
+ return {
8230
+ sections: candidateSpans.map((span, index) => {
8231
+ const pageStart = span.pageStart ?? span.location?.startPage ?? span.location?.page ?? startPage;
8232
+ const pageEnd = span.pageEnd ?? span.location?.endPage ?? pageStart;
8233
+ const title = span.sectionId ?? span.formNumber ?? firstHeadingLine(span.text) ?? `Policy text page ${pageStart}`;
8234
+ return {
8235
+ title,
8236
+ sectionNumber: span.formNumber,
8237
+ pageStart,
8238
+ pageEnd,
8239
+ type: inferSectionType(title, span.text),
8240
+ excerpt: span.text.slice(0, 240),
8241
+ recordId: `section_index_${pageStart}_${index}`,
8242
+ sourceSpanIds: [span.id],
8243
+ sourceTextHash: span.textHash ?? sourceSpanTextHash(span.text)
8244
+ };
8245
+ })
8246
+ };
8247
+ }
8248
+ function firstHeadingLine(text) {
8249
+ const line = text.split(/\r?\n/).map((item) => item.trim()).find((item) => item.length > 0);
8250
+ if (!line) return void 0;
8251
+ return line.slice(0, 100);
8252
+ }
8253
+ function buildDeterministicFormInventory(spans, pageCount) {
8254
+ if (spans.length === 0) return void 0;
8255
+ const formPattern = /\b([A-Z]{1,4}\s?\d{2,5}(?:\s?\d{2,4})?|[A-Z]{2,8}-\d{2,8})\b/g;
8256
+ const seen = /* @__PURE__ */ new Map();
8257
+ for (const span of spans) {
8258
+ const page = pageNumberForSpan(span);
8259
+ if (!page) continue;
8260
+ const title = firstHeadingLine(span.text) ?? span.sectionId;
8261
+ const matches = /* @__PURE__ */ new Set([
8262
+ ...span.formNumber ? [span.formNumber] : [],
8263
+ ...Array.from(span.text.slice(0, 1200).matchAll(formPattern), (match) => match[1].trim())
8264
+ ]);
8265
+ for (const formNumber of matches) {
8266
+ const key = `${formNumber}:${title ?? ""}`;
8267
+ const existing = seen.get(key);
8268
+ const formType = inferFormType(title ?? "", span.text);
8269
+ if (existing) {
8270
+ existing.pageStart = Math.min(existing.pageStart ?? page, page);
8271
+ existing.pageEnd = Math.max(existing.pageEnd ?? page, page);
8272
+ continue;
8273
+ }
8274
+ seen.set(key, {
8275
+ formNumber,
8276
+ title,
8277
+ formType,
8278
+ pageStart: Math.min(page, pageCount),
8279
+ pageEnd: Math.min(page, pageCount)
8280
+ });
8281
+ }
8282
+ }
8283
+ const forms = [...seen.values()].sort((a, b) => (a.pageStart ?? 0) - (b.pageStart ?? 0));
8284
+ return forms.length > 0 ? { forms } : void 0;
8285
+ }
8286
+ function inferFormType(title, text) {
8287
+ const value = `${title} ${text.slice(0, 700)}`.toLowerCase();
8288
+ if (/\bdeclarations?|schedule\b/.test(value)) return "declarations";
8289
+ if (/\bendorsement|amendatory|additional insured\b/.test(value)) return "endorsement";
8290
+ if (/\bnotice|department of insurance|complaint|privacy\b/.test(value)) return "notice";
8291
+ if (/\bapplication|questionnaire\b/.test(value)) return "application";
8292
+ if (/\bcoverage|policy form|conditions|exclusions|definitions|causes of loss\b/.test(value)) return "coverage";
8293
+ return "other";
8294
+ }
8295
+ function buildDeterministicPageAssignments(spans, pageCount) {
8296
+ if (spans.length === 0) return [];
8297
+ const pageTexts = /* @__PURE__ */ new Map();
8298
+ for (const span of spans) {
8299
+ const page = pageNumberForSpan(span);
8300
+ if (!page) continue;
8301
+ pageTexts.set(page, [pageTexts.get(page), span.sectionId, span.text].filter(Boolean).join("\n"));
8302
+ }
8303
+ return Array.from({ length: pageCount }, (_, index) => {
8304
+ const page = index + 1;
8305
+ const text = pageTexts.get(page) ?? "";
8306
+ const lower = text.toLowerCase();
8307
+ const extractorNames = [];
8308
+ if (/\bdeclarations?|policy period|named insured|producer|schedule\b/.test(lower)) {
8309
+ extractorNames.push("carrier_info", "named_insured", "declarations");
8310
+ }
8311
+ if (/\blimit|deductible|premium|coinsurance|scheduled amount|blanket\b/.test(lower) && /\$|\b\d{2,}(?:,\d{3})*\b/.test(lower)) {
8312
+ extractorNames.push("coverage_limits");
8313
+ }
8314
+ if (/\bpremium|tax|fee|surcharge\b/.test(lower) && /\$/.test(lower)) extractorNames.push("premium_breakdown");
8315
+ if (/\bendorsement|additional insured|amendatory\b/.test(lower)) extractorNames.push("endorsements");
8316
+ if (/\bexclusion|not covered|does not apply\b/.test(lower)) extractorNames.push("exclusions");
8317
+ if (/\bcondition|duties in the event|loss condition|general condition\b/.test(lower)) extractorNames.push("conditions");
8318
+ if (/\bdefinition|defined terms?\b/.test(lower)) extractorNames.push("definitions");
8319
+ if (/\bcovered cause|covered peril|cause of loss|perils insured\b/.test(lower)) extractorNames.push("covered_reasons");
8320
+ if (textIncludesSupplementarySignal(text)) extractorNames.push("supplementary");
8321
+ if (extractorNames.length === 0 && text.trim().length > 180) extractorNames.push("sections");
8322
+ return {
8323
+ localPageNumber: page,
8324
+ extractorNames: [...new Set(extractorNames)],
8325
+ pageRole: inferPageRole(lower),
8326
+ hasScheduleValues: /\$|\b\d{2,}(?:,\d{3})*\b/.test(lower) && /\blimit|deductible|premium|schedule|class|location\b/.test(lower),
8327
+ confidence: text.trim().length > 0 ? 0.8 : 0,
8328
+ notes: "Deterministic source-span page assignment"
8329
+ };
8330
+ });
8331
+ }
8332
+ function inferPageRole(lowerText) {
8333
+ if (/\bdeclarations?|schedule\b/.test(lowerText)) return "declarations_schedule";
8334
+ if (/\bendorsement\b/.test(lowerText)) return "endorsement_form";
8335
+ if (/\bexclusion|condition\b/.test(lowerText)) return "condition_exclusion_form";
8336
+ if (/\bnotice|department of insurance|complaint\b/.test(lowerText)) return "supplementary";
8337
+ if (lowerText.trim()) return "policy_form";
8338
+ return "other";
8339
+ }
8218
8340
  function shouldRunLlmReview(mode, report, sourceSpansAvailable) {
8219
8341
  if (mode === "skip" || maxReviewRounds <= 0) return false;
8220
8342
  if (mode === "always") return true;
@@ -8250,7 +8372,14 @@ function createExtractor(config) {
8250
8372
  }
8251
8373
  return lines.length > 0 ? lines.join("\n") : "";
8252
8374
  }
8253
- async function runFocusedExtractorTask(task, pdfInput, memory, pageRangeCache, getPageRangePdf, getPageImages, getPageRangeText) {
8375
+ async function runFocusedExtractorTask(task, pdfInput, memory, sourceSpansForSections, pageRangeCache, getPageRangePdf, getPageImages, getPageRangeText) {
8376
+ if (task.extractorName === "sections" && sourceSpansForSections.length > 0) {
8377
+ return {
8378
+ name: "sections",
8379
+ data: buildSourceBackedSectionIndex(sourceSpansForSections, task.startPage, task.endPage),
8380
+ usage: void 0
8381
+ };
8382
+ }
8254
8383
  if (task.extractorName === "supplementary") {
8255
8384
  const alreadyExtractedSummary = buildAlreadyExtractedSummary(memory);
8256
8385
  const budget = resolveBudget("extraction_focused", 4096);
@@ -8499,6 +8628,17 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8499
8628
  formInventory = resumed.formInventory;
8500
8629
  memory.set("form_inventory", formInventory);
8501
8630
  onProgress?.("Resuming from checkpoint (form inventory complete)...");
8631
+ } else if (sourceSpans.length > 0) {
8632
+ formInventory = buildDeterministicFormInventory(sourceSpans, pageCount) ?? { forms: [] };
8633
+ onProgress?.(`Built deterministic form inventory for ${primaryType} ${documentType}.`);
8634
+ memory.set("form_inventory", formInventory);
8635
+ await pipelineCtx.save("form_inventory", {
8636
+ id,
8637
+ pageCount,
8638
+ classifyResult,
8639
+ formInventory,
8640
+ memory: Object.fromEntries(memory)
8641
+ });
8502
8642
  } else {
8503
8643
  onProgress?.(`Building form inventory for ${primaryType} ${documentType}...`);
8504
8644
  const budget = resolveBudget("extraction_form_inventory", 2048);
@@ -8539,6 +8679,20 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8539
8679
  if (resumed?.pageAssignments && pipelineCtx.isPhaseComplete("page_map")) {
8540
8680
  pageAssignments = resumed.pageAssignments;
8541
8681
  onProgress?.("Resuming from checkpoint (page map complete)...");
8682
+ } else if (sourceSpans.length > 0) {
8683
+ onProgress?.(`Mapping document pages from source spans for ${primaryType} ${documentType}...`);
8684
+ pageAssignments = normalizePageAssignments(
8685
+ buildDeterministicPageAssignments(sourceSpans, pageCount),
8686
+ formInventory
8687
+ );
8688
+ await pipelineCtx.save("page_map", {
8689
+ id,
8690
+ pageCount,
8691
+ classifyResult,
8692
+ formInventory,
8693
+ pageAssignments,
8694
+ memory: Object.fromEntries(memory)
8695
+ });
8542
8696
  } else {
8543
8697
  onProgress?.(`Mapping document pages for ${primaryType} ${documentType}...`);
8544
8698
  const chunkSize = 8;
@@ -8658,6 +8812,7 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8658
8812
  task,
8659
8813
  extractionPdfInput,
8660
8814
  memory,
8815
+ sourceSpans,
8661
8816
  completedPageRangePdfCache,
8662
8817
  getPageRangePdf,
8663
8818
  convertPdfToImages ? getPageImages : void 0,
@@ -8822,6 +8977,7 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8822
8977
  task,
8823
8978
  extractionPdfInput,
8824
8979
  memory,
8980
+ sourceSpans,
8825
8981
  completedPageRangePdfCache,
8826
8982
  getPageRangePdf,
8827
8983
  convertPdfToImages ? getPageImages : void 0,