@claritylabs/cl-sdk 3.0.33 → 3.1.2
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 +2 -2
- package/dist/application.d.mts +2 -0
- package/dist/application.d.ts +2 -0
- package/dist/application.js +2351 -0
- package/dist/application.js.map +1 -0
- package/dist/application.mjs +2283 -0
- package/dist/application.mjs.map +1 -0
- package/dist/index-D23NUbxr.d.mts +28983 -0
- package/dist/index-D23NUbxr.d.ts +28983 -0
- package/dist/index.d.mts +2169 -29743
- package/dist/index.d.ts +2169 -29743
- package/dist/index.js +594 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +571 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,2351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/application/index.ts
|
|
21
|
+
var application_exports = {};
|
|
22
|
+
__export(application_exports, {
|
|
23
|
+
AcroFormMappingSchema: () => AcroFormMappingSchema,
|
|
24
|
+
AnswerParsingResultSchema: () => AnswerParsingResultSchema,
|
|
25
|
+
ApplicationClassifyResultSchema: () => ApplicationClassifyResultSchema,
|
|
26
|
+
ApplicationContextProposalSchema: () => ApplicationContextProposalSchema,
|
|
27
|
+
ApplicationEmailReviewSchema: () => ApplicationEmailReviewSchema,
|
|
28
|
+
ApplicationFieldSchema: () => ApplicationFieldSchema,
|
|
29
|
+
ApplicationPacketAnswerSchema: () => ApplicationPacketAnswerSchema,
|
|
30
|
+
ApplicationPacketSchema: () => ApplicationPacketSchema,
|
|
31
|
+
ApplicationQualityArtifactSchema: () => ApplicationQualityArtifactSchema,
|
|
32
|
+
ApplicationQualityIssueSchema: () => ApplicationQualityIssueSchema,
|
|
33
|
+
ApplicationQualityReportSchema: () => ApplicationQualityReportSchema,
|
|
34
|
+
ApplicationQualityRoundSchema: () => ApplicationQualityRoundSchema,
|
|
35
|
+
ApplicationQuestionConditionSchema: () => ApplicationQuestionConditionSchema,
|
|
36
|
+
ApplicationQuestionGraphSchema: () => ApplicationQuestionGraphSchema,
|
|
37
|
+
ApplicationQuestionNodeSchema: () => ApplicationQuestionNodeSchema,
|
|
38
|
+
ApplicationRepeatSchema: () => ApplicationRepeatSchema,
|
|
39
|
+
ApplicationStateSchema: () => ApplicationStateSchema,
|
|
40
|
+
ApplicationTemplateSchema: () => ApplicationTemplateSchema,
|
|
41
|
+
AutoFillMatchSchema: () => AutoFillMatchSchema,
|
|
42
|
+
AutoFillResultSchema: () => AutoFillResultSchema,
|
|
43
|
+
FieldExtractionResultSchema: () => FieldExtractionResultSchema,
|
|
44
|
+
FieldTypeSchema: () => FieldTypeSchema,
|
|
45
|
+
FlatPdfPlacementSchema: () => FlatPdfPlacementSchema,
|
|
46
|
+
LookupFillResultSchema: () => LookupFillResultSchema,
|
|
47
|
+
LookupFillSchema: () => LookupFillSchema,
|
|
48
|
+
LookupRequestSchema: () => LookupRequestSchema,
|
|
49
|
+
ParsedAnswerSchema: () => ParsedAnswerSchema,
|
|
50
|
+
QuestionBatchResultSchema: () => QuestionBatchResultSchema,
|
|
51
|
+
ReplyIntentSchema: () => ReplyIntentSchema,
|
|
52
|
+
applyApplicationAnswers: () => applyApplicationAnswers,
|
|
53
|
+
buildApplicationPacket: () => buildApplicationPacket,
|
|
54
|
+
buildQuestionGraphFromFields: () => buildQuestionGraphFromFields,
|
|
55
|
+
createApplicationPipeline: () => createApplicationPipeline,
|
|
56
|
+
createApplicationRun: () => createApplicationRun,
|
|
57
|
+
extractQuestionGraphFromFields: () => extractQuestionGraphFromFields,
|
|
58
|
+
flattenQuestionGraph: () => flattenQuestionGraph,
|
|
59
|
+
getActiveApplicationFields: () => getActiveApplicationFields,
|
|
60
|
+
getNextApplicationQuestions: () => getNextApplicationQuestions,
|
|
61
|
+
normalizeApplicationQuestionGraph: () => normalizeApplicationQuestionGraph,
|
|
62
|
+
planNextApplicationQuestions: () => planNextApplicationQuestions,
|
|
63
|
+
proposeContextWrites: () => proposeContextWrites,
|
|
64
|
+
validateApplicationPacket: () => validateApplicationPacket
|
|
65
|
+
});
|
|
66
|
+
module.exports = __toCommonJS(application_exports);
|
|
67
|
+
|
|
68
|
+
// src/core/model-budget.ts
|
|
69
|
+
function positiveInteger(value) {
|
|
70
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
|
|
71
|
+
}
|
|
72
|
+
function resolveModelBudget(params) {
|
|
73
|
+
const { taskKind, modelCapabilities, constraint } = params;
|
|
74
|
+
const hintTokens = positiveInteger(params.hintTokens) ?? 4096;
|
|
75
|
+
const taskCapability = positiveInteger(modelCapabilities?.taskOutputTokens?.[taskKind]);
|
|
76
|
+
const longListCapability = taskKind === "extraction_long_list" ? positiveInteger(modelCapabilities?.longListOutputTokens) : void 0;
|
|
77
|
+
const defaultCapability = positiveInteger(modelCapabilities?.defaultOutputTokens);
|
|
78
|
+
const constrainedPreference = positiveInteger(constraint?.outputTokens);
|
|
79
|
+
const minOutputTokens = positiveInteger(constraint?.minOutputTokens);
|
|
80
|
+
const modelMaxOutputTokens = positiveInteger(modelCapabilities?.maxOutputTokens);
|
|
81
|
+
const providerMaxOutputTokens = positiveInteger(params.providerMaxOutputTokens);
|
|
82
|
+
const hardMaxOutputTokens = positiveInteger(constraint?.maxOutputTokens) ?? providerMaxOutputTokens;
|
|
83
|
+
const estimatedInputTokens = estimateTokens(params.inputContextBytes);
|
|
84
|
+
const schemaTokens = estimateTokens(params.schemaSizeBytes) ?? 0;
|
|
85
|
+
const expectedListLength = positiveInteger(params.expectedListLength) ?? 0;
|
|
86
|
+
const warnings = [];
|
|
87
|
+
const preferredOutputTokens = constrainedPreference ?? taskCapability ?? longListCapability ?? defaultCapability ?? hintTokens;
|
|
88
|
+
let maxTokens = hardMaxOutputTokens ?? modelMaxOutputTokens ?? preferredOutputTokens;
|
|
89
|
+
if (minOutputTokens) {
|
|
90
|
+
maxTokens = Math.max(maxTokens, minOutputTokens);
|
|
91
|
+
}
|
|
92
|
+
if (modelMaxOutputTokens) {
|
|
93
|
+
if (maxTokens > modelMaxOutputTokens) {
|
|
94
|
+
warnings.push(`Resolved ${taskKind} budget was capped by model max output tokens.`);
|
|
95
|
+
}
|
|
96
|
+
maxTokens = Math.min(maxTokens, modelMaxOutputTokens);
|
|
97
|
+
}
|
|
98
|
+
if (hardMaxOutputTokens) {
|
|
99
|
+
if (maxTokens > hardMaxOutputTokens) {
|
|
100
|
+
warnings.push(`Resolved ${taskKind} budget was capped by an explicit hard max output token constraint.`);
|
|
101
|
+
}
|
|
102
|
+
maxTokens = Math.min(maxTokens, hardMaxOutputTokens);
|
|
103
|
+
}
|
|
104
|
+
const expectedOutputFloor = expectedOutputTokensFloor(taskKind, schemaTokens, expectedListLength, hintTokens);
|
|
105
|
+
const outputTruncationRisk = maxTokens < expectedOutputFloor * 0.65 ? "high" : maxTokens < expectedOutputFloor ? "medium" : "low";
|
|
106
|
+
if (outputTruncationRisk !== "low") {
|
|
107
|
+
warnings.push(`Resolved ${taskKind} budget may be under-sized for the expected output shape.`);
|
|
108
|
+
}
|
|
109
|
+
const maxInputTokens = positiveInteger(modelCapabilities?.maxInputTokens);
|
|
110
|
+
if (estimatedInputTokens && maxInputTokens && estimatedInputTokens > maxInputTokens * 0.9) {
|
|
111
|
+
warnings.push(`Estimated ${taskKind} input context is close to or above the configured model input limit.`);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
taskKind,
|
|
115
|
+
maxTokens,
|
|
116
|
+
hintTokens,
|
|
117
|
+
preferredOutputTokens,
|
|
118
|
+
modelMaxOutputTokens,
|
|
119
|
+
hardMaxOutputTokens,
|
|
120
|
+
estimatedInputTokens,
|
|
121
|
+
outputTruncationRisk,
|
|
122
|
+
warnings
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function estimateTokens(bytes) {
|
|
126
|
+
const positive = positiveInteger(bytes);
|
|
127
|
+
if (!positive) return void 0;
|
|
128
|
+
return Math.ceil(positive / 4);
|
|
129
|
+
}
|
|
130
|
+
function expectedOutputTokensFloor(taskKind, schemaTokens, expectedListLength, hintTokens) {
|
|
131
|
+
const listMultiplier = taskKind === "extraction_long_list" ? 90 : 45;
|
|
132
|
+
const listFloor = expectedListLength > 0 ? expectedListLength * listMultiplier : 0;
|
|
133
|
+
return Math.max(Math.ceil(schemaTokens * 1.5), listFloor, Math.floor(hintTokens * 0.75));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/core/concurrency.ts
|
|
137
|
+
function pLimit(concurrency) {
|
|
138
|
+
const maxConcurrency = Number.isFinite(concurrency) ? Math.max(1, Math.floor(concurrency)) : 1;
|
|
139
|
+
let active = 0;
|
|
140
|
+
const queue = [];
|
|
141
|
+
function next() {
|
|
142
|
+
if (queue.length > 0 && active < maxConcurrency) {
|
|
143
|
+
active++;
|
|
144
|
+
queue.shift()();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return (fn) => new Promise((resolve, reject) => {
|
|
148
|
+
const run = () => {
|
|
149
|
+
fn().then(resolve, reject).finally(() => {
|
|
150
|
+
active--;
|
|
151
|
+
next();
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
queue.push(run);
|
|
155
|
+
next();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/core/retry.ts
|
|
160
|
+
var MAX_RETRIES = 5;
|
|
161
|
+
var BASE_DELAY_MS = 2e3;
|
|
162
|
+
function isRetryableError(error) {
|
|
163
|
+
if (error instanceof Error) {
|
|
164
|
+
const msg = error.message.toLowerCase();
|
|
165
|
+
if (msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("too many requests")) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (msg.includes("grammar compilation timed out")) return true;
|
|
169
|
+
if (msg.includes("no output generated")) return true;
|
|
170
|
+
if (msg.includes("overloaded")) return true;
|
|
171
|
+
if (msg.includes("internal server error")) return true;
|
|
172
|
+
if (msg.includes("service unavailable")) return true;
|
|
173
|
+
if (msg.includes("gateway timeout")) return true;
|
|
174
|
+
}
|
|
175
|
+
if (typeof error === "object" && error !== null) {
|
|
176
|
+
const status = error.status ?? error.statusCode;
|
|
177
|
+
if (status === 429 || status === 500 || status === 502 || status === 503 || status === 504) return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
async function withRetry(fn, log) {
|
|
182
|
+
for (let attempt = 0; ; attempt++) {
|
|
183
|
+
try {
|
|
184
|
+
return await fn();
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (!isRetryableError(error) || attempt >= MAX_RETRIES) {
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
const jitter = Math.random() * 1e3;
|
|
190
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt) + jitter;
|
|
191
|
+
await log?.(`Retryable error, retrying in ${(delay / 1e3).toFixed(1)}s (attempt ${attempt + 1}/${MAX_RETRIES})...`);
|
|
192
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/prompts/application/classify.ts
|
|
198
|
+
var APPLICATION_CLASSIFY_PROMPT = `You are classifying a PDF document. Determine if this is an insurance APPLICATION FORM (a form to be filled out to apply for insurance) versus a policy document, quote, certificate, or other document.
|
|
199
|
+
|
|
200
|
+
Insurance applications typically:
|
|
201
|
+
- Have blank fields, checkboxes, or spaces to fill in
|
|
202
|
+
- Ask for company information, coverage limits, loss history
|
|
203
|
+
- Include ACORD form numbers or "Application for" in the title
|
|
204
|
+
- Request signatures and dates
|
|
205
|
+
|
|
206
|
+
Respond with JSON only:
|
|
207
|
+
{
|
|
208
|
+
"isApplication": boolean,
|
|
209
|
+
"confidence": number (0-1),
|
|
210
|
+
"applicationType": string | null // e.g. "General Liability", "Professional Liability", "Commercial Property", "Workers Compensation", "ACORD 125", etc.
|
|
211
|
+
}`;
|
|
212
|
+
|
|
213
|
+
// src/schemas/application.ts
|
|
214
|
+
var import_zod = require("zod");
|
|
215
|
+
var FieldTypeSchema = import_zod.z.enum([
|
|
216
|
+
"text",
|
|
217
|
+
"numeric",
|
|
218
|
+
"currency",
|
|
219
|
+
"date",
|
|
220
|
+
"yes_no",
|
|
221
|
+
"table",
|
|
222
|
+
"declaration"
|
|
223
|
+
]);
|
|
224
|
+
var ApplicationFieldSchema = import_zod.z.object({
|
|
225
|
+
id: import_zod.z.string(),
|
|
226
|
+
label: import_zod.z.string(),
|
|
227
|
+
section: import_zod.z.string(),
|
|
228
|
+
fieldType: FieldTypeSchema,
|
|
229
|
+
required: import_zod.z.boolean(),
|
|
230
|
+
options: import_zod.z.array(import_zod.z.string()).optional(),
|
|
231
|
+
columns: import_zod.z.array(import_zod.z.string()).optional(),
|
|
232
|
+
requiresExplanationIfYes: import_zod.z.boolean().optional(),
|
|
233
|
+
condition: import_zod.z.object({
|
|
234
|
+
dependsOn: import_zod.z.string(),
|
|
235
|
+
whenValue: import_zod.z.string()
|
|
236
|
+
}).optional(),
|
|
237
|
+
value: import_zod.z.string().optional(),
|
|
238
|
+
source: import_zod.z.string().optional().describe("Where the value came from: auto-fill, user, lookup"),
|
|
239
|
+
confidence: import_zod.z.enum(["confirmed", "high", "medium", "low"]).optional(),
|
|
240
|
+
sourceSpanIds: import_zod.z.array(import_zod.z.string()).optional().describe("Stable source spans that support the field value or field anchor"),
|
|
241
|
+
userSourceSpanIds: import_zod.z.array(import_zod.z.string()).optional().describe("Message or attachment spans that support user-provided values"),
|
|
242
|
+
pageNumber: import_zod.z.number().int().positive().optional().describe("Application page where the field label or anchor appears"),
|
|
243
|
+
fieldAnchorId: import_zod.z.string().optional().describe("Stable field anchor ID derived from page, section, label, and form metadata"),
|
|
244
|
+
acroFormName: import_zod.z.string().optional().describe("Native PDF AcroForm field name when available"),
|
|
245
|
+
validationStatus: import_zod.z.enum(["valid", "needs_review", "unsupported", "missing"]).optional()
|
|
246
|
+
});
|
|
247
|
+
var ApplicationQuestionConditionSchema = import_zod.z.object({
|
|
248
|
+
dependsOn: import_zod.z.string(),
|
|
249
|
+
operator: import_zod.z.enum(["equals", "not_equals", "in", "not_in", "exists"]).optional(),
|
|
250
|
+
value: import_zod.z.string().optional(),
|
|
251
|
+
whenValue: import_zod.z.string().optional(),
|
|
252
|
+
values: import_zod.z.array(import_zod.z.string()).optional()
|
|
253
|
+
});
|
|
254
|
+
var ApplicationRepeatSchema = import_zod.z.object({
|
|
255
|
+
min: import_zod.z.number().int().nonnegative().optional(),
|
|
256
|
+
max: import_zod.z.number().int().positive().optional(),
|
|
257
|
+
label: import_zod.z.string().optional()
|
|
258
|
+
});
|
|
259
|
+
var ApplicationQuestionNodeSchema = import_zod.z.lazy(
|
|
260
|
+
() => import_zod.z.object({
|
|
261
|
+
id: import_zod.z.string(),
|
|
262
|
+
nodeType: import_zod.z.enum(["group", "question", "repeat_group", "table"]),
|
|
263
|
+
fieldId: import_zod.z.string().optional(),
|
|
264
|
+
fieldPath: import_zod.z.string().optional(),
|
|
265
|
+
parentId: import_zod.z.string().optional(),
|
|
266
|
+
order: import_zod.z.number().int().nonnegative().optional(),
|
|
267
|
+
label: import_zod.z.string(),
|
|
268
|
+
section: import_zod.z.string().optional(),
|
|
269
|
+
fieldType: FieldTypeSchema.optional(),
|
|
270
|
+
required: import_zod.z.boolean().optional(),
|
|
271
|
+
prompt: import_zod.z.string().optional(),
|
|
272
|
+
options: import_zod.z.array(import_zod.z.string()).optional(),
|
|
273
|
+
columns: import_zod.z.array(import_zod.z.string()).optional(),
|
|
274
|
+
condition: ApplicationQuestionConditionSchema.optional(),
|
|
275
|
+
repeat: ApplicationRepeatSchema.optional(),
|
|
276
|
+
children: import_zod.z.array(ApplicationQuestionNodeSchema).optional()
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
var ApplicationQuestionGraphSchema = import_zod.z.object({
|
|
280
|
+
id: import_zod.z.string(),
|
|
281
|
+
version: import_zod.z.string(),
|
|
282
|
+
title: import_zod.z.string().optional(),
|
|
283
|
+
applicationType: import_zod.z.string().nullable().optional(),
|
|
284
|
+
source: import_zod.z.enum(["pdf", "manual", "imported", "generated"]).default("generated"),
|
|
285
|
+
rootNodeIds: import_zod.z.array(import_zod.z.string()).optional(),
|
|
286
|
+
nodes: import_zod.z.array(ApplicationQuestionNodeSchema)
|
|
287
|
+
});
|
|
288
|
+
var ApplicationTemplateSchema = import_zod.z.object({
|
|
289
|
+
id: import_zod.z.string(),
|
|
290
|
+
version: import_zod.z.string(),
|
|
291
|
+
title: import_zod.z.string(),
|
|
292
|
+
applicationType: import_zod.z.string().nullable().optional(),
|
|
293
|
+
questionGraph: ApplicationQuestionGraphSchema,
|
|
294
|
+
fields: import_zod.z.array(ApplicationFieldSchema).optional()
|
|
295
|
+
});
|
|
296
|
+
var ApplicationClassifyResultSchema = import_zod.z.object({
|
|
297
|
+
isApplication: import_zod.z.boolean(),
|
|
298
|
+
confidence: import_zod.z.number().min(0).max(1),
|
|
299
|
+
applicationType: import_zod.z.string().nullable()
|
|
300
|
+
});
|
|
301
|
+
var FieldExtractionResultSchema = import_zod.z.object({
|
|
302
|
+
fields: import_zod.z.array(ApplicationFieldSchema)
|
|
303
|
+
});
|
|
304
|
+
var AutoFillMatchSchema = import_zod.z.object({
|
|
305
|
+
fieldId: import_zod.z.string(),
|
|
306
|
+
value: import_zod.z.string(),
|
|
307
|
+
confidence: import_zod.z.enum(["confirmed"]),
|
|
308
|
+
contextKey: import_zod.z.string()
|
|
309
|
+
});
|
|
310
|
+
var AutoFillResultSchema = import_zod.z.object({
|
|
311
|
+
matches: import_zod.z.array(AutoFillMatchSchema)
|
|
312
|
+
});
|
|
313
|
+
var QuestionBatchResultSchema = import_zod.z.object({
|
|
314
|
+
batches: import_zod.z.array(import_zod.z.array(import_zod.z.string()).describe("Array of field IDs in this batch"))
|
|
315
|
+
});
|
|
316
|
+
var LookupRequestSchema = import_zod.z.object({
|
|
317
|
+
type: import_zod.z.string().describe("Type of lookup: 'records', 'website', 'policy'"),
|
|
318
|
+
description: import_zod.z.string(),
|
|
319
|
+
url: import_zod.z.string().optional(),
|
|
320
|
+
targetFieldIds: import_zod.z.array(import_zod.z.string())
|
|
321
|
+
});
|
|
322
|
+
var ReplyIntentSchema = import_zod.z.object({
|
|
323
|
+
primaryIntent: import_zod.z.enum(["answers_only", "question", "lookup_request", "mixed"]),
|
|
324
|
+
hasAnswers: import_zod.z.boolean(),
|
|
325
|
+
questionText: import_zod.z.string().optional(),
|
|
326
|
+
questionFieldIds: import_zod.z.array(import_zod.z.string()).optional(),
|
|
327
|
+
lookupRequests: import_zod.z.array(LookupRequestSchema).optional()
|
|
328
|
+
});
|
|
329
|
+
var ParsedAnswerSchema = import_zod.z.object({
|
|
330
|
+
fieldId: import_zod.z.string(),
|
|
331
|
+
value: import_zod.z.string(),
|
|
332
|
+
explanation: import_zod.z.string().optional()
|
|
333
|
+
});
|
|
334
|
+
var AnswerParsingResultSchema = import_zod.z.object({
|
|
335
|
+
answers: import_zod.z.array(ParsedAnswerSchema),
|
|
336
|
+
unanswered: import_zod.z.array(import_zod.z.string()).describe("Field IDs that were not answered")
|
|
337
|
+
});
|
|
338
|
+
var LookupFillSchema = import_zod.z.object({
|
|
339
|
+
fieldId: import_zod.z.string(),
|
|
340
|
+
value: import_zod.z.string(),
|
|
341
|
+
source: import_zod.z.string().describe("Specific citable reference, e.g. 'GL Policy #POL-12345 (Hartford)'"),
|
|
342
|
+
sourceSpanIds: import_zod.z.array(import_zod.z.string()).optional()
|
|
343
|
+
});
|
|
344
|
+
var LookupFillResultSchema = import_zod.z.object({
|
|
345
|
+
fills: import_zod.z.array(LookupFillSchema),
|
|
346
|
+
unfillable: import_zod.z.array(import_zod.z.string()),
|
|
347
|
+
explanation: import_zod.z.string().optional()
|
|
348
|
+
});
|
|
349
|
+
var FlatPdfPlacementSchema = import_zod.z.object({
|
|
350
|
+
fieldId: import_zod.z.string(),
|
|
351
|
+
page: import_zod.z.number(),
|
|
352
|
+
x: import_zod.z.number().describe("Percentage from left edge (0-100)"),
|
|
353
|
+
y: import_zod.z.number().describe("Percentage from top edge (0-100)"),
|
|
354
|
+
text: import_zod.z.string(),
|
|
355
|
+
fontSize: import_zod.z.number().optional(),
|
|
356
|
+
isCheckmark: import_zod.z.boolean().optional()
|
|
357
|
+
});
|
|
358
|
+
var AcroFormMappingSchema = import_zod.z.object({
|
|
359
|
+
fieldId: import_zod.z.string(),
|
|
360
|
+
acroFormName: import_zod.z.string(),
|
|
361
|
+
value: import_zod.z.string()
|
|
362
|
+
});
|
|
363
|
+
var QualityGateStatusSchema = import_zod.z.enum(["passed", "warning", "failed"]);
|
|
364
|
+
var QualitySeveritySchema = import_zod.z.enum(["info", "warning", "blocking"]);
|
|
365
|
+
var ApplicationQualityIssueSchema = import_zod.z.object({
|
|
366
|
+
code: import_zod.z.string(),
|
|
367
|
+
severity: QualitySeveritySchema,
|
|
368
|
+
message: import_zod.z.string(),
|
|
369
|
+
fieldId: import_zod.z.string().optional()
|
|
370
|
+
});
|
|
371
|
+
var ApplicationQualityRoundSchema = import_zod.z.object({
|
|
372
|
+
round: import_zod.z.number(),
|
|
373
|
+
kind: import_zod.z.string(),
|
|
374
|
+
status: QualityGateStatusSchema,
|
|
375
|
+
summary: import_zod.z.string().optional()
|
|
376
|
+
});
|
|
377
|
+
var ApplicationQualityArtifactSchema = import_zod.z.object({
|
|
378
|
+
kind: import_zod.z.string(),
|
|
379
|
+
label: import_zod.z.string().optional(),
|
|
380
|
+
itemCount: import_zod.z.number().optional()
|
|
381
|
+
});
|
|
382
|
+
var ApplicationEmailReviewSchema = import_zod.z.object({
|
|
383
|
+
issues: import_zod.z.array(ApplicationQualityIssueSchema),
|
|
384
|
+
qualityGateStatus: QualityGateStatusSchema
|
|
385
|
+
});
|
|
386
|
+
var ApplicationQualityReportSchema = import_zod.z.object({
|
|
387
|
+
issues: import_zod.z.array(ApplicationQualityIssueSchema),
|
|
388
|
+
rounds: import_zod.z.array(ApplicationQualityRoundSchema).optional(),
|
|
389
|
+
artifacts: import_zod.z.array(ApplicationQualityArtifactSchema).optional(),
|
|
390
|
+
emailReview: ApplicationEmailReviewSchema.optional(),
|
|
391
|
+
qualityGateStatus: QualityGateStatusSchema
|
|
392
|
+
});
|
|
393
|
+
var ApplicationContextProposalSchema = import_zod.z.object({
|
|
394
|
+
id: import_zod.z.string(),
|
|
395
|
+
fieldId: import_zod.z.string().optional(),
|
|
396
|
+
key: import_zod.z.string(),
|
|
397
|
+
value: import_zod.z.string(),
|
|
398
|
+
category: import_zod.z.string(),
|
|
399
|
+
source: import_zod.z.enum(["application", "user", "lookup", "policy", "email", "chat", "imessage", "mcp"]),
|
|
400
|
+
confidence: import_zod.z.enum(["confirmed", "high", "medium", "low"]),
|
|
401
|
+
sourceSpanIds: import_zod.z.array(import_zod.z.string()).optional(),
|
|
402
|
+
userSourceSpanIds: import_zod.z.array(import_zod.z.string()).optional()
|
|
403
|
+
});
|
|
404
|
+
var ApplicationPacketAnswerSchema = import_zod.z.object({
|
|
405
|
+
fieldId: import_zod.z.string(),
|
|
406
|
+
label: import_zod.z.string(),
|
|
407
|
+
section: import_zod.z.string(),
|
|
408
|
+
value: import_zod.z.string(),
|
|
409
|
+
source: import_zod.z.string(),
|
|
410
|
+
confidence: import_zod.z.enum(["confirmed", "high", "medium", "low"]).optional(),
|
|
411
|
+
sourceSpanIds: import_zod.z.array(import_zod.z.string()).optional(),
|
|
412
|
+
userSourceSpanIds: import_zod.z.array(import_zod.z.string()).optional()
|
|
413
|
+
});
|
|
414
|
+
var ApplicationPacketSchema = import_zod.z.object({
|
|
415
|
+
id: import_zod.z.string(),
|
|
416
|
+
applicationId: import_zod.z.string(),
|
|
417
|
+
title: import_zod.z.string(),
|
|
418
|
+
status: import_zod.z.enum(["draft", "broker_ready", "submitted"]),
|
|
419
|
+
answers: import_zod.z.array(ApplicationPacketAnswerSchema),
|
|
420
|
+
missingFieldIds: import_zod.z.array(import_zod.z.string()),
|
|
421
|
+
qualityReport: ApplicationQualityReportSchema,
|
|
422
|
+
submissionNotes: import_zod.z.string().optional(),
|
|
423
|
+
createdAt: import_zod.z.number()
|
|
424
|
+
});
|
|
425
|
+
var ApplicationStateSchema = import_zod.z.object({
|
|
426
|
+
id: import_zod.z.string(),
|
|
427
|
+
pdfBase64: import_zod.z.string().optional().describe("Original PDF, omitted after extraction"),
|
|
428
|
+
templateId: import_zod.z.string().optional(),
|
|
429
|
+
templateVersion: import_zod.z.string().optional(),
|
|
430
|
+
templateSnapshot: ApplicationTemplateSchema.optional(),
|
|
431
|
+
title: import_zod.z.string().optional(),
|
|
432
|
+
applicationType: import_zod.z.string().nullable().optional(),
|
|
433
|
+
questionGraph: ApplicationQuestionGraphSchema.optional(),
|
|
434
|
+
fields: import_zod.z.array(ApplicationFieldSchema),
|
|
435
|
+
batches: import_zod.z.array(import_zod.z.array(import_zod.z.string())).optional(),
|
|
436
|
+
currentBatchIndex: import_zod.z.number().default(0),
|
|
437
|
+
contextProposals: import_zod.z.array(ApplicationContextProposalSchema).optional(),
|
|
438
|
+
packet: ApplicationPacketSchema.optional(),
|
|
439
|
+
qualityReport: ApplicationQualityReportSchema.optional(),
|
|
440
|
+
status: import_zod.z.enum([
|
|
441
|
+
"classifying",
|
|
442
|
+
"extracting",
|
|
443
|
+
"auto_filling",
|
|
444
|
+
"batching",
|
|
445
|
+
"collecting",
|
|
446
|
+
"confirming",
|
|
447
|
+
"mapping",
|
|
448
|
+
"broker_review",
|
|
449
|
+
"packet_ready",
|
|
450
|
+
"submitted",
|
|
451
|
+
"cancelled",
|
|
452
|
+
"complete"
|
|
453
|
+
]),
|
|
454
|
+
createdAt: import_zod.z.number(),
|
|
455
|
+
updatedAt: import_zod.z.number()
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// src/application/agents/classifier.ts
|
|
459
|
+
async function classifyApplication(pdfContent, generateObject, providerOptions, maxTokens = 512) {
|
|
460
|
+
const { object, usage } = await withRetry(
|
|
461
|
+
() => generateObject({
|
|
462
|
+
prompt: `${APPLICATION_CLASSIFY_PROMPT}
|
|
463
|
+
|
|
464
|
+
Analyze the attached insurance document. If text source units are provided in provider options, use them as supporting context. Do not infer from base64 text.`,
|
|
465
|
+
schema: ApplicationClassifyResultSchema,
|
|
466
|
+
maxTokens,
|
|
467
|
+
taskKind: "application_classify",
|
|
468
|
+
providerOptions: {
|
|
469
|
+
...providerOptions,
|
|
470
|
+
pdfBase64: providerOptions?.pdfBase64 ?? pdfContent
|
|
471
|
+
}
|
|
472
|
+
})
|
|
473
|
+
);
|
|
474
|
+
return { result: object, usage };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/prompts/application/field-extraction.ts
|
|
478
|
+
function buildFieldExtractionPrompt() {
|
|
479
|
+
return `Extract all fillable fields from this insurance application PDF as a JSON array. Be concise \u2014 use short IDs and minimal keys.
|
|
480
|
+
|
|
481
|
+
Field types: "text", "numeric", "currency", "date", "yes_no", "table", "declaration"
|
|
482
|
+
|
|
483
|
+
Required keys per field:
|
|
484
|
+
- "id": short provisional snake_case ID. The SDK will replace this with a stable deterministic ID.
|
|
485
|
+
- "label": field label \u2014 a clear, natural question that a human would understand
|
|
486
|
+
- "section": section heading
|
|
487
|
+
- "fieldType": one of the types above
|
|
488
|
+
- "required": boolean
|
|
489
|
+
|
|
490
|
+
Optional keys (only include when applicable):
|
|
491
|
+
- "sourceSpanIds": stable source span IDs if the caller provided source units for this application
|
|
492
|
+
- "pageNumber": PDF page number where the field label/anchor appears
|
|
493
|
+
- "fieldAnchorId": stable caller-provided field anchor ID, when available
|
|
494
|
+
- "acroFormName": native PDF form field name, when visible or provided
|
|
495
|
+
- "validationStatus": "missing" for extracted blank fields, "needs_review" for prefilled fields that need source validation
|
|
496
|
+
- "options": array of strings \u2014 for fields with checkboxes/radio buttons/multiple choices (e.g. business type, state selections). Use "text" fieldType with options.
|
|
497
|
+
- "columns": array of {"name","type"} \u2014 tables only
|
|
498
|
+
- "requiresExplanationIfYes": boolean \u2014 declarations only
|
|
499
|
+
- "condition": {"dependsOn":"field_id","whenValue":"value"} \u2014 conditional fields only
|
|
500
|
+
|
|
501
|
+
IMPORTANT \u2014 Grouped fields: When you see a group of checkboxes or radio buttons for a single question (e.g. "Type of Business: Corporation / Partnership / LLC / Individual / Joint Venture / Other"), extract as ONE field with the group label and an "options" array \u2014 NOT as separate fields for each option. The label should describe what's being asked (e.g. "Type of Business Entity"), and options lists the choices.
|
|
502
|
+
|
|
503
|
+
Example:
|
|
504
|
+
[
|
|
505
|
+
{"id":"company_name","label":"Applicant Name","section":"General Info","fieldType":"text","required":true},
|
|
506
|
+
{"id":"business_type","label":"Type of Business Entity","section":"General Info","fieldType":"text","required":true,"options":["Corporation","Partnership","LLC","Individual","Joint Venture","Other"]},
|
|
507
|
+
{"id":"loss_history","label":"Loss History","section":"Losses","fieldType":"table","required":true,"columns":[{"name":"Year","type":"numeric"},{"name":"Amount","type":"currency"}]},
|
|
508
|
+
{"id":"prior_claims","text":"Any claims in past 5 years?","section":"Declarations","fieldType":"declaration","required":true,"requiresExplanationIfYes":true}
|
|
509
|
+
]
|
|
510
|
+
|
|
511
|
+
Extract ALL fields. Prefer page numbers and source span IDs over model-generated guesses whenever source units are supplied. Respond with ONLY the JSON array, no other text.`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/application/field-ids.ts
|
|
515
|
+
function normalizePart(value) {
|
|
516
|
+
const normalized = (value ?? "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
517
|
+
return normalized || "unknown";
|
|
518
|
+
}
|
|
519
|
+
function hashText(value) {
|
|
520
|
+
let hash = 2166136261;
|
|
521
|
+
for (let index = 0; index < value.length; index++) {
|
|
522
|
+
hash ^= value.charCodeAt(index);
|
|
523
|
+
hash = Math.imul(hash, 16777619);
|
|
524
|
+
}
|
|
525
|
+
return (hash >>> 0).toString(16).padStart(8, "0").slice(0, 8);
|
|
526
|
+
}
|
|
527
|
+
function buildApplicationFieldAnchorId(field) {
|
|
528
|
+
const page = field.pageNumber ? `p${field.pageNumber}` : "pna";
|
|
529
|
+
const section = normalizePart(field.section);
|
|
530
|
+
const label = normalizePart(field.label);
|
|
531
|
+
const acroFormName = normalizePart(field.acroFormName);
|
|
532
|
+
const hash = hashText(`${page}|${section}|${label}|${acroFormName}`);
|
|
533
|
+
return `app_field_anchor:${page}:${section}:${label}:${hash}`;
|
|
534
|
+
}
|
|
535
|
+
function buildStableApplicationFieldId(field) {
|
|
536
|
+
const page = field.pageNumber ? `p${field.pageNumber}` : "pna";
|
|
537
|
+
const section = normalizePart(field.section);
|
|
538
|
+
const label = normalizePart(field.label);
|
|
539
|
+
const fieldType = normalizePart(field.fieldType);
|
|
540
|
+
const anchor = field.fieldAnchorId ?? buildApplicationFieldAnchorId(field);
|
|
541
|
+
const hash = hashText(`${page}|${section}|${label}|${fieldType}|${field.acroFormName ?? ""}|${anchor}`);
|
|
542
|
+
return `app_field:${page}:${section}:${label}:${hash}`;
|
|
543
|
+
}
|
|
544
|
+
function normalizeApplicationFields(fields) {
|
|
545
|
+
const seen = /* @__PURE__ */ new Map();
|
|
546
|
+
return fields.map((field) => {
|
|
547
|
+
const fieldAnchorId = field.fieldAnchorId ?? buildApplicationFieldAnchorId(field);
|
|
548
|
+
const baseId = buildStableApplicationFieldId({ ...field, fieldAnchorId });
|
|
549
|
+
const count = seen.get(baseId) ?? 0;
|
|
550
|
+
seen.set(baseId, count + 1);
|
|
551
|
+
return {
|
|
552
|
+
...field,
|
|
553
|
+
id: count === 0 ? baseId : `${baseId}:${count + 1}`,
|
|
554
|
+
fieldAnchorId,
|
|
555
|
+
validationStatus: field.validationStatus ?? (field.value ? "needs_review" : "missing")
|
|
556
|
+
};
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// src/application/agents/field-extractor.ts
|
|
561
|
+
async function extractFields(pdfContent, generateObject, providerOptions, maxTokens = 8192) {
|
|
562
|
+
const prompt = `${buildFieldExtractionPrompt()}
|
|
563
|
+
|
|
564
|
+
Extract fields from the attached application PDF. Use provider-supplied source units/spans for page numbers and anchors when present. Do not treat raw base64 as readable document text.`;
|
|
565
|
+
const { object, usage } = await withRetry(
|
|
566
|
+
() => generateObject({
|
|
567
|
+
prompt,
|
|
568
|
+
schema: FieldExtractionResultSchema,
|
|
569
|
+
maxTokens,
|
|
570
|
+
taskKind: "application_extract_fields",
|
|
571
|
+
providerOptions: {
|
|
572
|
+
...providerOptions,
|
|
573
|
+
pdfBase64: providerOptions?.pdfBase64 ?? pdfContent
|
|
574
|
+
}
|
|
575
|
+
})
|
|
576
|
+
);
|
|
577
|
+
const result = object;
|
|
578
|
+
return { fields: normalizeApplicationFields(result.fields), usage };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/prompts/application/auto-fill.ts
|
|
582
|
+
function buildAutoFillPrompt(fields, orgContext) {
|
|
583
|
+
const fieldList = fields.map((f) => `- ${f.id}: "${f.label}" (${f.fieldType}, section: ${f.section})`).join("\n");
|
|
584
|
+
const contextList = orgContext.map((c) => `- ${c.key}: "${c.value}" (category: ${c.category})`).join("\n");
|
|
585
|
+
return `You are matching insurance application fields to existing business context data.
|
|
586
|
+
|
|
587
|
+
APPLICATION FIELDS:
|
|
588
|
+
${fieldList}
|
|
589
|
+
|
|
590
|
+
AVAILABLE BUSINESS CONTEXT:
|
|
591
|
+
${contextList}
|
|
592
|
+
|
|
593
|
+
For each field that can be filled from the context, provide a match. Only match when you are confident the context value correctly answers the field. For date fields, ensure format compatibility.
|
|
594
|
+
|
|
595
|
+
Respond with JSON only:
|
|
596
|
+
{
|
|
597
|
+
"matches": [
|
|
598
|
+
{
|
|
599
|
+
"fieldId": "company_name",
|
|
600
|
+
"value": "Acme Corp",
|
|
601
|
+
"confidence": "confirmed",
|
|
602
|
+
"contextKey": "company_name"
|
|
603
|
+
}
|
|
604
|
+
]
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
Only include fields you can confidently fill. Do not guess or fabricate values.`;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/application/agents/auto-filler.ts
|
|
611
|
+
async function autoFillFromContext(fields, orgContext, generateObject, providerOptions, maxTokens = 4096) {
|
|
612
|
+
const fieldSummaries = fields.map((f) => ({
|
|
613
|
+
id: f.id,
|
|
614
|
+
label: f.label,
|
|
615
|
+
fieldType: f.fieldType,
|
|
616
|
+
section: f.section
|
|
617
|
+
}));
|
|
618
|
+
const prompt = buildAutoFillPrompt(fieldSummaries, orgContext);
|
|
619
|
+
const { object, usage } = await withRetry(
|
|
620
|
+
() => generateObject({
|
|
621
|
+
prompt,
|
|
622
|
+
schema: AutoFillResultSchema,
|
|
623
|
+
maxTokens,
|
|
624
|
+
taskKind: "application_auto_fill",
|
|
625
|
+
providerOptions
|
|
626
|
+
})
|
|
627
|
+
);
|
|
628
|
+
return { result: object, usage };
|
|
629
|
+
}
|
|
630
|
+
async function backfillFromPriorAnswers(fields, backfillProvider) {
|
|
631
|
+
const unfilled = fields.filter((f) => !f.value);
|
|
632
|
+
if (unfilled.length === 0) return [];
|
|
633
|
+
return backfillProvider.searchPriorAnswers(
|
|
634
|
+
unfilled.map((f) => ({
|
|
635
|
+
id: f.id,
|
|
636
|
+
label: f.label,
|
|
637
|
+
section: f.section,
|
|
638
|
+
fieldType: f.fieldType
|
|
639
|
+
})),
|
|
640
|
+
{ limit: unfilled.length * 2 }
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// src/prompts/application/question-batch.ts
|
|
645
|
+
function buildQuestionBatchPrompt(unfilledFields) {
|
|
646
|
+
const fieldList = unfilledFields.map(
|
|
647
|
+
(f) => {
|
|
648
|
+
let line = `- ${f.id}: "${f.label ?? f.text}" (${f.fieldType}, section: ${f.section}, required: ${f.required})`;
|
|
649
|
+
if (f.condition) line += ` [depends on: ${f.condition.dependsOn} when "${f.condition.whenValue}"]`;
|
|
650
|
+
return line;
|
|
651
|
+
}
|
|
652
|
+
).join("\n");
|
|
653
|
+
return `You are organizing insurance application questions into topic-based email batches. Each batch = one email, grouped by topic so the recipient can answer related questions together.
|
|
654
|
+
|
|
655
|
+
UNFILLED FIELDS:
|
|
656
|
+
${fieldList}
|
|
657
|
+
|
|
658
|
+
Rules:
|
|
659
|
+
- Group by TOPIC, not by fixed size. All questions about the same topic belong in the same batch.
|
|
660
|
+
- Typical topics: Company/Applicant Info, Business Operations, Financial/Revenue, Coverage/Limits, Loss History, Declarations, Premises/Location, etc.
|
|
661
|
+
- A batch can have as many questions as the topic requires \u2014 don't split a natural topic group across multiple emails.
|
|
662
|
+
- If a topic has 20+ fields, you may split into sub-topics (e.g. "Premises - Location" vs "Premises - Details").
|
|
663
|
+
- Put required fields before optional ones within each batch.
|
|
664
|
+
- Keep conditional fields in the same batch as the field they depend on, with the parent field listed BEFORE dependents.
|
|
665
|
+
- Keep related address-like fields (street, city, state, zip, address) in the same batch so the email generator can merge them into a single compound question.
|
|
666
|
+
- Order batches by importance: company info first, then operations, financial, coverage, declarations last.
|
|
667
|
+
- Aim for roughly 3-8 batches total. Fewer large topical batches are better than many tiny ones.
|
|
668
|
+
|
|
669
|
+
Respond with JSON only:
|
|
670
|
+
{
|
|
671
|
+
"batches": [
|
|
672
|
+
["field_id_1", "field_id_2", "field_id_3", ...],
|
|
673
|
+
["field_id_4", "field_id_5", ...]
|
|
674
|
+
]
|
|
675
|
+
}`;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/application/agents/batcher.ts
|
|
679
|
+
async function batchQuestions(unfilledFields, generateObject, providerOptions, maxTokens = 2048) {
|
|
680
|
+
const fieldSummaries = unfilledFields.map((f) => ({
|
|
681
|
+
id: f.id,
|
|
682
|
+
label: f.label,
|
|
683
|
+
text: f.label,
|
|
684
|
+
fieldType: f.fieldType,
|
|
685
|
+
section: f.section,
|
|
686
|
+
required: f.required,
|
|
687
|
+
condition: f.condition
|
|
688
|
+
}));
|
|
689
|
+
const prompt = buildQuestionBatchPrompt(fieldSummaries);
|
|
690
|
+
const { object, usage } = await withRetry(
|
|
691
|
+
() => generateObject({
|
|
692
|
+
prompt,
|
|
693
|
+
schema: QuestionBatchResultSchema,
|
|
694
|
+
maxTokens,
|
|
695
|
+
taskKind: "application_batch",
|
|
696
|
+
providerOptions
|
|
697
|
+
})
|
|
698
|
+
);
|
|
699
|
+
return { result: object, usage };
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/prompts/application/reply-intent.ts
|
|
703
|
+
function buildReplyIntentClassificationPrompt(questions, emailBody) {
|
|
704
|
+
const questionList = questions.map((q, i) => `${i + 1}. ${q.id}: "${q.label}"`).join("\n");
|
|
705
|
+
return `Classify the intent of this email reply to insurance application questions.
|
|
706
|
+
|
|
707
|
+
QUESTIONS THAT WERE ASKED:
|
|
708
|
+
${questionList}
|
|
709
|
+
|
|
710
|
+
USER'S EMAIL REPLY:
|
|
711
|
+
${emailBody}
|
|
712
|
+
|
|
713
|
+
Classify the primary intent:
|
|
714
|
+
- "answers_only": User is providing answers to the questions
|
|
715
|
+
- "question": User is asking a question about one or more fields (e.g. "What does aggregate limit mean?")
|
|
716
|
+
- "lookup_request": User is requesting data be pulled from existing records OR from a third-party website (e.g. "Use our GL policy for coverage info", "Check Stripe's site for PCI compliance info", "Pull from our last application")
|
|
717
|
+
- "mixed": User is providing some answers AND asking questions or requesting lookups
|
|
718
|
+
|
|
719
|
+
IMPORTANT: When a user provides answers AND asks you to look something up (e.g. "Yes we use Stripe, check their site for PCI info"), classify as "mixed" with hasAnswers=true and a lookupRequest \u2014 NOT as "question". A "question" is when the user asks what a field means, not when they direct you to a data source.
|
|
720
|
+
|
|
721
|
+
Respond with JSON only:
|
|
722
|
+
{
|
|
723
|
+
"primaryIntent": "answers_only" | "question" | "lookup_request" | "mixed",
|
|
724
|
+
"hasAnswers": boolean,
|
|
725
|
+
"questionText": "the user's question if any, or null",
|
|
726
|
+
"questionFieldIds": ["field_ids the question is about, if identifiable"],
|
|
727
|
+
"lookupRequests": [
|
|
728
|
+
{
|
|
729
|
+
"type": "policy" | "quote" | "profile" | "business_context" | "web",
|
|
730
|
+
"description": "what they want looked up",
|
|
731
|
+
"url": "URL or domain mentioned (e.g. 'stripe.com'), or null if not a web lookup",
|
|
732
|
+
"targetFieldIds": ["field_ids to fill from the lookup"]
|
|
733
|
+
}
|
|
734
|
+
]
|
|
735
|
+
}`;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// src/application/agents/reply-router.ts
|
|
739
|
+
async function classifyReplyIntent(fields, replyText, generateObject, providerOptions, maxTokens = 1024) {
|
|
740
|
+
const fieldSummaries = fields.map((f) => ({ id: f.id, label: f.label }));
|
|
741
|
+
const prompt = buildReplyIntentClassificationPrompt(fieldSummaries, replyText);
|
|
742
|
+
const { object, usage } = await withRetry(
|
|
743
|
+
() => generateObject({
|
|
744
|
+
prompt,
|
|
745
|
+
schema: ReplyIntentSchema,
|
|
746
|
+
maxTokens,
|
|
747
|
+
taskKind: "application_classify",
|
|
748
|
+
providerOptions
|
|
749
|
+
})
|
|
750
|
+
);
|
|
751
|
+
return { intent: object, usage };
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/prompts/application/answer-parsing.ts
|
|
755
|
+
function buildAnswerParsingPrompt(questions, emailBody) {
|
|
756
|
+
const questionList = questions.map(
|
|
757
|
+
(q, i) => `${i + 1}. ${q.id}: "${q.label ?? q.text}" (type: ${q.fieldType})`
|
|
758
|
+
).join("\n");
|
|
759
|
+
return `You are parsing a user's email reply to extract answers for specific insurance application questions.
|
|
760
|
+
|
|
761
|
+
QUESTIONS ASKED:
|
|
762
|
+
${questionList}
|
|
763
|
+
|
|
764
|
+
USER'S EMAIL REPLY:
|
|
765
|
+
${emailBody}
|
|
766
|
+
|
|
767
|
+
Extract answers for each question. Handle:
|
|
768
|
+
- Direct numbered answers (1. answer, 2. answer)
|
|
769
|
+
- Inline answers referencing the question
|
|
770
|
+
- Table data provided as lists or comma-separated values
|
|
771
|
+
- Yes/no answers with optional explanations
|
|
772
|
+
- Partial responses (some questions answered, others skipped)
|
|
773
|
+
|
|
774
|
+
Respond with JSON only:
|
|
775
|
+
{
|
|
776
|
+
"answers": [
|
|
777
|
+
{
|
|
778
|
+
"fieldId": "company_name",
|
|
779
|
+
"value": "Acme Corp"
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
"fieldId": "prior_claims_decl",
|
|
783
|
+
"value": "yes",
|
|
784
|
+
"explanation": "One claim in 2024 for water damage, $15,000 paid"
|
|
785
|
+
}
|
|
786
|
+
],
|
|
787
|
+
"unanswered": ["field_id_that_was_not_answered"]
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
Only include answers you are confident about. If a response is ambiguous, include the field in "unanswered".`;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/application/agents/answer-parser.ts
|
|
794
|
+
async function parseAnswers(fields, replyText, generateObject, providerOptions, maxTokens = 4096) {
|
|
795
|
+
const questions = fields.map((f) => ({
|
|
796
|
+
id: f.id,
|
|
797
|
+
label: f.label,
|
|
798
|
+
text: f.label,
|
|
799
|
+
fieldType: f.fieldType
|
|
800
|
+
}));
|
|
801
|
+
const prompt = buildAnswerParsingPrompt(questions, replyText);
|
|
802
|
+
const { object, usage } = await withRetry(
|
|
803
|
+
() => generateObject({
|
|
804
|
+
prompt,
|
|
805
|
+
schema: AnswerParsingResultSchema,
|
|
806
|
+
maxTokens,
|
|
807
|
+
taskKind: "application_parse_answers",
|
|
808
|
+
providerOptions
|
|
809
|
+
})
|
|
810
|
+
);
|
|
811
|
+
return { result: object, usage };
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// src/prompts/application/pdf-mapping.ts
|
|
815
|
+
function buildLookupFillPrompt(requests, targetFields, availableData) {
|
|
816
|
+
const requestList = requests.map((r) => `- ${r.type}: ${r.description} (target fields: ${r.targetFieldIds.join(", ")})`).join("\n");
|
|
817
|
+
const fieldList = targetFields.map((f) => `- ${f.id}: "${f.label}" (${f.fieldType})`).join("\n");
|
|
818
|
+
return `You are an internal risk management assistant filling out an insurance application for your company. A colleague asked you to look up data from existing company records to fill certain fields.
|
|
819
|
+
|
|
820
|
+
LOOKUP REQUESTS:
|
|
821
|
+
${requestList}
|
|
822
|
+
|
|
823
|
+
TARGET FIELDS:
|
|
824
|
+
${fieldList}
|
|
825
|
+
|
|
826
|
+
AVAILABLE DATA:
|
|
827
|
+
${availableData}
|
|
828
|
+
|
|
829
|
+
Match the available data to the target fields. Only fill fields where you have a confident match.
|
|
830
|
+
|
|
831
|
+
IMPORTANT: The "source" field must be a specific, citable reference that will be shown to the user. Examples:
|
|
832
|
+
- "GL Policy #POL-12345 (Hartford)"
|
|
833
|
+
- "vercel.com (Security page)"
|
|
834
|
+
- "Business Context (company_info)"
|
|
835
|
+
- "User Profile"
|
|
836
|
+
Never use vague sources like "existing records" or "available data".
|
|
837
|
+
If AVAILABLE DATA contains sourceSpanId values, include them in "sourceSpanIds" for every value filled from that source. Existing policy values such as policy numbers, dates, limits, deductibles, premiums, coverages, exclusions, conditions, endorsements, locations, vehicles, or named insureds must not be filled without sourceSpanIds unless the value is explicitly marked for review.
|
|
838
|
+
|
|
839
|
+
Respond with JSON only:
|
|
840
|
+
{
|
|
841
|
+
"fills": [
|
|
842
|
+
{ "fieldId": "field_id", "value": "the value from data", "source": "Specific source with identifier (e.g. GL Policy #ABC123, stripe.com)", "sourceSpanIds": ["doc-1:span:1:0:abcd1234"] }
|
|
843
|
+
],
|
|
844
|
+
"unfillable": ["field_ids that couldn't be matched"],
|
|
845
|
+
"explanation": "Brief note about what was filled and what couldn't be found, citing sources"
|
|
846
|
+
}`;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/application/agents/lookup-filler.ts
|
|
850
|
+
async function fillFromLookup(requests, targetFields, availableData, generateObject, providerOptions, maxTokens = 4096) {
|
|
851
|
+
const requestSummaries = requests.map((r) => ({
|
|
852
|
+
type: r.type,
|
|
853
|
+
description: r.description,
|
|
854
|
+
targetFieldIds: r.targetFieldIds
|
|
855
|
+
}));
|
|
856
|
+
const fieldSummaries = targetFields.map((f) => ({
|
|
857
|
+
id: f.id,
|
|
858
|
+
label: f.label,
|
|
859
|
+
fieldType: f.fieldType
|
|
860
|
+
}));
|
|
861
|
+
const prompt = buildLookupFillPrompt(requestSummaries, fieldSummaries, availableData);
|
|
862
|
+
const { object, usage } = await withRetry(
|
|
863
|
+
() => generateObject({
|
|
864
|
+
prompt,
|
|
865
|
+
schema: LookupFillResultSchema,
|
|
866
|
+
maxTokens,
|
|
867
|
+
taskKind: "application_lookup",
|
|
868
|
+
providerOptions
|
|
869
|
+
})
|
|
870
|
+
);
|
|
871
|
+
return { result: object, usage };
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/prompts/application/batch-email.ts
|
|
875
|
+
function buildBatchEmailGenerationPrompt(batchFields, batchIndex, totalBatches, appTitle, totalFieldCount, filledFieldCount, previousBatchSummary, companyName) {
|
|
876
|
+
const nonConditionalFields = batchFields.filter((f) => !f.condition);
|
|
877
|
+
const conditionalFields = batchFields.filter((f) => f.condition);
|
|
878
|
+
const fieldList = nonConditionalFields.map((f, i) => {
|
|
879
|
+
let line = `${i + 1}. id="${f.id}" label="${f.label}" type=${f.fieldType}`;
|
|
880
|
+
if (f.options) line += ` options=[${f.options.join(", ")}]`;
|
|
881
|
+
return line;
|
|
882
|
+
}).join("\n");
|
|
883
|
+
const conditionalNote = conditionalFields.length > 0 ? `
|
|
884
|
+
|
|
885
|
+
CONDITIONAL FIELDS (DO NOT include in this email \u2014 they will be asked as follow-ups in a separate email after the parent is answered):
|
|
886
|
+
${conditionalFields.map((f) => `- id="${f.id}" label="${f.label}" depends on ${f.condition.dependsOn} = "${f.condition.whenValue}"`).join("\n")}` : "";
|
|
887
|
+
const company = companyName ?? "the company";
|
|
888
|
+
const remainingFields = totalFieldCount - filledFieldCount;
|
|
889
|
+
const estMinutes = Math.max(1, Math.round(remainingFields * 0.5));
|
|
890
|
+
return `You are an internal risk management assistant helping your colleague fill out an insurance application for ${company}. You work FOR ${company} \u2014 you are NOT the insurer, broker, or any external party.
|
|
891
|
+
|
|
892
|
+
APPLICATION: ${appTitle ?? "Insurance Application"}
|
|
893
|
+
COMPANY: ${company}
|
|
894
|
+
PROGRESS: ${filledFieldCount} of ${totalFieldCount} fields done, ~${remainingFields} remaining (~${estMinutes} min of questions left)
|
|
895
|
+
${previousBatchSummary ? `
|
|
896
|
+
PREVIOUS ANSWERS RECEIVED:
|
|
897
|
+
${previousBatchSummary}
|
|
898
|
+
` : ""}
|
|
899
|
+
FIELDS TO ASK ABOUT:
|
|
900
|
+
${fieldList}${conditionalNote}
|
|
901
|
+
|
|
902
|
+
Rules:
|
|
903
|
+
- ${previousBatchSummary ? 'Start by acknowledging previous answers or auto-filled data. If fields were auto-filled, list each field with its value AND cite the specific source (e.g. "from your GL Policy #ABC123", "from vercel.com", "from your business context"). If a web lookup was done, name the URL that was checked. Ask them to reply with corrections if anything is wrong.' : "Start with a one-line intro."}
|
|
904
|
+
- Mention progress once using estimated time remaining. Don't mention section/batch numbers or field counts.
|
|
905
|
+
- Use "${company}" by name when referring to the company. Also fine: "we" or "our". Never "our company" or "the company".
|
|
906
|
+
- Ask questions plainly. No em-dashes for dramatic effect, no filler phrases like "need to nail down" or "let's dive into". Just ask.
|
|
907
|
+
- For yes/no questions, ask naturally in one sentence. Don't list "Yes / No" as options. Mention what you'll need if the answer triggers a follow-up (e.g. "If not, I'll need a brief explanation.").
|
|
908
|
+
- For fields with 2-3 options, mention them inline. 4+ options can be a short list.
|
|
909
|
+
- Group related fields (address, coverage limits) into single compound questions.
|
|
910
|
+
- Do NOT include conditional/follow-up fields. They will be sent separately.
|
|
911
|
+
- Number each question.
|
|
912
|
+
- Note expected format where relevant: dollar amounts for currency, MM/DD/YYYY for dates, column descriptions for tables.
|
|
913
|
+
- End with a short closing.
|
|
914
|
+
- Tone: professional, brief, matter-of-fact. Write like a busy coworker, not a chatbot. No flourishes, no em-dashes between clauses, no editorializing about the questions.
|
|
915
|
+
|
|
916
|
+
NEVER:
|
|
917
|
+
- Sound like a salesperson or customer service agent
|
|
918
|
+
- Use em-dashes for emphasis or dramatic pacing
|
|
919
|
+
- Editorialize ("these two should wrap up this section", "just a couple more")
|
|
920
|
+
- List "Yes / No / N/A" as bullet options
|
|
921
|
+
- Include conditional follow-up questions
|
|
922
|
+
- Mention section numbers, batch numbers, or field counts
|
|
923
|
+
|
|
924
|
+
Output the email body text ONLY. No subject line, no JSON. Use markdown for numbered lists.`;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// src/application/agents/email-generator.ts
|
|
928
|
+
async function generateBatchEmail(batchFields, batchIndex, totalBatches, opts, generateText, providerOptions, maxTokens = 2048) {
|
|
929
|
+
const fieldSummaries = batchFields.map((f) => ({
|
|
930
|
+
id: f.id,
|
|
931
|
+
label: f.label,
|
|
932
|
+
fieldType: f.fieldType,
|
|
933
|
+
options: f.options,
|
|
934
|
+
condition: f.condition
|
|
935
|
+
}));
|
|
936
|
+
const prompt = buildBatchEmailGenerationPrompt(
|
|
937
|
+
fieldSummaries,
|
|
938
|
+
batchIndex,
|
|
939
|
+
totalBatches,
|
|
940
|
+
opts.appTitle,
|
|
941
|
+
opts.totalFieldCount,
|
|
942
|
+
opts.filledFieldCount,
|
|
943
|
+
opts.previousBatchSummary,
|
|
944
|
+
opts.companyName
|
|
945
|
+
);
|
|
946
|
+
const { text, usage } = await withRetry(
|
|
947
|
+
() => generateText({
|
|
948
|
+
prompt,
|
|
949
|
+
maxTokens,
|
|
950
|
+
taskKind: "application_email",
|
|
951
|
+
providerOptions
|
|
952
|
+
})
|
|
953
|
+
);
|
|
954
|
+
return { text, usage };
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// src/core/quality.ts
|
|
958
|
+
function evaluateQualityGate(params) {
|
|
959
|
+
const { issues, hasRoundWarnings = false } = params;
|
|
960
|
+
const hasBlocking = issues.some((issue) => issue.severity === "blocking");
|
|
961
|
+
const hasWarnings = issues.some((issue) => issue.severity === "warning") || hasRoundWarnings;
|
|
962
|
+
return hasBlocking ? "failed" : hasWarnings ? "warning" : "passed";
|
|
963
|
+
}
|
|
964
|
+
function shouldFailQualityGate(mode, status) {
|
|
965
|
+
return mode === "strict" && status === "failed";
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/application/quality.ts
|
|
969
|
+
function isVagueSource(source) {
|
|
970
|
+
if (!source) return true;
|
|
971
|
+
const normalized = source.trim().toLowerCase();
|
|
972
|
+
return normalized === "unknown" || normalized.includes("existing records") || normalized.includes("available data") || normalized === "context" || normalized === "user provided";
|
|
973
|
+
}
|
|
974
|
+
function isSourceGroundedPolicyValue(field) {
|
|
975
|
+
if (!field.value) return false;
|
|
976
|
+
const source = field.source?.toLowerCase() ?? "";
|
|
977
|
+
if (field.sourceSpanIds?.length) return false;
|
|
978
|
+
if (field.userSourceSpanIds?.length) return false;
|
|
979
|
+
const label = `${field.section} ${field.label}`.toLowerCase();
|
|
980
|
+
const highValueLabel = /\b(policy|effective|expiration|date|limit|deductible|premium|coverage|exclusion|condition|endorsement|location|vehicle|named insured|revenue|payroll|loss|claim|prior)\b/.test(label);
|
|
981
|
+
const highValueType = field.fieldType === "currency" || field.fieldType === "date" || field.fieldType === "numeric" || field.fieldType === "declaration";
|
|
982
|
+
const fromPolicyLikeSource = /\b(policy|quote|document|lookup|carrier|endorsement)\b/.test(source);
|
|
983
|
+
return fromPolicyLikeSource && (highValueLabel || highValueType);
|
|
984
|
+
}
|
|
985
|
+
function buildApplicationQualityReport(state) {
|
|
986
|
+
const issues = [];
|
|
987
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
988
|
+
for (const field of state.fields) {
|
|
989
|
+
if (seenIds.has(field.id)) {
|
|
990
|
+
issues.push({
|
|
991
|
+
code: "duplicate_field_id",
|
|
992
|
+
severity: "blocking",
|
|
993
|
+
message: `Field "${field.label}" has a duplicate id "${field.id}".`,
|
|
994
|
+
fieldId: field.id
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
seenIds.add(field.id);
|
|
998
|
+
if (field.required && !field.value) {
|
|
999
|
+
issues.push({
|
|
1000
|
+
code: "required_field_unfilled",
|
|
1001
|
+
severity: "warning",
|
|
1002
|
+
message: `Required field "${field.label}" is still unfilled.`,
|
|
1003
|
+
fieldId: field.id
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
if (field.value && !field.source) {
|
|
1007
|
+
issues.push({
|
|
1008
|
+
code: "filled_field_missing_source",
|
|
1009
|
+
severity: "blocking",
|
|
1010
|
+
message: `Filled field "${field.label}" is missing source provenance.`,
|
|
1011
|
+
fieldId: field.id
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
if (field.value && isVagueSource(field.source)) {
|
|
1015
|
+
issues.push({
|
|
1016
|
+
code: "filled_field_vague_source",
|
|
1017
|
+
severity: "warning",
|
|
1018
|
+
message: `Filled field "${field.label}" has a vague or non-citable source.`,
|
|
1019
|
+
fieldId: field.id
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
if (field.value && (!field.confidence || field.confidence === "low")) {
|
|
1023
|
+
issues.push({
|
|
1024
|
+
code: "filled_field_low_confidence",
|
|
1025
|
+
severity: "warning",
|
|
1026
|
+
message: `Filled field "${field.label}" has low or missing confidence.`,
|
|
1027
|
+
fieldId: field.id
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
if (isSourceGroundedPolicyValue(field)) {
|
|
1031
|
+
issues.push({
|
|
1032
|
+
code: "policy_value_missing_source_span",
|
|
1033
|
+
severity: "blocking",
|
|
1034
|
+
message: `Filled policy-derived field "${field.label}" is missing source span evidence.`,
|
|
1035
|
+
fieldId: field.id
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
issues,
|
|
1041
|
+
rounds: [],
|
|
1042
|
+
artifacts: [
|
|
1043
|
+
{ kind: "application_fields", label: "Application Fields", itemCount: state.fields.length }
|
|
1044
|
+
],
|
|
1045
|
+
qualityGateStatus: evaluateQualityGate({ issues })
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function reviewBatchEmail(text, batchFields) {
|
|
1049
|
+
const issues = [];
|
|
1050
|
+
const normalized = text.toLowerCase();
|
|
1051
|
+
for (const field of batchFields) {
|
|
1052
|
+
const label = field.label.trim().toLowerCase();
|
|
1053
|
+
if (label.length >= 6 && !normalized.includes(label)) {
|
|
1054
|
+
issues.push({
|
|
1055
|
+
code: "email_missing_field_prompt",
|
|
1056
|
+
severity: "warning",
|
|
1057
|
+
message: `Generated email does not clearly mention field "${field.label}".`,
|
|
1058
|
+
fieldId: field.id
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
issues,
|
|
1064
|
+
qualityGateStatus: evaluateQualityGate({ issues })
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// src/application/workflow.ts
|
|
1069
|
+
var MAX_DOCUMENT_SEARCH_FIELDS = 5;
|
|
1070
|
+
var LOW_VALUE_FIELD_RATIO_LIMIT = 0.6;
|
|
1071
|
+
function planApplicationWorkflow(input) {
|
|
1072
|
+
const unfilledFields = input.fields.filter(isUnfilled);
|
|
1073
|
+
const documentSearchFields = planDocumentSearchFields(
|
|
1074
|
+
unfilledFields,
|
|
1075
|
+
input.hasDocumentStore && input.hasMemoryStore
|
|
1076
|
+
);
|
|
1077
|
+
return {
|
|
1078
|
+
runBackfill: input.hasBackfillProvider && unfilledFields.length > 0,
|
|
1079
|
+
runContextAutoFill: input.orgContextCount > 0 && unfilledFields.length > 0,
|
|
1080
|
+
documentSearchFields,
|
|
1081
|
+
runBatching: unfilledFields.length > 0,
|
|
1082
|
+
unfilledFields
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
function planReplyActions(input) {
|
|
1086
|
+
const hasCurrentFields = input.currentBatchFields.length > 0;
|
|
1087
|
+
const nextBatchNeedsAnswers = (input.nextBatchFields ?? []).some(isUnfilled);
|
|
1088
|
+
const hasLookupRequests = (input.intent.lookupRequests?.length ?? 0) > 0;
|
|
1089
|
+
return {
|
|
1090
|
+
parseAnswers: input.intent.hasAnswers && hasCurrentFields,
|
|
1091
|
+
runLookup: hasLookupRequests && input.hasDocumentStore,
|
|
1092
|
+
answerQuestion: Boolean(input.intent.questionText) && (input.intent.primaryIntent === "question" || input.intent.primaryIntent === "mixed"),
|
|
1093
|
+
advanceBatch: hasCurrentFields && input.currentBatchFields.every((field) => !isUnfilled(field)) || !hasCurrentFields && nextBatchNeedsAnswers,
|
|
1094
|
+
generateNextEmail: nextBatchNeedsAnswers
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
function planDocumentSearchFields(unfilledFields, hasStores) {
|
|
1098
|
+
if (!hasStores || unfilledFields.length === 0) return [];
|
|
1099
|
+
const searchableFields = unfilledFields.filter(isHighValueLookupField);
|
|
1100
|
+
if (searchableFields.length === 0) return [];
|
|
1101
|
+
const lowValueRatio = 1 - searchableFields.length / unfilledFields.length;
|
|
1102
|
+
if (unfilledFields.length > MAX_DOCUMENT_SEARCH_FIELDS && lowValueRatio > LOW_VALUE_FIELD_RATIO_LIMIT) {
|
|
1103
|
+
return [];
|
|
1104
|
+
}
|
|
1105
|
+
return searchableFields.slice(0, MAX_DOCUMENT_SEARCH_FIELDS);
|
|
1106
|
+
}
|
|
1107
|
+
function isUnfilled(field) {
|
|
1108
|
+
return field.value === void 0 || field.value.trim() === "";
|
|
1109
|
+
}
|
|
1110
|
+
function isHighValueLookupField(field) {
|
|
1111
|
+
const text = `${field.section} ${field.label}`.toLowerCase();
|
|
1112
|
+
if (field.required) return true;
|
|
1113
|
+
return [
|
|
1114
|
+
"carrier",
|
|
1115
|
+
"policy",
|
|
1116
|
+
"premium",
|
|
1117
|
+
"limit",
|
|
1118
|
+
"deductible",
|
|
1119
|
+
"insured",
|
|
1120
|
+
"address",
|
|
1121
|
+
"revenue",
|
|
1122
|
+
"payroll",
|
|
1123
|
+
"effective",
|
|
1124
|
+
"expiration",
|
|
1125
|
+
"coverage",
|
|
1126
|
+
"class code",
|
|
1127
|
+
"fein",
|
|
1128
|
+
"entity"
|
|
1129
|
+
].some((term) => text.includes(term));
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/source/schemas.ts
|
|
1133
|
+
var import_zod2 = require("zod");
|
|
1134
|
+
var SourceSpanKindSchema = import_zod2.z.enum([
|
|
1135
|
+
"pdf_text",
|
|
1136
|
+
"pdf_image",
|
|
1137
|
+
"html",
|
|
1138
|
+
"markdown",
|
|
1139
|
+
"plain_text",
|
|
1140
|
+
"structured_field"
|
|
1141
|
+
]);
|
|
1142
|
+
var SourceSpanUnitSchema = import_zod2.z.enum([
|
|
1143
|
+
"page",
|
|
1144
|
+
"section",
|
|
1145
|
+
"table",
|
|
1146
|
+
"table_row",
|
|
1147
|
+
"table_cell",
|
|
1148
|
+
"key_value",
|
|
1149
|
+
"text"
|
|
1150
|
+
]);
|
|
1151
|
+
var SourceKindSchema = import_zod2.z.enum([
|
|
1152
|
+
"policy_pdf",
|
|
1153
|
+
"application_pdf",
|
|
1154
|
+
"email",
|
|
1155
|
+
"attachment",
|
|
1156
|
+
"manual_note"
|
|
1157
|
+
]);
|
|
1158
|
+
var SourceSpanBBoxSchema = import_zod2.z.object({
|
|
1159
|
+
page: import_zod2.z.number().int().positive(),
|
|
1160
|
+
x: import_zod2.z.number(),
|
|
1161
|
+
y: import_zod2.z.number(),
|
|
1162
|
+
width: import_zod2.z.number(),
|
|
1163
|
+
height: import_zod2.z.number()
|
|
1164
|
+
});
|
|
1165
|
+
var SourceSpanLocationSchema = import_zod2.z.object({
|
|
1166
|
+
page: import_zod2.z.number().int().positive().optional(),
|
|
1167
|
+
startPage: import_zod2.z.number().int().positive().optional(),
|
|
1168
|
+
endPage: import_zod2.z.number().int().positive().optional(),
|
|
1169
|
+
charStart: import_zod2.z.number().int().nonnegative().optional(),
|
|
1170
|
+
charEnd: import_zod2.z.number().int().nonnegative().optional(),
|
|
1171
|
+
lineStart: import_zod2.z.number().int().positive().optional(),
|
|
1172
|
+
lineEnd: import_zod2.z.number().int().positive().optional(),
|
|
1173
|
+
fieldPath: import_zod2.z.string().optional()
|
|
1174
|
+
});
|
|
1175
|
+
var SourceSpanTableLocationSchema = import_zod2.z.object({
|
|
1176
|
+
tableId: import_zod2.z.string().optional(),
|
|
1177
|
+
rowIndex: import_zod2.z.number().int().nonnegative().optional(),
|
|
1178
|
+
columnIndex: import_zod2.z.number().int().nonnegative().optional(),
|
|
1179
|
+
columnName: import_zod2.z.string().optional(),
|
|
1180
|
+
rowSpanId: import_zod2.z.string().optional(),
|
|
1181
|
+
tableSpanId: import_zod2.z.string().optional(),
|
|
1182
|
+
isHeader: import_zod2.z.boolean().optional()
|
|
1183
|
+
});
|
|
1184
|
+
var SourceSpanSchema = import_zod2.z.object({
|
|
1185
|
+
id: import_zod2.z.string().min(1),
|
|
1186
|
+
documentId: import_zod2.z.string().min(1),
|
|
1187
|
+
sourceKind: SourceKindSchema.optional(),
|
|
1188
|
+
chunkId: import_zod2.z.string().optional(),
|
|
1189
|
+
kind: SourceSpanKindSchema,
|
|
1190
|
+
text: import_zod2.z.string(),
|
|
1191
|
+
hash: import_zod2.z.string().min(1),
|
|
1192
|
+
textHash: import_zod2.z.string().optional(),
|
|
1193
|
+
pageStart: import_zod2.z.number().int().positive().optional(),
|
|
1194
|
+
pageEnd: import_zod2.z.number().int().positive().optional(),
|
|
1195
|
+
sectionId: import_zod2.z.string().optional(),
|
|
1196
|
+
formNumber: import_zod2.z.string().optional(),
|
|
1197
|
+
sourceUnit: SourceSpanUnitSchema.optional(),
|
|
1198
|
+
parentSpanId: import_zod2.z.string().optional(),
|
|
1199
|
+
table: SourceSpanTableLocationSchema.optional(),
|
|
1200
|
+
bbox: import_zod2.z.array(SourceSpanBBoxSchema).optional(),
|
|
1201
|
+
location: SourceSpanLocationSchema.optional(),
|
|
1202
|
+
metadata: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional()
|
|
1203
|
+
});
|
|
1204
|
+
var SourceSpanRefSchema = import_zod2.z.object({
|
|
1205
|
+
sourceSpanId: import_zod2.z.string().min(1),
|
|
1206
|
+
documentId: import_zod2.z.string().min(1).optional(),
|
|
1207
|
+
chunkId: import_zod2.z.string().optional(),
|
|
1208
|
+
quote: import_zod2.z.string().optional(),
|
|
1209
|
+
hash: import_zod2.z.string().optional(),
|
|
1210
|
+
location: SourceSpanLocationSchema.optional()
|
|
1211
|
+
});
|
|
1212
|
+
var SourceChunkSchema = import_zod2.z.object({
|
|
1213
|
+
id: import_zod2.z.string().min(1),
|
|
1214
|
+
documentId: import_zod2.z.string().min(1),
|
|
1215
|
+
sourceSpanIds: import_zod2.z.array(import_zod2.z.string().min(1)),
|
|
1216
|
+
text: import_zod2.z.string(),
|
|
1217
|
+
textHash: import_zod2.z.string().min(1),
|
|
1218
|
+
pageStart: import_zod2.z.number().int().positive().optional(),
|
|
1219
|
+
pageEnd: import_zod2.z.number().int().positive().optional(),
|
|
1220
|
+
metadata: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).default({})
|
|
1221
|
+
});
|
|
1222
|
+
var DocumentSourceNodeKindSchema = import_zod2.z.enum([
|
|
1223
|
+
"document",
|
|
1224
|
+
"page_group",
|
|
1225
|
+
"page",
|
|
1226
|
+
"form",
|
|
1227
|
+
"endorsement",
|
|
1228
|
+
"section",
|
|
1229
|
+
"schedule",
|
|
1230
|
+
"clause",
|
|
1231
|
+
"table",
|
|
1232
|
+
"table_row",
|
|
1233
|
+
"table_cell",
|
|
1234
|
+
"text"
|
|
1235
|
+
]);
|
|
1236
|
+
var DocumentSourceNodeSchema = import_zod2.z.object({
|
|
1237
|
+
id: import_zod2.z.string().min(1),
|
|
1238
|
+
documentId: import_zod2.z.string().min(1),
|
|
1239
|
+
parentId: import_zod2.z.string().optional(),
|
|
1240
|
+
kind: DocumentSourceNodeKindSchema,
|
|
1241
|
+
title: import_zod2.z.string(),
|
|
1242
|
+
description: import_zod2.z.string(),
|
|
1243
|
+
textExcerpt: import_zod2.z.string().optional(),
|
|
1244
|
+
sourceSpanIds: import_zod2.z.array(import_zod2.z.string().min(1)),
|
|
1245
|
+
pageStart: import_zod2.z.number().int().positive().optional(),
|
|
1246
|
+
pageEnd: import_zod2.z.number().int().positive().optional(),
|
|
1247
|
+
bbox: import_zod2.z.array(SourceSpanBBoxSchema).optional(),
|
|
1248
|
+
order: import_zod2.z.number().int().nonnegative(),
|
|
1249
|
+
path: import_zod2.z.string(),
|
|
1250
|
+
metadata: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional()
|
|
1251
|
+
});
|
|
1252
|
+
var SourceBackedValueSchema = import_zod2.z.object({
|
|
1253
|
+
value: import_zod2.z.string(),
|
|
1254
|
+
normalizedValue: import_zod2.z.string().optional(),
|
|
1255
|
+
confidence: import_zod2.z.enum(["low", "medium", "high"]).default("medium"),
|
|
1256
|
+
sourceNodeIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([]),
|
|
1257
|
+
sourceSpanIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([])
|
|
1258
|
+
});
|
|
1259
|
+
var OperationalCoverageTermSchema = import_zod2.z.object({
|
|
1260
|
+
kind: import_zod2.z.enum([
|
|
1261
|
+
"each_claim_limit",
|
|
1262
|
+
"each_occurrence_limit",
|
|
1263
|
+
"each_loss_limit",
|
|
1264
|
+
"aggregate_limit",
|
|
1265
|
+
"sublimit",
|
|
1266
|
+
"retention",
|
|
1267
|
+
"deductible",
|
|
1268
|
+
"retroactive_date",
|
|
1269
|
+
"premium",
|
|
1270
|
+
"other"
|
|
1271
|
+
]).default("other"),
|
|
1272
|
+
label: import_zod2.z.string(),
|
|
1273
|
+
value: import_zod2.z.string(),
|
|
1274
|
+
amount: import_zod2.z.number().optional(),
|
|
1275
|
+
appliesTo: import_zod2.z.string().optional(),
|
|
1276
|
+
sourceNodeIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([]),
|
|
1277
|
+
sourceSpanIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([])
|
|
1278
|
+
});
|
|
1279
|
+
var OperationalCoverageLineSchema = import_zod2.z.object({
|
|
1280
|
+
name: import_zod2.z.string(),
|
|
1281
|
+
coverageCode: import_zod2.z.string().optional(),
|
|
1282
|
+
limit: import_zod2.z.string().optional(),
|
|
1283
|
+
deductible: import_zod2.z.string().optional(),
|
|
1284
|
+
premium: import_zod2.z.string().optional(),
|
|
1285
|
+
retroactiveDate: import_zod2.z.string().optional(),
|
|
1286
|
+
formNumber: import_zod2.z.string().optional(),
|
|
1287
|
+
sectionRef: import_zod2.z.string().optional(),
|
|
1288
|
+
coverageOrigin: import_zod2.z.enum(["core", "endorsement"]).optional(),
|
|
1289
|
+
endorsementNumber: import_zod2.z.string().optional(),
|
|
1290
|
+
limits: import_zod2.z.array(OperationalCoverageTermSchema).default([]),
|
|
1291
|
+
sourceNodeIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([]),
|
|
1292
|
+
sourceSpanIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([])
|
|
1293
|
+
});
|
|
1294
|
+
var OperationalPartySchema = import_zod2.z.object({
|
|
1295
|
+
role: import_zod2.z.string(),
|
|
1296
|
+
name: import_zod2.z.string(),
|
|
1297
|
+
sourceNodeIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([]),
|
|
1298
|
+
sourceSpanIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([])
|
|
1299
|
+
});
|
|
1300
|
+
var OperationalEndorsementSupportSchema = import_zod2.z.object({
|
|
1301
|
+
kind: import_zod2.z.string(),
|
|
1302
|
+
status: import_zod2.z.enum(["supported", "excluded", "requires_review"]),
|
|
1303
|
+
summary: import_zod2.z.string(),
|
|
1304
|
+
sourceNodeIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([]),
|
|
1305
|
+
sourceSpanIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([])
|
|
1306
|
+
});
|
|
1307
|
+
var PolicyOperationalProfileSchema = import_zod2.z.object({
|
|
1308
|
+
documentType: import_zod2.z.enum(["policy", "quote"]).default("policy"),
|
|
1309
|
+
policyTypes: import_zod2.z.array(import_zod2.z.string()).default(["other"]),
|
|
1310
|
+
policyNumber: SourceBackedValueSchema.optional(),
|
|
1311
|
+
namedInsured: SourceBackedValueSchema.optional(),
|
|
1312
|
+
insurer: SourceBackedValueSchema.optional(),
|
|
1313
|
+
broker: SourceBackedValueSchema.optional(),
|
|
1314
|
+
effectiveDate: SourceBackedValueSchema.optional(),
|
|
1315
|
+
expirationDate: SourceBackedValueSchema.optional(),
|
|
1316
|
+
retroactiveDate: SourceBackedValueSchema.optional(),
|
|
1317
|
+
premium: SourceBackedValueSchema.optional(),
|
|
1318
|
+
coverageTypes: import_zod2.z.array(import_zod2.z.string()).default([]),
|
|
1319
|
+
coverages: import_zod2.z.array(OperationalCoverageLineSchema).default([]),
|
|
1320
|
+
parties: import_zod2.z.array(OperationalPartySchema).default([]),
|
|
1321
|
+
endorsementSupport: import_zod2.z.array(OperationalEndorsementSupportSchema).default([]),
|
|
1322
|
+
sourceNodeIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([]),
|
|
1323
|
+
sourceSpanIds: import_zod2.z.array(import_zod2.z.string().min(1)).default([]),
|
|
1324
|
+
warnings: import_zod2.z.array(import_zod2.z.string()).default([])
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
// src/source/ids.ts
|
|
1328
|
+
function normalizeText(text) {
|
|
1329
|
+
return text.replace(/\s+/g, " ").trim();
|
|
1330
|
+
}
|
|
1331
|
+
function stableStringify(value) {
|
|
1332
|
+
if (value === void 0) {
|
|
1333
|
+
return "undefined";
|
|
1334
|
+
}
|
|
1335
|
+
if (value === null || typeof value !== "object") {
|
|
1336
|
+
return JSON.stringify(value) ?? "undefined";
|
|
1337
|
+
}
|
|
1338
|
+
if (Array.isArray(value)) {
|
|
1339
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
1340
|
+
}
|
|
1341
|
+
const record = value;
|
|
1342
|
+
return `{${Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
|
|
1343
|
+
}
|
|
1344
|
+
function stableHash(value) {
|
|
1345
|
+
const input = stableStringify(value);
|
|
1346
|
+
let hashA = 2166136261;
|
|
1347
|
+
let hashB = 73244475;
|
|
1348
|
+
for (let index = 0; index < input.length; index++) {
|
|
1349
|
+
const char = input.charCodeAt(index);
|
|
1350
|
+
hashA ^= char;
|
|
1351
|
+
hashA = Math.imul(hashA, 16777619);
|
|
1352
|
+
hashB ^= char + index;
|
|
1353
|
+
hashB = Math.imul(hashB, 668265261);
|
|
1354
|
+
}
|
|
1355
|
+
return `${(hashA >>> 0).toString(16).padStart(8, "0")}${(hashB >>> 0).toString(16).padStart(8, "0")}`;
|
|
1356
|
+
}
|
|
1357
|
+
function sourceSpanTextHash(text) {
|
|
1358
|
+
return stableHash(normalizeText(text));
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// src/source/extraction.ts
|
|
1362
|
+
function normalizeWhitespace(value) {
|
|
1363
|
+
return value.replace(/\s+/g, " ").trim();
|
|
1364
|
+
}
|
|
1365
|
+
function sanitizeIdPart(value) {
|
|
1366
|
+
return value.replace(/[^a-zA-Z0-9_.:-]/g, "_");
|
|
1367
|
+
}
|
|
1368
|
+
function buildSourceSpan(input, localIndex = 0) {
|
|
1369
|
+
const text = normalizeWhitespace(input.text);
|
|
1370
|
+
const textHash = sourceSpanTextHash(text);
|
|
1371
|
+
const pagePart = input.pageStart ?? "na";
|
|
1372
|
+
const id = [
|
|
1373
|
+
sanitizeIdPart(input.documentId),
|
|
1374
|
+
"span",
|
|
1375
|
+
pagePart,
|
|
1376
|
+
localIndex,
|
|
1377
|
+
textHash.slice(0, 12)
|
|
1378
|
+
].join(":");
|
|
1379
|
+
return SourceSpanSchema.parse({
|
|
1380
|
+
id,
|
|
1381
|
+
documentId: input.documentId,
|
|
1382
|
+
sourceKind: input.sourceKind,
|
|
1383
|
+
kind: input.sourceKind.endsWith("_pdf") ? "pdf_text" : "plain_text",
|
|
1384
|
+
text,
|
|
1385
|
+
hash: textHash,
|
|
1386
|
+
textHash,
|
|
1387
|
+
pageStart: input.pageStart,
|
|
1388
|
+
pageEnd: input.pageEnd,
|
|
1389
|
+
sectionId: input.sectionId,
|
|
1390
|
+
formNumber: input.formNumber,
|
|
1391
|
+
sourceUnit: input.sourceUnit,
|
|
1392
|
+
parentSpanId: input.parentSpanId,
|
|
1393
|
+
table: input.table,
|
|
1394
|
+
location: {
|
|
1395
|
+
page: input.pageStart === input.pageEnd ? input.pageStart : void 0,
|
|
1396
|
+
startPage: input.pageStart,
|
|
1397
|
+
endPage: input.pageEnd,
|
|
1398
|
+
fieldPath: input.sectionId
|
|
1399
|
+
},
|
|
1400
|
+
metadata: input.metadata
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
function buildTextSourceSpans(input, options = {}) {
|
|
1404
|
+
const maxChars = options.maxChars ?? 4e3;
|
|
1405
|
+
const overlapChars = Math.min(options.overlapChars ?? 0, Math.max(0, maxChars - 1));
|
|
1406
|
+
const text = normalizeWhitespace(input.text);
|
|
1407
|
+
if (!text) return [];
|
|
1408
|
+
const spans = [];
|
|
1409
|
+
let cursor = 0;
|
|
1410
|
+
while (cursor < text.length) {
|
|
1411
|
+
const end = Math.min(text.length, cursor + maxChars);
|
|
1412
|
+
const unitText = text.slice(cursor, end);
|
|
1413
|
+
spans.push(buildSourceSpan({ ...input, text: unitText }, spans.length));
|
|
1414
|
+
if (end === text.length) break;
|
|
1415
|
+
cursor = end - overlapChars;
|
|
1416
|
+
}
|
|
1417
|
+
return spans;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// src/application/question-graph.ts
|
|
1421
|
+
function buildQuestionGraphFromFields(fields, options) {
|
|
1422
|
+
const sectionNodes = /* @__PURE__ */ new Map();
|
|
1423
|
+
const nodes = [];
|
|
1424
|
+
for (const [index, field] of fields.entries()) {
|
|
1425
|
+
const sectionId = stableNodeId(["section", field.section]);
|
|
1426
|
+
let sectionNode = sectionNodes.get(sectionId);
|
|
1427
|
+
if (!sectionNode) {
|
|
1428
|
+
sectionNode = {
|
|
1429
|
+
id: sectionId,
|
|
1430
|
+
nodeType: "group",
|
|
1431
|
+
label: field.section,
|
|
1432
|
+
order: sectionNodes.size,
|
|
1433
|
+
children: []
|
|
1434
|
+
};
|
|
1435
|
+
sectionNodes.set(sectionId, sectionNode);
|
|
1436
|
+
nodes.push(sectionNode);
|
|
1437
|
+
}
|
|
1438
|
+
const child = {
|
|
1439
|
+
id: field.fieldAnchorId ?? stableNodeId(["field", field.section, field.id || field.label]),
|
|
1440
|
+
nodeType: field.fieldType === "table" ? "table" : "question",
|
|
1441
|
+
fieldId: field.id,
|
|
1442
|
+
fieldPath: `${field.section}.${field.id}`,
|
|
1443
|
+
parentId: sectionId,
|
|
1444
|
+
order: index,
|
|
1445
|
+
label: field.label,
|
|
1446
|
+
section: field.section,
|
|
1447
|
+
fieldType: field.fieldType,
|
|
1448
|
+
required: field.required,
|
|
1449
|
+
options: field.options,
|
|
1450
|
+
columns: field.columns,
|
|
1451
|
+
condition: field.condition ? {
|
|
1452
|
+
dependsOn: field.condition.dependsOn,
|
|
1453
|
+
operator: "equals",
|
|
1454
|
+
value: field.condition.whenValue,
|
|
1455
|
+
whenValue: field.condition.whenValue
|
|
1456
|
+
} : void 0
|
|
1457
|
+
};
|
|
1458
|
+
sectionNode.children = [...sectionNode.children ?? [], child];
|
|
1459
|
+
}
|
|
1460
|
+
return normalizeApplicationQuestionGraph({
|
|
1461
|
+
id: options.id,
|
|
1462
|
+
version: options.version ?? "v1",
|
|
1463
|
+
title: options.title,
|
|
1464
|
+
applicationType: options.applicationType,
|
|
1465
|
+
source: options.source ?? "generated",
|
|
1466
|
+
rootNodeIds: nodes.map((node) => node.id),
|
|
1467
|
+
nodes
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
function normalizeApplicationQuestionGraph(graph) {
|
|
1471
|
+
const normalizedNodes = graph.nodes.map((node, index) => normalizeNode(node, void 0, index)).sort(compareNodes);
|
|
1472
|
+
return {
|
|
1473
|
+
...graph,
|
|
1474
|
+
rootNodeIds: graph.rootNodeIds?.length ? graph.rootNodeIds : normalizedNodes.map((node) => node.id),
|
|
1475
|
+
nodes: normalizedNodes
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
function flattenQuestionGraph(graph) {
|
|
1479
|
+
const fields = [];
|
|
1480
|
+
for (const node of graph.nodes.sort(compareNodes)) {
|
|
1481
|
+
collectFields(node, fields);
|
|
1482
|
+
}
|
|
1483
|
+
return fields;
|
|
1484
|
+
}
|
|
1485
|
+
function getActiveApplicationFields(state) {
|
|
1486
|
+
const valueByFieldId = new Map(
|
|
1487
|
+
state.fields.filter((field) => field.value !== void 0 && field.value.trim() !== "").map((field) => [field.id, field.value ?? ""])
|
|
1488
|
+
);
|
|
1489
|
+
const graphConditions = /* @__PURE__ */ new Map();
|
|
1490
|
+
for (const node of state.questionGraph?.nodes ?? []) {
|
|
1491
|
+
collectNodeConditions(node, graphConditions);
|
|
1492
|
+
}
|
|
1493
|
+
return state.fields.filter((field) => {
|
|
1494
|
+
const graphCondition = graphConditions.get(field.id);
|
|
1495
|
+
return isConditionSatisfied(field.condition, valueByFieldId) && isQuestionConditionSatisfied(graphCondition, valueByFieldId);
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
function getNextApplicationQuestions(state, limit = 8) {
|
|
1499
|
+
const activeUnfilled = getActiveApplicationFields(state).filter((field) => !field.value);
|
|
1500
|
+
if (activeUnfilled.length === 0) return [];
|
|
1501
|
+
const currentBatchIds = state.batches?.[state.currentBatchIndex] ?? [];
|
|
1502
|
+
const currentBatchFields = activeUnfilled.filter((field) => currentBatchIds.includes(field.id));
|
|
1503
|
+
return (currentBatchFields.length > 0 ? currentBatchFields : activeUnfilled).slice(0, limit);
|
|
1504
|
+
}
|
|
1505
|
+
function normalizeNode(node, parentId, index) {
|
|
1506
|
+
const children = node.children?.map((child, childIndex) => normalizeNode(child, node.id, childIndex));
|
|
1507
|
+
return {
|
|
1508
|
+
...node,
|
|
1509
|
+
parentId: node.parentId ?? parentId,
|
|
1510
|
+
order: node.order ?? index,
|
|
1511
|
+
fieldPath: node.fieldPath ?? (node.fieldId ? [node.section, node.fieldId].filter(Boolean).join(".") : void 0),
|
|
1512
|
+
children
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
function collectFields(node, fields) {
|
|
1516
|
+
if ((node.nodeType === "question" || node.nodeType === "table") && node.fieldId) {
|
|
1517
|
+
fields.push({
|
|
1518
|
+
id: node.fieldId,
|
|
1519
|
+
label: node.label,
|
|
1520
|
+
section: node.section ?? "General",
|
|
1521
|
+
fieldType: node.fieldType ?? (node.nodeType === "table" ? "table" : "text"),
|
|
1522
|
+
required: node.required ?? false,
|
|
1523
|
+
options: node.options,
|
|
1524
|
+
columns: node.columns,
|
|
1525
|
+
condition: node.condition ? {
|
|
1526
|
+
dependsOn: node.condition.dependsOn,
|
|
1527
|
+
whenValue: node.condition.value ?? node.condition.whenValue ?? ""
|
|
1528
|
+
} : void 0,
|
|
1529
|
+
fieldAnchorId: node.id
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
for (const child of node.children ?? []) {
|
|
1533
|
+
collectFields(child, fields);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
function collectNodeConditions(node, conditions) {
|
|
1537
|
+
if (node.fieldId && node.condition) {
|
|
1538
|
+
conditions.set(node.fieldId, node.condition);
|
|
1539
|
+
}
|
|
1540
|
+
for (const child of node.children ?? []) {
|
|
1541
|
+
collectNodeConditions(child, conditions);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
function isConditionSatisfied(condition, valueByFieldId) {
|
|
1545
|
+
if (!condition) return true;
|
|
1546
|
+
const current = valueByFieldId.get(condition.dependsOn);
|
|
1547
|
+
return normalizeValue(current) === normalizeValue(condition.whenValue);
|
|
1548
|
+
}
|
|
1549
|
+
function isQuestionConditionSatisfied(condition, valueByFieldId) {
|
|
1550
|
+
if (!condition) return true;
|
|
1551
|
+
const current = valueByFieldId.get(condition.dependsOn);
|
|
1552
|
+
const expected = condition.value ?? condition.whenValue;
|
|
1553
|
+
const values = condition.values ?? (expected !== void 0 ? [expected] : []);
|
|
1554
|
+
switch (condition.operator) {
|
|
1555
|
+
case "exists":
|
|
1556
|
+
return current !== void 0 && current.trim() !== "";
|
|
1557
|
+
case "not_equals":
|
|
1558
|
+
return normalizeValue(current) !== normalizeValue(expected);
|
|
1559
|
+
case "in":
|
|
1560
|
+
return values.map(normalizeValue).includes(normalizeValue(current));
|
|
1561
|
+
case "not_in":
|
|
1562
|
+
return !values.map(normalizeValue).includes(normalizeValue(current));
|
|
1563
|
+
case "equals":
|
|
1564
|
+
default:
|
|
1565
|
+
return normalizeValue(current) === normalizeValue(expected);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
function compareNodes(a, b) {
|
|
1569
|
+
return (a.order ?? 0) - (b.order ?? 0) || a.id.localeCompare(b.id);
|
|
1570
|
+
}
|
|
1571
|
+
function normalizeValue(value) {
|
|
1572
|
+
return (value ?? "").trim().toLowerCase();
|
|
1573
|
+
}
|
|
1574
|
+
function stableNodeId(parts) {
|
|
1575
|
+
return parts.join(":").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node";
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// src/application/intake.ts
|
|
1579
|
+
function extractQuestionGraphFromFields(fields, options) {
|
|
1580
|
+
return buildQuestionGraphFromFields(fields, options);
|
|
1581
|
+
}
|
|
1582
|
+
function createApplicationRun(params) {
|
|
1583
|
+
const now = params.now ?? Date.now();
|
|
1584
|
+
const graph = normalizeApplicationQuestionGraph(params.template.questionGraph);
|
|
1585
|
+
const fields = params.template.fields?.length ? params.template.fields : flattenQuestionGraph(graph);
|
|
1586
|
+
return {
|
|
1587
|
+
id: params.applicationId,
|
|
1588
|
+
templateId: params.template.id,
|
|
1589
|
+
templateVersion: params.template.version,
|
|
1590
|
+
templateSnapshot: params.template,
|
|
1591
|
+
title: params.template.title,
|
|
1592
|
+
applicationType: params.template.applicationType,
|
|
1593
|
+
questionGraph: graph,
|
|
1594
|
+
fields,
|
|
1595
|
+
currentBatchIndex: 0,
|
|
1596
|
+
status: "collecting",
|
|
1597
|
+
createdAt: now,
|
|
1598
|
+
updatedAt: now
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function planNextApplicationQuestions(state, limit) {
|
|
1602
|
+
const fields = getNextApplicationQuestions(state, limit);
|
|
1603
|
+
return {
|
|
1604
|
+
status: fields.length === 0 ? "complete" : "needs_answers",
|
|
1605
|
+
fieldIds: fields.map((field) => field.id),
|
|
1606
|
+
fields
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
function applyApplicationAnswers(state, answers, now = Date.now()) {
|
|
1610
|
+
const answerByFieldId = new Map(answers.map((answer) => [answer.fieldId, answer]));
|
|
1611
|
+
const fields = state.fields.map((field) => {
|
|
1612
|
+
const answer = answerByFieldId.get(field.id);
|
|
1613
|
+
if (!answer) return field;
|
|
1614
|
+
return {
|
|
1615
|
+
...field,
|
|
1616
|
+
value: answer.value,
|
|
1617
|
+
source: answer.source ?? "user",
|
|
1618
|
+
confidence: answer.confidence ?? "confirmed",
|
|
1619
|
+
sourceSpanIds: answer.sourceSpanIds ?? field.sourceSpanIds,
|
|
1620
|
+
userSourceSpanIds: answer.userSourceSpanIds ?? field.userSourceSpanIds,
|
|
1621
|
+
validationStatus: "valid"
|
|
1622
|
+
};
|
|
1623
|
+
});
|
|
1624
|
+
const nextState = {
|
|
1625
|
+
...state,
|
|
1626
|
+
fields,
|
|
1627
|
+
updatedAt: now
|
|
1628
|
+
};
|
|
1629
|
+
const nextQuestions = planNextApplicationQuestions(nextState);
|
|
1630
|
+
return {
|
|
1631
|
+
...nextState,
|
|
1632
|
+
status: nextQuestions.status === "complete" ? "confirming" : nextState.status,
|
|
1633
|
+
qualityReport: buildApplicationQualityReport(nextState)
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function proposeContextWrites(state) {
|
|
1637
|
+
return getActiveApplicationFields(state).filter((field) => field.value && field.confidence && field.confidence !== "low").map((field) => ({
|
|
1638
|
+
id: `${state.id}:${field.id}:context`,
|
|
1639
|
+
fieldId: field.id,
|
|
1640
|
+
key: stableContextKey(field),
|
|
1641
|
+
value: field.value ?? "",
|
|
1642
|
+
category: field.section,
|
|
1643
|
+
source: field.source?.startsWith("lookup:") ? "lookup" : "application",
|
|
1644
|
+
confidence: field.confidence ?? "medium",
|
|
1645
|
+
sourceSpanIds: field.sourceSpanIds,
|
|
1646
|
+
userSourceSpanIds: field.userSourceSpanIds
|
|
1647
|
+
}));
|
|
1648
|
+
}
|
|
1649
|
+
function buildApplicationPacket(state, options = {}) {
|
|
1650
|
+
const qualityReport = buildApplicationQualityReport(state);
|
|
1651
|
+
const activeFields = getActiveApplicationFields(state);
|
|
1652
|
+
const answers = activeFields.filter((field) => field.value).map((field) => ({
|
|
1653
|
+
fieldId: field.id,
|
|
1654
|
+
label: field.label,
|
|
1655
|
+
section: field.section,
|
|
1656
|
+
value: field.value ?? "",
|
|
1657
|
+
source: field.source ?? "unknown",
|
|
1658
|
+
confidence: field.confidence,
|
|
1659
|
+
sourceSpanIds: field.sourceSpanIds,
|
|
1660
|
+
userSourceSpanIds: field.userSourceSpanIds
|
|
1661
|
+
}));
|
|
1662
|
+
const missingFieldIds = activeFields.filter((field) => field.required && !field.value).map((field) => field.id);
|
|
1663
|
+
return {
|
|
1664
|
+
id: `${state.id}:packet`,
|
|
1665
|
+
applicationId: state.id,
|
|
1666
|
+
title: state.title ?? state.applicationType ?? "Insurance Application",
|
|
1667
|
+
status: qualityReport.qualityGateStatus === "failed" || missingFieldIds.length > 0 ? "draft" : "broker_ready",
|
|
1668
|
+
answers,
|
|
1669
|
+
missingFieldIds,
|
|
1670
|
+
qualityReport,
|
|
1671
|
+
submissionNotes: options.submissionNotes,
|
|
1672
|
+
createdAt: options.now ?? Date.now()
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
function validateApplicationPacket(packet) {
|
|
1676
|
+
const issues = [...packet.qualityReport.issues];
|
|
1677
|
+
if (packet.missingFieldIds.length > 0) {
|
|
1678
|
+
issues.push({
|
|
1679
|
+
code: "packet_missing_required_answers",
|
|
1680
|
+
severity: "blocking",
|
|
1681
|
+
message: "Packet still has required unanswered application fields."
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
return {
|
|
1685
|
+
...packet.qualityReport,
|
|
1686
|
+
issues,
|
|
1687
|
+
qualityGateStatus: issues.some((issue) => issue.severity === "blocking") ? "failed" : packet.qualityReport.qualityGateStatus
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
function stableContextKey(field) {
|
|
1691
|
+
return `${field.section}.${field.label}`.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// src/application/coordinator.ts
|
|
1695
|
+
function createApplicationPipeline(config) {
|
|
1696
|
+
const {
|
|
1697
|
+
generateText,
|
|
1698
|
+
generateObject,
|
|
1699
|
+
applicationStore,
|
|
1700
|
+
templateStore,
|
|
1701
|
+
documentStore,
|
|
1702
|
+
memoryStore,
|
|
1703
|
+
backfillProvider,
|
|
1704
|
+
orgContext = [],
|
|
1705
|
+
concurrency = 4,
|
|
1706
|
+
onTokenUsage,
|
|
1707
|
+
onProgress,
|
|
1708
|
+
log,
|
|
1709
|
+
providerOptions,
|
|
1710
|
+
qualityGate = "warn",
|
|
1711
|
+
modelCapabilities,
|
|
1712
|
+
modelBudgetConstraints
|
|
1713
|
+
} = config;
|
|
1714
|
+
const limit = pLimit(concurrency);
|
|
1715
|
+
let totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
1716
|
+
function trackUsage(usage) {
|
|
1717
|
+
if (usage) {
|
|
1718
|
+
totalUsage.inputTokens += usage.inputTokens;
|
|
1719
|
+
totalUsage.outputTokens += usage.outputTokens;
|
|
1720
|
+
onTokenUsage?.(usage);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
function resolveBudget(taskKind, hintTokens) {
|
|
1724
|
+
return resolveModelBudget({
|
|
1725
|
+
taskKind,
|
|
1726
|
+
hintTokens,
|
|
1727
|
+
modelCapabilities,
|
|
1728
|
+
constraint: modelBudgetConstraints?.[taskKind]
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
async function processApplication(input) {
|
|
1732
|
+
totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
1733
|
+
const { pdfBase64, context } = input;
|
|
1734
|
+
const applicationProviderOptions = input.sourceSpans?.length ? { ...providerOptions, sourceSpans: input.sourceSpans } : providerOptions;
|
|
1735
|
+
const id = input.applicationId ?? `app-${Date.now()}`;
|
|
1736
|
+
const now = Date.now();
|
|
1737
|
+
if (input.template) {
|
|
1738
|
+
await templateStore?.saveTemplate(input.template);
|
|
1739
|
+
}
|
|
1740
|
+
let state = {
|
|
1741
|
+
id,
|
|
1742
|
+
templateId: input.template?.id,
|
|
1743
|
+
templateVersion: input.template?.version,
|
|
1744
|
+
templateSnapshot: input.template,
|
|
1745
|
+
pdfBase64: void 0,
|
|
1746
|
+
title: void 0,
|
|
1747
|
+
applicationType: null,
|
|
1748
|
+
questionGraph: input.questionGraph ?? input.template?.questionGraph,
|
|
1749
|
+
fields: [],
|
|
1750
|
+
qualityReport: void 0,
|
|
1751
|
+
batches: void 0,
|
|
1752
|
+
currentBatchIndex: 0,
|
|
1753
|
+
status: "classifying",
|
|
1754
|
+
createdAt: now,
|
|
1755
|
+
updatedAt: now
|
|
1756
|
+
};
|
|
1757
|
+
onProgress?.("Classifying document...");
|
|
1758
|
+
await applicationStore?.save(state);
|
|
1759
|
+
let classifyResult;
|
|
1760
|
+
try {
|
|
1761
|
+
const { result, usage: classifyUsage } = await classifyApplication(
|
|
1762
|
+
pdfBase64,
|
|
1763
|
+
generateObject,
|
|
1764
|
+
applicationProviderOptions,
|
|
1765
|
+
resolveBudget("application_classify", 512).maxTokens
|
|
1766
|
+
);
|
|
1767
|
+
trackUsage(classifyUsage);
|
|
1768
|
+
classifyResult = result;
|
|
1769
|
+
} catch (error) {
|
|
1770
|
+
await log?.(`Classification failed, treating as non-application: ${error instanceof Error ? error.message : String(error)}`);
|
|
1771
|
+
classifyResult = { isApplication: false, confidence: 0, applicationType: null };
|
|
1772
|
+
}
|
|
1773
|
+
if (!classifyResult.isApplication) {
|
|
1774
|
+
state.status = "complete";
|
|
1775
|
+
state.updatedAt = Date.now();
|
|
1776
|
+
state.qualityReport = buildApplicationQualityReport(state);
|
|
1777
|
+
await applicationStore?.save(state);
|
|
1778
|
+
return { state, tokenUsage: totalUsage, reviewReport: state.qualityReport };
|
|
1779
|
+
}
|
|
1780
|
+
state.applicationType = classifyResult.applicationType;
|
|
1781
|
+
state.status = "extracting";
|
|
1782
|
+
state.updatedAt = Date.now();
|
|
1783
|
+
await applicationStore?.save(state);
|
|
1784
|
+
onProgress?.("Extracting form fields...");
|
|
1785
|
+
let fields;
|
|
1786
|
+
try {
|
|
1787
|
+
const { fields: extractedFields, usage: extractUsage } = await extractFields(
|
|
1788
|
+
pdfBase64,
|
|
1789
|
+
generateObject,
|
|
1790
|
+
applicationProviderOptions,
|
|
1791
|
+
resolveBudget("application_extract_fields", 8192).maxTokens
|
|
1792
|
+
);
|
|
1793
|
+
trackUsage(extractUsage);
|
|
1794
|
+
fields = extractedFields;
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
await log?.(`Field extraction failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1797
|
+
fields = [];
|
|
1798
|
+
}
|
|
1799
|
+
if (fields.length === 0) {
|
|
1800
|
+
await log?.("No fields extracted, completing pipeline with empty result");
|
|
1801
|
+
state.status = "complete";
|
|
1802
|
+
state.updatedAt = Date.now();
|
|
1803
|
+
state.qualityReport = buildApplicationQualityReport(state);
|
|
1804
|
+
await applicationStore?.save(state);
|
|
1805
|
+
return { state, tokenUsage: totalUsage, reviewReport: state.qualityReport };
|
|
1806
|
+
}
|
|
1807
|
+
state.fields = fields;
|
|
1808
|
+
state.questionGraph = input.questionGraph ?? input.template?.questionGraph ?? buildQuestionGraphFromFields(fields, {
|
|
1809
|
+
id: `${id}:graph`,
|
|
1810
|
+
title: classifyResult.applicationType ?? void 0,
|
|
1811
|
+
applicationType: classifyResult.applicationType,
|
|
1812
|
+
source: "pdf"
|
|
1813
|
+
});
|
|
1814
|
+
state.title = classifyResult.applicationType ?? void 0;
|
|
1815
|
+
state.status = "auto_filling";
|
|
1816
|
+
state.updatedAt = Date.now();
|
|
1817
|
+
await applicationStore?.save(state);
|
|
1818
|
+
onProgress?.(`Auto-filling ${fields.length} fields...`);
|
|
1819
|
+
let workflowPlan = planApplicationWorkflow({
|
|
1820
|
+
fields: state.fields,
|
|
1821
|
+
hasBackfillProvider: Boolean(backfillProvider),
|
|
1822
|
+
orgContextCount: orgContext.length,
|
|
1823
|
+
hasDocumentStore: Boolean(documentStore),
|
|
1824
|
+
hasMemoryStore: Boolean(memoryStore)
|
|
1825
|
+
});
|
|
1826
|
+
if (workflowPlan.runBackfill && backfillProvider) {
|
|
1827
|
+
try {
|
|
1828
|
+
const priorAnswers = await backfillFromPriorAnswers(state.fields, backfillProvider);
|
|
1829
|
+
for (const pa of priorAnswers) {
|
|
1830
|
+
const field = state.fields.find((f) => f.id === pa.fieldId);
|
|
1831
|
+
if (field && !field.value && pa.relevance > 0.8) {
|
|
1832
|
+
field.value = pa.value;
|
|
1833
|
+
field.source = `backfill: ${pa.source}`;
|
|
1834
|
+
field.confidence = pa.confidence ?? "high";
|
|
1835
|
+
field.validationStatus = pa.sourceSpanIds?.length ? "valid" : "needs_review";
|
|
1836
|
+
field.sourceSpanIds = pa.sourceSpanIds;
|
|
1837
|
+
field.userSourceSpanIds = pa.userSourceSpanIds;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
} catch (e) {
|
|
1841
|
+
await log?.(`Backfill failed: ${e}`);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
workflowPlan = planApplicationWorkflow({
|
|
1845
|
+
fields: state.fields,
|
|
1846
|
+
hasBackfillProvider: false,
|
|
1847
|
+
orgContextCount: orgContext.length,
|
|
1848
|
+
hasDocumentStore: Boolean(documentStore),
|
|
1849
|
+
hasMemoryStore: Boolean(memoryStore)
|
|
1850
|
+
});
|
|
1851
|
+
const fillTasks = [];
|
|
1852
|
+
if (workflowPlan.runContextAutoFill) {
|
|
1853
|
+
fillTasks.push(
|
|
1854
|
+
limit(async () => {
|
|
1855
|
+
const unfilledFields2 = state.fields.filter((f) => !f.value);
|
|
1856
|
+
if (unfilledFields2.length === 0) return;
|
|
1857
|
+
try {
|
|
1858
|
+
const { result: autoFillResult, usage: afUsage } = await autoFillFromContext(
|
|
1859
|
+
unfilledFields2,
|
|
1860
|
+
orgContext,
|
|
1861
|
+
generateObject,
|
|
1862
|
+
providerOptions,
|
|
1863
|
+
resolveBudget("application_auto_fill", 4096).maxTokens
|
|
1864
|
+
);
|
|
1865
|
+
trackUsage(afUsage);
|
|
1866
|
+
for (const match of autoFillResult.matches) {
|
|
1867
|
+
const field = state.fields.find((f) => f.id === match.fieldId);
|
|
1868
|
+
if (field && !field.value) {
|
|
1869
|
+
field.value = match.value;
|
|
1870
|
+
field.source = `auto-fill: ${match.contextKey}`;
|
|
1871
|
+
field.confidence = match.confidence;
|
|
1872
|
+
field.validationStatus = "valid";
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
} catch (e) {
|
|
1876
|
+
await log?.(`Auto-fill from context failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1877
|
+
}
|
|
1878
|
+
})
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
if (workflowPlan.documentSearchFields.length > 0 && memoryStore) {
|
|
1882
|
+
fillTasks.push(
|
|
1883
|
+
(async () => {
|
|
1884
|
+
try {
|
|
1885
|
+
const searchPromises = workflowPlan.documentSearchFields.map(
|
|
1886
|
+
(f) => limit(async () => {
|
|
1887
|
+
const chunks = await memoryStore.search(`${f.section} ${f.label}`, { limit: 3 });
|
|
1888
|
+
const match = selectMemoryBackfillMatch(f, chunks);
|
|
1889
|
+
if (match) {
|
|
1890
|
+
const field = state.fields.find((candidate) => candidate.id === f.id);
|
|
1891
|
+
if (field && !field.value) {
|
|
1892
|
+
field.value = match.value;
|
|
1893
|
+
field.source = match.source;
|
|
1894
|
+
field.confidence = match.confidence;
|
|
1895
|
+
field.validationStatus = match.sourceSpanIds.length > 0 ? "valid" : "needs_review";
|
|
1896
|
+
field.sourceSpanIds = match.sourceSpanIds.length > 0 ? match.sourceSpanIds : void 0;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
})
|
|
1900
|
+
);
|
|
1901
|
+
await Promise.all(searchPromises);
|
|
1902
|
+
} catch (e) {
|
|
1903
|
+
await log?.(`Document backfill search failed: ${e}`);
|
|
1904
|
+
}
|
|
1905
|
+
})()
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
await Promise.all(fillTasks);
|
|
1909
|
+
state.updatedAt = Date.now();
|
|
1910
|
+
await applicationStore?.save(state);
|
|
1911
|
+
workflowPlan = planApplicationWorkflow({
|
|
1912
|
+
fields: state.fields,
|
|
1913
|
+
hasBackfillProvider: false,
|
|
1914
|
+
orgContextCount: 0,
|
|
1915
|
+
hasDocumentStore: false,
|
|
1916
|
+
hasMemoryStore: false
|
|
1917
|
+
});
|
|
1918
|
+
const unfilledFields = getActiveApplicationFields(state).filter((field) => !field.value);
|
|
1919
|
+
if (workflowPlan.runBatching) {
|
|
1920
|
+
onProgress?.(`Batching ${unfilledFields.length} remaining questions...`);
|
|
1921
|
+
state.status = "batching";
|
|
1922
|
+
try {
|
|
1923
|
+
const { result: batchResult, usage: batchUsage } = await batchQuestions(
|
|
1924
|
+
unfilledFields,
|
|
1925
|
+
generateObject,
|
|
1926
|
+
providerOptions,
|
|
1927
|
+
resolveBudget("application_batch", 2048).maxTokens
|
|
1928
|
+
);
|
|
1929
|
+
trackUsage(batchUsage);
|
|
1930
|
+
state.batches = batchResult.batches;
|
|
1931
|
+
} catch (error) {
|
|
1932
|
+
await log?.(`Batching failed, using single-batch fallback: ${error instanceof Error ? error.message : String(error)}`);
|
|
1933
|
+
state.batches = [unfilledFields.map((f) => f.id)];
|
|
1934
|
+
}
|
|
1935
|
+
state.currentBatchIndex = 0;
|
|
1936
|
+
state.status = "collecting";
|
|
1937
|
+
} else {
|
|
1938
|
+
state.status = "confirming";
|
|
1939
|
+
}
|
|
1940
|
+
state.contextProposals = proposeContextWrites(state);
|
|
1941
|
+
state.qualityReport = buildApplicationQualityReport(state);
|
|
1942
|
+
state.updatedAt = Date.now();
|
|
1943
|
+
await applicationStore?.save(state);
|
|
1944
|
+
if (shouldFailQualityGate(qualityGate, state.qualityReport.qualityGateStatus)) {
|
|
1945
|
+
throw new Error("Application quality gate failed. See state.qualityReport for blocking issues.");
|
|
1946
|
+
}
|
|
1947
|
+
const filledCount = state.fields.filter((f) => f.value).length;
|
|
1948
|
+
onProgress?.(`Application processed: ${filledCount}/${state.fields.length} fields filled, ${state.batches?.length ?? 0} batches to collect.`);
|
|
1949
|
+
return { state, tokenUsage: totalUsage, reviewReport: state.qualityReport };
|
|
1950
|
+
}
|
|
1951
|
+
async function processReply(input) {
|
|
1952
|
+
totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
1953
|
+
const { applicationId, replyText, context } = input;
|
|
1954
|
+
const replySourceSpanIds = input.replySourceSpanIds?.length ? input.replySourceSpanIds : buildTextSourceSpans({
|
|
1955
|
+
documentId: `${applicationId}:reply:${sourceSpanTextHash(replyText).slice(0, 12)}`,
|
|
1956
|
+
sourceKind: "email",
|
|
1957
|
+
text: replyText,
|
|
1958
|
+
metadata: { applicationId }
|
|
1959
|
+
}).map((span) => span.id);
|
|
1960
|
+
let state = null;
|
|
1961
|
+
if (applicationStore) {
|
|
1962
|
+
state = await applicationStore.get(applicationId);
|
|
1963
|
+
}
|
|
1964
|
+
if (!state) {
|
|
1965
|
+
throw new Error(`Application ${applicationId} not found`);
|
|
1966
|
+
}
|
|
1967
|
+
const currentBatchFieldIds = state.batches?.[state.currentBatchIndex] ?? [];
|
|
1968
|
+
const activeFields = getActiveApplicationFields(state);
|
|
1969
|
+
const currentBatchFields = activeFields.filter(
|
|
1970
|
+
(f) => currentBatchFieldIds.includes(f.id)
|
|
1971
|
+
);
|
|
1972
|
+
onProgress?.("Classifying reply...");
|
|
1973
|
+
let intent;
|
|
1974
|
+
try {
|
|
1975
|
+
const { intent: classifiedIntent, usage: intentUsage } = await classifyReplyIntent(
|
|
1976
|
+
currentBatchFields,
|
|
1977
|
+
replyText,
|
|
1978
|
+
generateObject,
|
|
1979
|
+
providerOptions,
|
|
1980
|
+
resolveBudget("application_classify", 1024).maxTokens
|
|
1981
|
+
);
|
|
1982
|
+
trackUsage(intentUsage);
|
|
1983
|
+
intent = classifiedIntent;
|
|
1984
|
+
} catch (error) {
|
|
1985
|
+
await log?.(`Reply intent classification failed, defaulting to answers_only: ${error instanceof Error ? error.message : String(error)}`);
|
|
1986
|
+
intent = {
|
|
1987
|
+
primaryIntent: "answers_only",
|
|
1988
|
+
hasAnswers: true,
|
|
1989
|
+
questionText: void 0,
|
|
1990
|
+
questionFieldIds: void 0,
|
|
1991
|
+
lookupRequests: void 0
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
let fieldsFilled = 0;
|
|
1995
|
+
let responseText;
|
|
1996
|
+
let replyPlan = planReplyActions({
|
|
1997
|
+
intent,
|
|
1998
|
+
currentBatchFields,
|
|
1999
|
+
hasDocumentStore: Boolean(documentStore)
|
|
2000
|
+
});
|
|
2001
|
+
if (replyPlan.parseAnswers) {
|
|
2002
|
+
onProgress?.("Parsing answers...");
|
|
2003
|
+
try {
|
|
2004
|
+
const { result: parseResult, usage: parseUsage } = await parseAnswers(
|
|
2005
|
+
currentBatchFields,
|
|
2006
|
+
replyText,
|
|
2007
|
+
generateObject,
|
|
2008
|
+
providerOptions,
|
|
2009
|
+
resolveBudget("application_parse_answers", 4096).maxTokens
|
|
2010
|
+
);
|
|
2011
|
+
trackUsage(parseUsage);
|
|
2012
|
+
for (const answer of parseResult.answers) {
|
|
2013
|
+
const field = state.fields.find((f) => f.id === answer.fieldId);
|
|
2014
|
+
if (field) {
|
|
2015
|
+
field.value = answer.value;
|
|
2016
|
+
field.source = "user";
|
|
2017
|
+
field.confidence = "confirmed";
|
|
2018
|
+
field.userSourceSpanIds = replySourceSpanIds;
|
|
2019
|
+
field.validationStatus = "valid";
|
|
2020
|
+
fieldsFilled++;
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
} catch (error) {
|
|
2024
|
+
await log?.(`Answer parsing failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
if (replyPlan.runLookup && intent.lookupRequests?.length) {
|
|
2028
|
+
onProgress?.("Processing lookup requests...");
|
|
2029
|
+
let availableData = "";
|
|
2030
|
+
if (documentStore) {
|
|
2031
|
+
try {
|
|
2032
|
+
const docs = await documentStore.query({});
|
|
2033
|
+
availableData = docs.map((d) => {
|
|
2034
|
+
const doc = d;
|
|
2035
|
+
return `Document ${doc.id}: ${doc.type} - ${doc.carrier ?? "unknown carrier"} - ${doc.insuredName ?? ""}`;
|
|
2036
|
+
}).join("\n");
|
|
2037
|
+
} catch (e) {
|
|
2038
|
+
await log?.(`Document query for lookup failed: ${e}`);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
if (availableData) {
|
|
2042
|
+
const targetFields = state.fields.filter(
|
|
2043
|
+
(f) => intent.lookupRequests.some((lr) => lr.targetFieldIds.includes(f.id))
|
|
2044
|
+
);
|
|
2045
|
+
try {
|
|
2046
|
+
const { result: lookupResult, usage: lookupUsage } = await fillFromLookup(
|
|
2047
|
+
intent.lookupRequests,
|
|
2048
|
+
targetFields,
|
|
2049
|
+
availableData,
|
|
2050
|
+
generateObject,
|
|
2051
|
+
providerOptions,
|
|
2052
|
+
resolveBudget("application_lookup", 4096).maxTokens
|
|
2053
|
+
);
|
|
2054
|
+
trackUsage(lookupUsage);
|
|
2055
|
+
for (const fill of lookupResult.fills) {
|
|
2056
|
+
const field = state.fields.find((f) => f.id === fill.fieldId);
|
|
2057
|
+
if (field) {
|
|
2058
|
+
field.value = fill.value;
|
|
2059
|
+
field.source = `lookup: ${fill.source}`;
|
|
2060
|
+
field.confidence = "high";
|
|
2061
|
+
field.validationStatus = fill.sourceSpanIds?.length ? "valid" : "needs_review";
|
|
2062
|
+
if (fill.sourceSpanIds?.length) {
|
|
2063
|
+
field.sourceSpanIds = fill.sourceSpanIds;
|
|
2064
|
+
}
|
|
2065
|
+
fieldsFilled++;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
} catch (error) {
|
|
2069
|
+
await log?.(`Lookup fill failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
if (replyPlan.answerQuestion && intent.questionText) {
|
|
2074
|
+
try {
|
|
2075
|
+
const budget = resolveBudget("application_email", 512);
|
|
2076
|
+
const { text, usage } = await generateText({
|
|
2077
|
+
prompt: `The user is filling out an insurance application and asked: "${intent.questionText}"
|
|
2078
|
+
|
|
2079
|
+
Provide a brief, helpful explanation (2-3 sentences). End with "Just reply with the answer when you're ready and I'll fill it in."`,
|
|
2080
|
+
maxTokens: budget.maxTokens,
|
|
2081
|
+
taskKind: "application_email",
|
|
2082
|
+
budgetDiagnostics: budget,
|
|
2083
|
+
providerOptions
|
|
2084
|
+
});
|
|
2085
|
+
trackUsage(usage);
|
|
2086
|
+
responseText = text;
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
await log?.(`Question response generation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2089
|
+
responseText = `I wasn't able to generate an explanation for your question. Could you rephrase it, or just provide the answer directly?`;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
const activeCurrentBatchFieldIds = currentBatchFields.map((field) => field.id);
|
|
2093
|
+
const currentBatchComplete = activeCurrentBatchFieldIds.every(
|
|
2094
|
+
(fid) => state.fields.find((f) => f.id === fid)?.value
|
|
2095
|
+
);
|
|
2096
|
+
let nextBatchIndex;
|
|
2097
|
+
let nextBatchFields;
|
|
2098
|
+
if (state.batches) {
|
|
2099
|
+
for (let index = state.currentBatchIndex + 1; index < state.batches.length; index++) {
|
|
2100
|
+
const activeCandidateFields = getActiveApplicationFields(state);
|
|
2101
|
+
const candidateFields = activeCandidateFields.filter((f) => state.batches[index].includes(f.id));
|
|
2102
|
+
if (candidateFields.some((f) => !f.value)) {
|
|
2103
|
+
nextBatchIndex = index;
|
|
2104
|
+
nextBatchFields = candidateFields;
|
|
2105
|
+
break;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
replyPlan = planReplyActions({
|
|
2110
|
+
intent,
|
|
2111
|
+
currentBatchFields,
|
|
2112
|
+
nextBatchFields,
|
|
2113
|
+
hasDocumentStore: Boolean(documentStore)
|
|
2114
|
+
});
|
|
2115
|
+
if (currentBatchComplete && replyPlan.advanceBatch && state.batches) {
|
|
2116
|
+
if (nextBatchIndex !== void 0 && nextBatchFields) {
|
|
2117
|
+
state.currentBatchIndex = nextBatchIndex;
|
|
2118
|
+
const filledCount = state.fields.filter((f) => f.value).length;
|
|
2119
|
+
if (replyPlan.generateNextEmail) {
|
|
2120
|
+
try {
|
|
2121
|
+
const { text: emailText, usage: emailUsage } = await generateBatchEmail(
|
|
2122
|
+
nextBatchFields,
|
|
2123
|
+
state.currentBatchIndex,
|
|
2124
|
+
state.batches.length,
|
|
2125
|
+
{
|
|
2126
|
+
appTitle: state.title,
|
|
2127
|
+
totalFieldCount: state.fields.length,
|
|
2128
|
+
filledFieldCount: filledCount,
|
|
2129
|
+
companyName: context?.companyName
|
|
2130
|
+
},
|
|
2131
|
+
generateText,
|
|
2132
|
+
providerOptions,
|
|
2133
|
+
resolveBudget("application_email", 2048).maxTokens
|
|
2134
|
+
);
|
|
2135
|
+
trackUsage(emailUsage);
|
|
2136
|
+
const emailReview = reviewBatchEmail(emailText, nextBatchFields);
|
|
2137
|
+
state.qualityReport = {
|
|
2138
|
+
...buildApplicationQualityReport(state),
|
|
2139
|
+
emailReview
|
|
2140
|
+
};
|
|
2141
|
+
if (!responseText) {
|
|
2142
|
+
responseText = emailText;
|
|
2143
|
+
} else {
|
|
2144
|
+
responseText += `
|
|
2145
|
+
|
|
2146
|
+
${emailText}`;
|
|
2147
|
+
}
|
|
2148
|
+
} catch (error) {
|
|
2149
|
+
await log?.(`Batch email generation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
} else {
|
|
2153
|
+
state.status = "confirming";
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
state.updatedAt = Date.now();
|
|
2157
|
+
state.contextProposals = proposeContextWrites(state);
|
|
2158
|
+
state.qualityReport = buildApplicationQualityReport(state);
|
|
2159
|
+
await applicationStore?.save(state);
|
|
2160
|
+
if (shouldFailQualityGate(qualityGate, state.qualityReport.qualityGateStatus)) {
|
|
2161
|
+
throw new Error("Application quality gate failed. See state.qualityReport for blocking issues.");
|
|
2162
|
+
}
|
|
2163
|
+
return {
|
|
2164
|
+
state,
|
|
2165
|
+
intent: intent.primaryIntent,
|
|
2166
|
+
fieldsFilled,
|
|
2167
|
+
responseText,
|
|
2168
|
+
tokenUsage: totalUsage,
|
|
2169
|
+
reviewReport: state.qualityReport
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
async function generateCurrentBatchEmail(applicationId, opts) {
|
|
2173
|
+
totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
2174
|
+
const state = await applicationStore?.get(applicationId);
|
|
2175
|
+
if (!state) throw new Error(`Application ${applicationId} not found`);
|
|
2176
|
+
if (!state.batches?.length) throw new Error("No batches available");
|
|
2177
|
+
const batchFieldIds = state.batches[state.currentBatchIndex];
|
|
2178
|
+
const batchFields = getActiveApplicationFields(state).filter((f) => batchFieldIds.includes(f.id));
|
|
2179
|
+
const filledCount = state.fields.filter((f) => f.value).length;
|
|
2180
|
+
const { text, usage } = await generateBatchEmail(
|
|
2181
|
+
batchFields,
|
|
2182
|
+
state.currentBatchIndex,
|
|
2183
|
+
state.batches.length,
|
|
2184
|
+
{
|
|
2185
|
+
appTitle: state.title,
|
|
2186
|
+
totalFieldCount: state.fields.length,
|
|
2187
|
+
filledFieldCount: filledCount,
|
|
2188
|
+
companyName: opts?.companyName,
|
|
2189
|
+
previousBatchSummary: opts?.previousBatchSummary
|
|
2190
|
+
},
|
|
2191
|
+
generateText,
|
|
2192
|
+
providerOptions,
|
|
2193
|
+
resolveBudget("application_email", 2048).maxTokens
|
|
2194
|
+
);
|
|
2195
|
+
trackUsage(usage);
|
|
2196
|
+
const emailReview = reviewBatchEmail(text, batchFields);
|
|
2197
|
+
state.qualityReport = {
|
|
2198
|
+
...buildApplicationQualityReport(state),
|
|
2199
|
+
emailReview
|
|
2200
|
+
};
|
|
2201
|
+
await applicationStore?.save(state);
|
|
2202
|
+
return { text, tokenUsage: totalUsage };
|
|
2203
|
+
}
|
|
2204
|
+
async function getConfirmationSummary(applicationId) {
|
|
2205
|
+
totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
2206
|
+
const state = await applicationStore?.get(applicationId);
|
|
2207
|
+
if (!state) throw new Error(`Application ${applicationId} not found`);
|
|
2208
|
+
const filledFields = state.fields.filter((f) => f.value);
|
|
2209
|
+
const fieldSummary = filledFields.map((f) => `${f.section} > ${f.label}: ${f.value} (source: ${f.source ?? "unknown"})`).join("\n");
|
|
2210
|
+
const budget = resolveBudget("application_email", 4096);
|
|
2211
|
+
const { text, usage } = await generateText({
|
|
2212
|
+
prompt: `Format these filled insurance application fields as a clean confirmation summary for the user to review. Group by section, show each field as "Label: Value". End with a note asking them to confirm or request changes.
|
|
2213
|
+
|
|
2214
|
+
Application: ${state.title ?? "Insurance Application"}
|
|
2215
|
+
|
|
2216
|
+
Fields:
|
|
2217
|
+
${fieldSummary}`,
|
|
2218
|
+
maxTokens: budget.maxTokens,
|
|
2219
|
+
taskKind: "application_email",
|
|
2220
|
+
budgetDiagnostics: budget,
|
|
2221
|
+
providerOptions
|
|
2222
|
+
});
|
|
2223
|
+
trackUsage(usage);
|
|
2224
|
+
return { text, tokenUsage: totalUsage };
|
|
2225
|
+
}
|
|
2226
|
+
async function createApplicationRun2(input) {
|
|
2227
|
+
const state = createApplicationRun(input);
|
|
2228
|
+
await applicationStore?.save(state);
|
|
2229
|
+
return state;
|
|
2230
|
+
}
|
|
2231
|
+
async function planNextQuestions(applicationId, limit2) {
|
|
2232
|
+
const state = await applicationStore?.get(applicationId);
|
|
2233
|
+
if (!state) throw new Error(`Application ${applicationId} not found`);
|
|
2234
|
+
return planNextApplicationQuestions(state, limit2);
|
|
2235
|
+
}
|
|
2236
|
+
async function proposeContextWrites2(applicationId) {
|
|
2237
|
+
const state = await applicationStore?.get(applicationId);
|
|
2238
|
+
if (!state) throw new Error(`Application ${applicationId} not found`);
|
|
2239
|
+
const proposals = proposeContextWrites(state);
|
|
2240
|
+
await applicationStore?.save({
|
|
2241
|
+
...state,
|
|
2242
|
+
contextProposals: proposals,
|
|
2243
|
+
updatedAt: Date.now()
|
|
2244
|
+
});
|
|
2245
|
+
return { proposals };
|
|
2246
|
+
}
|
|
2247
|
+
async function buildApplicationPacket2(input) {
|
|
2248
|
+
const state = await applicationStore?.get(input.applicationId);
|
|
2249
|
+
if (!state) throw new Error(`Application ${input.applicationId} not found`);
|
|
2250
|
+
const packet = buildApplicationPacket(state, {
|
|
2251
|
+
submissionNotes: input.submissionNotes,
|
|
2252
|
+
now: input.now
|
|
2253
|
+
});
|
|
2254
|
+
const reviewReport = validateApplicationPacket(packet);
|
|
2255
|
+
await applicationStore?.save({
|
|
2256
|
+
...state,
|
|
2257
|
+
packet: { ...packet, qualityReport: reviewReport },
|
|
2258
|
+
status: reviewReport.qualityGateStatus === "failed" ? "broker_review" : "packet_ready",
|
|
2259
|
+
qualityReport: reviewReport,
|
|
2260
|
+
updatedAt: Date.now()
|
|
2261
|
+
});
|
|
2262
|
+
return { packet: { ...packet, qualityReport: reviewReport }, reviewReport };
|
|
2263
|
+
}
|
|
2264
|
+
return {
|
|
2265
|
+
processApplication,
|
|
2266
|
+
processReply,
|
|
2267
|
+
generateCurrentBatchEmail,
|
|
2268
|
+
getConfirmationSummary,
|
|
2269
|
+
createApplicationRun: createApplicationRun2,
|
|
2270
|
+
planNextQuestions,
|
|
2271
|
+
proposeContextWrites: proposeContextWrites2,
|
|
2272
|
+
buildApplicationPacket: buildApplicationPacket2
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
function selectMemoryBackfillMatch(field, chunks) {
|
|
2276
|
+
for (const chunk of chunks) {
|
|
2277
|
+
const value = chunk.metadata.value ?? chunk.metadata.answer ?? chunk.metadata.fieldValue;
|
|
2278
|
+
if (!value) continue;
|
|
2279
|
+
const metadataFieldId = chunk.metadata.fieldId ?? chunk.metadata.applicationFieldId;
|
|
2280
|
+
const metadataLabel = chunk.metadata.fieldLabel?.toLowerCase();
|
|
2281
|
+
const labelMatches = metadataLabel === field.label.toLowerCase();
|
|
2282
|
+
if (metadataFieldId && metadataFieldId !== field.id && !labelMatches) continue;
|
|
2283
|
+
return {
|
|
2284
|
+
value,
|
|
2285
|
+
source: chunk.metadata.source ?? `memory: ${chunk.documentId}`,
|
|
2286
|
+
confidence: metadataFieldId === field.id || labelMatches ? "high" : "medium",
|
|
2287
|
+
sourceSpanIds: parseSourceSpanIds(chunk.metadata.sourceSpanIds)
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
return null;
|
|
2291
|
+
}
|
|
2292
|
+
function parseSourceSpanIds(value) {
|
|
2293
|
+
if (!value) return [];
|
|
2294
|
+
const trimmed = value.trim();
|
|
2295
|
+
if (!trimmed) return [];
|
|
2296
|
+
if (trimmed.startsWith("[")) {
|
|
2297
|
+
try {
|
|
2298
|
+
const parsed = JSON.parse(trimmed);
|
|
2299
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
2300
|
+
} catch {
|
|
2301
|
+
return [];
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
return trimmed.split(",").map((item) => item.trim()).filter(Boolean);
|
|
2305
|
+
}
|
|
2306
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2307
|
+
0 && (module.exports = {
|
|
2308
|
+
AcroFormMappingSchema,
|
|
2309
|
+
AnswerParsingResultSchema,
|
|
2310
|
+
ApplicationClassifyResultSchema,
|
|
2311
|
+
ApplicationContextProposalSchema,
|
|
2312
|
+
ApplicationEmailReviewSchema,
|
|
2313
|
+
ApplicationFieldSchema,
|
|
2314
|
+
ApplicationPacketAnswerSchema,
|
|
2315
|
+
ApplicationPacketSchema,
|
|
2316
|
+
ApplicationQualityArtifactSchema,
|
|
2317
|
+
ApplicationQualityIssueSchema,
|
|
2318
|
+
ApplicationQualityReportSchema,
|
|
2319
|
+
ApplicationQualityRoundSchema,
|
|
2320
|
+
ApplicationQuestionConditionSchema,
|
|
2321
|
+
ApplicationQuestionGraphSchema,
|
|
2322
|
+
ApplicationQuestionNodeSchema,
|
|
2323
|
+
ApplicationRepeatSchema,
|
|
2324
|
+
ApplicationStateSchema,
|
|
2325
|
+
ApplicationTemplateSchema,
|
|
2326
|
+
AutoFillMatchSchema,
|
|
2327
|
+
AutoFillResultSchema,
|
|
2328
|
+
FieldExtractionResultSchema,
|
|
2329
|
+
FieldTypeSchema,
|
|
2330
|
+
FlatPdfPlacementSchema,
|
|
2331
|
+
LookupFillResultSchema,
|
|
2332
|
+
LookupFillSchema,
|
|
2333
|
+
LookupRequestSchema,
|
|
2334
|
+
ParsedAnswerSchema,
|
|
2335
|
+
QuestionBatchResultSchema,
|
|
2336
|
+
ReplyIntentSchema,
|
|
2337
|
+
applyApplicationAnswers,
|
|
2338
|
+
buildApplicationPacket,
|
|
2339
|
+
buildQuestionGraphFromFields,
|
|
2340
|
+
createApplicationPipeline,
|
|
2341
|
+
createApplicationRun,
|
|
2342
|
+
extractQuestionGraphFromFields,
|
|
2343
|
+
flattenQuestionGraph,
|
|
2344
|
+
getActiveApplicationFields,
|
|
2345
|
+
getNextApplicationQuestions,
|
|
2346
|
+
normalizeApplicationQuestionGraph,
|
|
2347
|
+
planNextApplicationQuestions,
|
|
2348
|
+
proposeContextWrites,
|
|
2349
|
+
validateApplicationPacket
|
|
2350
|
+
});
|
|
2351
|
+
//# sourceMappingURL=application.js.map
|