From ac57ded876ec31b03c579e87393e4f08aa442a35 Mon Sep 17 00:00:00 2001 From: ipu Date: Tue, 9 Sep 2025 23:08:05 +0300 Subject: [PATCH] add more descriptable dtq/review reasons --- src/services/estimation_service_v2.py | 109 +++++++++++++++++--------- 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/src/services/estimation_service_v2.py b/src/services/estimation_service_v2.py index 953aba6..22829e6 100644 --- a/src/services/estimation_service_v2.py +++ b/src/services/estimation_service_v2.py @@ -1,7 +1,7 @@ from datetime import date from enum import Enum import json -from typing import Optional +from typing import Optional, Union from src.cache.drug_cache import fetch_drug_with_dosage from src.models import PHQ, Applicant, Plan, EstimationResponse, EstimationDetails, EstimationResult from src.cache.redis_cache import fetch_conditions, get_plan_by_id, search_drug @@ -315,12 +315,12 @@ class EstimationService: print(f"AI DTQ Response: {response_json}") ai_response = json.loads(response_json) dtq = not ai_response["insurable"] - reason = ai_response["reason"] + reason = "[AI] " + ai_response["reason"] return dtq, reason except Exception as e: print(f"Error in AI DTQ check: {e}") - # Fallback to not DTQ on error - return False, "" + # Fallback to DTQ on error + return True, f"Error on AI service: {e}" def check_dtq(self, phq: PHQ) -> tuple[bool, str]: for medication in phq.medications: @@ -330,26 +330,26 @@ class EstimationService: uninsurable_conditions = [condition["key"] for condition in uninsurable_conditions_response] if phq.pregnancy: - return True, "Pregnancy" + return True, "Applicant is pregnant" for issue in phq.issues: if issue.key == "surgery": for detail in issue.details: if detail.key == "not_performed": - return True, "Surgery that was not performed" + return True, "Applicant have surgery that was not performed" for condition in phq.conditions: if condition.key in uninsurable_conditions: - return True, f"Condition: {condition.key}" + return True, f"Applicant have uninsurable condition: {condition.key}" # issues would be checked by AI - return False, None + return False, "" def calculate_age(self, born): today = date.today() return today.year - born.year - ((today.month, today.day) < (born.month, born.day)) - async def calculate_rx_spend(self, phq: PHQ, applicant_id: int) -> (float, list[str]): + async def calculate_rx_spend(self, phq: PHQ, applicant_id: int) -> tuple[float, list[str]]: rx_spend = 0 review_reasons = [] for medication in phq.medications: @@ -391,7 +391,7 @@ class EstimationService: pass return rx_spend, review_reasons - def get_tier(self, coverage: int, rx_spend: float) -> Optional[Tier]: + def get_tier(self, coverage: int, rx_spend: float) -> tuple[Tier | None, str | None]: tiers = [ Tier.TIER_1, Tier.TIER_15, Tier.TIER_2, Tier.TIER_25, Tier.TIER_3, Tier.TIER_35, Tier.TIER_4, Tier.TIER_45, Tier.TIER_5, Tier.TIER_55, Tier.TIER_6, Tier.TIER_65, Tier.TIER_7 @@ -409,9 +409,9 @@ class EstimationService: for i, threshold in enumerate(rates[coverage]): if rx_spend <= threshold: - return tiers[i] + return tiers[i], None - return None + return None, f"Rx spend ({rx_spend}) is greater than {rates[coverage][-1]} for coverage {coverage}" def get_plan_coverage(self, plan: Plan): plan_data = get_plan_by_id(plan.id) @@ -447,19 +447,25 @@ class EstimationService: is_dtq, reason = self.check_dtq(phq) if not is_dtq: is_dtq, reason = await self.check_dtq_ai(phq) - if reason: + if is_dtq: dtq_reasons.append(reason) max_age = max(self.calculate_age(applicant.dob) for applicant in applicants) + base_tier = None for base_age, tier in BASE_TIERS.items(): if max_age <= base_age: base_tier = tier break - + plan_coverage = self.get_plan_coverage(plans[0]) rx_spend = 0 for applicant_id, applicant in enumerate(applicants): + applicant_review_reasons = [] + applicant_dtq_reasons = [] + applicant_accept_reasons = [] + applicant_age = self.calculate_age(applicant.dob) + applicant_tier = None for base_age, tier in BASE_TIERS.items(): if applicant_age <= base_age: applicant_tier = tier @@ -467,25 +473,32 @@ class EstimationService: if applicant_age >= 65: is_dtq = True - reason = "Age is over 65" + reason = f"Applicant age {applicant_age} is over 65" dtq_reasons.append(reason) + applicant_dtq_reasons.append(reason) if (applicant.heightFt, applicant.heightIn) in HEIGHT_WEIGHT_TABLE: w1, w2, w3, w4 = HEIGHT_WEIGHT_TABLE[(applicant.heightFt, applicant.heightIn)] if applicant.weight < w1: is_dtq = True - reason = "Declined due to low BMI of one or more applicants" + reason = f"Applicant weight {applicant.weight}lbs is less than {w1}, at height {applicant.heightFt}' {applicant.heightIn}''" dtq_reasons.append(reason) + applicant_dtq_reasons.append(reason) elif w1 <= applicant.weight < w2: is_review = True - review_reasons.append("low BMI of one or more applicants") + weight_reason = f"Applicant weight {applicant.weight}lbs is less than {w2}, at height {applicant.heightFt}' {applicant.heightIn}''" + review_reasons.append(weight_reason) + applicant_review_reasons.append(weight_reason) elif w3 <= applicant.weight < w4: is_review = True - review_reasons.append("high BMI of one or more applicants") + weight_reason = f"Applicant weight {applicant.weight}lbs is greater than {w3-1}, at height {applicant.heightFt}' {applicant.heightIn}''" + review_reasons.append(weight_reason) + applicant_review_reasons.append(weight_reason) elif applicant.weight >= w4: is_dtq = True - reason = "Declined due to high BMI of one or more applicants" + reason = f"Applicant weight {applicant.weight}lbs is greater than {w4-1}, at height {applicant.heightFt}' {applicant.heightIn}''" dtq_reasons.append(reason) + applicant_dtq_reasons.append(reason) rx_spend_applicant, rx_review_reasons = await self.calculate_rx_spend(phq, applicant_id) if rx_review_reasons: @@ -494,17 +507,26 @@ class EstimationService: rx_spend += rx_spend_applicant - applicant_new_tier = self.get_tier(plan_coverage, rx_spend_applicant) + applicant_new_tier, tier_reason = self.get_tier(plan_coverage, rx_spend_applicant) if applicant_new_tier is None: is_dtq = True - reason = "Declined due to high Rx spend" - dtq_reasons.append(reason) + dtq_reasons.append(tier_reason) + applicant_dtq_reasons.append(tier_reason) if applicant_new_tier and applicant_new_tier > applicant_tier: applicant_tier = applicant_new_tier reason = f"Rx spend increased tier to {applicant_new_tier}." accept_reasons.append(reason) + applicant_accept_reasons.append(reason) + if applicant_dtq_reasons: + final_reason = "\n".join(applicant_dtq_reasons) + elif applicant_review_reasons: + final_reason = "\n".join(applicant_review_reasons) + elif applicant_accept_reasons: + final_reason = "\n".join(applicant_accept_reasons) + else: + final_reason = f"Tier {applicant_tier.value} assigned with Rx spend within allowed limits." estimation_results.append( EstimationResult( name=applicant.firstName, @@ -513,7 +535,7 @@ class EstimationService: bmi=self.calculate_bmi(applicant.weight, applicant.heightFt, applicant.heightIn), tier=applicant_tier.value, rx_spend=rx_spend_applicant, - message=reason if reason else f"Tier {applicant_tier.value} assigned with Rx spend within allowed limits." + message=final_reason, ) ) @@ -543,14 +565,16 @@ class EstimationService: results=estimation_results ) - new_tier = self.get_tier(plan_coverage, rx_spend) + new_tier, tier_reason = self.get_tier(plan_coverage, rx_spend) if new_tier is None: + dtq_reasons.append(tier_reason) + reason = "\n".join(dtq_reasons) return EstimationResponse( status="rejected", details=EstimationDetails( dtq=True, - reason="Declined due to high Rx spend", + reason=reason, price_id=plan_price_id, ), results=estimation_results @@ -560,16 +584,27 @@ class EstimationService: base_tier = new_tier plan_price_id = self.get_plan_price(plans[0], base_tier, plan_coverage) - - status = "accepted" if base_tier is not None else "rejected" - - return EstimationResponse( - status=status, - details=EstimationDetails( - dtq=is_dtq, - reason=reason, - price_id=plan_price_id, - ), - results=estimation_results - ) + + if base_tier is not None: + reason = "\n".join(accept_reasons) + return EstimationResponse( + status="accepted", + details=EstimationDetails( + dtq=is_dtq, + reason=reason, + price_id=plan_price_id, + ), + results=estimation_results + ) + else: + reason = "\n".join(dtq_reasons) + return EstimationResponse( + status="rejected", + details=EstimationDetails( + dtq=is_dtq, + reason=reason, + price_id=plan_price_id, + ), + results=estimation_results + ) \ No newline at end of file