Muhammadidrees commited on
Commit
84e3bf5
Β·
verified Β·
1 Parent(s): 5e59dd7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +874 -874
app.py CHANGED
@@ -1,875 +1,875 @@
1
- from fastapi import FastAPI, HTTPException
2
- from fastapi.middleware.cors import CORSMiddleware
3
- from fastapi.responses import FileResponse
4
- from pydantic import BaseModel
5
- from typing import List, Optional, Dict
6
- import google.generativeai as genai
7
- import os
8
- from datetime import datetime
9
- import uuid
10
- import json
11
- from pathlib import Path
12
- from reportlab.lib.pagesizes import letter, A4
13
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
14
- from reportlab.lib.units import inch
15
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle
16
- from reportlab.lib import colors
17
- from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
18
- from reportlab.pdfgen import canvas
19
-
20
- # Configure Gemini API
21
- os.environ["GOOGLE_API_KEY"] = "AIzaSyDid3I53qFohhZTnLjnOjz4QqlvK4RZm7o"
22
- genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
23
-
24
- MODEL_ID = "gemini-2.0-flash-exp"
25
-
26
- # Create storage directories
27
- STORAGE_DIR = Path("consultation_storage")
28
- STORAGE_DIR.mkdir(exist_ok=True)
29
-
30
- PDF_DIR = Path("consultation_pdfs")
31
- PDF_DIR.mkdir(exist_ok=True)
32
-
33
- # System prompt (same as before)
34
- DOCTOR_SYSTEM_PROMPT = """
35
- You are Dr. HealBot, a calm, knowledgeable, and empathetic virtual doctor.
36
-
37
- GOAL:
38
- Hold a natural, focused conversation with the patient to understand their health issue and offer helpful preliminary medical guidance.
39
-
40
- CONVERSATION LOGIC:
41
- - Ask only relevant and concise medical questions necessary for diagnosing the illness.
42
- - Each question should help clarify symptoms or narrow possible causes.
43
- - Stop asking once enough information is collected for a basic assessment.
44
- - Then, provide a structured, friendly, and visually clear medical response using headings, emojis, and bullet points.
45
-
46
- FINAL RESPONSE FORMAT:
47
- When giving your full assessment, use this markdown-styled format:
48
-
49
- 🩺 Based on what you've told me...
50
- Brief summary of what the patient described.
51
-
52
- πŸ’‘ Possible Causes (Preliminary)
53
- - List 1–2 possible conditions using phrases like "It could be" or "This sounds like".
54
- - Include a disclaimer that this is not a confirmed diagnosis.
55
-
56
- πŸ’Š Suggested Over-the-Counter Medicines
57
- - Generic medicine names only (e.g., "Paracetamol 500mg every 6 hours if fever or pain")
58
- - Mention to check packaging or consult a pharmacist for dosage confirmation.
59
-
60
- πŸ₯— Lifestyle & Home Care Tips
61
- - 2–3 practical suggestions (rest, hydration, warm compress, balanced diet, etc.)
62
-
63
- ⚠ When to See a Real Doctor
64
- - 2–3 warning signs or conditions when urgent medical care is needed.
65
-
66
- πŸ“… Follow-Up Advice
67
- - Brief recommendation for self-care or follow-up timing (e.g., "If not improving in 3 days, visit a clinic.")
68
-
69
- TONE & STYLE:
70
- - Speak like a real, caring doctor β€” short, clear, and empathetic (1–2 sentences per reply).
71
- - Use plain language, no jargon.
72
- - Only one question per turn unless clarification is essential.
73
- - Keep tone warm, calm, and professional.
74
- - Early messages: short questions only.
75
- - Final message: structured output with emojis and headings.
76
-
77
- IMPORTANT:
78
- - Always emphasize that this is preliminary guidance and not a substitute for professional care.
79
- - Never make definitive diagnoses; use phrases like "it sounds like" or "it could be".
80
- - If symptoms seem serious, always recommend urgent medical attention.
81
-
82
- CONVERSATION FLOW:
83
- 1. Ask about the main symptom.
84
- 2. Ask about its duration, severity, and any triggers.
85
- 3. Ask about accompanying symptoms.
86
- 4. Ask about medical history, allergies, or medications.
87
- 5. Then, provide your structured assessment as described above.
88
- """
89
-
90
- # =====================================================
91
- # PDF GENERATION FUNCTIONS
92
- # =====================================================
93
-
94
- def generate_pdf_summary(session_id: str, summary_text: str, patient_data: Dict, history: List[Dict]) -> str:
95
- """Generate a professional PDF summary of the consultation"""
96
-
97
- pdf_filename = f"{session_id}_summary.pdf"
98
- pdf_path = PDF_DIR / pdf_filename
99
-
100
- # Create PDF document
101
- doc = SimpleDocTemplate(str(pdf_path), pagesize=letter,
102
- rightMargin=72, leftMargin=72,
103
- topMargin=72, bottomMargin=18)
104
-
105
- # Container for the 'Flowable' objects
106
- elements = []
107
-
108
- # Define styles
109
- styles = getSampleStyleSheet()
110
-
111
- # Custom styles
112
- title_style = ParagraphStyle(
113
- 'CustomTitle',
114
- parent=styles['Heading1'],
115
- fontSize=24,
116
- textColor=colors.HexColor('#667eea'),
117
- spaceAfter=30,
118
- alignment=TA_CENTER,
119
- fontName='Helvetica-Bold'
120
- )
121
-
122
- heading_style = ParagraphStyle(
123
- 'CustomHeading',
124
- parent=styles['Heading2'],
125
- fontSize=16,
126
- textColor=colors.HexColor('#667eea'),
127
- spaceAfter=12,
128
- spaceBefore=12,
129
- fontName='Helvetica-Bold'
130
- )
131
-
132
- normal_style = ParagraphStyle(
133
- 'CustomNormal',
134
- parent=styles['Normal'],
135
- fontSize=11,
136
- spaceAfter=12,
137
- alignment=TA_JUSTIFY,
138
- leading=14
139
- )
140
-
141
- # Add Title
142
- elements.append(Paragraph("🩺 AI DOCTOR CONSULTATION SUMMARY", title_style))
143
- elements.append(Spacer(1, 0.3*inch))
144
-
145
- # Add horizontal line
146
- elements.append(Spacer(1, 0.1*inch))
147
-
148
- # Patient Information Table
149
- patient_info_data = [
150
- ['Patient Name:', patient_data.get('name', 'N/A')],
151
- ['Age:', patient_data.get('age', 'N/A')],
152
- ['Session ID:', session_id[:20] + '...'],
153
- ['Consultation Date:', datetime.now().strftime('%B %d, %Y at %I:%M %p')],
154
- ['Total Messages:', str(len(history))]
155
- ]
156
-
157
- patient_table = Table(patient_info_data, colWidths=[2*inch, 4*inch])
158
- patient_table.setStyle(TableStyle([
159
- ('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f0f0f0')),
160
- ('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
161
- ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
162
- ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
163
- ('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
164
- ('FONTSIZE', (0, 0), (-1, -1), 10),
165
- ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
166
- ('TOPPADDING', (0, 0), (-1, -1), 8),
167
- ('GRID', (0, 0), (-1, -1), 1, colors.grey)
168
- ]))
169
-
170
- elements.append(patient_table)
171
- elements.append(Spacer(1, 0.3*inch))
172
-
173
- # Add Consultation Summary
174
- elements.append(Paragraph("CONSULTATION SUMMARY", heading_style))
175
-
176
- # Process summary text - split by lines and convert to paragraphs
177
- summary_lines = summary_text.split('\n')
178
- for line in summary_lines:
179
- if line.strip():
180
- # Replace emojis with text equivalents for PDF compatibility
181
- line = line.replace('🩺', '[Medical] ')
182
- line = line.replace('πŸ’‘', '[Insight] ')
183
- line = line.replace('πŸ’Š', '[Medicine] ')
184
- line = line.replace('πŸ₯—', '[Lifestyle] ')
185
- line = line.replace('⚠️', '[Warning] ')
186
- line = line.replace('⚠', '[Warning] ')
187
- line = line.replace('πŸ“…', '[Follow-up] ')
188
- line = line.replace('━', '-')
189
-
190
- # Check if it's a heading (starts with **)
191
- if line.strip().startswith('**') and line.strip().endswith('**'):
192
- elements.append(Paragraph(line.strip('*'), heading_style))
193
- else:
194
- elements.append(Paragraph(line, normal_style))
195
-
196
- elements.append(Spacer(1, 0.3*inch))
197
-
198
- # Add Conversation History
199
- elements.append(PageBreak())
200
- elements.append(Paragraph("CONVERSATION HISTORY", heading_style))
201
- elements.append(Spacer(1, 0.2*inch))
202
-
203
- for i, msg in enumerate(history, 1):
204
- role = "DOCTOR" if msg['role'] == 'assistant' else "PATIENT"
205
- timestamp = msg.get('timestamp', 'N/A')
206
-
207
- role_style = ParagraphStyle(
208
- f'Role{i}',
209
- parent=styles['Normal'],
210
- fontSize=10,
211
- textColor=colors.HexColor('#667eea') if role == "DOCTOR" else colors.HexColor('#28a745'),
212
- fontName='Helvetica-Bold',
213
- spaceAfter=4
214
- )
215
-
216
- elements.append(Paragraph(f"{role} ({timestamp}):", role_style))
217
-
218
- content = msg['content'].replace('🩺', '').replace('πŸ’‘', '').replace('πŸ’Š', '')
219
- content = content.replace('πŸ₯—', '').replace('⚠️', '').replace('⚠', '').replace('πŸ“…', '')
220
- elements.append(Paragraph(content, normal_style))
221
- elements.append(Spacer(1, 0.15*inch))
222
-
223
- # Add disclaimer at the end
224
- elements.append(Spacer(1, 0.3*inch))
225
-
226
- disclaimer_style = ParagraphStyle(
227
- 'Disclaimer',
228
- parent=styles['Normal'],
229
- fontSize=9,
230
- textColor=colors.red,
231
- alignment=TA_CENTER,
232
- fontName='Helvetica-Bold',
233
- borderColor=colors.red,
234
- borderWidth=1,
235
- borderPadding=10,
236
- spaceAfter=12
237
- )
238
-
239
- elements.append(Paragraph(
240
- "⚠ IMPORTANT DISCLAIMER ⚠<br/>" +
241
- "This is a preliminary AI-generated consultation for informational purposes only.<br/>" +
242
- "It is NOT a substitute for professional medical advice, diagnosis, or treatment.<br/>" +
243
- "Always seek the advice of a qualified healthcare provider with any questions regarding a medical condition.",
244
- disclaimer_style
245
- ))
246
-
247
- # Build PDF
248
- doc.build(elements)
249
-
250
- return pdf_filename
251
-
252
- # =====================================================
253
- # STORAGE FUNCTIONS (same as before)
254
- # =====================================================
255
-
256
- def save_session_to_json(session_id: str, memory: 'ConversationMemory'):
257
- """Save session data to JSON file"""
258
- file_path = STORAGE_DIR / f"{session_id}.json"
259
-
260
- session_data = {
261
- "session_id": session_id,
262
- "created_at": memory.created_at.isoformat(),
263
- "last_updated": datetime.now().isoformat(),
264
- "patient_data": memory.patient_data,
265
- "questions_asked": memory.questions_asked,
266
- "history": memory.history,
267
- "message_count": len(memory.history),
268
- "pdf_filename": getattr(memory, 'pdf_filename', None)
269
- }
270
-
271
- with open(file_path, 'w', encoding='utf-8') as f:
272
- json.dump(session_data, f, indent=2, ensure_ascii=False)
273
-
274
- def load_session_from_json(session_id: str) -> Optional[Dict]:
275
- """Load session data from JSON file"""
276
- file_path = STORAGE_DIR / f"{session_id}.json"
277
-
278
- if not file_path.exists():
279
- return None
280
-
281
- with open(file_path, 'r', encoding='utf-8') as f:
282
- return json.load(f)
283
-
284
- def list_all_sessions() -> List[Dict]:
285
- """List all stored sessions"""
286
- sessions_list = []
287
-
288
- for file_path in STORAGE_DIR.glob("*.json"):
289
- try:
290
- with open(file_path, 'r', encoding='utf-8') as f:
291
- data = json.load(f)
292
- sessions_list.append({
293
- "session_id": data["session_id"],
294
- "created_at": data["created_at"],
295
- "last_updated": data.get("last_updated", data["created_at"]),
296
- "patient_name": data["patient_data"].get("name", "Unknown"),
297
- "message_count": data["message_count"],
298
- "has_pdf": data.get("pdf_filename") is not None
299
- })
300
- except Exception as e:
301
- print(f"Error reading {file_path}: {e}")
302
-
303
- return sorted(sessions_list, key=lambda x: x["last_updated"], reverse=True)
304
-
305
- # =====================================================
306
- # MEMORY MANAGEMENT (same as before)
307
- # =====================================================
308
-
309
- class ConversationMemory:
310
- """Manages short-term memory for each session"""
311
- def __init__(self, max_messages: int = 20, session_id: str = None):
312
- self.max_messages = max_messages
313
- self.history = []
314
- self.patient_data = {}
315
- self.created_at = datetime.now()
316
- self.questions_asked = 0
317
- self.session_id = session_id
318
- self.pdf_filename = None
319
-
320
- def add_message(self, role: str, content: str):
321
- """Add message to history with memory management"""
322
- self.history.append({
323
- "role": role,
324
- "content": content,
325
- "timestamp": datetime.now().isoformat()
326
- })
327
-
328
- if role == "assistant" and "?" in content:
329
- self.questions_asked += 1
330
-
331
- if len(self.history) > self.max_messages:
332
- self.history = [self.history[0]] + self.history[-(self.max_messages-1):]
333
-
334
- if self.session_id:
335
- save_session_to_json(self.session_id, self)
336
-
337
- def extract_patient_info(self, message: str):
338
- """Extract and store patient information from conversation"""
339
- message_lower = message.lower()
340
-
341
- if any(word in message_lower for word in ["name is", "i'm", "i am", "im"]):
342
- words = message.split()
343
- for i, word in enumerate(words):
344
- if word.lower() in ["is", "i'm", "am", "im"] and i + 1 < len(words):
345
- self.patient_data["name"] = words[i + 1].strip(".,!?")
346
-
347
- if "year" in message_lower or "age" in message_lower:
348
- import re
349
- age_match = re.search(r'\b(\d{1,3})\b', message)
350
- if age_match:
351
- self.patient_data["age"] = age_match.group(1)
352
-
353
- if "fever" in message_lower or "pain" in message_lower or "sick" in message_lower:
354
- self.patient_data["has_symptoms"] = True
355
-
356
- def should_give_recommendations(self) -> bool:
357
- """Check if we should provide recommendations now"""
358
- return (
359
- self.questions_asked >= 7 or
360
- self.patient_data.get("has_symptoms", False)
361
- )
362
-
363
- def get_context_summary(self) -> str:
364
- """Generate a brief context summary for the AI"""
365
- summary = "\n[Session Context: "
366
- if "name" in self.patient_data:
367
- summary += f"Name: {self.patient_data['name']}, "
368
- if "age" in self.patient_data:
369
- summary += f"Age: {self.patient_data['age']}, "
370
- summary += f"Questions asked: {self.questions_asked}/7, "
371
-
372
- if self.questions_asked >= 5:
373
- summary += "⚠️ IMPORTANT: You've asked enough questions. After the next 1-2 answers, IMMEDIATELY provide comprehensive medical recommendations.]"
374
- elif self.questions_asked >= 7:
375
- summary += "⚠️ CRITICAL: You MUST provide comprehensive medical recommendations NOW. Do not ask more questions!]"
376
- else:
377
- summary += f"Ask {7 - self.questions_asked} more essential questions then give recommendations.]"
378
-
379
- return summary
380
-
381
- def get_gemini_history(self) -> List[Dict]:
382
- """Convert history to Gemini format"""
383
- gemini_history = []
384
- for msg in self.history:
385
- gemini_history.append({
386
- "role": "user" if msg["role"] == "user" else "model",
387
- "parts": [msg["content"]]
388
- })
389
- return gemini_history
390
-
391
- @classmethod
392
- def from_json(cls, session_data: Dict) -> 'ConversationMemory':
393
- """Create ConversationMemory from JSON data"""
394
- memory = cls(session_id=session_data["session_id"])
395
- memory.history = session_data["history"]
396
- memory.patient_data = session_data["patient_data"]
397
- memory.questions_asked = session_data["questions_asked"]
398
- memory.created_at = datetime.fromisoformat(session_data["created_at"])
399
- memory.pdf_filename = session_data.get("pdf_filename")
400
- return memory
401
-
402
- sessions: Dict[str, ConversationMemory] = {}
403
-
404
- def cleanup_old_sessions():
405
- """Remove sessions older than 1 hour from memory"""
406
- current_time = datetime.now()
407
- expired_sessions = []
408
-
409
- for session_id, memory in sessions.items():
410
- age = (current_time - memory.created_at).total_seconds()
411
- if age > 3600:
412
- expired_sessions.append(session_id)
413
-
414
- for session_id in expired_sessions:
415
- del sessions[session_id]
416
-
417
- # =====================================================
418
- # FASTAPI APPLICATION
419
- # =====================================================
420
-
421
- app = FastAPI(
422
- title="AI Doctor Consultation API with PDF Generation",
423
- description="Professional medical consultation API with PDF summary generation",
424
- version="3.0.0"
425
- )
426
-
427
- app.add_middleware(
428
- CORSMiddleware,
429
- allow_origins=["*"],
430
- allow_credentials=True,
431
- allow_methods=["*"],
432
- allow_headers=["*"],
433
- )
434
-
435
- # Pydantic models
436
- class ChatRequest(BaseModel):
437
- session_id: Optional[str] = None
438
- message: str
439
-
440
- class ChatResponse(BaseModel):
441
- session_id: str
442
- response: str
443
- timestamp: str
444
- patient_data: Dict
445
-
446
- class SessionRequest(BaseModel):
447
- session_id: str
448
-
449
- class SummaryResponse(BaseModel):
450
- summary: str
451
- session_id: str
452
- pdf_filename: str
453
- pdf_url: str
454
-
455
- class HealthCheck(BaseModel):
456
- status: str
457
- timestamp: str
458
- active_sessions: int
459
- stored_sessions: int
460
- stored_pdfs: int
461
-
462
- # =====================================================
463
- # API ENDPOINTS
464
- # =====================================================
465
-
466
- @app.get("/", response_model=HealthCheck)
467
- async def root():
468
- """Health check endpoint"""
469
- cleanup_old_sessions()
470
- stored_count = len(list(STORAGE_DIR.glob("*.json")))
471
- pdf_count = len(list(PDF_DIR.glob("*.pdf")))
472
- return {
473
- "status": "healthy",
474
- "timestamp": datetime.now().isoformat(),
475
- "active_sessions": len(sessions),
476
- "stored_sessions": stored_count,
477
- "stored_pdfs": pdf_count
478
- }
479
-
480
- @app.post("/start-session")
481
- async def start_session():
482
- """Start a new consultation session"""
483
- session_id = str(uuid.uuid4())
484
- sessions[session_id] = ConversationMemory(max_messages=20, session_id=session_id)
485
-
486
- initial_message = "Hello! I'm Dr. AI Assistant. I'm here to help you today.\n\nπŸ‘€ May I have your name, please?"
487
-
488
- sessions[session_id].add_message("assistant", initial_message)
489
-
490
- return {
491
- "session_id": session_id,
492
- "message": initial_message,
493
- "timestamp": datetime.now().isoformat()
494
- }
495
-
496
- @app.post("/chat", response_model=ChatResponse)
497
- async def chat(request: ChatRequest):
498
- """Send a message and get doctor's response"""
499
- try:
500
- if not request.session_id or request.session_id not in sessions:
501
- session_id = str(uuid.uuid4())
502
- sessions[session_id] = ConversationMemory(max_messages=20, session_id=session_id)
503
- else:
504
- session_id = request.session_id
505
-
506
- memory = sessions[session_id]
507
- memory.extract_patient_info(request.message)
508
- memory.add_message("user", request.message)
509
-
510
- context = memory.get_context_summary()
511
- system_prompt = DOCTOR_SYSTEM_PROMPT + context
512
-
513
- model = genai.GenerativeModel(
514
- model_name=MODEL_ID,
515
- system_instruction=system_prompt
516
- )
517
-
518
- chat = model.start_chat(history=memory.get_gemini_history()[:-1])
519
- response = chat.send_message(request.message)
520
- doctor_response = response.text
521
-
522
- memory.add_message("assistant", doctor_response)
523
-
524
- return {
525
- "session_id": session_id,
526
- "response": doctor_response,
527
- "timestamp": datetime.now().isoformat(),
528
- "patient_data": memory.patient_data
529
- }
530
-
531
- except Exception as e:
532
- raise HTTPException(status_code=500, detail=f"Error: {str(e)}")
533
-
534
- @app.post("/summary", response_model=SummaryResponse)
535
- async def generate_summary(request: SessionRequest):
536
- """Generate consultation summary and PDF"""
537
- if request.session_id not in sessions:
538
- session_data = load_session_from_json(request.session_id)
539
- if not session_data:
540
- raise HTTPException(status_code=404, detail="Session not found")
541
- memory = ConversationMemory.from_json(session_data)
542
- sessions[request.session_id] = memory
543
- else:
544
- memory = sessions[request.session_id]
545
-
546
- summary_request = """Please generate a COMPREHENSIVE and DETAILED medical consultation summary based on our entire conversation. Make it thorough and professional:
547
-
548
- πŸ“‹ **COMPREHENSIVE MEDICAL CONSULTATION SUMMARY**
549
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
550
-
551
- **PATIENT INFORMATION:**
552
- - Full Name: [Patient's name]
553
- - Age: [Patient's age if mentioned]
554
- - Gender: [If mentioned]
555
- - Consultation Date: [Current date and time]
556
- - Session Duration: [Approximate]
557
- - Current Medications: [List all mentioned]
558
- - Known Allergies: [If mentioned]
559
-
560
- **CHIEF COMPLAINTS & SYMPTOMS:**
561
- [Provide a detailed description of ALL symptoms mentioned, including:]
562
- - Primary symptom and severity
563
- - Duration of each symptom
564
- - Onset and progression
565
- - Associated symptoms
566
- - Aggravating and relieving factors
567
- - Impact on daily activities
568
-
569
- **DETAILED MEDICAL HISTORY:**
570
- [Include everything discussed:]
571
- - Current medications and dosages
572
- - Past medical conditions
573
- - Recent illnesses or infections
574
- - Family medical history (if mentioned)
575
- - Lifestyle factors (sleep, stress, diet)
576
- - Recent travel or exposures
577
-
578
- **CLINICAL ASSESSMENT:**
579
- [Provide detailed analysis:]
580
- - Most likely diagnosis with explanation
581
- - Differential diagnoses (2-3 possibilities)
582
- - Reasoning behind each possibility
583
- - Risk factors present
584
- - Severity assessment
585
-
586
- **COMPREHENSIVE TREATMENT PLAN:**
587
-
588
- 1. **IMMEDIATE CARE RECOMMENDATIONS:**
589
- - What to do in the next 24-48 hours
590
- - Symptom management strategies
591
- - Warning signs to watch for
592
-
593
- 2. **MEDICATION RECOMMENDATIONS:**
594
- - Primary medications (generic names, dosages, frequency, duration)
595
- - Alternative options if first choice unavailable
596
- - Potential side effects to monitor
597
- - Drug interactions to avoid
598
- - When to take each medication (with/without food)
599
- - Important: Check with pharmacist for exact dosing
600
-
601
- 3. **DETAILED DIETARY RECOMMENDATIONS:**
602
- - Foods to eat (specific examples and portions)
603
- - Foods to avoid completely
604
- - Meal timing and frequency
605
- - Hydration guidelines (specific amounts)
606
- - Nutritional supplements if needed
607
- - Sample meal plan for recovery
608
-
609
- 4. **LIFESTYLE MODIFICATIONS:**
610
- - Sleep recommendations (hours, timing, environment)
611
- - Rest and activity balance
612
- - Stress management techniques
613
- - Environmental modifications
614
- - Work/school attendance guidance
615
- - Specific activities to avoid
616
-
617
- 5. **HOME CARE REMEDIES:**
618
- - Natural remedies that may help
619
- - Temperature management techniques
620
- - Pain relief methods
621
- - Steam inhalation or other therapies
622
- - Specific home treatments for symptoms
623
-
624
- 6. **EXERCISE & PHYSICAL ACTIVITY:**
625
- - Current activity restrictions
626
- - Safe exercises during recovery
627
- - When to resume normal activities
628
- - Gradual activity progression plan
629
- - Post-recovery exercise recommendations
630
-
631
- 7. **PREVENTIVE MEASURES:**
632
- - How to prevent recurrence
633
- - Hygiene practices
634
- - Vaccination recommendations
635
- - Family/household precautions
636
- - Long-term health maintenance
637
-
638
- 8. **MONITORING PLAN:**
639
- - Symptoms to track daily
640
- - How to measure improvement
641
- - When improvement should be expected
642
- - What to document for doctor visit
643
-
644
- **CRITICAL WARNING SIGNS - SEEK IMMEDIATE MEDICAL ATTENTION IF:**
645
- [List 5-7 specific warning signs that require emergency care:]
646
- - [Specific symptom with threshold]
647
- - [Specific symptom with threshold]
648
- - [Continue with detailed warnings]
649
-
650
- **FOLLOW-UP CARE PLAN:**
651
- - Timeline for self-care (e.g., "Monitor for 48 hours")
652
- - When to schedule doctor appointment (specific timeframe)
653
- - What information to bring to doctor
654
- - Specialist referral recommendations if needed
655
- - Follow-up testing that may be needed
656
-
657
- **PROGNOSIS & EXPECTED RECOVERY:**
658
- - Expected recovery timeline
659
- - What to expect during recovery process
660
- - Signs of improvement to look for
661
- - Long-term outlook
662
-
663
- **ADDITIONAL RESOURCES:**
664
- - Reputable health information sources
665
- - Support resources if applicable
666
- - Emergency contact information reminder
667
-
668
- **PATIENT EDUCATION:**
669
- - Understanding your condition
670
- - How the body fights this illness
671
- - Why specific recommendations are important
672
- - Common misconceptions about this condition
673
-
674
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
675
- ⚠️ **CRITICAL DISCLAIMER** ⚠️
676
- This is a preliminary AI-generated consultation for informational and educational purposes ONLY.
677
- This is NOT a substitute for professional medical advice, diagnosis, or treatment.
678
- This AI cannot examine you physically, run laboratory tests, or make definitive diagnoses.
679
- ALWAYS seek the advice of a qualified, licensed healthcare provider with any questions regarding a medical condition.
680
- Never disregard professional medical advice or delay seeking it because of this AI consultation.
681
- In case of emergency, call your local emergency services immediately.
682
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
683
-
684
- Please make this summary as detailed, professional, and helpful as possible. Include specific, actionable advice."""
685
-
686
- try:
687
- model = genai.GenerativeModel(
688
- model_name=MODEL_ID,
689
- system_instruction=DOCTOR_SYSTEM_PROMPT
690
- )
691
-
692
- chat = model.start_chat(history=memory.get_gemini_history())
693
- response = chat.send_message(summary_request)
694
- summary_text = response.text
695
-
696
- # Generate PDF
697
- pdf_filename = generate_pdf_summary(
698
- request.session_id,
699
- summary_text,
700
- memory.patient_data,
701
- memory.history
702
- )
703
-
704
- # Save PDF filename to memory
705
- memory.pdf_filename = pdf_filename
706
- save_session_to_json(request.session_id, memory)
707
-
708
- return {
709
- "summary": summary_text,
710
- "session_id": request.session_id,
711
- "pdf_filename": pdf_filename,
712
- "pdf_url": f"/download-pdf/{request.session_id}"
713
- }
714
-
715
- except Exception as e:
716
- raise HTTPException(status_code=500, detail=f"Error generating summary: {str(e)}")
717
-
718
- @app.get("/download-pdf/{session_id}")
719
- async def download_pdf(session_id: str):
720
- """Download PDF summary for a session"""
721
- # Check if session exists
722
- if session_id in sessions:
723
- memory = sessions[session_id]
724
- else:
725
- session_data = load_session_from_json(session_id)
726
- if not session_data:
727
- raise HTTPException(status_code=404, detail="Session not found")
728
- memory = ConversationMemory.from_json(session_data)
729
-
730
- if not memory.pdf_filename:
731
- raise HTTPException(status_code=404, detail="PDF not generated yet. Please generate summary first.")
732
-
733
- pdf_path = PDF_DIR / memory.pdf_filename
734
-
735
- if not pdf_path.exists():
736
- raise HTTPException(status_code=404, detail="PDF file not found")
737
-
738
- patient_name = memory.patient_data.get('name', 'Patient')
739
- download_filename = f"Consultation_Summary_{patient_name}_{datetime.now().strftime('%Y%m%d')}.pdf"
740
-
741
- return FileResponse(
742
- path=str(pdf_path),
743
- media_type='application/pdf',
744
- filename=download_filename
745
- )
746
-
747
- @app.get("/load-session/{session_id}")
748
- async def load_session(session_id: str):
749
- """Load a previous consultation session by ID"""
750
- if session_id in sessions:
751
- memory = sessions[session_id]
752
- return {
753
- "session_id": session_id,
754
- "loaded": True,
755
- "from_cache": True,
756
- "history": memory.history,
757
- "patient_data": memory.patient_data,
758
- "created_at": memory.created_at.isoformat(),
759
- "questions_asked": memory.questions_asked,
760
- "has_pdf": memory.pdf_filename is not None,
761
- "pdf_url": f"/download-pdf/{session_id}" if memory.pdf_filename else None
762
- }
763
-
764
- session_data = load_session_from_json(session_id)
765
-
766
- if not session_data:
767
- raise HTTPException(status_code=404, detail=f"Session {session_id} not found")
768
-
769
- memory = ConversationMemory.from_json(session_data)
770
- sessions[session_id] = memory
771
-
772
- return {
773
- "session_id": session_id,
774
- "loaded": True,
775
- "from_cache": False,
776
- "history": memory.history,
777
- "patient_data": memory.patient_data,
778
- "created_at": memory.created_at.isoformat(),
779
- "questions_asked": memory.questions_asked,
780
- "has_pdf": memory.pdf_filename is not None,
781
- "pdf_url": f"/download-pdf/{session_id}" if memory.pdf_filename else None,
782
- "message": "Session loaded successfully. You can continue the conversation."
783
- }
784
-
785
- @app.get("/all-sessions")
786
- async def get_all_sessions():
787
- """Get list of all stored consultation sessions"""
788
- return {
789
- "total_sessions": len(list(STORAGE_DIR.glob("*.json"))),
790
- "sessions": list_all_sessions()
791
- }
792
-
793
- @app.post("/restart-session")
794
- async def restart_session(request: SessionRequest):
795
- """Restart a consultation session"""
796
- if request.session_id in sessions:
797
- del sessions[request.session_id]
798
-
799
- sessions[request.session_id] = ConversationMemory(max_messages=20, session_id=request.session_id)
800
-
801
- initial_message = "Consultation restarted. Hello! I'm Dr. AI Assistant. May I have your name please?"
802
- sessions[request.session_id].add_message("assistant", initial_message)
803
-
804
- return {
805
- "session_id": request.session_id,
806
- "message": initial_message,
807
- "timestamp": datetime.now().isoformat()
808
- }
809
-
810
- @app.delete("/session/{session_id}")
811
- async def delete_session(session_id: str):
812
- """Delete a consultation session (from memory, JSON, and PDF)"""
813
- if session_id in sessions:
814
- memory = sessions[session_id]
815
- pdf_filename = memory.pdf_filename
816
- del sessions[session_id]
817
- else:
818
- session_data = load_session_from_json(session_id)
819
- pdf_filename = session_data.get('pdf_filename') if session_data else None
820
-
821
- # Remove JSON file
822
- file_path = STORAGE_DIR / f"{session_id}.json"
823
- if file_path.exists():
824
- file_path.unlink()
825
-
826
- # Remove PDF file if exists
827
- if pdf_filename:
828
- pdf_path = PDF_DIR / pdf_filename
829
- if pdf_path.exists():
830
- pdf_path.unlink()
831
-
832
- return {"message": "Session and associated files deleted successfully"}
833
-
834
- @app.get("/session/{session_id}/history")
835
- async def get_session_history(session_id: str):
836
- """Get conversation history for a session"""
837
- if session_id in sessions:
838
- memory = sessions[session_id]
839
- else:
840
- session_data = load_session_from_json(session_id)
841
- if not session_data:
842
- raise HTTPException(status_code=404, detail="Session not found")
843
- memory = ConversationMemory.from_json(session_data)
844
-
845
- return {
846
- "session_id": session_id,
847
- "history": memory.history,
848
- "patient_data": memory.patient_data,
849
- "created_at": memory.created_at.isoformat(),
850
- "questions_asked": memory.questions_asked,
851
- "has_pdf": memory.pdf_filename is not None
852
- }
853
-
854
- @app.get("/active-sessions")
855
- async def get_active_sessions():
856
- """Get list of all active sessions in memory"""
857
- cleanup_old_sessions()
858
- return {
859
- "active_sessions": len(sessions),
860
- "sessions": [
861
- {
862
- "session_id": sid,
863
- "created_at": mem.created_at.isoformat(),
864
- "message_count": len(mem.history),
865
- "questions_asked": mem.questions_asked,
866
- "patient_data": mem.patient_data,
867
- "has_pdf": mem.pdf_filename is not None
868
- }
869
- for sid, mem in sessions.items()
870
- ]
871
- }
872
-
873
- if __name__ == "__main__":
874
- import uvicorn
875
  uvicorn.run(app, host="0.0.0.0", port=8000)
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.responses import FileResponse
4
+ from pydantic import BaseModel
5
+ from typing import List, Optional, Dict
6
+ import google.generativeai as genai
7
+ import os
8
+ from datetime import datetime
9
+ import uuid
10
+ import json
11
+ from pathlib import Path
12
+ from reportlab.lib.pagesizes import letter, A4
13
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
14
+ from reportlab.lib.units import inch
15
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle
16
+ from reportlab.lib import colors
17
+ from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
18
+ from reportlab.pdfgen import canvas
19
+
20
+ # Configure Gemini API
21
+ os.environ["GOOGLE_API_KEY"] = "AIzaSyDTyKE4apGmzAi38CrWvjdVJ1vV6fdm-w8"
22
+ genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
23
+
24
+ MODEL_ID = "gemini-2.0-flash-exp"
25
+
26
+ # Create storage directories
27
+ STORAGE_DIR = Path("consultation_storage")
28
+ STORAGE_DIR.mkdir(exist_ok=True)
29
+
30
+ PDF_DIR = Path("consultation_pdfs")
31
+ PDF_DIR.mkdir(exist_ok=True)
32
+
33
+ # System prompt (same as before)
34
+ DOCTOR_SYSTEM_PROMPT = """
35
+ You are Dr. HealBot, a calm, knowledgeable, and empathetic virtual doctor.
36
+
37
+ GOAL:
38
+ Hold a natural, focused conversation with the patient to understand their health issue and offer helpful preliminary medical guidance.
39
+
40
+ CONVERSATION LOGIC:
41
+ - Ask only relevant and concise medical questions necessary for diagnosing the illness.
42
+ - Each question should help clarify symptoms or narrow possible causes.
43
+ - Stop asking once enough information is collected for a basic assessment.
44
+ - Then, provide a structured, friendly, and visually clear medical response using headings, emojis, and bullet points.
45
+
46
+ FINAL RESPONSE FORMAT:
47
+ When giving your full assessment, use this markdown-styled format:
48
+
49
+ 🩺 Based on what you've told me...
50
+ Brief summary of what the patient described.
51
+
52
+ πŸ’‘ Possible Causes (Preliminary)
53
+ - List 1–2 possible conditions using phrases like "It could be" or "This sounds like".
54
+ - Include a disclaimer that this is not a confirmed diagnosis.
55
+
56
+ πŸ’Š Suggested Over-the-Counter Medicines
57
+ - Generic medicine names only (e.g., "Paracetamol 500mg every 6 hours if fever or pain")
58
+ - Mention to check packaging or consult a pharmacist for dosage confirmation.
59
+
60
+ πŸ₯— Lifestyle & Home Care Tips
61
+ - 2–3 practical suggestions (rest, hydration, warm compress, balanced diet, etc.)
62
+
63
+ ⚠ When to See a Real Doctor
64
+ - 2–3 warning signs or conditions when urgent medical care is needed.
65
+
66
+ πŸ“… Follow-Up Advice
67
+ - Brief recommendation for self-care or follow-up timing (e.g., "If not improving in 3 days, visit a clinic.")
68
+
69
+ TONE & STYLE:
70
+ - Speak like a real, caring doctor β€” short, clear, and empathetic (1–2 sentences per reply).
71
+ - Use plain language, no jargon.
72
+ - Only one question per turn unless clarification is essential.
73
+ - Keep tone warm, calm, and professional.
74
+ - Early messages: short questions only.
75
+ - Final message: structured output with emojis and headings.
76
+
77
+ IMPORTANT:
78
+ - Always emphasize that this is preliminary guidance and not a substitute for professional care.
79
+ - Never make definitive diagnoses; use phrases like "it sounds like" or "it could be".
80
+ - If symptoms seem serious, always recommend urgent medical attention.
81
+
82
+ CONVERSATION FLOW:
83
+ 1. Ask about the main symptom.
84
+ 2. Ask about its duration, severity, and any triggers.
85
+ 3. Ask about accompanying symptoms.
86
+ 4. Ask about medical history, allergies, or medications.
87
+ 5. Then, provide your structured assessment as described above.
88
+ """
89
+
90
+ # =====================================================
91
+ # PDF GENERATION FUNCTIONS
92
+ # =====================================================
93
+
94
+ def generate_pdf_summary(session_id: str, summary_text: str, patient_data: Dict, history: List[Dict]) -> str:
95
+ """Generate a professional PDF summary of the consultation"""
96
+
97
+ pdf_filename = f"{session_id}_summary.pdf"
98
+ pdf_path = PDF_DIR / pdf_filename
99
+
100
+ # Create PDF document
101
+ doc = SimpleDocTemplate(str(pdf_path), pagesize=letter,
102
+ rightMargin=72, leftMargin=72,
103
+ topMargin=72, bottomMargin=18)
104
+
105
+ # Container for the 'Flowable' objects
106
+ elements = []
107
+
108
+ # Define styles
109
+ styles = getSampleStyleSheet()
110
+
111
+ # Custom styles
112
+ title_style = ParagraphStyle(
113
+ 'CustomTitle',
114
+ parent=styles['Heading1'],
115
+ fontSize=24,
116
+ textColor=colors.HexColor('#667eea'),
117
+ spaceAfter=30,
118
+ alignment=TA_CENTER,
119
+ fontName='Helvetica-Bold'
120
+ )
121
+
122
+ heading_style = ParagraphStyle(
123
+ 'CustomHeading',
124
+ parent=styles['Heading2'],
125
+ fontSize=16,
126
+ textColor=colors.HexColor('#667eea'),
127
+ spaceAfter=12,
128
+ spaceBefore=12,
129
+ fontName='Helvetica-Bold'
130
+ )
131
+
132
+ normal_style = ParagraphStyle(
133
+ 'CustomNormal',
134
+ parent=styles['Normal'],
135
+ fontSize=11,
136
+ spaceAfter=12,
137
+ alignment=TA_JUSTIFY,
138
+ leading=14
139
+ )
140
+
141
+ # Add Title
142
+ elements.append(Paragraph("🩺 AI DOCTOR CONSULTATION SUMMARY", title_style))
143
+ elements.append(Spacer(1, 0.3*inch))
144
+
145
+ # Add horizontal line
146
+ elements.append(Spacer(1, 0.1*inch))
147
+
148
+ # Patient Information Table
149
+ patient_info_data = [
150
+ ['Patient Name:', patient_data.get('name', 'N/A')],
151
+ ['Age:', patient_data.get('age', 'N/A')],
152
+ ['Session ID:', session_id[:20] + '...'],
153
+ ['Consultation Date:', datetime.now().strftime('%B %d, %Y at %I:%M %p')],
154
+ ['Total Messages:', str(len(history))]
155
+ ]
156
+
157
+ patient_table = Table(patient_info_data, colWidths=[2*inch, 4*inch])
158
+ patient_table.setStyle(TableStyle([
159
+ ('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f0f0f0')),
160
+ ('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
161
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
162
+ ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
163
+ ('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
164
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
165
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
166
+ ('TOPPADDING', (0, 0), (-1, -1), 8),
167
+ ('GRID', (0, 0), (-1, -1), 1, colors.grey)
168
+ ]))
169
+
170
+ elements.append(patient_table)
171
+ elements.append(Spacer(1, 0.3*inch))
172
+
173
+ # Add Consultation Summary
174
+ elements.append(Paragraph("CONSULTATION SUMMARY", heading_style))
175
+
176
+ # Process summary text - split by lines and convert to paragraphs
177
+ summary_lines = summary_text.split('\n')
178
+ for line in summary_lines:
179
+ if line.strip():
180
+ # Replace emojis with text equivalents for PDF compatibility
181
+ line = line.replace('🩺', '[Medical] ')
182
+ line = line.replace('πŸ’‘', '[Insight] ')
183
+ line = line.replace('πŸ’Š', '[Medicine] ')
184
+ line = line.replace('πŸ₯—', '[Lifestyle] ')
185
+ line = line.replace('⚠️', '[Warning] ')
186
+ line = line.replace('⚠', '[Warning] ')
187
+ line = line.replace('πŸ“…', '[Follow-up] ')
188
+ line = line.replace('━', '-')
189
+
190
+ # Check if it's a heading (starts with **)
191
+ if line.strip().startswith('**') and line.strip().endswith('**'):
192
+ elements.append(Paragraph(line.strip('*'), heading_style))
193
+ else:
194
+ elements.append(Paragraph(line, normal_style))
195
+
196
+ elements.append(Spacer(1, 0.3*inch))
197
+
198
+ # Add Conversation History
199
+ elements.append(PageBreak())
200
+ elements.append(Paragraph("CONVERSATION HISTORY", heading_style))
201
+ elements.append(Spacer(1, 0.2*inch))
202
+
203
+ for i, msg in enumerate(history, 1):
204
+ role = "DOCTOR" if msg['role'] == 'assistant' else "PATIENT"
205
+ timestamp = msg.get('timestamp', 'N/A')
206
+
207
+ role_style = ParagraphStyle(
208
+ f'Role{i}',
209
+ parent=styles['Normal'],
210
+ fontSize=10,
211
+ textColor=colors.HexColor('#667eea') if role == "DOCTOR" else colors.HexColor('#28a745'),
212
+ fontName='Helvetica-Bold',
213
+ spaceAfter=4
214
+ )
215
+
216
+ elements.append(Paragraph(f"{role} ({timestamp}):", role_style))
217
+
218
+ content = msg['content'].replace('🩺', '').replace('πŸ’‘', '').replace('πŸ’Š', '')
219
+ content = content.replace('πŸ₯—', '').replace('⚠️', '').replace('⚠', '').replace('πŸ“…', '')
220
+ elements.append(Paragraph(content, normal_style))
221
+ elements.append(Spacer(1, 0.15*inch))
222
+
223
+ # Add disclaimer at the end
224
+ elements.append(Spacer(1, 0.3*inch))
225
+
226
+ disclaimer_style = ParagraphStyle(
227
+ 'Disclaimer',
228
+ parent=styles['Normal'],
229
+ fontSize=9,
230
+ textColor=colors.red,
231
+ alignment=TA_CENTER,
232
+ fontName='Helvetica-Bold',
233
+ borderColor=colors.red,
234
+ borderWidth=1,
235
+ borderPadding=10,
236
+ spaceAfter=12
237
+ )
238
+
239
+ elements.append(Paragraph(
240
+ "⚠ IMPORTANT DISCLAIMER ⚠<br/>" +
241
+ "This is a preliminary AI-generated consultation for informational purposes only.<br/>" +
242
+ "It is NOT a substitute for professional medical advice, diagnosis, or treatment.<br/>" +
243
+ "Always seek the advice of a qualified healthcare provider with any questions regarding a medical condition.",
244
+ disclaimer_style
245
+ ))
246
+
247
+ # Build PDF
248
+ doc.build(elements)
249
+
250
+ return pdf_filename
251
+
252
+ # =====================================================
253
+ # STORAGE FUNCTIONS (same as before)
254
+ # =====================================================
255
+
256
+ def save_session_to_json(session_id: str, memory: 'ConversationMemory'):
257
+ """Save session data to JSON file"""
258
+ file_path = STORAGE_DIR / f"{session_id}.json"
259
+
260
+ session_data = {
261
+ "session_id": session_id,
262
+ "created_at": memory.created_at.isoformat(),
263
+ "last_updated": datetime.now().isoformat(),
264
+ "patient_data": memory.patient_data,
265
+ "questions_asked": memory.questions_asked,
266
+ "history": memory.history,
267
+ "message_count": len(memory.history),
268
+ "pdf_filename": getattr(memory, 'pdf_filename', None)
269
+ }
270
+
271
+ with open(file_path, 'w', encoding='utf-8') as f:
272
+ json.dump(session_data, f, indent=2, ensure_ascii=False)
273
+
274
+ def load_session_from_json(session_id: str) -> Optional[Dict]:
275
+ """Load session data from JSON file"""
276
+ file_path = STORAGE_DIR / f"{session_id}.json"
277
+
278
+ if not file_path.exists():
279
+ return None
280
+
281
+ with open(file_path, 'r', encoding='utf-8') as f:
282
+ return json.load(f)
283
+
284
+ def list_all_sessions() -> List[Dict]:
285
+ """List all stored sessions"""
286
+ sessions_list = []
287
+
288
+ for file_path in STORAGE_DIR.glob("*.json"):
289
+ try:
290
+ with open(file_path, 'r', encoding='utf-8') as f:
291
+ data = json.load(f)
292
+ sessions_list.append({
293
+ "session_id": data["session_id"],
294
+ "created_at": data["created_at"],
295
+ "last_updated": data.get("last_updated", data["created_at"]),
296
+ "patient_name": data["patient_data"].get("name", "Unknown"),
297
+ "message_count": data["message_count"],
298
+ "has_pdf": data.get("pdf_filename") is not None
299
+ })
300
+ except Exception as e:
301
+ print(f"Error reading {file_path}: {e}")
302
+
303
+ return sorted(sessions_list, key=lambda x: x["last_updated"], reverse=True)
304
+
305
+ # =====================================================
306
+ # MEMORY MANAGEMENT (same as before)
307
+ # =====================================================
308
+
309
+ class ConversationMemory:
310
+ """Manages short-term memory for each session"""
311
+ def __init__(self, max_messages: int = 20, session_id: str = None):
312
+ self.max_messages = max_messages
313
+ self.history = []
314
+ self.patient_data = {}
315
+ self.created_at = datetime.now()
316
+ self.questions_asked = 0
317
+ self.session_id = session_id
318
+ self.pdf_filename = None
319
+
320
+ def add_message(self, role: str, content: str):
321
+ """Add message to history with memory management"""
322
+ self.history.append({
323
+ "role": role,
324
+ "content": content,
325
+ "timestamp": datetime.now().isoformat()
326
+ })
327
+
328
+ if role == "assistant" and "?" in content:
329
+ self.questions_asked += 1
330
+
331
+ if len(self.history) > self.max_messages:
332
+ self.history = [self.history[0]] + self.history[-(self.max_messages-1):]
333
+
334
+ if self.session_id:
335
+ save_session_to_json(self.session_id, self)
336
+
337
+ def extract_patient_info(self, message: str):
338
+ """Extract and store patient information from conversation"""
339
+ message_lower = message.lower()
340
+
341
+ if any(word in message_lower for word in ["name is", "i'm", "i am", "im"]):
342
+ words = message.split()
343
+ for i, word in enumerate(words):
344
+ if word.lower() in ["is", "i'm", "am", "im"] and i + 1 < len(words):
345
+ self.patient_data["name"] = words[i + 1].strip(".,!?")
346
+
347
+ if "year" in message_lower or "age" in message_lower:
348
+ import re
349
+ age_match = re.search(r'\b(\d{1,3})\b', message)
350
+ if age_match:
351
+ self.patient_data["age"] = age_match.group(1)
352
+
353
+ if "fever" in message_lower or "pain" in message_lower or "sick" in message_lower:
354
+ self.patient_data["has_symptoms"] = True
355
+
356
+ def should_give_recommendations(self) -> bool:
357
+ """Check if we should provide recommendations now"""
358
+ return (
359
+ self.questions_asked >= 7 or
360
+ self.patient_data.get("has_symptoms", False)
361
+ )
362
+
363
+ def get_context_summary(self) -> str:
364
+ """Generate a brief context summary for the AI"""
365
+ summary = "\n[Session Context: "
366
+ if "name" in self.patient_data:
367
+ summary += f"Name: {self.patient_data['name']}, "
368
+ if "age" in self.patient_data:
369
+ summary += f"Age: {self.patient_data['age']}, "
370
+ summary += f"Questions asked: {self.questions_asked}/7, "
371
+
372
+ if self.questions_asked >= 5:
373
+ summary += "⚠️ IMPORTANT: You've asked enough questions. After the next 1-2 answers, IMMEDIATELY provide comprehensive medical recommendations.]"
374
+ elif self.questions_asked >= 7:
375
+ summary += "⚠️ CRITICAL: You MUST provide comprehensive medical recommendations NOW. Do not ask more questions!]"
376
+ else:
377
+ summary += f"Ask {7 - self.questions_asked} more essential questions then give recommendations.]"
378
+
379
+ return summary
380
+
381
+ def get_gemini_history(self) -> List[Dict]:
382
+ """Convert history to Gemini format"""
383
+ gemini_history = []
384
+ for msg in self.history:
385
+ gemini_history.append({
386
+ "role": "user" if msg["role"] == "user" else "model",
387
+ "parts": [msg["content"]]
388
+ })
389
+ return gemini_history
390
+
391
+ @classmethod
392
+ def from_json(cls, session_data: Dict) -> 'ConversationMemory':
393
+ """Create ConversationMemory from JSON data"""
394
+ memory = cls(session_id=session_data["session_id"])
395
+ memory.history = session_data["history"]
396
+ memory.patient_data = session_data["patient_data"]
397
+ memory.questions_asked = session_data["questions_asked"]
398
+ memory.created_at = datetime.fromisoformat(session_data["created_at"])
399
+ memory.pdf_filename = session_data.get("pdf_filename")
400
+ return memory
401
+
402
+ sessions: Dict[str, ConversationMemory] = {}
403
+
404
+ def cleanup_old_sessions():
405
+ """Remove sessions older than 1 hour from memory"""
406
+ current_time = datetime.now()
407
+ expired_sessions = []
408
+
409
+ for session_id, memory in sessions.items():
410
+ age = (current_time - memory.created_at).total_seconds()
411
+ if age > 3600:
412
+ expired_sessions.append(session_id)
413
+
414
+ for session_id in expired_sessions:
415
+ del sessions[session_id]
416
+
417
+ # =====================================================
418
+ # FASTAPI APPLICATION
419
+ # =====================================================
420
+
421
+ app = FastAPI(
422
+ title="AI Doctor Consultation API with PDF Generation",
423
+ description="Professional medical consultation API with PDF summary generation",
424
+ version="3.0.0"
425
+ )
426
+
427
+ app.add_middleware(
428
+ CORSMiddleware,
429
+ allow_origins=["*"],
430
+ allow_credentials=True,
431
+ allow_methods=["*"],
432
+ allow_headers=["*"],
433
+ )
434
+
435
+ # Pydantic models
436
+ class ChatRequest(BaseModel):
437
+ session_id: Optional[str] = None
438
+ message: str
439
+
440
+ class ChatResponse(BaseModel):
441
+ session_id: str
442
+ response: str
443
+ timestamp: str
444
+ patient_data: Dict
445
+
446
+ class SessionRequest(BaseModel):
447
+ session_id: str
448
+
449
+ class SummaryResponse(BaseModel):
450
+ summary: str
451
+ session_id: str
452
+ pdf_filename: str
453
+ pdf_url: str
454
+
455
+ class HealthCheck(BaseModel):
456
+ status: str
457
+ timestamp: str
458
+ active_sessions: int
459
+ stored_sessions: int
460
+ stored_pdfs: int
461
+
462
+ # =====================================================
463
+ # API ENDPOINTS
464
+ # =====================================================
465
+
466
+ @app.get("/", response_model=HealthCheck)
467
+ async def root():
468
+ """Health check endpoint"""
469
+ cleanup_old_sessions()
470
+ stored_count = len(list(STORAGE_DIR.glob("*.json")))
471
+ pdf_count = len(list(PDF_DIR.glob("*.pdf")))
472
+ return {
473
+ "status": "healthy",
474
+ "timestamp": datetime.now().isoformat(),
475
+ "active_sessions": len(sessions),
476
+ "stored_sessions": stored_count,
477
+ "stored_pdfs": pdf_count
478
+ }
479
+
480
+ @app.post("/start-session")
481
+ async def start_session():
482
+ """Start a new consultation session"""
483
+ session_id = str(uuid.uuid4())
484
+ sessions[session_id] = ConversationMemory(max_messages=20, session_id=session_id)
485
+
486
+ initial_message = "Hello! I'm Dr. AI Assistant. I'm here to help you today.\n\nπŸ‘€ May I have your name, please?"
487
+
488
+ sessions[session_id].add_message("assistant", initial_message)
489
+
490
+ return {
491
+ "session_id": session_id,
492
+ "message": initial_message,
493
+ "timestamp": datetime.now().isoformat()
494
+ }
495
+
496
+ @app.post("/chat", response_model=ChatResponse)
497
+ async def chat(request: ChatRequest):
498
+ """Send a message and get doctor's response"""
499
+ try:
500
+ if not request.session_id or request.session_id not in sessions:
501
+ session_id = str(uuid.uuid4())
502
+ sessions[session_id] = ConversationMemory(max_messages=20, session_id=session_id)
503
+ else:
504
+ session_id = request.session_id
505
+
506
+ memory = sessions[session_id]
507
+ memory.extract_patient_info(request.message)
508
+ memory.add_message("user", request.message)
509
+
510
+ context = memory.get_context_summary()
511
+ system_prompt = DOCTOR_SYSTEM_PROMPT + context
512
+
513
+ model = genai.GenerativeModel(
514
+ model_name=MODEL_ID,
515
+ system_instruction=system_prompt
516
+ )
517
+
518
+ chat = model.start_chat(history=memory.get_gemini_history()[:-1])
519
+ response = chat.send_message(request.message)
520
+ doctor_response = response.text
521
+
522
+ memory.add_message("assistant", doctor_response)
523
+
524
+ return {
525
+ "session_id": session_id,
526
+ "response": doctor_response,
527
+ "timestamp": datetime.now().isoformat(),
528
+ "patient_data": memory.patient_data
529
+ }
530
+
531
+ except Exception as e:
532
+ raise HTTPException(status_code=500, detail=f"Error: {str(e)}")
533
+
534
+ @app.post("/summary", response_model=SummaryResponse)
535
+ async def generate_summary(request: SessionRequest):
536
+ """Generate consultation summary and PDF"""
537
+ if request.session_id not in sessions:
538
+ session_data = load_session_from_json(request.session_id)
539
+ if not session_data:
540
+ raise HTTPException(status_code=404, detail="Session not found")
541
+ memory = ConversationMemory.from_json(session_data)
542
+ sessions[request.session_id] = memory
543
+ else:
544
+ memory = sessions[request.session_id]
545
+
546
+ summary_request = """Please generate a COMPREHENSIVE and DETAILED medical consultation summary based on our entire conversation. Make it thorough and professional:
547
+
548
+ πŸ“‹ **COMPREHENSIVE MEDICAL CONSULTATION SUMMARY**
549
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
550
+
551
+ **PATIENT INFORMATION:**
552
+ - Full Name: [Patient's name]
553
+ - Age: [Patient's age if mentioned]
554
+ - Gender: [If mentioned]
555
+ - Consultation Date: [Current date and time]
556
+ - Session Duration: [Approximate]
557
+ - Current Medications: [List all mentioned]
558
+ - Known Allergies: [If mentioned]
559
+
560
+ **CHIEF COMPLAINTS & SYMPTOMS:**
561
+ [Provide a detailed description of ALL symptoms mentioned, including:]
562
+ - Primary symptom and severity
563
+ - Duration of each symptom
564
+ - Onset and progression
565
+ - Associated symptoms
566
+ - Aggravating and relieving factors
567
+ - Impact on daily activities
568
+
569
+ **DETAILED MEDICAL HISTORY:**
570
+ [Include everything discussed:]
571
+ - Current medications and dosages
572
+ - Past medical conditions
573
+ - Recent illnesses or infections
574
+ - Family medical history (if mentioned)
575
+ - Lifestyle factors (sleep, stress, diet)
576
+ - Recent travel or exposures
577
+
578
+ **CLINICAL ASSESSMENT:**
579
+ [Provide detailed analysis:]
580
+ - Most likely diagnosis with explanation
581
+ - Differential diagnoses (2-3 possibilities)
582
+ - Reasoning behind each possibility
583
+ - Risk factors present
584
+ - Severity assessment
585
+
586
+ **COMPREHENSIVE TREATMENT PLAN:**
587
+
588
+ 1. **IMMEDIATE CARE RECOMMENDATIONS:**
589
+ - What to do in the next 24-48 hours
590
+ - Symptom management strategies
591
+ - Warning signs to watch for
592
+
593
+ 2. **MEDICATION RECOMMENDATIONS:**
594
+ - Primary medications (generic names, dosages, frequency, duration)
595
+ - Alternative options if first choice unavailable
596
+ - Potential side effects to monitor
597
+ - Drug interactions to avoid
598
+ - When to take each medication (with/without food)
599
+ - Important: Check with pharmacist for exact dosing
600
+
601
+ 3. **DETAILED DIETARY RECOMMENDATIONS:**
602
+ - Foods to eat (specific examples and portions)
603
+ - Foods to avoid completely
604
+ - Meal timing and frequency
605
+ - Hydration guidelines (specific amounts)
606
+ - Nutritional supplements if needed
607
+ - Sample meal plan for recovery
608
+
609
+ 4. **LIFESTYLE MODIFICATIONS:**
610
+ - Sleep recommendations (hours, timing, environment)
611
+ - Rest and activity balance
612
+ - Stress management techniques
613
+ - Environmental modifications
614
+ - Work/school attendance guidance
615
+ - Specific activities to avoid
616
+
617
+ 5. **HOME CARE REMEDIES:**
618
+ - Natural remedies that may help
619
+ - Temperature management techniques
620
+ - Pain relief methods
621
+ - Steam inhalation or other therapies
622
+ - Specific home treatments for symptoms
623
+
624
+ 6. **EXERCISE & PHYSICAL ACTIVITY:**
625
+ - Current activity restrictions
626
+ - Safe exercises during recovery
627
+ - When to resume normal activities
628
+ - Gradual activity progression plan
629
+ - Post-recovery exercise recommendations
630
+
631
+ 7. **PREVENTIVE MEASURES:**
632
+ - How to prevent recurrence
633
+ - Hygiene practices
634
+ - Vaccination recommendations
635
+ - Family/household precautions
636
+ - Long-term health maintenance
637
+
638
+ 8. **MONITORING PLAN:**
639
+ - Symptoms to track daily
640
+ - How to measure improvement
641
+ - When improvement should be expected
642
+ - What to document for doctor visit
643
+
644
+ **CRITICAL WARNING SIGNS - SEEK IMMEDIATE MEDICAL ATTENTION IF:**
645
+ [List 5-7 specific warning signs that require emergency care:]
646
+ - [Specific symptom with threshold]
647
+ - [Specific symptom with threshold]
648
+ - [Continue with detailed warnings]
649
+
650
+ **FOLLOW-UP CARE PLAN:**
651
+ - Timeline for self-care (e.g., "Monitor for 48 hours")
652
+ - When to schedule doctor appointment (specific timeframe)
653
+ - What information to bring to doctor
654
+ - Specialist referral recommendations if needed
655
+ - Follow-up testing that may be needed
656
+
657
+ **PROGNOSIS & EXPECTED RECOVERY:**
658
+ - Expected recovery timeline
659
+ - What to expect during recovery process
660
+ - Signs of improvement to look for
661
+ - Long-term outlook
662
+
663
+ **ADDITIONAL RESOURCES:**
664
+ - Reputable health information sources
665
+ - Support resources if applicable
666
+ - Emergency contact information reminder
667
+
668
+ **PATIENT EDUCATION:**
669
+ - Understanding your condition
670
+ - How the body fights this illness
671
+ - Why specific recommendations are important
672
+ - Common misconceptions about this condition
673
+
674
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
675
+ ⚠️ **CRITICAL DISCLAIMER** ⚠️
676
+ This is a preliminary AI-generated consultation for informational and educational purposes ONLY.
677
+ This is NOT a substitute for professional medical advice, diagnosis, or treatment.
678
+ This AI cannot examine you physically, run laboratory tests, or make definitive diagnoses.
679
+ ALWAYS seek the advice of a qualified, licensed healthcare provider with any questions regarding a medical condition.
680
+ Never disregard professional medical advice or delay seeking it because of this AI consultation.
681
+ In case of emergency, call your local emergency services immediately.
682
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
683
+
684
+ Please make this summary as detailed, professional, and helpful as possible. Include specific, actionable advice."""
685
+
686
+ try:
687
+ model = genai.GenerativeModel(
688
+ model_name=MODEL_ID,
689
+ system_instruction=DOCTOR_SYSTEM_PROMPT
690
+ )
691
+
692
+ chat = model.start_chat(history=memory.get_gemini_history())
693
+ response = chat.send_message(summary_request)
694
+ summary_text = response.text
695
+
696
+ # Generate PDF
697
+ pdf_filename = generate_pdf_summary(
698
+ request.session_id,
699
+ summary_text,
700
+ memory.patient_data,
701
+ memory.history
702
+ )
703
+
704
+ # Save PDF filename to memory
705
+ memory.pdf_filename = pdf_filename
706
+ save_session_to_json(request.session_id, memory)
707
+
708
+ return {
709
+ "summary": summary_text,
710
+ "session_id": request.session_id,
711
+ "pdf_filename": pdf_filename,
712
+ "pdf_url": f"/download-pdf/{request.session_id}"
713
+ }
714
+
715
+ except Exception as e:
716
+ raise HTTPException(status_code=500, detail=f"Error generating summary: {str(e)}")
717
+
718
+ @app.get("/download-pdf/{session_id}")
719
+ async def download_pdf(session_id: str):
720
+ """Download PDF summary for a session"""
721
+ # Check if session exists
722
+ if session_id in sessions:
723
+ memory = sessions[session_id]
724
+ else:
725
+ session_data = load_session_from_json(session_id)
726
+ if not session_data:
727
+ raise HTTPException(status_code=404, detail="Session not found")
728
+ memory = ConversationMemory.from_json(session_data)
729
+
730
+ if not memory.pdf_filename:
731
+ raise HTTPException(status_code=404, detail="PDF not generated yet. Please generate summary first.")
732
+
733
+ pdf_path = PDF_DIR / memory.pdf_filename
734
+
735
+ if not pdf_path.exists():
736
+ raise HTTPException(status_code=404, detail="PDF file not found")
737
+
738
+ patient_name = memory.patient_data.get('name', 'Patient')
739
+ download_filename = f"Consultation_Summary_{patient_name}_{datetime.now().strftime('%Y%m%d')}.pdf"
740
+
741
+ return FileResponse(
742
+ path=str(pdf_path),
743
+ media_type='application/pdf',
744
+ filename=download_filename
745
+ )
746
+
747
+ @app.get("/load-session/{session_id}")
748
+ async def load_session(session_id: str):
749
+ """Load a previous consultation session by ID"""
750
+ if session_id in sessions:
751
+ memory = sessions[session_id]
752
+ return {
753
+ "session_id": session_id,
754
+ "loaded": True,
755
+ "from_cache": True,
756
+ "history": memory.history,
757
+ "patient_data": memory.patient_data,
758
+ "created_at": memory.created_at.isoformat(),
759
+ "questions_asked": memory.questions_asked,
760
+ "has_pdf": memory.pdf_filename is not None,
761
+ "pdf_url": f"/download-pdf/{session_id}" if memory.pdf_filename else None
762
+ }
763
+
764
+ session_data = load_session_from_json(session_id)
765
+
766
+ if not session_data:
767
+ raise HTTPException(status_code=404, detail=f"Session {session_id} not found")
768
+
769
+ memory = ConversationMemory.from_json(session_data)
770
+ sessions[session_id] = memory
771
+
772
+ return {
773
+ "session_id": session_id,
774
+ "loaded": True,
775
+ "from_cache": False,
776
+ "history": memory.history,
777
+ "patient_data": memory.patient_data,
778
+ "created_at": memory.created_at.isoformat(),
779
+ "questions_asked": memory.questions_asked,
780
+ "has_pdf": memory.pdf_filename is not None,
781
+ "pdf_url": f"/download-pdf/{session_id}" if memory.pdf_filename else None,
782
+ "message": "Session loaded successfully. You can continue the conversation."
783
+ }
784
+
785
+ @app.get("/all-sessions")
786
+ async def get_all_sessions():
787
+ """Get list of all stored consultation sessions"""
788
+ return {
789
+ "total_sessions": len(list(STORAGE_DIR.glob("*.json"))),
790
+ "sessions": list_all_sessions()
791
+ }
792
+
793
+ @app.post("/restart-session")
794
+ async def restart_session(request: SessionRequest):
795
+ """Restart a consultation session"""
796
+ if request.session_id in sessions:
797
+ del sessions[request.session_id]
798
+
799
+ sessions[request.session_id] = ConversationMemory(max_messages=20, session_id=request.session_id)
800
+
801
+ initial_message = "Consultation restarted. Hello! I'm Dr. AI Assistant. May I have your name please?"
802
+ sessions[request.session_id].add_message("assistant", initial_message)
803
+
804
+ return {
805
+ "session_id": request.session_id,
806
+ "message": initial_message,
807
+ "timestamp": datetime.now().isoformat()
808
+ }
809
+
810
+ @app.delete("/session/{session_id}")
811
+ async def delete_session(session_id: str):
812
+ """Delete a consultation session (from memory, JSON, and PDF)"""
813
+ if session_id in sessions:
814
+ memory = sessions[session_id]
815
+ pdf_filename = memory.pdf_filename
816
+ del sessions[session_id]
817
+ else:
818
+ session_data = load_session_from_json(session_id)
819
+ pdf_filename = session_data.get('pdf_filename') if session_data else None
820
+
821
+ # Remove JSON file
822
+ file_path = STORAGE_DIR / f"{session_id}.json"
823
+ if file_path.exists():
824
+ file_path.unlink()
825
+
826
+ # Remove PDF file if exists
827
+ if pdf_filename:
828
+ pdf_path = PDF_DIR / pdf_filename
829
+ if pdf_path.exists():
830
+ pdf_path.unlink()
831
+
832
+ return {"message": "Session and associated files deleted successfully"}
833
+
834
+ @app.get("/session/{session_id}/history")
835
+ async def get_session_history(session_id: str):
836
+ """Get conversation history for a session"""
837
+ if session_id in sessions:
838
+ memory = sessions[session_id]
839
+ else:
840
+ session_data = load_session_from_json(session_id)
841
+ if not session_data:
842
+ raise HTTPException(status_code=404, detail="Session not found")
843
+ memory = ConversationMemory.from_json(session_data)
844
+
845
+ return {
846
+ "session_id": session_id,
847
+ "history": memory.history,
848
+ "patient_data": memory.patient_data,
849
+ "created_at": memory.created_at.isoformat(),
850
+ "questions_asked": memory.questions_asked,
851
+ "has_pdf": memory.pdf_filename is not None
852
+ }
853
+
854
+ @app.get("/active-sessions")
855
+ async def get_active_sessions():
856
+ """Get list of all active sessions in memory"""
857
+ cleanup_old_sessions()
858
+ return {
859
+ "active_sessions": len(sessions),
860
+ "sessions": [
861
+ {
862
+ "session_id": sid,
863
+ "created_at": mem.created_at.isoformat(),
864
+ "message_count": len(mem.history),
865
+ "questions_asked": mem.questions_asked,
866
+ "patient_data": mem.patient_data,
867
+ "has_pdf": mem.pdf_filename is not None
868
+ }
869
+ for sid, mem in sessions.items()
870
+ ]
871
+ }
872
+
873
+ if __name__ == "__main__":
874
+ import uvicorn
875
  uvicorn.run(app, host="0.0.0.0", port=8000)