import json
import uuid
from pathlib import Path
import pandas as pd
import plotly.express as px
import plotly.io as pio
from fastapi import APIRouter, HTTPException
from jinja2 import Environment, FileSystemLoader, select_autoescape
from app.config import get_settings
router = APIRouter(prefix="/api/reports", tags=["reports"])
_DATA_DIR = Path(__file__).parent / "data"
_TEMPLATES_DIR = Path(__file__).parent / "templates"
_env = Environment(
loader=FileSystemLoader(str(_TEMPLATES_DIR)),
autoescape=select_autoescape(["html"]),
)
CARE_REPORT_PRESET = [
{"element_type": "header", "parameters": {"title": "Care Report"}},
{
"element_type": "overview",
"parameters": {
"prompt": "Summarize this email exchange in 3 bullet points: ",
"text_file": "./data/combined_email.txt",
"title": "Customer Overview",
},
},
{"element_type": "claim_review_chart", "parameters": {}},
{
"element_type": "table",
"parameters": {
"csv_file": "./data/aetna_claim_review_summary.csv",
"title": "Claim Review Summary",
},
},
]
def _resolve_path(relative: str) -> Path:
cleaned = relative.lstrip("./")
if cleaned.startswith("data/"):
cleaned = cleaned[len("data/"):]
return _DATA_DIR / cleaned
def _render_header(params: dict) -> str:
title = params.get("title", "")
return f"
{title}
"
def _render_overview(params: dict) -> str:
title = params.get("title", "Overview")
text_file = _resolve_path(params.get("text_file", ""))
if not text_file.exists():
raise HTTPException(status_code=400, detail=f"text_file not found: {params.get('text_file')}")
text = text_file.read_text(encoding="utf-8")
paragraphs = "".join(f"{line}
" for line in text.splitlines() if line.strip())
return f"{title}
{paragraphs}
"
def _render_claim_chart() -> str:
df = pd.read_csv(_DATA_DIR / "aetna_claim_review_summary.csv")
fig = px.bar(
df,
x="CPT_Code",
y=["Charged_Amount", "Allowed_Amount", "Patient_Responsibility"],
barmode="group",
title="Claim Review by CPT Code",
color_discrete_sequence=["#0A1F44", "#1E4FA8", "#FF7A00"],
)
return pio.to_html(fig, full_html=False, include_plotlyjs="cdn")
def _render_table(params: dict) -> str:
title = params.get("title", "")
csv_file = _resolve_path(params.get("csv_file", ""))
if not csv_file.exists():
raise HTTPException(status_code=400, detail=f"csv_file not found: {params.get('csv_file')}")
df = pd.read_csv(csv_file)
return f"{title}
" + df.to_html(index=False, classes="report-table", border=0)
_RENDERERS = {
"header": lambda params: _render_header(params),
"overview": lambda params: _render_overview(params),
"claim_review_chart": lambda _params: _render_claim_chart(),
"table": lambda params: _render_table(params),
}
@router.post("/generate-report")
def generate_report(payload: dict):
try:
layout = json.loads(payload["layout_config"])
except (KeyError, json.JSONDecodeError) as exc:
raise HTTPException(status_code=400, detail=f"Invalid layout_config: {exc}")
expanded: list[dict] = []
for item in layout:
if item == "care_report":
expanded.extend(CARE_REPORT_PRESET)
elif isinstance(item, dict):
expanded.append(item)
else:
raise HTTPException(status_code=400, detail=f"Unknown layout item: {item!r}")
parts: list[str] = []
for el in expanded:
kind = el.get("element_type")
if kind not in _RENDERERS:
raise HTTPException(status_code=400, detail=f"Unknown element_type: {kind}")
parts.append(_RENDERERS[kind](el.get("parameters", {})))
template = _env.get_template("report.html")
html = template.render(content="\n".join(parts))
settings = get_settings()
output_dir = Path(settings.reports_output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
report_id = f"{uuid.uuid4().hex[:12]}.html"
(output_dir / report_id).write_text(html, encoding="utf-8")
return {"public_url": f"{settings.base_url}/api/reports/output/{report_id}"}