Ali2206 commited on
Commit
d36071e
·
verified ·
1 Parent(s): 7bdd8f4

Update endpoints.py

Browse files
Files changed (1) hide show
  1. endpoints.py +220 -216
endpoints.py CHANGED
@@ -1,242 +1,246 @@
1
- from fastapi import Depends, HTTPException, UploadFile, File, Query, Form
2
  from fastapi.responses import StreamingResponse, JSONResponse
3
  from fastapi.encoders import jsonable_encoder
4
- from config import app, agent, logger
5
  from models import ChatRequest, VoiceOutputRequest, RiskLevel
6
  from auth import get_current_user
7
  from utils import clean_text_response
8
- from analysis import analyze_patient_report, analyze_all_patients
9
  from voice import recognize_speech, text_to_speech, extract_text_from_pdf
10
  from docx import Document
11
  import re
12
- import mimetypes
13
- from bson import ObjectId
14
  from datetime import datetime
 
15
  import asyncio
16
 
17
- @app.get("/status")
18
- async def status(current_user: dict = Depends(get_current_user)):
19
- logger.info(f"Status endpoint accessed by {current_user['email']}")
20
- return {
21
- "status": "running",
22
- "timestamp": datetime.utcnow().isoformat(),
23
- "version": "2.6.0",
24
- "features": ["chat", "voice-input", "voice-output", "patient-analysis", "report-upload"]
25
- }
26
-
27
- @app.get("/patients/analysis-results")
28
- async def get_patient_analysis_results(
29
- name: Optional[str] = Query(None),
30
- current_user: dict = Depends(get_current_user)
31
- ):
32
- logger.info(f"Fetching analysis results by {current_user['email']}")
33
- try:
34
- query = {}
35
- if name:
36
- name_regex = re.compile(name, re.IGNORECASE)
37
- matching_patients = await patients_collection.find({"full_name": name_regex}).to_list(length=None)
38
- patient_ids = [p["fhir_id"] for p in matching_patients if "fhir_id" in p]
39
- if not patient_ids:
40
- return []
41
- query = {"patient_id": {"$in": patient_ids}}
42
-
43
- analyses = await analysis_collection.find(query).sort("timestamp", -1).to_list(length=100)
44
- enriched_results = []
45
- for analysis in analyses:
46
- patient = await patients_collection.find_one({"fhir_id": analysis.get("patient_id")})
47
- if patient:
48
- analysis["full_name"] = patient.get("full_name", "Unknown")
49
- analysis["_id"] = str(analysis["_id"])
50
- enriched_results.append(analysis)
51
-
52
- return enriched_results
53
-
54
- except Exception as e:
55
- logger.error(f"Error fetching analysis results: {e}")
56
- raise HTTPException(status_code=500, detail="Failed to retrieve analysis results")
57
-
58
- @app.post("/chat-stream")
59
- async def chat_stream_endpoint(
60
- request: ChatRequest,
61
- current_user: dict = Depends(get_current_user)
62
- ):
63
- logger.info(f"Chat stream initiated by {current_user['email']}")
64
- async def token_stream():
65
  try:
66
- conversation = [{"role": "system", "content": agent.chat_prompt}]
67
- if request.history:
68
- conversation.extend(request.history)
69
- conversation.append({"role": "user", "content": request.message})
70
-
71
- input_ids = agent.tokenizer.apply_chat_template(
72
- conversation, add_generation_prompt=True, return_tensors="pt"
73
- ).to(agent.device)
74
-
75
- output = agent.model.generate(
76
- input_ids,
77
- do_sample=True,
78
- temperature=request.temperature,
79
- max_new_tokens=request.max_new_tokens,
80
- pad_token_id=agent.tokenizer.eos_token_id,
81
- return_dict_in_generate=True
82
- )
 
 
83
 
84
- text = agent.tokenizer.decode(output["sequences"][0][input_ids.shape[1]:], skip_special_tokens=True)
85
- for chunk in text.split():
86
- yield chunk + " "
87
- await asyncio.sleep(0.05)
88
  except Exception as e:
89
- logger.error(f"Streaming error: {e}")
90
- yield f"⚠️ Error: {e}"
91
-
92
- return StreamingResponse(token_stream(), media_type="text/plain")
93
-
94
- @app.post("/voice/transcribe")
95
- async def transcribe_voice(
96
- audio: UploadFile = File(...),
97
- language: str = Query("en-US", description="Language code for speech recognition"),
98
- current_user: dict = Depends(get_current_user)
99
- ):
100
- logger.info(f"Voice transcription initiated by {current_user['email']}")
101
- try:
102
- audio_data = await audio.read()
103
- if not audio.filename.lower().endswith(('.wav', '.mp3', '.ogg', '.flac')):
104
- raise HTTPException(status_code=400, detail="Unsupported audio format")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
- text = recognize_speech(audio_data, language)
107
- return {"text": text}
108
-
109
- except HTTPException:
110
- raise
111
- except Exception as e:
112
- logger.error(f"Error in voice transcription: {e}")
113
- raise HTTPException(status_code=500, detail="Error processing voice input")
114
-
115
- @app.post("/voice/synthesize")
116
- async def synthesize_voice(
117
- request: VoiceOutputRequest,
118
- current_user: dict = Depends(get_current_user)
119
- ):
120
- logger.info(f"Voice synthesis initiated by {current_user['email']}")
121
- try:
122
- audio_data = text_to_speech(request.text, request.language, request.slow)
 
 
 
 
 
 
123
 
124
- if request.return_format == "base64":
125
- return {"audio": base64.b64encode(audio_data).decode('utf-8')}
126
- else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  return StreamingResponse(
128
  io.BytesIO(audio_data),
129
  media_type="audio/mpeg",
130
- headers={"Content-Disposition": "attachment; filename=speech.mp3"}
131
  )
132
-
133
- except HTTPException:
134
- raise
135
- except Exception as e:
136
- logger.error(f"Error in voice synthesis: {e}")
137
- raise HTTPException(status_code=500, detail="Error generating voice output")
138
-
139
- @app.post("/voice/chat")
140
- async def voice_chat_endpoint(
141
- audio: UploadFile = File(...),
142
- language: str = Query("en-US", description="Language code for speech recognition"),
143
- temperature: float = Query(0.7, ge=0.1, le=1.0),
144
- max_new_tokens: int = Query(512, ge=50, le=1024),
145
- current_user: dict = Depends(get_current_user)
146
- ):
147
- logger.info(f"Voice chat initiated by {current_user['email']}")
148
- try:
149
- audio_data = await audio.read()
150
- user_message = recognize_speech(audio_data, language)
151
 
152
- chat_response = agent.chat(
153
- message=user_message,
154
- history=[],
155
- temperature=temperature,
156
- max_new_tokens=max_new_tokens
157
- )
158
-
159
- audio_data = text_to_speech(chat_response, language.split('-')[0])
160
-
161
- return StreamingResponse(
162
- io.BytesIO(audio_data),
163
- media_type="audio/mpeg",
164
- headers={"Content-Disposition": "attachment; filename=response.mp3"}
165
- )
166
-
167
- except HTTPException:
168
- raise
169
- except Exception as e:
170
- logger.error(f"Error in voice chat: {e}")
171
- raise HTTPException(status_code=500, detail="Error processing voice chat")
172
-
173
- @app.post("/analyze-report")
174
- async def analyze_clinical_report(
175
- file: UploadFile = File(...),
176
- patient_id: Optional[str] = Form(None),
177
- temperature: float = Form(0.5),
178
- max_new_tokens: int = Form(1024),
179
- current_user: dict = Depends(get_current_user)
180
- ):
181
- logger.info(f"Report analysis initiated by {current_user['email']}")
182
- try:
183
- content_type = file.content_type
184
- allowed_types = [
185
- 'application/pdf',
186
- 'text/plain',
187
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
188
- ]
189
-
190
- if content_type not in allowed_types:
191
- raise HTTPException(
192
- status_code=400,
193
- detail=f"Unsupported file type: {content_type}. Supported types: PDF, TXT, DOCX"
 
 
 
 
 
 
 
 
 
 
 
194
  )
195
 
196
- file_content = await file.read()
197
-
198
- if content_type == 'application/pdf':
199
- text = extract_text_from_pdf(file_content)
200
- elif content_type == 'text/plain':
201
- text = file_content.decode('utf-8')
202
- elif content_type == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
203
- doc = Document(io.BytesIO(file_content))
204
- text = "\n".join([para.text for para in doc.paragraphs])
205
- else:
206
- raise HTTPException(status_code=400, detail="Unsupported file type")
207
-
208
- text = clean_text_response(text)
209
- if len(text.strip()) < 50:
 
 
 
210
  raise HTTPException(
211
- status_code=400,
212
- detail="Extracted text is too short (minimum 50 characters required)"
213
  )
214
 
215
- analysis = await analyze_patient_report(
216
- patient_id=patient_id,
217
- report_content=text,
218
- file_type=content_type,
219
- file_content=file_content
220
- )
221
-
222
- if "_id" in analysis and isinstance(analysis["_id"], ObjectId):
223
- analysis["_id"] = str(analysis["_id"])
224
- if "timestamp" in analysis and isinstance(analysis["timestamp"], datetime):
225
- analysis["timestamp"] = analysis["timestamp"].isoformat()
226
-
227
- return JSONResponse(content=jsonable_encoder({
228
- "status": "success",
229
- "analysis": analysis,
230
- "patient_id": patient_id,
231
- "file_type": content_type,
232
- "file_size": len(file_content)
233
- }))
234
-
235
- except HTTPException:
236
- raise
237
- except Exception as e:
238
- logger.error(f"Error in report analysis: {str(e)}")
239
- raise HTTPException(
240
- status_code=500,
241
- detail=f"Failed to analyze report: {str(e)}"
242
- )
 
1
+ from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query, Form
2
  from fastapi.responses import StreamingResponse, JSONResponse
3
  from fastapi.encoders import jsonable_encoder
 
4
  from models import ChatRequest, VoiceOutputRequest, RiskLevel
5
  from auth import get_current_user
6
  from utils import clean_text_response
7
+ from analysis import analyze_patient_report
8
  from voice import recognize_speech, text_to_speech, extract_text_from_pdf
9
  from docx import Document
10
  import re
11
+ import io
 
12
  from datetime import datetime
13
+ from bson import ObjectId
14
  import asyncio
15
 
16
+ def create_router(agent, logger, patients_collection, analysis_collection):
17
+ router = APIRouter()
18
+
19
+ @router.get("/status")
20
+ async def status(current_user: dict = Depends(get_current_user)):
21
+ logger.info(f"Status endpoint accessed by {current_user['email']}")
22
+ return {
23
+ "status": "running",
24
+ "timestamp": datetime.utcnow().isoformat(),
25
+ "version": "2.6.0",
26
+ "features": ["chat", "voice-input", "voice-output", "patient-analysis", "report-upload"]
27
+ }
28
+
29
+ @router.get("/patients/analysis-results")
30
+ async def get_patient_analysis_results(
31
+ name: Optional[str] = Query(None),
32
+ current_user: dict = Depends(get_current_user)
33
+ ):
34
+ logger.info(f"Fetching analysis results by {current_user['email']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  try:
36
+ query = {}
37
+ if name:
38
+ name_regex = re.compile(name, re.IGNORECASE)
39
+ matching_patients = await patients_collection.find({"full_name": name_regex}).to_list(length=None)
40
+ patient_ids = [p["fhir_id"] for p in matching_patients if "fhir_id" in p]
41
+ if not patient_ids:
42
+ return []
43
+ query = {"patient_id": {"$in": patient_ids}}
44
+
45
+ analyses = await analysis_collection.find(query).sort("timestamp", -1).to_list(length=100)
46
+ enriched_results = []
47
+ for analysis in analyses:
48
+ patient = await patients_collection.find_one({"fhir_id": analysis.get("patient_id")})
49
+ if patient:
50
+ analysis["full_name"] = patient.get("full_name", "Unknown")
51
+ analysis["_id"] = str(analysis["_id"])
52
+ enriched_results.append(analysis)
53
+
54
+ return enriched_results
55
 
 
 
 
 
56
  except Exception as e:
57
+ logger.error(f"Error fetching analysis results: {e}")
58
+ raise HTTPException(status_code=500, detail="Failed to retrieve analysis results")
59
+
60
+ @router.post("/chat-stream")
61
+ async def chat_stream_endpoint(
62
+ request: ChatRequest,
63
+ current_user: dict = Depends(get_current_user)
64
+ ):
65
+ logger.info(f"Chat stream initiated by {current_user['email']}")
66
+ async def token_stream():
67
+ try:
68
+ conversation = [{"role": "system", "content": agent.chat_prompt}]
69
+ if request.history:
70
+ conversation.extend(request.history)
71
+ conversation.append({"role": "user", "content": request.message})
72
+
73
+ input_ids = agent.tokenizer.apply_chat_template(
74
+ conversation, add_generation_prompt=True, return_tensors="pt"
75
+ ).to(agent.device)
76
+
77
+ output = agent.model.generate(
78
+ input_ids,
79
+ do_sample=True,
80
+ temperature=request.temperature,
81
+ max_new_tokens=request.max_new_tokens,
82
+ pad_token_id=agent.tokenizer.eos_token_id,
83
+ return_dict_in_generate=True
84
+ )
85
+
86
+ text = agent.tokenizer.decode(output["sequences"][0][input_ids.shape[1]:], skip_special_tokens=True)
87
+ for chunk in text.split():
88
+ yield chunk + " "
89
+ await asyncio.sleep(0.05)
90
+ except Exception as e:
91
+ logger.error(f"Streaming error: {e}")
92
+ yield f"⚠️ Error: {e}"
93
+
94
+ return StreamingResponse(token_stream(), media_type="text/plain")
95
+
96
+ @router.post("/voice/transcribe")
97
+ async def transcribe_voice(
98
+ audio: UploadFile = File(...),
99
+ language: str = Query("en-US", description="Language code for speech recognition"),
100
+ current_user: dict = Depends(get_current_user)
101
+ ):
102
+ logger.info(f"Voice transcription initiated by {current_user['email']}")
103
+ try:
104
+ audio_data = await audio.read()
105
+ if not audio.filename.lower().endswith(('.wav', '.mp3', '.ogg', '.flac')):
106
+ raise HTTPException(status_code=400, detail="Unsupported audio format")
107
+
108
+ text = recognize_speech(audio_data, language)
109
+ return {"text": text}
110
 
111
+ except HTTPException:
112
+ raise
113
+ except Exception as e:
114
+ logger.error(f"Error in voice transcription: {e}")
115
+ raise HTTPException(status_code=500, detail="Error processing voice input")
116
+
117
+ @router.post("/voice/synthesize")
118
+ async def synthesize_voice(
119
+ request: VoiceOutputRequest,
120
+ current_user: dict = Depends(get_current_user)
121
+ ):
122
+ logger.info(f"Voice synthesis initiated by {current_user['email']}")
123
+ try:
124
+ audio_data = text_to_speech(request.text, request.language, request.slow)
125
+
126
+ if request.return_format == "base64":
127
+ return {"audio": base64.b64encode(audio_data).decode('utf-8')}
128
+ else:
129
+ return StreamingResponse(
130
+ io.BytesIO(audio_data),
131
+ media_type="audio/mpeg",
132
+ headers={"Content-Disposition": "attachment; filename=speech.mp3"}
133
+ )
134
 
135
+ except HTTPException:
136
+ raise
137
+ except Exception as e:
138
+ logger.error(f"Error in voice synthesis: {e}")
139
+ raise HTTPException(status_code=500, detail="Error generating voice output")
140
+
141
+ @router.post("/voice/chat")
142
+ async def voice_chat_endpoint(
143
+ audio: UploadFile = File(...),
144
+ language: str = Query("en-US", description="Language code for speech recognition"),
145
+ temperature: float = Query(0.7, ge=0.1, le=1.0),
146
+ max_new_tokens: int = Query(512, ge=50, le=1024),
147
+ current_user: dict = Depends(get_current_user)
148
+ ):
149
+ logger.info(f"Voice chat initiated by {current_user['email']}")
150
+ try:
151
+ audio_data = await audio.read()
152
+ user_message = recognize_speech(audio_data, language)
153
+
154
+ chat_response = agent.chat(
155
+ message=user_message,
156
+ history=[],
157
+ temperature=temperature,
158
+ max_new_tokens=max_new_tokens
159
+ )
160
+
161
+ audio_data = text_to_speech(chat_response, language.split('-')[0])
162
+
163
  return StreamingResponse(
164
  io.BytesIO(audio_data),
165
  media_type="audio/mpeg",
166
+ headers={"Content-Disposition": "attachment; filename=response.mp3"}
167
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
+ except HTTPException:
170
+ raise
171
+ except Exception as e:
172
+ logger.error(f"Error in voice chat: {e}")
173
+ raise HTTPException(status_code=500, detail="Error processing voice chat")
174
+
175
+ @router.post("/analyze-report")
176
+ async def analyze_clinical_report(
177
+ file: UploadFile = File(...),
178
+ patient_id: Optional[str] = Form(None),
179
+ temperature: float = Form(0.5),
180
+ max_new_tokens: int = Form(1024),
181
+ current_user: dict = Depends(get_current_user)
182
+ ):
183
+ logger.info(f"Report analysis initiated by {current_user['email']}")
184
+ try:
185
+ content_type = file.content_type
186
+ allowed_types = [
187
+ 'application/pdf',
188
+ 'text/plain',
189
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
190
+ ]
191
+
192
+ if content_type not in allowed_types:
193
+ raise HTTPException(
194
+ status_code=400,
195
+ detail=f"Unsupported file type: {content_type}. Supported types: PDF, TXT, DOCX"
196
+ )
197
+
198
+ file_content = await file.read()
199
+
200
+ if content_type == 'application/pdf':
201
+ text = extract_text_from_pdf(file_content)
202
+ elif content_type == 'text/plain':
203
+ text = file_content.decode('utf-8')
204
+ elif content_type == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
205
+ doc = Document(io.BytesIO(file_content))
206
+ text = "\n".join([para.text for para in doc.paragraphs])
207
+ else:
208
+ raise HTTPException(status_code=400, detail="Unsupported file type")
209
+
210
+ text = clean_text_response(text)
211
+ if len(text.strip()) < 50:
212
+ raise HTTPException(
213
+ status_code=400,
214
+ detail="Extracted text is too short (minimum 50 characters required)"
215
+ )
216
+
217
+ analysis = await analyze_patient_report(
218
+ patient_id=patient_id,
219
+ report_content=text,
220
+ file_type=content_type,
221
+ file_content=file_content
222
  )
223
 
224
+ if "_id" in analysis and isinstance(analysis["_id"], ObjectId):
225
+ analysis["_id"] = str(analysis["_id"])
226
+ if "timestamp" in analysis and isinstance(analysis["timestamp"], datetime):
227
+ analysis["timestamp"] = analysis["timestamp"].isoformat()
228
+
229
+ return JSONResponse(content=jsonable_encoder({
230
+ "status": "success",
231
+ "analysis": analysis,
232
+ "patient_id": patient_id,
233
+ "file_type": content_type,
234
+ "file_size": len(file_content)
235
+ }))
236
+
237
+ except HTTPException:
238
+ raise
239
+ except Exception as e:
240
+ logger.error(f"Error in report analysis: {str(e)}")
241
  raise HTTPException(
242
+ status_code=500,
243
+ detail=f"Failed to analyze report: {str(e)}"
244
  )
245
 
246
+ return router