@claritylabs/cl-sdk 0.9.0 → 0.10.0
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 +18 -636
- package/dist/index.d.mts +841 -65
- package/dist/index.d.ts +841 -65
- package/dist/index.js +1175 -335
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1169 -335
- package/dist/index.mjs.map +1 -1
- package/dist/storage-sqlite.d.mts +114 -24
- package/dist/storage-sqlite.d.ts +114 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29,7 +29,12 @@ __export(index_exports, {
|
|
|
29
29
|
AdmittedStatusSchema: () => AdmittedStatusSchema,
|
|
30
30
|
AnswerParsingResultSchema: () => AnswerParsingResultSchema,
|
|
31
31
|
ApplicationClassifyResultSchema: () => ApplicationClassifyResultSchema,
|
|
32
|
+
ApplicationEmailReviewSchema: () => ApplicationEmailReviewSchema,
|
|
32
33
|
ApplicationFieldSchema: () => ApplicationFieldSchema,
|
|
34
|
+
ApplicationQualityArtifactSchema: () => ApplicationQualityArtifactSchema,
|
|
35
|
+
ApplicationQualityIssueSchema: () => ApplicationQualityIssueSchema,
|
|
36
|
+
ApplicationQualityReportSchema: () => ApplicationQualityReportSchema,
|
|
37
|
+
ApplicationQualityRoundSchema: () => ApplicationQualityRoundSchema,
|
|
33
38
|
ApplicationStateSchema: () => ApplicationStateSchema,
|
|
34
39
|
AuditTypeSchema: () => AuditTypeSchema,
|
|
35
40
|
AutoFillMatchSchema: () => AutoFillMatchSchema,
|
|
@@ -61,6 +66,7 @@ __export(index_exports, {
|
|
|
61
66
|
CoverageFormSchema: () => CoverageFormSchema,
|
|
62
67
|
CoverageSchema: () => CoverageSchema,
|
|
63
68
|
CoverageTriggerSchema: () => CoverageTriggerSchema,
|
|
69
|
+
CoverageValueTypeSchema: () => CoverageValueTypeSchema,
|
|
64
70
|
CrimeDeclarationsSchema: () => CrimeDeclarationsSchema,
|
|
65
71
|
CyberDeclarationsSchema: () => CyberDeclarationsSchema,
|
|
66
72
|
DEDUCTIBLE_TYPES: () => DEDUCTIBLE_TYPES,
|
|
@@ -730,7 +736,9 @@ var FormReferenceSchema = import_zod3.z.object({
|
|
|
730
736
|
formNumber: import_zod3.z.string(),
|
|
731
737
|
editionDate: import_zod3.z.string().optional(),
|
|
732
738
|
title: import_zod3.z.string().optional(),
|
|
733
|
-
formType: import_zod3.z.enum(["coverage", "endorsement", "declarations", "application", "notice", "other"])
|
|
739
|
+
formType: import_zod3.z.enum(["coverage", "endorsement", "declarations", "application", "notice", "other"]),
|
|
740
|
+
pageStart: import_zod3.z.number().optional(),
|
|
741
|
+
pageEnd: import_zod3.z.number().optional()
|
|
734
742
|
});
|
|
735
743
|
var TaxFeeItemSchema = import_zod3.z.object({
|
|
736
744
|
name: import_zod3.z.string(),
|
|
@@ -767,12 +775,25 @@ var NamedInsuredSchema = import_zod3.z.object({
|
|
|
767
775
|
|
|
768
776
|
// src/schemas/coverage.ts
|
|
769
777
|
var import_zod4 = require("zod");
|
|
778
|
+
var CoverageValueTypeSchema = import_zod4.z.enum([
|
|
779
|
+
"numeric",
|
|
780
|
+
"included",
|
|
781
|
+
"not_included",
|
|
782
|
+
"as_stated",
|
|
783
|
+
"waiting_period",
|
|
784
|
+
"referential",
|
|
785
|
+
"other"
|
|
786
|
+
]);
|
|
770
787
|
var CoverageSchema = import_zod4.z.object({
|
|
771
788
|
name: import_zod4.z.string(),
|
|
772
789
|
limit: import_zod4.z.string(),
|
|
790
|
+
limitValueType: CoverageValueTypeSchema.optional(),
|
|
773
791
|
deductible: import_zod4.z.string().optional(),
|
|
792
|
+
deductibleValueType: CoverageValueTypeSchema.optional(),
|
|
793
|
+
formNumber: import_zod4.z.string().optional(),
|
|
774
794
|
pageNumber: import_zod4.z.number().optional(),
|
|
775
|
-
sectionRef: import_zod4.z.string().optional()
|
|
795
|
+
sectionRef: import_zod4.z.string().optional(),
|
|
796
|
+
originalContent: import_zod4.z.string().optional()
|
|
776
797
|
});
|
|
777
798
|
var EnrichedCoverageSchema = import_zod4.z.object({
|
|
778
799
|
name: import_zod4.z.string(),
|
|
@@ -781,8 +802,10 @@ var EnrichedCoverageSchema = import_zod4.z.object({
|
|
|
781
802
|
formEditionDate: import_zod4.z.string().optional(),
|
|
782
803
|
limit: import_zod4.z.string(),
|
|
783
804
|
limitType: LimitTypeSchema.optional(),
|
|
805
|
+
limitValueType: CoverageValueTypeSchema.optional(),
|
|
784
806
|
deductible: import_zod4.z.string().optional(),
|
|
785
807
|
deductibleType: DeductibleTypeSchema.optional(),
|
|
808
|
+
deductibleValueType: CoverageValueTypeSchema.optional(),
|
|
786
809
|
sir: import_zod4.z.string().optional(),
|
|
787
810
|
sublimit: import_zod4.z.string().optional(),
|
|
788
811
|
coinsurance: import_zod4.z.string().optional(),
|
|
@@ -793,7 +816,8 @@ var EnrichedCoverageSchema = import_zod4.z.object({
|
|
|
793
816
|
included: import_zod4.z.boolean(),
|
|
794
817
|
premium: import_zod4.z.string().optional(),
|
|
795
818
|
pageNumber: import_zod4.z.number().optional(),
|
|
796
|
-
sectionRef: import_zod4.z.string().optional()
|
|
819
|
+
sectionRef: import_zod4.z.string().optional(),
|
|
820
|
+
originalContent: import_zod4.z.string().optional()
|
|
797
821
|
});
|
|
798
822
|
|
|
799
823
|
// src/schemas/endorsement.ts
|
|
@@ -1802,6 +1826,7 @@ function assembleDocument(documentId, documentType, memory) {
|
|
|
1802
1826
|
const lossHistory = memory.get("loss_history");
|
|
1803
1827
|
const sections = memory.get("sections");
|
|
1804
1828
|
const supplementary = memory.get("supplementary");
|
|
1829
|
+
const formInventory = memory.get("form_inventory");
|
|
1805
1830
|
const classify = memory.get("classify");
|
|
1806
1831
|
const base = {
|
|
1807
1832
|
id: documentId,
|
|
@@ -1818,6 +1843,7 @@ function assembleDocument(documentId, documentType, memory) {
|
|
|
1818
1843
|
exclusions: exclusions?.exclusions,
|
|
1819
1844
|
conditions: conditions?.conditions,
|
|
1820
1845
|
sections: sections?.sections,
|
|
1846
|
+
formInventory: formInventory?.forms,
|
|
1821
1847
|
declarations: declarations ? sanitizeNulls(declarations) : void 0,
|
|
1822
1848
|
...sanitizeNulls(lossHistory ?? {})
|
|
1823
1849
|
};
|
|
@@ -2059,6 +2085,11 @@ async function formatDocumentContent(doc, generateText, options) {
|
|
|
2059
2085
|
function chunkDocument(doc) {
|
|
2060
2086
|
const chunks = [];
|
|
2061
2087
|
const docId = doc.id;
|
|
2088
|
+
function stringMetadata(entries) {
|
|
2089
|
+
return Object.fromEntries(
|
|
2090
|
+
Object.entries(entries).filter(([, value]) => value !== void 0 && value !== null && String(value).length > 0).map(([key, value]) => [key, String(value)])
|
|
2091
|
+
);
|
|
2092
|
+
}
|
|
2062
2093
|
chunks.push({
|
|
2063
2094
|
id: `${docId}:carrier_info:0`,
|
|
2064
2095
|
documentId: docId,
|
|
@@ -2070,7 +2101,7 @@ function chunkDocument(doc) {
|
|
|
2070
2101
|
doc.carrierAmBestRating ? `AM Best: ${doc.carrierAmBestRating}` : null,
|
|
2071
2102
|
doc.mga ? `MGA: ${doc.mga}` : null
|
|
2072
2103
|
].filter(Boolean).join("\n"),
|
|
2073
|
-
metadata: { carrier: doc.carrier, documentType: doc.type }
|
|
2104
|
+
metadata: stringMetadata({ carrier: doc.carrier, documentType: doc.type })
|
|
2074
2105
|
});
|
|
2075
2106
|
chunks.push({
|
|
2076
2107
|
id: `${docId}:named_insured:0`,
|
|
@@ -2082,17 +2113,32 @@ function chunkDocument(doc) {
|
|
|
2082
2113
|
doc.insuredFein ? `FEIN: ${doc.insuredFein}` : null,
|
|
2083
2114
|
doc.insuredAddress ? `Address: ${doc.insuredAddress.street1}, ${doc.insuredAddress.city}, ${doc.insuredAddress.state} ${doc.insuredAddress.zip}` : null
|
|
2084
2115
|
].filter(Boolean).join("\n"),
|
|
2085
|
-
metadata: { insuredName: doc.insuredName, documentType: doc.type }
|
|
2116
|
+
metadata: stringMetadata({ insuredName: doc.insuredName, documentType: doc.type })
|
|
2086
2117
|
});
|
|
2087
2118
|
doc.coverages.forEach((cov, i) => {
|
|
2088
2119
|
chunks.push({
|
|
2089
2120
|
id: `${docId}:coverage:${i}`,
|
|
2090
2121
|
documentId: docId,
|
|
2091
2122
|
type: "coverage",
|
|
2092
|
-
text:
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2123
|
+
text: [
|
|
2124
|
+
`Coverage: ${cov.name}`,
|
|
2125
|
+
`Limit: ${cov.limit}`,
|
|
2126
|
+
cov.limitValueType ? `Limit Type: ${cov.limitValueType}` : null,
|
|
2127
|
+
cov.deductible ? `Deductible: ${cov.deductible}` : null,
|
|
2128
|
+
cov.deductibleValueType ? `Deductible Type: ${cov.deductibleValueType}` : null,
|
|
2129
|
+
cov.originalContent ? `Source: ${cov.originalContent}` : null
|
|
2130
|
+
].filter(Boolean).join("\n"),
|
|
2131
|
+
metadata: stringMetadata({
|
|
2132
|
+
coverageName: cov.name,
|
|
2133
|
+
limit: cov.limit,
|
|
2134
|
+
limitValueType: cov.limitValueType,
|
|
2135
|
+
deductible: cov.deductible,
|
|
2136
|
+
deductibleValueType: cov.deductibleValueType,
|
|
2137
|
+
formNumber: cov.formNumber,
|
|
2138
|
+
pageNumber: cov.pageNumber,
|
|
2139
|
+
sectionRef: cov.sectionRef,
|
|
2140
|
+
documentType: doc.type
|
|
2141
|
+
})
|
|
2096
2142
|
});
|
|
2097
2143
|
});
|
|
2098
2144
|
doc.endorsements?.forEach((end, i) => {
|
|
@@ -2102,7 +2148,13 @@ Deductible: ${cov.deductible}` : ""}`,
|
|
|
2102
2148
|
type: "endorsement",
|
|
2103
2149
|
text: `Endorsement: ${end.title}
|
|
2104
2150
|
${end.content}`.trim(),
|
|
2105
|
-
metadata: {
|
|
2151
|
+
metadata: stringMetadata({
|
|
2152
|
+
endorsementType: end.endorsementType,
|
|
2153
|
+
formNumber: end.formNumber,
|
|
2154
|
+
pageStart: end.pageStart,
|
|
2155
|
+
pageEnd: end.pageEnd,
|
|
2156
|
+
documentType: doc.type
|
|
2157
|
+
})
|
|
2106
2158
|
});
|
|
2107
2159
|
});
|
|
2108
2160
|
doc.exclusions?.forEach((exc, i) => {
|
|
@@ -2112,7 +2164,7 @@ ${end.content}`.trim(),
|
|
|
2112
2164
|
type: "exclusion",
|
|
2113
2165
|
text: `Exclusion: ${exc.name}
|
|
2114
2166
|
${exc.content}`.trim(),
|
|
2115
|
-
metadata: { documentType: doc.type }
|
|
2167
|
+
metadata: stringMetadata({ formNumber: exc.formNumber, pageNumber: exc.pageNumber, documentType: doc.type })
|
|
2116
2168
|
});
|
|
2117
2169
|
});
|
|
2118
2170
|
doc.sections?.forEach((sec, i) => {
|
|
@@ -2122,7 +2174,7 @@ ${exc.content}`.trim(),
|
|
|
2122
2174
|
type: "section",
|
|
2123
2175
|
text: `Section: ${sec.title}
|
|
2124
2176
|
${sec.content}`,
|
|
2125
|
-
metadata: { sectionType: sec.type, documentType: doc.type }
|
|
2177
|
+
metadata: stringMetadata({ sectionType: sec.type, pageStart: sec.pageStart, pageEnd: sec.pageEnd, documentType: doc.type })
|
|
2126
2178
|
});
|
|
2127
2179
|
});
|
|
2128
2180
|
if (doc.premium) {
|
|
@@ -2132,7 +2184,7 @@ ${sec.content}`,
|
|
|
2132
2184
|
type: "premium",
|
|
2133
2185
|
text: `Premium: ${doc.premium}${doc.totalCost ? `
|
|
2134
2186
|
Total Cost: ${doc.totalCost}` : ""}`,
|
|
2135
|
-
metadata: { premium: doc.premium, documentType: doc.type }
|
|
2187
|
+
metadata: stringMetadata({ premium: doc.premium, documentType: doc.type })
|
|
2136
2188
|
});
|
|
2137
2189
|
}
|
|
2138
2190
|
return chunks;
|
|
@@ -2184,12 +2236,19 @@ function mergeCoverageLimits(existing, incoming) {
|
|
|
2184
2236
|
const merged = mergeShallowPreferPresent(existing, incoming);
|
|
2185
2237
|
const existingCoverages = Array.isArray(existing.coverages) ? existing.coverages : [];
|
|
2186
2238
|
const incomingCoverages = Array.isArray(incoming.coverages) ? incoming.coverages : [];
|
|
2187
|
-
|
|
2239
|
+
const coverageKey = (coverage) => [
|
|
2188
2240
|
String(coverage.name ?? "").toLowerCase(),
|
|
2189
2241
|
String(coverage.limit ?? "").toLowerCase(),
|
|
2190
2242
|
String(coverage.deductible ?? "").toLowerCase(),
|
|
2191
2243
|
String(coverage.formNumber ?? "").toLowerCase()
|
|
2192
|
-
].join("|")
|
|
2244
|
+
].join("|");
|
|
2245
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2246
|
+
for (const coverage of [...existingCoverages, ...incomingCoverages]) {
|
|
2247
|
+
const key = coverageKey(coverage);
|
|
2248
|
+
const current = byKey.get(key);
|
|
2249
|
+
byKey.set(key, current ? mergeShallowPreferPresent(current, coverage) : coverage);
|
|
2250
|
+
}
|
|
2251
|
+
merged.coverages = [...byKey.values()];
|
|
2193
2252
|
return merged;
|
|
2194
2253
|
}
|
|
2195
2254
|
function mergeDeclarations(existing, incoming) {
|
|
@@ -3046,9 +3105,45 @@ Return JSON only:
|
|
|
3046
3105
|
}`;
|
|
3047
3106
|
}
|
|
3048
3107
|
|
|
3049
|
-
// src/prompts/coordinator/
|
|
3108
|
+
// src/prompts/coordinator/form-inventory.ts
|
|
3050
3109
|
var import_zod19 = require("zod");
|
|
3051
|
-
var
|
|
3110
|
+
var FormInventoryEntrySchema = FormReferenceSchema.extend({
|
|
3111
|
+
formNumber: FormReferenceSchema.shape.formNumber.describe("Form number or identifier, e.g. PR5070CF"),
|
|
3112
|
+
pageStart: FormReferenceSchema.shape.pageStart.describe("Original document page where the form begins"),
|
|
3113
|
+
pageEnd: FormReferenceSchema.shape.pageEnd.describe("Original document page where the form ends")
|
|
3114
|
+
});
|
|
3115
|
+
var FormInventorySchema = import_zod19.z.object({
|
|
3116
|
+
forms: import_zod19.z.array(FormInventoryEntrySchema)
|
|
3117
|
+
});
|
|
3118
|
+
function buildFormInventoryPrompt(templateHints) {
|
|
3119
|
+
return `You are building a form inventory for an insurance document.
|
|
3120
|
+
|
|
3121
|
+
DOCUMENT TYPE HINTS:
|
|
3122
|
+
${templateHints}
|
|
3123
|
+
|
|
3124
|
+
Extract every distinct declarations page set, policy form, coverage form, endorsement, application form, and notice form that appears in the document.
|
|
3125
|
+
|
|
3126
|
+
For EACH form, extract:
|
|
3127
|
+
- formNumber: REQUIRED when present
|
|
3128
|
+
- editionDate: if shown
|
|
3129
|
+
- title: if shown
|
|
3130
|
+
- formType: one of coverage, endorsement, declarations, application, notice, other
|
|
3131
|
+
- pageStart: original page where the form begins
|
|
3132
|
+
- pageEnd: original page where the form ends
|
|
3133
|
+
|
|
3134
|
+
Critical rules:
|
|
3135
|
+
- Include declarations page sets even if they do not show a standard form number.
|
|
3136
|
+
- Use original document page numbers, not local chunk page numbers.
|
|
3137
|
+
- Do not emit duplicate entries for repeated headers/footers.
|
|
3138
|
+
- Multi-page forms should be represented once with pageStart/pageEnd covering the full span when visible.
|
|
3139
|
+
- If a form number is visible in endorsements, schedules, or form headers, include it even if the full form title is partial.
|
|
3140
|
+
|
|
3141
|
+
Respond with JSON only.`;
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
// src/prompts/coordinator/page-map.ts
|
|
3145
|
+
var import_zod20 = require("zod");
|
|
3146
|
+
var PageExtractorSchema = import_zod20.z.enum([
|
|
3052
3147
|
"carrier_info",
|
|
3053
3148
|
"named_insured",
|
|
3054
3149
|
"coverage_limits",
|
|
@@ -3061,23 +3156,37 @@ var PageExtractorSchema = import_zod19.z.enum([
|
|
|
3061
3156
|
"sections",
|
|
3062
3157
|
"supplementary"
|
|
3063
3158
|
]);
|
|
3064
|
-
var PageAssignmentSchema =
|
|
3065
|
-
localPageNumber:
|
|
3066
|
-
extractorNames:
|
|
3067
|
-
|
|
3068
|
-
|
|
3159
|
+
var PageAssignmentSchema = import_zod20.z.object({
|
|
3160
|
+
localPageNumber: import_zod20.z.number().int().positive().describe("1-based page number within this supplied PDF chunk"),
|
|
3161
|
+
extractorNames: import_zod20.z.array(PageExtractorSchema).describe("Focused extractors that should inspect this page"),
|
|
3162
|
+
pageRole: import_zod20.z.enum([
|
|
3163
|
+
"declarations_schedule",
|
|
3164
|
+
"endorsement_schedule",
|
|
3165
|
+
"policy_form",
|
|
3166
|
+
"endorsement_form",
|
|
3167
|
+
"condition_exclusion_form",
|
|
3168
|
+
"supplementary",
|
|
3169
|
+
"other"
|
|
3170
|
+
]).optional().describe("Primary role of the page"),
|
|
3171
|
+
hasScheduleValues: import_zod20.z.boolean().optional().describe("True only when the page contains insured-specific declaration or schedule values, tables, or rows to extract"),
|
|
3172
|
+
confidence: import_zod20.z.number().min(0).max(1).optional().describe("Confidence in the page assignment"),
|
|
3173
|
+
notes: import_zod20.z.string().optional().describe("Short explanation of what appears on the page")
|
|
3069
3174
|
});
|
|
3070
|
-
var PageMapChunkSchema =
|
|
3071
|
-
pages:
|
|
3175
|
+
var PageMapChunkSchema = import_zod20.z.object({
|
|
3176
|
+
pages: import_zod20.z.array(PageAssignmentSchema)
|
|
3072
3177
|
});
|
|
3073
|
-
function buildPageMapPrompt(templateHints, startPage, endPage) {
|
|
3178
|
+
function buildPageMapPrompt(templateHints, startPage, endPage, formInventoryHint) {
|
|
3179
|
+
const inventoryBlock = formInventoryHint ? `
|
|
3180
|
+
FORM INVENTORY (already identified \u2014 use this to constrain your assignments):
|
|
3181
|
+
${formInventoryHint}
|
|
3182
|
+
` : "";
|
|
3074
3183
|
return `You are mapping insurance document pages to focused extractors.
|
|
3075
3184
|
|
|
3076
3185
|
These supplied pages are ORIGINAL DOCUMENT PAGES ${startPage}-${endPage}.
|
|
3077
3186
|
|
|
3078
3187
|
DOCUMENT TYPE HINTS:
|
|
3079
3188
|
${templateHints}
|
|
3080
|
-
|
|
3189
|
+
${inventoryBlock}
|
|
3081
3190
|
For each page in this supplied PDF chunk, decide which extractor(s) should inspect it.
|
|
3082
3191
|
|
|
3083
3192
|
Available extractors:
|
|
@@ -3099,7 +3208,11 @@ Rules:
|
|
|
3099
3208
|
- Avoid assigning broad ranges mentally; decide page by page.
|
|
3100
3209
|
- A page may map to multiple extractors if it legitimately contains multiple relevant sections.
|
|
3101
3210
|
- Prefer declarations and schedules for numeric limits/deductibles over later generic form wording.
|
|
3102
|
-
-
|
|
3211
|
+
- Assign "coverage_limits" only when the page itself contains insured-specific declaration or schedule values to capture, such as location/building rows, coverage tables, limits, deductibles, coinsurance percentages, or scheduled amounts tied to this policy.
|
|
3212
|
+
- Do NOT assign "coverage_limits" for generic policy-form or endorsement text that merely explains how limits, deductibles, waiting periods, or coinsurance work, or that says values are "shown in the declarations", "shown in the schedule", "as stated", or "if applicable".
|
|
3213
|
+
- Headings like "Limits of Insurance", "Deductible", "Coinsurance", "Loss Conditions", or "Definitions" inside a policy form usually indicate form language, not declarations or schedules.
|
|
3214
|
+
- Continuation pages near the end of a form should stay mapped to "sections" plus "conditions"/"exclusions" when applicable, even if they mention limits or deductibles.
|
|
3215
|
+
- When a form inventory entry identifies a page range as a specific form type (e.g., endorsement, coverage, application), use that classification to guide your extractor choice. Do not assign "coverage_limits" to pages the inventory identifies as endorsement or condition/exclusion forms unless the page contains actual schedule values.
|
|
3103
3216
|
- Return every page in the supplied chunk exactly once.
|
|
3104
3217
|
|
|
3105
3218
|
Return JSON:
|
|
@@ -3108,6 +3221,8 @@ Return JSON:
|
|
|
3108
3221
|
{
|
|
3109
3222
|
"localPageNumber": 1,
|
|
3110
3223
|
"extractorNames": ["declarations", "carrier_info", "named_insured", "coverage_limits"],
|
|
3224
|
+
"pageRole": "declarations_schedule",
|
|
3225
|
+
"hasScheduleValues": true,
|
|
3111
3226
|
"confidence": 0.96,
|
|
3112
3227
|
"notes": "Declarations page with insured, policy period, and scheduled limits"
|
|
3113
3228
|
}
|
|
@@ -3116,18 +3231,26 @@ Return JSON:
|
|
|
3116
3231
|
|
|
3117
3232
|
Respond with JSON only.`;
|
|
3118
3233
|
}
|
|
3234
|
+
function formatFormInventoryForPageMap(forms) {
|
|
3235
|
+
if (forms.length === 0) return "";
|
|
3236
|
+
return forms.filter((f) => f.pageStart != null).map((f) => {
|
|
3237
|
+
const range = f.pageEnd && f.pageEnd !== f.pageStart ? `pages ${f.pageStart}-${f.pageEnd}` : `page ${f.pageStart}`;
|
|
3238
|
+
const title = f.title ? ` "${f.title}"` : "";
|
|
3239
|
+
return `- ${f.formNumber}${title} [${f.formType}] \u2192 ${range}`;
|
|
3240
|
+
}).join("\n");
|
|
3241
|
+
}
|
|
3119
3242
|
|
|
3120
3243
|
// src/prompts/coordinator/review.ts
|
|
3121
|
-
var
|
|
3122
|
-
var ReviewResultSchema =
|
|
3123
|
-
complete:
|
|
3124
|
-
missingFields:
|
|
3125
|
-
qualityIssues:
|
|
3126
|
-
additionalTasks:
|
|
3127
|
-
extractorName:
|
|
3128
|
-
startPage:
|
|
3129
|
-
endPage:
|
|
3130
|
-
description:
|
|
3244
|
+
var import_zod21 = require("zod");
|
|
3245
|
+
var ReviewResultSchema = import_zod21.z.object({
|
|
3246
|
+
complete: import_zod21.z.boolean(),
|
|
3247
|
+
missingFields: import_zod21.z.array(import_zod21.z.string()),
|
|
3248
|
+
qualityIssues: import_zod21.z.array(import_zod21.z.string()).optional(),
|
|
3249
|
+
additionalTasks: import_zod21.z.array(import_zod21.z.object({
|
|
3250
|
+
extractorName: import_zod21.z.string(),
|
|
3251
|
+
startPage: import_zod21.z.number(),
|
|
3252
|
+
endPage: import_zod21.z.number(),
|
|
3253
|
+
description: import_zod21.z.string()
|
|
3131
3254
|
}))
|
|
3132
3255
|
});
|
|
3133
3256
|
function buildReviewPrompt(templateExpected, extractedKeys, extractionSummary, pageMapSummary) {
|
|
@@ -3174,20 +3297,20 @@ Respond with JSON only.`;
|
|
|
3174
3297
|
}
|
|
3175
3298
|
|
|
3176
3299
|
// src/prompts/extractors/carrier-info.ts
|
|
3177
|
-
var
|
|
3178
|
-
var CarrierInfoSchema =
|
|
3179
|
-
carrierName:
|
|
3180
|
-
carrierLegalName:
|
|
3181
|
-
naicNumber:
|
|
3182
|
-
amBestRating:
|
|
3183
|
-
admittedStatus:
|
|
3184
|
-
mga:
|
|
3185
|
-
underwriter:
|
|
3186
|
-
policyNumber:
|
|
3187
|
-
effectiveDate:
|
|
3188
|
-
expirationDate:
|
|
3189
|
-
quoteNumber:
|
|
3190
|
-
proposedEffectiveDate:
|
|
3300
|
+
var import_zod22 = require("zod");
|
|
3301
|
+
var CarrierInfoSchema = import_zod22.z.object({
|
|
3302
|
+
carrierName: import_zod22.z.string().describe("Primary insurance company name for display"),
|
|
3303
|
+
carrierLegalName: import_zod22.z.string().optional().describe("Legal entity name of insurer"),
|
|
3304
|
+
naicNumber: import_zod22.z.string().optional().describe("NAIC company code"),
|
|
3305
|
+
amBestRating: import_zod22.z.string().optional().describe("AM Best rating, e.g. 'A+ XV'"),
|
|
3306
|
+
admittedStatus: import_zod22.z.enum(["admitted", "non_admitted", "surplus_lines"]).optional().describe("Admitted status of the carrier"),
|
|
3307
|
+
mga: import_zod22.z.string().optional().describe("Managing General Agent or Program Administrator name"),
|
|
3308
|
+
underwriter: import_zod22.z.string().optional().describe("Named individual underwriter"),
|
|
3309
|
+
policyNumber: import_zod22.z.string().optional().describe("Policy or quote reference number"),
|
|
3310
|
+
effectiveDate: import_zod22.z.string().optional().describe("Policy effective date (MM/DD/YYYY)"),
|
|
3311
|
+
expirationDate: import_zod22.z.string().optional().describe("Policy expiration date (MM/DD/YYYY)"),
|
|
3312
|
+
quoteNumber: import_zod22.z.string().optional().describe("Quote or proposal reference number"),
|
|
3313
|
+
proposedEffectiveDate: import_zod22.z.string().optional().describe("Proposed effective date for quotes (MM/DD/YYYY)")
|
|
3191
3314
|
});
|
|
3192
3315
|
function buildCarrierInfoPrompt() {
|
|
3193
3316
|
return `You are an expert insurance document analyst. Extract carrier and policy identification information from this document.
|
|
@@ -3207,18 +3330,18 @@ Return JSON only.`;
|
|
|
3207
3330
|
}
|
|
3208
3331
|
|
|
3209
3332
|
// src/prompts/extractors/named-insured.ts
|
|
3210
|
-
var
|
|
3211
|
-
var AddressSchema2 =
|
|
3212
|
-
street1:
|
|
3213
|
-
city:
|
|
3214
|
-
state:
|
|
3215
|
-
zip:
|
|
3333
|
+
var import_zod23 = require("zod");
|
|
3334
|
+
var AddressSchema2 = import_zod23.z.object({
|
|
3335
|
+
street1: import_zod23.z.string(),
|
|
3336
|
+
city: import_zod23.z.string(),
|
|
3337
|
+
state: import_zod23.z.string(),
|
|
3338
|
+
zip: import_zod23.z.string()
|
|
3216
3339
|
});
|
|
3217
|
-
var NamedInsuredSchema2 =
|
|
3218
|
-
insuredName:
|
|
3219
|
-
insuredDba:
|
|
3340
|
+
var NamedInsuredSchema2 = import_zod23.z.object({
|
|
3341
|
+
insuredName: import_zod23.z.string().describe("Name of primary named insured"),
|
|
3342
|
+
insuredDba: import_zod23.z.string().optional().describe("Doing-business-as name"),
|
|
3220
3343
|
insuredAddress: AddressSchema2.optional().describe("Primary insured mailing address"),
|
|
3221
|
-
insuredEntityType:
|
|
3344
|
+
insuredEntityType: import_zod23.z.enum([
|
|
3222
3345
|
"corporation",
|
|
3223
3346
|
"llc",
|
|
3224
3347
|
"partnership",
|
|
@@ -3231,13 +3354,13 @@ var NamedInsuredSchema2 = import_zod22.z.object({
|
|
|
3231
3354
|
"married_couple",
|
|
3232
3355
|
"other"
|
|
3233
3356
|
]).optional().describe("Legal entity type of the insured"),
|
|
3234
|
-
insuredFein:
|
|
3235
|
-
insuredSicCode:
|
|
3236
|
-
insuredNaicsCode:
|
|
3237
|
-
additionalNamedInsureds:
|
|
3238
|
-
|
|
3239
|
-
name:
|
|
3240
|
-
relationship:
|
|
3357
|
+
insuredFein: import_zod23.z.string().optional().describe("Federal Employer Identification Number"),
|
|
3358
|
+
insuredSicCode: import_zod23.z.string().optional().describe("SIC code"),
|
|
3359
|
+
insuredNaicsCode: import_zod23.z.string().optional().describe("NAICS code"),
|
|
3360
|
+
additionalNamedInsureds: import_zod23.z.array(
|
|
3361
|
+
import_zod23.z.object({
|
|
3362
|
+
name: import_zod23.z.string(),
|
|
3363
|
+
relationship: import_zod23.z.string().optional().describe("e.g. subsidiary, affiliate"),
|
|
3241
3364
|
address: AddressSchema2.optional()
|
|
3242
3365
|
})
|
|
3243
3366
|
).optional().describe("Additional named insureds listed on the policy")
|
|
@@ -3258,23 +3381,20 @@ Return JSON only.`;
|
|
|
3258
3381
|
}
|
|
3259
3382
|
|
|
3260
3383
|
// src/prompts/extractors/coverage-limits.ts
|
|
3261
|
-
var
|
|
3262
|
-
var
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
formNumber: import_zod23.z.string().optional().describe("Associated form number, e.g. 'CG 00 01'")
|
|
3270
|
-
})
|
|
3271
|
-
).describe("All coverages with their limits"),
|
|
3272
|
-
coverageForm: import_zod23.z.enum(["occurrence", "claims_made", "accident"]).optional().describe("Primary coverage trigger type"),
|
|
3273
|
-
retroactiveDate: import_zod23.z.string().optional().describe("Retroactive date for claims-made policies (MM/DD/YYYY)")
|
|
3384
|
+
var import_zod24 = require("zod");
|
|
3385
|
+
var ExtractorCoverageSchema = CoverageSchema.extend({
|
|
3386
|
+
coverageCode: import_zod24.z.string().optional().describe("Coverage code or class code")
|
|
3387
|
+
});
|
|
3388
|
+
var CoverageLimitsSchema = import_zod24.z.object({
|
|
3389
|
+
coverages: import_zod24.z.array(ExtractorCoverageSchema).describe("All coverages with their limits"),
|
|
3390
|
+
coverageForm: import_zod24.z.enum(["occurrence", "claims_made", "accident"]).optional().describe("Primary coverage trigger type"),
|
|
3391
|
+
retroactiveDate: import_zod24.z.string().optional().describe("Retroactive date for claims-made policies (MM/DD/YYYY)")
|
|
3274
3392
|
});
|
|
3275
3393
|
function buildCoverageLimitsPrompt() {
|
|
3276
3394
|
return `You are an expert insurance document analyst. Extract all coverage limits and deductibles from this document.
|
|
3277
3395
|
|
|
3396
|
+
Extract only insured-specific declaration, schedule, or endorsement entries that state actual coverage terms for this policy.
|
|
3397
|
+
|
|
3278
3398
|
Focus on:
|
|
3279
3399
|
- Every coverage listed on the declarations page or coverage schedule
|
|
3280
3400
|
- Per-occurrence, aggregate, and sub-limits for each coverage
|
|
@@ -3285,20 +3405,34 @@ Focus on:
|
|
|
3285
3405
|
- Standard limit fields: per occurrence, general aggregate, products/completed ops aggregate, personal & advertising injury, fire damage, medical expense, combined single limit, BI/PD splits, umbrella each occurrence/aggregate/retention, statutory (WC), employers liability
|
|
3286
3406
|
- Defense cost treatment: inside limits, outside limits, or supplementary
|
|
3287
3407
|
|
|
3288
|
-
|
|
3408
|
+
For EACH coverage, also extract:
|
|
3409
|
+
- pageNumber: the original page number where the coverage row/value appears
|
|
3410
|
+
- sectionRef: the declarations/schedule/endorsement section heading where it appears
|
|
3411
|
+
- originalContent: the verbatim row or short source snippet used for this coverage
|
|
3412
|
+
- limitValueType: classify the limit as numeric, included, not_included, as_stated, waiting_period, referential, or other
|
|
3413
|
+
- deductibleValueType: classify the deductible/value term similarly when deductible is present
|
|
3414
|
+
|
|
3415
|
+
Critical rules:
|
|
3416
|
+
- Do not extract table-of-contents lines, index entries, headers, footers, page labels, or cross-references as coverages.
|
|
3417
|
+
- Do not create a coverage entry from generic policy-form text that only says a limit/deductible is "shown in the declarations", "shown in the Business Income Declarations", "as stated", "if applicable", or similar referential wording.
|
|
3418
|
+
- Do not treat a generic waiting period, deductible explanation, limits clause, coinsurance clause, or definitions text as a standalone coverage unless the page contains an actual policy-specific schedule row or declaration entry.
|
|
3419
|
+
- Values like "Included" or "Not Included" are valid only when they appear as an explicit declarations/schedule/endorsement entry for a named coverage. Do not infer them from narrative form language.
|
|
3420
|
+
- If a waiting period or hour deductible is shown as part of a specific declarations/schedule row, it may be captured in deductible. Otherwise omit it.
|
|
3421
|
+
- Use limitValueType or deductibleValueType to preserve non-numeric terms precisely instead of forcing them into numeric semantics.
|
|
3422
|
+
- Preserve one row per real coverage entry. Do not merge adjacent schedule rows into malformed names.
|
|
3289
3423
|
|
|
3290
3424
|
Return JSON only.`;
|
|
3291
3425
|
}
|
|
3292
3426
|
|
|
3293
3427
|
// src/prompts/extractors/endorsements.ts
|
|
3294
|
-
var
|
|
3295
|
-
var EndorsementsSchema =
|
|
3296
|
-
endorsements:
|
|
3297
|
-
|
|
3298
|
-
formNumber:
|
|
3299
|
-
editionDate:
|
|
3300
|
-
title:
|
|
3301
|
-
endorsementType:
|
|
3428
|
+
var import_zod25 = require("zod");
|
|
3429
|
+
var EndorsementsSchema = import_zod25.z.object({
|
|
3430
|
+
endorsements: import_zod25.z.array(
|
|
3431
|
+
import_zod25.z.object({
|
|
3432
|
+
formNumber: import_zod25.z.string().describe("Form number, e.g. 'CG 21 47'"),
|
|
3433
|
+
editionDate: import_zod25.z.string().optional().describe("Edition date, e.g. '12 07'"),
|
|
3434
|
+
title: import_zod25.z.string().describe("Endorsement title"),
|
|
3435
|
+
endorsementType: import_zod25.z.enum([
|
|
3302
3436
|
"additional_insured",
|
|
3303
3437
|
"waiver_of_subrogation",
|
|
3304
3438
|
"primary_noncontributory",
|
|
@@ -3318,12 +3452,12 @@ var EndorsementsSchema = import_zod24.z.object({
|
|
|
3318
3452
|
"territorial_extension",
|
|
3319
3453
|
"other"
|
|
3320
3454
|
]).describe("Endorsement type classification"),
|
|
3321
|
-
effectiveDate:
|
|
3322
|
-
affectedCoverageParts:
|
|
3323
|
-
namedParties:
|
|
3324
|
-
|
|
3325
|
-
name:
|
|
3326
|
-
role:
|
|
3455
|
+
effectiveDate: import_zod25.z.string().optional().describe("Endorsement effective date"),
|
|
3456
|
+
affectedCoverageParts: import_zod25.z.array(import_zod25.z.string()).optional().describe("Coverage parts affected by this endorsement"),
|
|
3457
|
+
namedParties: import_zod25.z.array(
|
|
3458
|
+
import_zod25.z.object({
|
|
3459
|
+
name: import_zod25.z.string().describe("Party name"),
|
|
3460
|
+
role: import_zod25.z.enum([
|
|
3327
3461
|
"additional_insured",
|
|
3328
3462
|
"loss_payee",
|
|
3329
3463
|
"mortgage_holder",
|
|
@@ -3332,15 +3466,15 @@ var EndorsementsSchema = import_zod24.z.object({
|
|
|
3332
3466
|
"designated_person",
|
|
3333
3467
|
"other"
|
|
3334
3468
|
]).describe("Party role"),
|
|
3335
|
-
relationship:
|
|
3336
|
-
scope:
|
|
3469
|
+
relationship: import_zod25.z.string().optional().describe("Relationship to insured"),
|
|
3470
|
+
scope: import_zod25.z.string().optional().describe("Scope of coverage for this party")
|
|
3337
3471
|
})
|
|
3338
3472
|
).optional().describe("Named parties (additional insureds, loss payees, etc.)"),
|
|
3339
|
-
keyTerms:
|
|
3340
|
-
premiumImpact:
|
|
3341
|
-
content:
|
|
3342
|
-
pageStart:
|
|
3343
|
-
pageEnd:
|
|
3473
|
+
keyTerms: import_zod25.z.array(import_zod25.z.string()).optional().describe("Key terms or notable provisions in the endorsement"),
|
|
3474
|
+
premiumImpact: import_zod25.z.string().optional().describe("Additional premium or credit"),
|
|
3475
|
+
content: import_zod25.z.string().describe("Full verbatim text of the endorsement"),
|
|
3476
|
+
pageStart: import_zod25.z.number().describe("Starting page number of this endorsement"),
|
|
3477
|
+
pageEnd: import_zod25.z.number().optional().describe("Ending page number of this endorsement")
|
|
3344
3478
|
})
|
|
3345
3479
|
).describe("All endorsements found in the document")
|
|
3346
3480
|
});
|
|
@@ -3371,20 +3505,20 @@ Return JSON only.`;
|
|
|
3371
3505
|
}
|
|
3372
3506
|
|
|
3373
3507
|
// src/prompts/extractors/exclusions.ts
|
|
3374
|
-
var
|
|
3375
|
-
var ExclusionsSchema =
|
|
3376
|
-
exclusions:
|
|
3377
|
-
|
|
3378
|
-
name:
|
|
3379
|
-
formNumber:
|
|
3380
|
-
excludedPerils:
|
|
3381
|
-
isAbsolute:
|
|
3382
|
-
exceptions:
|
|
3383
|
-
buybackAvailable:
|
|
3384
|
-
buybackEndorsement:
|
|
3385
|
-
appliesTo:
|
|
3386
|
-
content:
|
|
3387
|
-
pageNumber:
|
|
3508
|
+
var import_zod26 = require("zod");
|
|
3509
|
+
var ExclusionsSchema = import_zod26.z.object({
|
|
3510
|
+
exclusions: import_zod26.z.array(
|
|
3511
|
+
import_zod26.z.object({
|
|
3512
|
+
name: import_zod26.z.string().describe("Exclusion title or short description"),
|
|
3513
|
+
formNumber: import_zod26.z.string().optional().describe("Form number if part of a named endorsement"),
|
|
3514
|
+
excludedPerils: import_zod26.z.array(import_zod26.z.string()).optional().describe("Specific perils excluded"),
|
|
3515
|
+
isAbsolute: import_zod26.z.boolean().optional().describe("Whether the exclusion is absolute (no exceptions)"),
|
|
3516
|
+
exceptions: import_zod26.z.array(import_zod26.z.string()).optional().describe("Exceptions to the exclusion, if any"),
|
|
3517
|
+
buybackAvailable: import_zod26.z.boolean().optional().describe("Whether coverage can be bought back via endorsement"),
|
|
3518
|
+
buybackEndorsement: import_zod26.z.string().optional().describe("Form number of the buyback endorsement if available"),
|
|
3519
|
+
appliesTo: import_zod26.z.array(import_zod26.z.string()).optional().describe("Coverage types this exclusion applies to"),
|
|
3520
|
+
content: import_zod26.z.string().describe("Full verbatim exclusion text"),
|
|
3521
|
+
pageNumber: import_zod26.z.number().optional().describe("Page number where exclusion appears")
|
|
3388
3522
|
})
|
|
3389
3523
|
).describe("All exclusions found in the document")
|
|
3390
3524
|
});
|
|
@@ -3409,18 +3543,23 @@ Focus on:
|
|
|
3409
3543
|
- Exclusions within insuring agreements or conditions if clearly labeled
|
|
3410
3544
|
- Full verbatim exclusion text \u2014 do not summarize
|
|
3411
3545
|
|
|
3546
|
+
Critical rules:
|
|
3547
|
+
- Ignore table-of-contents entries, running headers/footers, and references that only point to another page or section.
|
|
3548
|
+
- Do not emit a standalone exclusion from a fragment unless the fragment itself contains substantive exclusion wording.
|
|
3549
|
+
- Always include pageNumber when the exclusion appears on a specific page in the supplied document chunk.
|
|
3550
|
+
|
|
3412
3551
|
Common personal lines exclusion patterns: animal liability, business pursuits, home daycare, watercraft, aircraft.
|
|
3413
3552
|
|
|
3414
3553
|
Return JSON only.`;
|
|
3415
3554
|
}
|
|
3416
3555
|
|
|
3417
3556
|
// src/prompts/extractors/conditions.ts
|
|
3418
|
-
var
|
|
3419
|
-
var ConditionsSchema =
|
|
3420
|
-
conditions:
|
|
3421
|
-
|
|
3422
|
-
name:
|
|
3423
|
-
conditionType:
|
|
3557
|
+
var import_zod27 = require("zod");
|
|
3558
|
+
var ConditionsSchema = import_zod27.z.object({
|
|
3559
|
+
conditions: import_zod27.z.array(
|
|
3560
|
+
import_zod27.z.object({
|
|
3561
|
+
name: import_zod27.z.string().describe("Condition title"),
|
|
3562
|
+
conditionType: import_zod27.z.enum([
|
|
3424
3563
|
"duties_after_loss",
|
|
3425
3564
|
"notice_requirements",
|
|
3426
3565
|
"other_insurance",
|
|
@@ -3439,14 +3578,14 @@ var ConditionsSchema = import_zod26.z.object({
|
|
|
3439
3578
|
"separation_of_insureds",
|
|
3440
3579
|
"other"
|
|
3441
3580
|
]).describe("Condition category"),
|
|
3442
|
-
content:
|
|
3443
|
-
keyValues:
|
|
3444
|
-
|
|
3445
|
-
key:
|
|
3446
|
-
value:
|
|
3581
|
+
content: import_zod27.z.string().describe("Full verbatim condition text"),
|
|
3582
|
+
keyValues: import_zod27.z.array(
|
|
3583
|
+
import_zod27.z.object({
|
|
3584
|
+
key: import_zod27.z.string().describe("Key name (e.g. 'noticePeriod', 'suitDeadline')"),
|
|
3585
|
+
value: import_zod27.z.string().describe("Value (e.g. '30 days', '2 years')")
|
|
3447
3586
|
})
|
|
3448
3587
|
).optional().describe("Key values extracted from the condition (notice periods, deadlines, etc.)"),
|
|
3449
|
-
pageNumber:
|
|
3588
|
+
pageNumber: import_zod27.z.number().optional().describe("Page number where condition appears")
|
|
3450
3589
|
})
|
|
3451
3590
|
).describe("All policy conditions found in the document")
|
|
3452
3591
|
});
|
|
@@ -3458,7 +3597,7 @@ For EACH condition, extract:
|
|
|
3458
3597
|
- conditionType: classify as one of: duties_after_loss, notice_requirements, other_insurance, cancellation, nonrenewal, transfer_of_rights, liberalization, arbitration, concealment_fraud, examination_under_oath, legal_action, loss_payment, appraisal, mortgage_holders, policy_territory, separation_of_insureds, other \u2014 REQUIRED
|
|
3459
3598
|
- content: full verbatim condition text \u2014 REQUIRED
|
|
3460
3599
|
- keyValues: extract specific values as key-value pairs (e.g. noticePeriod: "30 days", suitDeadline: "2 years")
|
|
3461
|
-
- pageNumber: page number where the condition appears
|
|
3600
|
+
- pageNumber: original document page number where the substantive condition text appears
|
|
3462
3601
|
|
|
3463
3602
|
Focus on:
|
|
3464
3603
|
- Duties after loss / notice of occurrence conditions
|
|
@@ -3475,32 +3614,37 @@ Focus on:
|
|
|
3475
3614
|
- Mortgage holders clause
|
|
3476
3615
|
- Any other named conditions
|
|
3477
3616
|
|
|
3617
|
+
Critical rules:
|
|
3618
|
+
- Ignore table-of-contents entries, section indexes, running headers/footers, and page references such as "Appraisal ..... 19".
|
|
3619
|
+
- Do not emit a condition unless the page contains substantive condition text, not just a heading or reference.
|
|
3620
|
+
- If a condition continues from a prior page, keep the substantive text together and use the page where the condition text appears in this extracted chunk.
|
|
3621
|
+
|
|
3478
3622
|
Return JSON only.`;
|
|
3479
3623
|
}
|
|
3480
3624
|
|
|
3481
3625
|
// src/prompts/extractors/premium-breakdown.ts
|
|
3482
|
-
var
|
|
3483
|
-
var PremiumBreakdownSchema =
|
|
3484
|
-
premium:
|
|
3485
|
-
totalCost:
|
|
3486
|
-
premiumBreakdown:
|
|
3487
|
-
|
|
3488
|
-
line:
|
|
3489
|
-
amount:
|
|
3626
|
+
var import_zod28 = require("zod");
|
|
3627
|
+
var PremiumBreakdownSchema = import_zod28.z.object({
|
|
3628
|
+
premium: import_zod28.z.string().optional().describe("Total premium amount, e.g. '$5,000'"),
|
|
3629
|
+
totalCost: import_zod28.z.string().optional().describe("Total cost including taxes and fees, e.g. '$5,250'"),
|
|
3630
|
+
premiumBreakdown: import_zod28.z.array(
|
|
3631
|
+
import_zod28.z.object({
|
|
3632
|
+
line: import_zod28.z.string().describe("Coverage line name"),
|
|
3633
|
+
amount: import_zod28.z.string().describe("Premium amount for this line")
|
|
3490
3634
|
})
|
|
3491
3635
|
).optional().describe("Per-coverage-line premium breakdown"),
|
|
3492
|
-
taxesAndFees:
|
|
3493
|
-
|
|
3494
|
-
name:
|
|
3495
|
-
amount:
|
|
3496
|
-
type:
|
|
3636
|
+
taxesAndFees: import_zod28.z.array(
|
|
3637
|
+
import_zod28.z.object({
|
|
3638
|
+
name: import_zod28.z.string().describe("Fee or tax name"),
|
|
3639
|
+
amount: import_zod28.z.string().describe("Dollar amount"),
|
|
3640
|
+
type: import_zod28.z.enum(["tax", "fee", "surcharge", "assessment"]).optional().describe("Fee category")
|
|
3497
3641
|
})
|
|
3498
3642
|
).optional().describe("Taxes, fees, surcharges, and assessments"),
|
|
3499
|
-
minimumPremium:
|
|
3500
|
-
depositPremium:
|
|
3501
|
-
paymentPlan:
|
|
3502
|
-
auditType:
|
|
3503
|
-
ratingBasis:
|
|
3643
|
+
minimumPremium: import_zod28.z.string().optional().describe("Minimum premium if stated"),
|
|
3644
|
+
depositPremium: import_zod28.z.string().optional().describe("Deposit premium if stated"),
|
|
3645
|
+
paymentPlan: import_zod28.z.string().optional().describe("Payment plan description"),
|
|
3646
|
+
auditType: import_zod28.z.enum(["annual", "semi_annual", "quarterly", "monthly", "final", "self"]).optional().describe("Premium audit type"),
|
|
3647
|
+
ratingBasis: import_zod28.z.string().optional().describe("Rating basis, e.g. payroll, revenue, area, units")
|
|
3504
3648
|
});
|
|
3505
3649
|
function buildPremiumBreakdownPrompt() {
|
|
3506
3650
|
return `You are an expert insurance document analyst. Extract all premium and cost information from this document.
|
|
@@ -3520,14 +3664,14 @@ Return JSON only.`;
|
|
|
3520
3664
|
}
|
|
3521
3665
|
|
|
3522
3666
|
// src/prompts/extractors/declarations.ts
|
|
3523
|
-
var
|
|
3524
|
-
var DeclarationsFieldSchema =
|
|
3525
|
-
field:
|
|
3526
|
-
value:
|
|
3527
|
-
section:
|
|
3667
|
+
var import_zod29 = require("zod");
|
|
3668
|
+
var DeclarationsFieldSchema = import_zod29.z.object({
|
|
3669
|
+
field: import_zod29.z.string().describe("Descriptive field name (e.g. 'policyNumber', 'effectiveDate', 'coverageALimit')"),
|
|
3670
|
+
value: import_zod29.z.string().describe("Extracted value exactly as it appears in the document"),
|
|
3671
|
+
section: import_zod29.z.string().optional().describe("Section or grouping this field belongs to (e.g. 'Coverage Limits', 'Vehicle Schedule')")
|
|
3528
3672
|
});
|
|
3529
|
-
var DeclarationsExtractSchema =
|
|
3530
|
-
fields:
|
|
3673
|
+
var DeclarationsExtractSchema = import_zod29.z.object({
|
|
3674
|
+
fields: import_zod29.z.array(DeclarationsFieldSchema).describe("All declarations page fields extracted as key-value pairs. Structure varies by line of business.")
|
|
3531
3675
|
});
|
|
3532
3676
|
function buildDeclarationsPrompt() {
|
|
3533
3677
|
return `You are an expert insurance document analyst. Extract all declarations page data from this document into a flexible key-value structure.
|
|
@@ -3567,21 +3711,21 @@ Preserve original values exactly as they appear. Return JSON only.`;
|
|
|
3567
3711
|
}
|
|
3568
3712
|
|
|
3569
3713
|
// src/prompts/extractors/loss-history.ts
|
|
3570
|
-
var
|
|
3571
|
-
var LossHistorySchema =
|
|
3572
|
-
lossSummary:
|
|
3573
|
-
individualClaims:
|
|
3574
|
-
|
|
3575
|
-
date:
|
|
3576
|
-
type:
|
|
3577
|
-
description:
|
|
3578
|
-
amountPaid:
|
|
3579
|
-
amountReserved:
|
|
3580
|
-
status:
|
|
3581
|
-
claimNumber:
|
|
3714
|
+
var import_zod30 = require("zod");
|
|
3715
|
+
var LossHistorySchema = import_zod30.z.object({
|
|
3716
|
+
lossSummary: import_zod30.z.string().optional().describe("Summary of loss history, e.g. '3 claims in past 5 years totaling $125,000'"),
|
|
3717
|
+
individualClaims: import_zod30.z.array(
|
|
3718
|
+
import_zod30.z.object({
|
|
3719
|
+
date: import_zod30.z.string().optional().describe("Date of loss or claim"),
|
|
3720
|
+
type: import_zod30.z.string().optional().describe("Type of claim, e.g. 'property damage', 'bodily injury'"),
|
|
3721
|
+
description: import_zod30.z.string().optional().describe("Brief description of the claim"),
|
|
3722
|
+
amountPaid: import_zod30.z.string().optional().describe("Amount paid"),
|
|
3723
|
+
amountReserved: import_zod30.z.string().optional().describe("Amount reserved"),
|
|
3724
|
+
status: import_zod30.z.enum(["open", "closed", "reopened"]).optional().describe("Claim status"),
|
|
3725
|
+
claimNumber: import_zod30.z.string().optional().describe("Claim reference number")
|
|
3582
3726
|
})
|
|
3583
3727
|
).optional().describe("Individual claim records"),
|
|
3584
|
-
experienceMod:
|
|
3728
|
+
experienceMod: import_zod30.z.string().optional().describe("Experience modification factor for workers comp, e.g. '0.85'")
|
|
3585
3729
|
});
|
|
3586
3730
|
function buildLossHistoryPrompt() {
|
|
3587
3731
|
return `You are an expert insurance document analyst. Extract all loss history and claims information from this document.
|
|
@@ -3598,18 +3742,18 @@ Return JSON only.`;
|
|
|
3598
3742
|
}
|
|
3599
3743
|
|
|
3600
3744
|
// src/prompts/extractors/sections.ts
|
|
3601
|
-
var
|
|
3602
|
-
var SubsectionSchema2 =
|
|
3603
|
-
title:
|
|
3604
|
-
sectionNumber:
|
|
3605
|
-
pageNumber:
|
|
3606
|
-
content:
|
|
3745
|
+
var import_zod31 = require("zod");
|
|
3746
|
+
var SubsectionSchema2 = import_zod31.z.object({
|
|
3747
|
+
title: import_zod31.z.string().describe("Subsection title"),
|
|
3748
|
+
sectionNumber: import_zod31.z.string().optional().describe("Subsection number"),
|
|
3749
|
+
pageNumber: import_zod31.z.number().optional().describe("Page number"),
|
|
3750
|
+
content: import_zod31.z.string().describe("Full verbatim text")
|
|
3607
3751
|
});
|
|
3608
|
-
var SectionsSchema =
|
|
3609
|
-
sections:
|
|
3610
|
-
|
|
3611
|
-
title:
|
|
3612
|
-
type:
|
|
3752
|
+
var SectionsSchema = import_zod31.z.object({
|
|
3753
|
+
sections: import_zod31.z.array(
|
|
3754
|
+
import_zod31.z.object({
|
|
3755
|
+
title: import_zod31.z.string().describe("Section title"),
|
|
3756
|
+
type: import_zod31.z.enum([
|
|
3613
3757
|
"declarations",
|
|
3614
3758
|
"insuring_agreement",
|
|
3615
3759
|
"policy_form",
|
|
@@ -3623,10 +3767,10 @@ var SectionsSchema = import_zod30.z.object({
|
|
|
3623
3767
|
"regulatory",
|
|
3624
3768
|
"other"
|
|
3625
3769
|
]).describe("Section type classification"),
|
|
3626
|
-
content:
|
|
3627
|
-
pageStart:
|
|
3628
|
-
pageEnd:
|
|
3629
|
-
subsections:
|
|
3770
|
+
content: import_zod31.z.string().describe("Full verbatim text of the section"),
|
|
3771
|
+
pageStart: import_zod31.z.number().describe("Starting page number"),
|
|
3772
|
+
pageEnd: import_zod31.z.number().optional().describe("Ending page number"),
|
|
3773
|
+
subsections: import_zod31.z.array(SubsectionSchema2).optional().describe("Subsections within this section")
|
|
3630
3774
|
})
|
|
3631
3775
|
).describe("All document sections")
|
|
3632
3776
|
});
|
|
@@ -3645,25 +3789,31 @@ For each section, classify its type:
|
|
|
3645
3789
|
- "other" \u2014 anything that doesn't fit the above categories
|
|
3646
3790
|
|
|
3647
3791
|
Include accurate page numbers for every section. Include subsections only if the section has clearly defined subsections with their own titles.
|
|
3792
|
+
If a page begins or ends in the middle of a section, treat it as a continuation of the existing section instead of creating a new orphan section from the fragment.
|
|
3793
|
+
|
|
3794
|
+
Critical rules:
|
|
3795
|
+
- Ignore table-of-contents entries, page-number references, repeating headers/footers, and other navigational artifacts.
|
|
3796
|
+
- Do not create a new section from a lone continuation fragment such as a single paragraph tail or list item that clearly belongs to the previous page's section.
|
|
3797
|
+
- When a section spans multiple pages, keep it as one section with pageStart/pageEnd covering the full span represented in this extraction.
|
|
3648
3798
|
|
|
3649
3799
|
Return JSON only.`;
|
|
3650
3800
|
}
|
|
3651
3801
|
|
|
3652
3802
|
// src/prompts/extractors/supplementary.ts
|
|
3653
|
-
var
|
|
3654
|
-
var ContactSchema2 =
|
|
3655
|
-
name:
|
|
3656
|
-
phone:
|
|
3657
|
-
email:
|
|
3658
|
-
address:
|
|
3659
|
-
type:
|
|
3803
|
+
var import_zod32 = require("zod");
|
|
3804
|
+
var ContactSchema2 = import_zod32.z.object({
|
|
3805
|
+
name: import_zod32.z.string().optional().describe("Organization or person name"),
|
|
3806
|
+
phone: import_zod32.z.string().optional().describe("Phone number"),
|
|
3807
|
+
email: import_zod32.z.string().optional().describe("Email address"),
|
|
3808
|
+
address: import_zod32.z.string().optional().describe("Mailing address"),
|
|
3809
|
+
type: import_zod32.z.string().optional().describe("Contact type, e.g. 'State Department of Insurance'")
|
|
3660
3810
|
});
|
|
3661
|
-
var SupplementarySchema =
|
|
3662
|
-
regulatoryContacts:
|
|
3663
|
-
claimsContacts:
|
|
3664
|
-
thirdPartyAdministrators:
|
|
3665
|
-
cancellationNoticeDays:
|
|
3666
|
-
nonrenewalNoticeDays:
|
|
3811
|
+
var SupplementarySchema = import_zod32.z.object({
|
|
3812
|
+
regulatoryContacts: import_zod32.z.array(ContactSchema2).optional().describe("Regulatory body contacts (state department of insurance, ombudsman)"),
|
|
3813
|
+
claimsContacts: import_zod32.z.array(ContactSchema2).optional().describe("Claims reporting contacts and instructions"),
|
|
3814
|
+
thirdPartyAdministrators: import_zod32.z.array(ContactSchema2).optional().describe("Third-party administrators for claims handling"),
|
|
3815
|
+
cancellationNoticeDays: import_zod32.z.number().optional().describe("Required notice period for cancellation in days"),
|
|
3816
|
+
nonrenewalNoticeDays: import_zod32.z.number().optional().describe("Required notice period for nonrenewal in days")
|
|
3667
3817
|
});
|
|
3668
3818
|
function buildSupplementaryPrompt() {
|
|
3669
3819
|
return `You are an expert insurance document analyst. Extract supplementary and regulatory information from this document.
|
|
@@ -3700,6 +3850,313 @@ function getExtractor(name) {
|
|
|
3700
3850
|
return EXTRACTORS[name];
|
|
3701
3851
|
}
|
|
3702
3852
|
|
|
3853
|
+
// src/core/quality.ts
|
|
3854
|
+
function evaluateQualityGate(params) {
|
|
3855
|
+
const { issues, hasRoundWarnings = false } = params;
|
|
3856
|
+
const hasBlocking = issues.some((issue) => issue.severity === "blocking");
|
|
3857
|
+
const hasWarnings = issues.some((issue) => issue.severity === "warning") || hasRoundWarnings;
|
|
3858
|
+
return hasBlocking ? "failed" : hasWarnings ? "warning" : "passed";
|
|
3859
|
+
}
|
|
3860
|
+
function shouldFailQualityGate(mode, status) {
|
|
3861
|
+
return mode === "strict" && status === "failed";
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
// src/extraction/quality.ts
|
|
3865
|
+
function normalizeFormNumber(value) {
|
|
3866
|
+
if (typeof value !== "string") return void 0;
|
|
3867
|
+
const trimmed = value.trim();
|
|
3868
|
+
if (!trimmed) return void 0;
|
|
3869
|
+
return trimmed;
|
|
3870
|
+
}
|
|
3871
|
+
function addFormEntry(inventory, formNumber, source, extra) {
|
|
3872
|
+
if (!formNumber) return;
|
|
3873
|
+
const existing = inventory.get(formNumber);
|
|
3874
|
+
if (existing) {
|
|
3875
|
+
if (!existing.title && extra?.title) existing.title = extra.title;
|
|
3876
|
+
if (!existing.pageStart && extra?.pageStart) existing.pageStart = extra.pageStart;
|
|
3877
|
+
if (!existing.pageEnd && extra?.pageEnd) existing.pageEnd = extra.pageEnd;
|
|
3878
|
+
if (!existing.sources.includes(source)) existing.sources.push(source);
|
|
3879
|
+
return;
|
|
3880
|
+
}
|
|
3881
|
+
inventory.set(formNumber, {
|
|
3882
|
+
formNumber,
|
|
3883
|
+
title: extra?.title,
|
|
3884
|
+
pageStart: extra?.pageStart,
|
|
3885
|
+
pageEnd: extra?.pageEnd,
|
|
3886
|
+
sources: [source]
|
|
3887
|
+
});
|
|
3888
|
+
}
|
|
3889
|
+
function looksReferential(value) {
|
|
3890
|
+
if (typeof value !== "string") return false;
|
|
3891
|
+
const normalized = value.toLowerCase();
|
|
3892
|
+
return normalized.includes("shown in the declarations") || normalized.includes("shown in declarations") || normalized.includes("shown in the schedule") || normalized.includes("as stated") || normalized.includes("if applicable");
|
|
3893
|
+
}
|
|
3894
|
+
function looksTocArtifact(value) {
|
|
3895
|
+
if (typeof value !== "string") return false;
|
|
3896
|
+
return /\.{4,}\d{1,3}$/.test(value.trim()) || /^\d+\.\s+[A-Z][\s\S]*\.{3,}\d{1,3}$/.test(value.trim());
|
|
3897
|
+
}
|
|
3898
|
+
function sourcePrecedence(sectionRef) {
|
|
3899
|
+
if (typeof sectionRef !== "string") return 0;
|
|
3900
|
+
const normalized = sectionRef.toLowerCase();
|
|
3901
|
+
if (normalized.includes("declaration") || normalized.includes("scheduled coverages") || normalized.includes("schedule")) return 4;
|
|
3902
|
+
if (normalized.includes("endorsement")) return 3;
|
|
3903
|
+
if (normalized.includes("additional coverages")) return 2;
|
|
3904
|
+
if (normalized.includes("coverage form") || normalized.includes("policy form")) return 1;
|
|
3905
|
+
return 0;
|
|
3906
|
+
}
|
|
3907
|
+
function buildExtractionReviewReport(params) {
|
|
3908
|
+
const { memory, reviewRounds } = params;
|
|
3909
|
+
const deterministicIssues = [];
|
|
3910
|
+
const inventory = /* @__PURE__ */ new Map();
|
|
3911
|
+
const extractedFormInventory = memory.get("form_inventory")?.forms ?? [];
|
|
3912
|
+
const coverages = memory.get("coverage_limits")?.coverages ?? [];
|
|
3913
|
+
const endorsements = memory.get("endorsements")?.endorsements ?? [];
|
|
3914
|
+
const exclusions = memory.get("exclusions")?.exclusions ?? [];
|
|
3915
|
+
const conditions = memory.get("conditions")?.conditions ?? [];
|
|
3916
|
+
const sections = memory.get("sections")?.sections ?? [];
|
|
3917
|
+
for (const form of extractedFormInventory) {
|
|
3918
|
+
addFormEntry(
|
|
3919
|
+
inventory,
|
|
3920
|
+
normalizeFormNumber(form.formNumber),
|
|
3921
|
+
"form_inventory",
|
|
3922
|
+
{
|
|
3923
|
+
title: form.title,
|
|
3924
|
+
pageStart: form.pageStart,
|
|
3925
|
+
pageEnd: form.pageEnd
|
|
3926
|
+
}
|
|
3927
|
+
);
|
|
3928
|
+
}
|
|
3929
|
+
for (const endorsement of endorsements) {
|
|
3930
|
+
addFormEntry(
|
|
3931
|
+
inventory,
|
|
3932
|
+
normalizeFormNumber(endorsement.formNumber),
|
|
3933
|
+
"endorsements",
|
|
3934
|
+
{
|
|
3935
|
+
title: typeof endorsement.title === "string" ? endorsement.title : void 0,
|
|
3936
|
+
pageStart: typeof endorsement.pageStart === "number" ? endorsement.pageStart : void 0,
|
|
3937
|
+
pageEnd: typeof endorsement.pageEnd === "number" ? endorsement.pageEnd : void 0
|
|
3938
|
+
}
|
|
3939
|
+
);
|
|
3940
|
+
if (typeof endorsement.formNumber !== "string" || !endorsement.formNumber.trim()) {
|
|
3941
|
+
deterministicIssues.push({
|
|
3942
|
+
code: "endorsement_missing_form_number",
|
|
3943
|
+
severity: "blocking",
|
|
3944
|
+
message: "Endorsement is missing formNumber.",
|
|
3945
|
+
extractorName: "endorsements",
|
|
3946
|
+
pageNumber: typeof endorsement.pageStart === "number" ? endorsement.pageStart : void 0,
|
|
3947
|
+
itemName: typeof endorsement.title === "string" ? endorsement.title : void 0
|
|
3948
|
+
});
|
|
3949
|
+
}
|
|
3950
|
+
const endorsementFormNumber = normalizeFormNumber(endorsement.formNumber);
|
|
3951
|
+
if (endorsementFormNumber && !inventory.has(endorsementFormNumber)) {
|
|
3952
|
+
deterministicIssues.push({
|
|
3953
|
+
code: "endorsement_form_missing_from_inventory",
|
|
3954
|
+
severity: "warning",
|
|
3955
|
+
message: `Endorsement "${String(endorsement.title ?? endorsementFormNumber)}" is not present in form inventory.`,
|
|
3956
|
+
extractorName: "endorsements",
|
|
3957
|
+
formNumber: endorsementFormNumber,
|
|
3958
|
+
pageNumber: typeof endorsement.pageStart === "number" ? endorsement.pageStart : void 0,
|
|
3959
|
+
itemName: typeof endorsement.title === "string" ? endorsement.title : void 0
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
for (const coverage of coverages) {
|
|
3964
|
+
const formNumber = normalizeFormNumber(coverage.formNumber);
|
|
3965
|
+
addFormEntry(inventory, formNumber, "coverage_limits", {
|
|
3966
|
+
title: typeof coverage.name === "string" ? coverage.name : void 0,
|
|
3967
|
+
pageStart: typeof coverage.pageNumber === "number" ? coverage.pageNumber : void 0,
|
|
3968
|
+
pageEnd: typeof coverage.pageNumber === "number" ? coverage.pageNumber : void 0
|
|
3969
|
+
});
|
|
3970
|
+
if (typeof coverage.name === "string" && /coverage form$/i.test(coverage.name.trim())) {
|
|
3971
|
+
deterministicIssues.push({
|
|
3972
|
+
code: "generic_form_row_as_coverage",
|
|
3973
|
+
severity: "blocking",
|
|
3974
|
+
message: `Coverage "${coverage.name}" looks like a form header rather than a real coverage row.`,
|
|
3975
|
+
extractorName: "coverage_limits",
|
|
3976
|
+
formNumber,
|
|
3977
|
+
pageNumber: typeof coverage.pageNumber === "number" ? coverage.pageNumber : void 0,
|
|
3978
|
+
itemName: coverage.name
|
|
3979
|
+
});
|
|
3980
|
+
}
|
|
3981
|
+
if (typeof coverage.pageNumber !== "number") {
|
|
3982
|
+
deterministicIssues.push({
|
|
3983
|
+
code: "coverage_missing_page_number",
|
|
3984
|
+
severity: "warning",
|
|
3985
|
+
message: `Coverage "${String(coverage.name ?? "unknown")}" is missing pageNumber provenance.`,
|
|
3986
|
+
extractorName: "coverage_limits",
|
|
3987
|
+
formNumber,
|
|
3988
|
+
itemName: typeof coverage.name === "string" ? coverage.name : void 0
|
|
3989
|
+
});
|
|
3990
|
+
}
|
|
3991
|
+
if (typeof coverage.sectionRef !== "string" || !coverage.sectionRef.trim()) {
|
|
3992
|
+
deterministicIssues.push({
|
|
3993
|
+
code: "coverage_missing_section_ref",
|
|
3994
|
+
severity: "warning",
|
|
3995
|
+
message: `Coverage "${String(coverage.name ?? "unknown")}" is missing sectionRef provenance.`,
|
|
3996
|
+
extractorName: "coverage_limits",
|
|
3997
|
+
formNumber,
|
|
3998
|
+
pageNumber: typeof coverage.pageNumber === "number" ? coverage.pageNumber : void 0,
|
|
3999
|
+
itemName: typeof coverage.name === "string" ? coverage.name : void 0
|
|
4000
|
+
});
|
|
4001
|
+
}
|
|
4002
|
+
if (typeof coverage.originalContent !== "string" || !coverage.originalContent.trim()) {
|
|
4003
|
+
deterministicIssues.push({
|
|
4004
|
+
code: "coverage_missing_original_content",
|
|
4005
|
+
severity: "warning",
|
|
4006
|
+
message: `Coverage "${String(coverage.name ?? "unknown")}" is missing originalContent source text.`,
|
|
4007
|
+
extractorName: "coverage_limits",
|
|
4008
|
+
formNumber,
|
|
4009
|
+
pageNumber: typeof coverage.pageNumber === "number" ? coverage.pageNumber : void 0,
|
|
4010
|
+
itemName: typeof coverage.name === "string" ? coverage.name : void 0
|
|
4011
|
+
});
|
|
4012
|
+
}
|
|
4013
|
+
if (looksReferential(coverage.limit) || looksReferential(coverage.deductible)) {
|
|
4014
|
+
deterministicIssues.push({
|
|
4015
|
+
code: "coverage_referential_value",
|
|
4016
|
+
severity: "warning",
|
|
4017
|
+
message: `Coverage "${String(coverage.name ?? "unknown")}" contains referential language instead of a concrete scheduled term.`,
|
|
4018
|
+
extractorName: "coverage_limits",
|
|
4019
|
+
formNumber,
|
|
4020
|
+
pageNumber: typeof coverage.pageNumber === "number" ? coverage.pageNumber : void 0,
|
|
4021
|
+
itemName: typeof coverage.name === "string" ? coverage.name : void 0
|
|
4022
|
+
});
|
|
4023
|
+
}
|
|
4024
|
+
if (formNumber && !inventory.has(formNumber)) {
|
|
4025
|
+
deterministicIssues.push({
|
|
4026
|
+
code: "coverage_form_missing_from_inventory",
|
|
4027
|
+
severity: "warning",
|
|
4028
|
+
message: `Coverage "${String(coverage.name ?? "unknown")}" references form "${formNumber}" that is missing from form inventory.`,
|
|
4029
|
+
extractorName: "coverage_limits",
|
|
4030
|
+
formNumber,
|
|
4031
|
+
pageNumber: typeof coverage.pageNumber === "number" ? coverage.pageNumber : void 0,
|
|
4032
|
+
itemName: typeof coverage.name === "string" ? coverage.name : void 0
|
|
4033
|
+
});
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
const coverageGroups = /* @__PURE__ */ new Map();
|
|
4037
|
+
for (const coverage of coverages) {
|
|
4038
|
+
const key = [
|
|
4039
|
+
String(coverage.name ?? "").toLowerCase(),
|
|
4040
|
+
String(coverage.formNumber ?? "").toLowerCase()
|
|
4041
|
+
].join("|");
|
|
4042
|
+
coverageGroups.set(key, [...coverageGroups.get(key) ?? [], coverage]);
|
|
4043
|
+
}
|
|
4044
|
+
for (const [key, groupedCoverages] of coverageGroups.entries()) {
|
|
4045
|
+
if (groupedCoverages.length < 2) continue;
|
|
4046
|
+
const sorted = [...groupedCoverages].sort((a, b) => sourcePrecedence(b.sectionRef) - sourcePrecedence(a.sectionRef));
|
|
4047
|
+
const highest = sorted[0];
|
|
4048
|
+
for (const lower of sorted.slice(1)) {
|
|
4049
|
+
const highestLimit = String(highest.limit ?? "").trim();
|
|
4050
|
+
const lowerLimit = String(lower.limit ?? "").trim();
|
|
4051
|
+
const highestDeductible = String(highest.deductible ?? "").trim();
|
|
4052
|
+
const lowerDeductible = String(lower.deductible ?? "").trim();
|
|
4053
|
+
if (highestLimit && lowerLimit && highestLimit !== lowerLimit || highestDeductible && lowerDeductible && highestDeductible !== lowerDeductible) {
|
|
4054
|
+
deterministicIssues.push({
|
|
4055
|
+
code: "coverage_precedence_conflict",
|
|
4056
|
+
severity: "warning",
|
|
4057
|
+
message: `Coverage "${String(highest.name ?? key)}" has conflicting extracted terms across sources with different precedence.`,
|
|
4058
|
+
extractorName: "coverage_limits",
|
|
4059
|
+
formNumber: normalizeFormNumber(highest.formNumber) ?? normalizeFormNumber(lower.formNumber),
|
|
4060
|
+
pageNumber: typeof lower.pageNumber === "number" ? lower.pageNumber : void 0,
|
|
4061
|
+
itemName: typeof highest.name === "string" ? highest.name : void 0
|
|
4062
|
+
});
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
for (const exclusion of exclusions) {
|
|
4067
|
+
addFormEntry(inventory, normalizeFormNumber(exclusion.formNumber), "exclusions", {
|
|
4068
|
+
title: typeof exclusion.name === "string" ? exclusion.name : void 0,
|
|
4069
|
+
pageStart: typeof exclusion.pageNumber === "number" ? exclusion.pageNumber : void 0,
|
|
4070
|
+
pageEnd: typeof exclusion.pageNumber === "number" ? exclusion.pageNumber : void 0
|
|
4071
|
+
});
|
|
4072
|
+
if (typeof exclusion.pageNumber !== "number") {
|
|
4073
|
+
deterministicIssues.push({
|
|
4074
|
+
code: "exclusion_missing_page_number",
|
|
4075
|
+
severity: "warning",
|
|
4076
|
+
message: `Exclusion "${String(exclusion.name ?? "unknown")}" is missing pageNumber provenance.`,
|
|
4077
|
+
extractorName: "exclusions",
|
|
4078
|
+
formNumber: normalizeFormNumber(exclusion.formNumber),
|
|
4079
|
+
itemName: typeof exclusion.name === "string" ? exclusion.name : void 0
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
if (looksTocArtifact(exclusion.content)) {
|
|
4083
|
+
deterministicIssues.push({
|
|
4084
|
+
code: "exclusion_toc_artifact",
|
|
4085
|
+
severity: "blocking",
|
|
4086
|
+
message: `Exclusion "${String(exclusion.name ?? "unknown")}" appears to be a table-of-contents artifact.`,
|
|
4087
|
+
extractorName: "exclusions",
|
|
4088
|
+
pageNumber: typeof exclusion.pageNumber === "number" ? exclusion.pageNumber : void 0,
|
|
4089
|
+
itemName: typeof exclusion.name === "string" ? exclusion.name : void 0
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
for (const condition of conditions) {
|
|
4094
|
+
if (typeof condition.pageNumber !== "number") {
|
|
4095
|
+
deterministicIssues.push({
|
|
4096
|
+
code: "condition_missing_page_number",
|
|
4097
|
+
severity: "warning",
|
|
4098
|
+
message: `Condition "${String(condition.name ?? "unknown")}" is missing pageNumber provenance.`,
|
|
4099
|
+
extractorName: "conditions",
|
|
4100
|
+
itemName: typeof condition.name === "string" ? condition.name : void 0
|
|
4101
|
+
});
|
|
4102
|
+
}
|
|
4103
|
+
if (looksTocArtifact(condition.content)) {
|
|
4104
|
+
deterministicIssues.push({
|
|
4105
|
+
code: "condition_toc_artifact",
|
|
4106
|
+
severity: "blocking",
|
|
4107
|
+
message: `Condition "${String(condition.name ?? "unknown")}" appears to be a table-of-contents artifact.`,
|
|
4108
|
+
extractorName: "conditions",
|
|
4109
|
+
pageNumber: typeof condition.pageNumber === "number" ? condition.pageNumber : void 0,
|
|
4110
|
+
itemName: typeof condition.name === "string" ? condition.name : void 0
|
|
4111
|
+
});
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
for (const section of sections) {
|
|
4115
|
+
if (typeof section.content === "string" && section.content.trim().length < 120 && typeof section.pageStart === "number" && (!("pageEnd" in section) || section.pageEnd === section.pageStart || section.pageEnd === void 0)) {
|
|
4116
|
+
deterministicIssues.push({
|
|
4117
|
+
code: "section_short_fragment",
|
|
4118
|
+
severity: "warning",
|
|
4119
|
+
message: `Section "${String(section.title ?? "unknown")}" may be an orphan continuation fragment.`,
|
|
4120
|
+
extractorName: "sections",
|
|
4121
|
+
pageNumber: typeof section.pageStart === "number" ? section.pageStart : void 0,
|
|
4122
|
+
itemName: typeof section.title === "string" ? section.title : void 0
|
|
4123
|
+
});
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
const formInventory = [...inventory.values()].sort((a, b) => a.formNumber.localeCompare(b.formNumber));
|
|
4127
|
+
const rounds = reviewRounds.map((round) => ({
|
|
4128
|
+
round: round.round,
|
|
4129
|
+
kind: "llm_review",
|
|
4130
|
+
status: round.complete && round.qualityIssues.length === 0 ? "passed" : "warning",
|
|
4131
|
+
summary: round.qualityIssues[0] ?? (round.complete ? "Review passed." : "Review requested follow-up extraction.")
|
|
4132
|
+
}));
|
|
4133
|
+
const artifacts = [
|
|
4134
|
+
{ kind: "form_inventory", label: "Form Inventory", itemCount: formInventory.length },
|
|
4135
|
+
{ kind: "page_map", label: "Page Map", itemCount: params.pageAssignments.length }
|
|
4136
|
+
];
|
|
4137
|
+
const qualityGateStatus = evaluateQualityGate({
|
|
4138
|
+
issues: deterministicIssues,
|
|
4139
|
+
hasRoundWarnings: reviewRounds.some((round) => round.qualityIssues.length > 0 || !round.complete)
|
|
4140
|
+
});
|
|
4141
|
+
return {
|
|
4142
|
+
issues: deterministicIssues,
|
|
4143
|
+
rounds,
|
|
4144
|
+
artifacts,
|
|
4145
|
+
reviewRoundRecords: reviewRounds,
|
|
4146
|
+
formInventory,
|
|
4147
|
+
qualityGateStatus
|
|
4148
|
+
};
|
|
4149
|
+
}
|
|
4150
|
+
function toReviewRoundRecord(round, review) {
|
|
4151
|
+
return {
|
|
4152
|
+
round,
|
|
4153
|
+
complete: review.complete,
|
|
4154
|
+
missingFields: review.missingFields,
|
|
4155
|
+
qualityIssues: review.qualityIssues ?? [],
|
|
4156
|
+
additionalTasks: review.additionalTasks
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4159
|
+
|
|
3703
4160
|
// src/extraction/coordinator.ts
|
|
3704
4161
|
function createExtractor(config) {
|
|
3705
4162
|
const {
|
|
@@ -3712,6 +4169,7 @@ function createExtractor(config) {
|
|
|
3712
4169
|
onProgress,
|
|
3713
4170
|
log,
|
|
3714
4171
|
providerOptions,
|
|
4172
|
+
qualityGate = "warn",
|
|
3715
4173
|
onCheckpointSave
|
|
3716
4174
|
} = config;
|
|
3717
4175
|
const limit = pLimit(concurrency);
|
|
@@ -3768,6 +4226,50 @@ function createExtractor(config) {
|
|
|
3768
4226
|
if (extractorPages.size === 0) return "No page assignments available.";
|
|
3769
4227
|
return [...extractorPages.entries()].map(([extractorName, pages]) => `${extractorName}: pages ${pages.join(", ")}`).join("\n");
|
|
3770
4228
|
}
|
|
4229
|
+
function normalizePageAssignments(pageAssignments, formInventory) {
|
|
4230
|
+
const pageFormTypes = /* @__PURE__ */ new Map();
|
|
4231
|
+
if (formInventory) {
|
|
4232
|
+
for (const form of formInventory.forms) {
|
|
4233
|
+
if (form.pageStart != null) {
|
|
4234
|
+
const end = form.pageEnd ?? form.pageStart;
|
|
4235
|
+
for (let p = form.pageStart; p <= end; p++) {
|
|
4236
|
+
const types = pageFormTypes.get(p) ?? /* @__PURE__ */ new Set();
|
|
4237
|
+
types.add(form.formType);
|
|
4238
|
+
pageFormTypes.set(p, types);
|
|
4239
|
+
}
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
return pageAssignments.map((assignment) => {
|
|
4244
|
+
let extractorNames = [...new Set(
|
|
4245
|
+
(assignment.extractorNames.length > 0 ? assignment.extractorNames : ["sections"]).filter(Boolean)
|
|
4246
|
+
)];
|
|
4247
|
+
const hasDeclarations = extractorNames.includes("declarations");
|
|
4248
|
+
const hasConditions = extractorNames.includes("conditions");
|
|
4249
|
+
const hasExclusions = extractorNames.includes("exclusions");
|
|
4250
|
+
const hasEndorsements = extractorNames.includes("endorsements");
|
|
4251
|
+
const looksLikeScheduleValues = assignment.hasScheduleValues === true;
|
|
4252
|
+
const roleBlocksCoverageLimits = assignment.pageRole === "policy_form" || assignment.pageRole === "condition_exclusion_form" || assignment.pageRole === "endorsement_form";
|
|
4253
|
+
const inventoryTypes = pageFormTypes.get(assignment.localPageNumber);
|
|
4254
|
+
const inventoryBlocksCoverageLimits = inventoryTypes != null && !looksLikeScheduleValues && !hasDeclarations && (inventoryTypes.has("endorsement") || inventoryTypes.has("notice") || inventoryTypes.has("application"));
|
|
4255
|
+
if (extractorNames.includes("coverage_limits")) {
|
|
4256
|
+
const shouldDropCoverageLimits = inventoryBlocksCoverageLimits || !looksLikeScheduleValues && roleBlocksCoverageLimits || !hasDeclarations && !looksLikeScheduleValues && (hasConditions || hasExclusions) || !hasDeclarations && !looksLikeScheduleValues && hasEndorsements;
|
|
4257
|
+
if (shouldDropCoverageLimits) {
|
|
4258
|
+
extractorNames = extractorNames.filter((name) => name !== "coverage_limits");
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
if (inventoryTypes?.has("endorsement") && !extractorNames.includes("endorsements")) {
|
|
4262
|
+
extractorNames = [...extractorNames, "endorsements"];
|
|
4263
|
+
}
|
|
4264
|
+
if (extractorNames.length === 0) {
|
|
4265
|
+
extractorNames = ["sections"];
|
|
4266
|
+
}
|
|
4267
|
+
return {
|
|
4268
|
+
...assignment,
|
|
4269
|
+
extractorNames
|
|
4270
|
+
};
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
3771
4273
|
function buildTemplateHints(primaryType, documentType, pageCount, template) {
|
|
3772
4274
|
return [
|
|
3773
4275
|
`Document type: ${primaryType} ${documentType}`,
|
|
@@ -3886,6 +4388,38 @@ function createExtractor(config) {
|
|
|
3886
4388
|
const template = getTemplate(primaryType);
|
|
3887
4389
|
const pageCount = resumed?.pageCount ?? await getPdfPageCount(pdfBase64);
|
|
3888
4390
|
const templateHints = buildTemplateHints(primaryType, documentType, pageCount, template);
|
|
4391
|
+
let formInventory;
|
|
4392
|
+
if (resumed?.formInventory && pipelineCtx.isPhaseComplete("form_inventory")) {
|
|
4393
|
+
formInventory = resumed.formInventory;
|
|
4394
|
+
memory.set("form_inventory", formInventory);
|
|
4395
|
+
onProgress?.("Resuming from checkpoint (form inventory complete)...");
|
|
4396
|
+
} else {
|
|
4397
|
+
onProgress?.(`Building form inventory for ${primaryType} ${documentType}...`);
|
|
4398
|
+
const formInventoryResponse = await safeGenerateObject(
|
|
4399
|
+
generateObject,
|
|
4400
|
+
{
|
|
4401
|
+
prompt: buildFormInventoryPrompt(templateHints),
|
|
4402
|
+
schema: FormInventorySchema,
|
|
4403
|
+
maxTokens: 2048,
|
|
4404
|
+
providerOptions: { ...providerOptions, pdfBase64 }
|
|
4405
|
+
},
|
|
4406
|
+
{
|
|
4407
|
+
fallback: { forms: [] },
|
|
4408
|
+
log,
|
|
4409
|
+
onError: (err, attempt) => log?.(`Form inventory attempt ${attempt + 1} failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
4410
|
+
}
|
|
4411
|
+
);
|
|
4412
|
+
trackUsage(formInventoryResponse.usage);
|
|
4413
|
+
formInventory = formInventoryResponse.object;
|
|
4414
|
+
memory.set("form_inventory", formInventory);
|
|
4415
|
+
await pipelineCtx.save("form_inventory", {
|
|
4416
|
+
id,
|
|
4417
|
+
pageCount,
|
|
4418
|
+
classifyResult,
|
|
4419
|
+
formInventory,
|
|
4420
|
+
memory: Object.fromEntries(memory)
|
|
4421
|
+
});
|
|
4422
|
+
}
|
|
3889
4423
|
let pageAssignments;
|
|
3890
4424
|
if (resumed?.pageAssignments && pipelineCtx.isPhaseComplete("page_map")) {
|
|
3891
4425
|
pageAssignments = resumed.pageAssignments;
|
|
@@ -3894,13 +4428,14 @@ function createExtractor(config) {
|
|
|
3894
4428
|
onProgress?.(`Mapping document pages for ${primaryType} ${documentType}...`);
|
|
3895
4429
|
const chunkSize = 8;
|
|
3896
4430
|
const collectedAssignments = [];
|
|
4431
|
+
const formInventoryHint = formInventory?.forms.length ? formatFormInventoryForPageMap(formInventory.forms) : void 0;
|
|
3897
4432
|
for (let startPage = 1; startPage <= pageCount; startPage += chunkSize) {
|
|
3898
4433
|
const endPage = Math.min(pageCount, startPage + chunkSize - 1);
|
|
3899
4434
|
const pagesPdf = await extractPageRange(pdfBase64, startPage, endPage);
|
|
3900
4435
|
const mapResponse = await safeGenerateObject(
|
|
3901
4436
|
generateObject,
|
|
3902
4437
|
{
|
|
3903
|
-
prompt: buildPageMapPrompt(templateHints, startPage, endPage),
|
|
4438
|
+
prompt: buildPageMapPrompt(templateHints, startPage, endPage, formInventoryHint),
|
|
3904
4439
|
schema: PageMapChunkSchema,
|
|
3905
4440
|
maxTokens: 2048,
|
|
3906
4441
|
providerOptions: { ...providerOptions, pdfBase64: pagesPdf }
|
|
@@ -3932,10 +4467,12 @@ function createExtractor(config) {
|
|
|
3932
4467
|
confidence: 0,
|
|
3933
4468
|
notes: "Full-document fallback page assignment"
|
|
3934
4469
|
}));
|
|
4470
|
+
pageAssignments = normalizePageAssignments(pageAssignments, formInventory);
|
|
3935
4471
|
await pipelineCtx.save("page_map", {
|
|
3936
4472
|
id,
|
|
3937
4473
|
pageCount,
|
|
3938
4474
|
classifyResult,
|
|
4475
|
+
formInventory,
|
|
3939
4476
|
pageAssignments,
|
|
3940
4477
|
memory: Object.fromEntries(memory)
|
|
3941
4478
|
});
|
|
@@ -3951,6 +4488,7 @@ function createExtractor(config) {
|
|
|
3951
4488
|
id,
|
|
3952
4489
|
pageCount,
|
|
3953
4490
|
classifyResult,
|
|
4491
|
+
formInventory,
|
|
3954
4492
|
pageAssignments,
|
|
3955
4493
|
plan,
|
|
3956
4494
|
memory: Object.fromEntries(memory)
|
|
@@ -3999,12 +4537,16 @@ function createExtractor(config) {
|
|
|
3999
4537
|
id,
|
|
4000
4538
|
pageCount,
|
|
4001
4539
|
classifyResult,
|
|
4540
|
+
formInventory,
|
|
4002
4541
|
pageAssignments,
|
|
4003
4542
|
plan,
|
|
4004
4543
|
memory: Object.fromEntries(memory)
|
|
4005
4544
|
});
|
|
4006
4545
|
}
|
|
4546
|
+
let reviewRounds = resumed?.reviewReport?.reviewRoundRecords ?? [];
|
|
4547
|
+
let reviewReport = resumed?.reviewReport;
|
|
4007
4548
|
if (!pipelineCtx.isPhaseComplete("review")) {
|
|
4549
|
+
reviewRounds = [];
|
|
4008
4550
|
for (let round = 0; round < maxReviewRounds; round++) {
|
|
4009
4551
|
const extractedKeys = [...memory.keys()].filter((k) => k !== "classify");
|
|
4010
4552
|
const extractionSummary = summarizeExtraction(memory);
|
|
@@ -4024,6 +4566,7 @@ function createExtractor(config) {
|
|
|
4024
4566
|
}
|
|
4025
4567
|
);
|
|
4026
4568
|
trackUsage(reviewResponse.usage);
|
|
4569
|
+
reviewRounds.push(toReviewRoundRecord(round + 1, reviewResponse.object));
|
|
4027
4570
|
if (reviewResponse.object.qualityIssues?.length) {
|
|
4028
4571
|
await log?.(`Review round ${round + 1} quality issues: ${reviewResponse.object.qualityIssues.join("; ")}`);
|
|
4029
4572
|
}
|
|
@@ -4065,23 +4608,45 @@ function createExtractor(config) {
|
|
|
4065
4608
|
}
|
|
4066
4609
|
}
|
|
4067
4610
|
}
|
|
4611
|
+
reviewReport = buildExtractionReviewReport({
|
|
4612
|
+
memory,
|
|
4613
|
+
pageAssignments,
|
|
4614
|
+
reviewRounds
|
|
4615
|
+
});
|
|
4616
|
+
if (reviewReport.issues.length > 0) {
|
|
4617
|
+
await log?.(
|
|
4618
|
+
`Deterministic review issues: ${reviewReport.issues.map((issue) => issue.message).join("; ")}`
|
|
4619
|
+
);
|
|
4620
|
+
}
|
|
4621
|
+
if (shouldFailQualityGate(qualityGate, reviewReport.qualityGateStatus)) {
|
|
4622
|
+
throw new Error("Extraction quality gate failed. See reviewReport for blocking issues.");
|
|
4623
|
+
}
|
|
4068
4624
|
await pipelineCtx.save("review", {
|
|
4069
4625
|
id,
|
|
4070
4626
|
pageCount,
|
|
4071
4627
|
classifyResult,
|
|
4628
|
+
formInventory,
|
|
4072
4629
|
pageAssignments,
|
|
4073
4630
|
plan,
|
|
4631
|
+
reviewReport,
|
|
4074
4632
|
memory: Object.fromEntries(memory)
|
|
4075
4633
|
});
|
|
4076
4634
|
}
|
|
4635
|
+
reviewReport ?? (reviewReport = buildExtractionReviewReport({
|
|
4636
|
+
memory,
|
|
4637
|
+
pageAssignments,
|
|
4638
|
+
reviewRounds
|
|
4639
|
+
}));
|
|
4077
4640
|
onProgress?.("Assembling document...");
|
|
4078
4641
|
const document = assembleDocument(id, documentType, memory);
|
|
4079
4642
|
await pipelineCtx.save("assemble", {
|
|
4080
4643
|
id,
|
|
4081
4644
|
pageCount,
|
|
4082
4645
|
classifyResult,
|
|
4646
|
+
formInventory,
|
|
4083
4647
|
pageAssignments,
|
|
4084
4648
|
plan,
|
|
4649
|
+
reviewReport,
|
|
4085
4650
|
memory: Object.fromEntries(memory),
|
|
4086
4651
|
document
|
|
4087
4652
|
});
|
|
@@ -4107,7 +4672,8 @@ function createExtractor(config) {
|
|
|
4107
4672
|
callsWithUsage,
|
|
4108
4673
|
callsMissingUsage
|
|
4109
4674
|
},
|
|
4110
|
-
checkpoint: finalCheckpoint
|
|
4675
|
+
checkpoint: finalCheckpoint,
|
|
4676
|
+
reviewReport
|
|
4111
4677
|
};
|
|
4112
4678
|
}
|
|
4113
4679
|
return { extract };
|
|
@@ -4327,8 +4893,8 @@ Respond with JSON only:
|
|
|
4327
4893
|
}`;
|
|
4328
4894
|
|
|
4329
4895
|
// src/schemas/application.ts
|
|
4330
|
-
var
|
|
4331
|
-
var FieldTypeSchema =
|
|
4896
|
+
var import_zod33 = require("zod");
|
|
4897
|
+
var FieldTypeSchema = import_zod33.z.enum([
|
|
4332
4898
|
"text",
|
|
4333
4899
|
"numeric",
|
|
4334
4900
|
"currency",
|
|
@@ -4337,100 +4903,131 @@ var FieldTypeSchema = import_zod32.z.enum([
|
|
|
4337
4903
|
"table",
|
|
4338
4904
|
"declaration"
|
|
4339
4905
|
]);
|
|
4340
|
-
var ApplicationFieldSchema =
|
|
4341
|
-
id:
|
|
4342
|
-
label:
|
|
4343
|
-
section:
|
|
4906
|
+
var ApplicationFieldSchema = import_zod33.z.object({
|
|
4907
|
+
id: import_zod33.z.string(),
|
|
4908
|
+
label: import_zod33.z.string(),
|
|
4909
|
+
section: import_zod33.z.string(),
|
|
4344
4910
|
fieldType: FieldTypeSchema,
|
|
4345
|
-
required:
|
|
4346
|
-
options:
|
|
4347
|
-
columns:
|
|
4348
|
-
requiresExplanationIfYes:
|
|
4349
|
-
condition:
|
|
4350
|
-
dependsOn:
|
|
4351
|
-
whenValue:
|
|
4911
|
+
required: import_zod33.z.boolean(),
|
|
4912
|
+
options: import_zod33.z.array(import_zod33.z.string()).optional(),
|
|
4913
|
+
columns: import_zod33.z.array(import_zod33.z.string()).optional(),
|
|
4914
|
+
requiresExplanationIfYes: import_zod33.z.boolean().optional(),
|
|
4915
|
+
condition: import_zod33.z.object({
|
|
4916
|
+
dependsOn: import_zod33.z.string(),
|
|
4917
|
+
whenValue: import_zod33.z.string()
|
|
4352
4918
|
}).optional(),
|
|
4353
|
-
value:
|
|
4354
|
-
source:
|
|
4355
|
-
confidence:
|
|
4919
|
+
value: import_zod33.z.string().optional(),
|
|
4920
|
+
source: import_zod33.z.string().optional().describe("Where the value came from: auto-fill, user, lookup"),
|
|
4921
|
+
confidence: import_zod33.z.enum(["confirmed", "high", "medium", "low"]).optional()
|
|
4356
4922
|
});
|
|
4357
|
-
var ApplicationClassifyResultSchema =
|
|
4358
|
-
isApplication:
|
|
4359
|
-
confidence:
|
|
4360
|
-
applicationType:
|
|
4923
|
+
var ApplicationClassifyResultSchema = import_zod33.z.object({
|
|
4924
|
+
isApplication: import_zod33.z.boolean(),
|
|
4925
|
+
confidence: import_zod33.z.number().min(0).max(1),
|
|
4926
|
+
applicationType: import_zod33.z.string().nullable()
|
|
4927
|
+
});
|
|
4928
|
+
var FieldExtractionResultSchema = import_zod33.z.object({
|
|
4929
|
+
fields: import_zod33.z.array(ApplicationFieldSchema)
|
|
4930
|
+
});
|
|
4931
|
+
var AutoFillMatchSchema = import_zod33.z.object({
|
|
4932
|
+
fieldId: import_zod33.z.string(),
|
|
4933
|
+
value: import_zod33.z.string(),
|
|
4934
|
+
confidence: import_zod33.z.enum(["confirmed"]),
|
|
4935
|
+
contextKey: import_zod33.z.string()
|
|
4936
|
+
});
|
|
4937
|
+
var AutoFillResultSchema = import_zod33.z.object({
|
|
4938
|
+
matches: import_zod33.z.array(AutoFillMatchSchema)
|
|
4361
4939
|
});
|
|
4362
|
-
var
|
|
4363
|
-
|
|
4940
|
+
var QuestionBatchResultSchema = import_zod33.z.object({
|
|
4941
|
+
batches: import_zod33.z.array(import_zod33.z.array(import_zod33.z.string()).describe("Array of field IDs in this batch"))
|
|
4364
4942
|
});
|
|
4365
|
-
var
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4943
|
+
var LookupRequestSchema = import_zod33.z.object({
|
|
4944
|
+
type: import_zod33.z.string().describe("Type of lookup: 'records', 'website', 'policy'"),
|
|
4945
|
+
description: import_zod33.z.string(),
|
|
4946
|
+
url: import_zod33.z.string().optional(),
|
|
4947
|
+
targetFieldIds: import_zod33.z.array(import_zod33.z.string())
|
|
4370
4948
|
});
|
|
4371
|
-
var
|
|
4372
|
-
|
|
4949
|
+
var ReplyIntentSchema = import_zod33.z.object({
|
|
4950
|
+
primaryIntent: import_zod33.z.enum(["answers_only", "question", "lookup_request", "mixed"]),
|
|
4951
|
+
hasAnswers: import_zod33.z.boolean(),
|
|
4952
|
+
questionText: import_zod33.z.string().optional(),
|
|
4953
|
+
questionFieldIds: import_zod33.z.array(import_zod33.z.string()).optional(),
|
|
4954
|
+
lookupRequests: import_zod33.z.array(LookupRequestSchema).optional()
|
|
4373
4955
|
});
|
|
4374
|
-
var
|
|
4375
|
-
|
|
4956
|
+
var ParsedAnswerSchema = import_zod33.z.object({
|
|
4957
|
+
fieldId: import_zod33.z.string(),
|
|
4958
|
+
value: import_zod33.z.string(),
|
|
4959
|
+
explanation: import_zod33.z.string().optional()
|
|
4376
4960
|
});
|
|
4377
|
-
var
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
url: import_zod32.z.string().optional(),
|
|
4381
|
-
targetFieldIds: import_zod32.z.array(import_zod32.z.string())
|
|
4961
|
+
var AnswerParsingResultSchema = import_zod33.z.object({
|
|
4962
|
+
answers: import_zod33.z.array(ParsedAnswerSchema),
|
|
4963
|
+
unanswered: import_zod33.z.array(import_zod33.z.string()).describe("Field IDs that were not answered")
|
|
4382
4964
|
});
|
|
4383
|
-
var
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
questionFieldIds: import_zod32.z.array(import_zod32.z.string()).optional(),
|
|
4388
|
-
lookupRequests: import_zod32.z.array(LookupRequestSchema).optional()
|
|
4965
|
+
var LookupFillSchema = import_zod33.z.object({
|
|
4966
|
+
fieldId: import_zod33.z.string(),
|
|
4967
|
+
value: import_zod33.z.string(),
|
|
4968
|
+
source: import_zod33.z.string().describe("Specific citable reference, e.g. 'GL Policy #POL-12345 (Hartford)'")
|
|
4389
4969
|
});
|
|
4390
|
-
var
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
explanation:
|
|
4970
|
+
var LookupFillResultSchema = import_zod33.z.object({
|
|
4971
|
+
fills: import_zod33.z.array(LookupFillSchema),
|
|
4972
|
+
unfillable: import_zod33.z.array(import_zod33.z.string()),
|
|
4973
|
+
explanation: import_zod33.z.string().optional()
|
|
4394
4974
|
});
|
|
4395
|
-
var
|
|
4396
|
-
|
|
4397
|
-
|
|
4975
|
+
var FlatPdfPlacementSchema = import_zod33.z.object({
|
|
4976
|
+
fieldId: import_zod33.z.string(),
|
|
4977
|
+
page: import_zod33.z.number(),
|
|
4978
|
+
x: import_zod33.z.number().describe("Percentage from left edge (0-100)"),
|
|
4979
|
+
y: import_zod33.z.number().describe("Percentage from top edge (0-100)"),
|
|
4980
|
+
text: import_zod33.z.string(),
|
|
4981
|
+
fontSize: import_zod33.z.number().optional(),
|
|
4982
|
+
isCheckmark: import_zod33.z.boolean().optional()
|
|
4398
4983
|
});
|
|
4399
|
-
var
|
|
4400
|
-
fieldId:
|
|
4401
|
-
|
|
4402
|
-
|
|
4984
|
+
var AcroFormMappingSchema = import_zod33.z.object({
|
|
4985
|
+
fieldId: import_zod33.z.string(),
|
|
4986
|
+
acroFormName: import_zod33.z.string(),
|
|
4987
|
+
value: import_zod33.z.string()
|
|
4403
4988
|
});
|
|
4404
|
-
var
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4989
|
+
var QualityGateStatusSchema = import_zod33.z.enum(["passed", "warning", "failed"]);
|
|
4990
|
+
var QualitySeveritySchema = import_zod33.z.enum(["info", "warning", "blocking"]);
|
|
4991
|
+
var ApplicationQualityIssueSchema = import_zod33.z.object({
|
|
4992
|
+
code: import_zod33.z.string(),
|
|
4993
|
+
severity: QualitySeveritySchema,
|
|
4994
|
+
message: import_zod33.z.string(),
|
|
4995
|
+
fieldId: import_zod33.z.string().optional()
|
|
4408
4996
|
});
|
|
4409
|
-
var
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
text: import_zod32.z.string(),
|
|
4415
|
-
fontSize: import_zod32.z.number().optional(),
|
|
4416
|
-
isCheckmark: import_zod32.z.boolean().optional()
|
|
4997
|
+
var ApplicationQualityRoundSchema = import_zod33.z.object({
|
|
4998
|
+
round: import_zod33.z.number(),
|
|
4999
|
+
kind: import_zod33.z.string(),
|
|
5000
|
+
status: QualityGateStatusSchema,
|
|
5001
|
+
summary: import_zod33.z.string().optional()
|
|
4417
5002
|
});
|
|
4418
|
-
var
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
5003
|
+
var ApplicationQualityArtifactSchema = import_zod33.z.object({
|
|
5004
|
+
kind: import_zod33.z.string(),
|
|
5005
|
+
label: import_zod33.z.string().optional(),
|
|
5006
|
+
itemCount: import_zod33.z.number().optional()
|
|
4422
5007
|
});
|
|
4423
|
-
var
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
5008
|
+
var ApplicationEmailReviewSchema = import_zod33.z.object({
|
|
5009
|
+
issues: import_zod33.z.array(ApplicationQualityIssueSchema),
|
|
5010
|
+
qualityGateStatus: QualityGateStatusSchema
|
|
5011
|
+
});
|
|
5012
|
+
var ApplicationQualityReportSchema = import_zod33.z.object({
|
|
5013
|
+
issues: import_zod33.z.array(ApplicationQualityIssueSchema),
|
|
5014
|
+
rounds: import_zod33.z.array(ApplicationQualityRoundSchema).optional(),
|
|
5015
|
+
artifacts: import_zod33.z.array(ApplicationQualityArtifactSchema).optional(),
|
|
5016
|
+
emailReview: ApplicationEmailReviewSchema.optional(),
|
|
5017
|
+
qualityGateStatus: QualityGateStatusSchema
|
|
5018
|
+
});
|
|
5019
|
+
var ApplicationStateSchema = import_zod33.z.object({
|
|
5020
|
+
id: import_zod33.z.string(),
|
|
5021
|
+
pdfBase64: import_zod33.z.string().optional().describe("Original PDF, omitted after extraction"),
|
|
5022
|
+
title: import_zod33.z.string().optional(),
|
|
5023
|
+
applicationType: import_zod33.z.string().nullable().optional(),
|
|
5024
|
+
fields: import_zod33.z.array(ApplicationFieldSchema),
|
|
5025
|
+
batches: import_zod33.z.array(import_zod33.z.array(import_zod33.z.string())).optional(),
|
|
5026
|
+
currentBatchIndex: import_zod33.z.number().default(0),
|
|
5027
|
+
qualityReport: ApplicationQualityReportSchema.optional(),
|
|
5028
|
+
status: import_zod33.z.enum(["classifying", "extracting", "auto_filling", "batching", "collecting", "confirming", "mapping", "complete"]),
|
|
5029
|
+
createdAt: import_zod33.z.number(),
|
|
5030
|
+
updatedAt: import_zod33.z.number()
|
|
4434
5031
|
});
|
|
4435
5032
|
|
|
4436
5033
|
// src/application/agents/classifier.ts
|
|
@@ -4938,6 +5535,87 @@ async function generateBatchEmail(batchFields, batchIndex, totalBatches, opts, g
|
|
|
4938
5535
|
return { text, usage };
|
|
4939
5536
|
}
|
|
4940
5537
|
|
|
5538
|
+
// src/application/quality.ts
|
|
5539
|
+
function isVagueSource(source) {
|
|
5540
|
+
if (!source) return true;
|
|
5541
|
+
const normalized = source.trim().toLowerCase();
|
|
5542
|
+
return normalized === "unknown" || normalized.includes("existing records") || normalized.includes("available data") || normalized === "context" || normalized === "user provided";
|
|
5543
|
+
}
|
|
5544
|
+
function buildApplicationQualityReport(state) {
|
|
5545
|
+
const issues = [];
|
|
5546
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
5547
|
+
for (const field of state.fields) {
|
|
5548
|
+
if (seenIds.has(field.id)) {
|
|
5549
|
+
issues.push({
|
|
5550
|
+
code: "duplicate_field_id",
|
|
5551
|
+
severity: "blocking",
|
|
5552
|
+
message: `Field "${field.label}" has a duplicate id "${field.id}".`,
|
|
5553
|
+
fieldId: field.id
|
|
5554
|
+
});
|
|
5555
|
+
}
|
|
5556
|
+
seenIds.add(field.id);
|
|
5557
|
+
if (field.required && !field.value) {
|
|
5558
|
+
issues.push({
|
|
5559
|
+
code: "required_field_unfilled",
|
|
5560
|
+
severity: "warning",
|
|
5561
|
+
message: `Required field "${field.label}" is still unfilled.`,
|
|
5562
|
+
fieldId: field.id
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
5565
|
+
if (field.value && !field.source) {
|
|
5566
|
+
issues.push({
|
|
5567
|
+
code: "filled_field_missing_source",
|
|
5568
|
+
severity: "blocking",
|
|
5569
|
+
message: `Filled field "${field.label}" is missing source provenance.`,
|
|
5570
|
+
fieldId: field.id
|
|
5571
|
+
});
|
|
5572
|
+
}
|
|
5573
|
+
if (field.value && isVagueSource(field.source)) {
|
|
5574
|
+
issues.push({
|
|
5575
|
+
code: "filled_field_vague_source",
|
|
5576
|
+
severity: "warning",
|
|
5577
|
+
message: `Filled field "${field.label}" has a vague or non-citable source.`,
|
|
5578
|
+
fieldId: field.id
|
|
5579
|
+
});
|
|
5580
|
+
}
|
|
5581
|
+
if (field.value && (!field.confidence || field.confidence === "low")) {
|
|
5582
|
+
issues.push({
|
|
5583
|
+
code: "filled_field_low_confidence",
|
|
5584
|
+
severity: "warning",
|
|
5585
|
+
message: `Filled field "${field.label}" has low or missing confidence.`,
|
|
5586
|
+
fieldId: field.id
|
|
5587
|
+
});
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
return {
|
|
5591
|
+
issues,
|
|
5592
|
+
rounds: [],
|
|
5593
|
+
artifacts: [
|
|
5594
|
+
{ kind: "application_fields", label: "Application Fields", itemCount: state.fields.length }
|
|
5595
|
+
],
|
|
5596
|
+
qualityGateStatus: evaluateQualityGate({ issues })
|
|
5597
|
+
};
|
|
5598
|
+
}
|
|
5599
|
+
function reviewBatchEmail(text, batchFields) {
|
|
5600
|
+
const issues = [];
|
|
5601
|
+
const normalized = text.toLowerCase();
|
|
5602
|
+
for (const field of batchFields) {
|
|
5603
|
+
const label = field.label.trim().toLowerCase();
|
|
5604
|
+
if (label.length >= 6 && !normalized.includes(label)) {
|
|
5605
|
+
issues.push({
|
|
5606
|
+
code: "email_missing_field_prompt",
|
|
5607
|
+
severity: "warning",
|
|
5608
|
+
message: `Generated email does not clearly mention field "${field.label}".`,
|
|
5609
|
+
fieldId: field.id
|
|
5610
|
+
});
|
|
5611
|
+
}
|
|
5612
|
+
}
|
|
5613
|
+
return {
|
|
5614
|
+
issues,
|
|
5615
|
+
qualityGateStatus: evaluateQualityGate({ issues })
|
|
5616
|
+
};
|
|
5617
|
+
}
|
|
5618
|
+
|
|
4941
5619
|
// src/application/coordinator.ts
|
|
4942
5620
|
function createApplicationPipeline(config) {
|
|
4943
5621
|
const {
|
|
@@ -4952,7 +5630,8 @@ function createApplicationPipeline(config) {
|
|
|
4952
5630
|
onTokenUsage,
|
|
4953
5631
|
onProgress,
|
|
4954
5632
|
log,
|
|
4955
|
-
providerOptions
|
|
5633
|
+
providerOptions,
|
|
5634
|
+
qualityGate = "warn"
|
|
4956
5635
|
} = config;
|
|
4957
5636
|
const limit = pLimit(concurrency);
|
|
4958
5637
|
let totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
@@ -4974,6 +5653,7 @@ function createApplicationPipeline(config) {
|
|
|
4974
5653
|
title: void 0,
|
|
4975
5654
|
applicationType: null,
|
|
4976
5655
|
fields: [],
|
|
5656
|
+
qualityReport: void 0,
|
|
4977
5657
|
batches: void 0,
|
|
4978
5658
|
currentBatchIndex: 0,
|
|
4979
5659
|
status: "classifying",
|
|
@@ -4998,8 +5678,9 @@ function createApplicationPipeline(config) {
|
|
|
4998
5678
|
if (!classifyResult.isApplication) {
|
|
4999
5679
|
state.status = "complete";
|
|
5000
5680
|
state.updatedAt = Date.now();
|
|
5681
|
+
state.qualityReport = buildApplicationQualityReport(state);
|
|
5001
5682
|
await applicationStore?.save(state);
|
|
5002
|
-
return { state, tokenUsage: totalUsage };
|
|
5683
|
+
return { state, tokenUsage: totalUsage, reviewReport: state.qualityReport };
|
|
5003
5684
|
}
|
|
5004
5685
|
state.applicationType = classifyResult.applicationType;
|
|
5005
5686
|
state.status = "extracting";
|
|
@@ -5023,8 +5704,9 @@ function createApplicationPipeline(config) {
|
|
|
5023
5704
|
await log?.("No fields extracted, completing pipeline with empty result");
|
|
5024
5705
|
state.status = "complete";
|
|
5025
5706
|
state.updatedAt = Date.now();
|
|
5707
|
+
state.qualityReport = buildApplicationQualityReport(state);
|
|
5026
5708
|
await applicationStore?.save(state);
|
|
5027
|
-
return { state, tokenUsage: totalUsage };
|
|
5709
|
+
return { state, tokenUsage: totalUsage, reviewReport: state.qualityReport };
|
|
5028
5710
|
}
|
|
5029
5711
|
state.fields = fields;
|
|
5030
5712
|
state.title = classifyResult.applicationType ?? void 0;
|
|
@@ -5124,11 +5806,15 @@ function createApplicationPipeline(config) {
|
|
|
5124
5806
|
} else {
|
|
5125
5807
|
state.status = "confirming";
|
|
5126
5808
|
}
|
|
5809
|
+
state.qualityReport = buildApplicationQualityReport(state);
|
|
5127
5810
|
state.updatedAt = Date.now();
|
|
5128
5811
|
await applicationStore?.save(state);
|
|
5812
|
+
if (shouldFailQualityGate(qualityGate, state.qualityReport.qualityGateStatus)) {
|
|
5813
|
+
throw new Error("Application quality gate failed. See state.qualityReport for blocking issues.");
|
|
5814
|
+
}
|
|
5129
5815
|
const filledCount = state.fields.filter((f) => f.value).length;
|
|
5130
5816
|
onProgress?.(`Application processed: ${filledCount}/${state.fields.length} fields filled, ${state.batches?.length ?? 0} batches to collect.`);
|
|
5131
|
-
return { state, tokenUsage: totalUsage };
|
|
5817
|
+
return { state, tokenUsage: totalUsage, reviewReport: state.qualityReport };
|
|
5132
5818
|
}
|
|
5133
5819
|
async function processReply(input) {
|
|
5134
5820
|
totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
@@ -5275,6 +5961,11 @@ Provide a brief, helpful explanation (2-3 sentences). End with "Just reply with
|
|
|
5275
5961
|
providerOptions
|
|
5276
5962
|
);
|
|
5277
5963
|
trackUsage(emailUsage);
|
|
5964
|
+
const emailReview = reviewBatchEmail(emailText, nextBatchFields);
|
|
5965
|
+
state.qualityReport = {
|
|
5966
|
+
...buildApplicationQualityReport(state),
|
|
5967
|
+
emailReview
|
|
5968
|
+
};
|
|
5278
5969
|
if (!responseText) {
|
|
5279
5970
|
responseText = emailText;
|
|
5280
5971
|
} else {
|
|
@@ -5290,13 +5981,18 @@ ${emailText}`;
|
|
|
5290
5981
|
}
|
|
5291
5982
|
}
|
|
5292
5983
|
state.updatedAt = Date.now();
|
|
5984
|
+
state.qualityReport = state.qualityReport ?? buildApplicationQualityReport(state);
|
|
5293
5985
|
await applicationStore?.save(state);
|
|
5986
|
+
if (shouldFailQualityGate(qualityGate, state.qualityReport.qualityGateStatus)) {
|
|
5987
|
+
throw new Error("Application quality gate failed. See state.qualityReport for blocking issues.");
|
|
5988
|
+
}
|
|
5294
5989
|
return {
|
|
5295
5990
|
state,
|
|
5296
5991
|
intent: intent.primaryIntent,
|
|
5297
5992
|
fieldsFilled,
|
|
5298
5993
|
responseText,
|
|
5299
|
-
tokenUsage: totalUsage
|
|
5994
|
+
tokenUsage: totalUsage,
|
|
5995
|
+
reviewReport: state.qualityReport
|
|
5300
5996
|
};
|
|
5301
5997
|
}
|
|
5302
5998
|
async function generateCurrentBatchEmail(applicationId, opts) {
|
|
@@ -5322,6 +6018,12 @@ ${emailText}`;
|
|
|
5322
6018
|
providerOptions
|
|
5323
6019
|
);
|
|
5324
6020
|
trackUsage(usage);
|
|
6021
|
+
const emailReview = reviewBatchEmail(text, batchFields);
|
|
6022
|
+
state.qualityReport = {
|
|
6023
|
+
...buildApplicationQualityReport(state),
|
|
6024
|
+
emailReview
|
|
6025
|
+
};
|
|
6026
|
+
await applicationStore?.save(state);
|
|
5325
6027
|
return { text, tokenUsage: totalUsage };
|
|
5326
6028
|
}
|
|
5327
6029
|
async function getConfirmationSummary(applicationId) {
|
|
@@ -5458,73 +6160,73 @@ Respond with the final answer, deduplicated citations array, overall confidence
|
|
|
5458
6160
|
}
|
|
5459
6161
|
|
|
5460
6162
|
// src/schemas/query.ts
|
|
5461
|
-
var
|
|
5462
|
-
var QueryIntentSchema =
|
|
6163
|
+
var import_zod34 = require("zod");
|
|
6164
|
+
var QueryIntentSchema = import_zod34.z.enum([
|
|
5463
6165
|
"policy_question",
|
|
5464
6166
|
"coverage_comparison",
|
|
5465
6167
|
"document_search",
|
|
5466
6168
|
"claims_inquiry",
|
|
5467
6169
|
"general_knowledge"
|
|
5468
6170
|
]);
|
|
5469
|
-
var SubQuestionSchema =
|
|
5470
|
-
question:
|
|
6171
|
+
var SubQuestionSchema = import_zod34.z.object({
|
|
6172
|
+
question: import_zod34.z.string().describe("Atomic sub-question to retrieve and answer independently"),
|
|
5471
6173
|
intent: QueryIntentSchema,
|
|
5472
|
-
chunkTypes:
|
|
5473
|
-
documentFilters:
|
|
5474
|
-
type:
|
|
5475
|
-
carrier:
|
|
5476
|
-
insuredName:
|
|
5477
|
-
policyNumber:
|
|
5478
|
-
quoteNumber:
|
|
6174
|
+
chunkTypes: import_zod34.z.array(import_zod34.z.string()).optional().describe("Chunk types to filter retrieval (e.g. coverage, endorsement, declaration)"),
|
|
6175
|
+
documentFilters: import_zod34.z.object({
|
|
6176
|
+
type: import_zod34.z.enum(["policy", "quote"]).optional(),
|
|
6177
|
+
carrier: import_zod34.z.string().optional(),
|
|
6178
|
+
insuredName: import_zod34.z.string().optional(),
|
|
6179
|
+
policyNumber: import_zod34.z.string().optional(),
|
|
6180
|
+
quoteNumber: import_zod34.z.string().optional()
|
|
5479
6181
|
}).optional().describe("Structured filters to narrow document lookup")
|
|
5480
6182
|
});
|
|
5481
|
-
var QueryClassifyResultSchema =
|
|
6183
|
+
var QueryClassifyResultSchema = import_zod34.z.object({
|
|
5482
6184
|
intent: QueryIntentSchema,
|
|
5483
|
-
subQuestions:
|
|
5484
|
-
requiresDocumentLookup:
|
|
5485
|
-
requiresChunkSearch:
|
|
5486
|
-
requiresConversationHistory:
|
|
6185
|
+
subQuestions: import_zod34.z.array(SubQuestionSchema).min(1).describe("Decomposed atomic sub-questions"),
|
|
6186
|
+
requiresDocumentLookup: import_zod34.z.boolean().describe("Whether structured document lookup is needed"),
|
|
6187
|
+
requiresChunkSearch: import_zod34.z.boolean().describe("Whether semantic chunk search is needed"),
|
|
6188
|
+
requiresConversationHistory: import_zod34.z.boolean().describe("Whether conversation history is relevant")
|
|
5487
6189
|
});
|
|
5488
|
-
var EvidenceItemSchema =
|
|
5489
|
-
source:
|
|
5490
|
-
chunkId:
|
|
5491
|
-
documentId:
|
|
5492
|
-
turnId:
|
|
5493
|
-
text:
|
|
5494
|
-
relevance:
|
|
5495
|
-
metadata:
|
|
6190
|
+
var EvidenceItemSchema = import_zod34.z.object({
|
|
6191
|
+
source: import_zod34.z.enum(["chunk", "document", "conversation"]),
|
|
6192
|
+
chunkId: import_zod34.z.string().optional(),
|
|
6193
|
+
documentId: import_zod34.z.string().optional(),
|
|
6194
|
+
turnId: import_zod34.z.string().optional(),
|
|
6195
|
+
text: import_zod34.z.string().describe("Text excerpt from the source"),
|
|
6196
|
+
relevance: import_zod34.z.number().min(0).max(1),
|
|
6197
|
+
metadata: import_zod34.z.array(import_zod34.z.object({ key: import_zod34.z.string(), value: import_zod34.z.string() })).optional()
|
|
5496
6198
|
});
|
|
5497
|
-
var RetrievalResultSchema =
|
|
5498
|
-
subQuestion:
|
|
5499
|
-
evidence:
|
|
6199
|
+
var RetrievalResultSchema = import_zod34.z.object({
|
|
6200
|
+
subQuestion: import_zod34.z.string(),
|
|
6201
|
+
evidence: import_zod34.z.array(EvidenceItemSchema)
|
|
5500
6202
|
});
|
|
5501
|
-
var CitationSchema =
|
|
5502
|
-
index:
|
|
5503
|
-
chunkId:
|
|
5504
|
-
documentId:
|
|
5505
|
-
documentType:
|
|
5506
|
-
field:
|
|
5507
|
-
quote:
|
|
5508
|
-
relevance:
|
|
6203
|
+
var CitationSchema = import_zod34.z.object({
|
|
6204
|
+
index: import_zod34.z.number().describe("Citation number [1], [2], etc."),
|
|
6205
|
+
chunkId: import_zod34.z.string().describe("Source chunk ID, e.g. doc-123:coverage:2"),
|
|
6206
|
+
documentId: import_zod34.z.string(),
|
|
6207
|
+
documentType: import_zod34.z.enum(["policy", "quote"]).optional(),
|
|
6208
|
+
field: import_zod34.z.string().optional().describe("Specific field path, e.g. coverages[0].deductible"),
|
|
6209
|
+
quote: import_zod34.z.string().describe("Exact text from source that supports the claim"),
|
|
6210
|
+
relevance: import_zod34.z.number().min(0).max(1)
|
|
5509
6211
|
});
|
|
5510
|
-
var SubAnswerSchema =
|
|
5511
|
-
subQuestion:
|
|
5512
|
-
answer:
|
|
5513
|
-
citations:
|
|
5514
|
-
confidence:
|
|
5515
|
-
needsMoreContext:
|
|
6212
|
+
var SubAnswerSchema = import_zod34.z.object({
|
|
6213
|
+
subQuestion: import_zod34.z.string(),
|
|
6214
|
+
answer: import_zod34.z.string(),
|
|
6215
|
+
citations: import_zod34.z.array(CitationSchema),
|
|
6216
|
+
confidence: import_zod34.z.number().min(0).max(1),
|
|
6217
|
+
needsMoreContext: import_zod34.z.boolean().describe("True if evidence was insufficient to answer fully")
|
|
5516
6218
|
});
|
|
5517
|
-
var VerifyResultSchema =
|
|
5518
|
-
approved:
|
|
5519
|
-
issues:
|
|
5520
|
-
retrySubQuestions:
|
|
6219
|
+
var VerifyResultSchema = import_zod34.z.object({
|
|
6220
|
+
approved: import_zod34.z.boolean().describe("Whether all sub-answers are adequately grounded"),
|
|
6221
|
+
issues: import_zod34.z.array(import_zod34.z.string()).describe("Specific grounding or consistency issues found"),
|
|
6222
|
+
retrySubQuestions: import_zod34.z.array(import_zod34.z.string()).optional().describe("Sub-questions that need additional retrieval or re-reasoning")
|
|
5521
6223
|
});
|
|
5522
|
-
var QueryResultSchema =
|
|
5523
|
-
answer:
|
|
5524
|
-
citations:
|
|
6224
|
+
var QueryResultSchema = import_zod34.z.object({
|
|
6225
|
+
answer: import_zod34.z.string(),
|
|
6226
|
+
citations: import_zod34.z.array(CitationSchema),
|
|
5525
6227
|
intent: QueryIntentSchema,
|
|
5526
|
-
confidence:
|
|
5527
|
-
followUp:
|
|
6228
|
+
confidence: import_zod34.z.number().min(0).max(1),
|
|
6229
|
+
followUp: import_zod34.z.string().optional().describe("Suggested follow-up question if applicable")
|
|
5528
6230
|
});
|
|
5529
6231
|
|
|
5530
6232
|
// src/query/retriever.ts
|
|
@@ -5812,6 +6514,112 @@ async function verify(originalQuestion, subAnswers, allEvidence, config) {
|
|
|
5812
6514
|
return { result: object, usage };
|
|
5813
6515
|
}
|
|
5814
6516
|
|
|
6517
|
+
// src/query/quality.ts
|
|
6518
|
+
function sourceIdForEvidence(evidence) {
|
|
6519
|
+
return evidence.chunkId ?? evidence.documentId ?? evidence.turnId;
|
|
6520
|
+
}
|
|
6521
|
+
function citationSourceId(citation) {
|
|
6522
|
+
return citation.chunkId || citation.documentId;
|
|
6523
|
+
}
|
|
6524
|
+
function buildQueryReviewReport(params) {
|
|
6525
|
+
const { subAnswers, evidence, finalResult, verifyRounds } = params;
|
|
6526
|
+
const issues = [];
|
|
6527
|
+
const evidenceBySource = /* @__PURE__ */ new Map();
|
|
6528
|
+
for (const item of evidence) {
|
|
6529
|
+
const sourceId = sourceIdForEvidence(item);
|
|
6530
|
+
if (!sourceId) continue;
|
|
6531
|
+
evidenceBySource.set(sourceId, [...evidenceBySource.get(sourceId) ?? [], item]);
|
|
6532
|
+
}
|
|
6533
|
+
for (const subAnswer of subAnswers) {
|
|
6534
|
+
if (!subAnswer.needsMoreContext && subAnswer.citations.length === 0) {
|
|
6535
|
+
issues.push({
|
|
6536
|
+
code: "subanswer_missing_citations",
|
|
6537
|
+
severity: "blocking",
|
|
6538
|
+
message: `Sub-answer "${subAnswer.subQuestion}" has no citations despite claiming an answer.`,
|
|
6539
|
+
subQuestion: subAnswer.subQuestion
|
|
6540
|
+
});
|
|
6541
|
+
}
|
|
6542
|
+
if (subAnswer.confidence >= 0.85 && subAnswer.citations.length === 0) {
|
|
6543
|
+
issues.push({
|
|
6544
|
+
code: "subanswer_high_confidence_without_citations",
|
|
6545
|
+
severity: "blocking",
|
|
6546
|
+
message: `Sub-answer "${subAnswer.subQuestion}" has high confidence without citations.`,
|
|
6547
|
+
subQuestion: subAnswer.subQuestion
|
|
6548
|
+
});
|
|
6549
|
+
}
|
|
6550
|
+
for (const citation of subAnswer.citations) {
|
|
6551
|
+
const sourceId = citationSourceId(citation);
|
|
6552
|
+
const supportedEvidence = sourceId ? evidenceBySource.get(sourceId) ?? [] : [];
|
|
6553
|
+
if (!sourceId || supportedEvidence.length === 0) {
|
|
6554
|
+
issues.push({
|
|
6555
|
+
code: "citation_missing_from_evidence",
|
|
6556
|
+
severity: "blocking",
|
|
6557
|
+
message: `Citation [${citation.index}] in "${subAnswer.subQuestion}" does not map to retrieved evidence.`,
|
|
6558
|
+
subQuestion: subAnswer.subQuestion,
|
|
6559
|
+
citationIndex: citation.index,
|
|
6560
|
+
sourceId
|
|
6561
|
+
});
|
|
6562
|
+
continue;
|
|
6563
|
+
}
|
|
6564
|
+
const quoteFound = supportedEvidence.some((item) => item.text.includes(citation.quote));
|
|
6565
|
+
if (!quoteFound) {
|
|
6566
|
+
issues.push({
|
|
6567
|
+
code: "citation_quote_not_in_evidence",
|
|
6568
|
+
severity: "warning",
|
|
6569
|
+
message: `Citation [${citation.index}] quote in "${subAnswer.subQuestion}" was not found verbatim in retrieved evidence.`,
|
|
6570
|
+
subQuestion: subAnswer.subQuestion,
|
|
6571
|
+
citationIndex: citation.index,
|
|
6572
|
+
sourceId
|
|
6573
|
+
});
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6576
|
+
}
|
|
6577
|
+
if (finalResult) {
|
|
6578
|
+
if (finalResult.answer.trim().length > 0 && finalResult.citations.length === 0 && finalResult.confidence > 0.4) {
|
|
6579
|
+
issues.push({
|
|
6580
|
+
code: "final_answer_missing_citations",
|
|
6581
|
+
severity: "blocking",
|
|
6582
|
+
message: "Final answer has non-trivial confidence but no citations."
|
|
6583
|
+
});
|
|
6584
|
+
}
|
|
6585
|
+
const knownCitationIds = new Set(
|
|
6586
|
+
subAnswers.flatMap((sa) => sa.citations.map((citation) => `${citation.index}|${citation.chunkId}|${citation.documentId}`))
|
|
6587
|
+
);
|
|
6588
|
+
for (const citation of finalResult.citations) {
|
|
6589
|
+
const key = `${citation.index}|${citation.chunkId}|${citation.documentId}`;
|
|
6590
|
+
if (!knownCitationIds.has(key)) {
|
|
6591
|
+
issues.push({
|
|
6592
|
+
code: "final_answer_unknown_citation",
|
|
6593
|
+
severity: "warning",
|
|
6594
|
+
message: `Final answer citation [${citation.index}] was not present in verified sub-answers.`,
|
|
6595
|
+
citationIndex: citation.index,
|
|
6596
|
+
sourceId: citationSourceId(citation)
|
|
6597
|
+
});
|
|
6598
|
+
}
|
|
6599
|
+
}
|
|
6600
|
+
}
|
|
6601
|
+
const rounds = verifyRounds.map((round) => ({
|
|
6602
|
+
round: round.round,
|
|
6603
|
+
kind: "verification",
|
|
6604
|
+
status: round.approved && round.issues.length === 0 ? "passed" : "warning",
|
|
6605
|
+
summary: round.issues[0] ?? (round.approved ? "Verification passed." : "Verification requested retry.")
|
|
6606
|
+
}));
|
|
6607
|
+
const artifacts = [
|
|
6608
|
+
{ kind: "evidence", label: "Retrieved Evidence", itemCount: evidence.length },
|
|
6609
|
+
{ kind: "sub_answers", label: "Sub Answers", itemCount: subAnswers.length }
|
|
6610
|
+
];
|
|
6611
|
+
return {
|
|
6612
|
+
issues,
|
|
6613
|
+
rounds,
|
|
6614
|
+
artifacts,
|
|
6615
|
+
verifyRounds,
|
|
6616
|
+
qualityGateStatus: evaluateQualityGate({
|
|
6617
|
+
issues,
|
|
6618
|
+
hasRoundWarnings: verifyRounds.some((round) => !round.approved || round.issues.length > 0)
|
|
6619
|
+
})
|
|
6620
|
+
};
|
|
6621
|
+
}
|
|
6622
|
+
|
|
5815
6623
|
// src/query/coordinator.ts
|
|
5816
6624
|
function createQueryAgent(config) {
|
|
5817
6625
|
const {
|
|
@@ -5825,7 +6633,8 @@ function createQueryAgent(config) {
|
|
|
5825
6633
|
onTokenUsage,
|
|
5826
6634
|
onProgress,
|
|
5827
6635
|
log,
|
|
5828
|
-
providerOptions
|
|
6636
|
+
providerOptions,
|
|
6637
|
+
qualityGate = "warn"
|
|
5829
6638
|
} = config;
|
|
5830
6639
|
const limit = pLimit(concurrency);
|
|
5831
6640
|
let totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
@@ -5894,6 +6703,7 @@ function createQueryAgent(config) {
|
|
|
5894
6703
|
await pipelineCtx.save("reason", { classification, evidence: allEvidence, subAnswers });
|
|
5895
6704
|
onProgress?.("Verifying answer grounding...");
|
|
5896
6705
|
const verifierConfig = { generateObject, providerOptions };
|
|
6706
|
+
const verifyRounds = [];
|
|
5897
6707
|
for (let round = 0; round < maxVerifyRounds; round++) {
|
|
5898
6708
|
const { result: verifyResult, usage } = await safeVerify(
|
|
5899
6709
|
question,
|
|
@@ -5902,6 +6712,12 @@ function createQueryAgent(config) {
|
|
|
5902
6712
|
verifierConfig
|
|
5903
6713
|
);
|
|
5904
6714
|
trackUsage(usage);
|
|
6715
|
+
verifyRounds.push({
|
|
6716
|
+
round: round + 1,
|
|
6717
|
+
approved: verifyResult.approved,
|
|
6718
|
+
issues: verifyResult.issues,
|
|
6719
|
+
retrySubQuestions: verifyResult.retrySubQuestions
|
|
6720
|
+
});
|
|
5905
6721
|
if (verifyResult.approved) {
|
|
5906
6722
|
onProgress?.("Verification passed.");
|
|
5907
6723
|
break;
|
|
@@ -5959,6 +6775,24 @@ function createQueryAgent(config) {
|
|
|
5959
6775
|
classification,
|
|
5960
6776
|
context?.platform
|
|
5961
6777
|
);
|
|
6778
|
+
const reviewReport = buildQueryReviewReport({
|
|
6779
|
+
subAnswers,
|
|
6780
|
+
evidence: allEvidence,
|
|
6781
|
+
finalResult: queryResult,
|
|
6782
|
+
verifyRounds
|
|
6783
|
+
});
|
|
6784
|
+
await pipelineCtx.save("review", {
|
|
6785
|
+
classification,
|
|
6786
|
+
evidence: allEvidence,
|
|
6787
|
+
subAnswers,
|
|
6788
|
+
reviewReport
|
|
6789
|
+
});
|
|
6790
|
+
if (reviewReport.issues.length > 0) {
|
|
6791
|
+
await log?.(`Query deterministic review issues: ${reviewReport.issues.map((issue) => issue.message).join("; ")}`);
|
|
6792
|
+
}
|
|
6793
|
+
if (shouldFailQualityGate(qualityGate, reviewReport.qualityGateStatus)) {
|
|
6794
|
+
throw new Error("Query quality gate failed. See reviewReport for blocking issues.");
|
|
6795
|
+
}
|
|
5962
6796
|
if (conversationId) {
|
|
5963
6797
|
try {
|
|
5964
6798
|
await memoryStore.addTurn({
|
|
@@ -5979,7 +6813,7 @@ function createQueryAgent(config) {
|
|
|
5979
6813
|
await log?.(`Failed to store conversation turn: ${e}`);
|
|
5980
6814
|
}
|
|
5981
6815
|
}
|
|
5982
|
-
return { ...queryResult, tokenUsage: totalUsage };
|
|
6816
|
+
return { ...queryResult, tokenUsage: totalUsage, reviewReport };
|
|
5983
6817
|
}
|
|
5984
6818
|
async function classify(question, conversationId) {
|
|
5985
6819
|
let conversationContext;
|
|
@@ -6201,7 +7035,12 @@ var AGENT_TOOLS = [
|
|
|
6201
7035
|
AdmittedStatusSchema,
|
|
6202
7036
|
AnswerParsingResultSchema,
|
|
6203
7037
|
ApplicationClassifyResultSchema,
|
|
7038
|
+
ApplicationEmailReviewSchema,
|
|
6204
7039
|
ApplicationFieldSchema,
|
|
7040
|
+
ApplicationQualityArtifactSchema,
|
|
7041
|
+
ApplicationQualityIssueSchema,
|
|
7042
|
+
ApplicationQualityReportSchema,
|
|
7043
|
+
ApplicationQualityRoundSchema,
|
|
6205
7044
|
ApplicationStateSchema,
|
|
6206
7045
|
AuditTypeSchema,
|
|
6207
7046
|
AutoFillMatchSchema,
|
|
@@ -6233,6 +7072,7 @@ var AGENT_TOOLS = [
|
|
|
6233
7072
|
CoverageFormSchema,
|
|
6234
7073
|
CoverageSchema,
|
|
6235
7074
|
CoverageTriggerSchema,
|
|
7075
|
+
CoverageValueTypeSchema,
|
|
6236
7076
|
CrimeDeclarationsSchema,
|
|
6237
7077
|
CyberDeclarationsSchema,
|
|
6238
7078
|
DEDUCTIBLE_TYPES,
|