ai_econsult_demo / src /billing.py
Cardiosense-AG's picture
Create billing.py
ae6ca67 verified
raw
history blame
3.74 kB
# src/billing.py
from __future__ import annotations
"""Billing helpers: CPT autosuggest and 837 claim builder (demo-only)."""
from dataclasses import dataclass
from typing import List, Dict, Any, Optional
from datetime import datetime, timezone
import json
from pathlib import Path
from .config import CPT, PROVIDER
@dataclass
class CPTSuggestion:
code: str
rate: float
descriptor: str
eligible: bool
why: str
def _why_for_minutes(code: str, minutes: int, spoke: bool) -> str:
if code == "99451":
if not spoke:
return "Eligible when no live interprofessional call; written report only; requires β‰₯5 minutes."
return "Ineligible: 99451 is for written report without live discussion; 'spoke' was checked."
ranges = {
"99446": (5, 10),
"99447": (11, 20),
"99448": (21, 30),
"99449": (31, 10**9),
}
lo, hi = ranges[code]
base = f"Eligible minutes window {lo}–{hi if hi<10**9 else '∞'}; requires live discussion with referring clinician."
if not spoke:
return f"Ineligible: requires live discussion (spoke=False). Suggested 99451 if β‰₯5 minutes."
if minutes < lo:
return f"Ineligible for {code}: minutes below required threshold ({minutes}<{lo})."
return base
def autosuggest_cpt(minutes: int, spoke: bool) -> List[CPTSuggestion]:
"""Suggest CPT codes based on minutes and whether a live discussion occurred."""
suggestions: List[CPTSuggestion] = []
if spoke:
for code in ["99446", "99447", "99448", "99449"]:
lo_hi = {
"99446": (5, 10),
"99447": (11, 20),
"99448": (21, 30),
"99449": (31, 10**9),
}[code]
eligible = (minutes >= lo_hi[0]) and (minutes <= lo_hi[1])
suggestions.append(CPTSuggestion(
code=code,
rate=float(CPT[code]["rate"]),
descriptor=str(CPT[code]["descriptor"]),
eligible=eligible,
why=_why_for_minutes(code, minutes, spoke),
))
else:
eligible = minutes >= 5
suggestions.append(CPTSuggestion(
code="99451",
rate=float(CPT["99451"]["rate"]),
descriptor=str(CPT["99451"]["descriptor"]),
eligible=eligible,
why=_why_for_minutes("99451", minutes, spoke),
))
return suggestions
def build_837_claim(case: Dict[str, Any], code: str, rate: float, minutes: int,
spoke: bool, attested: bool, *, template_path: Optional[Path] = None) -> Dict[str, Any]:
"""Build a demo 837 claim JSON from a case and billing selection."""
now = datetime.now(timezone.utc).isoformat()
# Load optional template
template: Dict[str, Any] = {}
try:
if template_path and Path(template_path).exists():
template = json.loads(Path(template_path).read_text(encoding="utf-8"))
except Exception:
template = {}
claim_id = f"EC-{case.get('case_id','UNKNOWN')}-{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}"
out: Dict[str, Any] = {
"schema_version": 2,
"demo_only": True,
"claim_id": claim_id,
"created_at": now,
"provider": PROVIDER,
"patient": case.get("patient", {}),
"referring": {"name": (case.get("referrer") or {}).get("name", "Referring Clinician")},
"service": {
"cpt_code": code,
"minutes": minutes,
"spoke_to_referrer": bool(spoke),
"amount": float(rate),
},
"attested": bool(attested),
"source_case_id": case.get("case_id"),
}
out = {**template, **out}
return out