import re def parse_plan_terms(text: str) -> dict: """Extract common plan numeric terms from SBC text. Returns a dict with keys like 'overall_deductible_network_individual', 'out_of_pocket_limit_network_individual', 'pcp_copay', 'specialist_copay', 'urgent_copay', 'hospital_coinsurance', 'other_coinsurance'. Values are numbers. """ terms = {} # overall deductible (network) individual m = re.search(r"For network providers\s*\$\s?([0-9,]+)\s*individual", text, re.I) if m: terms['overall_deductible_network_individual'] = float(m.group(1).replace(',', '')) else: m2 = re.search(r"deductible[^\$]{0,60}\$\s?([0-9,]+)", text, re.I) if m2: terms['overall_deductible_network_individual'] = float(m2.group(1).replace(',', '')) # out-of-pocket limit m = re.search(r"For network providers\s*\$\s?([0-9,]+)\s*individual\s*/\s*\$\s?([0-9,]+)\s*family", text, re.I) if m: terms['out_of_pocket_limit_network_individual'] = float(m.group(1).replace(',', '')) else: m2 = re.search(r"out-of-pocket limit[\s\S]{0,80}\$\s?([0-9,]+)\s*individual", text, re.I) if m2: terms['out_of_pocket_limit_network_individual'] = float(m2.group(1).replace(',', '')) # copays m = re.search(r"Primary care visit[\s\S]{0,80}\$\s?([0-9,]+)", text, re.I) if m: terms['pcp_copay'] = float(m.group(1).replace(',', '')) m = re.search(r"Specialist\s*Visit[\s\S]{0,80}\$\s?([0-9,]+)", text, re.I) if m: terms['specialist_copay'] = float(m.group(1).replace(',', '')) m = re.search(r"Urgent care[\s\S]{0,80}\$\s?([0-9,]+)", text, re.I) if m: terms['urgent_copay'] = float(m.group(1).replace(',', '')) # coinsurance selection by nearby context for mm in re.finditer(r"([0-9]{1,3})%\s*(?:\n|\s)*Coinsurance", text, re.I): pct = float(mm.group(1)) / 100.0 head = text[max(0, mm.start()-80):mm.start()].lower() if any(k in head for k in ('hospital', 'facility', 'hospital (facility)', 'facility fee')): terms['hospital_coinsurance'] = pct break if 'hospital_coinsurance' not in terms: for mm in re.finditer(r"([0-9]{1,3})%\s*(?:\n|\s)*Coinsurance", text, re.I): pct = float(mm.group(1)) / 100.0 head = text[max(0, mm.start()-80):mm.start()].lower() if 'other' in head: terms['other_coinsurance'] = pct break # fallback: any coinsurance if 'hospital_coinsurance' not in terms and 'other_coinsurance' not in terms: m = re.search(r"([0-9]{1,3})%\s*Coinsurance", text, re.I) if m: terms['other_coinsurance'] = float(m.group(1)) / 100.0 return terms def estimate_member_payment(bill_amount: float, service_type: str, network: str, plan: dict) -> str: """Estimate member payment for a single service given plan terms. Simplified rules: - Member pays deductible first up to overall deductible - After deductible, coinsurance applies to remaining amount - Cap at out-of-pocket limit if available """ ded = plan.get('overall_deductible_network_individual', 0.0) oop = plan.get('out_of_pocket_limit_network_individual', None) if service_type == 'hospital': coin = plan.get('hospital_coinsurance', plan.get('other_coinsurance', 0.0)) else: coin = plan.get('other_coinsurance', 0.0) # member pays up to deductible first member_ded = min(ded, bill_amount) remaining = max(0.0, bill_amount - member_ded) member_after_ded = coin * remaining member_total = member_ded + member_after_ded if oop is not None: member_total_capped = min(member_total, oop) else: member_total_capped = member_total return f"Estimate for ${bill_amount:,.0f} {('in-network' if network=='network' else '')} {service_type} bill: member pays ${member_total_capped:,.2f} (raw calc ${member_total:,.2f})"