|
|
|
|
|
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() |
|
|
|
|
|
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 |
|
|
|