@claritylabs/cl-sdk 1.3.1 → 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
@@ -223,7 +223,8 @@ function resolveModelBudget(params) {
223
223
  const schemaTokens = estimateTokens(params.schemaSizeBytes) ?? 0;
224
224
  const expectedListLength = positiveInteger(params.expectedListLength) ?? 0;
225
225
  const warnings = [];
226
- let maxTokens = constrainedPreference ?? taskCapability ?? longListCapability ?? defaultCapability ?? hintTokens;
226
+ const preferredOutputTokens = constrainedPreference ?? taskCapability ?? longListCapability ?? defaultCapability ?? hintTokens;
227
+ let maxTokens = hardMaxOutputTokens ?? modelMaxOutputTokens ?? preferredOutputTokens;
227
228
  if (minOutputTokens) {
228
229
  maxTokens = Math.max(maxTokens, minOutputTokens);
229
230
  }
@@ -252,6 +253,7 @@ function resolveModelBudget(params) {
252
253
  taskKind,
253
254
  maxTokens,
254
255
  hintTokens,
256
+ preferredOutputTokens,
255
257
  modelMaxOutputTokens,
256
258
  hardMaxOutputTokens,
257
259
  estimatedInputTokens,
@@ -7855,7 +7857,7 @@ function normalizePageAssignments(pageAssignments, formInventory) {
7855
7857
  }
7856
7858
  return pageAssignments.map((assignment) => {
7857
7859
  let extractorNames = [...new Set(
7858
- (assignment.extractorNames.length > 0 ? assignment.extractorNames : ["sections"]).filter(Boolean)
7860
+ assignment.extractorNames.filter(Boolean)
7859
7861
  )];
7860
7862
  const hasDeclarations = extractorNames.includes("declarations");
7861
7863
  const hasConditions = extractorNames.includes("conditions");
@@ -7874,9 +7876,6 @@ function normalizePageAssignments(pageAssignments, formInventory) {
7874
7876
  if (inventoryTypes?.has("endorsement") && !extractorNames.includes("endorsements")) {
7875
7877
  extractorNames = [...extractorNames, "endorsements"];
7876
7878
  }
7877
- if (extractorNames.length === 0) {
7878
- extractorNames = ["sections"];
7879
- }
7880
7879
  return {
7881
7880
  ...assignment,
7882
7881
  extractorNames
@@ -7913,20 +7912,12 @@ function groupContiguousPages(pages) {
7913
7912
  function buildPlanFromPageAssignments(pageAssignments, pageCount, formInventory) {
7914
7913
  const extractorPages = /* @__PURE__ */ new Map();
7915
7914
  for (const assignment of pageAssignments) {
7916
- 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;
7917
7917
  for (const extractorName of extractors) {
7918
7918
  extractorPages.set(extractorName, [...extractorPages.get(extractorName) ?? [], assignment.localPageNumber]);
7919
7919
  }
7920
7920
  }
7921
- const coveredPages = /* @__PURE__ */ new Set();
7922
- for (const pages of extractorPages.values()) {
7923
- for (const page of pages) coveredPages.add(page);
7924
- }
7925
- for (let page = 1; page <= pageCount; page += 1) {
7926
- if (!coveredPages.has(page)) {
7927
- extractorPages.set("sections", [...extractorPages.get("sections") ?? [], page]);
7928
- }
7929
- }
7930
7921
  const contextualExtractors = /* @__PURE__ */ new Set(["conditions", "covered_reasons", "definitions", "exclusions", "endorsements"]);
7931
7922
  const contextualForms = (formInventory?.forms ?? []).filter(
7932
7923
  (form) => form.pageStart != null && (form.pageEnd ?? form.pageStart) != null
@@ -8213,6 +8204,139 @@ function createExtractor(config) {
8213
8204
  ranges.push({ startPage, endPage: previousPage });
8214
8205
  return ranges;
8215
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
+ }
8216
8340
  function shouldRunLlmReview(mode, report, sourceSpansAvailable) {
8217
8341
  if (mode === "skip" || maxReviewRounds <= 0) return false;
8218
8342
  if (mode === "always") return true;
@@ -8248,7 +8372,14 @@ function createExtractor(config) {
8248
8372
  }
8249
8373
  return lines.length > 0 ? lines.join("\n") : "";
8250
8374
  }
8251
- 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
+ }
8252
8383
  if (task.extractorName === "supplementary") {
8253
8384
  const alreadyExtractedSummary = buildAlreadyExtractedSummary(memory);
8254
8385
  const budget = resolveBudget("extraction_focused", 4096);
@@ -8497,6 +8628,17 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8497
8628
  formInventory = resumed.formInventory;
8498
8629
  memory.set("form_inventory", formInventory);
8499
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
+ });
8500
8642
  } else {
8501
8643
  onProgress?.(`Building form inventory for ${primaryType} ${documentType}...`);
8502
8644
  const budget = resolveBudget("extraction_form_inventory", 2048);
@@ -8537,6 +8679,20 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8537
8679
  if (resumed?.pageAssignments && pipelineCtx.isPhaseComplete("page_map")) {
8538
8680
  pageAssignments = resumed.pageAssignments;
8539
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
+ });
8540
8696
  } else {
8541
8697
  onProgress?.(`Mapping document pages for ${primaryType} ${documentType}...`);
8542
8698
  const chunkSize = 8;
@@ -8656,6 +8812,7 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8656
8812
  task,
8657
8813
  extractionPdfInput,
8658
8814
  memory,
8815
+ sourceSpans,
8659
8816
  completedPageRangePdfCache,
8660
8817
  getPageRangePdf,
8661
8818
  convertPdfToImages ? getPageImages : void 0,
@@ -8820,6 +8977,7 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8820
8977
  task,
8821
8978
  extractionPdfInput,
8822
8979
  memory,
8980
+ sourceSpans,
8823
8981
  completedPageRangePdfCache,
8824
8982
  getPageRangePdf,
8825
8983
  convertPdfToImages ? getPageImages : void 0,