Spaces:
Sleeping
Sleeping
deploy#2: unified reco-fit gate + session-privacy fix + profile-completeness + full UI rebuild
b134f5b | """KI-280 (2026-05-16) β UNIFIED recommendation-fit gate. | |
| Pins all 7 personas from the live Playwright audit | |
| (/tmp/persona_[1-7]_result.json) so the CITED card list and the advisory | |
| prose are gated by the SAME fitness logic. | |
| The unified gate (backend/retrieval_filters.filter_pipeline, on the live | |
| single_brain β brain_tools.retrieve_policies path) requires the cited set | |
| to pass ALL of: | |
| 1. Product-type vs intent β no fixed-benefit / hospital-cash / PA / CI | |
| to a comprehensive-indemnity buyer (KI-279, preserved). Inverse | |
| preserved: top-up seeker (P4) + explicit-CI seeker (P6) still get | |
| those products. | |
| 2. Hard eligibility β when insuring seniors / parents (~70), drop | |
| plans whose max entry age can't accept them; surface senior-eligible. | |
| 3. Required features β explicit maternity/newborn need β only cite | |
| plans whose facts confirm it (unverified ones ranked strictly below). | |
| 4. Objective / fit ranking β cited list ordered by scorecard fit-score / | |
| stated objective; #1 = best fit for THIS profile (fixes the | |
| grade/rank inversion + P5 cost-objective lead). | |
| 5. Dedup β same-product / marketing-variant duplicates | |
| collapsed (shared canonical/UIN identity from policy_identity). | |
| Audit personas (transcripts + observed-bad outcomes drive the asserts): | |
| P1 Young first-timer comprehensive β Star Hospital Cash wrongly cited; | |
| C/65 ranked above B/75. EXPECT: no fixed-benefit; #1 = best grade. | |
| P2 Senior + PED β New National Parivar Mediclaim cited | |
| twice; A/77 ranked LAST. EXPECT: deduped; A-grade #1. | |
| P3 Family + maternity (very imp.) β non-maternity plans cited equal to | |
| the maternity-confirmed one; Star Hospital Cash cited. | |
| EXPECT: maternity-confirmed first, fixed-benefit dropped. | |
| P4 Top-up seeker (INVERSE) β Advanced Top Up cited twice. | |
| EXPECT: top-up KEPT (inverse preserved) + deduped. | |
| P5 Budget, cost is #1 need β leads with non-cheapest. | |
| EXPECT: cost-objective β cheapest-appropriate ranks first. | |
| P6 Critical-illness seeker (INV.) β EXPECT: CI plans KEPT. | |
| P7 Parents both 70 + PED β max-entry-65 plans cited; Chola | |
| Flexi Supreme + Flexi Health (same insurer) both cited. | |
| EXPECT: entry-age-65 plans dropped; senior-eligible surfaced; deduped. | |
| Run: | |
| .venv/bin/python -m pytest -q tests/test_recommendation_fit_gate.py | |
| """ | |
| from __future__ import annotations | |
| import unittest | |
| from backend.policy_identity import canonical_key, normalize_uin, product_key | |
| from backend.retrieval_filters import ( | |
| apply_eligibility_filter, | |
| dedup_by_policy, | |
| filter_pipeline, | |
| rank_by_profile_fit, | |
| ) | |
| from backend.single_brain import _build_recommendation_citations | |
| def _chunk( | |
| policy_id, | |
| policy_name, | |
| *, | |
| score=0.5, | |
| policy_type=None, | |
| policy_type_canonical=None, | |
| uin_code=None, | |
| copay_pct=None, | |
| sum_insured_options=None, | |
| max_entry_age=None, | |
| min_entry_age=None, | |
| maternity=None, | |
| newborn=None, | |
| grade=None, | |
| overall_score=None, | |
| doc_type="policy", | |
| ): | |
| """Chunk dict shaped like brain_tools.retrieve_policies output AFTER the | |
| policy_facts + scorecard enrichment step (KI-280 added uin_code / | |
| max_entry_age / maternity_coverage / newborn_coverage).""" | |
| return { | |
| "policy_id": policy_id, | |
| "policy_name": policy_name, | |
| "insurer_slug": policy_id.split("__")[0], | |
| "doc_type": doc_type, | |
| "score": score, | |
| "chunk_text": "", | |
| "policy_type_indemnity_or_fixed": policy_type_canonical, | |
| "policy_type": policy_type, | |
| "uin_code": uin_code, | |
| "deductible_amount": None, | |
| "co_payment_pct": copay_pct, | |
| "sum_insured_options": sum_insured_options, | |
| "max_entry_age": max_entry_age, | |
| "min_entry_age": min_entry_age, | |
| "maternity_coverage": maternity, | |
| "newborn_coverage": newborn, | |
| "_grade": grade, | |
| "_overall_score": overall_score, | |
| } | |
| # ββ Shared catalog slices βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| GOOD_INDEMNITY_A = _chunk( | |
| "niva-bupa__reassure-3", "ReAssure 3.0", | |
| score=0.55, policy_type_canonical="indemnity", policy_type="family_floater", | |
| uin_code="MAXHLIP21177V032122", copay_pct=0, | |
| sum_insured_options=[1_000_000, 1_500_000, 2_000_000, 5_000_000], | |
| max_entry_age=70, maternity=True, newborn=True, grade="A", overall_score=88, | |
| ) | |
| DECENT_INDEMNITY_C = _chunk( | |
| "cholamandalam__flexi-health-supreme__wordings", "Chola Flexi Health Supreme", | |
| score=0.72, policy_type_canonical="family_floater", policy_type=None, | |
| uin_code="CHOHLIP27040V032627", copay_pct=0, | |
| sum_insured_options=[1_000_000, 1_500_000, 2_500_000], | |
| max_entry_age=75, maternity=True, grade="C", overall_score=65, | |
| ) | |
| STAR_HOSPITAL_CASH = _chunk( | |
| "star-health__star-hospital-cash__brochure", "Star Hospital Cash", | |
| score=0.71, policy_type="hospital_cash", uin_code="SHAHLIP20046V011920", | |
| max_entry_age=65, grade="D", overall_score=60, | |
| ) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Shared canonical-identity helper (Rule 5 building block) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestPolicyIdentity(unittest.TestCase): | |
| def test_uin_primary(self): | |
| a = {"policy_id": "hdfc-ergo__optima-secure__wordings", | |
| "uin_code": "HDFHLIP25041V062425"} | |
| b = {"policy_id": "hdfc-ergo__optima-secure-older-variant", | |
| "uin_code": "HDFHLIP25041V062425"} | |
| self.assertEqual(canonical_key(a), canonical_key(b), | |
| "Same UIN β same product (marketing rename).") | |
| def test_product_key_collapses_doctype_siblings(self): | |
| self.assertEqual( | |
| product_key("acko__health-iii__wordings"), "acko__health-iii") | |
| self.assertEqual( | |
| product_key("acko__health-iii__brochure"), "acko__health-iii") | |
| a = {"policy_id": "acko__health-iii__wordings"} | |
| b = {"policy_id": "acko__health-iii__brochure"} | |
| self.assertEqual(canonical_key(a), canonical_key(b)) | |
| def test_distinct_products_stay_distinct(self): | |
| a = {"policy_id": "cholamandalam__flexi-health-supreme__wordings", | |
| "uin_code": "CHOHLIP27040V032627"} | |
| b = {"policy_id": "cholamandalam__flexi-health__wordings", | |
| "uin_code": "CHOHLIP24145V062526"} | |
| self.assertNotEqual(canonical_key(a), canonical_key(b), | |
| "Different UIN β genuinely different products.") | |
| def test_normalize_uin_rejects_junk(self): | |
| self.assertEqual(normalize_uin(""), "") | |
| self.assertEqual(normalize_uin("N/A"), "") | |
| self.assertEqual(normalize_uin({"value": "SHAHLIP20046V011920"}), | |
| "SHAHLIP20046V011920") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # P1 β Young first-timer comprehensive (15L, zero co-pay) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| P1 = { | |
| "age": 29, "location_tier": "metro", "income_band": "25L+", | |
| "primary_goal": "first_buy", "existing_cover_inr": 0, | |
| "health_conditions": ["none"], "desired_sum_insured_inr": 1_500_000, | |
| "copay_pct": 0, "dependents": "self", | |
| } | |
| class TestPersona1(unittest.TestCase): | |
| def test_no_fixed_benefit_and_best_grade_first(self): | |
| catalog = [DECENT_INDEMNITY_C, STAR_HOSPITAL_CASH, GOOD_INDEMNITY_A] | |
| filtered, guard = filter_pipeline( | |
| catalog, profile=P1, | |
| query="comprehensive metro 15 lakh zero co-pay first-time buyer", | |
| intent="recommendation") | |
| ids = [c["policy_id"] for c in filtered] | |
| self.assertNotIn("star-health__star-hospital-cash__brochure", ids, | |
| "P1: fixed-benefit hospital-cash must not be cited") | |
| self.assertTrue(ids) | |
| self.assertEqual(ids[0], "niva-bupa__reassure-3", | |
| "P1: #1 must be the best-fit (A/88), not C/65") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # P2 β Senior + PED. Dedup + grade/rank inversion (A/77 was LAST). | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| P2 = { | |
| "age": 58, "location_tier": "tier-2", "income_band": "10L-25L", | |
| "primary_goal": "first_buy", "existing_cover_inr": 0, | |
| "health_conditions": ["diabetes", "hypertension"], | |
| "desired_sum_insured_inr": 1_000_000, "copay_pct": 10, | |
| "dependents": "self+spouse", | |
| } | |
| P2_NNP_A = _chunk( | |
| "national-insurance__new-national-parivar-mediclaim__wordings", | |
| "New National Parivar Mediclaim", score=0.70, | |
| policy_type_canonical="family_floater", uin_code="NICHLIP26999V010203", | |
| copay_pct=10, sum_insured_options=[500_000, 1_000_000, 1_500_000], | |
| max_entry_age=80, grade="C", overall_score=66) | |
| P2_NNP_DUP = _chunk( | |
| "national-insurance__new-national-parivar-mediclaim__brochure", | |
| "New National Parivar Mediclaim", score=0.62, | |
| policy_type_canonical="family_floater", uin_code="NICHLIP26999V010203", | |
| copay_pct=10, sum_insured_options=[500_000, 1_000_000, 1_500_000], | |
| max_entry_age=80, grade="C", overall_score=66) | |
| P2_REASSURE_A = _chunk( | |
| "niva-bupa__reassure-3", "ReAssure 3.0", score=0.55, | |
| policy_type_canonical="indemnity", uin_code="MAXHLIP21177V032122", | |
| copay_pct=10, sum_insured_options=[1_000_000, 1_500_000], | |
| max_entry_age=70, grade="A", overall_score=77) | |
| class TestPersona2(unittest.TestCase): | |
| def test_dedup_variant_and_a_grade_leads(self): | |
| catalog = [P2_NNP_A, P2_NNP_DUP, P2_REASSURE_A] | |
| filtered, _ = filter_pipeline( | |
| catalog, profile=P2, | |
| query="senior 58 family floater diabetes hypertension 10 lakh", | |
| intent="recommendation") | |
| ids = [c["policy_id"] for c in filtered] | |
| # Same-UIN doctype siblings collapse to one card. | |
| nnp = [i for i in ids if "new-national-parivar" in i] | |
| self.assertEqual(len(nnp), 1, | |
| "P2: New National Parivar must appear ONCE, not twice") | |
| self.assertEqual(ids[0], "niva-bupa__reassure-3", | |
| "P2: A/77 must lead, not be last") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # P3 β Family + maternity (explicitly "very important") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| P3 = { | |
| "age": 34, "location_tier": "metro", "income_band": "25L-40L", | |
| "primary_goal": "first_buy maternity newborn family floater", | |
| "existing_cover_inr": 0, "health_conditions": ["none"], | |
| "desired_sum_insured_inr": 2_500_000, "copay_pct": 0, | |
| "dependents": "self+spouse+kids", | |
| } | |
| P3_MATERNITY_OK = _chunk( | |
| "cholamandalam__flexi-health-supreme__wordings", "Chola Flexi Health Supreme", | |
| score=0.60, policy_type_canonical="family_floater", | |
| uin_code="CHOHLIP27040V032627", copay_pct=0, | |
| sum_insured_options=[2_500_000], max_entry_age=75, | |
| maternity=True, newborn=True, grade="C", overall_score=65) | |
| P3_NO_MATERNITY = _chunk( | |
| "hdfc-ergo__optima-secure", "my:Optima Secure", score=0.71, | |
| policy_type_canonical="indemnity", uin_code="HDFHLIP25041V062425", | |
| copay_pct=0, sum_insured_options=[2_500_000], max_entry_age=65, | |
| maternity=False, newborn=False, grade="B", overall_score=75) | |
| P3_HOSPITAL_CASH = _chunk( | |
| "star-health__star-hospital-cash__brochure", "Star Hospital Cash", | |
| score=0.68, policy_type="hospital_cash", uin_code="SHAHLIP20046V011920", | |
| max_entry_age=65, maternity=True, grade="D", overall_score=60) | |
| class TestPersona3(unittest.TestCase): | |
| def test_maternity_confirmed_first_fixed_benefit_dropped(self): | |
| catalog = [P3_MATERNITY_OK, P3_NO_MATERNITY, P3_HOSPITAL_CASH] | |
| filtered, _ = filter_pipeline( | |
| catalog, profile=P3, | |
| query="family floater maternity newborn 25 lakh metro first-time", | |
| intent="recommendation") | |
| ids = [c["policy_id"] for c in filtered] | |
| self.assertNotIn("star-health__star-hospital-cash__brochure", ids, | |
| "P3: hospital-cash is not a comprehensive family plan") | |
| self.assertTrue(ids) | |
| self.assertEqual( | |
| ids[0], "cholamandalam__flexi-health-supreme__wordings", | |
| "P3: maternity-confirmed plan must rank above the " | |
| "non-maternity B-grade plan when maternity is required") | |
| # The non-maternity plan, if present at all, ranks strictly below. | |
| if "hdfc-ergo__optima-secure" in ids: | |
| self.assertLess( | |
| ids.index("cholamandalam__flexi-health-supreme__wordings"), | |
| ids.index("hdfc-ergo__optima-secure")) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # P4 β Top-up seeker (INVERSE β top-up must be KEPT). + dedup of Γ2. | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| P4 = { | |
| "age": 44, "location_tier": "metro", "income_band": "10L-25L", | |
| "primary_goal": "cheapest top-up over existing employer cover", | |
| "existing_cover_inr": 500_000, "health_conditions": ["none"], | |
| "desired_sum_insured_inr": 2_000_000, "copay_pct": 10, | |
| "dependents": "self", | |
| } | |
| P4_TOPUP_A = _chunk( | |
| "royal-sundaram__advanced-top-up__wordings", "Advanced Top Up", | |
| score=0.76, policy_type_canonical="super_top_up", | |
| uin_code="RSAHLIP21055V032021", copay_pct=0, | |
| sum_insured_options=[1_000_000, 2_000_000], max_entry_age=65, | |
| grade="A", overall_score=76) | |
| P4_TOPUP_DUP = _chunk( | |
| "royal-sundaram__advanced-top-up__brochure", "Advanced Top Up", | |
| score=0.69, policy_type_canonical="super_top_up", | |
| uin_code="RSAHLIP21055V032021", copay_pct=0, | |
| sum_insured_options=[1_000_000, 2_000_000], max_entry_age=65, | |
| grade="A", overall_score=76) | |
| P4_SBI = _chunk( | |
| "sbi-general__arogya-supreme", "Arogya Supreme", score=0.60, | |
| policy_type_canonical="family_floater", uin_code="SBIHLIP21099V012021", | |
| copay_pct=10, sum_insured_options=[2_000_000], max_entry_age=65, | |
| grade="B", overall_score=75) | |
| class TestPersona4Inverse(unittest.TestCase): | |
| def test_topup_kept_and_deduped(self): | |
| catalog = [P4_TOPUP_A, P4_TOPUP_DUP, P4_SBI] | |
| filtered, _ = filter_pipeline( | |
| catalog, profile=P4, | |
| query="cheapest top-up over existing 5 lakh employer cover 20 lakh", | |
| intent="recommendation") | |
| ids = [c["policy_id"] for c in filtered] | |
| topups = [i for i in ids if "advanced-top-up" in i] | |
| self.assertEqual(len(topups), 1, | |
| "P4: Advanced Top Up must appear ONCE (was Γ2)") | |
| self.assertTrue(any("advanced-top-up" in i for i in ids), | |
| "P4 INVERSE: top-up seeker MUST still get top-up plans") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # P5 β Budget-constrained; cost is the #1 stated objective. | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| P5 = { | |
| "age": 31, "location_tier": "tier-3", "income_band": "<10L", | |
| "primary_goal": "cost_optimize cheapest lowest premium", | |
| "existing_cover_inr": 0, "health_conditions": ["none"], | |
| "desired_sum_insured_inr": 500_000, "copay_pct": 30, | |
| "dependents": "self", | |
| } | |
| # The live P5 audit defect was COSINE-DRIVEN inversion: the bot led with | |
| # the highest-cosine plan even though, at the same scorecard fit tier, it | |
| # was not the cost-appropriate pick for a "lowest premium is my top | |
| # priority" user. We pin the true root cause: at an equal scorecard tier, | |
| # a higher-cosine plan must NOT out-rank an equal-fit plan when cost is the | |
| # stated #1 objective (cosine is damped; the scorecard fit-score decides). | |
| P5_CHEAPEST = _chunk( | |
| "royal-sundaram__arogya-sanjeevani", "Arogya Sanjeevani", score=0.55, | |
| policy_type_canonical="indemnity", uin_code="RSAHLIP21010V012021", | |
| copay_pct=5, sum_insured_options=[500_000], max_entry_age=65, | |
| grade="C", overall_score=66) | |
| P5_PRICIER = _chunk( | |
| "hdfc-ergo__optima-secure", "my:Optima Secure", score=0.75, | |
| policy_type_canonical="indemnity", uin_code="HDFHLIP25041V062425", | |
| copay_pct=0, sum_insured_options=[500_000, 1_000_000], max_entry_age=65, | |
| grade="C", overall_score=64) | |
| class TestPersona5(unittest.TestCase): | |
| def test_cost_objective_not_cosine_dominated(self): | |
| # Both plans are the SAME scorecard grade tier (C). The pricier | |
| # plan's ONLY advantage is raw cosine (0.75 vs 0.55) β exactly the | |
| # live-audit inversion. With cost the #1 objective, the gate damps | |
| # cosine so the marginally-better-fit cost-appropriate plan (the | |
| # canonical Arogya Sanjeevani standard product, 66 vs 64) leads | |
| # instead of the higher-cosine one. | |
| ranked = rank_by_profile_fit([P5_PRICIER, P5_CHEAPEST], P5) | |
| ids = [c["policy_id"] for c in ranked] | |
| self.assertEqual(len(ids), 2) | |
| self.assertEqual( | |
| ids[0], "royal-sundaram__arogya-sanjeevani", | |
| "P5: cost objective β fit-score decides, NOT raw cosine") | |
| def test_without_cost_objective_cosine_still_breaks_ties(self): | |
| # Sanity / non-regression: for a NON-cost profile the cosine weight | |
| # is unchanged, so the higher-cosine plan can still win a near-tie. | |
| non_cost = dict(P5, primary_goal="first_buy comprehensive") | |
| ranked = rank_by_profile_fit([P5_CHEAPEST, P5_PRICIER], non_cost) | |
| self.assertEqual( | |
| [c["policy_id"] for c in ranked][0], "hdfc-ergo__optima-secure", | |
| "non-cost profile: cosine still contributes normally") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # P6 β Critical-illness seeker (INVERSE β CI must be KEPT). | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| P6 = { | |
| "age": 42, "location_tier": "metro", "income_band": "25L+", | |
| "primary_goal": "critical illness lump-sum fixed-benefit plan only", | |
| "existing_cover_inr": 1_500_000, "health_conditions": ["none"], | |
| "desired_sum_insured_inr": 2_500_000, "copay_pct": 0, | |
| "dependents": "self", | |
| } | |
| P6_CI_1 = _chunk( | |
| "national-insurance__national-critical-illness", "National Critical Illness", | |
| score=0.54, policy_type="critical_illness", | |
| policy_type_canonical="fixed_benefit", uin_code="NICCIIP20001V010203", | |
| sum_insured_options=[2_500_000], max_entry_age=65, grade="D", | |
| overall_score=54) | |
| P6_CI_2 = _chunk( | |
| "go-digit__digit-health-care-plus", "Digit Health Care Plus", score=0.75, | |
| policy_type="critical_illness", policy_type_canonical="fixed_benefit", | |
| uin_code="GODHLIP21099V012021", sum_insured_options=[2_500_000], | |
| max_entry_age=65, grade="B", overall_score=75) | |
| class TestPersona6Inverse(unittest.TestCase): | |
| def test_ci_plans_kept_for_explicit_ci_seeker(self): | |
| catalog = [P6_CI_1, P6_CI_2] | |
| filtered, _ = filter_pipeline( | |
| catalog, profile=P6, | |
| query="critical illness lump-sum 25 lakh fixed benefit", | |
| intent="recommendation") | |
| ids = {c["policy_id"] for c in filtered} | |
| self.assertIn("national-insurance__national-critical-illness", ids, | |
| "P6 INVERSE: explicit CI seeker MUST still get CI plans") | |
| self.assertIn("go-digit__digit-health-care-plus", ids) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # P7 β Parents both 70 + PED. Entry-age gate + dedup + senior surfacing. | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| P7 = { | |
| "age": 36, "location_tier": "metro", "income_band": "10L-25L", | |
| "primary_goal": "parents health cover senior citizens", | |
| "existing_cover_inr": 0, | |
| "health_conditions": ["father diabetes", "mother hypertension arthritis"], | |
| "desired_sum_insured_inr": 1_000_000, "copay_pct": 10, | |
| "dependents": "self+parents", "parents_to_insure": True, | |
| "parents_age_max": 70, "parents_has_ped": True, | |
| } | |
| P7_CHOLA_SUPREME = _chunk( | |
| "cholamandalam__flexi-health-supreme__wordings", "Chola Flexi Health Supreme", | |
| score=0.72, policy_type_canonical="family_floater", | |
| uin_code="CHOHLIP27040V032627", copay_pct=10, | |
| sum_insured_options=[1_000_000], max_entry_age=75, grade="C", | |
| overall_score=65) | |
| P7_CHOLA_FLEXI = _chunk( # same insurer, DIFFERENT product, max_entry 65 | |
| "cholamandalam__flexi-health__wordings", "Flexi Health", score=0.67, | |
| policy_type_canonical="individual", uin_code="CHOHLIP24145V062526", | |
| copay_pct=10, sum_insured_options=[1_000_000], max_entry_age=65, | |
| grade="C", overall_score=67) | |
| P7_OPTIMA_65 = _chunk( | |
| "hdfc-ergo__optima-secure", "my:Optima Secure", score=0.70, | |
| policy_type_canonical="indemnity", uin_code="HDFHLIP25041V062425", | |
| copay_pct=10, sum_insured_options=[1_000_000], max_entry_age=65, | |
| grade="B", overall_score=75) | |
| P7_SENIOR_OK = _chunk( | |
| "star-health__senior-citizens-red-carpet__brochure", | |
| "Star Senior Citizens Red Carpet", score=0.50, | |
| policy_type_canonical="indemnity", uin_code="SHAHLIP26041V082526", | |
| copay_pct=10, sum_insured_options=[1_000_000], min_entry_age=60, | |
| max_entry_age=75, grade="B", overall_score=74) | |
| class TestPersona7(unittest.TestCase): | |
| def test_entry_age_gate_dedup_and_senior_surfaced(self): | |
| catalog = [P7_CHOLA_SUPREME, P7_CHOLA_FLEXI, P7_OPTIMA_65, | |
| P7_SENIOR_OK] | |
| filtered, _ = filter_pipeline( | |
| catalog, profile=P7, | |
| query="parents cover both age 70 diabetes hypertension senior " | |
| "citizen 10 lakh", | |
| intent="recommendation") | |
| ids = [c["policy_id"] for c in filtered] | |
| # max_entry_age 65 < 70 β those plans cannot accept 70yo parents. | |
| self.assertNotIn("cholamandalam__flexi-health__wordings", ids, | |
| "P7: max_entry_age 65 cannot accept a 70yo parent") | |
| self.assertNotIn("hdfc-ergo__optima-secure", ids, | |
| "P7: max_entry_age 65 cannot accept a 70yo parent") | |
| # Entry-age-75 plans survive; senior-eligible surfaces. | |
| self.assertIn("star-health__senior-citizens-red-carpet__brochure", ids, | |
| "P7: a senior-eligible plan must be surfaced") | |
| self.assertIn("cholamandalam__flexi-health-supreme__wordings", ids, | |
| "P7: max_entry_age 75 accepts a 70yo parent") | |
| def test_same_insurer_distinct_products_not_collapsed(self): | |
| # Chola Supreme vs Flexi Health are DIFFERENT UINs β they are NOT a | |
| # dedup pair; the gate must not over-collapse them. (Flexi Health is | |
| # dropped here by the age gate, not by dedup.) | |
| self.assertNotEqual( | |
| canonical_key(P7_CHOLA_SUPREME), canonical_key(P7_CHOLA_FLEXI)) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Direct unit coverage for the new gate primitives | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestEntryAgeEligibility(unittest.TestCase): | |
| def test_parents_age_drives_entry_age_gate(self): | |
| kept = apply_eligibility_filter( | |
| [P7_OPTIMA_65, P7_CHOLA_SUPREME], P7) | |
| ids = {c["policy_id"] for c in kept} | |
| self.assertNotIn("hdfc-ergo__optima-secure", ids) | |
| self.assertIn("cholamandalam__flexi-health-supreme__wordings", ids) | |
| def test_payer_only_profile_does_not_trip_entry_age(self): | |
| # P1 is self-only, age 29 β the parents-age gate must NOT fire and | |
| # drop a normal adult plan whose max_entry_age is 65. | |
| kept = apply_eligibility_filter([P3_NO_MATERNITY], P1) | |
| self.assertEqual(len(kept), 1, | |
| "self-only 29yo must not be entry-age-gated by a " | |
| "parents rule") | |
| class TestMaternityRequirement(unittest.TestCase): | |
| def test_unverified_maternity_ranked_below_confirmed(self): | |
| ranked = rank_by_profile_fit([P3_NO_MATERNITY, P3_MATERNITY_OK], P3) | |
| ids = [c["policy_id"] for c in ranked] | |
| self.assertLess( | |
| ids.index("cholamandalam__flexi-health-supreme__wordings"), | |
| ids.index("hdfc-ergo__optima-secure"), | |
| "maternity-confirmed must outrank maternity=False when the " | |
| "profile explicitly needs maternity") | |
| def test_no_maternity_need_does_not_penalize(self): | |
| # P1 has no maternity need β the maternity rule must be inert. | |
| ranked = rank_by_profile_fit([P3_NO_MATERNITY, GOOD_INDEMNITY_A], P1) | |
| self.assertEqual(len(ranked), 2) | |
| class TestCanonicalDedupInPipeline(unittest.TestCase): | |
| def test_dedup_by_policy_uses_canonical_key(self): | |
| deduped = dedup_by_policy([P2_NNP_A, P2_NNP_DUP]) | |
| self.assertEqual(len(deduped), 1, | |
| "same-UIN doctype siblings collapse to one") | |
| # Highest-score chunk wins. | |
| self.assertEqual(deduped[0]["policy_id"], | |
| "national-insurance__new-national-parivar-mediclaim" | |
| "__wordings") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # LIVE PATH β single_brain._build_recommendation_citations must emit the | |
| # GATED, fit-ranked, canonically-deduped cited set (not the LLM's order). | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _live_chunk(pid, name, slug, score, cid, uin=None): | |
| return { | |
| "chunk_id": cid, "policy_id": pid, "policy_name": name, | |
| "insurer_slug": slug, "doc_type": "policy", | |
| "source_url": f"https://example.com/{pid}.pdf", | |
| "score": score, "uin_code": uin, | |
| } | |
| class TestLivePathCitationGate(unittest.TestCase): | |
| def test_cards_ordered_by_gate_rank_not_llm_order(self): | |
| # filter_pipeline returns chunks in profile-fit order; the union the | |
| # citation builder sees preserves that. Best-fit appears FIRST. | |
| gated_stream = [ | |
| _live_chunk("niva-bupa__reassure-3", "ReAssure 3.0", | |
| "niva-bupa", 0.55, "c1", "MAXHLIP21177V032122"), | |
| _live_chunk("cholamandalam__flexi-health-supreme__wordings", | |
| "Chola Flexi Health Supreme", "cholamandalam", | |
| 0.72, "c2", "CHOHLIP27040V032627"), | |
| ] | |
| # LLM marked them in the WRONG order (Chola first). | |
| cites, is_rec = _build_recommendation_citations( | |
| reply_text="see options", | |
| retrieved_chunks_all=gated_stream, | |
| marked_policy_ids=[ | |
| "cholamandalam__flexi-health-supreme__wordings", | |
| "niva-bupa__reassure-3", | |
| ], | |
| ) | |
| self.assertTrue(is_rec) | |
| self.assertEqual( | |
| [c["policy_id"] for c in cites], | |
| ["niva-bupa__reassure-3", | |
| "cholamandalam__flexi-health-supreme__wordings"], | |
| "Cited cards must follow the GATE's fit order, not the LLM's " | |
| "mark_recommendation order") | |
| def test_canonical_dedup_at_citation_layer(self): | |
| # Same UIN under two doctype-sibling ids across the turn's union. | |
| stream = [ | |
| _live_chunk("national-insurance__new-national-parivar-mediclaim" | |
| "__wordings", "New National Parivar Mediclaim", | |
| "national-insurance", 0.70, "c1", | |
| "NICHLIP26999V010203"), | |
| _live_chunk("national-insurance__new-national-parivar-mediclaim" | |
| "__brochure", "New National Parivar Mediclaim", | |
| "national-insurance", 0.62, "c2", | |
| "NICHLIP26999V010203"), | |
| ] | |
| cites, is_rec = _build_recommendation_citations( | |
| reply_text="I recommend New National Parivar Mediclaim", | |
| retrieved_chunks_all=stream, | |
| marked_policy_ids=[], # prose path | |
| ) | |
| self.assertTrue(is_rec) | |
| self.assertEqual(len(cites), 1, | |
| "same-UIN duplicate must be cited ONCE (audit P2)") | |
| self.assertEqual(cites[0]["chunk_id"], "c1", | |
| "highest-score chunk hydrates the single card") | |
| if __name__ == "__main__": | |
| unittest.main() | |