@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.js CHANGED
@@ -8185,7 +8185,7 @@ function normalizePageAssignments(pageAssignments, formInventory) {
8185
8185
  }
8186
8186
  return pageAssignments.map((assignment) => {
8187
8187
  let extractorNames = [...new Set(
8188
- (assignment.extractorNames.length > 0 ? assignment.extractorNames : ["sections"]).filter(Boolean)
8188
+ assignment.extractorNames.filter(Boolean)
8189
8189
  )];
8190
8190
  const hasDeclarations = extractorNames.includes("declarations");
8191
8191
  const hasConditions = extractorNames.includes("conditions");
@@ -8204,9 +8204,6 @@ function normalizePageAssignments(pageAssignments, formInventory) {
8204
8204
  if (inventoryTypes?.has("endorsement") && !extractorNames.includes("endorsements")) {
8205
8205
  extractorNames = [...extractorNames, "endorsements"];
8206
8206
  }
8207
- if (extractorNames.length === 0) {
8208
- extractorNames = ["sections"];
8209
- }
8210
8207
  return {
8211
8208
  ...assignment,
8212
8209
  extractorNames
@@ -8243,20 +8240,12 @@ function groupContiguousPages(pages) {
8243
8240
  function buildPlanFromPageAssignments(pageAssignments, pageCount, formInventory) {
8244
8241
  const extractorPages = /* @__PURE__ */ new Map();
8245
8242
  for (const assignment of pageAssignments) {
8246
- const extractors = assignment.extractorNames.length > 0 ? assignment.extractorNames : ["sections"];
8243
+ const focusedExtractors = assignment.extractorNames.filter((name) => name !== "sections");
8244
+ const extractors = focusedExtractors.length > 0 ? focusedExtractors : assignment.extractorNames;
8247
8245
  for (const extractorName of extractors) {
8248
8246
  extractorPages.set(extractorName, [...extractorPages.get(extractorName) ?? [], assignment.localPageNumber]);
8249
8247
  }
8250
8248
  }
8251
- const coveredPages = /* @__PURE__ */ new Set();
8252
- for (const pages of extractorPages.values()) {
8253
- for (const page of pages) coveredPages.add(page);
8254
- }
8255
- for (let page = 1; page <= pageCount; page += 1) {
8256
- if (!coveredPages.has(page)) {
8257
- extractorPages.set("sections", [...extractorPages.get("sections") ?? [], page]);
8258
- }
8259
- }
8260
8249
  const contextualExtractors = /* @__PURE__ */ new Set(["conditions", "covered_reasons", "definitions", "exclusions", "endorsements"]);
8261
8250
  const contextualForms = (formInventory?.forms ?? []).filter(
8262
8251
  (form) => form.pageStart != null && (form.pageEnd ?? form.pageStart) != null
@@ -8543,6 +8532,139 @@ function createExtractor(config) {
8543
8532
  ranges.push({ startPage, endPage: previousPage });
8544
8533
  return ranges;
8545
8534
  }
8535
+ function pageNumberForSpan(span) {
8536
+ return span.pageStart ?? span.location?.startPage ?? span.location?.page;
8537
+ }
8538
+ function spansForPageRange(spans, startPage, endPage) {
8539
+ return spans.filter((span) => {
8540
+ const start = span.pageStart ?? span.location?.startPage ?? span.location?.page;
8541
+ const end = span.pageEnd ?? span.location?.endPage ?? start;
8542
+ return typeof start === "number" && typeof end === "number" && start <= endPage && end >= startPage;
8543
+ });
8544
+ }
8545
+ function inferSectionType(title, text) {
8546
+ const value = `${title} ${text.slice(0, 500)}`.toLowerCase();
8547
+ if (/\bdefinition|defined terms?\b/.test(value)) return "definition";
8548
+ if (/\bexclusion|not covered|does not apply\b/.test(value)) return "exclusion";
8549
+ if (/\bcondition|duties|loss condition|general condition\b/.test(value)) return "condition";
8550
+ if (/\bendorsement|amend|additional insured\b/.test(value)) return "endorsement";
8551
+ if (/\bcovered cause|covered reason|covered peril|cause of loss|perils insured\b/.test(value)) return "covered_reason";
8552
+ if (/\bdeclaration|schedule\b/.test(value)) return "declarations";
8553
+ return "policy_form";
8554
+ }
8555
+ function buildSourceBackedSectionIndex(spans, startPage, endPage) {
8556
+ 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);
8557
+ return {
8558
+ sections: candidateSpans.map((span, index) => {
8559
+ const pageStart = span.pageStart ?? span.location?.startPage ?? span.location?.page ?? startPage;
8560
+ const pageEnd = span.pageEnd ?? span.location?.endPage ?? pageStart;
8561
+ const title = span.sectionId ?? span.formNumber ?? firstHeadingLine(span.text) ?? `Policy text page ${pageStart}`;
8562
+ return {
8563
+ title,
8564
+ sectionNumber: span.formNumber,
8565
+ pageStart,
8566
+ pageEnd,
8567
+ type: inferSectionType(title, span.text),
8568
+ excerpt: span.text.slice(0, 240),
8569
+ recordId: `section_index_${pageStart}_${index}`,
8570
+ sourceSpanIds: [span.id],
8571
+ sourceTextHash: span.textHash ?? sourceSpanTextHash(span.text)
8572
+ };
8573
+ })
8574
+ };
8575
+ }
8576
+ function firstHeadingLine(text) {
8577
+ const line = text.split(/\r?\n/).map((item) => item.trim()).find((item) => item.length > 0);
8578
+ if (!line) return void 0;
8579
+ return line.slice(0, 100);
8580
+ }
8581
+ function buildDeterministicFormInventory(spans, pageCount) {
8582
+ if (spans.length === 0) return void 0;
8583
+ const formPattern = /\b([A-Z]{1,4}\s?\d{2,5}(?:\s?\d{2,4})?|[A-Z]{2,8}-\d{2,8})\b/g;
8584
+ const seen = /* @__PURE__ */ new Map();
8585
+ for (const span of spans) {
8586
+ const page = pageNumberForSpan(span);
8587
+ if (!page) continue;
8588
+ const title = firstHeadingLine(span.text) ?? span.sectionId;
8589
+ const matches = /* @__PURE__ */ new Set([
8590
+ ...span.formNumber ? [span.formNumber] : [],
8591
+ ...Array.from(span.text.slice(0, 1200).matchAll(formPattern), (match) => match[1].trim())
8592
+ ]);
8593
+ for (const formNumber of matches) {
8594
+ const key = `${formNumber}:${title ?? ""}`;
8595
+ const existing = seen.get(key);
8596
+ const formType = inferFormType(title ?? "", span.text);
8597
+ if (existing) {
8598
+ existing.pageStart = Math.min(existing.pageStart ?? page, page);
8599
+ existing.pageEnd = Math.max(existing.pageEnd ?? page, page);
8600
+ continue;
8601
+ }
8602
+ seen.set(key, {
8603
+ formNumber,
8604
+ title,
8605
+ formType,
8606
+ pageStart: Math.min(page, pageCount),
8607
+ pageEnd: Math.min(page, pageCount)
8608
+ });
8609
+ }
8610
+ }
8611
+ const forms = [...seen.values()].sort((a, b) => (a.pageStart ?? 0) - (b.pageStart ?? 0));
8612
+ return forms.length > 0 ? { forms } : void 0;
8613
+ }
8614
+ function inferFormType(title, text) {
8615
+ const value = `${title} ${text.slice(0, 700)}`.toLowerCase();
8616
+ if (/\bdeclarations?|schedule\b/.test(value)) return "declarations";
8617
+ if (/\bendorsement|amendatory|additional insured\b/.test(value)) return "endorsement";
8618
+ if (/\bnotice|department of insurance|complaint|privacy\b/.test(value)) return "notice";
8619
+ if (/\bapplication|questionnaire\b/.test(value)) return "application";
8620
+ if (/\bcoverage|policy form|conditions|exclusions|definitions|causes of loss\b/.test(value)) return "coverage";
8621
+ return "other";
8622
+ }
8623
+ function buildDeterministicPageAssignments(spans, pageCount) {
8624
+ if (spans.length === 0) return [];
8625
+ const pageTexts = /* @__PURE__ */ new Map();
8626
+ for (const span of spans) {
8627
+ const page = pageNumberForSpan(span);
8628
+ if (!page) continue;
8629
+ pageTexts.set(page, [pageTexts.get(page), span.sectionId, span.text].filter(Boolean).join("\n"));
8630
+ }
8631
+ return Array.from({ length: pageCount }, (_, index) => {
8632
+ const page = index + 1;
8633
+ const text = pageTexts.get(page) ?? "";
8634
+ const lower = text.toLowerCase();
8635
+ const extractorNames = [];
8636
+ if (/\bdeclarations?|policy period|named insured|producer|schedule\b/.test(lower)) {
8637
+ extractorNames.push("carrier_info", "named_insured", "declarations");
8638
+ }
8639
+ if (/\blimit|deductible|premium|coinsurance|scheduled amount|blanket\b/.test(lower) && /\$|\b\d{2,}(?:,\d{3})*\b/.test(lower)) {
8640
+ extractorNames.push("coverage_limits");
8641
+ }
8642
+ if (/\bpremium|tax|fee|surcharge\b/.test(lower) && /\$/.test(lower)) extractorNames.push("premium_breakdown");
8643
+ if (/\bendorsement|additional insured|amendatory\b/.test(lower)) extractorNames.push("endorsements");
8644
+ if (/\bexclusion|not covered|does not apply\b/.test(lower)) extractorNames.push("exclusions");
8645
+ if (/\bcondition|duties in the event|loss condition|general condition\b/.test(lower)) extractorNames.push("conditions");
8646
+ if (/\bdefinition|defined terms?\b/.test(lower)) extractorNames.push("definitions");
8647
+ if (/\bcovered cause|covered peril|cause of loss|perils insured\b/.test(lower)) extractorNames.push("covered_reasons");
8648
+ if (textIncludesSupplementarySignal(text)) extractorNames.push("supplementary");
8649
+ if (extractorNames.length === 0 && text.trim().length > 180) extractorNames.push("sections");
8650
+ return {
8651
+ localPageNumber: page,
8652
+ extractorNames: [...new Set(extractorNames)],
8653
+ pageRole: inferPageRole(lower),
8654
+ hasScheduleValues: /\$|\b\d{2,}(?:,\d{3})*\b/.test(lower) && /\blimit|deductible|premium|schedule|class|location\b/.test(lower),
8655
+ confidence: text.trim().length > 0 ? 0.8 : 0,
8656
+ notes: "Deterministic source-span page assignment"
8657
+ };
8658
+ });
8659
+ }
8660
+ function inferPageRole(lowerText) {
8661
+ if (/\bdeclarations?|schedule\b/.test(lowerText)) return "declarations_schedule";
8662
+ if (/\bendorsement\b/.test(lowerText)) return "endorsement_form";
8663
+ if (/\bexclusion|condition\b/.test(lowerText)) return "condition_exclusion_form";
8664
+ if (/\bnotice|department of insurance|complaint\b/.test(lowerText)) return "supplementary";
8665
+ if (lowerText.trim()) return "policy_form";
8666
+ return "other";
8667
+ }
8546
8668
  function shouldRunLlmReview(mode, report, sourceSpansAvailable) {
8547
8669
  if (mode === "skip" || maxReviewRounds <= 0) return false;
8548
8670
  if (mode === "always") return true;
@@ -8578,7 +8700,14 @@ function createExtractor(config) {
8578
8700
  }
8579
8701
  return lines.length > 0 ? lines.join("\n") : "";
8580
8702
  }
8581
- async function runFocusedExtractorTask(task, pdfInput, memory, pageRangeCache, getPageRangePdf, getPageImages, getPageRangeText) {
8703
+ async function runFocusedExtractorTask(task, pdfInput, memory, sourceSpansForSections, pageRangeCache, getPageRangePdf, getPageImages, getPageRangeText) {
8704
+ if (task.extractorName === "sections" && sourceSpansForSections.length > 0) {
8705
+ return {
8706
+ name: "sections",
8707
+ data: buildSourceBackedSectionIndex(sourceSpansForSections, task.startPage, task.endPage),
8708
+ usage: void 0
8709
+ };
8710
+ }
8582
8711
  if (task.extractorName === "supplementary") {
8583
8712
  const alreadyExtractedSummary = buildAlreadyExtractedSummary(memory);
8584
8713
  const budget = resolveBudget("extraction_focused", 4096);
@@ -8827,6 +8956,17 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8827
8956
  formInventory = resumed.formInventory;
8828
8957
  memory.set("form_inventory", formInventory);
8829
8958
  onProgress?.("Resuming from checkpoint (form inventory complete)...");
8959
+ } else if (sourceSpans.length > 0) {
8960
+ formInventory = buildDeterministicFormInventory(sourceSpans, pageCount) ?? { forms: [] };
8961
+ onProgress?.(`Built deterministic form inventory for ${primaryType} ${documentType}.`);
8962
+ memory.set("form_inventory", formInventory);
8963
+ await pipelineCtx.save("form_inventory", {
8964
+ id,
8965
+ pageCount,
8966
+ classifyResult,
8967
+ formInventory,
8968
+ memory: Object.fromEntries(memory)
8969
+ });
8830
8970
  } else {
8831
8971
  onProgress?.(`Building form inventory for ${primaryType} ${documentType}...`);
8832
8972
  const budget = resolveBudget("extraction_form_inventory", 2048);
@@ -8867,6 +9007,20 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8867
9007
  if (resumed?.pageAssignments && pipelineCtx.isPhaseComplete("page_map")) {
8868
9008
  pageAssignments = resumed.pageAssignments;
8869
9009
  onProgress?.("Resuming from checkpoint (page map complete)...");
9010
+ } else if (sourceSpans.length > 0) {
9011
+ onProgress?.(`Mapping document pages from source spans for ${primaryType} ${documentType}...`);
9012
+ pageAssignments = normalizePageAssignments(
9013
+ buildDeterministicPageAssignments(sourceSpans, pageCount),
9014
+ formInventory
9015
+ );
9016
+ await pipelineCtx.save("page_map", {
9017
+ id,
9018
+ pageCount,
9019
+ classifyResult,
9020
+ formInventory,
9021
+ pageAssignments,
9022
+ memory: Object.fromEntries(memory)
9023
+ });
8870
9024
  } else {
8871
9025
  onProgress?.(`Mapping document pages for ${primaryType} ${documentType}...`);
8872
9026
  const chunkSize = 8;
@@ -8986,6 +9140,7 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8986
9140
  task,
8987
9141
  extractionPdfInput,
8988
9142
  memory,
9143
+ sourceSpans,
8989
9144
  completedPageRangePdfCache,
8990
9145
  getPageRangePdf,
8991
9146
  convertPdfToImages ? getPageImages : void 0,
@@ -9150,6 +9305,7 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
9150
9305
  task,
9151
9306
  extractionPdfInput,
9152
9307
  memory,
9308
+ sourceSpans,
9153
9309
  completedPageRangePdfCache,
9154
9310
  getPageRangePdf,
9155
9311
  convertPdfToImages ? getPageImages : void 0,