"""Phase 2 — Clinic analytics from anonymized ohip_feedback approvals. No MOH remittance required. Underbilling proxy = optimized − selected when Phase 1 revenue fields were logged at approve time. """ from __future__ import annotations from collections import Counter, defaultdict from typing import Any from . import feedback from .opensearch_client import get_client def _num(value: Any) -> float | None: if value is None: return None try: return float(value) except (TypeError, ValueError): return None def clinic_analytics(*, size: int = 500) -> dict: """Aggregate clinic KPIs from recent approval events.""" client = get_client() if not client.indices.exists(index=feedback.FEEDBACK_INDEX): return _empty() resp = client.search( index=feedback.FEEDBACK_INDEX, body={ "size": size, "sort": [{"created_at": {"order": "desc"}}], "_source": { "excludes": ["case_vector"], }, "query": {"match_all": {}}, }, ) hits = [h.get("_source") or {} for h in resp.get("hits", {}).get("hits", [])] return _aggregate(hits) def _empty() -> dict: return { "total_cases": 0, "agreed": 0, "disagreed": 0, "agreement_rate": None, "override_rate": None, "avg_selected_cad": None, "avg_optimized_cad": None, "avg_difference_cad": None, "total_underbilling_cad": 0.0, "cases_with_revenue": 0, "avg_revenue_per_encounter_cad": None, "risk_levels": {}, "top_approved_codes": [], "top_override_codes": [], "top_missed_opportunity_proxy": [], "by_encounter_type": [], "recent_cases": [], } def _aggregate(hits: list[dict]) -> dict: if not hits: return _empty() total = len(hits) agreed_n = sum(1 for h in hits if h.get("agreed") is True) disagreed_n = total - agreed_n approved_counter: Counter[str] = Counter() override_counter: Counter[str] = Counter() risk_counter: Counter[str] = Counter() encounter_stats: dict[str, dict[str, float | int]] = defaultdict( lambda: {"cases": 0, "agreed": 0, "selected_sum": 0.0, "diff_sum": 0.0, "diff_n": 0} ) selected_vals: list[float] = [] optimized_vals: list[float] = [] diff_vals: list[float] = [] # Proxy for "missed": codes in ai_top that were not approved when disagreed, # or positive difference cases where optimized > selected. missed_proxy: Counter[str] = Counter() recent: list[dict] = [] for h in hits: for code in h.get("approved_codes") or []: approved_counter[code] += 1 for code in h.get("override_codes") or []: override_counter[code] += 1 risk = h.get("risk_level") if risk: risk_counter[str(risk)] += 1 enc = h.get("encounter_type") or "unspecified" est = encounter_stats[enc] est["cases"] = int(est["cases"]) + 1 if h.get("agreed") is True: est["agreed"] = int(est["agreed"]) + 1 sel = _num(h.get("selected_claim_cad")) opt = _num(h.get("optimized_claim_cad")) diff = _num(h.get("difference_cad")) if diff is None and sel is not None and opt is not None: diff = round(opt - sel, 2) if sel is not None: selected_vals.append(sel) est["selected_sum"] = float(est["selected_sum"]) + sel if opt is not None: optimized_vals.append(opt) if diff is not None: diff_vals.append(diff) est["diff_sum"] = float(est["diff_sum"]) + diff est["diff_n"] = int(est["diff_n"]) + 1 if diff > 0: # Rank-1 AI code not taken, or first unused AI suggestion as proxy. ai = h.get("ai_top_codes") or [] approved = set(h.get("approved_codes") or []) for code in ai: if code not in approved: missed_proxy[code] += 1 break recent.append( { "case_id": h.get("case_id"), "created_at": h.get("created_at"), "encounter_type": h.get("encounter_type"), "agreed": h.get("agreed"), "approved_codes": h.get("approved_codes") or [], "selected_claim_cad": sel, "optimized_claim_cad": opt, "difference_cad": diff, "risk_level": risk, } ) def avg(vals: list[float]) -> float | None: return round(sum(vals) / len(vals), 2) if vals else None underbilling = round(sum(d for d in diff_vals if d > 0), 2) by_encounter = [] for enc, est in sorted( encounter_stats.items(), key=lambda kv: int(kv[1]["cases"]), reverse=True ): cases = int(est["cases"]) by_encounter.append( { "encounter_type": enc, "cases": cases, "agreement_rate": round(int(est["agreed"]) / cases, 3) if cases else None, "avg_selected_cad": ( round(float(est["selected_sum"]) / cases, 2) if float(est["selected_sum"]) and cases else None ), "avg_difference_cad": ( round(float(est["diff_sum"]) / int(est["diff_n"]), 2) if int(est["diff_n"]) else None ), } ) return { "total_cases": total, "agreed": agreed_n, "disagreed": disagreed_n, "agreement_rate": round(agreed_n / total, 3) if total else None, "override_rate": round(disagreed_n / total, 3) if total else None, "avg_selected_cad": avg(selected_vals), "avg_optimized_cad": avg(optimized_vals), "avg_difference_cad": avg(diff_vals), "total_underbilling_cad": underbilling, "cases_with_revenue": len(selected_vals), "avg_revenue_per_encounter_cad": avg(selected_vals), "risk_levels": dict(risk_counter), "top_approved_codes": [ {"billing_code": c, "count": n} for c, n in approved_counter.most_common(10) ], "top_override_codes": [ {"billing_code": c, "count": n} for c, n in override_counter.most_common(10) ], "top_missed_opportunity_proxy": [ {"billing_code": c, "count": n} for c, n in missed_proxy.most_common(10) ], "by_encounter_type": by_encounter, "recent_cases": recent[:25], }