estimation service refactor
This commit is contained in:
parent
aaba8753ef
commit
18c95083c9
4 changed files with 367 additions and 62 deletions
98
README.md
98
README.md
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
@ -101,4 +251,7 @@ async def submit_application(application_payload: Dict[str, Any]) -> Dict[str, A
|
||||||
f"{settings.INSURANCE_API_BASE_URL}/applications/submit",
|
f"{settings.INSURANCE_API_BASE_URL}/applications/submit",
|
||||||
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"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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}")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue