add more descriptable dtq/review reasons
This commit is contained in:
parent
c8e6474edd
commit
ac57ded876
1 changed files with 72 additions and 37 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue