@claritylabs/cl-sdk 3.0.23 → 3.0.25

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
@@ -3300,6 +3300,9 @@ function cleanValue(value) {
3300
3300
  if (!value) return void 0;
3301
3301
  return normalizeWhitespace3(value.replace(/^[\s:;#-]+|[\s;,.]+$/g, ""));
3302
3302
  }
3303
+ function cleanCoverageLabel(value) {
3304
+ return cleanValue(value)?.replace(/^column\s+\d+\s*:\s*/i, "");
3305
+ }
3303
3306
  function moneyValue(value) {
3304
3307
  const clean = cleanValue(value);
3305
3308
  if (!clean) return void 0;
@@ -3356,13 +3359,17 @@ function inferDocumentType(nodes) {
3356
3359
  return "policy";
3357
3360
  }
3358
3361
  function coverageNameFromRow(text) {
3359
- const labelled = text.match(/\b(?:coverage|coverage part|line)\s*:?\s*([^|;$]{3,80})/i)?.[1];
3360
- if (labelled) return cleanValue(labelled);
3361
- const parts = text.split(/\s+\|\s+| {2,}|\t/).map(cleanValue).filter(Boolean);
3362
+ const labelled = text.match(/\b(coverage part|coverage|line)\s*:?\s*([^|;$]{3,80})/i);
3363
+ if (labelled) {
3364
+ const value = cleanCoverageLabel(labelled[2]);
3365
+ if (!value) return void 0;
3366
+ return /^coverage part$/i.test(labelled[1]) ? `Coverage Part ${value}` : value;
3367
+ }
3368
+ const parts = text.split(/\s+\|\s+| {2,}|\t/).map(cleanCoverageLabel).filter(Boolean);
3362
3369
  const first = parts.find(
3363
3370
  (part) => !/^(limit|limits?|deductible|premium|amount|basis|rate|retroactive|aggregate|each occurrence)$/i.test(part) && !/^\$?[\d,]+/.test(part)
3364
3371
  );
3365
- return cleanValue(first);
3372
+ return cleanCoverageLabel(first);
3366
3373
  }
3367
3374
  function limitFromText(text) {
3368
3375
  return moneyValue(
@@ -3479,21 +3486,40 @@ function endorsementAncestor(node, byId) {
3479
3486
  function endorsementNumberFrom(value) {
3480
3487
  return cleanValue(value?.match(/\bendorsement\s+no\.?\s*([0-9A-Z-]+)/i)?.[1]);
3481
3488
  }
3489
+ function endorsementNameFrom(node) {
3490
+ const candidates = [node.title, node.textExcerpt, node.description, nodeText(node)].filter(Boolean);
3491
+ for (const candidate of candidates) {
3492
+ const match = candidate.match(/\bendorsement\s+no\.?\s*([0-9A-Z-]+)\s*[—-]\s*(.{3,140}?)(?=\s+This\s+endorsement\b|\.|$)/i);
3493
+ if (match) return cleanValue(`Endorsement No. ${match[1]} - ${match[2]}`) ?? node.title;
3494
+ }
3495
+ return node.title;
3496
+ }
3482
3497
  function sourceIds(nodes) {
3483
3498
  return {
3484
3499
  sourceNodeIds: [...new Set(nodes.map((node) => node.id))],
3485
3500
  sourceSpanIds: [...new Set(nodes.flatMap((node) => node.sourceSpanIds))]
3486
3501
  };
3487
3502
  }
3503
+ function relabelGenericTerm(term, label) {
3504
+ const cleanLabel = cleanValue(label);
3505
+ if (!cleanLabel || !/^column\s+\d+$/i.test(term.label)) return term;
3506
+ return {
3507
+ ...term,
3508
+ kind: termKind(cleanLabel, term.value),
3509
+ label: cleanLabel
3510
+ };
3511
+ }
3488
3512
  function termFromCell(params) {
3489
3513
  const value = cleanValue(params.value);
3490
3514
  if (!value) return void 0;
3491
3515
  const nodes = [params.row, params.cell].filter((node) => Boolean(node));
3516
+ const kind = termKind(params.label, value);
3517
+ const amount = moneyAmount(value);
3492
3518
  return {
3493
- kind: termKind(params.label, value),
3519
+ kind,
3494
3520
  label: params.label,
3495
3521
  value,
3496
- ...moneyAmount(value) !== void 0 ? { amount: moneyAmount(value) } : {},
3522
+ ...amount !== void 0 && kind !== "retroactive_date" ? { amount } : {},
3497
3523
  ...params.appliesTo ? { appliesTo: params.appliesTo } : {},
3498
3524
  ...sourceIds(nodes)
3499
3525
  };
@@ -3530,7 +3556,7 @@ function nameFromRow(row, children) {
3530
3556
  const cells = cellRows(row, children);
3531
3557
  const named = cells.find((cell) => isNameCell(cell.label, cell.value));
3532
3558
  if (named) return cleanValue(named.value);
3533
- return coverageNameFromRow(nodeText(row));
3559
+ return coverageNameFromRow(row.textExcerpt ?? row.description ?? nodeText(row));
3534
3560
  }
3535
3561
  function legacyLimit(terms) {
3536
3562
  return terms.find(
@@ -3546,6 +3572,22 @@ function legacyPremium(terms) {
3546
3572
  function retroDate(terms) {
3547
3573
  return terms.find((term) => term.kind === "retroactive_date")?.value;
3548
3574
  }
3575
+ function hasCoverageLimitTerm(terms) {
3576
+ return terms.some(
3577
+ (term) => ["each_claim_limit", "each_occurrence_limit", "each_loss_limit", "aggregate_limit", "sublimit"].includes(term.kind) || term.kind === "other" && /\blimit\b/i.test(term.label) && moneyAmount(term.value) !== void 0
3578
+ );
3579
+ }
3580
+ function isOperationalCoverageRow(coverage) {
3581
+ const name = normalizeLabel(coverage.name);
3582
+ if (!hasCoverageLimitTerm(coverage.limits)) return false;
3583
+ if (/^(item\s+\d+|option:|annual policy premium|total premium|premium and payment)\b/i.test(coverage.name)) return false;
3584
+ if (/^(nwc|iso|cg|il|acord)[-\s]?[a-z0-9]/i.test(coverage.name)) return false;
3585
+ if (/\b(forms?|endorsements?|premium|payment|terrorism risk insurance act|tria|erp option|bilateral discovery)\b/i.test(name)) return false;
3586
+ if (coverage.name.split(/\s+/).length > 16 && !/\b(coverage|liability|sub[-\s]?limit|aggregate|each\s+(claim|loss|occurrence)|limit)\b/i.test(coverage.name)) {
3587
+ return false;
3588
+ }
3589
+ return /\b(coverage|coverage part|liability|sub[-\s]?limit|aggregate|each\s+(claim|loss|occurrence)|bricking|cyber|privacy|media|regulatory|defense|ai\/ml|errors?\s*&?\s*omissions|technology)\b/i.test(coverage.name);
3590
+ }
3549
3591
  function uniqueTerms(terms) {
3550
3592
  const seen = /* @__PURE__ */ new Set();
3551
3593
  const result = [];
@@ -3583,31 +3625,39 @@ function coverageFromEndorsement(endorsement, children) {
3583
3625
  const terms = uniqueTerms(rows.flatMap(
3584
3626
  (row) => termsFromRow(row, children).map((term) => {
3585
3627
  const appliesTo = nameFromRow(row, children);
3586
- return appliesTo && !/^(each claim limit|aggregate limit|retroactive date)$/i.test(appliesTo) ? { ...term, appliesTo } : term;
3628
+ const labelled = relabelGenericTerm(term, appliesTo);
3629
+ return appliesTo && labelled.label !== appliesTo ? { ...labelled, appliesTo } : labelled;
3587
3630
  })
3588
3631
  ));
3589
3632
  if (terms.length === 0) return void 0;
3590
3633
  const ids = sourceIds([endorsement, ...rows, ...rows.flatMap((row) => directChildren(row, children))]);
3634
+ const name = endorsementNameFrom(endorsement);
3591
3635
  return {
3592
- name: endorsement.title,
3636
+ name,
3593
3637
  limit: legacyLimit(terms),
3594
3638
  deductible: legacyDeductible(terms),
3595
3639
  premium: legacyPremium(terms),
3596
3640
  retroactiveDate: retroDate(terms),
3597
3641
  formNumber: typeof endorsement.metadata?.formNumber === "string" ? endorsement.metadata.formNumber : void 0,
3598
- sectionRef: endorsement.title,
3642
+ sectionRef: name,
3599
3643
  coverageOrigin: "endorsement",
3600
- endorsementNumber: endorsementNumberFrom(endorsement.title),
3644
+ endorsementNumber: endorsementNumberFrom(name),
3601
3645
  limits: terms,
3602
3646
  ...ids
3603
3647
  };
3604
3648
  }
3649
+ function hasDescendantEndorsementWithTableRows(endorsement, children) {
3650
+ return descendants(endorsement, children).some(
3651
+ (node) => node.kind === "endorsement" && descendants(node, children).some((descendant) => descendant.kind === "table_row")
3652
+ );
3653
+ }
3605
3654
  function buildCoverages(nodes) {
3606
3655
  const children = childMap(nodes);
3607
3656
  const byId = new Map(nodes.map((node) => [node.id, node]));
3608
3657
  const coverages = [];
3609
3658
  const seen = /* @__PURE__ */ new Set();
3610
3659
  for (const endorsement of nodes.filter((node) => node.kind === "endorsement")) {
3660
+ if (hasDescendantEndorsementWithTableRows(endorsement, children)) continue;
3611
3661
  const coverage = coverageFromEndorsement(endorsement, children);
3612
3662
  if (!coverage) continue;
3613
3663
  const key = [coverage.name.toLowerCase(), coverage.sourceNodeIds.join(",")].join("|");
@@ -3623,6 +3673,7 @@ function buildCoverages(nodes) {
3623
3673
  if (endorsementAncestor(row, byId)) continue;
3624
3674
  const structured = row.kind === "table_row" ? coverageFromTableRow(row, children, byId) : void 0;
3625
3675
  if (structured) {
3676
+ if (!isOperationalCoverageRow(structured)) continue;
3626
3677
  const key2 = [
3627
3678
  structured.name.toLowerCase(),
3628
3679
  structured.limits.map((term) => `${term.kind}:${term.value}`).join(","),
@@ -3640,6 +3691,20 @@ function buildCoverages(nodes) {
3640
3691
  const deductible = deductibleFromText(text);
3641
3692
  const premium = premiumFromText(text);
3642
3693
  if (!name || !limit && !deductible && !premium) continue;
3694
+ if (!isOperationalCoverageRow({
3695
+ name,
3696
+ limit,
3697
+ deductible,
3698
+ premium,
3699
+ coverageOrigin: "core",
3700
+ limits: [
3701
+ ...limit ? [{ kind: "other", label: "Limit", value: limit, sourceNodeIds: [row.id], sourceSpanIds: row.sourceSpanIds }] : [],
3702
+ ...deductible ? [{ kind: "deductible", label: "Deductible", value: deductible, sourceNodeIds: [row.id], sourceSpanIds: row.sourceSpanIds }] : [],
3703
+ ...premium ? [{ kind: "premium", label: "Premium", value: premium, sourceNodeIds: [row.id], sourceSpanIds: row.sourceSpanIds }] : []
3704
+ ],
3705
+ sourceNodeIds: [row.id],
3706
+ sourceSpanIds: row.sourceSpanIds
3707
+ })) continue;
3643
3708
  const key = [name.toLowerCase(), limit, deductible, premium].join("|");
3644
3709
  if (seen.has(key)) continue;
3645
3710
  seen.add(key);
@@ -3792,7 +3857,7 @@ function mergeOperationalProfile(base, candidate, validNodeIds, validSpanIds) {
3792
3857
  sourceSpanIds
3793
3858
  };
3794
3859
  };
3795
- const coverages = Array.isArray(candidate.coverages) ? candidate.coverages.map((coverage) => {
3860
+ const coverages = base.coverages.length > 0 ? base.coverages : Array.isArray(candidate.coverages) ? candidate.coverages.map((coverage) => {
3796
3861
  const record = coverage;
3797
3862
  const limits = Array.isArray(record.limits) ? record.limits.filter(
3798
3863
  (term) => Boolean(term) && typeof term === "object" && !Array.isArray(term)