Spaces:
Sleeping
Sleeping
Update api/routes/pdf.py
Browse files- api/routes/pdf.py +30 -31
api/routes/pdf.py
CHANGED
|
@@ -22,14 +22,17 @@ router = APIRouter()
|
|
| 22 |
|
| 23 |
@router.get("/{patient_id}/pdf", response_class=Response)
|
| 24 |
async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get_current_user)):
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
try:
|
| 29 |
-
if current_user.get('role') not in ['doctor', 'admin']:
|
| 30 |
-
raise HTTPException(status_code=403, detail="Only clinicians can generate patient PDFs")
|
| 31 |
-
|
| 32 |
-
# Determine if patient_id is ObjectId or fhir_id
|
| 33 |
try:
|
| 34 |
obj_id = ObjectId(patient_id)
|
| 35 |
query = {"$or": [{"_id": obj_id}, {"fhir_id": patient_id}]}
|
|
@@ -38,9 +41,12 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
|
|
| 38 |
|
| 39 |
patient = await patients_collection.find_one(query)
|
| 40 |
if not patient:
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
# Prepare table content with proper LaTeX formatting
|
| 44 |
def prepare_table_content(items, columns, default_message):
|
| 45 |
if not items:
|
| 46 |
return f"\\multicolumn{{{columns}}}{{l}}{{{default_message}}} \\\\"
|
|
@@ -54,7 +60,6 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
|
|
| 54 |
content.append(" & ".join(row) + " \\\\")
|
| 55 |
return "\n".join(content)
|
| 56 |
|
| 57 |
-
# Notes table
|
| 58 |
notes = patient.get("notes", [])
|
| 59 |
notes_content = prepare_table_content(
|
| 60 |
[{
|
|
@@ -66,7 +71,6 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
|
|
| 66 |
"No notes available"
|
| 67 |
)
|
| 68 |
|
| 69 |
-
# Conditions table
|
| 70 |
conditions = patient.get("conditions", [])
|
| 71 |
conditions_content = prepare_table_content(
|
| 72 |
[{
|
|
@@ -80,7 +84,6 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
|
|
| 80 |
"No conditions available"
|
| 81 |
)
|
| 82 |
|
| 83 |
-
# Medications table
|
| 84 |
medications = patient.get("medications", [])
|
| 85 |
medications_content = prepare_table_content(
|
| 86 |
[{
|
|
@@ -94,7 +97,6 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
|
|
| 94 |
"No medications available"
|
| 95 |
)
|
| 96 |
|
| 97 |
-
# Encounters table
|
| 98 |
encounters = patient.get("encounters", [])
|
| 99 |
encounters_content = prepare_table_content(
|
| 100 |
[{
|
|
@@ -108,7 +110,6 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
|
|
| 108 |
"No encounters available"
|
| 109 |
)
|
| 110 |
|
| 111 |
-
# LaTeX template with improved table formatting
|
| 112 |
latex_template = Template(r"""
|
| 113 |
\documentclass[a4paper,12pt]{article}
|
| 114 |
\usepackage[utf8]{inputenc}
|
|
@@ -189,8 +190,7 @@ $encounters
|
|
| 189 |
\end{document}
|
| 190 |
""")
|
| 191 |
|
| 192 |
-
|
| 193 |
-
generated_on = datetime.strptime("2025-05-17 14:54:00+02:00", "%Y-%m-%d %H:%M:%S%z").strftime("%A, %B %d, %Y at %I:%M %p %Z")
|
| 194 |
|
| 195 |
latex_filled = latex_template.substitute(
|
| 196 |
generated_on=generated_on,
|
|
@@ -214,7 +214,6 @@ $encounters
|
|
| 214 |
encounters=encounters_content
|
| 215 |
)
|
| 216 |
|
| 217 |
-
# Compile LaTeX in a temporary directory
|
| 218 |
with TemporaryDirectory() as tmpdir:
|
| 219 |
tex_path = os.path.join(tmpdir, "report.tex")
|
| 220 |
pdf_path = os.path.join(tmpdir, "report.pdf")
|
|
@@ -223,7 +222,6 @@ $encounters
|
|
| 223 |
f.write(latex_filled)
|
| 224 |
|
| 225 |
try:
|
| 226 |
-
# Run latexmk twice to ensure proper table rendering
|
| 227 |
for _ in range(2):
|
| 228 |
result = subprocess.run(
|
| 229 |
["latexmk", "-pdf", "-interaction=nonstopmode", tex_path],
|
|
@@ -234,43 +232,44 @@ $encounters
|
|
| 234 |
)
|
| 235 |
|
| 236 |
if result.returncode != 0:
|
|
|
|
| 237 |
raise HTTPException(
|
| 238 |
-
status_code=
|
| 239 |
-
detail=f"LaTeX compilation failed:
|
| 240 |
)
|
| 241 |
|
| 242 |
except subprocess.CalledProcessError as e:
|
|
|
|
| 243 |
raise HTTPException(
|
| 244 |
-
status_code=
|
| 245 |
-
detail=f"LaTeX compilation failed:
|
| 246 |
)
|
| 247 |
|
| 248 |
if not os.path.exists(pdf_path):
|
|
|
|
| 249 |
raise HTTPException(
|
| 250 |
-
status_code=
|
| 251 |
detail="PDF file was not generated"
|
| 252 |
)
|
| 253 |
|
| 254 |
with open(pdf_path, "rb") as f:
|
| 255 |
pdf_bytes = f.read()
|
| 256 |
|
| 257 |
-
|
|
|
|
| 258 |
content=pdf_bytes,
|
| 259 |
media_type="application/pdf",
|
| 260 |
headers={"Content-Disposition": f"attachment; filename=patient_{patient.get('fhir_id', 'unknown')}_report.pdf"}
|
| 261 |
)
|
| 262 |
-
return response
|
| 263 |
|
| 264 |
-
except HTTPException
|
| 265 |
-
raise
|
| 266 |
except Exception as e:
|
|
|
|
| 267 |
raise HTTPException(
|
| 268 |
-
status_code=
|
| 269 |
detail=f"Unexpected error generating PDF: {str(e)}"
|
| 270 |
)
|
| 271 |
-
finally:
|
| 272 |
-
# Restore the logger level for other routes
|
| 273 |
-
logger.setLevel(logging.INFO)
|
| 274 |
|
| 275 |
-
# Export the router
|
| 276 |
pdf = router
|
|
|
|
| 22 |
|
| 23 |
@router.get("/{patient_id}/pdf", response_class=Response)
|
| 24 |
async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get_current_user)):
|
| 25 |
+
logger.info(f"Generating PDF for patient {patient_id} by {current_user.get('email')}")
|
| 26 |
+
|
| 27 |
+
if current_user.get('role') not in ['doctor', 'admin']:
|
| 28 |
+
logger.warning(f"Unauthorized PDF generation attempt by {current_user.get('email')}")
|
| 29 |
+
raise HTTPException(
|
| 30 |
+
status_code=status.HTTP_403_FORBIDDEN,
|
| 31 |
+
detail="Only clinicians can generate patient PDFs",
|
| 32 |
+
headers={"WWW-Authenticate": "Bearer"}
|
| 33 |
+
)
|
| 34 |
|
| 35 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
try:
|
| 37 |
obj_id = ObjectId(patient_id)
|
| 38 |
query = {"$or": [{"_id": obj_id}, {"fhir_id": patient_id}]}
|
|
|
|
| 41 |
|
| 42 |
patient = await patients_collection.find_one(query)
|
| 43 |
if not patient:
|
| 44 |
+
logger.warning(f"Patient not found: {patient_id}")
|
| 45 |
+
raise HTTPException(
|
| 46 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 47 |
+
detail="Patient not found"
|
| 48 |
+
)
|
| 49 |
|
|
|
|
| 50 |
def prepare_table_content(items, columns, default_message):
|
| 51 |
if not items:
|
| 52 |
return f"\\multicolumn{{{columns}}}{{l}}{{{default_message}}} \\\\"
|
|
|
|
| 60 |
content.append(" & ".join(row) + " \\\\")
|
| 61 |
return "\n".join(content)
|
| 62 |
|
|
|
|
| 63 |
notes = patient.get("notes", [])
|
| 64 |
notes_content = prepare_table_content(
|
| 65 |
[{
|
|
|
|
| 71 |
"No notes available"
|
| 72 |
)
|
| 73 |
|
|
|
|
| 74 |
conditions = patient.get("conditions", [])
|
| 75 |
conditions_content = prepare_table_content(
|
| 76 |
[{
|
|
|
|
| 84 |
"No conditions available"
|
| 85 |
)
|
| 86 |
|
|
|
|
| 87 |
medications = patient.get("medications", [])
|
| 88 |
medications_content = prepare_table_content(
|
| 89 |
[{
|
|
|
|
| 97 |
"No medications available"
|
| 98 |
)
|
| 99 |
|
|
|
|
| 100 |
encounters = patient.get("encounters", [])
|
| 101 |
encounters_content = prepare_table_content(
|
| 102 |
[{
|
|
|
|
| 110 |
"No encounters available"
|
| 111 |
)
|
| 112 |
|
|
|
|
| 113 |
latex_template = Template(r"""
|
| 114 |
\documentclass[a4paper,12pt]{article}
|
| 115 |
\usepackage[utf8]{inputenc}
|
|
|
|
| 190 |
\end{document}
|
| 191 |
""")
|
| 192 |
|
| 193 |
+
generated_on = datetime.utcnow().strftime("%A, %B %d, %Y at %I:%M %p UTC")
|
|
|
|
| 194 |
|
| 195 |
latex_filled = latex_template.substitute(
|
| 196 |
generated_on=generated_on,
|
|
|
|
| 214 |
encounters=encounters_content
|
| 215 |
)
|
| 216 |
|
|
|
|
| 217 |
with TemporaryDirectory() as tmpdir:
|
| 218 |
tex_path = os.path.join(tmpdir, "report.tex")
|
| 219 |
pdf_path = os.path.join(tmpdir, "report.pdf")
|
|
|
|
| 222 |
f.write(latex_filled)
|
| 223 |
|
| 224 |
try:
|
|
|
|
| 225 |
for _ in range(2):
|
| 226 |
result = subprocess.run(
|
| 227 |
["latexmk", "-pdf", "-interaction=nonstopmode", tex_path],
|
|
|
|
| 232 |
)
|
| 233 |
|
| 234 |
if result.returncode != 0:
|
| 235 |
+
logger.error(f"LaTeX compilation failed: stdout={result.stdout}, stderr={result.stderr}")
|
| 236 |
raise HTTPException(
|
| 237 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 238 |
+
detail=f"LaTeX compilation failed: {result.stderr}"
|
| 239 |
)
|
| 240 |
|
| 241 |
except subprocess.CalledProcessError as e:
|
| 242 |
+
logger.error(f"LaTeX compilation error: stdout={e.stdout}, stderr={e.stderr}")
|
| 243 |
raise HTTPException(
|
| 244 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 245 |
+
detail=f"LaTeX compilation failed: {e.stderr}"
|
| 246 |
)
|
| 247 |
|
| 248 |
if not os.path.exists(pdf_path):
|
| 249 |
+
logger.error("PDF file was not generated")
|
| 250 |
raise HTTPException(
|
| 251 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 252 |
detail="PDF file was not generated"
|
| 253 |
)
|
| 254 |
|
| 255 |
with open(pdf_path, "rb") as f:
|
| 256 |
pdf_bytes = f.read()
|
| 257 |
|
| 258 |
+
logger.info(f"PDF generated successfully for patient {patient_id} by {current_user.get('email')}")
|
| 259 |
+
return Response(
|
| 260 |
content=pdf_bytes,
|
| 261 |
media_type="application/pdf",
|
| 262 |
headers={"Content-Disposition": f"attachment; filename=patient_{patient.get('fhir_id', 'unknown')}_report.pdf"}
|
| 263 |
)
|
|
|
|
| 264 |
|
| 265 |
+
except HTTPException:
|
| 266 |
+
raise
|
| 267 |
except Exception as e:
|
| 268 |
+
logger.error(f"Unexpected error generating PDF for patient {patient_id}: {str(e)}")
|
| 269 |
raise HTTPException(
|
| 270 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 271 |
detail=f"Unexpected error generating PDF: {str(e)}"
|
| 272 |
)
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
+
# Export the router
|
| 275 |
pdf = router
|