Improve insurance claim intake follow-up logic

This commit is contained in:
Shubhamsaboo
2026-05-22 21:20:56 -07:00
parent 2ea72a0e5a
commit 4fd8f2239d
2 changed files with 53 additions and 61 deletions
@@ -261,40 +261,6 @@ def _events(
return events
def _next_question(
validation: dict[str, Any],
coverage: dict[str, Any],
fraud_gate: dict[str, Any],
) -> str:
route = fraud_gate.get("final_routing_decision", coverage.get("routing_decision"))
signals = fraud_gate.get("signals", [])
if route == "emergency_escalation" and any(
signal.get("route_to_emergency") for signal in signals
):
return (
"Because you mentioned an injury or safety concern, are you and everyone "
"else currently safe, and has anyone needed emergency medical care?"
)
missing = validation.get("missing_fields", [])
question_map = {
"policyholder_name": "What is your full name as it appears on the policy?",
"contact_method": "What is the best phone number or email for the adjuster to reach you?",
"date_of_loss": "When did the loss happen?",
"loss_location": "Where did the loss happen?",
"loss_description": "Can you briefly describe what happened?",
}
for field_name in missing:
if field_name in question_map:
return question_map[field_name]
required_docs = coverage.get("required_documents", [])
if required_docs:
return f"Do you already have this document or evidence available: {required_docs[0]}?"
return "I have enough for the initial intake packet. Is there anything important the adjuster should know before handoff?"
def _ui_state(
session: IntakeSession,
validation: dict[str, Any],
@@ -383,7 +349,7 @@ def _ui_state(
"Priority": f"{classification.severity.title()} - {classification.severity_rationale}",
"Required actions": _join(required_doc_names, "No additional documents identified by current rules."),
"Attachments": evidence_text,
"Next best action": _next_question(validation, coverage, fraud_gate),
"Next best action": packet["claimant_next_message"],
},
"packet_markdown": packet["markdown"],
"model": MODEL,
@@ -542,10 +508,14 @@ async def live_voice(websocket: WebSocket) -> None:
response_modalities=["AUDIO"],
system_instruction=(
"You are a voice insurance FNOL intake agent. Speak naturally and briefly. "
"Collect claim facts one step at a time. If injury, unsafe housing, or immediate "
"danger is mentioned, prioritize safety and human escalation. Do not promise "
"coverage, payment, liability, benefits, or approval. Ask only one or two focused "
"follow-up questions at a time."
"Your job is to collect enough blocking intake facts for a smooth adjuster handoff, "
"not to end the call after the claimant's first narrative. If injury, unsafe housing, "
"or immediate danger is mentioned, prioritize safety and human escalation. Otherwise, "
"keep asking for missing blockers one step at a time: claimant name, contact method, "
"policy number if available, date and location of loss, what happened, safety/injury "
"status, evidence, documents, reports, tow details, or other involved parties. Do not "
"promise coverage, payment, liability, benefits, or approval. Ask only one or two "
"focused follow-up questions at a time."
),
speech_config=types.SpeechConfig(
voice_config=types.VoiceConfig(
@@ -74,6 +74,15 @@ TYPE_REQUIRED_DOCS: dict[str, list[tuple[str, str]]] = {
],
}
BLOCKING_FIELD_QUESTIONS = {
"policyholder_name": "What is your full name as it appears on the policy?",
"policy_number": "What is the policy number, if you have it available?",
"contact_method": "What is the best phone number or email for the adjuster to reach you?",
"date_of_loss": "When did the loss happen?",
"loss_location": "Where did the loss happen?",
"loss_description": "Can you briefly describe what happened?",
}
def _as_model(model_type, value):
if isinstance(value, model_type):
@@ -186,6 +195,39 @@ def _parse_date(value: str) -> datetime | None:
return None
def _next_claimant_message(
route: str,
missing: list[str],
required_documents: list[str],
) -> str:
if route == "emergency_escalation":
return (
"Your claim mentions injury, safety, or habitability concerns. A human representative "
"should review this immediately. If anyone is in danger, contact local emergency services first."
)
for field_name in missing:
if field_name in BLOCKING_FIELD_QUESTIONS:
return BLOCKING_FIELD_QUESTIONS[field_name]
if missing:
return f"Can you clarify this missing detail so the adjuster can process the claim smoothly: {missing[0]}?"
if required_documents:
return f"Do you have this document or evidence available now: {required_documents[0]}?"
if route == "special_investigation":
return (
"A human reviewer should look at this file, but the core intake packet is started. "
"Please keep originals of any receipts, reports, photos, or messages related to the loss."
)
return (
"Your intake packet has the core information needed for adjuster assignment. Keep copies of "
"all receipts, photos, reports, and communications related to the loss."
)
def validate_required_claim_fields(claim_value: Any) -> dict[str, Any]:
"""Validate minimum intake facts before coverage and evidence rules run."""
@@ -195,6 +237,7 @@ def validate_required_claim_fields(claim_value: Any) -> dict[str, Any]:
required_fields = {
"policyholder_name": claim.policyholder_name,
"policy_number": claim.policy_number,
"contact_method": claim.contact_method,
"date_of_loss": claim.date_of_loss,
"loss_location": claim.loss_location,
@@ -204,8 +247,6 @@ def validate_required_claim_fields(claim_value: Any) -> dict[str, Any]:
if _blank(value):
missing.append(field_name)
if _blank(claim.policy_number):
warnings.append("Policy number was not supplied; adjuster may need identity verification.")
if claim.estimated_loss_usd is None:
warnings.append("Estimated loss amount was not supplied.")
@@ -585,26 +626,7 @@ def build_claim_intake_packet(
f"Estimated loss: {claim.estimated_loss_usd if claim.estimated_loss_usd is not None else 'not supplied'}."
)
if route == "emergency_escalation":
claimant_next = (
"Your claim mentions injury, safety, or habitability concerns. A human representative "
"should review this immediately. If anyone is in danger, contact local emergency services first."
)
elif route == "special_investigation":
claimant_next = (
"We can open the intake packet, but some timing or evidence details require specialized "
"human review. Please provide the requested documents and keep originals available."
)
elif route == "needs_docs":
claimant_next = (
"Your intake packet is started. Please provide the missing information and checklist items "
"so an adjuster can evaluate the claim without delay."
)
else:
claimant_next = (
"Your intake packet has the core information needed for adjuster assignment. Keep copies of "
"all receipts, photos, reports, and communications related to the loss."
)
claimant_next = _next_claimant_message(route, missing, evidence_decision.required_documents)
audit_lines = [f"{idx}. {entry}" for idx, entry in enumerate(fraud_gate.audit_trail, start=1)]