@claritylabs/cl-sdk 3.1.19 → 3.1.21
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/README.md +1 -1
- package/dist/application.d.mts +1 -1
- package/dist/application.d.ts +1 -1
- package/dist/application.js +0 -2
- package/dist/application.js.map +1 -1
- package/dist/application.mjs +0 -2
- package/dist/application.mjs.map +1 -1
- package/dist/{index-q8LQXen9.d.mts → index-B2T7Jvir.d.mts} +0 -11
- package/dist/{index-q8LQXen9.d.ts → index-B2T7Jvir.d.ts} +0 -11
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +269 -862
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +269 -862
- package/dist/index.mjs.map +1 -1
- package/dist/storage-sqlite.js +0 -2
- package/dist/storage-sqlite.js.map +1 -1
- package/dist/storage-sqlite.mjs +0 -2
- package/dist/storage-sqlite.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2478,7 +2478,6 @@ var OperationalCoverageLineSchema = import_zod20.z.object({
|
|
|
2478
2478
|
retroactiveDate: import_zod20.z.string().optional(),
|
|
2479
2479
|
formNumber: import_zod20.z.string().optional(),
|
|
2480
2480
|
sectionRef: import_zod20.z.string().optional(),
|
|
2481
|
-
coverageOrigin: import_zod20.z.enum(["core", "endorsement"]).optional(),
|
|
2482
2481
|
endorsementNumber: import_zod20.z.string().optional(),
|
|
2483
2482
|
limits: import_zod20.z.array(OperationalCoverageTermSchema).default([]),
|
|
2484
2483
|
sourceNodeIds: import_zod20.z.array(import_zod20.z.string().min(1)).default([]),
|
|
@@ -2508,7 +2507,6 @@ var PolicyOperationalProfileSchema = import_zod20.z.object({
|
|
|
2508
2507
|
expirationDate: SourceBackedValueSchema.optional(),
|
|
2509
2508
|
retroactiveDate: SourceBackedValueSchema.optional(),
|
|
2510
2509
|
premium: SourceBackedValueSchema.optional(),
|
|
2511
|
-
coverageTypes: import_zod20.z.array(import_zod20.z.string()).default([]),
|
|
2512
2510
|
coverages: import_zod20.z.array(OperationalCoverageLineSchema).default([]),
|
|
2513
2511
|
parties: import_zod20.z.array(OperationalPartySchema).default([]),
|
|
2514
2512
|
endorsementSupport: import_zod20.z.array(OperationalEndorsementSupportSchema).default([]),
|
|
@@ -3625,9 +3623,9 @@ function normalizeDoclingDocument(document, options) {
|
|
|
3625
3623
|
pageTexts.set(page, appendText(pageTexts.get(page), unit.text));
|
|
3626
3624
|
}
|
|
3627
3625
|
const fullText = Array.from({ length: pageCount }, (_, index) => {
|
|
3628
|
-
const
|
|
3629
|
-
const text = pageTexts.get(
|
|
3630
|
-
return text ? `Page ${
|
|
3626
|
+
const pageNumber = index + 1;
|
|
3627
|
+
const text = pageTexts.get(pageNumber)?.trim();
|
|
3628
|
+
return text ? `Page ${pageNumber}
|
|
3631
3629
|
${text}` : "";
|
|
3632
3630
|
}).filter(Boolean).join("\n\n");
|
|
3633
3631
|
const sourceKind = options.sourceKind ?? "policy_pdf";
|
|
@@ -5956,7 +5954,7 @@ function mergeCoverageLimits(existing, incoming) {
|
|
|
5956
5954
|
const merged = mergeShallowPreferPresent(existing, incoming);
|
|
5957
5955
|
const existingCoverages = Array.isArray(existing.coverages) ? existing.coverages : [];
|
|
5958
5956
|
const incomingCoverages = Array.isArray(incoming.coverages) ? incoming.coverages : [];
|
|
5959
|
-
const
|
|
5957
|
+
const coverageKey = (coverage) => keyFromParts(
|
|
5960
5958
|
coverage.name,
|
|
5961
5959
|
coverage.limitType,
|
|
5962
5960
|
coverage.limit,
|
|
@@ -5965,7 +5963,7 @@ function mergeCoverageLimits(existing, incoming) {
|
|
|
5965
5963
|
);
|
|
5966
5964
|
const byKey = /* @__PURE__ */ new Map();
|
|
5967
5965
|
for (const coverage of [...existingCoverages, ...incomingCoverages]) {
|
|
5968
|
-
const key =
|
|
5966
|
+
const key = coverageKey(coverage);
|
|
5969
5967
|
const current = byKey.get(key);
|
|
5970
5968
|
byKey.set(key, current ? mergeShallowPreferPresent(current, coverage) : coverage);
|
|
5971
5969
|
}
|
|
@@ -6938,8 +6936,11 @@ For EACH form, extract:
|
|
|
6938
6936
|
|
|
6939
6937
|
Critical rules:
|
|
6940
6938
|
- Include declarations page sets even if they do not show a standard form number.
|
|
6941
|
-
- Do not classify policy jackets, claim-reporting notices, privacy notices, OFAC notices, terrorism/TRIA notices, sanctions notices, signatures, countersignatures, or marketing/admin pages as declarations.
|
|
6939
|
+
- Do not classify policy jackets, claim-reporting notices, privacy notices, OFAC notices, terrorism/TRIA notices, sanctions notices, signatures, countersignatures, or marketing/admin pages as declarations when they are only administrative content.
|
|
6942
6940
|
- Declarations pages contain policy-specific schedules such as named insured, policy number, policy period, premium, limits, retentions, coverage parts, or forms-and-endorsements schedules.
|
|
6941
|
+
- Declarations pages may be internally itemized as "Item 1.", "Item 2.", etc. Keep the whole itemized declarations set together, including continuation pages for coverage schedules, premium, ERP options, producer information, and forms or endorsements attached at inception.
|
|
6942
|
+
- A page that lists declaration items or form/endorsement schedules remains part of the declarations set even if the bottom of that page also contains countersignature, authorized-representative, or policy-execution language.
|
|
6943
|
+
- When "Forms and Endorsements Attached at Inception" or similar form schedules continue onto the next page, extend the declarations pageEnd through that continuation instead of treating the continuation as a standalone signature page.
|
|
6943
6944
|
- Use original document page numbers, not local chunk page numbers.
|
|
6944
6945
|
- Do not emit duplicate entries for repeated headers/footers.
|
|
6945
6946
|
- Multi-page forms should be represented once with pageStart/pageEnd covering the full span when visible.
|
|
@@ -7416,7 +7417,7 @@ var ExclusionsSchema = import_zod30.z.object({
|
|
|
7416
7417
|
exceptions: import_zod30.z.array(import_zod30.z.string()).optional().describe("Exceptions to the exclusion, if any"),
|
|
7417
7418
|
buybackAvailable: import_zod30.z.boolean().optional().describe("Whether coverage can be bought back via endorsement"),
|
|
7418
7419
|
buybackEndorsement: import_zod30.z.string().optional().describe("Form number of the buyback endorsement if available"),
|
|
7419
|
-
appliesTo: import_zod30.z.array(import_zod30.z.string()).optional().describe("
|
|
7420
|
+
appliesTo: import_zod30.z.array(import_zod30.z.string()).optional().describe("Policy types this exclusion applies to"),
|
|
7420
7421
|
content: import_zod30.z.string().describe("Full verbatim exclusion text"),
|
|
7421
7422
|
pageNumber: import_zod30.z.number().optional().describe("Page number where exclusion appears")
|
|
7422
7423
|
})
|
|
@@ -7433,7 +7434,7 @@ For EACH exclusion, extract:
|
|
|
7433
7434
|
- exceptions: any exceptions to the exclusion (things still covered despite the exclusion)
|
|
7434
7435
|
- buybackAvailable: whether coverage can be purchased back via endorsement
|
|
7435
7436
|
- buybackEndorsement: the form number of the buyback endorsement if known
|
|
7436
|
-
- appliesTo: which
|
|
7437
|
+
- appliesTo: which policy types or lines this exclusion applies to (as an array)
|
|
7437
7438
|
- content: full verbatim exclusion text \u2014 REQUIRED
|
|
7438
7439
|
- pageNumber: page number where the exclusion appears
|
|
7439
7440
|
|
|
@@ -8297,190 +8298,6 @@ async function resolveReferentialCoverages(params) {
|
|
|
8297
8298
|
};
|
|
8298
8299
|
}
|
|
8299
8300
|
|
|
8300
|
-
// src/extraction/coverage-schedule-recovery.ts
|
|
8301
|
-
function textValue(value) {
|
|
8302
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
8303
|
-
}
|
|
8304
|
-
function numberValue2(value) {
|
|
8305
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
8306
|
-
}
|
|
8307
|
-
function normalize(value) {
|
|
8308
|
-
return value.toLowerCase().replace(/&/g, "and").replace(/[^a-z0-9]+/g, " ").trim();
|
|
8309
|
-
}
|
|
8310
|
-
function sourceUnit5(span) {
|
|
8311
|
-
return span.sourceUnit ?? span.metadata?.sourceUnit;
|
|
8312
|
-
}
|
|
8313
|
-
function pageNumber(span) {
|
|
8314
|
-
return span.pageStart ?? span.location?.page ?? span.location?.startPage;
|
|
8315
|
-
}
|
|
8316
|
-
function coveragePages(pageAssignments) {
|
|
8317
|
-
const pages = /* @__PURE__ */ new Set();
|
|
8318
|
-
for (const assignment of pageAssignments) {
|
|
8319
|
-
if (assignment.extractorNames.includes("coverage_limits") || assignment.hasScheduleValues) {
|
|
8320
|
-
pages.add(assignment.localPageNumber);
|
|
8321
|
-
}
|
|
8322
|
-
}
|
|
8323
|
-
return pages;
|
|
8324
|
-
}
|
|
8325
|
-
function parseCurrencyAmount(value) {
|
|
8326
|
-
const match = value.match(/(?:CAD|USD|US)?\s*\$?\s*([0-9][0-9,]*(?:\.\d+)?)/i);
|
|
8327
|
-
if (!match) return void 0;
|
|
8328
|
-
const amount = Number(match[1].replace(/,/g, ""));
|
|
8329
|
-
return Number.isFinite(amount) ? amount : void 0;
|
|
8330
|
-
}
|
|
8331
|
-
function limitTypeFrom(value) {
|
|
8332
|
-
const normalized = normalize(value);
|
|
8333
|
-
if (normalized.includes("/") || normalized.includes("each claim") && normalized.includes("aggregate")) return "scheduled";
|
|
8334
|
-
if (normalized.includes("each claim") || normalized.includes("per claim")) return "per_claim";
|
|
8335
|
-
if (normalized.includes("each occurrence") || normalized.includes("per occurrence")) return "per_occurrence";
|
|
8336
|
-
if (normalized.includes("aggregate")) return "aggregate";
|
|
8337
|
-
if (normalized.includes("shared within") || normalized.includes("within coverage")) return "scheduled";
|
|
8338
|
-
if (normalized.includes("/")) return "scheduled";
|
|
8339
|
-
return void 0;
|
|
8340
|
-
}
|
|
8341
|
-
function limitValueTypeFrom(value) {
|
|
8342
|
-
const normalized = normalize(value);
|
|
8343
|
-
if (parseCurrencyAmount(value) !== void 0) return "numeric";
|
|
8344
|
-
if (normalized.includes("shared within") || normalized.includes("within coverage") || normalized.includes("shown above")) {
|
|
8345
|
-
return "referential";
|
|
8346
|
-
}
|
|
8347
|
-
if (normalized.includes("included")) return "included";
|
|
8348
|
-
if (normalized.includes("not included") || normalized === "nil" || normalized === "none") return "not_included";
|
|
8349
|
-
if (normalized.includes("as stated")) return "as_stated";
|
|
8350
|
-
return "other";
|
|
8351
|
-
}
|
|
8352
|
-
function cleanName(value) {
|
|
8353
|
-
return value.replace(/\s*\([^)]*part of and not in addition to[^)]*\)\s*/gi, " ").replace(/\s+/g, " ").trim().replace(/[.:;-]+$/, "").trim();
|
|
8354
|
-
}
|
|
8355
|
-
function isDeductibleOnly(name, rowText) {
|
|
8356
|
-
const normalizedName = normalize(name);
|
|
8357
|
-
const normalizedRow = normalize(rowText);
|
|
8358
|
-
if (!/\b(deductible|retention|sir)\b/.test(`${normalizedName} ${normalizedRow}`)) return false;
|
|
8359
|
-
if (/\b(coverage|sub limit|sublimit|limit)\b/.test(normalizedName) && !/\b(enhanced|standard)\s+deductible\b/.test(normalizedName)) {
|
|
8360
|
-
return false;
|
|
8361
|
-
}
|
|
8362
|
-
return /\b(enhanced|standard)?\s*(deductible|retention|sir)\b/.test(normalizedName) || /^\s*(deductible|retention|sir)\b/.test(normalizedRow);
|
|
8363
|
-
}
|
|
8364
|
-
function splitRowFields(rowText) {
|
|
8365
|
-
return rowText.split(/\s+\|\s+/).map((part) => part.trim()).filter(Boolean).map((part) => {
|
|
8366
|
-
const match = part.match(/^([^:]{1,100}):\s*(.*)$/);
|
|
8367
|
-
if (!match) return { value: part };
|
|
8368
|
-
return { key: match[1].trim(), value: match[2].trim() };
|
|
8369
|
-
});
|
|
8370
|
-
}
|
|
8371
|
-
function firstField(fields, patterns) {
|
|
8372
|
-
for (const field of fields) {
|
|
8373
|
-
const target = `${field.key ?? ""} ${field.value}`;
|
|
8374
|
-
if (patterns.some((pattern) => pattern.test(target))) return field.value || field.key;
|
|
8375
|
-
}
|
|
8376
|
-
return void 0;
|
|
8377
|
-
}
|
|
8378
|
-
function coverageFromRow(span) {
|
|
8379
|
-
const rowText = span.text.trim();
|
|
8380
|
-
if (!rowText || sourceUnit5(span) !== "table_row") return void 0;
|
|
8381
|
-
if (span.table?.isHeader || span.metadata?.isHeader === "true") return void 0;
|
|
8382
|
-
const fields = splitRowFields(rowText);
|
|
8383
|
-
let name;
|
|
8384
|
-
let limit;
|
|
8385
|
-
for (const field of fields) {
|
|
8386
|
-
const key = field.key?.trim();
|
|
8387
|
-
const value = field.value.trim();
|
|
8388
|
-
if (!key || !value) continue;
|
|
8389
|
-
if (!name && /^coverage$/i.test(key)) name = cleanName(value);
|
|
8390
|
-
if (!limit && /\blimit\b/i.test(key)) limit = value;
|
|
8391
|
-
if (!name && /\b(sub[-\s]?limit|aggregate|each claim)\b/i.test(key) && parseCurrencyAmount(value) !== void 0) {
|
|
8392
|
-
name = cleanName(key);
|
|
8393
|
-
limit = limit ?? value;
|
|
8394
|
-
}
|
|
8395
|
-
}
|
|
8396
|
-
if (!name || !limit) {
|
|
8397
|
-
for (const field of fields) {
|
|
8398
|
-
const key = field.key?.trim();
|
|
8399
|
-
const value = field.value.trim();
|
|
8400
|
-
if (!key || !value) continue;
|
|
8401
|
-
if (!name && /\b(sub[-\s]?limit|coverage|aggregate|each claim)\b/i.test(key) && !parseCurrencyAmount(key)) {
|
|
8402
|
-
name = cleanName(key);
|
|
8403
|
-
limit = limit ?? value;
|
|
8404
|
-
}
|
|
8405
|
-
}
|
|
8406
|
-
}
|
|
8407
|
-
if (!name || !limit) return void 0;
|
|
8408
|
-
if (isDeductibleOnly(name, rowText)) return void 0;
|
|
8409
|
-
const normalizedLimit = normalize(limit);
|
|
8410
|
-
const hasUsableLimit = parseCurrencyAmount(limit) !== void 0 || /\b(shared within|within coverage|as stated|included|not included)\b/i.test(normalizedLimit);
|
|
8411
|
-
if (!hasUsableLimit) return void 0;
|
|
8412
|
-
const deductible = firstField(fields, [/\bdeductible\b/i, /\bretention\b/i, /\bsir\b/i]);
|
|
8413
|
-
const basis = firstField(fields, [/\bbasis\b/i]);
|
|
8414
|
-
const retroactiveDate = firstField(fields, [/\bretroactive date\b/i, /\bretro date\b/i]);
|
|
8415
|
-
const page = pageNumber(span);
|
|
8416
|
-
const limitAmount = parseCurrencyAmount(limit);
|
|
8417
|
-
const deductibleAmount = deductible ? parseCurrencyAmount(deductible) : void 0;
|
|
8418
|
-
return {
|
|
8419
|
-
name,
|
|
8420
|
-
limit,
|
|
8421
|
-
...limitAmount !== void 0 ? { limitAmount } : {},
|
|
8422
|
-
...limitTypeFrom(`${name} ${limit}`) ? { limitType: limitTypeFrom(`${name} ${limit}`) } : {},
|
|
8423
|
-
limitValueType: limitValueTypeFrom(limit),
|
|
8424
|
-
...deductible && !/^nil|none$/i.test(deductible.trim()) ? { deductible } : {},
|
|
8425
|
-
...deductibleAmount !== void 0 ? { deductibleAmount } : {},
|
|
8426
|
-
...deductible ? { deductibleValueType: limitValueTypeFrom(deductible) } : {},
|
|
8427
|
-
...basis && /claims[- ]made/i.test(basis) ? { trigger: "claims_made" } : {},
|
|
8428
|
-
...retroactiveDate ? { retroactiveDate } : {},
|
|
8429
|
-
...span.formNumber ? { formNumber: span.formNumber } : {},
|
|
8430
|
-
...page ? { pageNumber: page } : {},
|
|
8431
|
-
sectionRef: span.sectionId ?? "SCHEDULE",
|
|
8432
|
-
originalContent: rowText,
|
|
8433
|
-
sourceSpanIds: [span.id],
|
|
8434
|
-
sourceTextHash: span.textHash ?? span.hash
|
|
8435
|
-
};
|
|
8436
|
-
}
|
|
8437
|
-
function coverageKey(coverage) {
|
|
8438
|
-
return [
|
|
8439
|
-
textValue(coverage.name) ?? "",
|
|
8440
|
-
textValue(coverage.limit) ?? "",
|
|
8441
|
-
textValue(coverage.limitType) ?? "",
|
|
8442
|
-
numberValue2(coverage.pageNumber) ?? ""
|
|
8443
|
-
].map((part) => normalize(String(part))).join("|");
|
|
8444
|
-
}
|
|
8445
|
-
function rowMatchesExisting(row, existing) {
|
|
8446
|
-
const rowKey = coverageKey(row);
|
|
8447
|
-
const rowName = normalize(textValue(row.name) ?? "");
|
|
8448
|
-
const rowLimit = normalize(textValue(row.limit) ?? "");
|
|
8449
|
-
return existing.some((coverage) => {
|
|
8450
|
-
if (coverageKey(coverage) === rowKey) return true;
|
|
8451
|
-
const name = normalize(textValue(coverage.name) ?? "");
|
|
8452
|
-
const limit = normalize(textValue(coverage.limit) ?? "");
|
|
8453
|
-
return Boolean(rowName && rowLimit && name === rowName && limit === rowLimit);
|
|
8454
|
-
});
|
|
8455
|
-
}
|
|
8456
|
-
function recoverCoverageScheduleRows(params) {
|
|
8457
|
-
const payload = params.memory.get("coverage_limits");
|
|
8458
|
-
const existing = Array.isArray(payload?.coverages) ? payload.coverages : [];
|
|
8459
|
-
const pages = coveragePages(params.pageAssignments);
|
|
8460
|
-
const candidates = params.sourceSpans.filter((span) => sourceUnit5(span) === "table_row").filter((span) => {
|
|
8461
|
-
const page = pageNumber(span);
|
|
8462
|
-
return page !== void 0 && pages.has(page);
|
|
8463
|
-
}).map(coverageFromRow).filter((coverage) => Boolean(coverage));
|
|
8464
|
-
const recovered = [];
|
|
8465
|
-
for (const coverage of candidates) {
|
|
8466
|
-
if (rowMatchesExisting(coverage, [...existing, ...recovered])) continue;
|
|
8467
|
-
recovered.push(coverage);
|
|
8468
|
-
}
|
|
8469
|
-
if (recovered.length > 0) {
|
|
8470
|
-
params.memory.set("coverage_limits", {
|
|
8471
|
-
...payload ?? {},
|
|
8472
|
-
coverages: [...existing, ...recovered]
|
|
8473
|
-
});
|
|
8474
|
-
}
|
|
8475
|
-
return {
|
|
8476
|
-
recovered,
|
|
8477
|
-
missingSourceRows: recovered.map((coverage) => {
|
|
8478
|
-
const id = Array.isArray(coverage.sourceSpanIds) ? coverage.sourceSpanIds[0] : void 0;
|
|
8479
|
-
return params.sourceSpans.find((span) => span.id === id);
|
|
8480
|
-
}).filter((span) => Boolean(span))
|
|
8481
|
-
};
|
|
8482
|
-
}
|
|
8483
|
-
|
|
8484
8301
|
// src/extraction/focused-dispatch.ts
|
|
8485
8302
|
async function runFocusedExtractorWithFallback(params) {
|
|
8486
8303
|
const {
|
|
@@ -8683,29 +8500,6 @@ function buildExtractionReviewReport(params) {
|
|
|
8683
8500
|
addMissingSourceGroundingIssues(deterministicIssues, "definitions", "definitions", definitions, "term");
|
|
8684
8501
|
addMissingSourceGroundingIssues(deterministicIssues, "covered_reasons", "coveredReasons", coveredReasons, "name");
|
|
8685
8502
|
}
|
|
8686
|
-
if (params.sourceSpans?.length) {
|
|
8687
|
-
const tempCoveragePayload = {
|
|
8688
|
-
...memory.get("coverage_limits") ?? {},
|
|
8689
|
-
coverages: coverages.map((coverage) => ({ ...coverage }))
|
|
8690
|
-
};
|
|
8691
|
-
const tempMemory = new Map(memory);
|
|
8692
|
-
tempMemory.set("coverage_limits", tempCoveragePayload);
|
|
8693
|
-
const scheduleRecovery = recoverCoverageScheduleRows({
|
|
8694
|
-
memory: tempMemory,
|
|
8695
|
-
sourceSpans: params.sourceSpans,
|
|
8696
|
-
pageAssignments: params.pageAssignments
|
|
8697
|
-
});
|
|
8698
|
-
for (const recovered of scheduleRecovery.recovered) {
|
|
8699
|
-
deterministicIssues.push({
|
|
8700
|
-
code: "coverage_schedule_row_missing",
|
|
8701
|
-
severity: "blocking",
|
|
8702
|
-
message: `Coverage schedule row "${String(recovered.name ?? "unknown")}" is present in source table evidence but missing from extracted coverages.`,
|
|
8703
|
-
extractorName: "coverage_limits",
|
|
8704
|
-
pageNumber: typeof recovered.pageNumber === "number" ? recovered.pageNumber : void 0,
|
|
8705
|
-
itemName: typeof recovered.name === "string" ? recovered.name : void 0
|
|
8706
|
-
});
|
|
8707
|
-
}
|
|
8708
|
-
}
|
|
8709
8503
|
if (mappedDefinitions && definitions.length === 0) {
|
|
8710
8504
|
deterministicIssues.push({
|
|
8711
8505
|
code: "definitions_mapped_but_empty",
|
|
@@ -9148,17 +8942,17 @@ var ARRAY_PATHS = [
|
|
|
9148
8942
|
{ memoryKey: "supplementary", arrayKeys: ["auxiliaryFacts", "supplementaryFacts"] },
|
|
9149
8943
|
{ memoryKey: "form_inventory", arrayKeys: ["forms"] }
|
|
9150
8944
|
];
|
|
9151
|
-
function
|
|
8945
|
+
function normalize(value) {
|
|
9152
8946
|
return value.replace(/\s+/g, " ").trim().toLowerCase();
|
|
9153
8947
|
}
|
|
9154
|
-
function
|
|
8948
|
+
function textValue(record, ...keys) {
|
|
9155
8949
|
for (const key of keys) {
|
|
9156
8950
|
const value = record[key];
|
|
9157
8951
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
9158
8952
|
}
|
|
9159
8953
|
return void 0;
|
|
9160
8954
|
}
|
|
9161
|
-
function
|
|
8955
|
+
function numberValue2(record, ...keys) {
|
|
9162
8956
|
for (const key of keys) {
|
|
9163
8957
|
const value = record[key];
|
|
9164
8958
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
@@ -9175,27 +8969,27 @@ function pageOverlaps(recordStart, recordEnd, span) {
|
|
|
9175
8969
|
return start <= (spanEnd ?? spanStart) && end >= spanStart;
|
|
9176
8970
|
}
|
|
9177
8971
|
function formMatches(record, span) {
|
|
9178
|
-
const formNumber =
|
|
8972
|
+
const formNumber = textValue(record, "formNumber");
|
|
9179
8973
|
if (!formNumber || !span.formNumber) return false;
|
|
9180
|
-
return
|
|
8974
|
+
return normalize(formNumber) === normalize(span.formNumber);
|
|
9181
8975
|
}
|
|
9182
8976
|
function textMatches(record, span) {
|
|
9183
|
-
const spanText =
|
|
8977
|
+
const spanText = normalize(span.text);
|
|
9184
8978
|
const candidates = [
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
8979
|
+
textValue(record, "originalContent", "content", "definition", "value"),
|
|
8980
|
+
textValue(record, "name", "title", "term", "field", "coverageName"),
|
|
8981
|
+
textValue(record, "limit", "deductible", "premium")
|
|
9188
8982
|
].filter((value) => !!value && value.length >= 3);
|
|
9189
|
-
return candidates.some((candidate) => spanText.includes(
|
|
8983
|
+
return candidates.some((candidate) => spanText.includes(normalize(candidate)));
|
|
9190
8984
|
}
|
|
9191
8985
|
function sourceHashFor(spans) {
|
|
9192
8986
|
return spans.map((span) => span.textHash ?? span.hash).filter(Boolean).join(":") || void 0;
|
|
9193
8987
|
}
|
|
9194
|
-
function
|
|
8988
|
+
function sourceUnit5(span) {
|
|
9195
8989
|
return span.sourceUnit ?? span.metadata?.sourceUnit;
|
|
9196
8990
|
}
|
|
9197
8991
|
function hierarchyScore(span) {
|
|
9198
|
-
switch (
|
|
8992
|
+
switch (sourceUnit5(span)) {
|
|
9199
8993
|
case "table_row":
|
|
9200
8994
|
return 5;
|
|
9201
8995
|
case "table":
|
|
@@ -9218,8 +9012,8 @@ function preferParentRows(matches, sourceSpans) {
|
|
|
9218
9012
|
const expanded = [];
|
|
9219
9013
|
const seen = /* @__PURE__ */ new Set();
|
|
9220
9014
|
for (const match of matches) {
|
|
9221
|
-
const parent =
|
|
9222
|
-
const preferred = parent &&
|
|
9015
|
+
const parent = sourceUnit5(match) === "table_cell" ? byId.get(parentRowId(match) ?? "") : void 0;
|
|
9016
|
+
const preferred = parent && sourceUnit5(parent) === "table_row" ? parent : match;
|
|
9223
9017
|
if (seen.has(preferred.id)) continue;
|
|
9224
9018
|
seen.add(preferred.id);
|
|
9225
9019
|
expanded.push(preferred);
|
|
@@ -9228,8 +9022,8 @@ function preferParentRows(matches, sourceSpans) {
|
|
|
9228
9022
|
}
|
|
9229
9023
|
function findSourceSpansForRecord(record, sourceSpans) {
|
|
9230
9024
|
if (sourceSpans.length === 0) return [];
|
|
9231
|
-
const pageStart2 =
|
|
9232
|
-
const pageEnd2 =
|
|
9025
|
+
const pageStart2 = numberValue2(record, "pageNumber", "pageStart");
|
|
9026
|
+
const pageEnd2 = numberValue2(record, "pageNumber", "pageEnd");
|
|
9233
9027
|
const scored = sourceSpans.map((span) => {
|
|
9234
9028
|
let score = 0;
|
|
9235
9029
|
if (pageOverlaps(pageStart2, pageEnd2, span)) score += 4;
|
|
@@ -9371,7 +9165,6 @@ function mergeOperationalProfile(base, candidate, validNodeIds, validSpanIds) {
|
|
|
9371
9165
|
retroactiveDate: typeof record.retroactiveDate === "string" ? cleanValue(record.retroactiveDate) : void 0,
|
|
9372
9166
|
formNumber: typeof record.formNumber === "string" ? cleanValue(record.formNumber) : void 0,
|
|
9373
9167
|
sectionRef: typeof record.sectionRef === "string" ? cleanValue(record.sectionRef) : void 0,
|
|
9374
|
-
coverageOrigin: record.coverageOrigin === "core" || record.coverageOrigin === "endorsement" ? record.coverageOrigin : void 0,
|
|
9375
9168
|
endorsementNumber: typeof record.endorsementNumber === "string" ? cleanValue(record.endorsementNumber) : void 0,
|
|
9376
9169
|
limits,
|
|
9377
9170
|
sourceNodeIds: sourceNodeIds2,
|
|
@@ -9457,7 +9250,6 @@ function mergeOperationalProfile(base, candidate, validNodeIds, validSpanIds) {
|
|
|
9457
9250
|
expirationDate,
|
|
9458
9251
|
retroactiveDate,
|
|
9459
9252
|
premium,
|
|
9460
|
-
coverageTypes: Array.isArray(candidate.coverageTypes) && candidate.coverageTypes.length > 0 ? candidate.coverageTypes : base.coverageTypes,
|
|
9461
9253
|
coverages,
|
|
9462
9254
|
parties,
|
|
9463
9255
|
endorsementSupport,
|
|
@@ -9492,7 +9284,6 @@ var OperationalProfileCleanupSchema = import_zod41.z.object({
|
|
|
9492
9284
|
deductible: import_zod41.z.string().nullable().optional(),
|
|
9493
9285
|
premium: import_zod41.z.string().nullable().optional(),
|
|
9494
9286
|
retroactiveDate: import_zod41.z.string().nullable().optional(),
|
|
9495
|
-
coverageOrigin: import_zod41.z.enum(["core", "endorsement"]).optional(),
|
|
9496
9287
|
sourceNodeIds: import_zod41.z.array(import_zod41.z.string()).optional(),
|
|
9497
9288
|
sourceSpanIds: import_zod41.z.array(import_zod41.z.string()).optional(),
|
|
9498
9289
|
termDecisions: import_zod41.z.array(import_zod41.z.object({
|
|
@@ -9537,7 +9328,6 @@ function compactCoverageForCleanup(coverage, coverageIndex) {
|
|
|
9537
9328
|
deductible: coverage.deductible,
|
|
9538
9329
|
premium: coverage.premium,
|
|
9539
9330
|
retroactiveDate: coverage.retroactiveDate,
|
|
9540
|
-
coverageOrigin: coverage.coverageOrigin,
|
|
9541
9331
|
sourceNodeIds: compactIds(coverage.sourceNodeIds),
|
|
9542
9332
|
sourceSpanIds: compactIds(coverage.sourceSpanIds),
|
|
9543
9333
|
terms: coverage.limits.map((term, termIndex) => ({
|
|
@@ -9613,7 +9403,7 @@ function selectCoverageCleanupNodes(sourceTree, coverages) {
|
|
|
9613
9403
|
...coverage.sourceNodeIds,
|
|
9614
9404
|
...coverage.limits.flatMap((term) => term.sourceNodeIds)
|
|
9615
9405
|
]));
|
|
9616
|
-
const
|
|
9406
|
+
const coveragePages = /* @__PURE__ */ new Set();
|
|
9617
9407
|
const coverageTerms = uniqueStrings(coverages.flatMap(
|
|
9618
9408
|
(coverage) => coverageTextForSelection(coverage).toLowerCase().split(/[^a-z0-9$,.]+/i).filter((part) => part.length >= 5)
|
|
9619
9409
|
));
|
|
@@ -9621,8 +9411,8 @@ function selectCoverageCleanupNodes(sourceTree, coverages) {
|
|
|
9621
9411
|
const node = nodeById.get(id);
|
|
9622
9412
|
if (!node) continue;
|
|
9623
9413
|
addNode(node, 1e3);
|
|
9624
|
-
if (node.pageStart)
|
|
9625
|
-
if (node.pageEnd)
|
|
9414
|
+
if (node.pageStart) coveragePages.add(node.pageStart);
|
|
9415
|
+
if (node.pageEnd) coveragePages.add(node.pageEnd);
|
|
9626
9416
|
let parentId = node.parentId;
|
|
9627
9417
|
let parentScore = 940;
|
|
9628
9418
|
while (parentId) {
|
|
@@ -9645,7 +9435,7 @@ function selectCoverageCleanupNodes(sourceTree, coverages) {
|
|
|
9645
9435
|
}
|
|
9646
9436
|
for (const node of sourceTree) {
|
|
9647
9437
|
if (node.kind === "document") continue;
|
|
9648
|
-
if (!node.pageStart || !
|
|
9438
|
+
if (!node.pageStart || !coveragePages.has(node.pageStart)) continue;
|
|
9649
9439
|
const text = nodeTextForSelection(node);
|
|
9650
9440
|
if (CLEANUP_KEYWORD.test(text) || nodeTextMatchesCoverage(node, coverageTerms)) {
|
|
9651
9441
|
addNode(node, 600);
|
|
@@ -9666,7 +9456,6 @@ function buildOperationalProfileCleanupPrompt(sourceTree, profile, options = {})
|
|
|
9666
9456
|
const candidate = {
|
|
9667
9457
|
documentType: profile.documentType,
|
|
9668
9458
|
policyTypes: profile.policyTypes,
|
|
9669
|
-
coverageTypes: profile.coverageTypes,
|
|
9670
9459
|
coverages: coverageEntries.map(({ coverage, coverageIndex }) => compactCoverageForCleanup(coverage, coverageIndex))
|
|
9671
9460
|
};
|
|
9672
9461
|
return `Review and clean a source-backed operational profile projection for an insurance policy.
|
|
@@ -9852,7 +9641,6 @@ function applyCoverageCleanupDecision(coverage, decision, validNodeIds, validSpa
|
|
|
9852
9641
|
};
|
|
9853
9642
|
const name = cleanProfileValue(decision.name);
|
|
9854
9643
|
if (name) next.name = name;
|
|
9855
|
-
if (decision.coverageOrigin) next.coverageOrigin = decision.coverageOrigin;
|
|
9856
9644
|
if (decision.limit != null) {
|
|
9857
9645
|
const value = cleanProfileValue(decision.limit);
|
|
9858
9646
|
if (value) next.limit = value;
|
|
@@ -9948,7 +9736,6 @@ function applyOperationalProfileCleanup(profile, cleanup, validNodeIds, validSpa
|
|
|
9948
9736
|
const nextProfile = {
|
|
9949
9737
|
...profile,
|
|
9950
9738
|
coverages,
|
|
9951
|
-
coverageTypes: uniqueStrings(coverages.map((coverage) => coverage.name)),
|
|
9952
9739
|
warnings: uniqueStrings([
|
|
9953
9740
|
...profile.warnings,
|
|
9954
9741
|
...cleanupWarnings.map((warning) => `Operational profile cleanup warning: ${warning}`)
|
|
@@ -9969,9 +9756,9 @@ var ORGANIZABLE_KINDS = [
|
|
|
9969
9756
|
"schedule",
|
|
9970
9757
|
"clause"
|
|
9971
9758
|
];
|
|
9972
|
-
var ORGANIZATION_TOP_LEVEL_BATCH_SIZE =
|
|
9973
|
-
var
|
|
9974
|
-
var
|
|
9759
|
+
var ORGANIZATION_TOP_LEVEL_BATCH_SIZE = 12;
|
|
9760
|
+
var ORGANIZER_MAX_TOP_LEVEL_NODES = 120;
|
|
9761
|
+
var ORGANIZER_MAX_BATCH_NODES = 180;
|
|
9975
9762
|
var OUTLINE_CLEANUP_MAX_TOP_LEVEL_NODES = 80;
|
|
9976
9763
|
var SourceTreeOrganizationSchema = import_zod42.z.object({
|
|
9977
9764
|
labels: import_zod42.z.array(import_zod42.z.object({
|
|
@@ -9997,26 +9784,10 @@ var SourceTreeOrganizationSchema = import_zod42.z.object({
|
|
|
9997
9784
|
kind: import_zod42.z.enum(ORGANIZABLE_KINDS),
|
|
9998
9785
|
title: import_zod42.z.string(),
|
|
9999
9786
|
description: import_zod42.z.string().optional(),
|
|
9787
|
+
parentNodeId: import_zod42.z.string().optional(),
|
|
10000
9788
|
childNodeIds: import_zod42.z.array(import_zod42.z.string()).min(1)
|
|
10001
9789
|
}))
|
|
10002
9790
|
});
|
|
10003
|
-
var SourceTreeVisualTableRepairSchema = import_zod42.z.object({
|
|
10004
|
-
tables: import_zod42.z.array(import_zod42.z.object({
|
|
10005
|
-
tableNodeId: import_zod42.z.string(),
|
|
10006
|
-
columnLabels: import_zod42.z.array(import_zod42.z.object({
|
|
10007
|
-
columnIndex: import_zod42.z.number().int().nonnegative(),
|
|
10008
|
-
label: import_zod42.z.string()
|
|
10009
|
-
})).default([]),
|
|
10010
|
-
continuationRows: import_zod42.z.array(import_zod42.z.object({
|
|
10011
|
-
sourceRowNodeId: import_zod42.z.string(),
|
|
10012
|
-
targetRowNodeId: import_zod42.z.string(),
|
|
10013
|
-
targetColumnIndex: import_zod42.z.number().int().nonnegative().optional(),
|
|
10014
|
-
targetColumnLabel: import_zod42.z.string().optional(),
|
|
10015
|
-
reason: import_zod42.z.string().optional()
|
|
10016
|
-
})).default([])
|
|
10017
|
-
})).default([]),
|
|
10018
|
-
warnings: import_zod42.z.array(import_zod42.z.string()).default([])
|
|
10019
|
-
});
|
|
10020
9791
|
var SourceBackedValueForPromptSchema = import_zod42.z.object({
|
|
10021
9792
|
value: import_zod42.z.string(),
|
|
10022
9793
|
normalizedValue: import_zod42.z.string().optional(),
|
|
@@ -10035,7 +9806,6 @@ var OperationalProfilePromptSchema = import_zod42.z.object({
|
|
|
10035
9806
|
expirationDate: SourceBackedValueForPromptSchema.optional(),
|
|
10036
9807
|
retroactiveDate: SourceBackedValueForPromptSchema.optional(),
|
|
10037
9808
|
premium: SourceBackedValueForPromptSchema.optional(),
|
|
10038
|
-
coverageTypes: import_zod42.z.array(import_zod42.z.string()).optional(),
|
|
10039
9809
|
coverages: import_zod42.z.array(import_zod42.z.object({
|
|
10040
9810
|
name: import_zod42.z.string(),
|
|
10041
9811
|
coverageCode: import_zod42.z.string().optional(),
|
|
@@ -10045,7 +9815,6 @@ var OperationalProfilePromptSchema = import_zod42.z.object({
|
|
|
10045
9815
|
retroactiveDate: import_zod42.z.string().optional(),
|
|
10046
9816
|
formNumber: import_zod42.z.string().optional(),
|
|
10047
9817
|
sectionRef: import_zod42.z.string().optional(),
|
|
10048
|
-
coverageOrigin: import_zod42.z.enum(["core", "endorsement"]).optional(),
|
|
10049
9818
|
endorsementNumber: import_zod42.z.string().optional(),
|
|
10050
9819
|
limits: import_zod42.z.array(import_zod42.z.object({
|
|
10051
9820
|
kind: OperationalCoverageTermKindSchema.optional(),
|
|
@@ -10129,6 +9898,12 @@ function endorsementDescription(title, node) {
|
|
|
10129
9898
|
title
|
|
10130
9899
|
);
|
|
10131
9900
|
}
|
|
9901
|
+
function endorsementTitleKey(node) {
|
|
9902
|
+
const title = endorsementTitle(sourceNodeText(node));
|
|
9903
|
+
if (title) return title.toLowerCase();
|
|
9904
|
+
const fallback = cleanText(node.title, "");
|
|
9905
|
+
return fallback ? fallback.toLowerCase() : void 0;
|
|
9906
|
+
}
|
|
10132
9907
|
function nodePageEnd2(node) {
|
|
10133
9908
|
return node.pageEnd ?? node.pageStart;
|
|
10134
9909
|
}
|
|
@@ -10229,13 +10004,18 @@ function pageHeadingTitleFromText(text, fallback) {
|
|
|
10229
10004
|
return fallback;
|
|
10230
10005
|
}
|
|
10231
10006
|
function pageFormTypeFromText(text) {
|
|
10007
|
+
if (hasSubstantiveDeclarationsScheduleText(text)) return "declarations";
|
|
10232
10008
|
if (/\b(declarations?\s+page|declarations?\s+schedule)\b/i.test(text)) return "declarations";
|
|
10233
10009
|
if (/\b(endorsement\s+(?:no\.?|number|#)|this endorsement changes the policy|[A-Z]{2,}-END\s+\d{2,})\b/i.test(text)) return "endorsement";
|
|
10234
10010
|
if (/\b(technology errors?\s*&?\s*omissions.*liability insurance policy|policy form|coverage form|insuring agreement|definitions?|exclusions?|conditions?)\b/i.test(text)) return "coverage";
|
|
10235
10011
|
if (/\b(important notice|privacy notice|ofac advisory|terrorism risk insurance act|tria|trade or economic sanctions)\b/i.test(text)) return "notice";
|
|
10236
10012
|
return "other";
|
|
10237
10013
|
}
|
|
10014
|
+
function hasSubstantiveDeclarationsScheduleText(text) {
|
|
10015
|
+
return /\bitem\s+\d+\.?\s*(?:named insured|policy number|policy period|renewal|form of business|coverage parts?|limits?|premium|extended reporting|producer|forms? and endorsements?)\b/i.test(text) || /\bforms? and endorsements attached at inception\b/i.test(text) || /\bcoverage parts?,?\s+limits? of liability,?\s+deductibles?,?\s+and retroactive dates\b/i.test(text) || /\bannual premium\s*\(all coverage parts?\)\b/i.test(text) || /\berp option\b/i.test(text) || /\bproducer\b[\s\S]{0,240}\blicense\b/i.test(text);
|
|
10016
|
+
}
|
|
10238
10017
|
function administrativeFormTypeFromText(text) {
|
|
10018
|
+
if (hasSubstantiveDeclarationsScheduleText(text)) return void 0;
|
|
10239
10019
|
if (/\b(important notice|privacy notice|ofac advisory|terrorism risk insurance act|tria|trade or economic sanctions|economic sanctions limitation|how to report a claim)\b/i.test(text)) {
|
|
10240
10020
|
return "notice";
|
|
10241
10021
|
}
|
|
@@ -10636,6 +10416,7 @@ function isDeclarationsNode(node) {
|
|
|
10636
10416
|
}
|
|
10637
10417
|
function isAdministrativeNoticeNode(node) {
|
|
10638
10418
|
const text = sourceNodeText(node);
|
|
10419
|
+
if (hasSubstantiveDeclarationsScheduleText(text)) return false;
|
|
10639
10420
|
return /\b(specimen policy|policy jacket|important notice|privacy notice|ofac advisory|terrorism risk insurance act|tria|trade or economic sanctions|economic sanctions limitation|signature|countersignature|how to report a claim)\b/i.test(text);
|
|
10640
10421
|
}
|
|
10641
10422
|
function mergeAdministrativeNoticesIntoFrontMatter(sourceTree) {
|
|
@@ -11050,7 +10831,76 @@ function applyEndorsementGrouping(sourceTree) {
|
|
|
11050
10831
|
(node) => endorsementGroupChildIds.has(node.id) ? { ...node, parentId: groupId, order: node.order + 1e-3 } : node
|
|
11051
10832
|
);
|
|
11052
10833
|
}
|
|
11053
|
-
return normalizeSemanticHierarchy(nextTree);
|
|
10834
|
+
return collapseNestedDuplicateEndorsements(normalizeSemanticHierarchy(nextTree));
|
|
10835
|
+
}
|
|
10836
|
+
function nearestEndorsementAncestor(node, byId) {
|
|
10837
|
+
let parentId = node.parentId;
|
|
10838
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10839
|
+
while (parentId) {
|
|
10840
|
+
if (seen.has(parentId)) return void 0;
|
|
10841
|
+
seen.add(parentId);
|
|
10842
|
+
const parent = byId.get(parentId);
|
|
10843
|
+
if (!parent) return void 0;
|
|
10844
|
+
if (parent.kind === "endorsement") return parent;
|
|
10845
|
+
parentId = parent.parentId;
|
|
10846
|
+
}
|
|
10847
|
+
return void 0;
|
|
10848
|
+
}
|
|
10849
|
+
function collapseNestedDuplicateEndorsements(sourceTree) {
|
|
10850
|
+
const byId = new Map(sourceTree.map((node) => [node.id, node]));
|
|
10851
|
+
const replacementById = /* @__PURE__ */ new Map();
|
|
10852
|
+
const duplicateEvidenceByTarget = /* @__PURE__ */ new Map();
|
|
10853
|
+
for (const node of sourceTree) {
|
|
10854
|
+
if (node.kind !== "endorsement") continue;
|
|
10855
|
+
const ancestor = nearestEndorsementAncestor(node, byId);
|
|
10856
|
+
if (!ancestor) continue;
|
|
10857
|
+
if (endorsementTitleKey(node) !== endorsementTitleKey(ancestor)) continue;
|
|
10858
|
+
replacementById.set(node.id, ancestor.id);
|
|
10859
|
+
duplicateEvidenceByTarget.set(ancestor.id, [
|
|
10860
|
+
...duplicateEvidenceByTarget.get(ancestor.id) ?? [],
|
|
10861
|
+
node
|
|
10862
|
+
]);
|
|
10863
|
+
}
|
|
10864
|
+
if (replacementById.size === 0) return sourceTree;
|
|
10865
|
+
const replacementParent = (parentId) => {
|
|
10866
|
+
let next = parentId;
|
|
10867
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10868
|
+
while (next && replacementById.has(next) && !seen.has(next)) {
|
|
10869
|
+
seen.add(next);
|
|
10870
|
+
next = replacementById.get(next);
|
|
10871
|
+
}
|
|
10872
|
+
return next;
|
|
10873
|
+
};
|
|
10874
|
+
const updated = sourceTree.filter((node) => !replacementById.has(node.id)).map((node) => {
|
|
10875
|
+
const evidence = duplicateEvidenceByTarget.get(node.id);
|
|
10876
|
+
const parentId = replacementParent(node.parentId);
|
|
10877
|
+
if (!evidence?.length) {
|
|
10878
|
+
return parentId === node.parentId ? node : {
|
|
10879
|
+
...node,
|
|
10880
|
+
parentId,
|
|
10881
|
+
metadata: {
|
|
10882
|
+
...node.metadata,
|
|
10883
|
+
organizerRepair: "collapse_duplicate_endorsement_wrapper"
|
|
10884
|
+
}
|
|
10885
|
+
};
|
|
10886
|
+
}
|
|
10887
|
+
const evidenceNodes = [node, ...evidence];
|
|
10888
|
+
const pageStarts = evidenceNodes.map((item) => item.pageStart).filter((page) => typeof page === "number");
|
|
10889
|
+
const pageEnds = evidenceNodes.map((item) => item.pageEnd ?? item.pageStart).filter((page) => typeof page === "number");
|
|
10890
|
+
return {
|
|
10891
|
+
...node,
|
|
10892
|
+
parentId,
|
|
10893
|
+
sourceSpanIds: [...new Set(evidenceNodes.flatMap((item) => item.sourceSpanIds))],
|
|
10894
|
+
pageStart: pageStarts.length ? Math.min(...pageStarts) : node.pageStart,
|
|
10895
|
+
pageEnd: pageEnds.length ? Math.max(...pageEnds) : node.pageEnd,
|
|
10896
|
+
bbox: evidenceNodes.flatMap((item) => item.bbox ?? []).slice(0, 12),
|
|
10897
|
+
metadata: {
|
|
10898
|
+
...node.metadata,
|
|
10899
|
+
organizerRepair: "collapse_duplicate_endorsement_wrapper"
|
|
10900
|
+
}
|
|
10901
|
+
};
|
|
10902
|
+
});
|
|
10903
|
+
return normalizeDocumentSourceTreePaths(normalizeContainerEvidenceFromChildren(updated));
|
|
11054
10904
|
}
|
|
11055
10905
|
function compactNode2(node, maxText = 700) {
|
|
11056
10906
|
return {
|
|
@@ -11084,18 +10934,36 @@ function rootChildren(sourceTree) {
|
|
|
11084
10934
|
const rootId = sourceTreeRootId(sourceTree);
|
|
11085
10935
|
return (byParent.get(rootId) ?? []).filter((node) => node.kind !== "document");
|
|
11086
10936
|
}
|
|
11087
|
-
function
|
|
11088
|
-
const children = rootChildren(sourceTree);
|
|
11089
|
-
const semanticCount = children.filter(
|
|
11090
|
-
(node) => node.kind === "page_group" || node.kind === "form" || node.kind === "endorsement" || node.kind === "section" || node.kind === "schedule"
|
|
11091
|
-
).length;
|
|
11092
|
-
return semanticCount >= 2 || children.some((node) => node.title === "Declarations") || children.some((node) => node.title === "Policy Form") || children.some((node) => node.title === "Endorsements");
|
|
11093
|
-
}
|
|
11094
|
-
function shouldRunSourceTreeOrganizer(sourceTree, sourceSpans) {
|
|
10937
|
+
function shouldRunSourceTreeOrganizer(sourceTree, _sourceSpans) {
|
|
11095
10938
|
const topLevelCount = rootChildren(sourceTree).length;
|
|
11096
|
-
if (sourceSpans.length > ORGANIZER_MAX_SOURCE_SPANS) return false;
|
|
11097
10939
|
if (topLevelCount > ORGANIZER_MAX_TOP_LEVEL_NODES) return false;
|
|
11098
|
-
return
|
|
10940
|
+
return true;
|
|
10941
|
+
}
|
|
10942
|
+
function organizationCandidateText(node) {
|
|
10943
|
+
return [node.title, node.description, node.textExcerpt].filter(Boolean).join(" ");
|
|
10944
|
+
}
|
|
10945
|
+
function isHighSignalOrganizationNode(node) {
|
|
10946
|
+
if (node.kind === "document") return false;
|
|
10947
|
+
if (["page", "page_group", "form", "endorsement", "section", "schedule", "clause", "table", "table_row"].includes(node.kind)) {
|
|
10948
|
+
return true;
|
|
10949
|
+
}
|
|
10950
|
+
if (node.kind !== "text" && node.kind !== "table_cell") return false;
|
|
10951
|
+
const text = organizationCandidateText(node);
|
|
10952
|
+
return /\b(SECTION|PART|ARTICLE|SCHEDULE)\s+[IVXLCDM0-9]+/i.test(text) || /\bItem\s+\d+[\.:]/i.test(text) || /^[A-Z]\.\s+\S/.test(cleanText(node.textExcerpt ?? node.title, "")) || /\b(forms? and endorsements?|coverage parts?|limits? of liability|extended reporting period|producer|premium|aggregate|retroactive date|endorsement no\.?)\b/i.test(text);
|
|
10953
|
+
}
|
|
10954
|
+
function organizationBatchNodes(topLevelBatch, byParent) {
|
|
10955
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
10956
|
+
const queue = [...topLevelBatch];
|
|
10957
|
+
while (queue.length > 0 && nodes.size < ORGANIZER_MAX_BATCH_NODES) {
|
|
10958
|
+
const node = queue.shift();
|
|
10959
|
+
if (!node || nodes.has(node.id) || !isHighSignalOrganizationNode(node)) continue;
|
|
10960
|
+
nodes.set(node.id, node);
|
|
10961
|
+
for (const child of byParent.get(node.id) ?? []) {
|
|
10962
|
+
if (nodes.size + queue.length >= ORGANIZER_MAX_BATCH_NODES) break;
|
|
10963
|
+
if (isHighSignalOrganizationNode(child)) queue.push(child);
|
|
10964
|
+
}
|
|
10965
|
+
}
|
|
10966
|
+
return [...nodes.values()];
|
|
11099
10967
|
}
|
|
11100
10968
|
function organizationBatches(sourceTree) {
|
|
11101
10969
|
const byParent = nodesByParent(sourceTree);
|
|
@@ -11112,14 +10980,10 @@ function organizationBatches(sourceTree) {
|
|
|
11112
10980
|
const batches = [];
|
|
11113
10981
|
for (let index = 0; index < topLevelNodes.length; index += ORGANIZATION_TOP_LEVEL_BATCH_SIZE) {
|
|
11114
10982
|
const topLevelBatch = topLevelNodes.slice(index, index + ORGANIZATION_TOP_LEVEL_BATCH_SIZE);
|
|
11115
|
-
const candidates = /* @__PURE__ */ new Map();
|
|
11116
|
-
for (const node of topLevelBatch) {
|
|
11117
|
-
candidates.set(node.id, node);
|
|
11118
|
-
}
|
|
11119
10983
|
batches.push({
|
|
11120
10984
|
label: `top-level nodes ${index + 1}-${index + topLevelBatch.length} of ${topLevelNodes.length}`,
|
|
11121
10985
|
topLevelNodeIds: topLevelBatch.map((node) => node.id),
|
|
11122
|
-
nodes:
|
|
10986
|
+
nodes: organizationBatchNodes(topLevelBatch, byParent)
|
|
11123
10987
|
});
|
|
11124
10988
|
}
|
|
11125
10989
|
return batches;
|
|
@@ -11132,7 +10996,7 @@ function mergeOrganizationResults(results) {
|
|
|
11132
10996
|
labels.set(label.nodeId, { ...labels.get(label.nodeId), ...label });
|
|
11133
10997
|
}
|
|
11134
10998
|
for (const group of result.groups) {
|
|
11135
|
-
const key = `${group.kind}:${group.childNodeIds.join("|")}`;
|
|
10999
|
+
const key = `${group.parentNodeId ?? ""}:${group.kind}:${group.childNodeIds.join("|")}`;
|
|
11136
11000
|
groups.set(key, group);
|
|
11137
11001
|
}
|
|
11138
11002
|
}
|
|
@@ -11156,9 +11020,16 @@ ${formatFormHintsForPrompt(formHints)}
|
|
|
11156
11020
|
Rules:
|
|
11157
11021
|
- Use only node IDs from the provided list.
|
|
11158
11022
|
- Do not invent text, page numbers, source spans, limits, or policy facts.
|
|
11159
|
-
- You may relabel existing nodes and group adjacent
|
|
11023
|
+
- You may relabel existing nodes and group adjacent sibling nodes from this batch only when they are clearly one continuous form, one declarations item, one policy section, one schedule, or one clause family.
|
|
11160
11024
|
- Treat the form inventory as a page-range hint for the expected order: front matter/notices, declarations, policy form, then endorsements.
|
|
11161
11025
|
- Prefer section hierarchy from printed title elements inside a form over page-by-page grouping.
|
|
11026
|
+
- Printed section markers are hard hierarchy boundaries. If source text moves from "SECTION XI" to "SECTION XII", "Section 12", "PART III", or similar sequential headings, create a new sibling section at that marker even when the marker appears mid-page after prior section text.
|
|
11027
|
+
- Declarations pages often use "Item 1.", "Item 2.", ... as the real section structure. Preserve each item as its own section or schedule under "Declarations"; do not bury item labels inside a single table when they mark new declaration fields.
|
|
11028
|
+
- For declaration item tables, a table belongs only to the current item until the next "Item N" marker. Start a new declaration item section at the next marker, even if the parser presents the marker as a table row or cell.
|
|
11029
|
+
- Keep forms-and-endorsements schedules attached to declarations when they appear as "Item 10", "Forms and Endorsements Attached at Inception", or a continuation page listing form numbers. Do not discard that page just because it also contains countersignature or authorized-representative language.
|
|
11030
|
+
- Prefer sections/schedules over table grouping when a table-like node mixes structural labels such as "Item 7", "Item 8", "SECTION XII", "Producer", or "Forms and Endorsements" with values.
|
|
11031
|
+
- Use group.parentNodeId only when the source clearly shows a child was nested under the wrong earlier section/table. Example: a "SECTION XII" marker nested below "SECTION XI" should be grouped with parentNodeId set to the Policy Form node so Section XII becomes a sibling of Section XI.
|
|
11032
|
+
- Use group.parentNodeId to place "Item N" groups under the Declarations node when parser table structure would otherwise leave those items trapped inside one table.
|
|
11162
11033
|
- Group adjacent separately numbered endorsements under a single generic "Endorsements" page_group parent, with each individual endorsement preserved as its own child node.
|
|
11163
11034
|
- Never create rollup titles such as "Endorsements 1-3 (...)" or merge multiple endorsements into one endorsement node.
|
|
11164
11035
|
- Add concise, human-readable titles to generic text, table, row, and cell nodes when the text makes their role clear.
|
|
@@ -11189,7 +11060,7 @@ function buildOutlineCleanupPrompt(sourceTree, formHints) {
|
|
|
11189
11060
|
|
|
11190
11061
|
Expected product-facing order:
|
|
11191
11062
|
1. Optional front matter: policy jacket, important notices, privacy notices, OFAC notices, TRIA/terrorism notices, marketing/admin pages, signatures, countersignatures, or other pages that are not the declarations, policy wording, or endorsements.
|
|
11192
|
-
2. Declarations: declarations page(s), schedules, named insured/policy period/premium rows, coverage limit schedules, forms-and-endorsements schedules.
|
|
11063
|
+
2. Declarations: declarations page(s), schedules, itemized declaration fields, named insured/policy period/premium rows, coverage limit schedules, forms-and-endorsements schedules.
|
|
11193
11064
|
3. Policy Form: the main policy wording, insuring agreements, definitions, exclusions, conditions, claim provisions, and general policy terms.
|
|
11194
11065
|
4. Endorsements: one generic "Endorsements" page_group containing each separately numbered endorsement as its own child.
|
|
11195
11066
|
|
|
@@ -11203,7 +11074,9 @@ Rules:
|
|
|
11203
11074
|
- Do not merge individually numbered endorsements into one endorsement node; use the generic "Endorsements" parent for the series.
|
|
11204
11075
|
- Use canonical terse titles: "Notices and Jacket", "Declarations", "Policy Form", "Endorsements", or the printed endorsement number.
|
|
11205
11076
|
- Keep page_group descriptions short and include the page range when pages are known, for example "Declarations pages 6-8".
|
|
11206
|
-
- If a page is an OFAC, privacy, terrorism/TRIA, claim-reporting notice, signature page, or jacket, do not label it as declarations or policy form.
|
|
11077
|
+
- If a page is only an OFAC, privacy, terrorism/TRIA, claim-reporting notice, signature page, or jacket, do not label it as declarations or policy form.
|
|
11078
|
+
- If a page contains declaration items, coverage schedules, premium rows, producer rows, or a forms-and-endorsements schedule, keep it in Declarations even if it also contains countersignature or authorized-representative text.
|
|
11079
|
+
- If a policy-form page contains a later printed section marker such as "SECTION XII \u2014 EXTENDED REPORTING PERIOD", preserve that marker as the start of a new sibling section rather than a paragraph under the previous section.
|
|
11207
11080
|
- If the form inventory provides page ranges, keep groups aligned to those ranges unless the source node text clearly contradicts them.
|
|
11208
11081
|
- If the existing deterministic outline is already correct, return empty labels and groups.
|
|
11209
11082
|
|
|
@@ -11225,7 +11098,6 @@ function emptyOperationalProfile() {
|
|
|
11225
11098
|
return {
|
|
11226
11099
|
documentType: "policy",
|
|
11227
11100
|
policyTypes: ["other"],
|
|
11228
|
-
coverageTypes: [],
|
|
11229
11101
|
coverages: [],
|
|
11230
11102
|
parties: [],
|
|
11231
11103
|
endorsementSupport: [],
|
|
@@ -11249,10 +11121,17 @@ Rules:
|
|
|
11249
11121
|
- Every returned value must include sourceNodeIds or sourceSpanIds from the provided nodes.
|
|
11250
11122
|
- If a value is not directly supported, omit it.
|
|
11251
11123
|
- Prefer declarations, schedules, premium tables, and endorsement schedules over generic policy wording.
|
|
11124
|
+
- On declarations pages, treat "Item N" labels as section boundaries. Use Item 6 or equivalent coverage-schedule rows for coverage limits, deductibles, aggregate terms, and retroactive dates; do not merge Item 7 premium, Item 8 ERP, Item 9 producer, or Item 10 forms into Item 6 coverage facts.
|
|
11125
|
+
- A coverage schedule row's coverage name should come from the "Coverage Part" or equivalent row label. Limit, deductible, aggregate, sublimit, retention, and retroactive-date values belong as nested terms under that coverage, not in the coverage title.
|
|
11126
|
+
- If a coverage schedule continues onto the next page before the next item marker, include the continuation rows in the same coverage or declaration item.
|
|
11127
|
+
- Forms-and-endorsements schedules are operational form inventory evidence, not coverage limits. Do not turn form schedule rows into coverage units unless the row also states a coverage-specific limit or deductible.
|
|
11128
|
+
- Keep each coverage unit tied to one evidence scope: a declaration/core schedule row, a core policy form section, or one specific endorsement schedule. Do not merge declaration facts and endorsement schedule facts into the same coverage unit, even when they use the same coverage name.
|
|
11129
|
+
- If the declarations schedule and an endorsement schedule both list Network Security, Social Engineering Fraud, Regulatory Proceedings, or another same-named coverage, return separate coverage units for each supported source scope.
|
|
11130
|
+
- Use the declaration coverage name for declaration/core schedule rows. Use the endorsement title or endorsement schedule coverage name for endorsement rows, and include formNumber and endorsementNumber when source-backed.
|
|
11252
11131
|
- For life, critical illness, disability, and long-term care policies, keep named benefit units and benefit subconditions as operational facts even when they do not have dollar limits. Examples include death benefit, disability benefit, total disability, catastrophic disability, return of premium, waiver, and conversion options. Put subcondition details in coverages[].limits with kind "other" when they belong under a broader benefit.
|
|
11253
11132
|
- Treat an endorsement as one coverage unit when it contains a schedule. Do not split an endorsement schedule into generic rows like "Aggregate Limit".
|
|
11254
11133
|
- For coverage schedules, put each claim, aggregate, sublimit, retention, deductible, and retroactive date values in coverages[].limits with labels and source IDs. Keep the legacy coverages[].limit as the primary display value only.
|
|
11255
|
-
-
|
|
11134
|
+
- Extract coinsurance, participation percentage, or insurer/named-insured split terms as coverages[].limits entries with kind "other" when they are part of a coverage schedule.
|
|
11256
11135
|
- Do not copy entire policy wording into fields.
|
|
11257
11136
|
- Extract facts directly from source nodes. There is no deterministic fact baseline.
|
|
11258
11137
|
|
|
@@ -11261,15 +11140,6 @@ ${JSON.stringify(nodes, null, 2)}
|
|
|
11261
11140
|
|
|
11262
11141
|
Return JSON for the operational profile.`;
|
|
11263
11142
|
}
|
|
11264
|
-
var VISUAL_TABLE_REPAIR_MAX_TABLES = 4;
|
|
11265
|
-
var VISUAL_TABLE_REPAIR_MAX_ROWS = 28;
|
|
11266
|
-
var VISUAL_TABLE_REPAIR_MAX_CELLS = 140;
|
|
11267
|
-
function isSourceTreeHeaderRow(row) {
|
|
11268
|
-
return row.metadata?.isHeader === true || row.metadata?.isHeader === "true";
|
|
11269
|
-
}
|
|
11270
|
-
function tableCellText(cell) {
|
|
11271
|
-
return cleanText(cell.textExcerpt ?? cell.description ?? cell.title, "");
|
|
11272
|
-
}
|
|
11273
11143
|
function tableCellValueText(cell) {
|
|
11274
11144
|
return cleanText(cell.textExcerpt ?? cell.description ?? "", "");
|
|
11275
11145
|
}
|
|
@@ -11323,505 +11193,10 @@ function normalizeSourceTreeTableDisplayText(sourceTree) {
|
|
|
11323
11193
|
if (updates.size === 0) return sourceTree;
|
|
11324
11194
|
return sourceTree.map((node) => updates.get(node.id) ?? node);
|
|
11325
11195
|
}
|
|
11326
|
-
function tableRowTextForPrompt(row, cells) {
|
|
11327
|
-
return cleanText(
|
|
11328
|
-
cells.length ? cells.map(tableCellText).filter(Boolean).join(" | ") : row.textExcerpt ?? row.description ?? row.title,
|
|
11329
|
-
row.title
|
|
11330
|
-
);
|
|
11331
|
-
}
|
|
11332
11196
|
function tableCellColumnIndex(cell, fallbackIndex) {
|
|
11333
11197
|
const metadataIndex = cell.metadata?.columnIndex;
|
|
11334
11198
|
return typeof metadataIndex === "number" && Number.isInteger(metadataIndex) ? metadataIndex : fallbackIndex;
|
|
11335
11199
|
}
|
|
11336
|
-
function isGenericColumnTitle(value) {
|
|
11337
|
-
const title = cleanText(value, "");
|
|
11338
|
-
return !title || /^(?:column\s+\d+|table cell|value)$/i.test(title);
|
|
11339
|
-
}
|
|
11340
|
-
function metadataColumnName(metadata) {
|
|
11341
|
-
const value = metadata?.columnName;
|
|
11342
|
-
return typeof value === "string" ? cleanText(value, "") || void 0 : void 0;
|
|
11343
|
-
}
|
|
11344
|
-
function metadataTableColumnName(metadata) {
|
|
11345
|
-
const table = metadata?.table;
|
|
11346
|
-
if (!table || typeof table !== "object" || Array.isArray(table)) return void 0;
|
|
11347
|
-
if (!("columnName" in table)) return void 0;
|
|
11348
|
-
const value = table.columnName;
|
|
11349
|
-
return typeof value === "string" ? cleanText(value, "") || void 0 : void 0;
|
|
11350
|
-
}
|
|
11351
|
-
function bboxSummary(node) {
|
|
11352
|
-
const box = node.bbox?.[0];
|
|
11353
|
-
if (!box) return void 0;
|
|
11354
|
-
const round = (value) => Math.round(value * 10) / 10;
|
|
11355
|
-
return {
|
|
11356
|
-
page: box.page,
|
|
11357
|
-
x: round(box.x),
|
|
11358
|
-
y: round(box.y),
|
|
11359
|
-
width: round(box.width),
|
|
11360
|
-
height: round(box.height)
|
|
11361
|
-
};
|
|
11362
|
-
}
|
|
11363
|
-
function tableRowsWithCells(table, byParent) {
|
|
11364
|
-
return (byParent.get(table.id) ?? []).filter((node) => node.kind === "table_row").map((row) => ({
|
|
11365
|
-
row,
|
|
11366
|
-
cells: (byParent.get(row.id) ?? []).filter((child) => child.kind === "table_cell").sort(
|
|
11367
|
-
(left, right) => tableCellColumnIndex(left, 0) - tableCellColumnIndex(right, 0) || left.order - right.order || left.id.localeCompare(right.id)
|
|
11368
|
-
)
|
|
11369
|
-
})).sort((left, right) => left.row.order - right.row.order || left.row.id.localeCompare(right.row.id));
|
|
11370
|
-
}
|
|
11371
|
-
function primaryHeaderColumnCount(rows) {
|
|
11372
|
-
const header = rows.find(({ row, cells }) => isSourceTreeHeaderRow(row) && cells.length > 1);
|
|
11373
|
-
if (header) return header.cells.length;
|
|
11374
|
-
return Math.max(0, ...rows.map(({ cells }) => cells.length));
|
|
11375
|
-
}
|
|
11376
|
-
function visualTableRepairScore(candidate) {
|
|
11377
|
-
const rows = candidate.rows;
|
|
11378
|
-
if (rows.length < 3) return 0;
|
|
11379
|
-
const headerCount = primaryHeaderColumnCount(rows);
|
|
11380
|
-
if (headerCount < 2) return 0;
|
|
11381
|
-
const firstHeaderIndex = rows.findIndex(({ row, cells }) => isSourceTreeHeaderRow(row) && cells.length > 1);
|
|
11382
|
-
const repeatedHeader = firstHeaderIndex >= 0 && rows.some(({ row }, index) => index > firstHeaderIndex && isSourceTreeHeaderRow(row));
|
|
11383
|
-
const shortContinuation = rows.some(
|
|
11384
|
-
({ row, cells }, index) => index > 0 && !isSourceTreeHeaderRow(row) && cells.length > 0 && cells.length < headerCount && !/^(?:item\s+\d+|[A-Z]\.)\b/i.test(tableCellText(cells[0]))
|
|
11385
|
-
);
|
|
11386
|
-
const genericDataLabels = rows.some(
|
|
11387
|
-
({ row, cells }) => !isSourceTreeHeaderRow(row) && cells.length >= 2 && cells.some((cell) => isGenericColumnTitle(cell.title))
|
|
11388
|
-
);
|
|
11389
|
-
const danglingSlash = rows.some(
|
|
11390
|
-
({ row, cells }) => !isSourceTreeHeaderRow(row) && /\/\s*$/.test(tableRowTextForPrompt(row, cells))
|
|
11391
|
-
);
|
|
11392
|
-
return (repeatedHeader ? 4 : 0) + (danglingSlash ? 3 : 0) + (shortContinuation ? 3 : 0) + (genericDataLabels ? 2 : 0);
|
|
11393
|
-
}
|
|
11394
|
-
function visualTableCandidates(sourceTree) {
|
|
11395
|
-
const byParent = nodesByParent(sourceTree);
|
|
11396
|
-
return sourceTree.filter((node) => node.kind === "table" && typeof node.pageStart === "number").map((table) => ({
|
|
11397
|
-
table,
|
|
11398
|
-
page: table.pageStart,
|
|
11399
|
-
rows: tableRowsWithCells(table, byParent)
|
|
11400
|
-
})).map((candidate) => ({ ...candidate, repairScore: visualTableRepairScore(candidate) })).filter((candidate) => candidate.repairScore > 0).sort(
|
|
11401
|
-
(left, right) => right.repairScore - left.repairScore || left.page - right.page || left.table.order - right.table.order || left.table.id.localeCompare(right.table.id)
|
|
11402
|
-
).slice(0, VISUAL_TABLE_REPAIR_MAX_TABLES).map(({ repairScore: _repairScore, ...candidate }) => candidate);
|
|
11403
|
-
}
|
|
11404
|
-
function compactVisualTableCandidate(candidate) {
|
|
11405
|
-
let cellCount = 0;
|
|
11406
|
-
const rows = candidate.rows.slice(0, VISUAL_TABLE_REPAIR_MAX_ROWS).map(({ row, cells }) => {
|
|
11407
|
-
const compactCells = cells.slice(0, Math.max(0, VISUAL_TABLE_REPAIR_MAX_CELLS - cellCount)).map((cell, index) => ({
|
|
11408
|
-
cellNodeId: cell.id,
|
|
11409
|
-
columnIndex: tableCellColumnIndex(cell, index),
|
|
11410
|
-
currentColumnName: cell.title,
|
|
11411
|
-
text: tableCellText(cell),
|
|
11412
|
-
bbox: bboxSummary(cell)
|
|
11413
|
-
}));
|
|
11414
|
-
cellCount += compactCells.length;
|
|
11415
|
-
return {
|
|
11416
|
-
rowNodeId: row.id,
|
|
11417
|
-
order: row.order,
|
|
11418
|
-
isHeader: isSourceTreeHeaderRow(row),
|
|
11419
|
-
text: tableRowTextForPrompt(row, cells),
|
|
11420
|
-
bbox: bboxSummary(row),
|
|
11421
|
-
cells: compactCells
|
|
11422
|
-
};
|
|
11423
|
-
});
|
|
11424
|
-
return {
|
|
11425
|
-
tableNodeId: candidate.table.id,
|
|
11426
|
-
page: candidate.page,
|
|
11427
|
-
title: candidate.table.title,
|
|
11428
|
-
bbox: bboxSummary(candidate.table),
|
|
11429
|
-
rows
|
|
11430
|
-
};
|
|
11431
|
-
}
|
|
11432
|
-
function buildVisualTableRepairPrompt(candidate) {
|
|
11433
|
-
return `Compare a parsed insurance source table against the original page visual layout.
|
|
11434
|
-
|
|
11435
|
-
If a page image is attached, use it as the primary reference. If no image is available, use the bbox coordinates below as the visual layout reference.
|
|
11436
|
-
|
|
11437
|
-
Task:
|
|
11438
|
-
- Identify rows that are not real standalone rows because they are visually wrapped continuation text for a nearby row.
|
|
11439
|
-
- Identify the primary printed column labels for the table.
|
|
11440
|
-
|
|
11441
|
-
Rules:
|
|
11442
|
-
- Return only high-confidence repairs.
|
|
11443
|
-
- Use only rowNodeId/tableNodeId/cellNodeId values from the provided JSON.
|
|
11444
|
-
- Do not invent policy facts, values, row text, source spans, or page numbers.
|
|
11445
|
-
- continuationRows.sourceRowNodeId must be a parsed row that should be removed as a standalone row.
|
|
11446
|
-
- continuationRows.targetRowNodeId must be the row that visually owns that wrapped text, usually the immediately previous non-header row.
|
|
11447
|
-
- targetColumnIndex/targetColumnLabel should point to the visual column that owns the wrapped text, usually the limit/amount/value column.
|
|
11448
|
-
- Do not mark actual data rows as continuations when they begin a new item, coverage part, endorsement, form, location, person, or premium/tax row.
|
|
11449
|
-
- columnLabels should be the primary visual header labels, not later wrapped cell text that the parser misread as a header.
|
|
11450
|
-
|
|
11451
|
-
Parsed table with visual coordinates:
|
|
11452
|
-
${JSON.stringify(compactVisualTableCandidate(candidate), null, 2)}
|
|
11453
|
-
|
|
11454
|
-
Return JSON with tables[].columnLabels and tables[].continuationRows. Return empty arrays if no repair is needed.`;
|
|
11455
|
-
}
|
|
11456
|
-
function sourceSpanIdsForNodes(nodes) {
|
|
11457
|
-
return [...new Set(nodes.flatMap((node) => node.sourceSpanIds))];
|
|
11458
|
-
}
|
|
11459
|
-
function appendDistinctText(base, addition) {
|
|
11460
|
-
const current = cleanText(base, "");
|
|
11461
|
-
const next = cleanText(addition, "");
|
|
11462
|
-
if (!current) return next || void 0;
|
|
11463
|
-
if (!next) return current;
|
|
11464
|
-
if (current.toLowerCase().includes(next.toLowerCase())) return current;
|
|
11465
|
-
const joinsWithSpace = /(?:[/(:;-]|,\s*)$/.test(current) || /\b(?:part of|including|subject to|not in addition to)$/i.test(current) || /^(?:aggregate|claim|loss|proceeding|occurrence|each\s+(?:claim|loss|proceeding|occurrence)|coverage\s+part\b)/i.test(next);
|
|
11466
|
-
const delimiter = joinsWithSpace ? " " : " / ";
|
|
11467
|
-
return cleanText(`${current}${delimiter}${next}`, current);
|
|
11468
|
-
}
|
|
11469
|
-
function mergedBbox(nodes) {
|
|
11470
|
-
const boxes = nodes.flatMap((node) => node.bbox ?? []);
|
|
11471
|
-
return boxes.length ? boxes.slice(0, 12) : void 0;
|
|
11472
|
-
}
|
|
11473
|
-
function normalizedRepairLabel(value) {
|
|
11474
|
-
const label = cleanText(value, "");
|
|
11475
|
-
if (!label || label.length > 80) return void 0;
|
|
11476
|
-
if (/^(?:source|page|row|table)$/i.test(label)) return void 0;
|
|
11477
|
-
return label;
|
|
11478
|
-
}
|
|
11479
|
-
function findCellForContinuation(params) {
|
|
11480
|
-
if (typeof params.targetColumnIndex === "number") {
|
|
11481
|
-
const byIndex = params.cells.find(
|
|
11482
|
-
(cell, index) => tableCellColumnIndex(cell, index) === params.targetColumnIndex
|
|
11483
|
-
);
|
|
11484
|
-
if (byIndex) return byIndex;
|
|
11485
|
-
}
|
|
11486
|
-
const label = normalizedRepairLabel(params.targetColumnLabel);
|
|
11487
|
-
if (label) {
|
|
11488
|
-
const byLabel = params.cells.find(
|
|
11489
|
-
(cell) => cleanText(cell.title, "").toLowerCase() === label.toLowerCase()
|
|
11490
|
-
);
|
|
11491
|
-
if (byLabel) return byLabel;
|
|
11492
|
-
}
|
|
11493
|
-
return params.cells[1] ?? params.cells[params.cells.length - 1];
|
|
11494
|
-
}
|
|
11495
|
-
function bboxCenterX(node) {
|
|
11496
|
-
const box = node.bbox?.[0];
|
|
11497
|
-
if (!box) return void 0;
|
|
11498
|
-
return box.x + box.width / 2;
|
|
11499
|
-
}
|
|
11500
|
-
function findCellByVisualPosition(sourceCell, targetCells) {
|
|
11501
|
-
const sourceX = bboxCenterX(sourceCell);
|
|
11502
|
-
if (sourceX === void 0) return void 0;
|
|
11503
|
-
const positioned = targetCells.map((cell) => ({ cell, centerX: bboxCenterX(cell) })).filter(
|
|
11504
|
-
(entry) => entry.centerX !== void 0
|
|
11505
|
-
);
|
|
11506
|
-
if (positioned.length === 0) return void 0;
|
|
11507
|
-
positioned.sort(
|
|
11508
|
-
(left, right) => Math.abs(left.centerX - sourceX) - Math.abs(right.centerX - sourceX)
|
|
11509
|
-
);
|
|
11510
|
-
return positioned[0]?.cell;
|
|
11511
|
-
}
|
|
11512
|
-
function columnLabelStartIndex(rows) {
|
|
11513
|
-
const headerIndex = rows.findIndex(
|
|
11514
|
-
({ row, cells }) => isSourceTreeHeaderRow(row) && cells.length > 1
|
|
11515
|
-
);
|
|
11516
|
-
return headerIndex >= 0 ? headerIndex : 0;
|
|
11517
|
-
}
|
|
11518
|
-
function startsNewVisualTableItem(cells) {
|
|
11519
|
-
const firstText = tableCellText(cells[0]);
|
|
11520
|
-
return /^(?:item\s+\d+\b|[A-Z][.)]\s+|coverage\s+part\b|endorsement\s+(?:no\.?|number|#|\d+)\b)/i.test(firstText);
|
|
11521
|
-
}
|
|
11522
|
-
function rowMatchesColumnLabels(cells, columnLabels) {
|
|
11523
|
-
if (columnLabels.size === 0 || cells.length < columnLabels.size) return false;
|
|
11524
|
-
return cells.every((cell, index) => {
|
|
11525
|
-
const label = columnLabels.get(tableCellColumnIndex(cell, index));
|
|
11526
|
-
return !label || tableCellText(cell).toLowerCase() === label.toLowerCase();
|
|
11527
|
-
});
|
|
11528
|
-
}
|
|
11529
|
-
function implicitContinuationRows(rows, columnLabels, firstLabelRowIndex) {
|
|
11530
|
-
const continuations = [];
|
|
11531
|
-
let targetRowId;
|
|
11532
|
-
const targetColumnLabel = columnLabels.get(1);
|
|
11533
|
-
for (const [rowIndex, { row, cells }] of rows.entries()) {
|
|
11534
|
-
if (rowIndex < firstLabelRowIndex) continue;
|
|
11535
|
-
if (rowIndex === firstLabelRowIndex && rowMatchesColumnLabels(cells, columnLabels)) continue;
|
|
11536
|
-
if (startsNewVisualTableItem(cells)) {
|
|
11537
|
-
targetRowId = row.id;
|
|
11538
|
-
continue;
|
|
11539
|
-
}
|
|
11540
|
-
if (!targetRowId || cells.length === 0) continue;
|
|
11541
|
-
const isSpuriousHeader = isSourceTreeHeaderRow(row) && !rowMatchesColumnLabels(cells, columnLabels);
|
|
11542
|
-
const isShortContinuation = !isSourceTreeHeaderRow(row) && cells.length < Math.max(2, columnLabels.size);
|
|
11543
|
-
if (!isSpuriousHeader && !isShortContinuation) continue;
|
|
11544
|
-
continuations.push({
|
|
11545
|
-
sourceRowNodeId: row.id,
|
|
11546
|
-
targetRowNodeId: targetRowId,
|
|
11547
|
-
targetColumnIndex: 1,
|
|
11548
|
-
targetColumnLabel,
|
|
11549
|
-
reason: "Continuation row inferred from repaired table header."
|
|
11550
|
-
});
|
|
11551
|
-
}
|
|
11552
|
-
return continuations;
|
|
11553
|
-
}
|
|
11554
|
-
function metadataWithColumnLabel(metadata, label) {
|
|
11555
|
-
const next = {
|
|
11556
|
-
...metadata ?? {},
|
|
11557
|
-
columnName: label,
|
|
11558
|
-
visualTableRepairColumnLabel: label
|
|
11559
|
-
};
|
|
11560
|
-
if (metadata?.table && typeof metadata.table === "object" && !Array.isArray(metadata.table)) {
|
|
11561
|
-
next.table = {
|
|
11562
|
-
...metadata.table,
|
|
11563
|
-
columnName: label
|
|
11564
|
-
};
|
|
11565
|
-
}
|
|
11566
|
-
return next;
|
|
11567
|
-
}
|
|
11568
|
-
function tableCellDescription(cell, label) {
|
|
11569
|
-
const text = tableCellText(cell);
|
|
11570
|
-
return cleanText(
|
|
11571
|
-
text && text.toLowerCase() !== label.toLowerCase() ? `${label} | ${text}` : label,
|
|
11572
|
-
cell.description
|
|
11573
|
-
);
|
|
11574
|
-
}
|
|
11575
|
-
function applyVisualTableRepair(sourceTree, repair) {
|
|
11576
|
-
if (repair.tables.length === 0) return sourceTree;
|
|
11577
|
-
const byId = new Map(sourceTree.map((node) => [node.id, node]));
|
|
11578
|
-
const byParent = nodesByParent(sourceTree);
|
|
11579
|
-
const updates = /* @__PURE__ */ new Map();
|
|
11580
|
-
const removeIds = /* @__PURE__ */ new Set();
|
|
11581
|
-
const rowsToRebuildText = /* @__PURE__ */ new Set();
|
|
11582
|
-
const currentNode = (id) => updates.get(id) ?? byId.get(id);
|
|
11583
|
-
for (const tableRepair of repair.tables) {
|
|
11584
|
-
const table = byId.get(tableRepair.tableNodeId);
|
|
11585
|
-
if (!table || table.kind !== "table") continue;
|
|
11586
|
-
const rows = tableRowsWithCells(table, byParent);
|
|
11587
|
-
const rowIds = new Set(rows.map(({ row }) => row.id));
|
|
11588
|
-
const rowOrder = new Map(rows.map(({ row }, index) => [row.id, index]));
|
|
11589
|
-
const columnLabels = /* @__PURE__ */ new Map();
|
|
11590
|
-
for (const label of tableRepair.columnLabels) {
|
|
11591
|
-
const normalized = normalizedRepairLabel(label.label);
|
|
11592
|
-
if (normalized) columnLabels.set(label.columnIndex, normalized);
|
|
11593
|
-
}
|
|
11594
|
-
const firstLabelRowIndex = columnLabelStartIndex(rows);
|
|
11595
|
-
if (columnLabels.size > 0) {
|
|
11596
|
-
const normalizedLabels = new Set([...columnLabels.values()].map((label) => label.toLowerCase()));
|
|
11597
|
-
for (const [rowIndex, { row, cells }] of rows.entries()) {
|
|
11598
|
-
if (removeIds.has(row.id)) continue;
|
|
11599
|
-
if (rowIndex < firstLabelRowIndex) {
|
|
11600
|
-
for (const cell of cells) {
|
|
11601
|
-
const metadataLabel = metadataColumnName(cell.metadata);
|
|
11602
|
-
if (metadataLabel && isGenericColumnTitle(metadataLabel) && normalizedLabels.has(cleanText(cell.title, "").toLowerCase())) {
|
|
11603
|
-
updates.set(cell.id, {
|
|
11604
|
-
...currentNode(cell.id) ?? cell,
|
|
11605
|
-
title: metadataLabel,
|
|
11606
|
-
description: tableCellDescription(cell, metadataLabel),
|
|
11607
|
-
metadata: metadataWithColumnLabel(cell.metadata, metadataLabel)
|
|
11608
|
-
});
|
|
11609
|
-
rowsToRebuildText.add(row.id);
|
|
11610
|
-
}
|
|
11611
|
-
}
|
|
11612
|
-
continue;
|
|
11613
|
-
}
|
|
11614
|
-
for (const [fallbackIndex, cell] of cells.entries()) {
|
|
11615
|
-
const columnIndex = tableCellColumnIndex(cell, fallbackIndex);
|
|
11616
|
-
const label = columnLabels.get(columnIndex);
|
|
11617
|
-
if (!label) continue;
|
|
11618
|
-
const current = currentNode(cell.id) ?? cell;
|
|
11619
|
-
if (current.title === label && metadataColumnName(current.metadata) === label && (metadataTableColumnName(current.metadata) ?? label) === label) {
|
|
11620
|
-
continue;
|
|
11621
|
-
}
|
|
11622
|
-
updates.set(cell.id, {
|
|
11623
|
-
...current,
|
|
11624
|
-
title: label,
|
|
11625
|
-
description: tableCellDescription(current, label),
|
|
11626
|
-
metadata: metadataWithColumnLabel(current.metadata, label)
|
|
11627
|
-
});
|
|
11628
|
-
rowsToRebuildText.add(row.id);
|
|
11629
|
-
}
|
|
11630
|
-
if (cells.length > 0) rowsToRebuildText.add(row.id);
|
|
11631
|
-
}
|
|
11632
|
-
}
|
|
11633
|
-
const continuationRows = [
|
|
11634
|
-
...tableRepair.continuationRows,
|
|
11635
|
-
...implicitContinuationRows(rows, columnLabels, firstLabelRowIndex)
|
|
11636
|
-
];
|
|
11637
|
-
const seenContinuationRows = /* @__PURE__ */ new Set();
|
|
11638
|
-
for (const continuation of continuationRows) {
|
|
11639
|
-
const continuationKey = `${continuation.sourceRowNodeId}:${continuation.targetRowNodeId}`;
|
|
11640
|
-
if (seenContinuationRows.has(continuationKey)) continue;
|
|
11641
|
-
seenContinuationRows.add(continuationKey);
|
|
11642
|
-
if (!rowIds.has(continuation.sourceRowNodeId) || !rowIds.has(continuation.targetRowNodeId)) continue;
|
|
11643
|
-
if (continuation.sourceRowNodeId === continuation.targetRowNodeId) continue;
|
|
11644
|
-
const sourceIndex = rowOrder.get(continuation.sourceRowNodeId);
|
|
11645
|
-
const targetIndex = rowOrder.get(continuation.targetRowNodeId);
|
|
11646
|
-
if (sourceIndex === void 0 || targetIndex === void 0) continue;
|
|
11647
|
-
if (Math.abs(sourceIndex - targetIndex) > 3) continue;
|
|
11648
|
-
const sourceRow = currentNode(continuation.sourceRowNodeId);
|
|
11649
|
-
const targetRow = currentNode(continuation.targetRowNodeId);
|
|
11650
|
-
if (!sourceRow || !targetRow || sourceRow.kind !== "table_row" || targetRow.kind !== "table_row") continue;
|
|
11651
|
-
if (isSourceTreeHeaderRow(targetRow)) continue;
|
|
11652
|
-
const sourceCells = (byParent.get(sourceRow.id) ?? []).filter((node) => node.kind === "table_cell").map((node) => currentNode(node.id) ?? node);
|
|
11653
|
-
const targetCells = (byParent.get(targetRow.id) ?? []).filter((node) => node.kind === "table_cell").map((node) => currentNode(node.id) ?? node);
|
|
11654
|
-
const sourceText = tableRowTextForPrompt(sourceRow, sourceCells);
|
|
11655
|
-
if (!sourceText) continue;
|
|
11656
|
-
const targetCell = findCellForContinuation({
|
|
11657
|
-
cells: targetCells,
|
|
11658
|
-
targetColumnIndex: continuation.targetColumnIndex,
|
|
11659
|
-
targetColumnLabel: continuation.targetColumnLabel
|
|
11660
|
-
});
|
|
11661
|
-
const sourceNodes = [sourceRow, ...sourceCells];
|
|
11662
|
-
const sourceSpanIds = sourceSpanIdsForNodes(sourceNodes);
|
|
11663
|
-
let mergedIntoCells = false;
|
|
11664
|
-
if (sourceCells.length > 0 && targetCells.length > 0) {
|
|
11665
|
-
for (const sourceCell of sourceCells) {
|
|
11666
|
-
const cellText = tableCellValueText(sourceCell);
|
|
11667
|
-
if (!cellText) continue;
|
|
11668
|
-
const visualTargetCell = findCellByVisualPosition(sourceCell, targetCells) ?? targetCell;
|
|
11669
|
-
if (!visualTargetCell) continue;
|
|
11670
|
-
const currentTargetCell = currentNode(visualTargetCell.id) ?? visualTargetCell;
|
|
11671
|
-
const nextCellText = appendDistinctText(tableCellText(currentTargetCell), cellText);
|
|
11672
|
-
if (!nextCellText) continue;
|
|
11673
|
-
updates.set(visualTargetCell.id, {
|
|
11674
|
-
...currentTargetCell,
|
|
11675
|
-
textExcerpt: nextCellText,
|
|
11676
|
-
description: cleanText(
|
|
11677
|
-
[currentTargetCell.title, nextCellText].filter(Boolean).join(" | "),
|
|
11678
|
-
currentTargetCell.description
|
|
11679
|
-
),
|
|
11680
|
-
sourceSpanIds: [
|
|
11681
|
-
.../* @__PURE__ */ new Set([
|
|
11682
|
-
...currentTargetCell.sourceSpanIds,
|
|
11683
|
-
...sourceRow.sourceSpanIds,
|
|
11684
|
-
...sourceCell.sourceSpanIds
|
|
11685
|
-
])
|
|
11686
|
-
],
|
|
11687
|
-
bbox: mergedBbox([currentTargetCell, sourceCell]),
|
|
11688
|
-
metadata: {
|
|
11689
|
-
...currentTargetCell.metadata ?? {},
|
|
11690
|
-
visualTableRepair: "merged_continuation"
|
|
11691
|
-
}
|
|
11692
|
-
});
|
|
11693
|
-
mergedIntoCells = true;
|
|
11694
|
-
}
|
|
11695
|
-
if (mergedIntoCells) rowsToRebuildText.add(targetRow.id);
|
|
11696
|
-
}
|
|
11697
|
-
if (!mergedIntoCells && targetCell) {
|
|
11698
|
-
const nextCellText = appendDistinctText(tableCellText(targetCell), sourceText);
|
|
11699
|
-
if (nextCellText) {
|
|
11700
|
-
const mergedNodes = [targetCell, ...sourceNodes];
|
|
11701
|
-
updates.set(targetCell.id, {
|
|
11702
|
-
...currentNode(targetCell.id) ?? targetCell,
|
|
11703
|
-
textExcerpt: nextCellText,
|
|
11704
|
-
description: cleanText([targetCell.title, nextCellText].filter(Boolean).join(" | "), targetCell.description),
|
|
11705
|
-
sourceSpanIds: [.../* @__PURE__ */ new Set([...targetCell.sourceSpanIds, ...sourceSpanIds])],
|
|
11706
|
-
bbox: mergedBbox(mergedNodes),
|
|
11707
|
-
metadata: {
|
|
11708
|
-
...targetCell.metadata ?? {},
|
|
11709
|
-
visualTableRepair: "merged_continuation"
|
|
11710
|
-
}
|
|
11711
|
-
});
|
|
11712
|
-
rowsToRebuildText.add(targetRow.id);
|
|
11713
|
-
}
|
|
11714
|
-
}
|
|
11715
|
-
const fallbackRowText = mergedIntoCells ? void 0 : appendDistinctText(targetRow.textExcerpt ?? targetRow.description, sourceText);
|
|
11716
|
-
updates.set(targetRow.id, {
|
|
11717
|
-
...currentNode(targetRow.id) ?? targetRow,
|
|
11718
|
-
...mergedIntoCells ? {} : {
|
|
11719
|
-
textExcerpt: fallbackRowText,
|
|
11720
|
-
description: cleanText(
|
|
11721
|
-
[targetRow.title, fallbackRowText].filter(Boolean).join(" | "),
|
|
11722
|
-
targetRow.description
|
|
11723
|
-
)
|
|
11724
|
-
},
|
|
11725
|
-
sourceSpanIds: [.../* @__PURE__ */ new Set([...targetRow.sourceSpanIds, ...sourceSpanIds])],
|
|
11726
|
-
bbox: mergedBbox([targetRow, ...sourceNodes]),
|
|
11727
|
-
metadata: {
|
|
11728
|
-
...targetRow.metadata ?? {},
|
|
11729
|
-
visualTableRepair: "merged_continuation"
|
|
11730
|
-
}
|
|
11731
|
-
});
|
|
11732
|
-
removeIds.add(sourceRow.id);
|
|
11733
|
-
for (const sourceCell of sourceCells) removeIds.add(sourceCell.id);
|
|
11734
|
-
}
|
|
11735
|
-
}
|
|
11736
|
-
for (const rowId of rowsToRebuildText) {
|
|
11737
|
-
if (removeIds.has(rowId)) continue;
|
|
11738
|
-
const row = currentNode(rowId);
|
|
11739
|
-
if (!row || row.kind !== "table_row") continue;
|
|
11740
|
-
const cells = (byParent.get(row.id) ?? []).filter((node) => node.kind === "table_cell" && !removeIds.has(node.id)).map((node) => currentNode(node.id) ?? node).sort(
|
|
11741
|
-
(left, right) => tableCellColumnIndex(left, 0) - tableCellColumnIndex(right, 0) || left.order - right.order || left.id.localeCompare(right.id)
|
|
11742
|
-
);
|
|
11743
|
-
const textExcerpt = tableRowTextFromCells(cells);
|
|
11744
|
-
if (!textExcerpt) continue;
|
|
11745
|
-
updates.set(row.id, {
|
|
11746
|
-
...row,
|
|
11747
|
-
textExcerpt,
|
|
11748
|
-
description: cleanText([row.title, textExcerpt].filter(Boolean).join(" | "), row.description)
|
|
11749
|
-
});
|
|
11750
|
-
}
|
|
11751
|
-
if (updates.size === 0 && removeIds.size === 0) return sourceTree;
|
|
11752
|
-
const repaired = sourceTree.filter((node) => !removeIds.has(node.id)).map((node) => updates.get(node.id) ?? node);
|
|
11753
|
-
return normalizeDocumentSourceTreePaths(normalizeContainerEvidenceFromChildren(repaired));
|
|
11754
|
-
}
|
|
11755
|
-
async function runVisualTableRepair(params) {
|
|
11756
|
-
const candidates = visualTableCandidates(params.sourceTree);
|
|
11757
|
-
if (candidates.length === 0) return { sourceTree: params.sourceTree, warnings: [] };
|
|
11758
|
-
let sourceTree = params.sourceTree;
|
|
11759
|
-
const warnings = [];
|
|
11760
|
-
const repairResults = await Promise.all(candidates.map(async (candidate, index) => {
|
|
11761
|
-
const label = `source_tree_visual_table_repair_p${candidate.page}`;
|
|
11762
|
-
try {
|
|
11763
|
-
const budget = params.resolveBudget("extraction_source_tree", 1800);
|
|
11764
|
-
const maxTokens = Math.min(budget.maxTokens, 2400);
|
|
11765
|
-
const startedAt = Date.now();
|
|
11766
|
-
const response = await safeGenerateObject(
|
|
11767
|
-
params.generateObject,
|
|
11768
|
-
{
|
|
11769
|
-
prompt: buildVisualTableRepairPrompt(candidate),
|
|
11770
|
-
schema: SourceTreeVisualTableRepairSchema,
|
|
11771
|
-
maxTokens,
|
|
11772
|
-
taskKind: "extraction_source_tree",
|
|
11773
|
-
budgetDiagnostics: { ...budget, maxTokens },
|
|
11774
|
-
trace: {
|
|
11775
|
-
label,
|
|
11776
|
-
startPage: candidate.page,
|
|
11777
|
-
endPage: candidate.page,
|
|
11778
|
-
batchIndex: index + 1,
|
|
11779
|
-
batchCount: candidates.length,
|
|
11780
|
-
sourceBacked: true
|
|
11781
|
-
}
|
|
11782
|
-
},
|
|
11783
|
-
{
|
|
11784
|
-
fallback: { tables: [], warnings: [] },
|
|
11785
|
-
maxRetries: 0,
|
|
11786
|
-
log: params.log,
|
|
11787
|
-
retry: false
|
|
11788
|
-
}
|
|
11789
|
-
);
|
|
11790
|
-
return {
|
|
11791
|
-
index,
|
|
11792
|
-
candidate,
|
|
11793
|
-
label,
|
|
11794
|
-
maxTokens,
|
|
11795
|
-
durationMs: Date.now() - startedAt,
|
|
11796
|
-
usage: response.usage,
|
|
11797
|
-
repair: response.object
|
|
11798
|
-
};
|
|
11799
|
-
} catch (error) {
|
|
11800
|
-
return {
|
|
11801
|
-
index,
|
|
11802
|
-
candidate,
|
|
11803
|
-
error: error instanceof Error ? error.message : String(error)
|
|
11804
|
-
};
|
|
11805
|
-
}
|
|
11806
|
-
}));
|
|
11807
|
-
for (const result of repairResults.sort((left, right) => left.index - right.index)) {
|
|
11808
|
-
if ("error" in result) {
|
|
11809
|
-
warnings.push(`Visual table repair skipped on page ${result.candidate.page}; parsed table kept (${result.error})`);
|
|
11810
|
-
continue;
|
|
11811
|
-
}
|
|
11812
|
-
params.trackUsage(result.usage, {
|
|
11813
|
-
taskKind: "extraction_source_tree",
|
|
11814
|
-
label: result.label,
|
|
11815
|
-
maxTokens: result.maxTokens,
|
|
11816
|
-
durationMs: result.durationMs
|
|
11817
|
-
});
|
|
11818
|
-
sourceTree = applyVisualTableRepair(sourceTree, result.repair);
|
|
11819
|
-
warnings.push(...result.repair.warnings.map(
|
|
11820
|
-
(warning) => `Visual table repair warning on page ${result.candidate.page}: ${warning}`
|
|
11821
|
-
));
|
|
11822
|
-
}
|
|
11823
|
-
return { sourceTree, warnings };
|
|
11824
|
-
}
|
|
11825
11200
|
function groupNodeId(documentId, group) {
|
|
11826
11201
|
return [
|
|
11827
11202
|
documentId.replace(/[^a-zA-Z0-9_.:-]/g, "_"),
|
|
@@ -11830,6 +11205,17 @@ function groupNodeId(documentId, group) {
|
|
|
11830
11205
|
group.childNodeIds.join("_").replace(/[^a-zA-Z0-9_.:-]/g, "_").slice(0, 80)
|
|
11831
11206
|
].join(":");
|
|
11832
11207
|
}
|
|
11208
|
+
function isDescendantOf(nodeId2, ancestorId, byId) {
|
|
11209
|
+
let parentId = nodeId2 ? byId.get(nodeId2)?.parentId : void 0;
|
|
11210
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11211
|
+
while (parentId) {
|
|
11212
|
+
if (parentId === ancestorId) return true;
|
|
11213
|
+
if (seen.has(parentId)) return false;
|
|
11214
|
+
seen.add(parentId);
|
|
11215
|
+
parentId = byId.get(parentId)?.parentId;
|
|
11216
|
+
}
|
|
11217
|
+
return false;
|
|
11218
|
+
}
|
|
11833
11219
|
function applyOrganization(sourceTree, organization) {
|
|
11834
11220
|
const byId = new Map(sourceTree.map((node) => [node.id, node]));
|
|
11835
11221
|
const labels = new Map(organization.labels.map((label) => [label.nodeId, label]));
|
|
@@ -11847,8 +11233,19 @@ function applyOrganization(sourceTree, organization) {
|
|
|
11847
11233
|
const children = group.childNodeIds.map((id2) => byId.get(id2)).filter((node2) => Boolean(node2));
|
|
11848
11234
|
if (children.length === 0) continue;
|
|
11849
11235
|
if (rejectsOrganizerGroup(group, children)) continue;
|
|
11850
|
-
const
|
|
11851
|
-
|
|
11236
|
+
const originalParentId = children[0].parentId;
|
|
11237
|
+
const requestedParent = group.parentNodeId ? byId.get(group.parentNodeId) : void 0;
|
|
11238
|
+
if (requestedParent && children.some((child) => child.id === requestedParent.id || isDescendantOf(requestedParent.id, child.id, byId))) {
|
|
11239
|
+
continue;
|
|
11240
|
+
}
|
|
11241
|
+
if (requestedParent) {
|
|
11242
|
+
if (!children.every((child) => child.parentId === requestedParent.id || hasAncestor(child, requestedParent.id, byId))) {
|
|
11243
|
+
continue;
|
|
11244
|
+
}
|
|
11245
|
+
} else if (!children.every((child) => child.parentId === originalParentId)) {
|
|
11246
|
+
continue;
|
|
11247
|
+
}
|
|
11248
|
+
const parentId = requestedParent?.id ?? originalParentId;
|
|
11852
11249
|
const documentId = children[0].documentId;
|
|
11853
11250
|
const title = simplifyOrganizerTitle(group.title, group.title, group.kind);
|
|
11854
11251
|
const description = descriptionWithPages(cleanText(group.description, title), children);
|
|
@@ -11950,7 +11347,6 @@ function materializeDocument(params) {
|
|
|
11950
11347
|
retroactiveDate: coverage.retroactiveDate,
|
|
11951
11348
|
formNumber: coverage.formNumber,
|
|
11952
11349
|
sectionRef: coverage.sectionRef,
|
|
11953
|
-
coverageOrigin: coverage.coverageOrigin,
|
|
11954
11350
|
endorsementNumber: coverage.endorsementNumber,
|
|
11955
11351
|
limits: coverage.limits,
|
|
11956
11352
|
sourceSpanIds: coverage.sourceSpanIds,
|
|
@@ -11983,7 +11379,7 @@ function materializeDocument(params) {
|
|
|
11983
11379
|
carrier !== "Unknown" ? carrier : void 0,
|
|
11984
11380
|
policyNumber !== "Unknown" ? `#${policyNumber}` : void 0,
|
|
11985
11381
|
insuredName !== "Unknown" ? `for ${insuredName}` : void 0,
|
|
11986
|
-
profile.
|
|
11382
|
+
profile.policyTypes.length ? `covering ${profile.policyTypes.slice(0, 5).join(", ")}` : void 0
|
|
11987
11383
|
].filter(Boolean).join(" ");
|
|
11988
11384
|
const base = {
|
|
11989
11385
|
id: params.id,
|
|
@@ -12044,6 +11440,68 @@ function materializeDocument(params) {
|
|
|
12044
11440
|
retroactiveDate: valueOf(profile, "retroactiveDate")
|
|
12045
11441
|
};
|
|
12046
11442
|
}
|
|
11443
|
+
function coverageCleanupGroups(profile) {
|
|
11444
|
+
return profile.coverages.length ? [{ id: "all", label: "Coverage schedule cleanup" }] : [];
|
|
11445
|
+
}
|
|
11446
|
+
async function cleanupOperationalCoverageSchedules(params) {
|
|
11447
|
+
const groups = coverageCleanupGroups(params.operationalProfile);
|
|
11448
|
+
const validNodeIds = new Set(params.sourceTree.map((node) => node.id));
|
|
11449
|
+
const validSpanIds = new Set(params.sourceSpans.map((span) => span.id));
|
|
11450
|
+
const results = await Promise.all(groups.map(async (group, groupIndex) => {
|
|
11451
|
+
const budget = params.resolveBudget("extraction_coverage_cleanup", 4096);
|
|
11452
|
+
const startedAt = Date.now();
|
|
11453
|
+
const response = await safeGenerateObject(
|
|
11454
|
+
params.generateObject,
|
|
11455
|
+
{
|
|
11456
|
+
prompt: buildOperationalProfileCleanupPrompt(
|
|
11457
|
+
params.sourceTree,
|
|
11458
|
+
params.operationalProfile,
|
|
11459
|
+
{ label: group.label }
|
|
11460
|
+
),
|
|
11461
|
+
schema: OperationalProfileCleanupSchema,
|
|
11462
|
+
maxTokens: budget.maxTokens,
|
|
11463
|
+
taskKind: "extraction_coverage_cleanup",
|
|
11464
|
+
budgetDiagnostics: budget,
|
|
11465
|
+
providerOptions: params.providerOptions,
|
|
11466
|
+
trace: {
|
|
11467
|
+
phase: "coverage_cleanup",
|
|
11468
|
+
label: group.label,
|
|
11469
|
+
itemCount: params.operationalProfile.coverages.length,
|
|
11470
|
+
coverageGroup: group.id,
|
|
11471
|
+
batchIndex: groups.length > 1 ? groupIndex + 1 : void 0,
|
|
11472
|
+
batchCount: groups.length > 1 ? groups.length : void 0,
|
|
11473
|
+
sourceBacked: true
|
|
11474
|
+
}
|
|
11475
|
+
},
|
|
11476
|
+
{
|
|
11477
|
+
fallback: { coverageDecisions: [], warnings: [] },
|
|
11478
|
+
maxRetries: 0,
|
|
11479
|
+
log: params.log,
|
|
11480
|
+
retry: false
|
|
11481
|
+
}
|
|
11482
|
+
);
|
|
11483
|
+
params.trackUsage(response.usage, {
|
|
11484
|
+
taskKind: "extraction_coverage_cleanup",
|
|
11485
|
+
label: group.id === "all" ? "coverage_cleanup" : `coverage_cleanup_${group.id}`,
|
|
11486
|
+
maxTokens: budget.maxTokens,
|
|
11487
|
+
durationMs: Date.now() - startedAt
|
|
11488
|
+
});
|
|
11489
|
+
return response.object;
|
|
11490
|
+
}));
|
|
11491
|
+
const cleanup = {
|
|
11492
|
+
coverageDecisions: results.flatMap((result) => result.coverageDecisions ?? []),
|
|
11493
|
+
warnings: results.flatMap((result) => result.warnings ?? [])
|
|
11494
|
+
};
|
|
11495
|
+
return {
|
|
11496
|
+
operationalProfile: applyOperationalProfileCleanup(
|
|
11497
|
+
params.operationalProfile,
|
|
11498
|
+
cleanup,
|
|
11499
|
+
validNodeIds,
|
|
11500
|
+
validSpanIds
|
|
11501
|
+
),
|
|
11502
|
+
warnings: cleanup.warnings
|
|
11503
|
+
};
|
|
11504
|
+
}
|
|
12047
11505
|
async function runSourceTreeExtraction(params) {
|
|
12048
11506
|
const sourceSpans = normalizeSourceSpans(params.sourceSpans);
|
|
12049
11507
|
const formHints = normalizeFormHints(params.formInventory?.forms, sourceSpans);
|
|
@@ -12156,15 +11614,7 @@ async function runSourceTreeExtraction(params) {
|
|
|
12156
11614
|
warnings.push(`Source-tree outline cleanup failed; deterministic tree used (${error instanceof Error ? error.message : String(error)})`);
|
|
12157
11615
|
}
|
|
12158
11616
|
}
|
|
12159
|
-
|
|
12160
|
-
sourceTree,
|
|
12161
|
-
generateObject: params.generateObject,
|
|
12162
|
-
resolveBudget: params.resolveBudget,
|
|
12163
|
-
trackUsage: localTrack,
|
|
12164
|
-
log: params.log
|
|
12165
|
-
});
|
|
12166
|
-
sourceTree = normalizeSourceTreeTableDisplayText(visualTableRepair.sourceTree);
|
|
12167
|
-
warnings.push(...visualTableRepair.warnings);
|
|
11617
|
+
sourceTree = normalizeSourceTreeTableDisplayText(sourceTree);
|
|
12168
11618
|
const emptyProfile = emptyOperationalProfile();
|
|
12169
11619
|
let operationalProfile = emptyProfile;
|
|
12170
11620
|
try {
|
|
@@ -12206,45 +11656,18 @@ async function runSourceTreeExtraction(params) {
|
|
|
12206
11656
|
}
|
|
12207
11657
|
if (operationalProfile.coverages.length > 0) {
|
|
12208
11658
|
try {
|
|
12209
|
-
const
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
const startedAt = Date.now();
|
|
12213
|
-
const response = await safeGenerateObject(
|
|
12214
|
-
params.generateObject,
|
|
12215
|
-
{
|
|
12216
|
-
prompt: buildOperationalProfileCleanupPrompt(sourceTree, operationalProfile),
|
|
12217
|
-
schema: OperationalProfileCleanupSchema,
|
|
12218
|
-
maxTokens: budget.maxTokens,
|
|
12219
|
-
taskKind: "extraction_coverage_cleanup",
|
|
12220
|
-
budgetDiagnostics: budget,
|
|
12221
|
-
providerOptions: params.providerOptions,
|
|
12222
|
-
trace: {
|
|
12223
|
-
phase: "coverage_cleanup",
|
|
12224
|
-
label: "Coverage cleanup",
|
|
12225
|
-
itemCount: operationalProfile.coverages.length,
|
|
12226
|
-
sourceBacked: true
|
|
12227
|
-
}
|
|
12228
|
-
},
|
|
12229
|
-
{
|
|
12230
|
-
fallback: { coverageDecisions: [], warnings: [] },
|
|
12231
|
-
maxRetries: 0,
|
|
12232
|
-
log: params.log,
|
|
12233
|
-
retry: false
|
|
12234
|
-
}
|
|
12235
|
-
);
|
|
12236
|
-
localTrack(response.usage, {
|
|
12237
|
-
taskKind: "extraction_coverage_cleanup",
|
|
12238
|
-
label: "coverage_cleanup",
|
|
12239
|
-
maxTokens: budget.maxTokens,
|
|
12240
|
-
durationMs: Date.now() - startedAt
|
|
12241
|
-
});
|
|
12242
|
-
operationalProfile = applyOperationalProfileCleanup(
|
|
11659
|
+
const cleanup = await cleanupOperationalCoverageSchedules({
|
|
11660
|
+
sourceTree,
|
|
11661
|
+
sourceSpans,
|
|
12243
11662
|
operationalProfile,
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
11663
|
+
generateObject: params.generateObject,
|
|
11664
|
+
providerOptions: params.providerOptions,
|
|
11665
|
+
resolveBudget: params.resolveBudget,
|
|
11666
|
+
trackUsage: localTrack,
|
|
11667
|
+
log: params.log
|
|
11668
|
+
});
|
|
11669
|
+
operationalProfile = cleanup.operationalProfile;
|
|
11670
|
+
warnings.push(...cleanup.warnings);
|
|
12248
11671
|
} catch (error) {
|
|
12249
11672
|
warnings.push(`Operational profile cleanup pass failed; uncleaned profile used (${error instanceof Error ? error.message : String(error)})`);
|
|
12250
11673
|
}
|
|
@@ -13055,14 +12478,6 @@ ${pageText}`;
|
|
|
13055
12478
|
mergeMemoryResult(result.name, result.data, memory);
|
|
13056
12479
|
}
|
|
13057
12480
|
}
|
|
13058
|
-
const recoveredCoverages = recoverCoverageScheduleRows({
|
|
13059
|
-
memory,
|
|
13060
|
-
sourceSpans,
|
|
13061
|
-
pageAssignments
|
|
13062
|
-
});
|
|
13063
|
-
if (recoveredCoverages.recovered.length > 0) {
|
|
13064
|
-
await log?.(`Recovered ${recoveredCoverages.recovered.length} source-backed coverage schedule row(s) from table evidence`);
|
|
13065
|
-
}
|
|
13066
12481
|
const planIncludesSupplementary = tasks.some((task) => task.extractorName === "supplementary");
|
|
13067
12482
|
if (!planIncludesSupplementary && hasSupplementaryExtractionSignal(pageAssignments, formInventory, memory)) {
|
|
13068
12483
|
onProgress?.("Extracting supplementary retrieval facts...");
|
|
@@ -13229,14 +12644,6 @@ ${pageText}`;
|
|
|
13229
12644
|
mergeMemoryResult(result.name, result.data, memory);
|
|
13230
12645
|
}
|
|
13231
12646
|
}
|
|
13232
|
-
const recoveredCoverages = recoverCoverageScheduleRows({
|
|
13233
|
-
memory,
|
|
13234
|
-
sourceSpans,
|
|
13235
|
-
pageAssignments
|
|
13236
|
-
});
|
|
13237
|
-
if (recoveredCoverages.recovered.length > 0) {
|
|
13238
|
-
await log?.(`Recovered ${recoveredCoverages.recovered.length} source-backed coverage schedule row(s) from follow-up table evidence`);
|
|
13239
|
-
}
|
|
13240
12647
|
}
|
|
13241
12648
|
} else {
|
|
13242
12649
|
onProgress?.("Skipping LLM extraction review; deterministic checks passed.");
|
|
@@ -17489,7 +16896,7 @@ var COI_GENERATION_TOOL = {
|
|
|
17489
16896
|
};
|
|
17490
16897
|
var COVERAGE_COMPARISON_TOOL = {
|
|
17491
16898
|
name: "coverage_comparison",
|
|
17492
|
-
description: "Compare coverages across two or more insurance documents (policies and/or quotes). Returns a side-by-side comparison of
|
|
16899
|
+
description: "Compare coverages across two or more insurance documents (policies and/or quotes). Returns a side-by-side comparison of policy types, limits, and deductibles.",
|
|
17493
16900
|
input_schema: {
|
|
17494
16901
|
type: "object",
|
|
17495
16902
|
properties: {
|
|
@@ -17498,10 +16905,10 @@ var COVERAGE_COMPARISON_TOOL = {
|
|
|
17498
16905
|
items: { type: "string" },
|
|
17499
16906
|
description: "Array of document IDs (policies or quotes) to compare."
|
|
17500
16907
|
},
|
|
17501
|
-
|
|
16908
|
+
policyTypes: {
|
|
17502
16909
|
type: "array",
|
|
17503
16910
|
items: { type: "string" },
|
|
17504
|
-
description: "Optional filter: only compare these
|
|
16911
|
+
description: "Optional filter: only compare these policy types (e.g. 'General Liability', 'Workers Compensation'). Omit to compare all."
|
|
17505
16912
|
}
|
|
17506
16913
|
},
|
|
17507
16914
|
required: ["documentIds"]
|