diff --git a/README.md b/README.md index 23ee4c6..fa72240 100644 --- a/README.md +++ b/README.md @@ -198,47 +198,95 @@ Handles insurance estimation and underwriting requests. **Request Body:** ```json { - "uid": "application-id", + "uid": "string", "applicants": [ { - "firstName": "John", - "dob": "15/03/1985", - "weight": 70, - "heightFt": 5, - "heightIn": 10, - "applicant": 1 + "applicant": 1, + "firstName": "string", + "lastName": "string", + "midName": "string", + "phone": "string", + "gender": "male", + "dob": "2025-07-25", + "nicotine": true, + "weight": 0, + "heightFt": 0, + "heightIn": 0 } ], "plans": [ { - "coverage": 1 + "id": 0, + "coverage": 1, + "tier": "string" } ], "phq": { - "effectiveDate": "01/01/2024", - "medications": [], - "conditions": [], - "issues": [] + "treatment": true, + "invalid": true, + "pregnancy": true, + "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, - "address": {} + "income": 0, + "address": { + "address1": "string", + "address2": "string", + "city": "string", + "state": "string", + "zipcode": "string" + } } ``` **Response:** ```json { - "uid": "application-id", - "status": "submitted", - "data": { - "results": [...], - "combined": { - "tier": 2.0, - "total_price": 243, - "dtq": false, - "message": "Final assigned tier is 2.0" - } + "status": "accepted", + "details": { + "dtq": true, + "reason": "string", + "tier": 0, + "total_price": 0 }, - "external": {...} + "results": [ + { + "name": "string", + "applicant_type": "Primary", + "age": 0, + "bmi": 0, + "tier": 0, + "rx_spend": 0, + "message": "string" + } + ] } ``` \ No newline at end of file diff --git a/src/api/v1/models.py b/src/api/v1/models.py index 1650a5e..f1be272 100644 --- a/src/api/v1/models.py +++ b/src/api/v1/models.py @@ -1,5 +1,6 @@ from pydantic import BaseModel, Field from typing import Optional, List, Dict, Any +from datetime import date class InsuranceChatRequest(BaseModel): message: str = Field(..., description="User message") @@ -20,19 +21,90 @@ class InsuranceChatResponse(BaseModel): sources: List[Source] = [] 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): - uid: str = Field(..., description="Application unique identifier") - applicants: List[Dict[str, Any]] = Field(..., description="List of applicants") - plans: List[Dict[str, Any]] = Field(..., description="List of insurance plans") - phq: Dict[str, Any] = Field(..., description="Personal Health Questionnaire data") - income: Optional[float] = Field(0, description="Annual income") - address: Optional[Dict[str, Any]] = Field({}, description="Address information") + uid: str + applicants: List[Applicant] + plans: List[Plan] + phq: PHQ + income: float + 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): - uid: str status: str - data: Dict[str, Any] - external: Optional[Dict[str, Any]] = None + details: EstimationDetails + results: List[EstimationResult] class SessionCreateResponse(BaseModel): session_id: str diff --git a/src/api/v1/router.py b/src/api/v1/router.py index 7cbcad3..0557d08 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -39,41 +39,191 @@ async def estimate(request: models.EstimationRequest): detail="Missing required applicants or plans" ) - # Step 1: Run estimation - underwriting_result = run_underwriting(request.applicants, request.phq, request.plans) + # Convert request to the format expected by run_underwriting + 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"): + # For DTQ cases, call external reject API and return rejected status reject_response = await reject_application(request.uid) return models.EstimationResponse( - uid=request.uid, status="rejected", - data=underwriting_result, - external=reject_response + details=models.EstimationDetails( + 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"] plans = request.plans.copy() if plans: - plans[0]["tier"] = f"tier_{str(final_tier).replace('.', '_')}" + plans[0].tier = f"tier_{str(final_tier).replace('.', '_')}" # Assemble external payload submission_payload = { - "applicants": request.applicants, - "plans": plans, - "phq": request.phq, + "applicants": [ + { + "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, - "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) return models.EstimationResponse( - uid=request.uid, - status="submitted", - data=underwriting_result, - external=submit_response + status="accepted", + details=models.EstimationDetails( + dtq=False, + 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: @@ -101,4 +251,7 @@ async def submit_application(application_payload: Dict[str, Any]) -> Dict[str, A f"{settings.INSURANCE_API_BASE_URL}/applications/submit", json=application_payload ) - return response.json() if response.status_code == 200 else {"error": "Failed to submit application"} \ No newline at end of file + return response.json() if response.status_code == 200 else {"error": "Failed to submit application"} + + + \ No newline at end of file diff --git a/src/examples/example_usage.py b/src/examples/example_usage.py index 16d2ab8..27ed34e 100644 --- a/src/examples/example_usage.py +++ b/src/examples/example_usage.py @@ -128,23 +128,45 @@ async def test_estimation(): "uid": "test-application-123", "applicants": [ { + "applicant": 1, "firstName": "John", - "dob": "15/03/1985", - "weight": 70, + "lastName": "Doe", + "midName": "M", + "phone": "555-123-4567", + "gender": "male", + "dob": "1985-03-15", + "nicotine": False, + "weight": 70.0, "heightFt": 5, - "heightIn": 10, - "applicant": 1 + "heightIn": 10 + } + ], + "plans": [ + { + "id": 1, + "coverage": 1, + "tier": "tier_1" } ], - "plans": [{"coverage": 1}], "phq": { - "effectiveDate": "01/01/2024", + "treatment": False, + "invalid": False, + "pregnancy": False, + "effectiveDate": "2024-01-01", + "disclaimer": True, + "signature": "John Doe", "medications": [], - "conditions": [], - "issues": [] + "issues": [], + "conditions": [] }, - "income": 50000, - "address": {} + "income": 50000.0, + "address": { + "address1": "123 Main St", + "address2": "Apt 4B", + "city": "New York", + "state": "NY", + "zipcode": "10001" + } } async with httpx.AsyncClient() as client: @@ -153,8 +175,18 @@ async def test_estimation(): if response.status_code == 200: data = response.json() print(f"✅ Estimation successful!") - print(f"Application ID: {data['uid']}") 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: print(f"❌ Estimation failed with status {response.status_code}") print(f"Error: {response.text}")