|
|
from fastapi import FastAPI, HTTPException
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
from fastapi.responses import FileResponse
|
|
|
from pydantic import BaseModel
|
|
|
from typing import List, Optional, Dict
|
|
|
import google.generativeai as genai
|
|
|
import os
|
|
|
from datetime import datetime
|
|
|
import uuid
|
|
|
import json
|
|
|
from pathlib import Path
|
|
|
from reportlab.lib.pagesizes import letter, A4
|
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
|
from reportlab.lib.units import inch
|
|
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle
|
|
|
from reportlab.lib import colors
|
|
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
|
|
|
from reportlab.pdfgen import canvas
|
|
|
|
|
|
|
|
|
os.environ["GOOGLE_API_KEY"] = "AIzaSyDid3I53qFohhZTnLjnOjz4QqlvK4RZm7o"
|
|
|
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
|
|
|
|
|
|
MODEL_ID = "gemini-2.0-flash-exp"
|
|
|
|
|
|
|
|
|
STORAGE_DIR = Path("consultation_storage")
|
|
|
STORAGE_DIR.mkdir(exist_ok=True)
|
|
|
|
|
|
PDF_DIR = Path("consultation_pdfs")
|
|
|
PDF_DIR.mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
|
DOCTOR_SYSTEM_PROMPT = """
|
|
|
You are Dr. HealBot, a calm, knowledgeable, and empathetic virtual doctor.
|
|
|
|
|
|
GOAL:
|
|
|
Hold a natural, focused conversation with the patient to understand their health issue and offer helpful preliminary medical guidance.
|
|
|
|
|
|
CONVERSATION LOGIC:
|
|
|
- Ask only relevant and concise medical questions necessary for diagnosing the illness.
|
|
|
- Each question should help clarify symptoms or narrow possible causes.
|
|
|
- Stop asking once enough information is collected for a basic assessment.
|
|
|
- Then, provide a structured, friendly, and visually clear medical response using headings, emojis, and bullet points.
|
|
|
|
|
|
FINAL RESPONSE FORMAT:
|
|
|
When giving your full assessment, use this markdown-styled format:
|
|
|
|
|
|
π©Ί Based on what you've told me...
|
|
|
Brief summary of what the patient described.
|
|
|
|
|
|
π‘ Possible Causes (Preliminary)
|
|
|
- List 1β2 possible conditions using phrases like "It could be" or "This sounds like".
|
|
|
- Include a disclaimer that this is not a confirmed diagnosis.
|
|
|
|
|
|
π Suggested Over-the-Counter Medicines
|
|
|
- Generic medicine names only (e.g., "Paracetamol 500mg every 6 hours if fever or pain")
|
|
|
- Mention to check packaging or consult a pharmacist for dosage confirmation.
|
|
|
|
|
|
π₯ Lifestyle & Home Care Tips
|
|
|
- 2β3 practical suggestions (rest, hydration, warm compress, balanced diet, etc.)
|
|
|
|
|
|
β When to See a Real Doctor
|
|
|
- 2β3 warning signs or conditions when urgent medical care is needed.
|
|
|
|
|
|
π
Follow-Up Advice
|
|
|
- Brief recommendation for self-care or follow-up timing (e.g., "If not improving in 3 days, visit a clinic.")
|
|
|
|
|
|
TONE & STYLE:
|
|
|
- Speak like a real, caring doctor β short, clear, and empathetic (1β2 sentences per reply).
|
|
|
- Use plain language, no jargon.
|
|
|
- Only one question per turn unless clarification is essential.
|
|
|
- Keep tone warm, calm, and professional.
|
|
|
- Early messages: short questions only.
|
|
|
- Final message: structured output with emojis and headings.
|
|
|
|
|
|
IMPORTANT:
|
|
|
- Always emphasize that this is preliminary guidance and not a substitute for professional care.
|
|
|
- Never make definitive diagnoses; use phrases like "it sounds like" or "it could be".
|
|
|
- If symptoms seem serious, always recommend urgent medical attention.
|
|
|
|
|
|
CONVERSATION FLOW:
|
|
|
1. Ask about the main symptom.
|
|
|
2. Ask about its duration, severity, and any triggers.
|
|
|
3. Ask about accompanying symptoms.
|
|
|
4. Ask about medical history, allergies, or medications.
|
|
|
5. Then, provide your structured assessment as described above.
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_pdf_summary(session_id: str, summary_text: str, patient_data: Dict, history: List[Dict]) -> str:
|
|
|
"""Generate a professional PDF summary of the consultation"""
|
|
|
|
|
|
pdf_filename = f"{session_id}_summary.pdf"
|
|
|
pdf_path = PDF_DIR / pdf_filename
|
|
|
|
|
|
|
|
|
doc = SimpleDocTemplate(str(pdf_path), pagesize=letter,
|
|
|
rightMargin=72, leftMargin=72,
|
|
|
topMargin=72, bottomMargin=18)
|
|
|
|
|
|
|
|
|
elements = []
|
|
|
|
|
|
|
|
|
styles = getSampleStyleSheet()
|
|
|
|
|
|
|
|
|
title_style = ParagraphStyle(
|
|
|
'CustomTitle',
|
|
|
parent=styles['Heading1'],
|
|
|
fontSize=24,
|
|
|
textColor=colors.HexColor('#667eea'),
|
|
|
spaceAfter=30,
|
|
|
alignment=TA_CENTER,
|
|
|
fontName='Helvetica-Bold'
|
|
|
)
|
|
|
|
|
|
heading_style = ParagraphStyle(
|
|
|
'CustomHeading',
|
|
|
parent=styles['Heading2'],
|
|
|
fontSize=16,
|
|
|
textColor=colors.HexColor('#667eea'),
|
|
|
spaceAfter=12,
|
|
|
spaceBefore=12,
|
|
|
fontName='Helvetica-Bold'
|
|
|
)
|
|
|
|
|
|
normal_style = ParagraphStyle(
|
|
|
'CustomNormal',
|
|
|
parent=styles['Normal'],
|
|
|
fontSize=11,
|
|
|
spaceAfter=12,
|
|
|
alignment=TA_JUSTIFY,
|
|
|
leading=14
|
|
|
)
|
|
|
|
|
|
|
|
|
elements.append(Paragraph("π©Ί AI DOCTOR CONSULTATION SUMMARY", title_style))
|
|
|
elements.append(Spacer(1, 0.3*inch))
|
|
|
|
|
|
|
|
|
elements.append(Spacer(1, 0.1*inch))
|
|
|
|
|
|
|
|
|
patient_info_data = [
|
|
|
['Patient Name:', patient_data.get('name', 'N/A')],
|
|
|
['Age:', patient_data.get('age', 'N/A')],
|
|
|
['Session ID:', session_id[:20] + '...'],
|
|
|
['Consultation Date:', datetime.now().strftime('%B %d, %Y at %I:%M %p')],
|
|
|
['Total Messages:', str(len(history))]
|
|
|
]
|
|
|
|
|
|
patient_table = Table(patient_info_data, colWidths=[2*inch, 4*inch])
|
|
|
patient_table.setStyle(TableStyle([
|
|
|
('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f0f0f0')),
|
|
|
('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
|
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
|
|
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
|
|
|
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
|
|
|
('FONTSIZE', (0, 0), (-1, -1), 10),
|
|
|
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
|
|
|
('TOPPADDING', (0, 0), (-1, -1), 8),
|
|
|
('GRID', (0, 0), (-1, -1), 1, colors.grey)
|
|
|
]))
|
|
|
|
|
|
elements.append(patient_table)
|
|
|
elements.append(Spacer(1, 0.3*inch))
|
|
|
|
|
|
|
|
|
elements.append(Paragraph("CONSULTATION SUMMARY", heading_style))
|
|
|
|
|
|
|
|
|
summary_lines = summary_text.split('\n')
|
|
|
for line in summary_lines:
|
|
|
if line.strip():
|
|
|
|
|
|
line = line.replace('π©Ί', '[Medical] ')
|
|
|
line = line.replace('π‘', '[Insight] ')
|
|
|
line = line.replace('π', '[Medicine] ')
|
|
|
line = line.replace('π₯', '[Lifestyle] ')
|
|
|
line = line.replace('β οΈ', '[Warning] ')
|
|
|
line = line.replace('β ', '[Warning] ')
|
|
|
line = line.replace('π
', '[Follow-up] ')
|
|
|
line = line.replace('β', '-')
|
|
|
|
|
|
|
|
|
if line.strip().startswith('**') and line.strip().endswith('**'):
|
|
|
elements.append(Paragraph(line.strip('*'), heading_style))
|
|
|
else:
|
|
|
elements.append(Paragraph(line, normal_style))
|
|
|
|
|
|
elements.append(Spacer(1, 0.3*inch))
|
|
|
|
|
|
|
|
|
elements.append(PageBreak())
|
|
|
elements.append(Paragraph("CONVERSATION HISTORY", heading_style))
|
|
|
elements.append(Spacer(1, 0.2*inch))
|
|
|
|
|
|
for i, msg in enumerate(history, 1):
|
|
|
role = "DOCTOR" if msg['role'] == 'assistant' else "PATIENT"
|
|
|
timestamp = msg.get('timestamp', 'N/A')
|
|
|
|
|
|
role_style = ParagraphStyle(
|
|
|
f'Role{i}',
|
|
|
parent=styles['Normal'],
|
|
|
fontSize=10,
|
|
|
textColor=colors.HexColor('#667eea') if role == "DOCTOR" else colors.HexColor('#28a745'),
|
|
|
fontName='Helvetica-Bold',
|
|
|
spaceAfter=4
|
|
|
)
|
|
|
|
|
|
elements.append(Paragraph(f"{role} ({timestamp}):", role_style))
|
|
|
|
|
|
content = msg['content'].replace('π©Ί', '').replace('π‘', '').replace('π', '')
|
|
|
content = content.replace('π₯', '').replace('β οΈ', '').replace('β ', '').replace('π
', '')
|
|
|
elements.append(Paragraph(content, normal_style))
|
|
|
elements.append(Spacer(1, 0.15*inch))
|
|
|
|
|
|
|
|
|
elements.append(Spacer(1, 0.3*inch))
|
|
|
|
|
|
disclaimer_style = ParagraphStyle(
|
|
|
'Disclaimer',
|
|
|
parent=styles['Normal'],
|
|
|
fontSize=9,
|
|
|
textColor=colors.red,
|
|
|
alignment=TA_CENTER,
|
|
|
fontName='Helvetica-Bold',
|
|
|
borderColor=colors.red,
|
|
|
borderWidth=1,
|
|
|
borderPadding=10,
|
|
|
spaceAfter=12
|
|
|
)
|
|
|
|
|
|
elements.append(Paragraph(
|
|
|
"β IMPORTANT DISCLAIMER β <br/>" +
|
|
|
"This is a preliminary AI-generated consultation for informational purposes only.<br/>" +
|
|
|
"It is NOT a substitute for professional medical advice, diagnosis, or treatment.<br/>" +
|
|
|
"Always seek the advice of a qualified healthcare provider with any questions regarding a medical condition.",
|
|
|
disclaimer_style
|
|
|
))
|
|
|
|
|
|
|
|
|
doc.build(elements)
|
|
|
|
|
|
return pdf_filename
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save_session_to_json(session_id: str, memory: 'ConversationMemory'):
|
|
|
"""Save session data to JSON file"""
|
|
|
file_path = STORAGE_DIR / f"{session_id}.json"
|
|
|
|
|
|
session_data = {
|
|
|
"session_id": session_id,
|
|
|
"created_at": memory.created_at.isoformat(),
|
|
|
"last_updated": datetime.now().isoformat(),
|
|
|
"patient_data": memory.patient_data,
|
|
|
"questions_asked": memory.questions_asked,
|
|
|
"history": memory.history,
|
|
|
"message_count": len(memory.history),
|
|
|
"pdf_filename": getattr(memory, 'pdf_filename', None)
|
|
|
}
|
|
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
|
json.dump(session_data, f, indent=2, ensure_ascii=False)
|
|
|
|
|
|
def load_session_from_json(session_id: str) -> Optional[Dict]:
|
|
|
"""Load session data from JSON file"""
|
|
|
file_path = STORAGE_DIR / f"{session_id}.json"
|
|
|
|
|
|
if not file_path.exists():
|
|
|
return None
|
|
|
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
|
return json.load(f)
|
|
|
|
|
|
def list_all_sessions() -> List[Dict]:
|
|
|
"""List all stored sessions"""
|
|
|
sessions_list = []
|
|
|
|
|
|
for file_path in STORAGE_DIR.glob("*.json"):
|
|
|
try:
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
|
data = json.load(f)
|
|
|
sessions_list.append({
|
|
|
"session_id": data["session_id"],
|
|
|
"created_at": data["created_at"],
|
|
|
"last_updated": data.get("last_updated", data["created_at"]),
|
|
|
"patient_name": data["patient_data"].get("name", "Unknown"),
|
|
|
"message_count": data["message_count"],
|
|
|
"has_pdf": data.get("pdf_filename") is not None
|
|
|
})
|
|
|
except Exception as e:
|
|
|
print(f"Error reading {file_path}: {e}")
|
|
|
|
|
|
return sorted(sessions_list, key=lambda x: x["last_updated"], reverse=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConversationMemory:
|
|
|
"""Manages short-term memory for each session"""
|
|
|
def __init__(self, max_messages: int = 20, session_id: str = None):
|
|
|
self.max_messages = max_messages
|
|
|
self.history = []
|
|
|
self.patient_data = {}
|
|
|
self.created_at = datetime.now()
|
|
|
self.questions_asked = 0
|
|
|
self.session_id = session_id
|
|
|
self.pdf_filename = None
|
|
|
|
|
|
def add_message(self, role: str, content: str):
|
|
|
"""Add message to history with memory management"""
|
|
|
self.history.append({
|
|
|
"role": role,
|
|
|
"content": content,
|
|
|
"timestamp": datetime.now().isoformat()
|
|
|
})
|
|
|
|
|
|
if role == "assistant" and "?" in content:
|
|
|
self.questions_asked += 1
|
|
|
|
|
|
if len(self.history) > self.max_messages:
|
|
|
self.history = [self.history[0]] + self.history[-(self.max_messages-1):]
|
|
|
|
|
|
if self.session_id:
|
|
|
save_session_to_json(self.session_id, self)
|
|
|
|
|
|
def extract_patient_info(self, message: str):
|
|
|
"""Extract and store patient information from conversation"""
|
|
|
message_lower = message.lower()
|
|
|
|
|
|
if any(word in message_lower for word in ["name is", "i'm", "i am", "im"]):
|
|
|
words = message.split()
|
|
|
for i, word in enumerate(words):
|
|
|
if word.lower() in ["is", "i'm", "am", "im"] and i + 1 < len(words):
|
|
|
self.patient_data["name"] = words[i + 1].strip(".,!?")
|
|
|
|
|
|
if "year" in message_lower or "age" in message_lower:
|
|
|
import re
|
|
|
age_match = re.search(r'\b(\d{1,3})\b', message)
|
|
|
if age_match:
|
|
|
self.patient_data["age"] = age_match.group(1)
|
|
|
|
|
|
if "fever" in message_lower or "pain" in message_lower or "sick" in message_lower:
|
|
|
self.patient_data["has_symptoms"] = True
|
|
|
|
|
|
def should_give_recommendations(self) -> bool:
|
|
|
"""Check if we should provide recommendations now"""
|
|
|
return (
|
|
|
self.questions_asked >= 7 or
|
|
|
self.patient_data.get("has_symptoms", False)
|
|
|
)
|
|
|
|
|
|
def get_context_summary(self) -> str:
|
|
|
"""Generate a brief context summary for the AI"""
|
|
|
summary = "\n[Session Context: "
|
|
|
if "name" in self.patient_data:
|
|
|
summary += f"Name: {self.patient_data['name']}, "
|
|
|
if "age" in self.patient_data:
|
|
|
summary += f"Age: {self.patient_data['age']}, "
|
|
|
summary += f"Questions asked: {self.questions_asked}/7, "
|
|
|
|
|
|
if self.questions_asked >= 5:
|
|
|
summary += "β οΈ IMPORTANT: You've asked enough questions. After the next 1-2 answers, IMMEDIATELY provide comprehensive medical recommendations.]"
|
|
|
elif self.questions_asked >= 7:
|
|
|
summary += "β οΈ CRITICAL: You MUST provide comprehensive medical recommendations NOW. Do not ask more questions!]"
|
|
|
else:
|
|
|
summary += f"Ask {7 - self.questions_asked} more essential questions then give recommendations.]"
|
|
|
|
|
|
return summary
|
|
|
|
|
|
def get_gemini_history(self) -> List[Dict]:
|
|
|
"""Convert history to Gemini format"""
|
|
|
gemini_history = []
|
|
|
for msg in self.history:
|
|
|
gemini_history.append({
|
|
|
"role": "user" if msg["role"] == "user" else "model",
|
|
|
"parts": [msg["content"]]
|
|
|
})
|
|
|
return gemini_history
|
|
|
|
|
|
@classmethod
|
|
|
def from_json(cls, session_data: Dict) -> 'ConversationMemory':
|
|
|
"""Create ConversationMemory from JSON data"""
|
|
|
memory = cls(session_id=session_data["session_id"])
|
|
|
memory.history = session_data["history"]
|
|
|
memory.patient_data = session_data["patient_data"]
|
|
|
memory.questions_asked = session_data["questions_asked"]
|
|
|
memory.created_at = datetime.fromisoformat(session_data["created_at"])
|
|
|
memory.pdf_filename = session_data.get("pdf_filename")
|
|
|
return memory
|
|
|
|
|
|
sessions: Dict[str, ConversationMemory] = {}
|
|
|
|
|
|
def cleanup_old_sessions():
|
|
|
"""Remove sessions older than 1 hour from memory"""
|
|
|
current_time = datetime.now()
|
|
|
expired_sessions = []
|
|
|
|
|
|
for session_id, memory in sessions.items():
|
|
|
age = (current_time - memory.created_at).total_seconds()
|
|
|
if age > 3600:
|
|
|
expired_sessions.append(session_id)
|
|
|
|
|
|
for session_id in expired_sessions:
|
|
|
del sessions[session_id]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = FastAPI(
|
|
|
title="AI Doctor Consultation API with PDF Generation",
|
|
|
description="Professional medical consultation API with PDF summary generation",
|
|
|
version="3.0.0"
|
|
|
)
|
|
|
|
|
|
app.add_middleware(
|
|
|
CORSMiddleware,
|
|
|
allow_origins=["*"],
|
|
|
allow_credentials=True,
|
|
|
allow_methods=["*"],
|
|
|
allow_headers=["*"],
|
|
|
)
|
|
|
|
|
|
|
|
|
class ChatRequest(BaseModel):
|
|
|
session_id: Optional[str] = None
|
|
|
message: str
|
|
|
|
|
|
class ChatResponse(BaseModel):
|
|
|
session_id: str
|
|
|
response: str
|
|
|
timestamp: str
|
|
|
patient_data: Dict
|
|
|
|
|
|
class SessionRequest(BaseModel):
|
|
|
session_id: str
|
|
|
|
|
|
class SummaryResponse(BaseModel):
|
|
|
summary: str
|
|
|
session_id: str
|
|
|
pdf_filename: str
|
|
|
pdf_url: str
|
|
|
|
|
|
class HealthCheck(BaseModel):
|
|
|
status: str
|
|
|
timestamp: str
|
|
|
active_sessions: int
|
|
|
stored_sessions: int
|
|
|
stored_pdfs: int
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/", response_model=HealthCheck)
|
|
|
async def root():
|
|
|
"""Health check endpoint"""
|
|
|
cleanup_old_sessions()
|
|
|
stored_count = len(list(STORAGE_DIR.glob("*.json")))
|
|
|
pdf_count = len(list(PDF_DIR.glob("*.pdf")))
|
|
|
return {
|
|
|
"status": "healthy",
|
|
|
"timestamp": datetime.now().isoformat(),
|
|
|
"active_sessions": len(sessions),
|
|
|
"stored_sessions": stored_count,
|
|
|
"stored_pdfs": pdf_count
|
|
|
}
|
|
|
|
|
|
@app.post("/start-session")
|
|
|
async def start_session():
|
|
|
"""Start a new consultation session"""
|
|
|
session_id = str(uuid.uuid4())
|
|
|
sessions[session_id] = ConversationMemory(max_messages=20, session_id=session_id)
|
|
|
|
|
|
initial_message = "Hello! I'm Dr. AI Assistant. I'm here to help you today.\n\nπ€ May I have your name, please?"
|
|
|
|
|
|
sessions[session_id].add_message("assistant", initial_message)
|
|
|
|
|
|
return {
|
|
|
"session_id": session_id,
|
|
|
"message": initial_message,
|
|
|
"timestamp": datetime.now().isoformat()
|
|
|
}
|
|
|
|
|
|
@app.post("/chat", response_model=ChatResponse)
|
|
|
async def chat(request: ChatRequest):
|
|
|
"""Send a message and get doctor's response"""
|
|
|
try:
|
|
|
if not request.session_id or request.session_id not in sessions:
|
|
|
session_id = str(uuid.uuid4())
|
|
|
sessions[session_id] = ConversationMemory(max_messages=20, session_id=session_id)
|
|
|
else:
|
|
|
session_id = request.session_id
|
|
|
|
|
|
memory = sessions[session_id]
|
|
|
memory.extract_patient_info(request.message)
|
|
|
memory.add_message("user", request.message)
|
|
|
|
|
|
context = memory.get_context_summary()
|
|
|
system_prompt = DOCTOR_SYSTEM_PROMPT + context
|
|
|
|
|
|
model = genai.GenerativeModel(
|
|
|
model_name=MODEL_ID,
|
|
|
system_instruction=system_prompt
|
|
|
)
|
|
|
|
|
|
chat = model.start_chat(history=memory.get_gemini_history()[:-1])
|
|
|
response = chat.send_message(request.message)
|
|
|
doctor_response = response.text
|
|
|
|
|
|
memory.add_message("assistant", doctor_response)
|
|
|
|
|
|
return {
|
|
|
"session_id": session_id,
|
|
|
"response": doctor_response,
|
|
|
"timestamp": datetime.now().isoformat(),
|
|
|
"patient_data": memory.patient_data
|
|
|
}
|
|
|
|
|
|
except Exception as e:
|
|
|
raise HTTPException(status_code=500, detail=f"Error: {str(e)}")
|
|
|
|
|
|
@app.post("/summary", response_model=SummaryResponse)
|
|
|
async def generate_summary(request: SessionRequest):
|
|
|
"""Generate consultation summary and PDF"""
|
|
|
if request.session_id not in sessions:
|
|
|
session_data = load_session_from_json(request.session_id)
|
|
|
if not session_data:
|
|
|
raise HTTPException(status_code=404, detail="Session not found")
|
|
|
memory = ConversationMemory.from_json(session_data)
|
|
|
sessions[request.session_id] = memory
|
|
|
else:
|
|
|
memory = sessions[request.session_id]
|
|
|
|
|
|
summary_request = """Please generate a COMPREHENSIVE and DETAILED medical consultation summary based on our entire conversation. Make it thorough and professional:
|
|
|
|
|
|
π **COMPREHENSIVE MEDICAL CONSULTATION SUMMARY**
|
|
|
βββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
**PATIENT INFORMATION:**
|
|
|
- Full Name: [Patient's name]
|
|
|
- Age: [Patient's age if mentioned]
|
|
|
- Gender: [If mentioned]
|
|
|
- Consultation Date: [Current date and time]
|
|
|
- Session Duration: [Approximate]
|
|
|
- Current Medications: [List all mentioned]
|
|
|
- Known Allergies: [If mentioned]
|
|
|
|
|
|
**CHIEF COMPLAINTS & SYMPTOMS:**
|
|
|
[Provide a detailed description of ALL symptoms mentioned, including:]
|
|
|
- Primary symptom and severity
|
|
|
- Duration of each symptom
|
|
|
- Onset and progression
|
|
|
- Associated symptoms
|
|
|
- Aggravating and relieving factors
|
|
|
- Impact on daily activities
|
|
|
|
|
|
**DETAILED MEDICAL HISTORY:**
|
|
|
[Include everything discussed:]
|
|
|
- Current medications and dosages
|
|
|
- Past medical conditions
|
|
|
- Recent illnesses or infections
|
|
|
- Family medical history (if mentioned)
|
|
|
- Lifestyle factors (sleep, stress, diet)
|
|
|
- Recent travel or exposures
|
|
|
|
|
|
**CLINICAL ASSESSMENT:**
|
|
|
[Provide detailed analysis:]
|
|
|
- Most likely diagnosis with explanation
|
|
|
- Differential diagnoses (2-3 possibilities)
|
|
|
- Reasoning behind each possibility
|
|
|
- Risk factors present
|
|
|
- Severity assessment
|
|
|
|
|
|
**COMPREHENSIVE TREATMENT PLAN:**
|
|
|
|
|
|
1. **IMMEDIATE CARE RECOMMENDATIONS:**
|
|
|
- What to do in the next 24-48 hours
|
|
|
- Symptom management strategies
|
|
|
- Warning signs to watch for
|
|
|
|
|
|
2. **MEDICATION RECOMMENDATIONS:**
|
|
|
- Primary medications (generic names, dosages, frequency, duration)
|
|
|
- Alternative options if first choice unavailable
|
|
|
- Potential side effects to monitor
|
|
|
- Drug interactions to avoid
|
|
|
- When to take each medication (with/without food)
|
|
|
- Important: Check with pharmacist for exact dosing
|
|
|
|
|
|
3. **DETAILED DIETARY RECOMMENDATIONS:**
|
|
|
- Foods to eat (specific examples and portions)
|
|
|
- Foods to avoid completely
|
|
|
- Meal timing and frequency
|
|
|
- Hydration guidelines (specific amounts)
|
|
|
- Nutritional supplements if needed
|
|
|
- Sample meal plan for recovery
|
|
|
|
|
|
4. **LIFESTYLE MODIFICATIONS:**
|
|
|
- Sleep recommendations (hours, timing, environment)
|
|
|
- Rest and activity balance
|
|
|
- Stress management techniques
|
|
|
- Environmental modifications
|
|
|
- Work/school attendance guidance
|
|
|
- Specific activities to avoid
|
|
|
|
|
|
5. **HOME CARE REMEDIES:**
|
|
|
- Natural remedies that may help
|
|
|
- Temperature management techniques
|
|
|
- Pain relief methods
|
|
|
- Steam inhalation or other therapies
|
|
|
- Specific home treatments for symptoms
|
|
|
|
|
|
6. **EXERCISE & PHYSICAL ACTIVITY:**
|
|
|
- Current activity restrictions
|
|
|
- Safe exercises during recovery
|
|
|
- When to resume normal activities
|
|
|
- Gradual activity progression plan
|
|
|
- Post-recovery exercise recommendations
|
|
|
|
|
|
7. **PREVENTIVE MEASURES:**
|
|
|
- How to prevent recurrence
|
|
|
- Hygiene practices
|
|
|
- Vaccination recommendations
|
|
|
- Family/household precautions
|
|
|
- Long-term health maintenance
|
|
|
|
|
|
8. **MONITORING PLAN:**
|
|
|
- Symptoms to track daily
|
|
|
- How to measure improvement
|
|
|
- When improvement should be expected
|
|
|
- What to document for doctor visit
|
|
|
|
|
|
**CRITICAL WARNING SIGNS - SEEK IMMEDIATE MEDICAL ATTENTION IF:**
|
|
|
[List 5-7 specific warning signs that require emergency care:]
|
|
|
- [Specific symptom with threshold]
|
|
|
- [Specific symptom with threshold]
|
|
|
- [Continue with detailed warnings]
|
|
|
|
|
|
**FOLLOW-UP CARE PLAN:**
|
|
|
- Timeline for self-care (e.g., "Monitor for 48 hours")
|
|
|
- When to schedule doctor appointment (specific timeframe)
|
|
|
- What information to bring to doctor
|
|
|
- Specialist referral recommendations if needed
|
|
|
- Follow-up testing that may be needed
|
|
|
|
|
|
**PROGNOSIS & EXPECTED RECOVERY:**
|
|
|
- Expected recovery timeline
|
|
|
- What to expect during recovery process
|
|
|
- Signs of improvement to look for
|
|
|
- Long-term outlook
|
|
|
|
|
|
**ADDITIONAL RESOURCES:**
|
|
|
- Reputable health information sources
|
|
|
- Support resources if applicable
|
|
|
- Emergency contact information reminder
|
|
|
|
|
|
**PATIENT EDUCATION:**
|
|
|
- Understanding your condition
|
|
|
- How the body fights this illness
|
|
|
- Why specific recommendations are important
|
|
|
- Common misconceptions about this condition
|
|
|
|
|
|
βββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
β οΈ **CRITICAL DISCLAIMER** β οΈ
|
|
|
This is a preliminary AI-generated consultation for informational and educational purposes ONLY.
|
|
|
This is NOT a substitute for professional medical advice, diagnosis, or treatment.
|
|
|
This AI cannot examine you physically, run laboratory tests, or make definitive diagnoses.
|
|
|
ALWAYS seek the advice of a qualified, licensed healthcare provider with any questions regarding a medical condition.
|
|
|
Never disregard professional medical advice or delay seeking it because of this AI consultation.
|
|
|
In case of emergency, call your local emergency services immediately.
|
|
|
βββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
Please make this summary as detailed, professional, and helpful as possible. Include specific, actionable advice."""
|
|
|
|
|
|
try:
|
|
|
model = genai.GenerativeModel(
|
|
|
model_name=MODEL_ID,
|
|
|
system_instruction=DOCTOR_SYSTEM_PROMPT
|
|
|
)
|
|
|
|
|
|
chat = model.start_chat(history=memory.get_gemini_history())
|
|
|
response = chat.send_message(summary_request)
|
|
|
summary_text = response.text
|
|
|
|
|
|
|
|
|
pdf_filename = generate_pdf_summary(
|
|
|
request.session_id,
|
|
|
summary_text,
|
|
|
memory.patient_data,
|
|
|
memory.history
|
|
|
)
|
|
|
|
|
|
|
|
|
memory.pdf_filename = pdf_filename
|
|
|
save_session_to_json(request.session_id, memory)
|
|
|
|
|
|
return {
|
|
|
"summary": summary_text,
|
|
|
"session_id": request.session_id,
|
|
|
"pdf_filename": pdf_filename,
|
|
|
"pdf_url": f"/download-pdf/{request.session_id}"
|
|
|
}
|
|
|
|
|
|
except Exception as e:
|
|
|
raise HTTPException(status_code=500, detail=f"Error generating summary: {str(e)}")
|
|
|
|
|
|
@app.get("/download-pdf/{session_id}")
|
|
|
async def download_pdf(session_id: str):
|
|
|
"""Download PDF summary for a session"""
|
|
|
|
|
|
if session_id in sessions:
|
|
|
memory = sessions[session_id]
|
|
|
else:
|
|
|
session_data = load_session_from_json(session_id)
|
|
|
if not session_data:
|
|
|
raise HTTPException(status_code=404, detail="Session not found")
|
|
|
memory = ConversationMemory.from_json(session_data)
|
|
|
|
|
|
if not memory.pdf_filename:
|
|
|
raise HTTPException(status_code=404, detail="PDF not generated yet. Please generate summary first.")
|
|
|
|
|
|
pdf_path = PDF_DIR / memory.pdf_filename
|
|
|
|
|
|
if not pdf_path.exists():
|
|
|
raise HTTPException(status_code=404, detail="PDF file not found")
|
|
|
|
|
|
patient_name = memory.patient_data.get('name', 'Patient')
|
|
|
download_filename = f"Consultation_Summary_{patient_name}_{datetime.now().strftime('%Y%m%d')}.pdf"
|
|
|
|
|
|
return FileResponse(
|
|
|
path=str(pdf_path),
|
|
|
media_type='application/pdf',
|
|
|
filename=download_filename
|
|
|
)
|
|
|
|
|
|
@app.get("/load-session/{session_id}")
|
|
|
async def load_session(session_id: str):
|
|
|
"""Load a previous consultation session by ID"""
|
|
|
if session_id in sessions:
|
|
|
memory = sessions[session_id]
|
|
|
return {
|
|
|
"session_id": session_id,
|
|
|
"loaded": True,
|
|
|
"from_cache": True,
|
|
|
"history": memory.history,
|
|
|
"patient_data": memory.patient_data,
|
|
|
"created_at": memory.created_at.isoformat(),
|
|
|
"questions_asked": memory.questions_asked,
|
|
|
"has_pdf": memory.pdf_filename is not None,
|
|
|
"pdf_url": f"/download-pdf/{session_id}" if memory.pdf_filename else None
|
|
|
}
|
|
|
|
|
|
session_data = load_session_from_json(session_id)
|
|
|
|
|
|
if not session_data:
|
|
|
raise HTTPException(status_code=404, detail=f"Session {session_id} not found")
|
|
|
|
|
|
memory = ConversationMemory.from_json(session_data)
|
|
|
sessions[session_id] = memory
|
|
|
|
|
|
return {
|
|
|
"session_id": session_id,
|
|
|
"loaded": True,
|
|
|
"from_cache": False,
|
|
|
"history": memory.history,
|
|
|
"patient_data": memory.patient_data,
|
|
|
"created_at": memory.created_at.isoformat(),
|
|
|
"questions_asked": memory.questions_asked,
|
|
|
"has_pdf": memory.pdf_filename is not None,
|
|
|
"pdf_url": f"/download-pdf/{session_id}" if memory.pdf_filename else None,
|
|
|
"message": "Session loaded successfully. You can continue the conversation."
|
|
|
}
|
|
|
|
|
|
@app.get("/all-sessions")
|
|
|
async def get_all_sessions():
|
|
|
"""Get list of all stored consultation sessions"""
|
|
|
return {
|
|
|
"total_sessions": len(list(STORAGE_DIR.glob("*.json"))),
|
|
|
"sessions": list_all_sessions()
|
|
|
}
|
|
|
|
|
|
@app.post("/restart-session")
|
|
|
async def restart_session(request: SessionRequest):
|
|
|
"""Restart a consultation session"""
|
|
|
if request.session_id in sessions:
|
|
|
del sessions[request.session_id]
|
|
|
|
|
|
sessions[request.session_id] = ConversationMemory(max_messages=20, session_id=request.session_id)
|
|
|
|
|
|
initial_message = "Consultation restarted. Hello! I'm Dr. AI Assistant. May I have your name please?"
|
|
|
sessions[request.session_id].add_message("assistant", initial_message)
|
|
|
|
|
|
return {
|
|
|
"session_id": request.session_id,
|
|
|
"message": initial_message,
|
|
|
"timestamp": datetime.now().isoformat()
|
|
|
}
|
|
|
|
|
|
@app.delete("/session/{session_id}")
|
|
|
async def delete_session(session_id: str):
|
|
|
"""Delete a consultation session (from memory, JSON, and PDF)"""
|
|
|
if session_id in sessions:
|
|
|
memory = sessions[session_id]
|
|
|
pdf_filename = memory.pdf_filename
|
|
|
del sessions[session_id]
|
|
|
else:
|
|
|
session_data = load_session_from_json(session_id)
|
|
|
pdf_filename = session_data.get('pdf_filename') if session_data else None
|
|
|
|
|
|
|
|
|
file_path = STORAGE_DIR / f"{session_id}.json"
|
|
|
if file_path.exists():
|
|
|
file_path.unlink()
|
|
|
|
|
|
|
|
|
if pdf_filename:
|
|
|
pdf_path = PDF_DIR / pdf_filename
|
|
|
if pdf_path.exists():
|
|
|
pdf_path.unlink()
|
|
|
|
|
|
return {"message": "Session and associated files deleted successfully"}
|
|
|
|
|
|
@app.get("/session/{session_id}/history")
|
|
|
async def get_session_history(session_id: str):
|
|
|
"""Get conversation history for a session"""
|
|
|
if session_id in sessions:
|
|
|
memory = sessions[session_id]
|
|
|
else:
|
|
|
session_data = load_session_from_json(session_id)
|
|
|
if not session_data:
|
|
|
raise HTTPException(status_code=404, detail="Session not found")
|
|
|
memory = ConversationMemory.from_json(session_data)
|
|
|
|
|
|
return {
|
|
|
"session_id": session_id,
|
|
|
"history": memory.history,
|
|
|
"patient_data": memory.patient_data,
|
|
|
"created_at": memory.created_at.isoformat(),
|
|
|
"questions_asked": memory.questions_asked,
|
|
|
"has_pdf": memory.pdf_filename is not None
|
|
|
}
|
|
|
|
|
|
@app.get("/active-sessions")
|
|
|
async def get_active_sessions():
|
|
|
"""Get list of all active sessions in memory"""
|
|
|
cleanup_old_sessions()
|
|
|
return {
|
|
|
"active_sessions": len(sessions),
|
|
|
"sessions": [
|
|
|
{
|
|
|
"session_id": sid,
|
|
|
"created_at": mem.created_at.isoformat(),
|
|
|
"message_count": len(mem.history),
|
|
|
"questions_asked": mem.questions_asked,
|
|
|
"patient_data": mem.patient_data,
|
|
|
"has_pdf": mem.pdf_filename is not None
|
|
|
}
|
|
|
for sid, mem in sessions.items()
|
|
|
]
|
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
import uvicorn
|
|
|
uvicorn.run(app, host="0.0.0.0", port=8000) |