feat: Implement report generator service for medical reports
- Added ReportGeneratorService to handle generation of medical reports from uploaded files. - Implemented methods for processing Pnoe CSV data, generating graphs, and calculating analysis metrics. - Integrated Jinja2 for HTML report generation with customizable templates. - Added functionality to convert HTML content to PDF using Playwright. - Ensured proper directory structure for saving generated graphs and reports.
This commit is contained in:
+193
@@ -0,0 +1,193 @@
|
||||
"""
|
||||
FastAPI application for medical report generation.
|
||||
|
||||
This API provides a single endpoint that accepts all required files
|
||||
and patient information, then generates a comprehensive medical report.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, File, Form, HTTPException, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from services.report_generator import ReportGeneratorService
|
||||
|
||||
app = FastAPI(
|
||||
title="Medical Report Generation API",
|
||||
description="API for generating medical performance reports with analysis and graphs",
|
||||
version="2.0.0",
|
||||
)
|
||||
|
||||
# Define output directories
|
||||
GRAPHS_DIR = Path("graphs")
|
||||
GRAPHS_DIR.mkdir(exist_ok=True)
|
||||
|
||||
REPORTS_DIR = Path("reports")
|
||||
REPORTS_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# Initialize report generator service
|
||||
report_service = ReportGeneratorService(
|
||||
template_dir="app/report_gen",
|
||||
graphs_dir=str(GRAPHS_DIR),
|
||||
reports_dir=str(REPORTS_DIR),
|
||||
)
|
||||
|
||||
|
||||
class ReportResponse(BaseModel):
|
||||
message: str
|
||||
report_path: str
|
||||
graphs_generated: list
|
||||
analysis_data: dict
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""Root endpoint with API information"""
|
||||
return {
|
||||
"message": "Medical Report Generation API",
|
||||
"version": "2.0.0",
|
||||
"endpoints": {
|
||||
"generate_report": "POST /generate-report",
|
||||
"download_report": "GET /download-report/{filename}",
|
||||
"health": "GET /health",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Health check endpoint"""
|
||||
return {"status": "healthy", "service": "report-generation-api"}
|
||||
|
||||
|
||||
@app.post("/generate-report", response_model=ReportResponse)
|
||||
async def generate_report(
|
||||
patient_name: str = Form(..., description="Patient name"),
|
||||
age: int = Form(..., description="Patient age"),
|
||||
height: str = Form(..., description="Patient height (e.g., 5'4\")"),
|
||||
weight: str = Form(..., description="Patient weight (e.g., 123lbs)"),
|
||||
focus: str = Form(default="Endurance", description="Training focus"),
|
||||
session_id: str = Form(default="default", description="Session ID"),
|
||||
spirometry_pdf: UploadFile = File(..., description="Spirometry PDF file"),
|
||||
pnoe_csv: UploadFile = File(..., description="Pnoe CSV file"),
|
||||
seca_excel: UploadFile = File(..., description="SECA Excel file"),
|
||||
):
|
||||
"""
|
||||
Generate a comprehensive medical report from uploaded files.
|
||||
|
||||
This endpoint accepts all required files and patient information,
|
||||
processes the data, generates graphs, and returns a PDF report.
|
||||
|
||||
Args:
|
||||
spirometry_pdf: Spirometry PDF file
|
||||
pnoe_csv: Pnoe CSV data file
|
||||
seca_excel: SECA body composition Excel file
|
||||
patient_name: Name of the patient
|
||||
age: Patient age
|
||||
height: Patient height
|
||||
weight: Patient weight
|
||||
focus: Training focus (default: Endurance)
|
||||
session_id: Session identifier (default: default)
|
||||
|
||||
Returns:
|
||||
ReportResponse with report path, graphs generated, and analysis data
|
||||
"""
|
||||
# Validate file types
|
||||
if not spirometry_pdf.filename.endswith(".pdf"):
|
||||
raise HTTPException(status_code=400, detail="Spirometry file must be a PDF")
|
||||
|
||||
if not pnoe_csv.filename.endswith(".csv"):
|
||||
raise HTTPException(status_code=400, detail="Pnoe file must be a CSV")
|
||||
|
||||
if not seca_excel.filename.endswith((".xlsx", ".xls")):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="SECA file must be an Excel file (.xlsx or .xls)"
|
||||
)
|
||||
|
||||
# Create temporary directory for uploaded files
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Save uploaded files temporarily
|
||||
spirometry_path = temp_path / f"spirometry_{spirometry_pdf.filename}"
|
||||
pnoe_path = temp_path / f"pnoe_{pnoe_csv.filename}"
|
||||
seca_path = temp_path / f"seca_{seca_excel.filename}"
|
||||
|
||||
try:
|
||||
# Write files
|
||||
with open(spirometry_path, "wb") as f:
|
||||
shutil.copyfileobj(spirometry_pdf.file, f)
|
||||
|
||||
with open(pnoe_path, "wb") as f:
|
||||
shutil.copyfileobj(pnoe_csv.file, f)
|
||||
|
||||
with open(seca_path, "wb") as f:
|
||||
shutil.copyfileobj(seca_excel.file, f)
|
||||
|
||||
# Prepare patient information
|
||||
patient_info = {
|
||||
"patient_name": patient_name,
|
||||
"age": age,
|
||||
"height": height,
|
||||
"weight": weight,
|
||||
"focus": focus,
|
||||
"session_id": session_id,
|
||||
}
|
||||
|
||||
# Generate report using the service
|
||||
result = report_service.generate_report(
|
||||
spirometry_pdf_path=str(spirometry_path),
|
||||
pnoe_csv_path=str(pnoe_path),
|
||||
seca_excel_path=str(seca_path),
|
||||
patient_info=patient_info,
|
||||
)
|
||||
|
||||
return ReportResponse(
|
||||
message="Report generated successfully",
|
||||
report_path=result["report_path"],
|
||||
graphs_generated=result["graphs_generated"],
|
||||
analysis_data=result["analysis_data"],
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error generating report: {str(e)}",
|
||||
)
|
||||
finally:
|
||||
# Close file handles
|
||||
spirometry_pdf.file.close()
|
||||
pnoe_csv.file.close()
|
||||
seca_excel.file.close()
|
||||
|
||||
|
||||
@app.get("/download-report/{filename}")
|
||||
async def download_report(filename: str):
|
||||
"""
|
||||
Download a generated report.
|
||||
|
||||
Args:
|
||||
filename: Name of the report file
|
||||
|
||||
Returns:
|
||||
PDF file
|
||||
"""
|
||||
file_path = REPORTS_DIR / filename
|
||||
|
||||
if not file_path.exists():
|
||||
raise HTTPException(status_code=404, detail="Report not found")
|
||||
|
||||
return FileResponse(
|
||||
path=file_path,
|
||||
media_type="application/pdf",
|
||||
filename=filename,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
Reference in New Issue
Block a user