Ali2206 commited on
Commit
51ace9a
·
verified ·
1 Parent(s): 6109265

Update api/routes/pdf.py

Browse files
Files changed (1) hide show
  1. api/routes/pdf.py +31 -38
api/routes/pdf.py CHANGED
@@ -22,17 +22,14 @@ 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
- 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,12 +38,9 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
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,6 +54,7 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
60
  content.append(" & ".join(row) + " \\\\")
61
  return "\n".join(content)
62
 
 
63
  notes = patient.get("notes", [])
64
  notes_content = prepare_table_content(
65
  [{
@@ -71,6 +66,7 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
71
  "No notes available"
72
  )
73
 
 
74
  conditions = patient.get("conditions", [])
75
  conditions_content = prepare_table_content(
76
  [{
@@ -84,6 +80,7 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
84
  "No conditions available"
85
  )
86
 
 
87
  medications = patient.get("medications", [])
88
  medications_content = prepare_table_content(
89
  [{
@@ -97,6 +94,7 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
97
  "No medications available"
98
  )
99
 
 
100
  encounters = patient.get("encounters", [])
101
  encounters_content = prepare_table_content(
102
  [{
@@ -110,6 +108,7 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
110
  "No encounters available"
111
  )
112
 
 
113
  latex_template = Template(r"""
114
  \documentclass[a4paper,12pt]{article}
115
  \usepackage[utf8]{inputenc}
@@ -126,15 +125,12 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
126
  \fancyhead[L]{Patient Report}
127
  \fancyhead[R]{Generated: \today}
128
  \fancyfoot[C]{\thepage}
129
-
130
  \begin{document}
131
-
132
  \begin{center}
133
  \Large\textbf{Patient Medical Report} \\
134
  \vspace{0.2cm}
135
  \textit{Generated on $generated_on}
136
  \end{center}
137
-
138
  \section*{Demographics}
139
  \begin{itemize}
140
  \item \textbf{FHIR ID:} $fhir_id
@@ -146,7 +142,6 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
146
  \item \textbf{Marital Status:} $marital_status
147
  \item \textbf{Language:} $language
148
  \end{itemize}
149
-
150
  \section*{Clinical Notes}
151
  \begin{longtable}[l]{>{\raggedright\arraybackslash}p{3.5cm}>{\raggedright\arraybackslash}p{3cm}>{\raggedright\arraybackslash}p{6.5cm}}
152
  \caption{Clinical Notes} \\
@@ -156,7 +151,6 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
156
  $notes
157
  \bottomrule
158
  \end{longtable}
159
-
160
  \section*{Conditions}
161
  \begin{longtable}[l]{>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{3cm}>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{3.5cm}>{\raggedright\arraybackslash}p{3cm}}
162
  \caption{Conditions} \\
@@ -166,7 +160,6 @@ $notes
166
  $conditions
167
  \bottomrule
168
  \end{longtable}
169
-
170
  \section*{Medications}
171
  \begin{longtable}[l]{>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{4cm}>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{3.5cm}>{\raggedright\arraybackslash}p{3cm}}
172
  \caption{Medications} \\
@@ -176,7 +169,6 @@ $conditions
176
  $medications
177
  \bottomrule
178
  \end{longtable}
179
-
180
  \section*{Encounters}
181
  \begin{longtable}[l]{>{\raggedright\arraybackslash}p{2.5cm}>{\raggedright\arraybackslash}p{4.5cm}>{\raggedright\arraybackslash}p{2.5cm}>{\raggedright\arraybackslash}p{4.5cm}>{\raggedright\arraybackslash}p{3.5cm}}
182
  \caption{Encounters} \\
@@ -186,11 +178,11 @@ $medications
186
  $encounters
187
  \bottomrule
188
  \end{longtable}
189
-
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,6 +206,7 @@ $encounters
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,6 +215,7 @@ $encounters
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,44 +226,43 @@ $encounters
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
 
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
+ # Suppress logging for this route
26
+ logger.setLevel(logging.CRITICAL)
 
 
 
 
 
 
 
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
 
39
  patient = await patients_collection.find_one(query)
40
  if not patient:
41
+ raise HTTPException(status_code=404, detail="Patient not found")
 
 
 
 
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
  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
  "No notes available"
67
  )
68
 
69
+ # Conditions table
70
  conditions = patient.get("conditions", [])
71
  conditions_content = prepare_table_content(
72
  [{
 
80
  "No conditions available"
81
  )
82
 
83
+ # Medications table
84
  medications = patient.get("medications", [])
85
  medications_content = prepare_table_content(
86
  [{
 
94
  "No medications available"
95
  )
96
 
97
+ # Encounters table
98
  encounters = patient.get("encounters", [])
99
  encounters_content = prepare_table_content(
100
  [{
 
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}
 
125
  \fancyhead[L]{Patient Report}
126
  \fancyhead[R]{Generated: \today}
127
  \fancyfoot[C]{\thepage}
 
128
  \begin{document}
 
129
  \begin{center}
130
  \Large\textbf{Patient Medical Report} \\
131
  \vspace{0.2cm}
132
  \textit{Generated on $generated_on}
133
  \end{center}
 
134
  \section*{Demographics}
135
  \begin{itemize}
136
  \item \textbf{FHIR ID:} $fhir_id
 
142
  \item \textbf{Marital Status:} $marital_status
143
  \item \textbf{Language:} $language
144
  \end{itemize}
 
145
  \section*{Clinical Notes}
146
  \begin{longtable}[l]{>{\raggedright\arraybackslash}p{3.5cm}>{\raggedright\arraybackslash}p{3cm}>{\raggedright\arraybackslash}p{6.5cm}}
147
  \caption{Clinical Notes} \\
 
151
  $notes
152
  \bottomrule
153
  \end{longtable}
 
154
  \section*{Conditions}
155
  \begin{longtable}[l]{>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{3cm}>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{3.5cm}>{\raggedright\arraybackslash}p{3cm}}
156
  \caption{Conditions} \\
 
160
  $conditions
161
  \bottomrule
162
  \end{longtable}
 
163
  \section*{Medications}
164
  \begin{longtable}[l]{>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{4cm}>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{3.5cm}>{\raggedright\arraybackslash}p{3cm}}
165
  \caption{Medications} \\
 
169
  $medications
170
  \bottomrule
171
  \end{longtable}
 
172
  \section*{Encounters}
173
  \begin{longtable}[l]{>{\raggedright\arraybackslash}p{2.5cm}>{\raggedright\arraybackslash}p{4.5cm}>{\raggedright\arraybackslash}p{2.5cm}>{\raggedright\arraybackslash}p{4.5cm}>{\raggedright\arraybackslash}p{3.5cm}}
174
  \caption{Encounters} \\
 
178
  $encounters
179
  \bottomrule
180
  \end{longtable}
 
181
  \end{document}
182
  """)
183
 
184
+ # Set the generated_on date to 02:54 PM CET, May 17, 2025
185
+ 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")
186
 
187
  latex_filled = latex_template.substitute(
188
  generated_on=generated_on,
 
206
  encounters=encounters_content
207
  )
208
 
209
+ # Compile LaTeX in a temporary directory
210
  with TemporaryDirectory() as tmpdir:
211
  tex_path = os.path.join(tmpdir, "report.tex")
212
  pdf_path = os.path.join(tmpdir, "report.pdf")
 
215
  f.write(latex_filled)
216
 
217
  try:
218
+ # Run latexmk twice to ensure proper table rendering
219
  for _ in range(2):
220
  result = subprocess.run(
221
  ["latexmk", "-pdf", "-interaction=nonstopmode", tex_path],
 
226
  )
227
 
228
  if result.returncode != 0:
 
229
  raise HTTPException(
230
+ status_code=500,
231
+ detail=f"LaTeX compilation failed: stdout={result.stdout}, stderr={result.stderr}"
232
  )
233
 
234
  except subprocess.CalledProcessError as e:
 
235
  raise HTTPException(
236
+ status_code=500,
237
+ detail=f"LaTeX compilation failed: stdout={e.stdout}, stderr={e.stderr}"
238
  )
239
 
240
  if not os.path.exists(pdf_path):
 
241
  raise HTTPException(
242
+ status_code=500,
243
  detail="PDF file was not generated"
244
  )
245
 
246
  with open(pdf_path, "rb") as f:
247
  pdf_bytes = f.read()
248
 
249
+ response = Response(
 
250
  content=pdf_bytes,
251
  media_type="application/pdf",
252
  headers={"Content-Disposition": f"attachment; filename=patient_{patient.get('fhir_id', 'unknown')}_report.pdf"}
253
  )
254
+ return response
255
 
256
+ except HTTPException as http_error:
257
+ raise http_error
258
  except Exception as e:
 
259
  raise HTTPException(
260
+ status_code=500,
261
  detail=f"Unexpected error generating PDF: {str(e)}"
262
  )
263
+ finally:
264
+ # Restore the logger level for other routes
265
+ logger.setLevel(logging.INFO)
266
 
267
+ # Export the router as 'pdf' for api.__init__.py
268
  pdf = router