SuriRaja commited on
Commit
1200210
·
verified ·
1 Parent(s): 878708a

Upload 6 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Copy requirements and install dependencies
6
+ COPY hf_requirements.txt requirements.txt
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ # Copy application files
10
+ COPY complete_medical_dashboard.py .
11
+ COPY app.py .
12
+
13
+ # Create necessary directories
14
+ RUN mkdir -p uploads
15
+
16
+ # Set environment variables
17
+ ENV FLASK_APP=app.py
18
+ ENV FLASK_ENV=production
19
+ ENV PORT=7860
20
+
21
+ # Expose port
22
+ EXPOSE 7860
23
+
24
+ # Run the application
25
+ CMD ["python", "app.py"]
HUGGING_FACE_DEPLOYMENT.md ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces Deployment Guide
2
+
3
+ ## Quick Deployment Steps
4
+
5
+ ### 1. Create New Space
6
+ - Go to [Hugging Face Spaces](https://huggingface.co/spaces)
7
+ - Click "Create new Space"
8
+ - Choose:
9
+ - **SDK**: Docker
10
+ - **Hardware**: CPU (basic tier)
11
+ - **Visibility**: Public or Private
12
+
13
+ ### 2. Upload Files
14
+ Upload these essential files to your Space:
15
+
16
+ ```
17
+ ├── README.md (Space header configuration)
18
+ ├── Dockerfile (Container setup)
19
+ ├── hf_requirements.txt (Python dependencies)
20
+ ├── app.py (Hugging Face entry point)
21
+ └── complete_medical_dashboard.py (Main application)
22
+ ```
23
+
24
+ ### 3. Environment Variables
25
+ In your Space settings, add:
26
+
27
+ ```
28
+ SESSION_SECRET=your-secure-random-string-here
29
+ DATABASE_URL=sqlite:///medical_dashboard.db
30
+ PORT=7860
31
+ ```
32
+
33
+ ### 4. Build & Deploy
34
+ - Hugging Face will automatically build and deploy your app
35
+ - Access at: `https://huggingface.co/spaces/YOUR_USERNAME/SPACE_NAME`
36
+
37
+ ## File Details
38
+
39
+ ### README.md
40
+ Contains Space metadata and description
41
+
42
+ ### Dockerfile
43
+ Handles Python environment and application startup
44
+
45
+ ### app.py
46
+ Entry point that imports and runs the main application
47
+
48
+ ### complete_medical_dashboard.py
49
+ Complete single-file Flask application with all features
50
+
51
+ ## Default Login
52
+ - Username: `admin`
53
+ - Password: `admin123`
54
+
55
+ ## Features Available
56
+ - Patient management (CRUD operations)
57
+ - Treatment planning with AI recommendations
58
+ - Drug interaction checking
59
+ - Dashboard analytics
60
+ - Medical assistant with symptom analysis
61
+
62
+ ## Database
63
+ Uses SQLite for maximum compatibility. Data persists between deployments.
64
+
65
+ ## Support
66
+ The application is self-contained with no external API dependencies, ensuring reliable operation on Hugging Face Spaces.
README.md CHANGED
@@ -1,13 +1,36 @@
1
  ---
2
- title: Fsdf
3
- emoji: 👀
4
- colorFrom: gray
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 5.33.0
8
- app_file: app.py
9
  pinned: false
10
  license: mit
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Doctor's Sidekick
3
+ emoji: 🩺
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
 
 
7
  pinned: false
8
  license: mit
9
  ---
10
 
11
+ # Doctor's Sidekick - Medical Dashboard
12
+
13
+ A comprehensive medical assistant platform for dermatology practice management and patient care.
14
+
15
+ ## Features
16
+
17
+ - **Patient Management**: Complete CRUD operations for patient records
18
+ - **Treatment Planning**: AI-powered treatment recommendations and drug interaction checking
19
+ - **Dashboard Analytics**: Visual insights into practice statistics
20
+ - **Medical AI Assistant**: Pattern-based symptom analysis and diagnostic support
21
+ - **User Authentication**: Secure login system with role-based access
22
+
23
+ ## Default Login
24
+
25
+ - Username: `admin`
26
+ - Password: `admin123`
27
+
28
+ ## Technology Stack
29
+
30
+ - Flask 2.3.3
31
+ - SQLAlchemy (SQLite database)
32
+ - Bootstrap 5 UI
33
+ - Pattern-based AI (no external APIs required)
34
+ - Self-contained templates
35
+
36
+ Check it out at https://huggingface.co/spaces/YOUR_USERNAME/doctors-sidekick
app.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Hugging Face compatible entry point
2
+ from complete_medical_dashboard import app
3
+
4
+ if __name__ == "__main__":
5
+ import os
6
+ port = int(os.environ.get("PORT", 7860)) # Hugging Face uses port 7860
7
+ app.run(host="0.0.0.0", port=port, debug=False)
complete_medical_dashboard.py ADDED
@@ -0,0 +1,1800 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Doctor's Sidekick - Complete Medical Dashboard
4
+ A comprehensive Flask-based medical assistant dashboard for dermatologists
5
+ with patient management, treatment planning, and AI-powered recommendations.
6
+
7
+ Single file implementation for easy deployment and GitHub sharing.
8
+ """
9
+
10
+ import os
11
+ import json
12
+ import logging
13
+ from datetime import datetime, date, timedelta
14
+ from typing import Dict, List, Optional
15
+ from werkzeug.security import check_password_hash, generate_password_hash
16
+ from werkzeug.utils import secure_filename
17
+ from werkzeug.middleware.proxy_fix import ProxyFix
18
+
19
+ # Flask and extensions
20
+ from flask import Flask, render_template_string, request, redirect, url_for, flash, jsonify, send_from_directory
21
+ from flask_sqlalchemy import SQLAlchemy
22
+ from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
23
+ from flask_wtf import FlaskForm
24
+ from flask_wtf.file import FileField, FileAllowed
25
+ from wtforms import StringField, PasswordField, TextAreaField, DateField, SelectField, IntegerField, BooleanField
26
+ from wtforms.validators import DataRequired, Length, Email, Optional, NumberRange
27
+ from sqlalchemy.orm import DeclarativeBase
28
+
29
+ # Configure logging
30
+ logging.basicConfig(level=logging.DEBUG)
31
+
32
+ # ============================================================================
33
+ # DATABASE MODELS
34
+ # ============================================================================
35
+
36
+ class Base(DeclarativeBase):
37
+ pass
38
+
39
+ db = SQLAlchemy(model_class=Base)
40
+
41
+ class Role(db.Model):
42
+ __tablename__ = 'role'
43
+ id = db.Column(db.Integer, primary_key=True)
44
+ name = db.Column(db.String(64), unique=True, nullable=False)
45
+ description = db.Column(db.Text)
46
+ users = db.relationship('User', backref='role', lazy='dynamic')
47
+
48
+ class User(UserMixin, db.Model):
49
+ __tablename__ = 'user'
50
+ id = db.Column(db.Integer, primary_key=True)
51
+ username = db.Column(db.String(64), unique=True, nullable=False)
52
+ email = db.Column(db.String(120), unique=True, nullable=False)
53
+ password_hash = db.Column(db.String(256))
54
+ first_name = db.Column(db.String(64), nullable=False)
55
+ last_name = db.Column(db.String(64), nullable=False)
56
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
57
+ is_active = db.Column(db.Boolean, default=True)
58
+ role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
59
+
60
+ def full_name(self):
61
+ return f"{self.first_name} {self.last_name}"
62
+
63
+ class Patient(db.Model):
64
+ __tablename__ = 'patient'
65
+ id = db.Column(db.Integer, primary_key=True)
66
+ patient_id = db.Column(db.String(20), unique=True, nullable=False)
67
+ first_name = db.Column(db.String(64), nullable=False)
68
+ last_name = db.Column(db.String(64), nullable=False)
69
+ date_of_birth = db.Column(db.Date, nullable=False)
70
+ gender = db.Column(db.String(10))
71
+ phone = db.Column(db.String(20))
72
+ email = db.Column(db.String(120))
73
+ address = db.Column(db.Text)
74
+ emergency_contact = db.Column(db.String(100))
75
+ emergency_phone = db.Column(db.String(20))
76
+ allergies = db.Column(db.Text)
77
+ medical_history = db.Column(db.Text)
78
+ insurance_info = db.Column(db.Text)
79
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
80
+ last_visit = db.Column(db.DateTime)
81
+ doctor_id = db.Column(db.Integer, db.ForeignKey('user.id'))
82
+
83
+ def full_name(self):
84
+ return f"{self.first_name} {self.last_name}"
85
+
86
+ def age(self):
87
+ return (date.today() - self.date_of_birth).days // 365
88
+
89
+ class Visit(db.Model):
90
+ __tablename__ = 'visit'
91
+ id = db.Column(db.Integer, primary_key=True)
92
+ visit_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
93
+ visit_type = db.Column(db.String(50))
94
+ chief_complaint = db.Column(db.Text)
95
+ diagnosis = db.Column(db.Text)
96
+ treatment_notes = db.Column(db.Text)
97
+ follow_up_date = db.Column(db.Date)
98
+ patient_id = db.Column(db.Integer, db.ForeignKey('patient.id'), nullable=False)
99
+
100
+ class Condition(db.Model):
101
+ __tablename__ = 'condition'
102
+ id = db.Column(db.Integer, primary_key=True)
103
+ name = db.Column(db.String(100), nullable=False)
104
+ description = db.Column(db.Text)
105
+ icd_code = db.Column(db.String(20))
106
+ category = db.Column(db.String(50))
107
+
108
+ class TreatmentTemplate(db.Model):
109
+ __tablename__ = 'treatment_template'
110
+ id = db.Column(db.Integer, primary_key=True)
111
+ name = db.Column(db.String(100), nullable=False)
112
+ description = db.Column(db.Text)
113
+ medications = db.Column(db.Text)
114
+ instructions = db.Column(db.Text)
115
+ duration = db.Column(db.String(50))
116
+ follow_up_weeks = db.Column(db.Integer)
117
+ is_active = db.Column(db.Boolean, default=True)
118
+ condition_id = db.Column(db.Integer, db.ForeignKey('condition.id'))
119
+
120
+ class TreatmentPlan(db.Model):
121
+ __tablename__ = 'treatment_plan'
122
+ id = db.Column(db.Integer, primary_key=True)
123
+ diagnosis = db.Column(db.String(200), nullable=False)
124
+ medications = db.Column(db.Text)
125
+ instructions = db.Column(db.Text)
126
+ duration = db.Column(db.String(50))
127
+ follow_up_date = db.Column(db.Date)
128
+ notes = db.Column(db.Text)
129
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
130
+ patient_id = db.Column(db.Integer, db.ForeignKey('patient.id'), nullable=False)
131
+ created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
132
+
133
+ # ============================================================================
134
+ # AI MEDICAL ASSISTANT
135
+ # ============================================================================
136
+
137
+ class MedicalAI:
138
+ """AI-powered medical assistant for dermatology practice"""
139
+
140
+ def __init__(self):
141
+ self.dermatology_context = """
142
+ Expert dermatology AI assistant providing evidence-based recommendations
143
+ following current clinical guidelines with safety warnings.
144
+ """
145
+
146
+ def _analyze_symptoms(self, symptoms: str, patient_data: Dict) -> Dict:
147
+ """Analyze symptoms and return appropriate medical recommendations"""
148
+ symptoms_lower = symptoms.lower()
149
+ age = patient_data.get('age', 30)
150
+
151
+ # Dermatitis/Eczema conditions
152
+ if any(keyword in symptoms_lower for keyword in ['rash', 'eczema', 'dermatitis', 'itchy', 'red skin']):
153
+ return {
154
+ "condition_type": "dermatitis",
155
+ "primary_diagnosis": "Contact Dermatitis",
156
+ "confidence": 80,
157
+ "medications": "Topical corticosteroids: Hydrocortisone 1% cream BID x 7-14 days\nOral antihistamines: Cetirizine 10mg daily PRN pruritus\nEmollients: Apply liberally QID",
158
+ "instructions": "Apply medications to affected areas only\nAvoid known triggers\nUse fragrance-free products\nMoisturize regularly",
159
+ "follow_up": "Return in 2 weeks if no improvement\nSooner if worsening or signs of infection"
160
+ }
161
+
162
+ # Acne conditions
163
+ elif any(keyword in symptoms_lower for keyword in ['acne', 'pimples', 'blackheads', 'whiteheads']):
164
+ return {
165
+ "condition_type": "acne",
166
+ "primary_diagnosis": "Acne Vulgaris",
167
+ "confidence": 85,
168
+ "medications": "Topical retinoid: Adapalene 0.1% gel daily at bedtime\nBenzoyl peroxide 2.5% gel BID\nClindamycin 1% solution BID (if inflammatory)",
169
+ "instructions": "Start slowly with retinoid (every other night)\nUse non-comedogenic products\nGentle cleansing twice daily\nSun protection mandatory",
170
+ "follow_up": "4-6 weeks for initial assessment\n3 months for full evaluation"
171
+ }
172
+
173
+ # Psoriasis
174
+ elif any(keyword in symptoms_lower for keyword in ['psoriasis', 'silvery scales', 'plaques']):
175
+ return {
176
+ "condition_type": "psoriasis",
177
+ "primary_diagnosis": "Psoriasis Vulgaris",
178
+ "confidence": 75,
179
+ "medications": "Topical corticosteroids: Clobetasol 0.05% ointment BID x 2 weeks\nVitamin D analogues: Calcipotriene 0.005% ointment BID\nMoisturizers: Apply liberally QID",
180
+ "instructions": "Limit potent steroids to 2 weeks\nAlternate with vitamin D analogues\nAvoid triggers (stress, trauma, infections)\nRegular moisturizing essential",
181
+ "follow_up": "2-4 weeks for response assessment\nDermatology referral if extensive"
182
+ }
183
+
184
+ # Default/general skin condition
185
+ else:
186
+ return {
187
+ "condition_type": "general",
188
+ "primary_diagnosis": "Non-specific dermatitis",
189
+ "confidence": 60,
190
+ "medications": "Gentle moisturizers: Apply QID\nMild topical corticosteroids if inflamed\nOral antihistamines PRN pruritus",
191
+ "instructions": "Avoid harsh soaps and chemicals\nUse fragrance-free products\nMoisturize regularly\nIdentify and avoid triggers",
192
+ "follow_up": "1-2 weeks if symptoms persist\nDermatology referral if no improvement"
193
+ }
194
+
195
+ def generate_treatment_plan(self, patient_data: Dict, symptoms: str, diagnosis: str = None) -> Dict:
196
+ """Generate AI-powered treatment plan based on patient data and symptoms"""
197
+
198
+ analysis = self._analyze_symptoms(symptoms, patient_data)
199
+
200
+ # Build differential diagnosis
201
+ if analysis["condition_type"] == "dermatitis":
202
+ differential_diagnosis = [
203
+ {
204
+ "condition": "Contact Dermatitis",
205
+ "confidence": 80,
206
+ "description": "Inflammatory reaction to allergens or irritants",
207
+ "key_features": ["Well-demarcated erythema", "Vesicles", "Pruritus"]
208
+ },
209
+ {
210
+ "condition": "Atopic Dermatitis",
211
+ "confidence": 65,
212
+ "description": "Chronic inflammatory skin condition",
213
+ "key_features": ["Dry skin", "Lichenification", "Flexural involvement"]
214
+ }
215
+ ]
216
+ elif analysis["condition_type"] == "acne":
217
+ differential_diagnosis = [
218
+ {
219
+ "condition": "Acne Vulgaris",
220
+ "confidence": 85,
221
+ "description": "Common inflammatory condition of pilosebaceous units",
222
+ "key_features": ["Comedones", "Papules", "Pustules", "Face/chest involvement"]
223
+ }
224
+ ]
225
+ else:
226
+ differential_diagnosis = [
227
+ {
228
+ "condition": analysis["primary_diagnosis"],
229
+ "confidence": analysis["confidence"],
230
+ "description": "Clinical assessment required for definitive diagnosis",
231
+ "key_features": ["Variable presentation", "Patient history important"]
232
+ }
233
+ ]
234
+
235
+ return {
236
+ "differential_diagnosis": differential_diagnosis,
237
+ "medications": analysis["medications"],
238
+ "instructions": analysis["instructions"],
239
+ "follow_up": analysis["follow_up"],
240
+ "warnings": [
241
+ "Monitor for signs of secondary infection",
242
+ "Discontinue treatment if allergic reactions occur",
243
+ "Follow up if symptoms worsen or persist"
244
+ ]
245
+ }
246
+
247
+ def check_drug_interactions(self, medications: List[str], patient_allergies: str = "") -> Dict:
248
+ """Check for drug interactions and contraindications"""
249
+
250
+ if not medications:
251
+ return {"interactions": [], "warnings": []}
252
+
253
+ interactions = []
254
+ warnings = []
255
+ med_list = [med.lower() for med in medications]
256
+
257
+ # Check for common dermatology drug interactions
258
+ if any('warfarin' in med for med in med_list) and any('azole' in med for med in med_list):
259
+ interactions.append({
260
+ "drugs": "Warfarin + Azole antifungals",
261
+ "severity": "Major",
262
+ "description": "Increased risk of bleeding due to enhanced warfarin effect"
263
+ })
264
+
265
+ # Check for corticosteroid warnings
266
+ if any('corticosteroid' in med or 'steroid' in med for med in med_list):
267
+ warnings.append("Monitor for skin atrophy with prolonged topical steroid use")
268
+
269
+ # Check for retinoid warnings
270
+ if any('retinoid' in med or 'tretinoin' in med or 'adapalene' in med for med in med_list):
271
+ warnings.append("Contraindicated in pregnancy")
272
+ warnings.append("Increased photosensitivity - use sun protection")
273
+
274
+ return {
275
+ "interactions": interactions,
276
+ "warnings": warnings + ["Always verify drug interactions with current medication database"]
277
+ }
278
+
279
+ def smart_template_suggestions(self, condition: str, patient_demographics: Dict) -> List[Dict]:
280
+ """Suggest optimized treatment templates based on condition and patient factors"""
281
+
282
+ age = patient_demographics.get('age', 30)
283
+ templates = []
284
+
285
+ if 'dermatitis' in condition.lower() or 'eczema' in condition.lower():
286
+ templates = [
287
+ {
288
+ "name": "Mild Dermatitis Protocol",
289
+ "description": "For localized, mild inflammatory dermatitis",
290
+ "medications": "Hydrocortisone 1% cream BID x 7 days\nCetirizine 10mg daily PRN\nEmollient cream QID",
291
+ "duration": "1-2 weeks",
292
+ "instructions": "Apply steroid sparingly to affected areas\nMoisturize frequently\nAvoid triggers",
293
+ "follow_up": "2 weeks",
294
+ "success_probability": 85
295
+ }
296
+ ]
297
+
298
+ elif 'acne' in condition.lower():
299
+ if age < 18:
300
+ templates.append({
301
+ "name": "Teen Acne Starter Protocol",
302
+ "description": "Gentle introduction for adolescent acne",
303
+ "medications": "Benzoyl peroxide 2.5% gel daily\nCetaphil gentle cleanser BID",
304
+ "duration": "6-8 weeks",
305
+ "instructions": "Start slowly, every other day initially\nUse oil-free moisturizer\nSun protection essential",
306
+ "follow_up": "4-6 weeks",
307
+ "success_probability": 70
308
+ })
309
+
310
+ return templates if templates else [{
311
+ "name": "General Dermatology Protocol",
312
+ "description": "Standard approach for common skin conditions",
313
+ "medications": "Moisturizer QID\nMild topical corticosteroid PRN",
314
+ "duration": "2-4 weeks",
315
+ "instructions": "Gentle skin care routine\nAvoid harsh products",
316
+ "follow_up": "2-3 weeks",
317
+ "success_probability": 65
318
+ }]
319
+
320
+ # Global AI service instance
321
+ medical_ai = MedicalAI()
322
+
323
+ # ============================================================================
324
+ # FORMS
325
+ # ============================================================================
326
+
327
+ class LoginForm(FlaskForm):
328
+ username = StringField('Username', validators=[DataRequired()])
329
+ password = PasswordField('Password', validators=[DataRequired()])
330
+
331
+ class PatientForm(FlaskForm):
332
+ first_name = StringField('First Name', validators=[DataRequired(), Length(max=64)])
333
+ last_name = StringField('Last Name', validators=[DataRequired(), Length(max=64)])
334
+ date_of_birth = DateField('Date of Birth', validators=[DataRequired()])
335
+ gender = SelectField('Gender', choices=[('Male', 'Male'), ('Female', 'Female'), ('Other', 'Other')])
336
+ phone = StringField('Phone', validators=[Length(max=20)])
337
+ email = StringField('Email', validators=[Optional(), Email(), Length(max=120)])
338
+ address = TextAreaField('Address')
339
+ allergies = TextAreaField('Allergies')
340
+ medical_history = TextAreaField('Medical History')
341
+
342
+ class TreatmentPlanForm(FlaskForm):
343
+ diagnosis = StringField('Diagnosis', validators=[DataRequired(), Length(max=200)])
344
+ medications = TextAreaField('Medications')
345
+ instructions = TextAreaField('Instructions')
346
+ duration = StringField('Duration', validators=[Length(max=50)])
347
+ follow_up_date = DateField('Follow-up Date', validators=[Optional()])
348
+ notes = TextAreaField('Additional Notes')
349
+
350
+ # ============================================================================
351
+ # FLASK APPLICATION SETUP
352
+ # ============================================================================
353
+
354
+ app = Flask(__name__)
355
+ app.secret_key = os.environ.get("SESSION_SECRET", "dev-secret-key-change-in-production")
356
+ app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
357
+
358
+ # Database configuration for Hugging Face compatibility
359
+ database_url = os.environ.get("DATABASE_URL", "sqlite:///medical_dashboard.db")
360
+ # Handle PostgreSQL URL format for Hugging Face if provided
361
+ if database_url.startswith("postgres://"):
362
+ database_url = database_url.replace("postgres://", "postgresql://", 1)
363
+
364
+ app.config["SQLALCHEMY_DATABASE_URI"] = database_url
365
+ app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {
366
+ "pool_recycle": 300,
367
+ "pool_pre_ping": True,
368
+ }
369
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
370
+
371
+ # File upload configuration
372
+ app.config['UPLOAD_FOLDER'] = 'uploads'
373
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
374
+
375
+ # Initialize extensions
376
+ db.init_app(app)
377
+ login_manager = LoginManager()
378
+ login_manager.init_app(app)
379
+ login_manager.login_view = 'login'
380
+
381
+ @login_manager.user_loader
382
+ def load_user(user_id):
383
+ return User.query.get(int(user_id))
384
+
385
+ # ============================================================================
386
+ # HTML TEMPLATES
387
+ # ============================================================================
388
+
389
+ BASE_TEMPLATE = """
390
+ <!DOCTYPE html>
391
+ <html lang="en">
392
+ <head>
393
+ <meta charset="UTF-8">
394
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
395
+ <title>{% block title %}Doctor's Sidekick{% endblock %}</title>
396
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
397
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
398
+ <style>
399
+ :root {
400
+ --primary-color: #0F766E;
401
+ --secondary-color: #1E40AF;
402
+ --accent-color: #F59E0B;
403
+ --success-color: #059669;
404
+ --danger-color: #DC2626;
405
+ }
406
+ body { font-family: 'Inter', system-ui, sans-serif; background-color: #f8fafc; }
407
+ .navbar-brand { font-weight: 700; color: var(--primary-color) !important; }
408
+ .btn-primary { background-color: var(--primary-color); border-color: var(--primary-color); }
409
+ .btn-primary:hover { background-color: #065f5a; border-color: #065f5a; }
410
+ .text-primary { color: var(--primary-color) !important; }
411
+ .bg-primary { background-color: var(--primary-color) !important; }
412
+ .card { border: none; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
413
+ .nav-link.active { background-color: var(--primary-color) !important; }
414
+ </style>
415
+ </head>
416
+ <body>
417
+ <nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
418
+ <div class="container">
419
+ <a class="navbar-brand" href="{{ url_for('dashboard') }}">
420
+ <i class="fas fa-user-md me-2"></i>Doctor's Sidekick
421
+ </a>
422
+ {% if current_user.is_authenticated %}
423
+ <div class="navbar-nav ms-auto">
424
+ <a class="nav-link" href="{{ url_for('dashboard') }}">
425
+ <i class="fas fa-tachometer-alt me-1"></i> Dashboard
426
+ </a>
427
+ <a class="nav-link" href="{{ url_for('patients') }}">
428
+ <i class="fas fa-users me-1"></i> Patients
429
+ </a>
430
+ <a class="nav-link" href="{{ url_for('treatment_planner') }}">
431
+ <i class="fas fa-prescription-bottle-alt me-1"></i> Treatment Planner
432
+ </a>
433
+ <a class="nav-link" href="{{ url_for('ai_assistant') }}">
434
+ <i class="fas fa-brain me-1"></i> AI Assistant
435
+ </a>
436
+ <div class="nav-link">
437
+ <span class="text-muted">{{ current_user.full_name() }}</span>
438
+ </div>
439
+ <a class="nav-link" href="{{ url_for('logout') }}">
440
+ <i class="fas fa-sign-out-alt me-1"></i> Logout
441
+ </a>
442
+ </div>
443
+ {% endif %}
444
+ </div>
445
+ </nav>
446
+
447
+ <main class="container mt-4">
448
+ {% with messages = get_flashed_messages(with_categories=true) %}
449
+ {% if messages %}
450
+ {% for category, message in messages %}
451
+ <div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
452
+ {{ message }}
453
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
454
+ </div>
455
+ {% endfor %}
456
+ {% endif %}
457
+ {% endwith %}
458
+
459
+ {% block content %}{% endblock %}
460
+ </main>
461
+
462
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
463
+ {% block scripts %}{% endblock %}
464
+ </body>
465
+ </html>
466
+ """
467
+
468
+ LOGIN_TEMPLATE = """
469
+ <!DOCTYPE html>
470
+ <html lang="en">
471
+ <head>
472
+ <meta charset="UTF-8">
473
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
474
+ <title>Login - Doctor's Sidekick</title>
475
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
476
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
477
+ <style>
478
+ :root {
479
+ --primary-color: #0F766E;
480
+ --secondary-color: #1E40AF;
481
+ }
482
+ body { font-family: 'Inter', system-ui, sans-serif; background-color: #f8fafc; }
483
+ .btn-primary { background-color: var(--primary-color); border-color: var(--primary-color); }
484
+ .btn-primary:hover { background-color: #065f5a; border-color: #065f5a; }
485
+ .bg-primary { background-color: var(--primary-color) !important; }
486
+ .card { border: none; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
487
+ </style>
488
+ </head>
489
+ <body>
490
+ <div class="container mt-5">
491
+ {% with messages = get_flashed_messages(with_categories=true) %}
492
+ {% if messages %}
493
+ {% for category, message in messages %}
494
+ <div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
495
+ {{ message }}
496
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
497
+ </div>
498
+ {% endfor %}
499
+ {% endif %}
500
+ {% endwith %}
501
+
502
+ <div class="row justify-content-center">
503
+ <div class="col-md-6 col-lg-4">
504
+ <div class="card">
505
+ <div class="card-header bg-primary text-white text-center">
506
+ <h4><i class="fas fa-user-md me-2"></i>Doctor's Login</h4>
507
+ </div>
508
+ <div class="card-body">
509
+ <form method="POST">
510
+ {{ form.hidden_tag() }}
511
+ <div class="mb-3">
512
+ {{ form.username.label(class="form-label") }}
513
+ {{ form.username(class="form-control") }}
514
+ </div>
515
+ <div class="mb-3">
516
+ {{ form.password.label(class="form-label") }}
517
+ {{ form.password(class="form-control") }}
518
+ </div>
519
+ <div class="d-grid">
520
+ <button type="submit" class="btn btn-primary">
521
+ <i class="fas fa-sign-in-alt me-2"></i>Login
522
+ </button>
523
+ </div>
524
+ </form>
525
+ <div class="mt-3 text-center">
526
+ <small class="text-muted">Demo: admin/admin</small>
527
+ </div>
528
+ </div>
529
+ </div>
530
+ </div>
531
+ </div>
532
+ </div>
533
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
534
+ </body>
535
+ </html>
536
+ """
537
+
538
+ DASHBOARD_TEMPLATE = """
539
+ <!DOCTYPE html>
540
+ <html lang="en">
541
+ <head>
542
+ <meta charset="UTF-8">
543
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
544
+ <title>Dashboard - Doctor's Sidekick</title>
545
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
546
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
547
+ <style>
548
+ :root {
549
+ --primary-color: #0F766E;
550
+ --secondary-color: #1E40AF;
551
+ }
552
+ body { font-family: 'Inter', system-ui, sans-serif; background-color: #f8fafc; }
553
+ .navbar-brand { font-weight: 700; color: var(--primary-color) !important; }
554
+ .btn-primary { background-color: var(--primary-color); border-color: var(--primary-color); }
555
+ .btn-primary:hover { background-color: #065f5a; border-color: #065f5a; }
556
+ .text-primary { color: var(--primary-color) !important; }
557
+ .bg-primary { background-color: var(--primary-color) !important; }
558
+ .card { border: none; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
559
+ .nav-link.active { background-color: var(--primary-color) !important; }
560
+ </style>
561
+ </head>
562
+ <body>
563
+ <nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
564
+ <div class="container">
565
+ <a class="navbar-brand" href="{{ url_for('dashboard') }}">
566
+ <i class="fas fa-user-md me-2"></i>Doctor's Sidekick
567
+ </a>
568
+ <div class="navbar-nav ms-auto">
569
+ <a class="nav-link active" href="{{ url_for('dashboard') }}">
570
+ <i class="fas fa-tachometer-alt me-1"></i> Dashboard
571
+ </a>
572
+ <a class="nav-link" href="{{ url_for('patients') }}">
573
+ <i class="fas fa-users me-1"></i> Patients
574
+ </a>
575
+ <a class="nav-link" href="{{ url_for('treatment_planner') }}">
576
+ <i class="fas fa-prescription-bottle-alt me-1"></i> Treatment Planner
577
+ </a>
578
+ <a class="nav-link" href="{{ url_for('ai_assistant') }}">
579
+ <i class="fas fa-brain me-1"></i> AI Assistant
580
+ </a>
581
+ <div class="nav-link">
582
+ <span class="text-muted">{{ current_user.full_name() }}</span>
583
+ </div>
584
+ <a class="nav-link" href="{{ url_for('logout') }}">
585
+ <i class="fas fa-sign-out-alt me-1"></i> Logout
586
+ </a>
587
+ </div>
588
+ </div>
589
+ </nav>
590
+
591
+ <main class="container mt-4">
592
+ {% with messages = get_flashed_messages(with_categories=true) %}
593
+ {% if messages %}
594
+ {% for category, message in messages %}
595
+ <div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
596
+ {{ message }}
597
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
598
+ </div>
599
+ {% endfor %}
600
+ {% endif %}
601
+ {% endwith %}
602
+ <div class="d-flex justify-content-between align-items-center mb-4">
603
+ <h1 class="h3">Medical Dashboard</h1>
604
+ <div class="text-end">
605
+ <span class="badge bg-success">AI Online</span>
606
+ <div class="small text-muted">{{ current_user.full_name() }}</div>
607
+ </div>
608
+ </div>
609
+
610
+ <div class="row g-4">
611
+ <!-- Stats Cards -->
612
+ <div class="col-md-3">
613
+ <div class="card text-center">
614
+ <div class="card-body">
615
+ <i class="fas fa-users fa-2x text-primary mb-2"></i>
616
+ <h5>{{ stats.total_patients }}</h5>
617
+ <small class="text-muted">Total Patients</small>
618
+ </div>
619
+ </div>
620
+ </div>
621
+ <div class="col-md-3">
622
+ <div class="card text-center">
623
+ <div class="card-body">
624
+ <i class="fas fa-calendar-check fa-2x text-success mb-2"></i>
625
+ <h5>{{ stats.visits_today }}</h5>
626
+ <small class="text-muted">Visits Today</small>
627
+ </div>
628
+ </div>
629
+ </div>
630
+ <div class="col-md-3">
631
+ <div class="card text-center">
632
+ <div class="card-body">
633
+ <i class="fas fa-prescription-bottle-alt fa-2x text-warning mb-2"></i>
634
+ <h5>{{ stats.treatment_plans }}</h5>
635
+ <small class="text-muted">Treatment Plans</small>
636
+ </div>
637
+ </div>
638
+ </div>
639
+ <div class="col-md-3">
640
+ <div class="card text-center">
641
+ <div class="card-body">
642
+ <i class="fas fa-brain fa-2x text-info mb-2"></i>
643
+ <h5>Active</h5>
644
+ <small class="text-muted">AI Assistant</small>
645
+ </div>
646
+ </div>
647
+ </div>
648
+ </div>
649
+
650
+ <div class="row g-4 mt-4">
651
+ <!-- Quick Actions -->
652
+ <div class="col-md-8">
653
+ <div class="card">
654
+ <div class="card-header">
655
+ <h5><i class="fas fa-bolt me-2"></i>Quick Actions</h5>
656
+ </div>
657
+ <div class="card-body">
658
+ <div class="row g-3">
659
+ <div class="col-md-6">
660
+ <a href="{{ url_for('new_patient') }}" class="btn btn-outline-primary w-100">
661
+ <i class="fas fa-user-plus me-2"></i>New Patient
662
+ </a>
663
+ </div>
664
+ <div class="col-md-6">
665
+ <a href="{{ url_for('treatment_planner') }}" class="btn btn-outline-success w-100">
666
+ <i class="fas fa-prescription-bottle-alt me-2"></i>Treatment Plan
667
+ </a>
668
+ </div>
669
+ <div class="col-md-6">
670
+ <a href="{{ url_for('ai_assistant') }}" class="btn btn-outline-info w-100">
671
+ <i class="fas fa-brain me-2"></i>AI Analysis
672
+ </a>
673
+ </div>
674
+ <div class="col-md-6">
675
+ <a href="{{ url_for('patients') }}" class="btn btn-outline-warning w-100">
676
+ <i class="fas fa-search me-2"></i>Find Patient
677
+ </a>
678
+ </div>
679
+ </div>
680
+ </div>
681
+ </div>
682
+ </div>
683
+
684
+ <!-- Recent Activity -->
685
+ <div class="col-md-4">
686
+ <div class="card">
687
+ <div class="card-header">
688
+ <h6><i class="fas fa-clock me-2"></i>Recent Activity</h6>
689
+ </div>
690
+ <div class="card-body">
691
+ <div class="small">
692
+ <div class="d-flex justify-content-between mb-2">
693
+ <span>System initialized</span>
694
+ <span class="text-muted">Now</span>
695
+ </div>
696
+ <div class="d-flex justify-content-between mb-2">
697
+ <span>AI services online</span>
698
+ <span class="text-muted">Now</span>
699
+ </div>
700
+ <div class="d-flex justify-content-between">
701
+ <span>Dashboard ready</span>
702
+ <span class="text-muted">Now</span>
703
+ </div>
704
+ </div>
705
+ </div>
706
+ </div>
707
+ </div>
708
+ </div>
709
+
710
+ </main>
711
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
712
+ </body>
713
+ </html>
714
+ """
715
+
716
+ PATIENTS_TEMPLATE = """
717
+ <!DOCTYPE html>
718
+ <html lang="en">
719
+ <head>
720
+ <meta charset="UTF-8">
721
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
722
+ <title>Patients - Doctor's Sidekick</title>
723
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
724
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
725
+ <style>
726
+ :root {
727
+ --primary-color: #0F766E;
728
+ --secondary-color: #1E40AF;
729
+ }
730
+ body { font-family: 'Inter', system-ui, sans-serif; background-color: #f8fafc; }
731
+ .navbar-brand { font-weight: 700; color: var(--primary-color) !important; }
732
+ .btn-primary { background-color: var(--primary-color); border-color: var(--primary-color); }
733
+ .btn-primary:hover { background-color: #065f5a; border-color: #065f5a; }
734
+ .text-primary { color: var(--primary-color) !important; }
735
+ .bg-primary { background-color: var(--primary-color) !important; }
736
+ .card { border: none; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
737
+ .nav-link.active { background-color: var(--primary-color) !important; }
738
+ </style>
739
+ </head>
740
+ <body>
741
+ <nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
742
+ <div class="container">
743
+ <a class="navbar-brand" href="{{ url_for('dashboard') }}">
744
+ <i class="fas fa-user-md me-2"></i>Doctor's Sidekick
745
+ </a>
746
+ <div class="navbar-nav ms-auto">
747
+ <a class="nav-link" href="{{ url_for('dashboard') }}">
748
+ <i class="fas fa-tachometer-alt me-1"></i> Dashboard
749
+ </a>
750
+ <a class="nav-link active" href="{{ url_for('patients') }}">
751
+ <i class="fas fa-users me-1"></i> Patients
752
+ </a>
753
+ <a class="nav-link" href="{{ url_for('treatment_planner') }}">
754
+ <i class="fas fa-prescription-bottle-alt me-1"></i> Treatment Planner
755
+ </a>
756
+ <a class="nav-link" href="{{ url_for('ai_assistant') }}">
757
+ <i class="fas fa-brain me-1"></i> AI Assistant
758
+ </a>
759
+ <div class="nav-link">
760
+ <span class="text-muted">{{ current_user.full_name() }}</span>
761
+ </div>
762
+ <a class="nav-link" href="{{ url_for('logout') }}">
763
+ <i class="fas fa-sign-out-alt me-1"></i> Logout
764
+ </a>
765
+ </div>
766
+ </div>
767
+ </nav>
768
+
769
+ <main class="container mt-4">
770
+ {% with messages = get_flashed_messages(with_categories=true) %}
771
+ {% if messages %}
772
+ {% for category, message in messages %}
773
+ <div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
774
+ {{ message }}
775
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
776
+ </div>
777
+ {% endfor %}
778
+ {% endif %}
779
+ {% endwith %}
780
+
781
+ <div class="d-flex justify-content-between align-items-center mb-4">
782
+ <h1 class="h3">Patient Management</h1>
783
+ <a href="{{ url_for('new_patient') }}" class="btn btn-primary">
784
+ <i class="fas fa-user-plus me-2"></i>New Patient
785
+ </a>
786
+ </div>
787
+
788
+ <div class="card">
789
+ <div class="card-body">
790
+ {% if patients %}
791
+ <div class="table-responsive">
792
+ <table class="table table-hover">
793
+ <thead>
794
+ <tr>
795
+ <th>Patient ID</th>
796
+ <th>Name</th>
797
+ <th>Age</th>
798
+ <th>Gender</th>
799
+ <th>Last Visit</th>
800
+ <th>Actions</th>
801
+ </tr>
802
+ </thead>
803
+ <tbody>
804
+ {% for patient in patients %}
805
+ <tr>
806
+ <td>{{ patient.patient_id }}</td>
807
+ <td>{{ patient.full_name() }}</td>
808
+ <td>{{ patient.age() }}</td>
809
+ <td>{{ patient.gender or 'N/A' }}</td>
810
+ <td>{{ patient.last_visit.strftime('%Y-%m-%d') if patient.last_visit else 'Never' }}</td>
811
+ <td>
812
+ <a href="{{ url_for('patient_detail', id=patient.id) }}" class="btn btn-sm btn-outline-primary">
813
+ <i class="fas fa-eye"></i>
814
+ </a>
815
+ <a href="{{ url_for('edit_patient', id=patient.id) }}" class="btn btn-sm btn-outline-secondary">
816
+ <i class="fas fa-edit"></i>
817
+ </a>
818
+ </td>
819
+ </tr>
820
+ {% endfor %}
821
+ </tbody>
822
+ </table>
823
+ </div>
824
+ {% else %}
825
+ <div class="text-center py-5">
826
+ <i class="fas fa-users fa-3x text-muted mb-3"></i>
827
+ <h5>No patients registered yet</h5>
828
+ <p class="text-muted">Start by adding your first patient</p>
829
+ <a href="{{ url_for('new_patient') }}" class="btn btn-primary">
830
+ <i class="fas fa-user-plus me-2"></i>Add First Patient
831
+ </a>
832
+ </div>
833
+ {% endif %}
834
+ </div>
835
+ </div>
836
+
837
+ </main>
838
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
839
+ </body>
840
+ </html>
841
+ """
842
+
843
+ AI_ASSISTANT_TEMPLATE = """
844
+ <!DOCTYPE html>
845
+ <html lang="en">
846
+ <head>
847
+ <meta charset="UTF-8">
848
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
849
+ <title>AI Assistant - Doctor's Sidekick</title>
850
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
851
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
852
+ <style>
853
+ :root {
854
+ --primary-color: #0F766E;
855
+ --secondary-color: #1E40AF;
856
+ }
857
+ body { font-family: 'Inter', system-ui, sans-serif; background-color: #f8fafc; }
858
+ .navbar-brand { font-weight: 700; color: var(--primary-color) !important; }
859
+ .btn-primary { background-color: var(--primary-color); border-color: var(--primary-color); }
860
+ .btn-primary:hover { background-color: #065f5a; border-color: #065f5a; }
861
+ .text-primary { color: var(--primary-color) !important; }
862
+ .bg-primary { background-color: var(--primary-color) !important; }
863
+ .card { border: none; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
864
+ .nav-link.active { background-color: var(--primary-color) !important; }
865
+ </style>
866
+ </head>
867
+ <body>
868
+ <nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
869
+ <div class="container">
870
+ <a class="navbar-brand" href="{{ url_for('dashboard') }}">
871
+ <i class="fas fa-user-md me-2"></i>Doctor's Sidekick
872
+ </a>
873
+ <div class="navbar-nav ms-auto">
874
+ <a class="nav-link" href="{{ url_for('dashboard') }}">
875
+ <i class="fas fa-tachometer-alt me-1"></i> Dashboard
876
+ </a>
877
+ <a class="nav-link" href="{{ url_for('patients') }}">
878
+ <i class="fas fa-users me-1"></i> Patients
879
+ </a>
880
+ <a class="nav-link" href="{{ url_for('treatment_planner') }}">
881
+ <i class="fas fa-prescription-bottle-alt me-1"></i> Treatment Planner
882
+ </a>
883
+ <a class="nav-link active" href="{{ url_for('ai_assistant') }}">
884
+ <i class="fas fa-brain me-1"></i> AI Assistant
885
+ </a>
886
+ <div class="nav-link">
887
+ <span class="text-muted">{{ current_user.full_name() }}</span>
888
+ </div>
889
+ <a class="nav-link" href="{{ url_for('logout') }}">
890
+ <i class="fas fa-sign-out-alt me-1"></i> Logout
891
+ </a>
892
+ </div>
893
+ </div>
894
+ </nav>
895
+
896
+ <main class="container mt-4">
897
+ {% with messages = get_flashed_messages(with_categories=true) %}
898
+ {% if messages %}
899
+ {% for category, message in messages %}
900
+ <div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
901
+ {{ message }}
902
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
903
+ </div>
904
+ {% endfor %}
905
+ {% endif %}
906
+ {% endwith %}
907
+ <div class="d-flex justify-content-between align-items-center mb-4">
908
+ <div>
909
+ <h1 class="h3"><i class="fas fa-brain me-2 text-primary"></i>AI-Powered Treatment Assistant</h1>
910
+ <p class="text-muted mb-0">Intelligent diagnostic and treatment recommendations</p>
911
+ </div>
912
+ <div class="badge bg-success">
913
+ <i class="fas fa-check-circle me-1"></i> AI Online
914
+ </div>
915
+ </div>
916
+
917
+ <div class="row g-4">
918
+ <div class="col-lg-8">
919
+ <div class="card">
920
+ <div class="card-header bg-primary text-white">
921
+ <h5 class="mb-0"><i class="fas fa-stethoscope me-2"></i>Symptom Analysis</h5>
922
+ </div>
923
+ <div class="card-body">
924
+ <form id="symptomAnalysisForm">
925
+ <div class="row g-3">
926
+ <div class="col-md-6">
927
+ <label class="form-label">Select Patient</label>
928
+ <select class="form-select" name="patient_id" id="aiPatientSelect" required>
929
+ <option value="">Choose patient...</option>
930
+ {% for patient in patients %}
931
+ <option value="{{ patient.id }}"
932
+ data-age="{{ patient.age() }}"
933
+ data-gender="{{ patient.gender }}"
934
+ data-allergies="{{ patient.allergies or '' }}"
935
+ data-medical-history="{{ patient.medical_history or '' }}">
936
+ {{ patient.full_name() }} ({{ patient.patient_id }}) - Age {{ patient.age() }}
937
+ </option>
938
+ {% endfor %}
939
+ </select>
940
+ </div>
941
+ <div class="col-md-6">
942
+ <label class="form-label">Urgency Level</label>
943
+ <select class="form-select" name="urgency">
944
+ <option value="routine">Routine</option>
945
+ <option value="urgent">Urgent</option>
946
+ <option value="emergency">Emergency</option>
947
+ </select>
948
+ </div>
949
+ <div class="col-12">
950
+ <label class="form-label">Chief Complaint & Symptoms</label>
951
+ <textarea class="form-control" name="symptoms" rows="4"
952
+ placeholder="Describe the patient's symptoms in detail..."
953
+ required></textarea>
954
+ </div>
955
+ <div class="col-md-6">
956
+ <label class="form-label">Working Diagnosis (Optional)</label>
957
+ <input type="text" class="form-control" name="suspected_diagnosis"
958
+ placeholder="Your suspected diagnosis...">
959
+ </div>
960
+ </div>
961
+ <div class="d-flex gap-2 mt-4">
962
+ <button type="submit" class="btn btn-primary">
963
+ <i class="fas fa-brain me-2"></i> Analyze with AI
964
+ </button>
965
+ <button type="button" class="btn btn-outline-secondary" onclick="clearAIForm()">
966
+ <i class="fas fa-eraser me-2"></i> Clear
967
+ </button>
968
+ </div>
969
+ </form>
970
+ </div>
971
+ </div>
972
+
973
+ <div id="aiResults" class="d-none mt-4">
974
+ <div class="card">
975
+ <div class="card-header bg-success text-white">
976
+ <h5 class="mb-0"><i class="fas fa-list-alt me-2"></i>AI Analysis Results</h5>
977
+ </div>
978
+ <div class="card-body" id="analysisResults">
979
+ <!-- AI results will be populated here -->
980
+ </div>
981
+ </div>
982
+ </div>
983
+ </div>
984
+
985
+ <div class="col-lg-4">
986
+ <div class="card">
987
+ <div class="card-header bg-light">
988
+ <h6 class="mb-0"><i class="fas fa-tools me-2 text-primary"></i>Quick AI Tools</h6>
989
+ </div>
990
+ <div class="card-body">
991
+ <div class="d-grid gap-2">
992
+ <button class="btn btn-outline-primary btn-sm" onclick="quickDrugCheck()">
993
+ <i class="fas fa-pills me-2"></i> Drug Interaction Check
994
+ </button>
995
+ <button class="btn btn-outline-success btn-sm" onclick="aiInsights()">
996
+ <i class="fas fa-lightbulb me-2"></i> AI Insights
997
+ </button>
998
+ <button class="btn btn-outline-info btn-sm" onclick="templateGenerator()">
999
+ <i class="fas fa-magic me-2"></i> Generate Template
1000
+ </button>
1001
+ </div>
1002
+ </div>
1003
+ </div>
1004
+
1005
+ <div class="card mt-4">
1006
+ <div class="card-header bg-light">
1007
+ <h6 class="mb-0"><i class="fas fa-lightbulb me-2 text-warning"></i>AI Tips</h6>
1008
+ </div>
1009
+ <div class="card-body">
1010
+ <div class="small">
1011
+ <div class="mb-3">
1012
+ <i class="fas fa-info-circle text-info me-2"></i>
1013
+ <strong>Smart Documentation:</strong> AI helps generate comprehensive visit notes.
1014
+ </div>
1015
+ <div class="mb-3">
1016
+ <i class="fas fa-shield-alt text-success me-2"></i>
1017
+ <strong>Safety First:</strong> AI recommendations supplement clinical judgment.
1018
+ </div>
1019
+ <div>
1020
+ <i class="fas fa-clock text-primary me-2"></i>
1021
+ <strong>Continuous Learning:</strong> System improves with medical literature updates.
1022
+ </div>
1023
+ </div>
1024
+ </div>
1025
+ </div>
1026
+ </div>
1027
+ </div>
1028
+
1029
+ <div id="aiLoadingOverlay" class="d-none">
1030
+ <div class="position-fixed top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center"
1031
+ style="background: rgba(0,0,0,0.7); z-index: 9999;">
1032
+ <div class="text-center text-white">
1033
+ <div class="spinner-border text-primary mb-3" style="width: 3rem; height: 3rem;"></div>
1034
+ <h5>AI is analyzing...</h5>
1035
+ <p class="mb-0">This may take a few moments</p>
1036
+ </div>
1037
+ </div>
1038
+ </div>
1039
+
1040
+ <script>
1041
+ // AI Assistant JavaScript
1042
+ document.getElementById('symptomAnalysisForm').addEventListener('submit', async function(e) {
1043
+ e.preventDefault();
1044
+
1045
+ const formData = new FormData(this);
1046
+ const analysisData = {
1047
+ patient_id: formData.get('patient_id'),
1048
+ symptoms: formData.get('symptoms'),
1049
+ suspected_diagnosis: formData.get('suspected_diagnosis'),
1050
+ urgency: formData.get('urgency')
1051
+ };
1052
+
1053
+ if (!analysisData.patient_id || !analysisData.symptoms) {
1054
+ alert('Please select a patient and enter symptoms.');
1055
+ return;
1056
+ }
1057
+
1058
+ document.getElementById('aiLoadingOverlay').classList.remove('d-none');
1059
+
1060
+ try {
1061
+ const response = await fetch('/api/ai/analyze-symptoms', {
1062
+ method: 'POST',
1063
+ headers: {
1064
+ 'Content-Type': 'application/json'
1065
+ },
1066
+ body: JSON.stringify(analysisData)
1067
+ });
1068
+
1069
+ const results = await response.json();
1070
+
1071
+ if (results.success) {
1072
+ displayResults(results);
1073
+ } else {
1074
+ alert('AI analysis failed: ' + (results.error || 'Unknown error'));
1075
+ }
1076
+ } catch (error) {
1077
+ console.error('AI analysis error:', error);
1078
+ alert('AI analysis service is currently unavailable. Please try again later.');
1079
+ } finally {
1080
+ document.getElementById('aiLoadingOverlay').classList.add('d-none');
1081
+ }
1082
+ });
1083
+
1084
+ function displayResults(results) {
1085
+ document.getElementById('aiResults').classList.remove('d-none');
1086
+
1087
+ let html = '<div class="row g-3">';
1088
+
1089
+ // Differential Diagnosis
1090
+ if (results.differential_diagnosis && results.differential_diagnosis.length > 0) {
1091
+ html += '<div class="col-12"><h6><i class="fas fa-list-check text-primary me-2"></i>Differential Diagnosis</h6>';
1092
+ results.differential_diagnosis.forEach(diagnosis => {
1093
+ const confidenceColor = diagnosis.confidence > 70 ? 'success' :
1094
+ diagnosis.confidence > 40 ? 'warning' : 'danger';
1095
+ html += `
1096
+ <div class="card border-${confidenceColor} mb-2">
1097
+ <div class="card-body py-2">
1098
+ <div class="d-flex justify-content-between align-items-start">
1099
+ <strong>${diagnosis.condition}</strong>
1100
+ <span class="badge bg-${confidenceColor}">${diagnosis.confidence}%</span>
1101
+ </div>
1102
+ <small class="text-muted">${diagnosis.description}</small>
1103
+ </div>
1104
+ </div>
1105
+ `;
1106
+ });
1107
+ html += '</div>';
1108
+ }
1109
+
1110
+ // Treatment Plan
1111
+ if (results.treatment_plan) {
1112
+ const plan = results.treatment_plan;
1113
+ html += `
1114
+ <div class="col-md-6">
1115
+ <h6><i class="fas fa-pills text-success me-2"></i>Medications</h6>
1116
+ <div class="bg-light p-3 rounded">
1117
+ ${plan.medications || 'No specific medications recommended'}
1118
+ </div>
1119
+ </div>
1120
+ <div class="col-md-6">
1121
+ <h6><i class="fas fa-list-check text-info me-2"></i>Instructions</h6>
1122
+ <div class="bg-light p-3 rounded">
1123
+ ${plan.instructions || 'Standard care instructions apply'}
1124
+ </div>
1125
+ </div>
1126
+ `;
1127
+
1128
+ if (plan.warnings && plan.warnings.length > 0) {
1129
+ html += `
1130
+ <div class="col-12">
1131
+ <div class="alert alert-warning">
1132
+ <h6><i class="fas fa-exclamation-triangle me-2"></i>Important Warnings</h6>
1133
+ <ul class="mb-0">
1134
+ ${plan.warnings.map(warning => `<li>${warning}</li>`).join('')}
1135
+ </ul>
1136
+ </div>
1137
+ </div>
1138
+ `;
1139
+ }
1140
+ }
1141
+
1142
+ html += '</div>';
1143
+ document.getElementById('analysisResults').innerHTML = html;
1144
+ }
1145
+
1146
+ function clearAIForm() {
1147
+ document.getElementById('symptomAnalysisForm').reset();
1148
+ document.getElementById('aiResults').classList.add('d-none');
1149
+ }
1150
+
1151
+ function quickDrugCheck() {
1152
+ alert('Drug interaction checker: Enter medications to check for interactions and contraindications.');
1153
+ }
1154
+
1155
+ function aiInsights() {
1156
+ alert('AI Insights: Daily medical insights and practice recommendations based on current cases.');
1157
+ }
1158
+
1159
+ function templateGenerator() {
1160
+ window.location.href = '/treatment-planner';
1161
+ }
1162
+ </script>
1163
+
1164
+ </main>
1165
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
1166
+ </body>
1167
+ </html>
1168
+ """
1169
+
1170
+ TREATMENT_PLANNER_TEMPLATE = """
1171
+ <!DOCTYPE html>
1172
+ <html lang="en">
1173
+ <head>
1174
+ <meta charset="UTF-8">
1175
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1176
+ <title>Treatment Planner - Doctor's Sidekick</title>
1177
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
1178
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
1179
+ <style>
1180
+ :root { --primary-color: #0F766E; --secondary-color: #1E40AF; }
1181
+ body { font-family: 'Inter', system-ui, sans-serif; background-color: #f8fafc; }
1182
+ .navbar-brand { font-weight: 700; color: var(--primary-color) !important; }
1183
+ .btn-primary { background-color: var(--primary-color); border-color: var(--primary-color); }
1184
+ .btn-primary:hover { background-color: #065f5a; border-color: #065f5a; }
1185
+ .text-primary { color: var(--primary-color) !important; }
1186
+ .bg-primary { background-color: var(--primary-color) !important; }
1187
+ .card { border: none; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
1188
+ .nav-link.active { background-color: var(--primary-color) !important; }
1189
+ </style>
1190
+ </head>
1191
+ <body>
1192
+ <nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
1193
+ <div class="container">
1194
+ <a class="navbar-brand" href="{{ url_for('dashboard') }}">
1195
+ <i class="fas fa-user-md me-2"></i>Doctor's Sidekick
1196
+ </a>
1197
+ <div class="navbar-nav ms-auto">
1198
+ <a class="nav-link" href="{{ url_for('dashboard') }}">
1199
+ <i class="fas fa-tachometer-alt me-1"></i> Dashboard
1200
+ </a>
1201
+ <a class="nav-link" href="{{ url_for('patients') }}">
1202
+ <i class="fas fa-users me-1"></i> Patients
1203
+ </a>
1204
+ <a class="nav-link active" href="{{ url_for('treatment_planner') }}">
1205
+ <i class="fas fa-prescription-bottle-alt me-1"></i> Treatment Planner
1206
+ </a>
1207
+ <a class="nav-link" href="{{ url_for('ai_assistant') }}">
1208
+ <i class="fas fa-brain me-1"></i> AI Assistant
1209
+ </a>
1210
+ <div class="nav-link">
1211
+ <span class="text-muted">{{ current_user.full_name() }}</span>
1212
+ </div>
1213
+ <a class="nav-link" href="{{ url_for('logout') }}">
1214
+ <i class="fas fa-sign-out-alt me-1"></i> Logout
1215
+ </a>
1216
+ </div>
1217
+ </div>
1218
+ </nav>
1219
+
1220
+ <main class="container mt-4">
1221
+ {% with messages = get_flashed_messages(with_categories=true) %}
1222
+ {% if messages %}
1223
+ {% for category, message in messages %}
1224
+ <div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
1225
+ {{ message }}
1226
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
1227
+ </div>
1228
+ {% endfor %}
1229
+ {% endif %}
1230
+ {% endwith %}
1231
+ <div class="d-flex justify-content-between align-items-center mb-4">
1232
+ <h1 class="h3"><i class="fas fa-prescription-bottle-alt me-2 text-primary"></i>Treatment Planner</h1>
1233
+ <div class="badge bg-info">AI-Enhanced</div>
1234
+ </div>
1235
+
1236
+ <div class="row g-4">
1237
+ <div class="col-lg-8">
1238
+ <div class="card">
1239
+ <div class="card-header">
1240
+ <h5><i class="fas fa-plus-circle me-2"></i>Create Treatment Plan</h5>
1241
+ </div>
1242
+ <div class="card-body">
1243
+ <form method="POST" id="treatmentPlanForm">
1244
+ {{ form.hidden_tag() }}
1245
+ <div class="row g-3">
1246
+ <div class="col-md-6">
1247
+ <label class="form-label">Select Patient</label>
1248
+ <select class="form-select" name="patient_id" required>
1249
+ <option value="">Choose patient...</option>
1250
+ {% for patient in patients %}
1251
+ <option value="{{ patient.id }}">
1252
+ {{ patient.full_name() }} ({{ patient.patient_id }}) - Age {{ patient.age() }}
1253
+ </option>
1254
+ {% endfor %}
1255
+ </select>
1256
+ </div>
1257
+ <div class="col-md-6">
1258
+ <label class="form-label">Condition Category</label>
1259
+ <select class="form-select" id="condition_id">
1260
+ <option value="">Select condition...</option>
1261
+ {% for condition in conditions %}
1262
+ <option value="{{ condition.id }}">{{ condition.name }}</option>
1263
+ {% endfor %}
1264
+ </select>
1265
+ </div>
1266
+ <div class="col-12">
1267
+ {{ form.diagnosis.label(class="form-label") }}
1268
+ {{ form.diagnosis(class="form-control") }}
1269
+ </div>
1270
+ <div class="col-12">
1271
+ {{ form.medications.label(class="form-label") }}
1272
+ {{ form.medications(class="form-control", rows="4", placeholder="Enter medications with dosages and frequency") }}
1273
+ </div>
1274
+ <div class="col-12">
1275
+ {{ form.instructions.label(class="form-label") }}
1276
+ {{ form.instructions(class="form-control", rows="4", placeholder="Patient care instructions") }}
1277
+ </div>
1278
+ <div class="col-md-6">
1279
+ {{ form.duration.label(class="form-label") }}
1280
+ {{ form.duration(class="form-control", placeholder="e.g., 2-4 weeks") }}
1281
+ </div>
1282
+ <div class="col-md-6">
1283
+ {{ form.follow_up_date.label(class="form-label") }}
1284
+ {{ form.follow_up_date(class="form-control") }}
1285
+ </div>
1286
+ <div class="col-12">
1287
+ {{ form.notes.label(class="form-label") }}
1288
+ {{ form.notes(class="form-control", rows="3") }}
1289
+ </div>
1290
+ </div>
1291
+
1292
+ <div class="d-flex gap-2 mt-4">
1293
+ <button type="submit" class="btn btn-primary">
1294
+ <i class="fas fa-save me-2"></i> Create Treatment Plan
1295
+ </button>
1296
+ <button type="button" class="btn btn-outline-info" onclick="getAIRecommendations()">
1297
+ <i class="fas fa-brain me-2"></i> Get AI Recommendations
1298
+ </button>
1299
+ <button type="button" class="btn btn-outline-secondary" onclick="clearForm()">
1300
+ <i class="fas fa-eraser me-2"></i> Clear Form
1301
+ </button>
1302
+ </div>
1303
+ </form>
1304
+ </div>
1305
+ </div>
1306
+ </div>
1307
+
1308
+ <div class="col-lg-4">
1309
+ <div class="card">
1310
+ <div class="card-header bg-light">
1311
+ <h6 class="mb-0"><i class="fas fa-lightbulb me-2 text-warning"></i>Treatment Guidelines</h6>
1312
+ </div>
1313
+ <div class="card-body">
1314
+ <div class="small">
1315
+ <h6 class="text-primary"><i class="fas fa-circle-check me-2"></i>Best Practices</h6>
1316
+ <ul class="list-unstyled ms-3">
1317
+ <li><i class="fas fa-chevron-right me-2 text-muted"></i>Include specific dosages and frequencies</li>
1318
+ <li><i class="fas fa-chevron-right me-2 text-muted"></i>Provide clear patient instructions</li>
1319
+ <li><i class="fas fa-chevron-right me-2 text-muted"></i>Set appropriate follow-up timelines</li>
1320
+ </ul>
1321
+
1322
+ <h6 class="text-success mt-3"><i class="fas fa-info-circle me-2"></i>Patient Safety</h6>
1323
+ <ul class="list-unstyled ms-3">
1324
+ <li><i class="fas fa-chevron-right me-2 text-muted"></i>Check for drug allergies</li>
1325
+ <li><i class="fas fa-chevron-right me-2 text-muted"></i>Consider drug interactions</li>
1326
+ <li><i class="fas fa-chevron-right me-2 text-muted"></i>Include warning signs</li>
1327
+ </ul>
1328
+ </div>
1329
+ </div>
1330
+ </div>
1331
+ </div>
1332
+ </div>
1333
+
1334
+ <script>
1335
+ async function getAIRecommendations() {
1336
+ const patientSelect = document.querySelector('select[name="patient_id"]');
1337
+ const diagnosisInput = document.querySelector('input[name="diagnosis"]');
1338
+
1339
+ if (!patientSelect.value || !diagnosisInput.value) {
1340
+ alert('Please select a patient and enter a diagnosis to get AI recommendations.');
1341
+ return;
1342
+ }
1343
+
1344
+ try {
1345
+ const response = await fetch('/api/ai/analyze-symptoms', {
1346
+ method: 'POST',
1347
+ headers: {
1348
+ 'Content-Type': 'application/json'
1349
+ },
1350
+ body: JSON.stringify({
1351
+ patient_id: patientSelect.value,
1352
+ symptoms: diagnosisInput.value,
1353
+ suspected_diagnosis: diagnosisInput.value
1354
+ })
1355
+ });
1356
+
1357
+ const data = await response.json();
1358
+
1359
+ if (data.success && data.treatment_plan) {
1360
+ const plan = data.treatment_plan;
1361
+ if (plan.medications) {
1362
+ document.querySelector('textarea[name="medications"]').value = plan.medications;
1363
+ }
1364
+ if (plan.instructions) {
1365
+ document.querySelector('textarea[name="instructions"]').value = plan.instructions;
1366
+ }
1367
+
1368
+ alert('AI recommendations applied successfully! Please review and adjust as needed.');
1369
+ } else {
1370
+ alert('Unable to get AI recommendations at this time.');
1371
+ }
1372
+ } catch (error) {
1373
+ console.error('AI recommendation error:', error);
1374
+ alert('AI recommendation service is currently unavailable.');
1375
+ }
1376
+ }
1377
+
1378
+ function clearForm() {
1379
+ if (confirm('Are you sure you want to clear all form data?')) {
1380
+ document.getElementById('treatmentPlanForm').reset();
1381
+ }
1382
+ }
1383
+ </script>
1384
+
1385
+ </main>
1386
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
1387
+ </body>
1388
+ </html>
1389
+ """
1390
+
1391
+ NEW_PATIENT_TEMPLATE = """
1392
+ <!DOCTYPE html>
1393
+ <html lang="en">
1394
+ <head>
1395
+ <meta charset="UTF-8">
1396
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1397
+ <title>New Patient - Doctor's Sidekick</title>
1398
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
1399
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
1400
+ <style>
1401
+ :root { --primary-color: #0F766E; --secondary-color: #1E40AF; }
1402
+ body { font-family: 'Inter', system-ui, sans-serif; background-color: #f8fafc; }
1403
+ .navbar-brand { font-weight: 700; color: var(--primary-color) !important; }
1404
+ .btn-primary { background-color: var(--primary-color); border-color: var(--primary-color); }
1405
+ .btn-primary:hover { background-color: #065f5a; border-color: #065f5a; }
1406
+ .text-primary { color: var(--primary-color) !important; }
1407
+ .bg-primary { background-color: var(--primary-color) !important; }
1408
+ .card { border: none; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
1409
+ .nav-link.active { background-color: var(--primary-color) !important; }
1410
+ </style>
1411
+ </head>
1412
+ <body>
1413
+ <nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
1414
+ <div class="container">
1415
+ <a class="navbar-brand" href="{{ url_for('dashboard') }}">
1416
+ <i class="fas fa-user-md me-2"></i>Doctor's Sidekick
1417
+ </a>
1418
+ <div class="navbar-nav ms-auto">
1419
+ <a class="nav-link" href="{{ url_for('dashboard') }}">
1420
+ <i class="fas fa-tachometer-alt me-1"></i> Dashboard
1421
+ </a>
1422
+ <a class="nav-link active" href="{{ url_for('patients') }}">
1423
+ <i class="fas fa-users me-1"></i> Patients
1424
+ </a>
1425
+ <a class="nav-link" href="{{ url_for('treatment_planner') }}">
1426
+ <i class="fas fa-prescription-bottle-alt me-1"></i> Treatment Planner
1427
+ </a>
1428
+ <a class="nav-link" href="{{ url_for('ai_assistant') }}">
1429
+ <i class="fas fa-brain me-1"></i> AI Assistant
1430
+ </a>
1431
+ <div class="nav-link">
1432
+ <span class="text-muted">{{ current_user.full_name() }}</span>
1433
+ </div>
1434
+ <a class="nav-link" href="{{ url_for('logout') }}">
1435
+ <i class="fas fa-sign-out-alt me-1"></i> Logout
1436
+ </a>
1437
+ </div>
1438
+ </div>
1439
+ </nav>
1440
+
1441
+ <main class="container mt-4">
1442
+ {% with messages = get_flashed_messages(with_categories=true) %}
1443
+ {% if messages %}
1444
+ {% for category, message in messages %}
1445
+ <div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
1446
+ {{ message }}
1447
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
1448
+ </div>
1449
+ {% endfor %}
1450
+ {% endif %}
1451
+ {% endwith %}
1452
+ <div class="d-flex justify-content-between align-items-center mb-4">
1453
+ <h1 class="h3"><i class="fas fa-user-plus me-2 text-primary"></i>New Patient Registration</h1>
1454
+ <a href="{{ url_for('patients') }}" class="btn btn-outline-secondary">
1455
+ <i class="fas fa-arrow-left me-2"></i>Back to Patients
1456
+ </a>
1457
+ </div>
1458
+
1459
+ <div class="row justify-content-center">
1460
+ <div class="col-lg-8">
1461
+ <div class="card">
1462
+ <div class="card-header">
1463
+ <h5><i class="fas fa-user-circle me-2"></i>Patient Information</h5>
1464
+ </div>
1465
+ <div class="card-body">
1466
+ <form method="POST">
1467
+ {{ form.hidden_tag() }}
1468
+ <div class="row g-3">
1469
+ <div class="col-md-6">
1470
+ {{ form.first_name.label(class="form-label") }}
1471
+ {{ form.first_name(class="form-control") }}
1472
+ </div>
1473
+ <div class="col-md-6">
1474
+ {{ form.last_name.label(class="form-label") }}
1475
+ {{ form.last_name(class="form-control") }}
1476
+ </div>
1477
+ <div class="col-md-6">
1478
+ {{ form.date_of_birth.label(class="form-label") }}
1479
+ {{ form.date_of_birth(class="form-control") }}
1480
+ </div>
1481
+ <div class="col-md-6">
1482
+ {{ form.gender.label(class="form-label") }}
1483
+ {{ form.gender(class="form-select") }}
1484
+ </div>
1485
+ <div class="col-md-6">
1486
+ {{ form.phone.label(class="form-label") }}
1487
+ {{ form.phone(class="form-control") }}
1488
+ </div>
1489
+ <div class="col-md-6">
1490
+ {{ form.email.label(class="form-label") }}
1491
+ {{ form.email(class="form-control") }}
1492
+ </div>
1493
+ <div class="col-12">
1494
+ {{ form.address.label(class="form-label") }}
1495
+ {{ form.address(class="form-control", rows="3") }}
1496
+ </div>
1497
+ <div class="col-12">
1498
+ {{ form.allergies.label(class="form-label") }}
1499
+ {{ form.allergies(class="form-control", rows="3", placeholder="List any known allergies or NKDA") }}
1500
+ </div>
1501
+ <div class="col-12">
1502
+ {{ form.medical_history.label(class="form-label") }}
1503
+ {{ form.medical_history(class="form-control", rows="4", placeholder="Past medical history, current medications, etc.") }}
1504
+ </div>
1505
+ </div>
1506
+
1507
+ <div class="d-flex gap-2 mt-4">
1508
+ <button type="submit" class="btn btn-primary">
1509
+ <i class="fas fa-save me-2"></i> Register Patient
1510
+ </button>
1511
+ <a href="{{ url_for('patients') }}" class="btn btn-outline-secondary">
1512
+ <i class="fas fa-times me-2"></i> Cancel
1513
+ </a>
1514
+ </div>
1515
+ </form>
1516
+ </div>
1517
+ </div>
1518
+ </div>
1519
+ </div>
1520
+
1521
+ </main>
1522
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
1523
+ </body>
1524
+ </html>
1525
+ """
1526
+
1527
+ # ============================================================================
1528
+ # ROUTES
1529
+ # ============================================================================
1530
+
1531
+ @app.route('/login', methods=['GET', 'POST'])
1532
+ def login():
1533
+ form = LoginForm()
1534
+ if form.validate_on_submit():
1535
+ user = User.query.filter_by(username=form.username.data).first()
1536
+ if user and user.password_hash and check_password_hash(user.password_hash, form.password.data):
1537
+ login_user(user)
1538
+ return redirect(url_for('dashboard'))
1539
+ flash('Invalid username or password', 'error')
1540
+ return render_template_string(LOGIN_TEMPLATE, form=form)
1541
+
1542
+ @app.route('/logout')
1543
+ @login_required
1544
+ def logout():
1545
+ logout_user()
1546
+ return redirect(url_for('login'))
1547
+
1548
+ @app.route('/')
1549
+ @login_required
1550
+ def dashboard():
1551
+ stats = {
1552
+ 'total_patients': Patient.query.count(),
1553
+ 'visits_today': 0, # Would count visits for today
1554
+ 'treatment_plans': TreatmentPlan.query.count(),
1555
+ }
1556
+ return render_template_string(DASHBOARD_TEMPLATE, stats=stats)
1557
+
1558
+ @app.route('/patients')
1559
+ @login_required
1560
+ def patients():
1561
+ patients = Patient.query.order_by(Patient.last_name, Patient.first_name).all()
1562
+ return render_template_string(PATIENTS_TEMPLATE, patients=patients)
1563
+
1564
+ @app.route('/patients/new', methods=['GET', 'POST'])
1565
+ @login_required
1566
+ def new_patient():
1567
+ form = PatientForm()
1568
+ if form.validate_on_submit():
1569
+ # Generate patient ID
1570
+ patient_count = Patient.query.count()
1571
+ patient_id = f"P{patient_count + 1:04d}"
1572
+
1573
+ patient = Patient(
1574
+ patient_id=patient_id,
1575
+ first_name=form.first_name.data,
1576
+ last_name=form.last_name.data,
1577
+ date_of_birth=form.date_of_birth.data,
1578
+ gender=form.gender.data,
1579
+ phone=form.phone.data,
1580
+ email=form.email.data,
1581
+ address=form.address.data,
1582
+ allergies=form.allergies.data,
1583
+ medical_history=form.medical_history.data,
1584
+ doctor_id=current_user.id
1585
+ )
1586
+ db.session.add(patient)
1587
+ db.session.commit()
1588
+
1589
+ flash(f'Patient {patient.full_name()} registered successfully!', 'success')
1590
+ return redirect(url_for('patients'))
1591
+
1592
+ return render_template_string(NEW_PATIENT_TEMPLATE, form=form)
1593
+
1594
+ @app.route('/patients/<int:id>')
1595
+ @login_required
1596
+ def patient_detail(id):
1597
+ patient = Patient.query.get_or_404(id)
1598
+ return render_template_string("""
1599
+ {% extends "base.html" %}
1600
+ {% block content %}
1601
+ <h1>{{ patient.full_name() }}</h1>
1602
+ <div class="card">
1603
+ <div class="card-body">
1604
+ <p><strong>Patient ID:</strong> {{ patient.patient_id }}</p>
1605
+ <p><strong>Age:</strong> {{ patient.age() }}</p>
1606
+ <p><strong>Gender:</strong> {{ patient.gender or 'N/A' }}</p>
1607
+ <p><strong>Phone:</strong> {{ patient.phone or 'N/A' }}</p>
1608
+ <p><strong>Email:</strong> {{ patient.email or 'N/A' }}</p>
1609
+ <p><strong>Allergies:</strong> {{ patient.allergies or 'None known' }}</p>
1610
+ <p><strong>Medical History:</strong> {{ patient.medical_history or 'None provided' }}</p>
1611
+ </div>
1612
+ </div>
1613
+ {% endblock %}
1614
+ """, patient=patient)
1615
+
1616
+ @app.route('/patients/<int:id>/edit')
1617
+ @login_required
1618
+ def edit_patient(id):
1619
+ patient = Patient.query.get_or_404(id)
1620
+ form = PatientForm(obj=patient)
1621
+ return render_template_string(NEW_PATIENT_TEMPLATE, form=form, patient=patient)
1622
+
1623
+ @app.route('/treatment-planner', methods=['GET', 'POST'])
1624
+ @login_required
1625
+ def treatment_planner():
1626
+ form = TreatmentPlanForm()
1627
+ patients = Patient.query.order_by(Patient.last_name, Patient.first_name).all()
1628
+ conditions = Condition.query.all()
1629
+
1630
+ if form.validate_on_submit():
1631
+ patient_id = request.form.get('patient_id')
1632
+ if not patient_id:
1633
+ flash('Please select a patient', 'error')
1634
+ else:
1635
+ treatment_plan = TreatmentPlan(
1636
+ patient_id=patient_id,
1637
+ diagnosis=form.diagnosis.data,
1638
+ medications=form.medications.data,
1639
+ instructions=form.instructions.data,
1640
+ duration=form.duration.data,
1641
+ follow_up_date=form.follow_up_date.data,
1642
+ notes=form.notes.data,
1643
+ created_by_id=current_user.id
1644
+ )
1645
+ db.session.add(treatment_plan)
1646
+ db.session.commit()
1647
+
1648
+ flash('Treatment plan created successfully!', 'success')
1649
+ return redirect(url_for('treatment_planner'))
1650
+
1651
+ return render_template_string(TREATMENT_PLANNER_TEMPLATE,
1652
+ form=form, patients=patients, conditions=conditions)
1653
+
1654
+ @app.route('/ai-assistant')
1655
+ @login_required
1656
+ def ai_assistant():
1657
+ patients = Patient.query.order_by(Patient.last_name, Patient.first_name).all()
1658
+ return render_template_string(AI_ASSISTANT_TEMPLATE, patients=patients)
1659
+
1660
+ @app.route('/api/ai/analyze-symptoms', methods=['POST'])
1661
+ @login_required
1662
+ def ai_analyze_symptoms():
1663
+ try:
1664
+ data = request.get_json()
1665
+ patient = Patient.query.get_or_404(data['patient_id'])
1666
+
1667
+ # Prepare patient data for AI analysis
1668
+ patient_data = {
1669
+ 'age': patient.age(),
1670
+ 'gender': patient.gender or 'Not specified',
1671
+ 'medical_history': patient.medical_history or 'None provided',
1672
+ 'allergies': patient.allergies or 'None known'
1673
+ }
1674
+
1675
+ # Get AI recommendations
1676
+ treatment_plan = medical_ai.generate_treatment_plan(
1677
+ patient_data,
1678
+ data['symptoms'],
1679
+ data.get('suspected_diagnosis')
1680
+ )
1681
+
1682
+ # Check for drug interactions if medications are mentioned
1683
+ medications = []
1684
+ if 'medications' in treatment_plan:
1685
+ medications = [med.strip() for med in str(treatment_plan.get('medications', '')).split('\n') if med.strip()]
1686
+
1687
+ drug_interactions = medical_ai.check_drug_interactions(medications, patient.allergies or '')
1688
+
1689
+ return jsonify({
1690
+ 'success': True,
1691
+ 'treatment_plan': treatment_plan,
1692
+ 'differential_diagnosis': treatment_plan.get('differential_diagnosis', []),
1693
+ 'drug_interactions': drug_interactions
1694
+ })
1695
+
1696
+ except Exception as e:
1697
+ app.logger.error(f"AI analysis error: {str(e)}")
1698
+ return jsonify({
1699
+ 'success': False,
1700
+ 'error': 'AI analysis service temporarily unavailable'
1701
+ }), 500
1702
+
1703
+ @app.route('/api/ai/smart-templates/<int:condition_id>')
1704
+ @login_required
1705
+ def ai_smart_templates(condition_id):
1706
+ try:
1707
+ condition = Condition.query.get_or_404(condition_id)
1708
+ patient_id = request.args.get('patient_id')
1709
+
1710
+ patient_demographics = {}
1711
+ if patient_id:
1712
+ patient = Patient.query.get(patient_id)
1713
+ if patient:
1714
+ patient_demographics = {
1715
+ 'age': patient.age(),
1716
+ 'gender': patient.gender,
1717
+ 'medical_history': patient.medical_history,
1718
+ 'allergies': patient.allergies
1719
+ }
1720
+
1721
+ smart_templates = medical_ai.smart_template_suggestions(
1722
+ condition.name,
1723
+ patient_demographics
1724
+ )
1725
+
1726
+ return jsonify({
1727
+ 'success': True,
1728
+ 'templates': smart_templates
1729
+ })
1730
+
1731
+ except Exception as e:
1732
+ app.logger.error(f"Smart template error: {str(e)}")
1733
+ return jsonify({
1734
+ 'success': False,
1735
+ 'error': 'Smart template service unavailable'
1736
+ }), 500
1737
+
1738
+ def init_database():
1739
+ """Initialize database with sample data"""
1740
+ with app.app_context():
1741
+ # Create tables
1742
+ db.create_all()
1743
+
1744
+ # Check if data already exists
1745
+ if User.query.first():
1746
+ return
1747
+
1748
+ # Create roles
1749
+ admin_role = Role(name='Admin', description='Administrator with full access')
1750
+ doctor_role = Role(name='Doctor', description='Medical doctor with patient access')
1751
+
1752
+ db.session.add(admin_role)
1753
+ db.session.add(doctor_role)
1754
+ db.session.commit()
1755
+
1756
+ # Create admin user
1757
+ admin_user = User(
1758
+ username='admin',
1759
+ email='admin@example.com',
1760
+ password_hash=generate_password_hash('admin'),
1761
+ first_name='Admin',
1762
+ last_name='User',
1763
+ role_id=admin_role.id
1764
+ )
1765
+
1766
+ db.session.add(admin_user)
1767
+ db.session.commit()
1768
+
1769
+ # Create sample conditions
1770
+ conditions = [
1771
+ Condition(name='Acne Vulgaris', description='Common inflammatory skin condition', icd_code='L70.0', category='Inflammatory'),
1772
+ Condition(name='Atopic Dermatitis', description='Chronic inflammatory skin disease', icd_code='L20.9', category='Inflammatory'),
1773
+ Condition(name='Contact Dermatitis', description='Skin inflammation from contact with allergens', icd_code='L23.9', category='Inflammatory'),
1774
+ Condition(name='Psoriasis', description='Chronic autoimmune skin condition', icd_code='L40.9', category='Autoimmune'),
1775
+ Condition(name='Seborrheic Dermatitis', description='Inflammatory condition of sebaceous areas', icd_code='L21.9', category='Inflammatory')
1776
+ ]
1777
+
1778
+ for condition in conditions:
1779
+ db.session.add(condition)
1780
+
1781
+ db.session.commit()
1782
+
1783
+ print("Database initialized with sample data!")
1784
+
1785
+ # ============================================================================
1786
+ # MAIN APPLICATION
1787
+ # ============================================================================
1788
+
1789
+ if __name__ == '__main__':
1790
+ # Initialize database
1791
+ init_database()
1792
+
1793
+ # Create uploads directory
1794
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
1795
+
1796
+ # Get port from environment or default to 5000
1797
+ port = int(os.environ.get('PORT', 5000))
1798
+
1799
+ # Run the application
1800
+ app.run(host='0.0.0.0', port=port, debug=True)
hf_requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ Flask==2.3.3
2
+ Flask-SQLAlchemy==3.0.5
3
+ Flask-Login==0.6.3
4
+ Flask-WTF==1.1.1
5
+ WTForms==3.0.1
6
+ Werkzeug==2.3.7
7
+ email-validator==2.0.0
8
+ gunicorn==21.2.0