secureshield-backend / tools /explanation_tools.py
Sharma7's picture
feat: SecureShield Agentic Insurance Engine — full prototype
0fe30af
Raw
History Blame Contribute Delete
12.5 kB
"""
Explanation Tools — Custom tools for the Explanation Agent.
Tools:
9. what_if_analyzer — Run decision engine with modified parameters
10. savings_calculator — Calculate savings from alternative choices
11. clause_explainer — Explain policy clauses in plain language
"""
import logging
import copy
from engine.decision_engine import evaluate
from models.case import CaseFacts, RoomType
from models.verdict import Verdict
from tools.case_tools import hospital_cost_estimator
logger = logging.getLogger(__name__)
# --- Tool 9: What-If Analyzer ---
_ROOM_DOWNGRADE_MAP = {
"suite": "deluxe",
"deluxe": "single_ac",
"single_ac": "private",
"private": "semi_private",
"semi_private": "general",
"icu": "icu", # Can't downgrade ICU
"general": "general", # Already lowest
}
# Approximate room costs per tier for what-if estimation
_ROOM_COST_ESTIMATES = {
"general": {"tier_1": 1500, "tier_2": 800, "tier_3": 500},
"semi_private": {"tier_1": 4000, "tier_2": 2500, "tier_3": 1500},
"private": {"tier_1": 8000, "tier_2": 5000, "tier_3": 3000},
"single_ac": {"tier_1": 10000, "tier_2": 6000, "tier_3": 4000},
"deluxe": {"tier_1": 18000, "tier_2": 12000, "tier_3": 8000},
"suite": {"tier_1": 30000, "tier_2": 20000, "tier_3": 15000},
"icu": {"tier_1": 25000, "tier_2": 15000, "tier_3": 8000},
}
def what_if_analyzer(
rules: list[dict],
original_facts: dict,
sum_insured: float,
modification: dict,
) -> dict:
"""
Run decision engine with modified case parameters and compare results.
Args:
rules: Policy rules
original_facts: Original case facts as dict
sum_insured: Policy sum insured
modification: Dict of field → new value changes to apply
Returns:
{
"original_verdict": {eligible, denied, coverage},
"modified_verdict": {eligible, denied, coverage},
"savings": float (positive = patient saves money),
"changes_applied": dict,
"recommendation": str
}
"""
# Run original
original_case = CaseFacts(**original_facts)
original_verdict = evaluate(rules, original_case, sum_insured)
# Apply modifications
modified_data = copy.deepcopy(original_facts)
for field, value in modification.items():
if field in modified_data:
modified_data[field] = value
# Recalculate total if room cost or stay changed
if "room_cost_per_day" in modification or "stay_duration_days" in modification:
room_cost = float(modified_data.get("room_cost_per_day", 0))
stay = int(modified_data.get("stay_duration_days", 1))
proc_cost = float(modified_data.get("procedure_cost", 0) or 0)
modified_data["total_claimed_amount"] = (room_cost * stay) + proc_cost
modified_case = CaseFacts(**modified_data)
modified_verdict = evaluate(rules, modified_case, sum_insured)
savings = modified_verdict.total_eligible - original_verdict.total_eligible
savings_out_of_pocket = original_verdict.total_denied - modified_verdict.total_denied
# Generate recommendation
if savings_out_of_pocket > 0:
recommendation = (
f"This change would reduce your out-of-pocket expense by ₹{savings_out_of_pocket:,.0f}. "
f"Coverage improves from {original_verdict.coverage_percentage}% to {modified_verdict.coverage_percentage}%."
)
elif savings_out_of_pocket == 0:
recommendation = "This change would not affect your out-of-pocket expenses."
else:
recommendation = (
f"This change would increase your out-of-pocket expense by ₹{abs(savings_out_of_pocket):,.0f}."
)
logger.info(f"[Tool:what_if_analyzer] Original: {original_verdict.coverage_percentage}% → "
f"Modified: {modified_verdict.coverage_percentage}%, Savings: ₹{savings_out_of_pocket:,.0f}")
return {
"original_verdict": {
"eligible": original_verdict.total_eligible,
"denied": original_verdict.total_denied,
"coverage_pct": original_verdict.coverage_percentage,
},
"modified_verdict": {
"eligible": modified_verdict.total_eligible,
"denied": modified_verdict.total_denied,
"coverage_pct": modified_verdict.coverage_percentage,
},
"savings_out_of_pocket": savings_out_of_pocket,
"changes_applied": modification,
"recommendation": recommendation,
}
# --- Tool 10: Savings Calculator ---
def savings_calculator(
rules: list[dict],
original_facts: dict,
sum_insured: float,
) -> dict:
"""
Automatically calculate potential savings by exploring common alternatives:
1. Room downgrade (one level down)
2. Switch to network hospital
3. Both combined
Args:
rules: Policy rules
original_facts: Original case facts
sum_insured: Policy sum insured
Returns:
{
"alternatives": [
{"change": str, "savings": float, "new_coverage": float, "details": str}
],
"max_possible_savings": float,
"best_alternative": str
}
"""
original_case = CaseFacts(**original_facts)
original_verdict = evaluate(rules, original_case, sum_insured)
alternatives = []
# Alternative 1: Room downgrade
current_room = original_facts.get("room_type", "single_ac")
lower_room = _ROOM_DOWNGRADE_MAP.get(current_room, current_room)
if lower_room != current_room:
city_tier = original_facts.get("city_tier", "tier_1")
new_room_cost = _ROOM_COST_ESTIMATES.get(lower_room, {}).get(city_tier, original_facts.get("room_cost_per_day", 0))
room_result = what_if_analyzer(
rules, original_facts, sum_insured,
{
"room_type": lower_room,
"room_cost_per_day": new_room_cost,
}
)
if room_result["savings_out_of_pocket"] > 0:
alternatives.append({
"change": f"Switch from {current_room.replace('_', ' ')} to {lower_room.replace('_', ' ')}",
"savings": room_result["savings_out_of_pocket"],
"new_coverage": room_result["modified_verdict"]["coverage_pct"],
"new_room_cost_per_day": new_room_cost,
"details": room_result["recommendation"],
})
# Alternative 2: Reduce stay by 1 day (if applicable)
stay = int(original_facts.get("stay_duration_days", 1))
if stay > 2:
stay_result = what_if_analyzer(
rules, original_facts, sum_insured,
{"stay_duration_days": stay - 1}
)
if stay_result["savings_out_of_pocket"] > 0:
alternatives.append({
"change": f"Reduce stay from {stay} to {stay - 1} days (if medically possible)",
"savings": stay_result["savings_out_of_pocket"],
"new_coverage": stay_result["modified_verdict"]["coverage_pct"],
"details": stay_result["recommendation"],
})
# Sort by savings
alternatives.sort(key=lambda x: x["savings"], reverse=True)
max_savings = alternatives[0]["savings"] if alternatives else 0
best = alternatives[0]["change"] if alternatives else "No cost-saving alternatives found"
logger.info(f"[Tool:savings_calculator] Found {len(alternatives)} alternatives, max savings: ₹{max_savings:,.0f}")
return {
"alternatives": alternatives,
"max_possible_savings": max_savings,
"best_alternative": best,
"original_out_of_pocket": original_verdict.total_denied,
}
# --- Tool 11: Clause Explainer ---
_CATEGORY_EXPLANATIONS = {
"room_rent": {
"simple": "This rule limits how much the insurance will pay for your hospital room per day.",
"example": "If your room costs ₹10,000/day but the policy cap is ₹5,000/day, "
"you pay the extra ₹5,000/day from your pocket.",
"tip": "Choose a room within the policy limit to avoid out-of-pocket costs.",
},
"copay": {
"simple": "Co-payment means you share a percentage of the total bill with the insurance company.",
"example": "With 20% co-pay on a ₹1,00,000 bill, insurance pays ₹80,000 and you pay ₹20,000.",
"tip": "If your policy has voluntary co-pay, removing it increases premium but eliminates co-pay deductions.",
},
"sublimit": {
"simple": "A sub-limit caps the maximum amount payable for a specific treatment or item.",
"example": "If cataract surgery has a ₹40,000 sub-limit but the actual cost is ₹60,000, "
"you pay the extra ₹20,000.",
"tip": "Check sub-limits for your specific procedure before choosing a hospital.",
},
"exclusion_permanent": {
"simple": "This procedure/condition is not covered at all under your policy.",
"example": "Cosmetic surgery is typically excluded. If you get rhinoplasty for cosmetic reasons, "
"insurance won't pay anything.",
"tip": "Check if there's a medical necessity exception — some exclusions don't apply if "
"the treatment is medically required (e.g., reconstructive surgery after an accident).",
},
"waiting_period_initial": {
"simple": "You cannot make claims during the first 30 days of your policy (except for accidents).",
"example": "If you buy insurance on January 1 and need planned surgery on January 15, it won't be covered.",
"tip": "Emergency and accident-related hospitalization is covered from day 1.",
},
"waiting_period_specific": {
"simple": "Certain diseases/procedures have a mandatory waiting period (usually 2-4 years) "
"before they're covered.",
"example": "Knee replacement typically has a 24-month wait. If your policy is 6 months old, "
"this surgery isn't covered yet.",
"tip": "Keep your policy continuously renewed — waiting periods are served only once.",
},
"waiting_period_pec": {
"simple": "Pre-existing diseases (conditions you had before buying insurance) "
"are covered only after 2-4 years.",
"example": "If you have diabetes when you buy insurance, diabetes-related hospitalization "
"won't be covered for 36-48 months.",
"tip": "After 8 years of continuous coverage, the insurer cannot deny any claim citing pre-existing disease.",
},
"deductible": {
"simple": "A deductible is a fixed amount you must pay before insurance kicks in.",
"example": "With a ₹10,000 deductible on a ₹50,000 bill, you pay ₹10,000 and insurance pays ₹40,000.",
"tip": "Higher deductible = lower premium. Choose based on your financial comfort.",
},
}
def clause_explainer(category: str, clause_text: str = "", clause_ref: str = "") -> dict:
"""
Explain a policy clause in simple, patient-friendly language.
Args:
category: Rule category (room_rent, copay, exclusion_permanent, etc.)
clause_text: The original clause text from the policy
clause_ref: Clause reference number
Returns:
{
"category": str,
"simple_explanation": str,
"example": str,
"tip": str,
"original_clause": str,
"clause_reference": str
}
"""
# Find the best matching explanation
explanation = _CATEGORY_EXPLANATIONS.get(category)
if not explanation:
# Try to match partial category names
for key, exp in _CATEGORY_EXPLANATIONS.items():
if key in category or category in key:
explanation = exp
break
if not explanation:
explanation = {
"simple": f"This rule ({category}) affects how your claim is processed.",
"example": "Please refer to your policy document for specific details.",
"tip": "Contact your insurer's customer service for clarification on this clause.",
}
return {
"category": category,
"simple_explanation": explanation["simple"],
"example": explanation["example"],
"tip": explanation["tip"],
"original_clause": clause_text,
"clause_reference": clause_ref,
}