InsuranceBot / tests /test_scorecard_parity.py
rohitsar567's picture
feat(#31): deterministic profile-aware {strengths,caveat} policy summary on all 3 surfaces
d486a56
Raw
History Blame Contribute Delete
8.73 kB
"""Regression guard β€” recommendation-path scorecard == marketplace scorecard.
ROOT CAUSE THIS LOCKS DOWN
--------------------------
1. KI-FULLFACTS (2026-05-17): `brain_tools._scorecard_signal` fed
`build_scorecard` the 7-key eligibility subset β†’ every recommended
policy came back grade "β€”"/0 β†’ the >=70 fitness gate dropped 100% β†’
empty `citations` β†’ CitedPolicyCards never rendered.
2. #40 (2026-05-18): even after (1), `_scorecard_signal` kept its OWN
doctype-rank + `_merge_curated` re-implementation that *mirrored*
`/api/policies/all`. Two parallel implementations drift: marketing-
rename / KI-145 variant / multi-doctype ids graded a different file
than the marketplace card (e.g. `my:Optima Secure (older variant)`
recommended at one grade, marketplace card at another).
THE FIX β€” SINGLE SOURCE OF TRUTH
--------------------------------
`backend.main._marketplace_catalogue()` is the ONE place a card set is
computed. `backend.main.marketplace_grade(policy_id)` resolves a policy's
canonical card (UIN-primary) from it. `_scorecard_signal` simply delegates
to `marketplace_grade`. The two surfaces can no longer diverge because
there is exactly one computation.
This test makes that *provable*: for the ENTIRE id universe the
recommender can cite β€” every marketplace card id, every curated id, every
extracted stem incl. `__wordings/__brochure/__cis/__prospectus`
permutations, and every alias name β€” the recommendation-path GRADE must
equal the marketplace GRADE. 0 mismatches, by construction.
"""
from __future__ import annotations
import sys
import unittest
from pathlib import Path
_REPO_ROOT = Path(__file__).resolve().parent.parent
if str(_REPO_ROOT) not in sys.path:
sys.path.insert(0, str(_REPO_ROOT))
class TestScorecardParity(unittest.TestCase):
"""Rec-path _scorecard_signal grade == marketplace grade, everywhere."""
@classmethod
def setUpClass(cls):
import backend.main as M
from backend.brain_tools import _scorecard_signal
cls.M = M
cls._scorecard_signal = staticmethod(_scorecard_signal)
cls._cards = M._marketplace_catalogue(None)
cls._cur = M._load_curated_facts()
def test_full_id_universe_parity(self):
"""The #40 oracle: rec grade == marketplace grade for EVERY id a
recommendation can surface (card ids + curated ids + extracted
stems + doctype permutations + aliases). Must be 0 mismatches."""
M = self.M
ids = {c.policy_id for c in self._cards}
ids |= set(self._cur.keys())
ids |= {p.stem for p in M.settings.EXTRACTED_DIR.glob("*.json")}
for c in self._cards:
ids |= set(c.aliases or [])
mismatches = []
checked = 0
for pid in sorted(ids):
exp = M.marketplace_grade(pid).get("_grade")
if exp is None:
continue # not a marketplace card (e.g. regulatory) β€” skip
checked += 1
rec = (self._scorecard_signal(pid) or {}).get("_grade")
if rec != exp:
mismatches.append(f"{pid}: rec={rec!r} marketplace={exp!r}")
self.assertGreater(checked, 140, "id universe collapsed β€” guard broke")
self.assertEqual(
mismatches, [],
f"#40 PARITY BROKEN ({len(mismatches)}/{checked}): the "
"recommendation grade diverged from the marketplace grade. "
"Both MUST flow through backend.main.marketplace_grade (the "
"single source of truth); do not re-implement scoring in "
"brain_tools._scorecard_signal.\n " + "\n ".join(mismatches[:40]),
)
def test_proven_alias_and_doctype_edges(self):
"""The exact cases #40 used to break: a marketing-rename variant id
and doctype-permuted ids must each equal their canonical card."""
M = self.M
for pid in (
"hdfc-ergo__my-optima-secure-older-variant",
"hdfc-ergo__optima-restore__brochure",
"hdfc-ergo__optima-restore__cis",
"royal-sundaram__lifeline__cis",
"new-india__mediclaim-policy__brochure",
):
mkt = M.marketplace_grade(pid).get("_grade")
if mkt is None:
continue # absent in this env β€” skip, don't false-fail
rec = (self._scorecard_signal(pid) or {}).get("_grade")
self.assertEqual(
rec, mkt,
f"{pid}: rec grade {rec!r} != marketplace {mkt!r} "
"(alias/doctype canonicalisation regressed)",
)
def test_optima_restore_is_a_strong_recommendable_grade(self):
# The user's own policy β€” a genuinely strong plan. It must NOT come
# back as the data-starved "β€”"/0 sentinel, and must clear the Bug
# #71 recommendation fitness floor so its card can render.
from backend.single_brain import _recommendation_fit
sig = self._scorecard_signal("hdfc-ergo__optima-restore", profile=None)
self.assertIn(sig.get("_grade"), ("A", "B"),
f"Optima Restore must be A/B, got {sig!r}")
strong, overall, grade = _recommendation_fit({
"_overall_score": sig.get("_overall_score"),
"_grade": sig.get("_grade"),
})
self.assertTrue(
strong,
f"Optima Restore must clear the rec-fit floor "
f"(grade={grade} overall={overall})",
)
class TestProfileSummaryEndpointParity(unittest.TestCase):
"""Task #31 β€” profile_summary returned by
/api/policies/{id}/scorecard?session_id MUST be byte-identical to the
same canonical card's profile_summary in /api/policies/all?session_id
(both flow through the SAME build_scorecard pass with the SAME resolved
profile). Proven across the FULL catalogue for a β‰₯0.6 profile."""
@classmethod
def setUpClass(cls):
from fastapi.testclient import TestClient
import backend.main as M
from backend.session_state import get_session
cls.M = M
cls.client = TestClient(M.app, raise_server_exceptions=False)
# Build a β‰₯0.6-complete session profile so the catalogue is
# profile-scored (otherwise both sides are profile-neutral and the
# parity is trivial).
cls.sid = "pytest-task31-parity"
sess = get_session(cls.sid)
p = sess.profile
p.name = "Parity Tester"
p.age = 52
p.dependents = "self+spouse"
p.income_band = "25L+"
p.primary_goal = "upgrade"
p.location_tier = "metro"
p.health_conditions = ["diabetes"]
p.existing_cover_inr = 500000
p.copay_pct = 0
p.family_medical_history = ["heart"]
# Mark them answered so profile_completeness counts them (parity
# with the live save path).
p.asked = [
"name", "age", "dependents", "income_band", "primary_goal",
"location_tier", "health_conditions",
]
def test_endpoint_profile_summary_matches_catalogue(self):
M = self.M
r = self.client.get(f"/api/policies/all?session_id={self.sid}")
self.assertEqual(r.status_code, 200, r.text)
cards = r.json()["policies"]
self.assertGreater(len(cards), 100, "catalogue collapsed")
# Confirm the catalogue is actually profile-scored (else the parity
# is vacuous): at least one card must carry profile-aware strengths.
self.assertTrue(
any((c.get("profile_summary") or {}).get("strengths") for c in cards),
"no card has a profile_summary β€” catalogue not profile-scored",
)
mismatches = []
for c in cards:
pid = c["policy_id"]
cat_ps = c.get("profile_summary")
er = self.client.get(
f"/api/policies/{pid}/scorecard?session_id={self.sid}"
)
self.assertEqual(er.status_code, 200, (pid, er.text[:200]))
ep_ps = er.json().get("profile_summary")
if cat_ps != ep_ps:
mismatches.append(
f"{pid}: catalogue={cat_ps!r} endpoint={ep_ps!r}"
)
self.assertEqual(
mismatches, [],
f"TASK #31 PARITY BROKEN ({len(mismatches)}/{len(cards)}): the "
"single-scorecard endpoint diverged from the marketplace card "
"profile_summary for the same canonical id. Both MUST resolve "
"the session profile identically (brain_tools.union_snapshot) "
"and run the SAME build_scorecard.\n " + "\n ".join(mismatches[:30]),
)
if __name__ == "__main__":
unittest.main()