Update endpoints.py
Browse files- endpoints.py +249 -2
endpoints.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query, Form, Path
|
2 |
-
from fastapi.responses import StreamingResponse, JSONResponse
|
3 |
from fastapi.encoders import jsonable_encoder
|
4 |
from typing import Optional, List
|
5 |
from pydantic import BaseModel
|
@@ -14,6 +14,11 @@ from datetime import datetime
|
|
14 |
from bson import ObjectId
|
15 |
import asyncio
|
16 |
from bson.errors import InvalidId
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
# Define the ChatRequest model with an optional patient_id
|
19 |
class ChatRequest(BaseModel):
|
@@ -45,7 +50,7 @@ def create_router(agent, logger, patients_collection, analysis_collection, users
|
|
45 |
"status": "running",
|
46 |
"timestamp": datetime.utcnow().isoformat(),
|
47 |
"version": "2.6.0",
|
48 |
-
"features": ["chat", "voice-input", "voice-output", "patient-analysis", "report-upload"]
|
49 |
}
|
50 |
|
51 |
@router.get("/patients/analysis-results")
|
@@ -80,6 +85,248 @@ def create_router(agent, logger, patients_collection, analysis_collection, users
|
|
80 |
logger.error(f"Error fetching analysis results: {e}")
|
81 |
raise HTTPException(status_code=500, detail="Failed to retrieve analysis results")
|
82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
@router.post("/chat-stream")
|
84 |
async def chat_stream_endpoint(
|
85 |
request: ChatRequest,
|
|
|
1 |
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query, Form, Path
|
2 |
+
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
|
3 |
from fastapi.encoders import jsonable_encoder
|
4 |
from typing import Optional, List
|
5 |
from pydantic import BaseModel
|
|
|
14 |
from bson import ObjectId
|
15 |
import asyncio
|
16 |
from bson.errors import InvalidId
|
17 |
+
import base64
|
18 |
+
import os
|
19 |
+
from pathlib import Path as PathLib
|
20 |
+
import tempfile
|
21 |
+
import subprocess
|
22 |
|
23 |
# Define the ChatRequest model with an optional patient_id
|
24 |
class ChatRequest(BaseModel):
|
|
|
50 |
"status": "running",
|
51 |
"timestamp": datetime.utcnow().isoformat(),
|
52 |
"version": "2.6.0",
|
53 |
+
"features": ["chat", "voice-input", "voice-output", "patient-analysis", "report-upload", "patient-reports-pdf", "all-patients-reports-pdf"]
|
54 |
}
|
55 |
|
56 |
@router.get("/patients/analysis-results")
|
|
|
85 |
logger.error(f"Error fetching analysis results: {e}")
|
86 |
raise HTTPException(status_code=500, detail="Failed to retrieve analysis results")
|
87 |
|
88 |
+
@router.get("/patients/{patient_id}/analysis-reports/pdf")
|
89 |
+
async def get_patient_analysis_reports_pdf(
|
90 |
+
patient_id: str = Path(..., description="The ID of the patient"),
|
91 |
+
current_user: dict = Depends(get_current_user)
|
92 |
+
):
|
93 |
+
logger.info(f"Generating PDF analysis reports for patient {patient_id} by {current_user['email']}")
|
94 |
+
try:
|
95 |
+
# Fetch patient details
|
96 |
+
patient = await patients_collection.find_one({"fhir_id": patient_id})
|
97 |
+
if not patient:
|
98 |
+
raise HTTPException(status_code=404, detail="Patient not found")
|
99 |
+
|
100 |
+
# Fetch all analyses for the patient
|
101 |
+
analyses = await analysis_collection.find({"patient_id": patient_id}).sort("timestamp", -1).to_list(length=None)
|
102 |
+
if not analyses:
|
103 |
+
raise HTTPException(status_code=404, detail="No analysis reports found for this patient")
|
104 |
+
|
105 |
+
# Creating LaTeX document
|
106 |
+
latex_content = r"""
|
107 |
+
\documentclass[a4paper,12pt]{article}
|
108 |
+
\usepackage[utf8]{inputenc}
|
109 |
+
\usepackage[T1]{fontenc}
|
110 |
+
\usepackage{lmodern}
|
111 |
+
\usepackage{geometry}
|
112 |
+
\geometry{margin=1in}
|
113 |
+
\usepackage{enumitem}
|
114 |
+
\usepackage{fancyhdr}
|
115 |
+
\usepackage{lastpage}
|
116 |
+
\usepackage{datetime}
|
117 |
+
\pagestyle{fancy}
|
118 |
+
\fancyhf{}
|
119 |
+
\rhead{Patient Analysis Report}
|
120 |
+
\lhead{\today}
|
121 |
+
\cfoot{Page \thepage\ of \pageref{LastPage}}
|
122 |
+
\begin{document}
|
123 |
+
"""
|
124 |
+
|
125 |
+
# Adding patient information
|
126 |
+
patient_name = patient.get("full_name", "Unknown")
|
127 |
+
latex_content += f"""
|
128 |
+
\\section*{{Analysis Reports for {patient_name} (ID: {patient_id})}}
|
129 |
+
\\textbf{{Patient Name:}} {patient_name}\\\\
|
130 |
+
\\textbf{{Patient ID:}} {patient_id}\\\\
|
131 |
+
\\textbf{{Generated on:}} \\today\\\\
|
132 |
+
"""
|
133 |
+
|
134 |
+
# Adding analysis reports
|
135 |
+
for idx, analysis in enumerate(analyses, 1):
|
136 |
+
timestamp = analysis.get("timestamp", datetime.utcnow()).strftime("%Y-%m-%d %H:%M:%S")
|
137 |
+
suicide_risk = analysis.get("suicide_risk", {})
|
138 |
+
risk_level = suicide_risk.get("level", "none").capitalize()
|
139 |
+
risk_score = suicide_risk.get("score", 0.0)
|
140 |
+
risk_factors = ", ".join(suicide_risk.get("factors", [])) or "None"
|
141 |
+
|
142 |
+
latex_content += f"""
|
143 |
+
\\subsection*{{Report {idx} - {timestamp}}}
|
144 |
+
\\begin{{description}}
|
145 |
+
\\item[Risk Level:] {risk_level}
|
146 |
+
\\item[Risk Score:] {risk_score:.2f}
|
147 |
+
\\item[Risk Factors:] {risk_factors}
|
148 |
+
"""
|
149 |
+
|
150 |
+
# Adding additional analysis details if available
|
151 |
+
if analysis.get("summary"):
|
152 |
+
latex_content += f" \\item[Summary:] {analysis['summary']}\n"
|
153 |
+
if analysis.get("recommendations"):
|
154 |
+
recommendations = ", ".join(analysis["recommendations"]) if isinstance(analysis["recommendations"], list) else analysis["recommendations"]
|
155 |
+
latex_content += f" \\item[Recommendations:] {recommendations}\n"
|
156 |
+
|
157 |
+
latex_content += r"\end{description}\vspace{0.5cm}"
|
158 |
+
|
159 |
+
latex_content += r"\end{document}"
|
160 |
+
|
161 |
+
# Creating temporary directory for LaTeX compilation
|
162 |
+
with tempfile.TemporaryDirectory() as tmpdirname:
|
163 |
+
latex_file = PathLib(tmpdirname) / "report.tex"
|
164 |
+
pdf_file = PathLib(tmpdirname) / "report.pdf"
|
165 |
+
|
166 |
+
# Writing LaTeX content to file
|
167 |
+
with open(latex_file, "w", encoding="utf-8") as f:
|
168 |
+
f.write(latex_content)
|
169 |
+
|
170 |
+
# Compiling LaTeX to PDF using pdflatex
|
171 |
+
try:
|
172 |
+
subprocess.run(
|
173 |
+
["pdflatex", "-output-directory", tmpdirname, str(latex_file)],
|
174 |
+
check=True,
|
175 |
+
stdout=subprocess.PIPE,
|
176 |
+
stderr=subprocess.PIPE,
|
177 |
+
text=True
|
178 |
+
)
|
179 |
+
except subprocess.CalledProcessError as e:
|
180 |
+
logger.error(f"LaTeX compilation failed: {e.stderr}")
|
181 |
+
raise HTTPException(status_code=500, detail="Failed to generate PDF report")
|
182 |
+
|
183 |
+
if not pdf_file.exists():
|
184 |
+
raise HTTPException(status_code=500, detail="PDF generation failed")
|
185 |
+
|
186 |
+
# Reading the generated PDF
|
187 |
+
with open(pdf_file, "rb") as f:
|
188 |
+
pdf_content = f.read()
|
189 |
+
|
190 |
+
# Returning the PDF as a response
|
191 |
+
return FileResponse(
|
192 |
+
pdf_file,
|
193 |
+
media_type="application/pdf",
|
194 |
+
headers={"Content-Disposition": f"attachment; filename={patient_name.replace(' ', '_')}_{patient_id}_analysis_reports.pdf"}
|
195 |
+
)
|
196 |
+
|
197 |
+
except HTTPException:
|
198 |
+
raise
|
199 |
+
except Exception as e:
|
200 |
+
logger.error(f"Error generating PDF report for patient {patient_id}: {str(e)}")
|
201 |
+
raise HTTPException(status_code=500, detail=f"Failed to generate PDF report: {str(e)}")
|
202 |
+
|
203 |
+
@router.get("/patients/analysis-reports/all/pdf")
|
204 |
+
async def get_all_patients_analysis_reports_pdf(
|
205 |
+
current_user: dict = Depends(get_current_user)
|
206 |
+
):
|
207 |
+
logger.info(f"Generating PDF analysis reports for all patients by {current_user['email']}")
|
208 |
+
try:
|
209 |
+
# Fetch all patients
|
210 |
+
patients = await patients_collection.find().to_list(length=None)
|
211 |
+
if not patients:
|
212 |
+
raise HTTPException(status_code=404, detail="No patients found")
|
213 |
+
|
214 |
+
# Creating LaTeX document
|
215 |
+
latex_content = r"""
|
216 |
+
\documentclass[a4paper,12pt]{article}
|
217 |
+
\usepackage[utf8]{inputenc}
|
218 |
+
\usepackage[T1]{fontenc}
|
219 |
+
\usepackage{lmodern}
|
220 |
+
\usepackage{geometry}
|
221 |
+
\geometry{margin=1in}
|
222 |
+
\usepackage{enumitem}
|
223 |
+
\usepackage{fancyhdr}
|
224 |
+
\usepackage{lastpage}
|
225 |
+
\usepackage{datetime}
|
226 |
+
\pagestyle{fancy}
|
227 |
+
\fancyhf{}
|
228 |
+
\rhead{All Patients Analysis Reports}
|
229 |
+
\lhead{\today}
|
230 |
+
\cfoot{Page \thepage\ of \pageref{LastPage}}
|
231 |
+
\begin{document}
|
232 |
+
\section*{Analysis Reports for All Patients}
|
233 |
+
\textbf{Generated on:} \today\\\\
|
234 |
+
"""
|
235 |
+
|
236 |
+
# Flag to track if any analyses exist
|
237 |
+
has_analyses = False
|
238 |
+
|
239 |
+
# Iterate through each patient
|
240 |
+
for patient in patients:
|
241 |
+
patient_id = patient.get("fhir_id")
|
242 |
+
patient_name = patient.get("full_name", "Unknown")
|
243 |
+
|
244 |
+
# Fetch all analyses for the current patient
|
245 |
+
analyses = await analysis_collection.find({"patient_id": patient_id}).sort("timestamp", -1).to_list(length=None)
|
246 |
+
if not analyses:
|
247 |
+
continue # Skip patients with no analyses
|
248 |
+
|
249 |
+
has_analyses = True
|
250 |
+
|
251 |
+
# Adding patient section
|
252 |
+
latex_content += f"""
|
253 |
+
\\section*{{Patient: {patient_name} (ID: {patient_id})}}
|
254 |
+
\\textbf{{Patient Name:}} {patient_name}\\\\
|
255 |
+
\\textbf{{Patient ID:}} {patient_id}\\\\
|
256 |
+
"""
|
257 |
+
|
258 |
+
# Adding analysis reports for the patient
|
259 |
+
for idx, analysis in enumerate(analyses, 1):
|
260 |
+
timestamp = analysis.get("timestamp", datetime.utcnow()).strftime("%Y-%m-%d %H:%M:%S")
|
261 |
+
suicide_risk = analysis.get("suicide_risk", {})
|
262 |
+
risk_level = suicide_risk.get("level", "none").capitalize()
|
263 |
+
risk_score = suicide_risk.get("score", 0.0)
|
264 |
+
risk_factors = ", ".join(suicide_risk.get("factors", [])) or "None"
|
265 |
+
|
266 |
+
latex_content += f"""
|
267 |
+
\\subsection*{{Report {idx} - {timestamp}}}
|
268 |
+
\\begin{{description}}
|
269 |
+
\\item[Risk Level:] {risk_level}
|
270 |
+
\\item[Risk Score:] {risk_score:.2f}
|
271 |
+
\\item[Risk Factors:] {risk_factors}
|
272 |
+
"""
|
273 |
+
|
274 |
+
# Adding additional analysis details if available
|
275 |
+
if analysis.get("summary"):
|
276 |
+
latex_content += f" \\item[Summary:] {analysis['summary']}\n"
|
277 |
+
if analysis.get("recommendations"):
|
278 |
+
recommendations = ", ".join(analysis["recommendations"]) if isinstance(analysis["recommendations"], list) else analysis["recommendations"]
|
279 |
+
latex_content += f" \\item[Recommendations:] {recommendations}\n"
|
280 |
+
|
281 |
+
latex_content += r"\end{description}\vspace{0.5cm}"
|
282 |
+
|
283 |
+
latex_content += r"\end{document}"
|
284 |
+
|
285 |
+
if not has_analyses:
|
286 |
+
raise HTTPException(status_code=404, detail="No analysis reports found for any patients")
|
287 |
+
|
288 |
+
# Creating temporary directory for LaTeX compilation
|
289 |
+
with tempfile.TemporaryDirectory() as tmpdirname:
|
290 |
+
latex_file = PathLib(tmpdirname) / "all_reports.tex"
|
291 |
+
pdf_file = PathLib(tmpdirname) / "all_reports.pdf"
|
292 |
+
|
293 |
+
# Writing LaTeX content to file
|
294 |
+
with open(latex_file, "w", encoding="utf-8") as f:
|
295 |
+
f.write(latex_content)
|
296 |
+
|
297 |
+
# Compiling LaTeX to PDF using pdflatex
|
298 |
+
try:
|
299 |
+
subprocess.run(
|
300 |
+
["pdflatex", "-output-directory", tmpdirname, str(latex_file)],
|
301 |
+
check=True,
|
302 |
+
stdout=subprocess.PIPE,
|
303 |
+
stderr=subprocess.PIPE,
|
304 |
+
text=True
|
305 |
+
)
|
306 |
+
except subprocess.CalledProcessError as e:
|
307 |
+
logger.error(f"LaTeX compilation failed: {e.stderr}")
|
308 |
+
raise HTTPException(status_code=500, detail="Failed to generate PDF report")
|
309 |
+
|
310 |
+
if not pdf_file.exists():
|
311 |
+
raise HTTPException(status_code=500, detail="PDF generation failed")
|
312 |
+
|
313 |
+
# Reading the generated PDF
|
314 |
+
with open(pdf_file, "rb") as f:
|
315 |
+
pdf_content = f.read()
|
316 |
+
|
317 |
+
# Returning the PDF as a response
|
318 |
+
return FileResponse(
|
319 |
+
pdf_file,
|
320 |
+
media_type="application/pdf",
|
321 |
+
headers={"Content-Disposition": f"attachment; filename=all_patients_analysis_reports_{datetime.utcnow().strftime('%Y%m%d')}.pdf"}
|
322 |
+
)
|
323 |
+
|
324 |
+
except HTTPException:
|
325 |
+
raise
|
326 |
+
except Exception as e:
|
327 |
+
logger.error(f"Error generating PDF report for all patients: {str(e)}")
|
328 |
+
raise HTTPException(status_code=500, detail=f"Failed to generate PDF report: {str(e)}")
|
329 |
+
|
330 |
@router.post("/chat-stream")
|
331 |
async def chat_stream_endpoint(
|
332 |
request: ChatRequest,
|