estimation service refactor

This commit is contained in:
ipu 2025-07-25 17:43:23 +03:00
parent aaba8753ef
commit 18c95083c9
4 changed files with 367 additions and 62 deletions

View file

@ -198,47 +198,95 @@ Handles insurance estimation and underwriting requests.
**Request Body:** **Request Body:**
```json ```json
{ {
"uid": "application-id", "uid": "string",
"applicants": [ "applicants": [
{ {
"firstName": "John", "applicant": 1,
"dob": "15/03/1985", "firstName": "string",
"weight": 70, "lastName": "string",
"heightFt": 5, "midName": "string",
"heightIn": 10, "phone": "string",
"applicant": 1 "gender": "male",
"dob": "2025-07-25",
"nicotine": true,
"weight": 0,
"heightFt": 0,
"heightIn": 0
} }
], ],
"plans": [ "plans": [
{ {
"coverage": 1 "id": 0,
"coverage": 1,
"tier": "string"
} }
], ],
"phq": { "phq": {
"effectiveDate": "01/01/2024", "treatment": true,
"medications": [], "invalid": true,
"conditions": [], "pregnancy": true,
"issues": [] "effectiveDate": "2025-07-25",
"disclaimer": true,
"signature": "string",
"medications": [
{
"applicant": 0,
"name": "string",
"rxcui": "string",
"dosage": "string",
"frequency": "string",
"description": "string"
}
],
"issues": [
{
"key": "string",
"details": [
{
"key": "string",
"description": "string"
}
]
}
],
"conditions": [
{
"key": "string",
"description": "string"
}
]
}, },
"income": 50000, "income": 0,
"address": {} "address": {
"address1": "string",
"address2": "string",
"city": "string",
"state": "string",
"zipcode": "string"
}
} }
``` ```
**Response:** **Response:**
```json ```json
{ {
"uid": "application-id", "status": "accepted",
"status": "submitted", "details": {
"data": { "dtq": true,
"results": [...], "reason": "string",
"combined": { "tier": 0,
"tier": 2.0, "total_price": 0
"total_price": 243,
"dtq": false,
"message": "Final assigned tier is 2.0"
}
}, },
"external": {...} "results": [
{
"name": "string",
"applicant_type": "Primary",
"age": 0,
"bmi": 0,
"tier": 0,
"rx_spend": 0,
"message": "string"
}
]
} }
``` ```

View file

@ -1,5 +1,6 @@
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from datetime import date
class InsuranceChatRequest(BaseModel): class InsuranceChatRequest(BaseModel):
message: str = Field(..., description="User message") message: str = Field(..., description="User message")
@ -20,19 +21,90 @@ class InsuranceChatResponse(BaseModel):
sources: List[Source] = [] sources: List[Source] = []
history: List[HistoryItem] = [] history: List[HistoryItem] = []
# New estimation models matching the specified format
class Applicant(BaseModel):
applicant: int
firstName: str
lastName: str
midName: str
phone: str
gender: str
dob: date
nicotine: bool
weight: float
heightFt: int
heightIn: int
class Plan(BaseModel):
id: int
coverage: int
tier: str
class Medication(BaseModel):
applicant: int
name: str
rxcui: str
dosage: str
frequency: str
description: str
class IssueDetail(BaseModel):
key: str
description: str
class Issue(BaseModel):
key: str
details: List[IssueDetail]
class Condition(BaseModel):
key: str
description: str
class PHQ(BaseModel):
treatment: bool
invalid: bool
pregnancy: bool
effectiveDate: date
disclaimer: bool
signature: str
medications: List[Medication]
issues: List[Issue]
conditions: List[Condition]
class Address(BaseModel):
address1: str
address2: str
city: str
state: str
zipcode: str
class EstimationRequest(BaseModel): class EstimationRequest(BaseModel):
uid: str = Field(..., description="Application unique identifier") uid: str
applicants: List[Dict[str, Any]] = Field(..., description="List of applicants") applicants: List[Applicant]
plans: List[Dict[str, Any]] = Field(..., description="List of insurance plans") plans: List[Plan]
phq: Dict[str, Any] = Field(..., description="Personal Health Questionnaire data") phq: PHQ
income: Optional[float] = Field(0, description="Annual income") income: float
address: Optional[Dict[str, Any]] = Field({}, description="Address information") address: Address
class EstimationDetails(BaseModel):
dtq: bool
reason: str
tier: int
total_price: float
class EstimationResult(BaseModel):
name: str
applicant_type: str
age: int
bmi: float
tier: int
rx_spend: float
message: str
class EstimationResponse(BaseModel): class EstimationResponse(BaseModel):
uid: str
status: str status: str
data: Dict[str, Any] details: EstimationDetails
external: Optional[Dict[str, Any]] = None results: List[EstimationResult]
class SessionCreateResponse(BaseModel): class SessionCreateResponse(BaseModel):
session_id: str session_id: str

View file

@ -39,41 +39,191 @@ async def estimate(request: models.EstimationRequest):
detail="Missing required applicants or plans" detail="Missing required applicants or plans"
) )
# Step 1: Run estimation # Convert request to the format expected by run_underwriting
underwriting_result = run_underwriting(request.applicants, request.phq, request.plans) applicants_dict = []
for applicant in request.applicants:
applicants_dict.append({
"applicant": applicant.applicant,
"firstName": applicant.firstName,
"lastName": applicant.lastName,
"midName": applicant.midName,
"phone": applicant.phone,
"gender": applicant.gender,
"dob": applicant.dob.strftime("%d/%m/%Y"),
"nicotine": applicant.nicotine,
"weight": applicant.weight,
"heightFt": applicant.heightFt,
"heightIn": applicant.heightIn
})
# Step 2: If DTQ → reject application phq_dict = {
"treatment": request.phq.treatment,
"invalid": request.phq.invalid,
"pregnancy": request.phq.pregnancy,
"effectiveDate": request.phq.effectiveDate.strftime("%d/%m/%Y"),
"disclaimer": request.phq.disclaimer,
"signature": request.phq.signature,
"medications": [
{
"applicant": med.applicant,
"name": med.name,
"rxcui": med.rxcui,
"dosage": med.dosage,
"frequency": med.frequency,
"description": med.description
} for med in request.phq.medications
],
"issues": [
{
"key": issue.key,
"details": [
{
"key": detail.key,
"description": detail.description
} for detail in issue.details
]
} for issue in request.phq.issues
],
"conditions": [
{
"key": condition.key,
"description": condition.description
} for condition in request.phq.conditions
]
}
plans_dict = [
{
"id": plan.id,
"coverage": plan.coverage,
"tier": plan.tier
} for plan in request.plans
]
# Step 1: Run estimation
underwriting_result = run_underwriting(applicants_dict, phq_dict, plans_dict)
# Step 2: Check if DTQ → reject application
if underwriting_result["combined"].get("dtq"): if underwriting_result["combined"].get("dtq"):
# For DTQ cases, call external reject API and return rejected status
reject_response = await reject_application(request.uid) reject_response = await reject_application(request.uid)
return models.EstimationResponse( return models.EstimationResponse(
uid=request.uid,
status="rejected", status="rejected",
data=underwriting_result, details=models.EstimationDetails(
external=reject_response dtq=True,
reason="Declined due to high-risk conditions (DTQ triggered).",
tier=int(underwriting_result["combined"]["tier"]),
total_price=underwriting_result["combined"]["total_price"]
),
results=[
models.EstimationResult(
name=result["name"],
applicant_type=result["applicant_type"],
age=result["age"] or 0,
bmi=result["bmi"] or 0.0,
tier=int(result["tier"]),
rx_spend=result["rx_spend"],
message=result["message"]
) for result in underwriting_result["results"]
]
) )
# Step 3: Else → assign tier and submit # Step 3: Else → assign tier and submit to external API
final_tier = underwriting_result["combined"]["tier"] final_tier = underwriting_result["combined"]["tier"]
plans = request.plans.copy() plans = request.plans.copy()
if plans: if plans:
plans[0]["tier"] = f"tier_{str(final_tier).replace('.', '_')}" plans[0].tier = f"tier_{str(final_tier).replace('.', '_')}"
# Assemble external payload # Assemble external payload
submission_payload = { submission_payload = {
"applicants": request.applicants, "applicants": [
"plans": plans, {
"phq": request.phq, "applicant": applicant.applicant,
"firstName": applicant.firstName,
"lastName": applicant.lastName,
"midName": applicant.midName,
"phone": applicant.phone,
"gender": applicant.gender,
"dob": applicant.dob.strftime("%d/%m/%Y"),
"nicotine": applicant.nicotine,
"weight": applicant.weight,
"heightFt": applicant.heightFt,
"heightIn": applicant.heightIn
} for applicant in request.applicants
],
"plans": [
{
"id": plan.id,
"coverage": plan.coverage,
"tier": plan.tier
} for plan in plans
],
"phq": {
"treatment": request.phq.treatment,
"invalid": request.phq.invalid,
"pregnancy": request.phq.pregnancy,
"effectiveDate": request.phq.effectiveDate.strftime("%d/%m/%Y"),
"disclaimer": request.phq.disclaimer,
"signature": request.phq.signature,
"medications": [
{
"applicant": med.applicant,
"name": med.name,
"rxcui": med.rxcui,
"dosage": med.dosage,
"frequency": med.frequency,
"description": med.description
} for med in request.phq.medications
],
"issues": [
{
"key": issue.key,
"details": [
{
"key": detail.key,
"description": detail.description
} for detail in issue.details
]
} for issue in request.phq.issues
],
"conditions": [
{
"key": condition.key,
"description": condition.description
} for condition in request.phq.conditions
]
},
"income": request.income, "income": request.income,
"address": request.address "address": {
"address1": request.address.address1,
"address2": request.address.address2,
"city": request.address.city,
"state": request.address.state,
"zipcode": request.address.zipcode
}
} }
submit_response = await submit_application(submission_payload) submit_response = await submit_application(submission_payload)
return models.EstimationResponse( return models.EstimationResponse(
uid=request.uid, status="accepted",
status="submitted", details=models.EstimationDetails(
data=underwriting_result, dtq=False,
external=submit_response reason=underwriting_result["combined"]["message"],
tier=int(underwriting_result["combined"]["tier"]),
total_price=underwriting_result["combined"]["total_price"]
),
results=[
models.EstimationResult(
name=result["name"],
applicant_type=result["applicant_type"],
age=result["age"] or 0,
bmi=result["bmi"] or 0.0,
tier=int(result["tier"]),
rx_spend=result["rx_spend"],
message=result["message"]
) for result in underwriting_result["results"]
]
) )
except HTTPException: except HTTPException:
@ -102,3 +252,6 @@ async def submit_application(application_payload: Dict[str, Any]) -> Dict[str, A
json=application_payload json=application_payload
) )
return response.json() if response.status_code == 200 else {"error": "Failed to submit application"} return response.json() if response.status_code == 200 else {"error": "Failed to submit application"}

View file

@ -128,23 +128,45 @@ async def test_estimation():
"uid": "test-application-123", "uid": "test-application-123",
"applicants": [ "applicants": [
{ {
"applicant": 1,
"firstName": "John", "firstName": "John",
"dob": "15/03/1985", "lastName": "Doe",
"weight": 70, "midName": "M",
"phone": "555-123-4567",
"gender": "male",
"dob": "1985-03-15",
"nicotine": False,
"weight": 70.0,
"heightFt": 5, "heightFt": 5,
"heightIn": 10, "heightIn": 10
"applicant": 1 }
],
"plans": [
{
"id": 1,
"coverage": 1,
"tier": "tier_1"
} }
], ],
"plans": [{"coverage": 1}],
"phq": { "phq": {
"effectiveDate": "01/01/2024", "treatment": False,
"invalid": False,
"pregnancy": False,
"effectiveDate": "2024-01-01",
"disclaimer": True,
"signature": "John Doe",
"medications": [], "medications": [],
"conditions": [], "issues": [],
"issues": [] "conditions": []
}, },
"income": 50000, "income": 50000.0,
"address": {} "address": {
"address1": "123 Main St",
"address2": "Apt 4B",
"city": "New York",
"state": "NY",
"zipcode": "10001"
}
} }
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
@ -153,8 +175,18 @@ async def test_estimation():
if response.status_code == 200: if response.status_code == 200:
data = response.json() data = response.json()
print(f"✅ Estimation successful!") print(f"✅ Estimation successful!")
print(f"Application ID: {data['uid']}")
print(f"Status: {data['status']}") print(f"Status: {data['status']}")
print(f"Details:")
print(f" DTQ: {data['details']['dtq']}")
print(f" Tier: {data['details']['tier']}")
print(f" Total Price: ${data['details']['total_price']}")
print(f" Reason: {data['details']['reason']}")
print(f"Results:")
for i, result in enumerate(data['results'], 1):
print(f" Applicant {i}: {result['name']} ({result['applicant_type']})")
print(f" Age: {result['age']}, BMI: {result['bmi']}")
print(f" Tier: {result['tier']}, Rx Spend: ${result['rx_spend']}")
print(f" Message: {result['message']}")
else: else:
print(f"❌ Estimation failed with status {response.status_code}") print(f"❌ Estimation failed with status {response.status_code}")
print(f"Error: {response.text}") print(f"Error: {response.text}")