add human estimation conditions

This commit is contained in:
ipu 2025-09-08 18:47:01 +03:00
parent 735ccb757c
commit c8e6474edd
3 changed files with 103 additions and 57 deletions

View file

@ -135,5 +135,5 @@ async def fetch_drug_with_dosage(drug_name: str, dosage: float) -> DrugFull | No
if drug_full:
return drug_full
raise Exception(f"Drug {drug_name} with dosage {dosage} not found")
return None

View file

@ -102,7 +102,7 @@ def fetch_drug(drug_name: str) -> DrugPriceResponse:
return result
def search_drug(drug_name: str) -> str:
def search_drug(drug_name: str) -> str | None:
cache_key = f"drug_search:{drug_name}"
redis_client = get_redis_client()
@ -113,15 +113,18 @@ def search_drug(drug_name: str) -> str:
except Exception:
pass
client = httpx.Client(
base_url="https://www.drugs.com/api/autocomplete",
# headers=headers,
timeout=httpx.Timeout(60.0, connect=10.0)
)
response = client.get(f"/?type=price-guide&s={drug_name}")
response_json = response.json()
try:
client = httpx.Client(
base_url="https://www.drugs.com/api/autocomplete",
# headers=headers,
timeout=httpx.Timeout(60.0, connect=10.0)
)
response = client.get(f"/?type=price-guide&s={drug_name}")
response_json = response.json()
result = response_json["categories"][0]["results"][0]["url"].replace("/price-guide/", "")
result = response_json["categories"][0]["results"][0]["url"].replace("/price-guide/", "")
except Exception:
return None
try:
redis_client.setex(cache_key, settings.REDIS_CACHE_TTL_DRUGS, result)

View file

@ -55,47 +55,48 @@ BASE_TIERS = {
}
HEIGHT_WEIGHT_TABLE = {
(4, 2): (60, 124),
(4, 3): (63, 129),
(4, 4): (65, 135),
(4, 5): (68, 140),
(4, 6): (71, 145),
(4, 7): (73, 151),
(4, 8): (76, 156),
(4, 9): (79, 162),
(4, 10): (81, 167),
(4, 11): (84, 173),
(5, 0): (87, 179),
(5, 1): (90, 185),
(5, 2): (93, 191),
(5, 3): (96, 197),
(5, 4): (99, 204),
(5, 5): (102, 210),
(5, 6): (105, 217),
(5, 7): (109, 223),
(5, 8): (112, 230),
(5, 9): (115, 237),
(5, 10): (118, 244),
(5, 11): (122, 251),
(6, 0): (125, 258),
(6, 1): (129, 265),
(6, 2): (132, 273),
(6, 3): (136, 280),
(6, 4): (140, 288),
(6, 5): (143, 295),
(6, 6): (147, 303),
(6, 7): (151, 311),
(6, 8): (155, 319),
(6, 9): (159, 327),
(6, 10): (163, 335),
(6, 11): (167, 343),
(7, 0): (171, 351),
(7, 1): (175, 360),
(7, 2): (179, 368),
(7, 3): (183, 377),
(7, 4): (187, 386),
(4, 2): (54, 60, 125, 151),
(4, 3): (56, 63, 130, 156),
(4, 4): (59, 65, 136, 162),
(4, 5): (60, 68, 141, 169),
(4, 6): (63, 71, 146, 175),
(4, 7): (65, 73, 152, 182),
(4, 8): (67, 76, 157, 188),
(4, 9): (70, 79, 163, 195),
(4, 10): (72, 81, 168, 202),
(4, 11): (75, 84, 174, 209),
(5, 0): (77, 87, 180, 216),
(5, 1): (80, 90, 186, 223),
(5, 2): (83, 93, 192, 230),
(5, 3): (85, 96, 199, 238),
(5, 4): (88, 99, 205, 245),
(5, 5): (91, 102, 211, 253),
(5, 6): (93, 105, 218, 261),
(5, 7): (96, 109, 224, 269),
(5, 8): (99, 112, 231, 277),
(5, 9): (102, 115, 238, 285),
(5, 10): (105, 118, 245, 293),
(5, 11): (108, 122, 252, 302),
(6, 0): (111, 125, 259, 310),
(6, 1): (114, 129, 266, 319),
(6, 2): (117, 132, 274, 327),
(6, 3): (121, 136, 281, 336),
(6, 4): (124, 140, 289, 345),
(6, 5): (127, 143, 296, 354),
(6, 6): (130, 147, 304, 364),
(6, 7): (134, 151, 312, 374),
(6, 8): (137, 155, 320, 383),
(6, 9): (140, 159, 328, 393),
(6, 10): (144, 163, 336, 403),
(6, 11): (147, 167, 344, 413),
(7, 0): (151, 171, 352, 423),
(7, 1): (155, 175, 361, 433),
(7, 2): (158, 179, 369, 443),
(7, 3): (162, 183, 378, 453),
(7, 4): (166, 187, 387, 464),
}
UNINSURABLE_MEDICATIONS = [
"abacavir",
"abarelix",
@ -348,17 +349,23 @@ class EstimationService:
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:
async def calculate_rx_spend(self, phq: PHQ, applicant_id: int) -> (float, list[str]):
rx_spend = 0
review_reasons = []
for medication in phq.medications:
if medication.applicant != applicant_id:
continue
try:
drug_name = medication.name.lower()
drug_url = search_drug(drug_name)
if not drug_url:
review_reasons.append(f"Drug not found: {drug_name}")
continue
drug_dosage = float(medication.dosage)
print(f"{drug_name} | {drug_url} | {drug_dosage}")
drug_price = await fetch_drug_with_dosage(drug_url, drug_dosage)
if not drug_price:
review_reasons.append(f"Dosage {drug_dosage} for drug {drug_name} not found")
continue
if medication.frequency in ["Once daily", "At bedtime"]:
month_times = 30
@ -375,13 +382,14 @@ class EstimationService:
elif medication.frequency == "Every other day":
month_times = 15
else:
review_reasons.append(f"Unclear frequency of drug {medication.name}: {medication.frequency}")
month_times = 1
rx_spend += drug_price.unit_price * month_times
except Exception as e:
print(f"Error calculating rx spend for {medication.name}: {e}")
pass
return rx_spend
return rx_spend, review_reasons
def get_tier(self, coverage: int, rx_spend: float) -> Optional[Tier]:
tiers = [
@ -431,9 +439,16 @@ class EstimationService:
async def estimate_insurance(self, applicants: list[Applicant], phq: PHQ, plans: list[Plan]):
estimation_results = []
is_review = False
review_reasons = []
dtq_reasons = []
accept_reasons = []
is_dtq, reason = self.check_dtq(phq)
if not is_dtq:
is_dtq, reason = await self.check_dtq_ai(phq)
if reason:
dtq_reasons.append(reason)
max_age = max(self.calculate_age(applicant.dob) for applicant in applicants)
for base_age, tier in BASE_TIERS.items():
@ -453,27 +468,42 @@ class EstimationService:
if applicant_age >= 65:
is_dtq = True
reason = "Age is over 65"
dtq_reasons.append(reason)
if (applicant.heightFt, applicant.heightIn) in HEIGHT_WEIGHT_TABLE:
weight_min, weight_max = HEIGHT_WEIGHT_TABLE[(applicant.heightFt, applicant.heightIn)]
if applicant.weight < weight_min:
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"
elif applicant.weight > weight_max:
dtq_reasons.append(reason)
elif w1 <= applicant.weight < w2:
is_review = True
review_reasons.append("low BMI of one or more applicants")
elif w3 <= applicant.weight < w4:
is_review = True
review_reasons.append("high BMI of one or more applicants")
elif applicant.weight >= w4:
is_dtq = True
reason = "Declined due to high BMI of one or more applicants"
dtq_reasons.append(reason)
rx_spend_applicant, rx_review_reasons = await self.calculate_rx_spend(phq, applicant_id)
if rx_review_reasons:
is_review = True
review_reasons += rx_review_reasons
rx_spend_applicant = await self.calculate_rx_spend(phq, applicant_id)
rx_spend += rx_spend_applicant
applicant_new_tier = 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)
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)
estimation_results.append(
EstimationResult(
@ -490,6 +520,7 @@ class EstimationService:
plan_price_id = self.get_plan_price(plans[0], base_tier, plan_coverage)
if is_dtq:
reason = "\n".join(dtq_reasons)
return EstimationResponse(
status="rejected",
details=EstimationDetails(
@ -500,6 +531,18 @@ class EstimationService:
results=estimation_results
)
if is_review:
reason = "\n".join(review_reasons)
return EstimationResponse(
status="human_review",
details=EstimationDetails(
dtq=is_dtq,
reason=reason,
price_id=plan_price_id,
),
results=estimation_results
)
new_tier = self.get_tier(plan_coverage, rx_spend)
if new_tier is None: