@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.mjs CHANGED
@@ -2944,6 +2944,9 @@ function cleanValue(value) {
2944
2944
  if (!value) return void 0;
2945
2945
  return normalizeWhitespace3(value.replace(/^[\s:;#-]+|[\s;,.]+$/g, ""));
2946
2946
  }
2947
+ function cleanCoverageLabel(value) {
2948
+ return cleanValue(value)?.replace(/^column\s+\d+\s*:\s*/i, "");
2949
+ }
2947
2950
  function moneyValue(value) {
2948
2951
  const clean = cleanValue(value);
2949
2952
  if (!clean) return void 0;
@@ -3000,13 +3003,17 @@ function inferDocumentType(nodes) {
3000
3003
  return "policy";
3001
3004
  }
3002
3005
  function coverageNameFromRow(text) {
3003
- const labelled = text.match(/\b(?:coverage|coverage part|line)\s*:?\s*([^|;$]{3,80})/i)?.[1];
3004
- if (labelled) return cleanValue(labelled);
3005
- const parts = text.split(/\s+\|\s+| {2,}|\t/).map(cleanValue).filter(Boolean);
3006
+ const labelled = text.match(/\b(coverage part|coverage|line)\s*:?\s*([^|;$]{3,80})/i);
3007
+ if (labelled) {
3008
+ const value = cleanCoverageLabel(labelled[2]);
3009
+ if (!value) return void 0;
3010
+ return /^coverage part$/i.test(labelled[1]) ? `Coverage Part ${value}` : value;
3011
+ }
3012
+ const parts = text.split(/\s+\|\s+| {2,}|\t/).map(cleanCoverageLabel).filter(Boolean);
3006
3013
  const first = parts.find(
3007
3014
  (part) => !/^(limit|limits?|deductible|premium|amount|basis|rate|retroactive|aggregate|each occurrence)$/i.test(part) && !/^\$?[\d,]+/.test(part)
3008
3015
  );
3009
- return cleanValue(first);
3016
+ return cleanCoverageLabel(first);
3010
3017
  }
3011
3018
  function limitFromText(text) {
3012
3019
  return moneyValue(
@@ -3123,21 +3130,40 @@ function endorsementAncestor(node, byId) {
3123
3130
  function endorsementNumberFrom(value) {
3124
3131
  return cleanValue(value?.match(/\bendorsement\s+no\.?\s*([0-9A-Z-]+)/i)?.[1]);
3125
3132
  }
3133
+ function endorsementNameFrom(node) {
3134
+ const candidates = [node.title, node.textExcerpt, node.description, nodeText(node)].filter(Boolean);
3135
+ for (const candidate of candidates) {
3136
+ const match = candidate.match(/\bendorsement\s+no\.?\s*([0-9A-Z-]+)\s*[—-]\s*(.{3,140}?)(?=\s+This\s+endorsement\b|\.|$)/i);
3137
+ if (match) return cleanValue(`Endorsement No. ${match[1]} - ${match[2]}`) ?? node.title;
3138
+ }
3139
+ return node.title;
3140
+ }
3126
3141
  function sourceIds(nodes) {
3127
3142
  return {
3128
3143
  sourceNodeIds: [...new Set(nodes.map((node) => node.id))],
3129
3144
  sourceSpanIds: [...new Set(nodes.flatMap((node) => node.sourceSpanIds))]
3130
3145
  };
3131
3146
  }
3147
+ function relabelGenericTerm(term, label) {
3148
+ const cleanLabel = cleanValue(label);
3149
+ if (!cleanLabel || !/^column\s+\d+$/i.test(term.label)) return term;
3150
+ return {
3151
+ ...term,
3152
+ kind: termKind(cleanLabel, term.value),
3153
+ label: cleanLabel
3154
+ };
3155
+ }
3132
3156
  function termFromCell(params) {
3133
3157
  const value = cleanValue(params.value);
3134
3158
  if (!value) return void 0;
3135
3159
  const nodes = [params.row, params.cell].filter((node) => Boolean(node));
3160
+ const kind = termKind(params.label, value);
3161
+ const amount = moneyAmount(value);
3136
3162
  return {
3137
- kind: termKind(params.label, value),
3163
+ kind,
3138
3164
  label: params.label,
3139
3165
  value,
3140
- ...moneyAmount(value) !== void 0 ? { amount: moneyAmount(value) } : {},
3166
+ ...amount !== void 0 && kind !== "retroactive_date" ? { amount } : {},
3141
3167
  ...params.appliesTo ? { appliesTo: params.appliesTo } : {},
3142
3168
  ...sourceIds(nodes)
3143
3169
  };
@@ -3174,7 +3200,7 @@ function nameFromRow(row, children) {
3174
3200
  const cells = cellRows(row, children);
3175
3201
  const named = cells.find((cell) => isNameCell(cell.label, cell.value));
3176
3202
  if (named) return cleanValue(named.value);
3177
- return coverageNameFromRow(nodeText(row));
3203
+ return coverageNameFromRow(row.textExcerpt ?? row.description ?? nodeText(row));
3178
3204
  }
3179
3205
  function legacyLimit(terms) {
3180
3206
  return terms.find(
@@ -3190,6 +3216,22 @@ function legacyPremium(terms) {
3190
3216
  function retroDate(terms) {
3191
3217
  return terms.find((term) => term.kind === "retroactive_date")?.value;
3192
3218
  }
3219
+ function hasCoverageLimitTerm(terms) {
3220
+ return terms.some(
3221
+ (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
3222
+ );
3223
+ }
3224
+ function isOperationalCoverageRow(coverage) {
3225
+ const name = normalizeLabel(coverage.name);
3226
+ if (!hasCoverageLimitTerm(coverage.limits)) return false;
3227
+ if (/^(item\s+\d+|option:|annual policy premium|total premium|premium and payment)\b/i.test(coverage.name)) return false;
3228
+ if (/^(nwc|iso|cg|il|acord)[-\s]?[a-z0-9]/i.test(coverage.name)) return false;
3229
+ if (/\b(forms?|endorsements?|premium|payment|terrorism risk insurance act|tria|erp option|bilateral discovery)\b/i.test(name)) return false;
3230
+ 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)) {
3231
+ return false;
3232
+ }
3233
+ 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);
3234
+ }
3193
3235
  function uniqueTerms(terms) {
3194
3236
  const seen = /* @__PURE__ */ new Set();
3195
3237
  const result = [];
@@ -3227,31 +3269,39 @@ function coverageFromEndorsement(endorsement, children) {
3227
3269
  const terms = uniqueTerms(rows.flatMap(
3228
3270
  (row) => termsFromRow(row, children).map((term) => {
3229
3271
  const appliesTo = nameFromRow(row, children);
3230
- return appliesTo && !/^(each claim limit|aggregate limit|retroactive date)$/i.test(appliesTo) ? { ...term, appliesTo } : term;
3272
+ const labelled = relabelGenericTerm(term, appliesTo);
3273
+ return appliesTo && labelled.label !== appliesTo ? { ...labelled, appliesTo } : labelled;
3231
3274
  })
3232
3275
  ));
3233
3276
  if (terms.length === 0) return void 0;
3234
3277
  const ids = sourceIds([endorsement, ...rows, ...rows.flatMap((row) => directChildren(row, children))]);
3278
+ const name = endorsementNameFrom(endorsement);
3235
3279
  return {
3236
- name: endorsement.title,
3280
+ name,
3237
3281
  limit: legacyLimit(terms),
3238
3282
  deductible: legacyDeductible(terms),
3239
3283
  premium: legacyPremium(terms),
3240
3284
  retroactiveDate: retroDate(terms),
3241
3285
  formNumber: typeof endorsement.metadata?.formNumber === "string" ? endorsement.metadata.formNumber : void 0,
3242
- sectionRef: endorsement.title,
3286
+ sectionRef: name,
3243
3287
  coverageOrigin: "endorsement",
3244
- endorsementNumber: endorsementNumberFrom(endorsement.title),
3288
+ endorsementNumber: endorsementNumberFrom(name),
3245
3289
  limits: terms,
3246
3290
  ...ids
3247
3291
  };
3248
3292
  }
3293
+ function hasDescendantEndorsementWithTableRows(endorsement, children) {
3294
+ return descendants(endorsement, children).some(
3295
+ (node) => node.kind === "endorsement" && descendants(node, children).some((descendant) => descendant.kind === "table_row")
3296
+ );
3297
+ }
3249
3298
  function buildCoverages(nodes) {
3250
3299
  const children = childMap(nodes);
3251
3300
  const byId = new Map(nodes.map((node) => [node.id, node]));
3252
3301
  const coverages = [];
3253
3302
  const seen = /* @__PURE__ */ new Set();
3254
3303
  for (const endorsement of nodes.filter((node) => node.kind === "endorsement")) {
3304
+ if (hasDescendantEndorsementWithTableRows(endorsement, children)) continue;
3255
3305
  const coverage = coverageFromEndorsement(endorsement, children);
3256
3306
  if (!coverage) continue;
3257
3307
  const key = [coverage.name.toLowerCase(), coverage.sourceNodeIds.join(",")].join("|");
@@ -3267,6 +3317,7 @@ function buildCoverages(nodes) {
3267
3317
  if (endorsementAncestor(row, byId)) continue;
3268
3318
  const structured = row.kind === "table_row" ? coverageFromTableRow(row, children, byId) : void 0;
3269
3319
  if (structured) {
3320
+ if (!isOperationalCoverageRow(structured)) continue;
3270
3321
  const key2 = [
3271
3322
  structured.name.toLowerCase(),
3272
3323
  structured.limits.map((term) => `${term.kind}:${term.value}`).join(","),
@@ -3284,6 +3335,20 @@ function buildCoverages(nodes) {
3284
3335
  const deductible = deductibleFromText(text);
3285
3336
  const premium = premiumFromText(text);
3286
3337
  if (!name || !limit && !deductible && !premium) continue;
3338
+ if (!isOperationalCoverageRow({
3339
+ name,
3340
+ limit,
3341
+ deductible,
3342
+ premium,
3343
+ coverageOrigin: "core",
3344
+ limits: [
3345
+ ...limit ? [{ kind: "other", label: "Limit", value: limit, sourceNodeIds: [row.id], sourceSpanIds: row.sourceSpanIds }] : [],
3346
+ ...deductible ? [{ kind: "deductible", label: "Deductible", value: deductible, sourceNodeIds: [row.id], sourceSpanIds: row.sourceSpanIds }] : [],
3347
+ ...premium ? [{ kind: "premium", label: "Premium", value: premium, sourceNodeIds: [row.id], sourceSpanIds: row.sourceSpanIds }] : []
3348
+ ],
3349
+ sourceNodeIds: [row.id],
3350
+ sourceSpanIds: row.sourceSpanIds
3351
+ })) continue;
3287
3352
  const key = [name.toLowerCase(), limit, deductible, premium].join("|");
3288
3353
  if (seen.has(key)) continue;
3289
3354
  seen.add(key);
@@ -3436,7 +3501,7 @@ function mergeOperationalProfile(base, candidate, validNodeIds, validSpanIds) {
3436
3501
  sourceSpanIds
3437
3502
  };
3438
3503
  };
3439
- const coverages = Array.isArray(candidate.coverages) ? candidate.coverages.map((coverage) => {
3504
+ const coverages = base.coverages.length > 0 ? base.coverages : Array.isArray(candidate.coverages) ? candidate.coverages.map((coverage) => {
3440
3505
  const record = coverage;
3441
3506
  const limits = Array.isArray(record.limits) ? record.limits.filter(
3442
3507
  (term) => Boolean(term) && typeof term === "object" && !Array.isArray(term)