add more descriptable dtq/review reasons

This commit is contained in:
ipu 2025-09-09 23:08:05 +03:00
parent c8e6474edd
commit ac57ded876

View file

@ -1,7 +1,7 @@
from datetime import date from datetime import date
from enum import Enum from enum import Enum
import json import json
from typing import Optional from typing import Optional, Union
from src.cache.drug_cache import fetch_drug_with_dosage from src.cache.drug_cache import fetch_drug_with_dosage
from src.models import PHQ, Applicant, Plan, EstimationResponse, EstimationDetails, EstimationResult from src.models import PHQ, Applicant, Plan, EstimationResponse, EstimationDetails, EstimationResult
from src.cache.redis_cache import fetch_conditions, get_plan_by_id, search_drug 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}") print(f"AI DTQ Response: {response_json}")
ai_response = json.loads(response_json) ai_response = json.loads(response_json)
dtq = not ai_response["insurable"] dtq = not ai_response["insurable"]
reason = ai_response["reason"] reason = "[AI] " + ai_response["reason"]
return dtq, reason return dtq, reason
except Exception as e: except Exception as e:
print(f"Error in AI DTQ check: {e}") print(f"Error in AI DTQ check: {e}")
# Fallback to not DTQ on error # Fallback to DTQ on error
return False, "" return True, f"Error on AI service: {e}"
def check_dtq(self, phq: PHQ) -> tuple[bool, str]: def check_dtq(self, phq: PHQ) -> tuple[bool, str]:
for medication in phq.medications: for medication in phq.medications:
@ -330,26 +330,26 @@ class EstimationService:
uninsurable_conditions = [condition["key"] for condition in uninsurable_conditions_response] uninsurable_conditions = [condition["key"] for condition in uninsurable_conditions_response]
if phq.pregnancy: if phq.pregnancy:
return True, "Pregnancy" return True, "Applicant is pregnant"
for issue in phq.issues: for issue in phq.issues:
if issue.key == "surgery": if issue.key == "surgery":
for detail in issue.details: for detail in issue.details:
if detail.key == "not_performed": 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: for condition in phq.conditions:
if condition.key in uninsurable_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 # issues would be checked by AI
return False, None return False, ""
def calculate_age(self, born): def calculate_age(self, born):
today = date.today() today = date.today()
return today.year - born.year - ((today.month, today.day) < (born.month, born.day)) 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 rx_spend = 0
review_reasons = [] review_reasons = []
for medication in phq.medications: for medication in phq.medications:
@ -391,7 +391,7 @@ class EstimationService:
pass pass
return rx_spend, review_reasons 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 = [ tiers = [
Tier.TIER_1, Tier.TIER_15, Tier.TIER_2, Tier.TIER_25, Tier.TIER_3, Tier.TIER_35, 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 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]): for i, threshold in enumerate(rates[coverage]):
if rx_spend <= threshold: 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): def get_plan_coverage(self, plan: Plan):
plan_data = get_plan_by_id(plan.id) plan_data = get_plan_by_id(plan.id)
@ -447,10 +447,11 @@ class EstimationService:
is_dtq, reason = self.check_dtq(phq) is_dtq, reason = self.check_dtq(phq)
if not is_dtq: if not is_dtq:
is_dtq, reason = await self.check_dtq_ai(phq) is_dtq, reason = await self.check_dtq_ai(phq)
if reason: if is_dtq:
dtq_reasons.append(reason) dtq_reasons.append(reason)
max_age = max(self.calculate_age(applicant.dob) for applicant in applicants) max_age = max(self.calculate_age(applicant.dob) for applicant in applicants)
base_tier = None
for base_age, tier in BASE_TIERS.items(): for base_age, tier in BASE_TIERS.items():
if max_age <= base_age: if max_age <= base_age:
base_tier = tier base_tier = tier
@ -459,7 +460,12 @@ class EstimationService:
plan_coverage = self.get_plan_coverage(plans[0]) plan_coverage = self.get_plan_coverage(plans[0])
rx_spend = 0 rx_spend = 0
for applicant_id, applicant in enumerate(applicants): for applicant_id, applicant in enumerate(applicants):
applicant_review_reasons = []
applicant_dtq_reasons = []
applicant_accept_reasons = []
applicant_age = self.calculate_age(applicant.dob) applicant_age = self.calculate_age(applicant.dob)
applicant_tier = None
for base_age, tier in BASE_TIERS.items(): for base_age, tier in BASE_TIERS.items():
if applicant_age <= base_age: if applicant_age <= base_age:
applicant_tier = tier applicant_tier = tier
@ -467,25 +473,32 @@ class EstimationService:
if applicant_age >= 65: if applicant_age >= 65:
is_dtq = True is_dtq = True
reason = "Age is over 65" reason = f"Applicant age {applicant_age} is over 65"
dtq_reasons.append(reason) dtq_reasons.append(reason)
applicant_dtq_reasons.append(reason)
if (applicant.heightFt, applicant.heightIn) in HEIGHT_WEIGHT_TABLE: if (applicant.heightFt, applicant.heightIn) in HEIGHT_WEIGHT_TABLE:
w1, w2, w3, w4 = HEIGHT_WEIGHT_TABLE[(applicant.heightFt, applicant.heightIn)] w1, w2, w3, w4 = HEIGHT_WEIGHT_TABLE[(applicant.heightFt, applicant.heightIn)]
if applicant.weight < w1: if applicant.weight < w1:
is_dtq = True 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) dtq_reasons.append(reason)
applicant_dtq_reasons.append(reason)
elif w1 <= applicant.weight < w2: elif w1 <= applicant.weight < w2:
is_review = True 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: elif w3 <= applicant.weight < w4:
is_review = True 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: elif applicant.weight >= w4:
is_dtq = True 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) dtq_reasons.append(reason)
applicant_dtq_reasons.append(reason)
rx_spend_applicant, rx_review_reasons = await self.calculate_rx_spend(phq, applicant_id) rx_spend_applicant, rx_review_reasons = await self.calculate_rx_spend(phq, applicant_id)
if rx_review_reasons: if rx_review_reasons:
@ -494,17 +507,26 @@ class EstimationService:
rx_spend += rx_spend_applicant 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: if applicant_new_tier is None:
is_dtq = True is_dtq = True
reason = "Declined due to high Rx spend" dtq_reasons.append(tier_reason)
dtq_reasons.append(reason) applicant_dtq_reasons.append(tier_reason)
if applicant_new_tier and applicant_new_tier > applicant_tier: if applicant_new_tier and applicant_new_tier > applicant_tier:
applicant_tier = applicant_new_tier applicant_tier = applicant_new_tier
reason = f"Rx spend increased tier to {applicant_new_tier}." reason = f"Rx spend increased tier to {applicant_new_tier}."
accept_reasons.append(reason) 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( estimation_results.append(
EstimationResult( EstimationResult(
name=applicant.firstName, name=applicant.firstName,
@ -513,7 +535,7 @@ class EstimationService:
bmi=self.calculate_bmi(applicant.weight, applicant.heightFt, applicant.heightIn), bmi=self.calculate_bmi(applicant.weight, applicant.heightFt, applicant.heightIn),
tier=applicant_tier.value, tier=applicant_tier.value,
rx_spend=rx_spend_applicant, 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 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: if new_tier is None:
dtq_reasons.append(tier_reason)
reason = "\n".join(dtq_reasons)
return EstimationResponse( return EstimationResponse(
status="rejected", status="rejected",
details=EstimationDetails( details=EstimationDetails(
dtq=True, dtq=True,
reason="Declined due to high Rx spend", reason=reason,
price_id=plan_price_id, price_id=plan_price_id,
), ),
results=estimation_results results=estimation_results
@ -561,10 +585,21 @@ class EstimationService:
plan_price_id = self.get_plan_price(plans[0], base_tier, plan_coverage) plan_price_id = self.get_plan_price(plans[0], base_tier, plan_coverage)
status = "accepted" if base_tier is not None else "rejected" if base_tier is not None:
reason = "\n".join(accept_reasons)
return EstimationResponse( return EstimationResponse(
status=status, 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( details=EstimationDetails(
dtq=is_dtq, dtq=is_dtq,
reason=reason, reason=reason,