Spaces:
Sleeping
Sleeping
| """KI-278 (2026-05-16) β header "Premium range" chip vs per-settings panel | |
| reconciliation + smoker / family-history wiring + full-profile exhaustiveness. | |
| Pins the bug from Image#7: | |
| For ONE profile the header chip showed βΉ6,500ββΉ26,500/yr while opening the | |
| panel showed βΉ16,235ββΉ21,965 (point βΉ19,100). Two contradictory numbers | |
| for the same person. | |
| Root cause: estimate_premium_band() hard-coded sum_insured_default=βΉ10L and | |
| IGNORED the profile, while PremiumCalculatorPanel seeded its SI slider from | |
| desired_sum_insured_inr β existing_cover_inr β βΉ10L. A user who stated a | |
| βΉ25L target therefore had the header priced at βΉ10L and the panel at βΉ25L. | |
| Fix: estimate_premium_band() resolves SI via resolve_profile_sum_insured() | |
| (the single source of truth shared with the panel/widget), prices the whole | |
| basket at that SI, and reports the p25βp75 INTERQUARTILE of the basket as | |
| the band (directionally rounded) β the honest "what similar profiles | |
| typically pay" range. Raw minβmax across the heterogeneous basket spans | |
| ~4-5x and renders as a useless, broken-looking band (KI broken-band fix); | |
| a specific plan's per-settings panel point may sit inside or just outside | |
| this typical band, which is expected and correct. | |
| """ | |
| from backend.premium_calculator import ( | |
| _DEFAULT_BAND_POLICY_IDS, | |
| _ceil_to_500, | |
| _floor_to_500, | |
| _median, | |
| _percentile, | |
| bulk_estimate, | |
| estimate, | |
| estimate_premium_band, | |
| resolve_profile_sum_insured, | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # resolve_profile_sum_insured β the shared SI contract | |
| # --------------------------------------------------------------------------- | |
| def test_si_precedence_desired_over_existing_over_default(): | |
| # desired_sum_insured_inr wins | |
| assert ( | |
| resolve_profile_sum_insured( | |
| {"desired_sum_insured_inr": 2_500_000, "existing_cover_inr": 800_000} | |
| ) | |
| == 2_500_000 | |
| ) | |
| # existing_cover_inr next when no desired | |
| assert resolve_profile_sum_insured({"existing_cover_inr": 800_000}) == 800_000 | |
| # legacy default when neither present | |
| assert resolve_profile_sum_insured({}) == 1_000_000 | |
| assert resolve_profile_sum_insured(None) == 1_000_000 | |
| def test_si_unclamped_and_snapped_to_slider_grid(): | |
| # SI RATIONALISATION (D2, 2026-05-16) β the global βΉ5 L / βΉ1 Cr clamp was | |
| # REMOVED. The user's actual stated target is now honoured (snapped to the | |
| # βΉ50k grid only), not squashed into a synthetic envelope. | |
| # Above the OLD ceiling: prices at the real βΉ2 Cr (was clamped to βΉ1 Cr). | |
| assert resolve_profile_sum_insured({"desired_sum_insured_inr": 20_000_000}) == 20_000_000 | |
| # Below the OLD floor: prices at the real βΉ1 L (was clamped to βΉ5 L). | |
| assert resolve_profile_sum_insured({"desired_sum_insured_inr": 100_000}) == 100_000 | |
| # Off-grid still snaps to nearest βΉ50k. | |
| assert resolve_profile_sum_insured({"desired_sum_insured_inr": 1_234_000}) == 1_250_000 | |
| def test_si_coerces_garbage_gracefully(): | |
| assert resolve_profile_sum_insured({"desired_sum_insured_inr": "2500000"}) == 2_500_000 | |
| assert resolve_profile_sum_insured({"desired_sum_insured_inr": None, | |
| "existing_cover_inr": "abc"}) == 1_000_000 | |
| assert resolve_profile_sum_insured({"desired_sum_insured_inr": 0}) == 1_000_000 | |
| # --------------------------------------------------------------------------- | |
| # Header β panel reconciliation β the core defect | |
| # --------------------------------------------------------------------------- | |
| def _panel_points_for(profile: dict) -> list[int]: | |
| """The per-settings panel = one basket policy priced at the SAME | |
| profile-resolved SI the header band uses. Reproduce every basket member's | |
| point so we can assert each one lies inside the displayed band.""" | |
| si = resolve_profile_sum_insured(profile) | |
| rows = bulk_estimate( | |
| list(_DEFAULT_BAND_POLICY_IDS), | |
| profile=profile, | |
| overrides={pid: {"sum_insured_inr": si} for pid in _DEFAULT_BAND_POLICY_IDS}, | |
| ) | |
| return sorted(r.premium_inr_annual for r in rows.values() if r.premium_inr_annual) | |
| import pytest | |
| def test_header_band_is_p25_p75_interquartile(profile, label): | |
| band = estimate_premium_band(dict(profile)) | |
| points = _panel_points_for(profile) # sorted basket points | |
| assert points, f"no basket points for {label}" | |
| # New contract: the band edges ARE the directionally-rounded p25 / p75 | |
| # of the basket β the interquartile "typical range", NOT raw min-max. | |
| assert band["min_inr"] == _floor_to_500(_percentile(points, 25)), label | |
| assert band["max_inr"] == _ceil_to_500(_percentile(points, 75)), label | |
| # min β€ median β€ max, all positive. | |
| assert 0 < band["min_inr"] <= band["median_inr"] <= band["max_inr"], label | |
| # The typical (median) plan lies inside the band. | |
| med = _median(points) | |
| assert band["min_inr"] <= med <= band["max_inr"], ( | |
| f"{label}: median basket point βΉ{med:,} fell outside the typical " | |
| f"band βΉ{band['min_inr']:,}ββΉ{band['max_inr']:,}" | |
| ) | |
| # And it is materially TIGHTER than the raw min-max envelope β the | |
| # entire point of the fix (the heterogeneous basket's raw spread is | |
| # ~4-5x; the interquartile band must be a strict subset of it). | |
| raw_lo = _floor_to_500(min(points)) | |
| raw_hi = _ceil_to_500(max(points)) | |
| assert band["min_inr"] >= raw_lo, label | |
| assert band["max_inr"] <= raw_hi, label | |
| assert (band["max_inr"] - band["min_inr"]) <= (raw_hi - raw_lo), label | |
| def test_band_exposes_resolved_si_for_panel_alignment(): | |
| band = estimate_premium_band({"desired_sum_insured_inr": 2_500_000}) | |
| assert band["sum_insured_used"] == 2_500_000 | |
| # And it is the same value the panel/widget would seed its slider with. | |
| assert band["sum_insured_used"] == resolve_profile_sum_insured( | |
| {"desired_sum_insured_inr": 2_500_000} | |
| ) | |
| def test_band_moves_with_stated_si_was_the_bug(): | |
| """Pre-fix this returned an IDENTICAL band regardless of stated SI.""" | |
| base = {"age": 35, "location_tier": "metro", "dependents": "self"} | |
| b_10l = estimate_premium_band(dict(base)) | |
| b_25l = estimate_premium_band({**base, "desired_sum_insured_inr": 2_500_000}) | |
| assert b_25l["max_inr"] > b_10l["max_inr"] | |
| assert b_25l["sum_insured_used"] == 2_500_000 | |
| assert b_10l["sum_insured_used"] == 1_000_000 | |
| # --------------------------------------------------------------------------- | |
| # Smoker + family_medical_history wiring (defect #2) | |
| # --------------------------------------------------------------------------- | |
| def test_smoker_moves_both_band_and_per_policy(): | |
| base = {"age": 40, "location_tier": "metro", "dependents": "self"} | |
| b0 = estimate_premium_band(dict(base)) | |
| b1 = estimate_premium_band({**base, "smoker": True}) | |
| assert b1["max_inr"] > b0["max_inr"], "smoker not reflected in header band" | |
| e0 = estimate(age=40, sum_insured_inr=1_000_000, policy_id="hdfc-ergo__optima-secure") | |
| e1 = estimate(age=40, sum_insured_inr=1_000_000, policy_id="hdfc-ergo__optima-secure", | |
| smoker=True) | |
| assert e1.point_estimate_inr > e0.point_estimate_inr, "smoker not in per-policy estimate" | |
| def test_family_medical_history_moves_both_band_and_per_policy(): | |
| base = {"age": 40, "location_tier": "metro", "dependents": "self"} | |
| b0 = estimate_premium_band(dict(base)) | |
| b1 = estimate_premium_band({**base, "family_medical_history": ["cancer", "diabetes"]}) | |
| assert b1["max_inr"] > b0["max_inr"], "family history not in header band" | |
| e0 = estimate(age=40, sum_insured_inr=1_000_000, policy_id="hdfc-ergo__optima-secure") | |
| e1 = estimate(age=40, sum_insured_inr=1_000_000, policy_id="hdfc-ergo__optima-secure", | |
| family_medical_history=["cancer", "diabetes"]) | |
| assert e1.point_estimate_inr > e0.point_estimate_inr, "family history not in estimate" | |
| # --------------------------------------------------------------------------- | |
| # Exhaustiveness β every pricing-relevant SLOT_UNION field must move the band | |
| # --------------------------------------------------------------------------- | |
| def test_every_pricing_slot_moves_the_header_band(delta, field): | |
| base = {"age": 35, "location_tier": "metro", "dependents": "self"} | |
| b0 = estimate_premium_band(dict(base)) | |
| b1 = estimate_premium_band({**base, **delta}) | |
| moved = ( | |
| b1["min_inr"] != b0["min_inr"] | |
| or b1["max_inr"] != b0["max_inr"] | |
| or b1["sum_insured_used"] != b0["sum_insured_used"] | |
| ) | |
| assert moved, f"{field} dropped on the header-band path (no effect)" | |
| def test_parents_on_cover_moves_band(): | |
| base = {"age": 35, "location_tier": "metro", "dependents": "parents"} | |
| b0 = estimate_premium_band(dict(base)) | |
| b1 = estimate_premium_band({**base, "parents_age_max": 72}) | |
| b2 = estimate_premium_band({**base, "parents_age_max": 72, "parents_has_ped": True}) | |
| assert b1["max_inr"] > b0["max_inr"], "parents_age_max dropped" | |
| assert b2["max_inr"] > b1["max_inr"], "parents_has_ped dropped" | |