""" Payer -> Group Name lookup for the categorization step. Exact-string match against a small hand-edited table (`payer_groups.json`, next to this package) — no fuzzy or substring matching. eCW's own PAYER values already differ by case and spacing for what is really the same carrier (see `tests/test_payer_groups.py`'s docstring), so "close enough" matching has no safe way to pick which known string a new one is closest to. An unrecognized payer must come back as None and get a human's attention — never a guess, on billing data. Since 2026-10-06 the table is the team's own template: the Payer Name -> Payer Group Name pairs of their Claim Status Dashboard, imported with `tools/import_payer_groups.py` (re-run it when the dashboard changes). Adding a payer by hand is still a one-line edit to `payer_groups.json`. """ import json from pathlib import Path _CONFIG_PATH = Path(__file__).resolve().parent.parent / "payer_groups.json" def load_mapping(path=None) -> dict: """The payer -> group table. `path` defaults to the shipped config.""" p = Path(path) if path else _CONFIG_PATH if not p.exists(): raise FileNotFoundError( f"{p} not found — the payer group table is missing") data = json.loads(p.read_text(encoding="utf-8")) if not isinstance(data, dict): raise ValueError( f"{p} does not contain a JSON object (payer -> group name) — " f"got {type(data).__name__}") return data def classify(payer: str, mapping: dict) -> str: """The Group Name for this exact PAYER string, or None if unmapped.""" return mapping.get((payer or "").strip()) # ── Group names the CONDITIONS decide on ───────────────────────────────────── # Since 2026-10-06 `payer_groups.json` is the team's own template — the # "Payer Name" -> "Payer Group Name" pairs of their Claim Status Dashboard # (tools/import_payer_groups.py) — so its group names are the dashboard's # ("Commercial Ins", "RAE (Behavioral Health)", "Dental - CHP+/DentaQuest"...) # and that is what the Group Name columns show. The condition modules were # written against the earlier, shorter names, which stay the vocabulary of # their rules; this table translates. A name not listed (including every # earlier short name) passes through unchanged. RULE_GROUP = { "Commercial Ins": "Commercial", "RAE (Behavioral Health)": "RAE payer", "Regional Accountable Entity-RAE (Reporting Group)": "RAE payer", "Dental - CHP+/DentaQuest": "Dentaquest", "Self Pay": "Program", # the clinic's programs (Outreach, slides…) "Payer Not In Group": None, # the dashboard's own "unclassified" } # A payer whose condition rule differs from its dashboard group. Empty since # 2026-10-21: the Medicare Advantage plans (AARP Medicare Advantage, Aetna # Medicare ADV) were kept on Medicare here until the operator ruled; the # ruling is "follow the dashboard", so they are Commercial for every # condition (#21 95/93 not #22 G2025; no #14, #19, #27-#29; #18 / #20 add). RULE_OVERRIDES = {} def rule_group(payer: str, mapping: dict) -> str: """ The group a condition's rule should use for this PAYER, or None if unmapped: the payer's own override if it has one, else its Group Name translated through RULE_GROUP. """ key = (payer or "").strip() if key in RULE_OVERRIDES: return RULE_OVERRIDES[key] group = classify(key, mapping) if group is None: return None return RULE_GROUP.get(group, group)