SmartHeal commited on
Commit
00c982c
·
verified ·
1 Parent(s): 781b858

Upload 16 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ src/eHealth[[:space:]]in[[:space:]]Wound[[:space:]]Care.pdf filter=lfs diff=lfs merge=lfs -text
37
+ src/evaluation.pdf filter=lfs diff=lfs merge=lfs -text
38
+ src/IWGDF[[:space:]]Guideline.pdf filter=lfs diff=lfs merge=lfs -text
enhanced_app.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import sys
5
+ import logging
6
+ import traceback
7
+ import gradio as gr
8
+ from datetime import datetime
9
+
10
+ # Add src directory to path
11
+ sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
12
+
13
+ # Import original modules (copy from original bot)
14
+ from src.config import Config
15
+ from src.auth import AuthManager
16
+
17
+ # Import enhanced modules
18
+ from src.dashboard_database_manager import DashboardDatabaseManager
19
+ from src.enhanced_ai_processor import EnhancedAIProcessor
20
+ from src.enhanced_ui_components import EnhancedUIComponents
21
+
22
+ # Logging setup
23
+ logging.basicConfig(
24
+ level=logging.INFO,
25
+ format='%(asctime)s - %(levelname)s - %(message)s',
26
+ handlers=[
27
+ logging.FileHandler('smartheal_enhanced.log'),
28
+ logging.StreamHandler()
29
+ ]
30
+ )
31
+
32
+ class EnhancedSmartHealApp:
33
+ """Enhanced SmartHeal Application with Dashboard Integration"""
34
+
35
+ def __init__(self):
36
+ self.ui_components = None
37
+ try:
38
+ logging.info("🚀 Initializing Enhanced SmartHeal App...")
39
+
40
+ # Initialize configuration
41
+ self.config = Config()
42
+ logging.info("✅ Configuration loaded")
43
+
44
+ # Initialize enhanced database manager
45
+ self.database_manager = DashboardDatabaseManager(self.config.get_mysql_config())
46
+ logging.info("✅ Enhanced database manager initialized")
47
+
48
+ # Initialize authentication manager
49
+ self.auth_manager = AuthManager(self.database_manager)
50
+ logging.info("✅ Authentication manager initialized")
51
+
52
+ # Initialize enhanced AI processor
53
+ self.ai_processor = EnhancedAIProcessor()
54
+ logging.info("✅ Enhanced AI processor initialized")
55
+
56
+ # Initialize enhanced UI components
57
+ self.ui_components = EnhancedUIComponents(
58
+ self.auth_manager,
59
+ self.database_manager,
60
+ self.ai_processor
61
+ )
62
+ logging.info("✅ Enhanced UI components initialized")
63
+
64
+ # Create database tables if they don't exist
65
+ self._ensure_database_tables()
66
+
67
+ logging.info("🎉 Enhanced SmartHeal App initialized successfully!")
68
+
69
+ except Exception as e:
70
+ logging.error(f"❌ Initialization error: {e}")
71
+ traceback.print_exc()
72
+ raise
73
+
74
+ def _ensure_database_tables(self):
75
+ """Ensure all required database tables exist"""
76
+ try:
77
+ # Check if dashboard tables exist, if not create them
78
+ tables_to_check = [
79
+ 'ai_analyses',
80
+ 'analysis_sessions',
81
+ 'bot_interactions',
82
+ 'questionnaire_responses',
83
+ 'wound_images'
84
+ ]
85
+
86
+ for table in tables_to_check:
87
+ result = self.database_manager.execute_query_one(f"SHOW TABLES LIKE '{table}'")
88
+ if not result:
89
+ logging.warning(f"Table '{table}' not found in database")
90
+ else:
91
+ logging.info(f"✅ Table '{table}' exists")
92
+
93
+ logging.info("✅ Database table check completed")
94
+
95
+ except Exception as e:
96
+ logging.error(f"❌ Error checking database tables: {e}")
97
+
98
+ def launch(self, port=7860, share=True, server_name="0.0.0.0"):
99
+ """Launch the enhanced application"""
100
+ try:
101
+ logging.info(f"🌐 Launching Enhanced SmartHeal App on {server_name}:{port}")
102
+
103
+ # Create the interface
104
+ interface = self.ui_components.create_interface()
105
+
106
+ # Launch with enhanced configuration
107
+ interface.launch(
108
+ server_name=server_name,
109
+ server_port=port,
110
+ share=share,
111
+ show_error=True,
112
+ quiet=False,
113
+ favicon_path="favicon.ico" if os.path.exists("favicon.ico") else None
114
+ )
115
+
116
+ except Exception as e:
117
+ logging.error(f"❌ Error launching application: {e}")
118
+ raise
119
+
120
+ def get_status(self):
121
+ """Get application status"""
122
+ try:
123
+ status = {
124
+ 'app_initialized': self.ui_components is not None,
125
+ 'database_connected': self.database_manager.get_connection() is not None,
126
+ 'ai_models_loaded': len(self.ai_processor.models_cache) > 0,
127
+ 'dashboard_integration': self.ui_components.dashboard_integration.get_integration_status() if self.ui_components else {},
128
+ 'timestamp': datetime.now().isoformat()
129
+ }
130
+
131
+ return status
132
+
133
+ except Exception as e:
134
+ logging.error(f"Error getting status: {e}")
135
+ return {'error': str(e), 'timestamp': datetime.now().isoformat()}
136
+
137
+ def main():
138
+ """Main application entry point"""
139
+ try:
140
+ print("=" * 60)
141
+ print("🏥 SmartHeal AI - Enhanced Edition")
142
+ print("Advanced Wound Care Analysis with Dashboard Integration")
143
+ print("=" * 60)
144
+
145
+ # Initialize and launch the app
146
+ app = EnhancedSmartHealApp()
147
+
148
+ # Print status
149
+ status = app.get_status()
150
+ print("\n📊 Application Status:")
151
+ print(f" • App Initialized: {status.get('app_initialized', False)}")
152
+ print(f" • Database Connected: {status.get('database_connected', False)}")
153
+ print(f" • AI Models Loaded: {status.get('ai_models_loaded', False)}")
154
+
155
+ dashboard_status = status.get('dashboard_integration', {})
156
+ print(f" • Dashboard API: {dashboard_status.get('api_running', False)}")
157
+ print(f" • Dashboard DB: {dashboard_status.get('database_connected', False)}")
158
+
159
+ print("\n🚀 Starting application...")
160
+ print("📱 Access the application at: http://localhost:7860")
161
+ print("📊 Dashboard API available at: http://localhost:5001")
162
+ print("📈 Dashboard Integration: Real-time analytics enabled")
163
+ print("\n⚠️ Press Ctrl+C to stop the application")
164
+ print("=" * 60)
165
+
166
+ # Launch the application
167
+ app.launch()
168
+
169
+ except KeyboardInterrupt:
170
+ print("\n\n👋 Application interrupted by user.")
171
+ logging.info("Application interrupted by user")
172
+ except Exception as e:
173
+ print(f"\n❌ Application failed to start: {e}")
174
+ logging.error(f"Application failed to start: {e}")
175
+ traceback.print_exc()
176
+ raise
177
+
178
+ if __name__ == "__main__":
179
+ main()
180
+
favicon.ico ADDED
requirements.txt ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Enhanced SmartHeal Bot Requirements
2
+ # --- Core Python Packages ---
3
+ numpy
4
+ pillow
5
+ opencv-python
6
+ matplotlib
7
+ requests
8
+ tqdm
9
+ pypdf
10
+
11
+ # --- Deep Learning ---
12
+ torch>=2.0.0
13
+ tensorflow
14
+ transformers>=4.40.0
15
+ accelerate
16
+ bitsandbytes
17
+ tf-keras
18
+
19
+ # --- Hugging Face ---
20
+ huggingface-hub
21
+ sentence-transformers
22
+
23
+ # --- LangChain & RAG ---
24
+ langchain>=0.1.14
25
+ faiss-cpu
26
+ pymupdf
27
+ langchain-community
28
+
29
+ # --- YOLO Detection ---
30
+ ultralytics
31
+
32
+ # --- Gradio UI ---
33
+ gradio>=4.28.0
34
+ spaces
35
+
36
+ # --- Logging & Utility ---
37
+ python-dotenv
38
+
39
+ # --- Database ---
40
+ mysql-connector-python>=8.0.0
41
+
42
+ # --- Enhanced Dashboard Integration ---
43
+ flask>=2.3.0
44
+ flask-cors>=4.0.0
45
+ flask-login>=0.6.0
46
+ sqlalchemy>=1.4.0
47
+ werkzeug>=2.3.0
48
+ bcrypt>=4.0.0
49
+
50
+ # --- Additional Analytics ---
51
+ pandas>=1.5.0
52
+ scikit-learn>=1.1.0
53
+ seaborn>=0.11.0
54
+
55
+ # --- Development Tools ---
56
+ pytest>=7.0.0
57
+ black>=22.0.0
src/IWGDF Guideline.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:da4378da81d438142a096c37f21bd47270b44fd13e9177732ee5a1b02c0a8703
3
+ size 1032231
src/auth.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from datetime import datetime
3
+ import hashlib
4
+
5
+ class AuthManager:
6
+ """Authentication and user management"""
7
+
8
+ def __init__(self, db_manager):
9
+ """Initialize auth manager with database manager"""
10
+ self.db = db_manager
11
+
12
+ def hash_password(self, password):
13
+ """Simple password hashing (in production, use bcrypt or similar)"""
14
+ return hashlib.sha256(password.encode()).hexdigest()
15
+
16
+ def authenticate_user(self, username, password):
17
+ """Authenticate user and return user data"""
18
+ try:
19
+ user = self.db.execute_query_one(
20
+ "SELECT id, username, email, name, role, org FROM users WHERE username = %s",
21
+ (username,)
22
+ )
23
+
24
+ if not user:
25
+ logging.warning(f"User not found: {username}")
26
+ return None
27
+
28
+ user_password = self.db.execute_query_one(
29
+ "SELECT password FROM users WHERE username = %s",
30
+ (username,)
31
+ )
32
+
33
+ if not user_password:
34
+ logging.warning(f"Password not found for user: {username}")
35
+ return None
36
+
37
+ stored_password = user_password['password']
38
+
39
+ # Check if stored password is hashed (64 chars) or plain text
40
+ is_hashed = len(stored_password) == 64 and all(c in '0123456789abcdef' for c in stored_password)
41
+
42
+ if is_hashed:
43
+ # Compare with hashed input
44
+ password_match = stored_password == self.hash_password(password)
45
+ else:
46
+ # Compare with plain text (for legacy compatibility)
47
+ password_match = stored_password == password
48
+
49
+ logging.info(f"Debug - Username: {username}, Password type: {'hashed' if is_hashed else 'plain'}")
50
+
51
+ if not password_match:
52
+ logging.warning(f"Invalid password for user: {username}")
53
+ return None
54
+
55
+ # Update last login (use updated_at since last_login column may not exist)
56
+ try:
57
+ self.db.execute_query(
58
+ "UPDATE users SET updated_at = %s WHERE id = %s",
59
+ (datetime.now(), user['id'])
60
+ )
61
+ except Exception as e:
62
+ logging.warning(f"Could not update last login: {e}")
63
+
64
+ logging.info(f"User authenticated successfully: {username}")
65
+ return user
66
+
67
+ except Exception as e:
68
+ logging.error(f"Authentication error: {e}")
69
+ return None
70
+
71
+ def get_user_data(self, username):
72
+ """Get user data by username - for compatibility with UI"""
73
+ try:
74
+ user = self.db.execute_query_one(
75
+ "SELECT id, username, email, name, role, org FROM users WHERE username = %s",
76
+ (username,)
77
+ )
78
+
79
+ if user:
80
+ logging.info(f"Retrieved user data for: {username}")
81
+ return user
82
+ else:
83
+ logging.warning(f"User not found: {username}")
84
+ return None
85
+
86
+ except Exception as e:
87
+ logging.error(f"Error getting user data: {e}")
88
+ return None
89
+
90
+ def create_user(self, username, email, password, name, role, org_name="",
91
+ phone="", country_code="", department="", location="", organization_id=None):
92
+ """Create a new user account"""
93
+ try:
94
+ # Check if user already exists
95
+ existing_user = self.db.execute_query_one(
96
+ "SELECT id FROM users WHERE username = %s OR email = %s",
97
+ (username, email)
98
+ )
99
+
100
+ if existing_user:
101
+ logging.warning(f"User already exists: {username} or {email}")
102
+ return False
103
+
104
+ # Create organization if role is organization
105
+ if role == 'organization':
106
+ org_result = self.db.execute_query(
107
+ """INSERT INTO organizations (name, email, phone, country_code, department, location, created_at)
108
+ VALUES (%s, %s, %s, %s, %s, %s, %s)""",
109
+ (org_name or name, email, phone, country_code, department, location, datetime.now())
110
+ )
111
+
112
+ if not org_result:
113
+ logging.error("Failed to create organization")
114
+ return False
115
+
116
+ # Get the organization ID
117
+ org_id = self.db.execute_query_one(
118
+ "SELECT id FROM organizations WHERE email = %s ORDER BY created_at DESC LIMIT 1",
119
+ (email,)
120
+ )
121
+ organization_id = org_id['id'] if org_id else None
122
+
123
+ # Create user with hashed password
124
+ hashed_password = self.hash_password(password)
125
+ user_result = self.db.execute_query(
126
+ """INSERT INTO users (username, email, password, name, role, org, created_at)
127
+ VALUES (%s, %s, %s, %s, %s, %s, %s)""",
128
+ (username, email, hashed_password, name, role, organization_id, datetime.now())
129
+ )
130
+
131
+ if user_result:
132
+ logging.info(f"User created successfully: {username}")
133
+ return True
134
+ else:
135
+ logging.error(f"Failed to create user: {username}")
136
+ return False
137
+
138
+ except Exception as e:
139
+ logging.error(f"User creation error: {e}")
140
+ return False
141
+
142
+ def get_user_by_id(self, user_id):
143
+ """Get user by ID"""
144
+ try:
145
+ user = self.db.execute_query_one(
146
+ "SELECT id, username, email, name, role, org FROM users WHERE id = %s",
147
+ (user_id,)
148
+ )
149
+ return user
150
+ except Exception as e:
151
+ logging.error(f"Error fetching user by ID: {e}")
152
+ return None
153
+
154
+ def update_user_last_login(self, user_id):
155
+ """Update user's last login timestamp"""
156
+ try:
157
+ result = self.db.execute_query(
158
+ "UPDATE users SET updated_at = %s WHERE id = %s",
159
+ (datetime.now(), user_id)
160
+ )
161
+ return bool(result)
162
+ except Exception as e:
163
+ logging.error(f"Error updating last login: {e}")
164
+ return False
src/auth_manager.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from datetime import datetime
3
+ from src.database_manager import DatabaseManager
4
+
5
+ class AuthManager:
6
+ def __init__(self):
7
+ self.db_manager = DatabaseManager()
8
+
9
+ def authenticate_user(self, username, password):
10
+ """Authenticate user and return user data"""
11
+ try:
12
+ # Find user by username
13
+ user = self.db_manager.execute_query_one(
14
+ "SELECT id, username, email, name, role, org FROM users WHERE username = %s",
15
+ (username,)
16
+ )
17
+
18
+ if not user:
19
+ return None
20
+
21
+ # Check password (plaintext comparison as per requirements)
22
+ user_password = self.db_manager.execute_query_one(
23
+ "SELECT password FROM users WHERE username = %s",
24
+ (username,)
25
+ )
26
+
27
+ if not user_password or user_password['password'] != password:
28
+ return None
29
+
30
+ # Update last login
31
+ self.db_manager.execute_query(
32
+ "UPDATE users SET last_login = %s WHERE id = %s",
33
+ (datetime.now(), user['id'])
34
+ )
35
+
36
+ return user
37
+
38
+ except Exception as e:
39
+ logging.error(f"Authentication error: {e}")
40
+ return None
41
+
42
+ def create_user(self, username, email, password, name, role, org_name="", phone="",
43
+ country_code="", department="", location="", organization_id=None):
44
+ """Create a new user account"""
45
+ try:
46
+ # Check if username or email already exists
47
+ existing_user = self.db_manager.execute_query_one(
48
+ "SELECT id FROM users WHERE username = %s OR email = %s",
49
+ (username, email)
50
+ )
51
+
52
+ if existing_user:
53
+ return False
54
+
55
+ # Handle organization creation
56
+ if role == 'organization':
57
+ # Create organization entry first
58
+ org_result = self.db_manager.execute_query(
59
+ """INSERT INTO organizations (name, email, phone, country_code, department, location, created_at)
60
+ VALUES (%s, %s, %s, %s, %s, %s, %s)""",
61
+ (org_name or name, email, phone, country_code, department, location, datetime.now())
62
+ )
63
+
64
+ if not org_result:
65
+ return False
66
+
67
+ # Get the organization ID
68
+ org_id = self.db_manager.execute_query_one(
69
+ "SELECT id FROM organizations WHERE email = %s ORDER BY created_at DESC LIMIT 1",
70
+ (email,)
71
+ )
72
+ organization_id = org_id['id'] if org_id else None
73
+
74
+ # Create user entry
75
+ user_result = self.db_manager.execute_query(
76
+ """INSERT INTO users (username, email, password, name, role, org, created_at)
77
+ VALUES (%s, %s, %s, %s, %s, %s, %s)""",
78
+ (username, email, password, name, role, organization_id, datetime.now())
79
+ )
80
+
81
+ return bool(user_result)
82
+
83
+ except Exception as e:
84
+ logging.error(f"User creation error: {e}")
85
+ return False
86
+
87
+ def get_organizations(self):
88
+ """Get list of all organizations"""
89
+ try:
90
+ organizations = self.db_manager.execute_query(
91
+ "SELECT id, name FROM organizations ORDER BY name",
92
+ fetch=True
93
+ )
94
+ return organizations or []
95
+ except Exception as e:
96
+ logging.error(f"Error fetching organizations: {e}")
97
+ return []
98
+
99
+ def get_organization_practitioners(self, organization_id):
100
+ """Get practitioners for an organization"""
101
+ try:
102
+ practitioners = self.db_manager.execute_query(
103
+ """SELECT id, username, name, email, created_at, last_login
104
+ FROM users WHERE org = %s AND role = 'practitioner'
105
+ ORDER BY created_at DESC""",
106
+ (organization_id,),
107
+ fetch=True
108
+ )
109
+ return practitioners or []
110
+ except Exception as e:
111
+ logging.error(f"Error fetching practitioners: {e}")
112
+ return []
113
+
114
+ def update_user_profile(self, user_id, name=None, email=None):
115
+ """Update user profile information"""
116
+ try:
117
+ if name:
118
+ self.db_manager.execute_query(
119
+ "UPDATE users SET name = %s WHERE id = %s",
120
+ (name, user_id)
121
+ )
122
+
123
+ if email:
124
+ # Check if email is already taken by another user
125
+ existing = self.db_manager.execute_query_one(
126
+ "SELECT id FROM users WHERE email = %s AND id != %s",
127
+ (email, user_id)
128
+ )
129
+
130
+ if not existing:
131
+ self.db_manager.execute_query(
132
+ "UPDATE users SET email = %s WHERE id = %s",
133
+ (email, user_id)
134
+ )
135
+ return True
136
+ else:
137
+ return False
138
+
139
+ return True
140
+
141
+ except Exception as e:
142
+ logging.error(f"Error updating user profile: {e}")
143
+ return False
src/best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dc7b01e2bf4e70eca6321059de6f0965612ad5cca55154e8adb0ea15c77e350d
3
+ size 136689065
src/config.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from pathlib import Path
4
+
5
+ class Config:
6
+ """Configuration management for SmartHeal application"""
7
+
8
+ def __init__(self):
9
+ """Initialize configuration with environment variables and defaults"""
10
+
11
+ # Database configuration
12
+ self.MYSQL_CONFIG = {
13
+ "host": os.getenv("MYSQL_HOST", "sg-nme-web545.main-hosting.eu"),
14
+ "user": os.getenv("MYSQL_USER", "u124249738_SmartHealApp"),
15
+ "password": os.getenv("MYSQL_PASSWORD", "I^4y1b12y"),
16
+ "database": os.getenv("MYSQL_DATABASE", "u124249738_SmartHealAppDB"),
17
+ "port": int(os.getenv("MYSQL_PORT", 3306))
18
+ }
19
+
20
+ # Application directories
21
+ self.UPLOADS_DIR = os.getenv("UPLOADS_DIR", "uploads")
22
+ self.MODELS_DIR = os.getenv("MODELS_DIR", "models")
23
+
24
+ # AI/ML configuration
25
+ self.HF_TOKEN = os.getenv("HF_TOKEN")
26
+ self.OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
27
+
28
+ # AI Model Configuration
29
+ # AI Model Configuration
30
+ self.YOLO_MODEL_PATH = os.getenv("YOLO_MODEL_PATH", "src/best.pt")
31
+ self.SEG_MODEL_PATH = os.getenv("SEG_MODEL_PATH", "src/segmentation_model.h5")
32
+
33
+ self.MEDGEMMA_MODEL = os.getenv("MEDGEMMA_MODEL", "google/medgemma-4b-it")
34
+ self.WOUND_CLASSIFICATION_MODEL = os.getenv("WOUND_CLASSIFICATION_MODEL", "Hemg/Wound-classification")
35
+ self.EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
36
+
37
+ # AI Processing Configuration
38
+ self.PIXELS_PER_CM = int(os.getenv("PIXELS_PER_CM", "37"))
39
+ self.MAX_NEW_TOKENS = int(os.getenv("MAX_NEW_TOKENS", "512"))
40
+ self.DATASET_ID = os.getenv("DATASET_ID", "")
41
+ self.GUIDELINE_PDFS = [
42
+ os.path.join("src", "eHealth in Wound Care.pdf"),
43
+ os.path.join("src", "IWGDF Guideline.pdf"),
44
+ os.path.join("src", "evaluation.pdf")
45
+ ]
46
+
47
+
48
+ # Application settings
49
+ self.DEBUG = os.getenv("DEBUG", "False").lower() == "true"
50
+ self.LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
51
+
52
+ # Create required directories
53
+ self._create_directories()
54
+
55
+ logging.info("✅ Configuration initialized")
56
+
57
+ def _create_directories(self):
58
+ """Create required directories if they don't exist"""
59
+ directories = [self.UPLOADS_DIR, self.MODELS_DIR]
60
+
61
+ for directory in directories:
62
+ if not os.path.exists(directory):
63
+ os.makedirs(directory, exist_ok=True)
64
+ logging.info(f"Created directory: {directory}")
65
+
66
+ def get_mysql_config(self):
67
+ """Get MySQL configuration dictionary"""
68
+ return self.MYSQL_CONFIG.copy()
69
+
70
+ def get_upload_path(self, filename):
71
+ """Get full path for uploaded file"""
72
+ return os.path.join(self.UPLOADS_DIR, filename)
73
+
74
+ def get_model_path(self, model_name):
75
+ """Get full path for model file"""
76
+ return os.path.join(self.MODELS_DIR, model_name)
src/dashboard_api.py ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, jsonify, request
2
+ from flask_cors import CORS
3
+ import logging
4
+ from datetime import datetime, timedelta
5
+ import json
6
+ from typing import Dict, Any, List, Optional
7
+ import threading
8
+ import time
9
+
10
+ from .dashboard_database_manager import DashboardDatabaseManager
11
+
12
+ class DashboardAPI:
13
+ """API layer for dashboard integration"""
14
+
15
+ def __init__(self, database_manager: DashboardDatabaseManager, port: int = 5001):
16
+ self.database_manager = database_manager
17
+ self.port = port
18
+ self.app = Flask(__name__)
19
+ CORS(self.app) # Enable CORS for all routes
20
+
21
+ # Configure logging
22
+ logging.basicConfig(level=logging.INFO)
23
+ self.logger = logging.getLogger(__name__)
24
+
25
+ # Setup routes
26
+ self._setup_routes()
27
+
28
+ # Background thread for API server
29
+ self.api_thread = None
30
+ self.running = False
31
+
32
+ def _setup_routes(self):
33
+ """Setup API routes for dashboard integration"""
34
+
35
+ @self.app.route('/api/health', methods=['GET'])
36
+ def health_check():
37
+ """Health check endpoint"""
38
+ return jsonify({
39
+ 'status': 'healthy',
40
+ 'timestamp': datetime.now().isoformat(),
41
+ 'service': 'SmartHeal Bot API'
42
+ })
43
+
44
+ @self.app.route('/api/bot/analytics', methods=['GET'])
45
+ def get_bot_analytics():
46
+ """Get comprehensive bot analytics for dashboard"""
47
+ try:
48
+ analytics_data = self.database_manager.get_analytics_data()
49
+
50
+ # Add trend data for charts
51
+ analytics_data.update(self._get_trend_data())
52
+
53
+ return jsonify({
54
+ 'success': True,
55
+ 'data': analytics_data,
56
+ 'timestamp': datetime.now().isoformat()
57
+ })
58
+
59
+ except Exception as e:
60
+ self.logger.error(f"Error getting bot analytics: {e}")
61
+ return jsonify({
62
+ 'success': False,
63
+ 'error': str(e),
64
+ 'timestamp': datetime.now().isoformat()
65
+ }), 500
66
+
67
+ @self.app.route('/api/bot/analytics/details/<int:analysis_id>', methods=['GET'])
68
+ def get_analysis_details(analysis_id):
69
+ """Get detailed analysis information"""
70
+ try:
71
+ query = "SELECT * FROM ai_analyses WHERE id = %s"
72
+ analysis = self.database_manager.execute_query_one(query, (analysis_id,))
73
+
74
+ if not analysis:
75
+ return jsonify({
76
+ 'success': False,
77
+ 'error': 'Analysis not found'
78
+ }), 404
79
+
80
+ # Convert datetime objects to strings for JSON serialization
81
+ if analysis.get('created_at'):
82
+ analysis['created_at'] = analysis['created_at'].isoformat()
83
+
84
+ return jsonify({
85
+ 'success': True,
86
+ 'data': analysis,
87
+ 'timestamp': datetime.now().isoformat()
88
+ })
89
+
90
+ except Exception as e:
91
+ self.logger.error(f"Error getting analysis details: {e}")
92
+ return jsonify({
93
+ 'success': False,
94
+ 'error': str(e)
95
+ }), 500
96
+
97
+ @self.app.route('/api/bot/interactions', methods=['GET'])
98
+ def get_bot_interactions():
99
+ """Get bot interaction history"""
100
+ try:
101
+ limit = request.args.get('limit', 50, type=int)
102
+ interactions = self.database_manager.get_interaction_history(limit)
103
+
104
+ # Convert datetime objects for JSON serialization
105
+ for interaction in interactions:
106
+ if interaction.get('interacted_at'):
107
+ interaction['interacted_at'] = interaction['interacted_at'].isoformat()
108
+
109
+ return jsonify({
110
+ 'success': True,
111
+ 'data': interactions,
112
+ 'count': len(interactions),
113
+ 'timestamp': datetime.now().isoformat()
114
+ })
115
+
116
+ except Exception as e:
117
+ self.logger.error(f"Error getting bot interactions: {e}")
118
+ return jsonify({
119
+ 'success': False,
120
+ 'error': str(e)
121
+ }), 500
122
+
123
+ @self.app.route('/api/bot/sessions', methods=['GET'])
124
+ def get_session_analytics():
125
+ """Get session analytics"""
126
+ try:
127
+ session_data = self.database_manager.get_session_analytics()
128
+
129
+ return jsonify({
130
+ 'success': True,
131
+ 'data': session_data,
132
+ 'timestamp': datetime.now().isoformat()
133
+ })
134
+
135
+ except Exception as e:
136
+ self.logger.error(f"Error getting session analytics: {e}")
137
+ return jsonify({
138
+ 'success': False,
139
+ 'error': str(e)
140
+ }), 500
141
+
142
+ @self.app.route('/api/bot/stats/summary', methods=['GET'])
143
+ def get_summary_stats():
144
+ """Get summary statistics for dashboard widgets"""
145
+ try:
146
+ # Get basic counts
147
+ total_analyses = self.database_manager.execute_query_one("SELECT COUNT(*) as count FROM ai_analyses")
148
+ total_patients = self.database_manager.execute_query_one("SELECT COUNT(DISTINCT patient_id) as count FROM bot_interactions WHERE patient_id IS NOT NULL")
149
+ total_sessions = self.database_manager.execute_query_one("SELECT COUNT(*) as count FROM analysis_sessions")
150
+
151
+ # Get today's activity
152
+ today_analyses = self.database_manager.execute_query_one("""
153
+ SELECT COUNT(*) as count FROM ai_analyses
154
+ WHERE DATE(created_at) = CURDATE()
155
+ """)
156
+
157
+ # Get average metrics
158
+ avg_processing_time = self.database_manager.execute_query_one("""
159
+ SELECT AVG(processing_time) as avg_time FROM ai_analyses
160
+ WHERE processing_time IS NOT NULL
161
+ """)
162
+
163
+ avg_risk_score = self.database_manager.execute_query_one("""
164
+ SELECT AVG(risk_score) as avg_risk FROM ai_analyses
165
+ WHERE risk_score IS NOT NULL
166
+ """)
167
+
168
+ summary = {
169
+ 'total_analyses': total_analyses['count'] if total_analyses else 0,
170
+ 'total_patients': total_patients['count'] if total_patients else 0,
171
+ 'total_sessions': total_sessions['count'] if total_sessions else 0,
172
+ 'today_analyses': today_analyses['count'] if today_analyses else 0,
173
+ 'avg_processing_time': round(avg_processing_time['avg_time'], 2) if avg_processing_time and avg_processing_time['avg_time'] else 0,
174
+ 'avg_risk_score': round(avg_risk_score['avg_risk'], 1) if avg_risk_score and avg_risk_score['avg_risk'] else 0
175
+ }
176
+
177
+ return jsonify({
178
+ 'success': True,
179
+ 'data': summary,
180
+ 'timestamp': datetime.now().isoformat()
181
+ })
182
+
183
+ except Exception as e:
184
+ self.logger.error(f"Error getting summary stats: {e}")
185
+ return jsonify({
186
+ 'success': False,
187
+ 'error': str(e)
188
+ }), 500
189
+
190
+ @self.app.route('/api/bot/models/performance', methods=['GET'])
191
+ def get_model_performance():
192
+ """Get AI model performance metrics"""
193
+ try:
194
+ performance_data = self.database_manager.execute_query("""
195
+ SELECT
196
+ model_version,
197
+ COUNT(*) as total_analyses,
198
+ AVG(processing_time) as avg_processing_time,
199
+ AVG(risk_score) as avg_risk_score,
200
+ MIN(created_at) as first_used,
201
+ MAX(created_at) as last_used
202
+ FROM ai_analyses
203
+ WHERE model_version IS NOT NULL
204
+ GROUP BY model_version
205
+ ORDER BY last_used DESC
206
+ """, fetch=True)
207
+
208
+ # Convert datetime objects for JSON serialization
209
+ for model in performance_data:
210
+ if model.get('first_used'):
211
+ model['first_used'] = model['first_used'].isoformat()
212
+ if model.get('last_used'):
213
+ model['last_used'] = model['last_used'].isoformat()
214
+ if model.get('avg_processing_time'):
215
+ model['avg_processing_time'] = round(model['avg_processing_time'], 2)
216
+ if model.get('avg_risk_score'):
217
+ model['avg_risk_score'] = round(model['avg_risk_score'], 1)
218
+
219
+ return jsonify({
220
+ 'success': True,
221
+ 'data': performance_data or [],
222
+ 'timestamp': datetime.now().isoformat()
223
+ })
224
+
225
+ except Exception as e:
226
+ self.logger.error(f"Error getting model performance: {e}")
227
+ return jsonify({
228
+ 'success': False,
229
+ 'error': str(e)
230
+ }), 500
231
+
232
+ def _get_trend_data(self) -> Dict[str, Any]:
233
+ """Get trend data for dashboard charts"""
234
+ try:
235
+ # Get last 30 days of analysis data
236
+ trend_data = self.database_manager.execute_query("""
237
+ SELECT
238
+ DATE(created_at) as analysis_date,
239
+ COUNT(*) as count
240
+ FROM ai_analyses
241
+ WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
242
+ GROUP BY DATE(created_at)
243
+ ORDER BY analysis_date
244
+ """, fetch=True)
245
+
246
+ # Prepare data for Chart.js
247
+ labels = []
248
+ data = []
249
+
250
+ if trend_data:
251
+ for row in trend_data:
252
+ labels.append(row['analysis_date'].strftime('%Y-%m-%d'))
253
+ data.append(row['count'])
254
+
255
+ # Get risk level distribution
256
+ risk_distribution = self.database_manager.execute_query("""
257
+ SELECT risk_level, COUNT(*) as count
258
+ FROM ai_analyses
259
+ GROUP BY risk_level
260
+ """, fetch=True)
261
+
262
+ risk_labels = []
263
+ risk_data = []
264
+
265
+ if risk_distribution:
266
+ for row in risk_distribution:
267
+ risk_labels.append(row['risk_level'])
268
+ risk_data.append(row['count'])
269
+
270
+ # Get processing time distribution
271
+ processing_time_data = self.database_manager.execute_query("""
272
+ SELECT
273
+ CASE
274
+ WHEN processing_time < 1 THEN '< 1s'
275
+ WHEN processing_time < 2 THEN '1-2s'
276
+ WHEN processing_time < 5 THEN '2-5s'
277
+ WHEN processing_time < 10 THEN '5-10s'
278
+ ELSE '> 10s'
279
+ END as time_range,
280
+ COUNT(*) as count
281
+ FROM ai_analyses
282
+ WHERE processing_time IS NOT NULL
283
+ GROUP BY time_range
284
+ ORDER BY
285
+ CASE
286
+ WHEN processing_time < 1 THEN 1
287
+ WHEN processing_time < 2 THEN 2
288
+ WHEN processing_time < 5 THEN 3
289
+ WHEN processing_time < 10 THEN 4
290
+ ELSE 5
291
+ END
292
+ """, fetch=True)
293
+
294
+ processing_labels = []
295
+ processing_data = []
296
+
297
+ if processing_time_data:
298
+ for row in processing_time_data:
299
+ processing_labels.append(row['time_range'])
300
+ processing_data.append(row['count'])
301
+
302
+ return {
303
+ 'trend_labels': labels,
304
+ 'trend_data': data,
305
+ 'risk_level_labels': risk_labels,
306
+ 'risk_level_data': risk_data,
307
+ 'processing_time_labels': processing_labels,
308
+ 'processing_time_data': processing_data
309
+ }
310
+
311
+ except Exception as e:
312
+ self.logger.error(f"Error getting trend data: {e}")
313
+ return {
314
+ 'trend_labels': [],
315
+ 'trend_data': [],
316
+ 'risk_level_labels': [],
317
+ 'risk_level_data': [],
318
+ 'processing_time_labels': [],
319
+ 'processing_time_data': []
320
+ }
321
+
322
+ def start_api_server(self):
323
+ """Start the API server in a background thread"""
324
+ if self.running:
325
+ self.logger.warning("API server is already running")
326
+ return
327
+
328
+ def run_server():
329
+ try:
330
+ self.logger.info(f"Starting SmartHeal Bot API server on port {self.port}")
331
+ self.app.run(host='0.0.0.0', port=self.port, debug=False, threaded=True)
332
+ except Exception as e:
333
+ self.logger.error(f"Error starting API server: {e}")
334
+
335
+ self.running = True
336
+ self.api_thread = threading.Thread(target=run_server, daemon=True)
337
+ self.api_thread.start()
338
+
339
+ # Give the server a moment to start
340
+ time.sleep(1)
341
+ self.logger.info(f"✅ SmartHeal Bot API server started on http://0.0.0.0:{self.port}")
342
+
343
+ def stop_api_server(self):
344
+ """Stop the API server"""
345
+ self.running = False
346
+ if self.api_thread and self.api_thread.is_alive():
347
+ self.logger.info("Stopping SmartHeal Bot API server")
348
+ # Note: Flask development server doesn't have a clean shutdown method
349
+ # In production, you would use a proper WSGI server like Gunicorn
350
+
351
+ def is_running(self) -> bool:
352
+ """Check if the API server is running"""
353
+ return self.running and self.api_thread and self.api_thread.is_alive()
354
+
355
+ class DashboardIntegrationManager:
356
+ """Manager class for dashboard integration functionality"""
357
+
358
+ def __init__(self, database_manager: DashboardDatabaseManager):
359
+ self.database_manager = database_manager
360
+ self.api = DashboardAPI(database_manager)
361
+ self.logger = logging.getLogger(__name__)
362
+
363
+ def start_integration(self):
364
+ """Start dashboard integration services"""
365
+ try:
366
+ self.api.start_api_server()
367
+ self.logger.info("✅ Dashboard integration started successfully")
368
+ except Exception as e:
369
+ self.logger.error(f"❌ Failed to start dashboard integration: {e}")
370
+
371
+ def stop_integration(self):
372
+ """Stop dashboard integration services"""
373
+ try:
374
+ self.api.stop_api_server()
375
+ self.logger.info("✅ Dashboard integration stopped")
376
+ except Exception as e:
377
+ self.logger.error(f"❌ Error stopping dashboard integration: {e}")
378
+
379
+ def log_analysis_session(self, session_data: Dict[str, Any]) -> Optional[int]:
380
+ """Log an analysis session for dashboard tracking"""
381
+ try:
382
+ session_id = self.database_manager.save_analysis_session(session_data)
383
+ if session_id:
384
+ self.logger.info(f"✅ Analysis session logged with ID: {session_id}")
385
+ return session_id
386
+ except Exception as e:
387
+ self.logger.error(f"❌ Error logging analysis session: {e}")
388
+ return None
389
+
390
+ def log_bot_interaction(self, interaction_data: Dict[str, Any]) -> Optional[int]:
391
+ """Log a bot interaction for dashboard tracking"""
392
+ try:
393
+ interaction_id = self.database_manager.save_bot_interaction(interaction_data)
394
+ if interaction_id:
395
+ self.logger.info(f"✅ Bot interaction logged with ID: {interaction_id}")
396
+ return interaction_id
397
+ except Exception as e:
398
+ self.logger.error(f"❌ Error logging bot interaction: {e}")
399
+ return None
400
+
401
+ def get_integration_status(self) -> Dict[str, Any]:
402
+ """Get the status of dashboard integration"""
403
+ return {
404
+ 'api_running': self.api.is_running(),
405
+ 'database_connected': self.database_manager.get_connection() is not None,
406
+ 'timestamp': datetime.now().isoformat()
407
+ }
408
+
src/dashboard_database_manager.py ADDED
@@ -0,0 +1,571 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mysql.connector
2
+ from mysql.connector import Error
3
+ import logging
4
+ from datetime import datetime
5
+ import json
6
+ import uuid
7
+ import os
8
+ from PIL import Image
9
+ from typing import Dict, Any, Optional, List
10
+
11
+ class DashboardDatabaseManager:
12
+ """Enhanced database manager for SmartHeal bot with dashboard integration"""
13
+
14
+ def __init__(self, mysql_config):
15
+ """Initialize database manager with MySQL configuration"""
16
+ self.mysql_config = mysql_config
17
+ self.test_connection()
18
+
19
+ def test_connection(self):
20
+ """Test database connection"""
21
+ try:
22
+ connection = self.get_connection()
23
+ if connection:
24
+ connection.close()
25
+ logging.info("✅ Database connection successful")
26
+ else:
27
+ logging.error("❌ Database connection failed")
28
+ except Exception as e:
29
+ logging.error(f"Database connection test failed: {e}")
30
+
31
+ def get_connection(self):
32
+ """Get a database connection"""
33
+ try:
34
+ connection = mysql.connector.connect(**self.mysql_config)
35
+ return connection
36
+ except Error as e:
37
+ logging.error(f"Error connecting to MySQL: {e}")
38
+ return None
39
+
40
+ def execute_query(self, query, params=None, fetch=False):
41
+ """Execute a query and return results if fetch=True"""
42
+ connection = self.get_connection()
43
+ if not connection:
44
+ return None
45
+
46
+ cursor = None
47
+ try:
48
+ cursor = connection.cursor(dictionary=True)
49
+ cursor.execute(query, params or ())
50
+
51
+ if fetch:
52
+ result = cursor.fetchall()
53
+ else:
54
+ connection.commit()
55
+ result = cursor.rowcount
56
+
57
+ return result
58
+ except Error as e:
59
+ logging.error(f"Error executing query: {e}")
60
+ if connection:
61
+ connection.rollback()
62
+ return None
63
+ finally:
64
+ if cursor:
65
+ cursor.close()
66
+ if connection and connection.is_connected():
67
+ connection.close()
68
+
69
+ def execute_query_one(self, query, params=None):
70
+ """Execute a query and return one result"""
71
+ connection = self.get_connection()
72
+ if not connection:
73
+ return None
74
+
75
+ cursor = None
76
+ try:
77
+ cursor = connection.cursor(dictionary=True)
78
+ cursor.execute(query, params or ())
79
+ result = cursor.fetchone()
80
+ return result
81
+ except Error as e:
82
+ logging.error(f"Error executing query: {e}")
83
+ return None
84
+ finally:
85
+ if cursor:
86
+ cursor.close()
87
+ if connection and connection.is_connected():
88
+ connection.close()
89
+
90
+ def get_last_insert_id(self, connection, cursor):
91
+ """Get the last inserted ID"""
92
+ return cursor.lastrowid
93
+
94
+ def save_questionnaire_response(self, questionnaire_data: Dict[str, Any], user_id: int) -> Optional[int]:
95
+ """
96
+ Save questionnaire response to dashboard-compatible tables
97
+ """
98
+ connection = None
99
+ cursor = None
100
+ try:
101
+ connection = self.get_connection()
102
+ if not connection:
103
+ return None
104
+ cursor = connection.cursor()
105
+
106
+ # (1) Create or get patient
107
+ patient_id = self._create_or_get_patient(cursor, questionnaire_data)
108
+ if not patient_id:
109
+ raise Exception("Failed to get or create patient")
110
+
111
+ # (2) Get or create default questionnaire
112
+ questionnaire_id = self._get_or_create_default_questionnaire(cursor)
113
+ if not questionnaire_id:
114
+ raise Exception("Failed to get or create questionnaire")
115
+
116
+ # (3) Prepare response_data JSON
117
+ response_data = {
118
+ 'patient_info': {
119
+ 'name': questionnaire_data.get('patient_name', ''),
120
+ 'age': questionnaire_data.get('patient_age', 0),
121
+ 'gender': questionnaire_data.get('patient_gender', '')
122
+ },
123
+ 'wound_details': {
124
+ 'location': questionnaire_data.get('wound_location', ''),
125
+ 'duration': questionnaire_data.get('wound_duration', ''),
126
+ 'pain_level': questionnaire_data.get('pain_level', 0),
127
+ 'moisture_level': questionnaire_data.get('moisture_level', ''),
128
+ 'infection_signs': questionnaire_data.get('infection_signs', ''),
129
+ 'diabetic_status': questionnaire_data.get('diabetic_status', '')
130
+ },
131
+ 'medical_history': {
132
+ 'previous_treatment': questionnaire_data.get('previous_treatment', ''),
133
+ 'medical_history': questionnaire_data.get('medical_history', ''),
134
+ 'medications': questionnaire_data.get('medications', ''),
135
+ 'allergies': questionnaire_data.get('allergies', ''),
136
+ 'additional_notes': questionnaire_data.get('additional_notes', '')
137
+ }
138
+ }
139
+
140
+ # (4) Insert into questionnaire_responses
141
+ insert_resp = """
142
+ INSERT INTO questionnaire_responses
143
+ (questionnaire_id, patient_id, practitioner_id, response_data, submitted_at)
144
+ VALUES (%s, %s, %s, %s, %s)
145
+ """
146
+ cursor.execute(insert_resp, (
147
+ questionnaire_id,
148
+ patient_id,
149
+ user_id,
150
+ json.dumps(response_data),
151
+ datetime.now()
152
+ ))
153
+ response_id = cursor.lastrowid
154
+
155
+ connection.commit()
156
+ logging.info(f"✅ Saved questionnaire response ID {response_id}")
157
+ return response_id
158
+
159
+ except Exception as e:
160
+ logging.error(f"❌ Error saving questionnaire response: {e}")
161
+ if connection:
162
+ connection.rollback()
163
+ return None
164
+ finally:
165
+ if cursor:
166
+ cursor.close()
167
+ if connection:
168
+ connection.close()
169
+
170
+ def _get_or_create_default_questionnaire(self, cursor) -> Optional[int]:
171
+ """Get or create default questionnaire"""
172
+ try:
173
+ # Check if default questionnaire exists
174
+ cursor.execute("SELECT id FROM questionnaires WHERE name = 'Default Patient Assessment' LIMIT 1")
175
+ questionnaire_row = cursor.fetchone()
176
+
177
+ if questionnaire_row:
178
+ return questionnaire_row[0]
179
+
180
+ # Create default questionnaire
181
+ cursor.execute("""
182
+ INSERT INTO questionnaires (name, description, created_at)
183
+ VALUES ('Default Patient Assessment', 'Standard patient wound assessment form', NOW())
184
+ """)
185
+ return cursor.lastrowid
186
+
187
+ except Exception as e:
188
+ logging.error(f"Error getting/creating questionnaire: {e}")
189
+ return None
190
+
191
+ def _create_or_get_patient(self, cursor, questionnaire_data: Dict[str, Any]) -> Optional[int]:
192
+ """Create or get existing patient record"""
193
+ try:
194
+ # Check if patient exists by name and age
195
+ select_query = """
196
+ SELECT id FROM patients
197
+ WHERE name = %s AND age = %s
198
+ LIMIT 1
199
+ """
200
+ cursor.execute(select_query, (
201
+ questionnaire_data.get('patient_name', ''),
202
+ questionnaire_data.get('patient_age', 0)
203
+ ))
204
+
205
+ existing_patient = cursor.fetchone()
206
+ if existing_patient:
207
+ return existing_patient[0]
208
+
209
+ # Create new patient
210
+ patient_uuid = str(uuid.uuid4())
211
+ insert_query = """
212
+ INSERT INTO patients (
213
+ uuid, name, age, gender, illness, allergy, notes, created_at
214
+ ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
215
+ """
216
+
217
+ cursor.execute(insert_query, (
218
+ patient_uuid,
219
+ questionnaire_data.get('patient_name', ''),
220
+ questionnaire_data.get('patient_age', 0),
221
+ questionnaire_data.get('patient_gender', ''),
222
+ questionnaire_data.get('medical_history', ''),
223
+ questionnaire_data.get('allergies', ''),
224
+ questionnaire_data.get('additional_notes', ''),
225
+ datetime.now()
226
+ ))
227
+
228
+ return cursor.lastrowid
229
+
230
+ except Exception as e:
231
+ logging.error(f"Error creating/getting patient: {e}")
232
+ return None
233
+
234
+ def save_wound_image(self, questionnaire_response_id: int, image, original_filename: str = None) -> Optional[int]:
235
+ """Save wound image to filesystem and database"""
236
+ try:
237
+ # Generate unique filename
238
+ image_id = str(uuid.uuid4())
239
+ filename = f"wound_{image_id}.jpg"
240
+ file_path = os.path.join("uploads", filename)
241
+
242
+ # Ensure uploads directory exists
243
+ os.makedirs("uploads", exist_ok=True)
244
+
245
+ # Save image to disk
246
+ if hasattr(image, 'save'):
247
+ image.save(file_path, format='JPEG', quality=95)
248
+
249
+ # Get image dimensions and file size
250
+ width, height = image.size
251
+ file_size = os.path.getsize(file_path)
252
+
253
+ # Save to wound_images table
254
+ query = """
255
+ INSERT INTO wound_images (
256
+ questionnaire_id, image_url, original_filename, file_size,
257
+ image_width, image_height, created_at
258
+ ) VALUES (%s, %s, %s, %s, %s, %s, %s)
259
+ """
260
+
261
+ params = (
262
+ questionnaire_response_id,
263
+ file_path,
264
+ original_filename or filename,
265
+ file_size,
266
+ width,
267
+ height,
268
+ datetime.now()
269
+ )
270
+
271
+ result = self.execute_query(query, params)
272
+ if result:
273
+ # Get the inserted ID
274
+ connection = self.get_connection()
275
+ cursor = connection.cursor()
276
+ cursor.execute("SELECT LAST_INSERT_ID()")
277
+ image_db_id = cursor.fetchone()[0]
278
+ cursor.close()
279
+ connection.close()
280
+
281
+ logging.info(f"✅ Image saved with ID: {image_db_id}")
282
+ return image_db_id
283
+
284
+ except Exception as e:
285
+ logging.error(f"❌ Error saving wound image: {e}")
286
+
287
+ return None
288
+
289
+ def save_ai_analysis(self, analysis_data: Dict[str, Any]) -> Optional[int]:
290
+ """
291
+ Save AI analysis results to dashboard-compatible ai_analyses table
292
+ """
293
+ try:
294
+ query = """
295
+ INSERT INTO ai_analyses (
296
+ questionnaire_id, image_id, analysis_data, summary, recommendations,
297
+ risk_score, risk_level, wound_type, wound_dimensions, processing_time,
298
+ model_version, created_at
299
+ ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
300
+ """
301
+
302
+ # Calculate risk level based on risk score
303
+ risk_score = analysis_data.get('risk_score', 0)
304
+ if risk_score >= 70:
305
+ risk_level = 'High'
306
+ elif risk_score >= 40:
307
+ risk_level = 'Moderate'
308
+ else:
309
+ risk_level = 'Low'
310
+
311
+ # Format wound dimensions
312
+ visual_results = analysis_data.get('visual_results', {})
313
+ wound_dimensions = f"{visual_results.get('length_cm', 0)}x{visual_results.get('breadth_cm', 0)} cm"
314
+
315
+ params = (
316
+ analysis_data.get('questionnaire_id'),
317
+ analysis_data.get('image_id'),
318
+ json.dumps(analysis_data.get('analysis_data', {})),
319
+ analysis_data.get('summary', ''),
320
+ analysis_data.get('recommendations', ''),
321
+ risk_score,
322
+ risk_level,
323
+ visual_results.get('wound_type', 'Unknown'),
324
+ wound_dimensions,
325
+ analysis_data.get('processing_time', 0.0),
326
+ analysis_data.get('model_version', 'v1.0'),
327
+ datetime.now()
328
+ )
329
+
330
+ result = self.execute_query(query, params)
331
+ if result:
332
+ # Get the inserted ID
333
+ connection = self.get_connection()
334
+ cursor = connection.cursor()
335
+ cursor.execute("SELECT LAST_INSERT_ID()")
336
+ analysis_id = cursor.fetchone()[0]
337
+ cursor.close()
338
+ connection.close()
339
+
340
+ logging.info(f"✅ AI analysis saved with ID: {analysis_id}")
341
+ return analysis_id
342
+
343
+ except Exception as e:
344
+ logging.error(f"❌ Error saving AI analysis: {e}")
345
+
346
+ return None
347
+
348
+ def save_analysis_session(self, session_data: Dict[str, Any]) -> Optional[int]:
349
+ """
350
+ Save analysis session data for dashboard analytics
351
+ """
352
+ try:
353
+ query = """
354
+ INSERT INTO analysis_sessions (
355
+ user_id, questionnaire_id, image_id, analysis_id,
356
+ session_duration, created_at
357
+ ) VALUES (%s, %s, %s, %s, %s, %s)
358
+ """
359
+
360
+ params = (
361
+ session_data.get('user_id'),
362
+ session_data.get('questionnaire_id'),
363
+ session_data.get('image_id'),
364
+ session_data.get('analysis_id'),
365
+ session_data.get('session_duration', 0.0),
366
+ datetime.now()
367
+ )
368
+
369
+ result = self.execute_query(query, params)
370
+ if result:
371
+ connection = self.get_connection()
372
+ cursor = connection.cursor()
373
+ cursor.execute("SELECT LAST_INSERT_ID()")
374
+ session_id = cursor.fetchone()[0]
375
+ cursor.close()
376
+ connection.close()
377
+
378
+ logging.info(f"✅ Analysis session saved with ID: {session_id}")
379
+ return session_id
380
+
381
+ except Exception as e:
382
+ logging.error(f"❌ Error saving analysis session: {e}")
383
+
384
+ return None
385
+
386
+ def save_bot_interaction(self, interaction_data: Dict[str, Any]) -> Optional[int]:
387
+ """
388
+ Save bot interaction data for dashboard analytics
389
+ """
390
+ try:
391
+ query = """
392
+ INSERT INTO bot_interactions (
393
+ patient_id, practitioner_id, input_text, output_text,
394
+ wound_image_url, interaction_type, interacted_at
395
+ ) VALUES (%s, %s, %s, %s, %s, %s, %s)
396
+ """
397
+
398
+ params = (
399
+ interaction_data.get('patient_id'),
400
+ interaction_data.get('practitioner_id'),
401
+ interaction_data.get('input_text', ''),
402
+ interaction_data.get('output_text', ''),
403
+ interaction_data.get('wound_image_url', ''),
404
+ interaction_data.get('interaction_type', 'analysis'),
405
+ datetime.now()
406
+ )
407
+
408
+ result = self.execute_query(query, params)
409
+ if result:
410
+ connection = self.get_connection()
411
+ cursor = connection.cursor()
412
+ cursor.execute("SELECT LAST_INSERT_ID()")
413
+ interaction_id = cursor.fetchone()[0]
414
+ cursor.close()
415
+ connection.close()
416
+
417
+ logging.info(f"✅ Bot interaction saved with ID: {interaction_id}")
418
+ return interaction_id
419
+
420
+ except Exception as e:
421
+ logging.error(f"❌ Error saving bot interaction: {e}")
422
+
423
+ return None
424
+
425
+ def get_analytics_data(self) -> Dict[str, Any]:
426
+ """
427
+ Get comprehensive analytics data for dashboard
428
+ """
429
+ try:
430
+ analytics = {}
431
+
432
+ # Total analyses
433
+ total_analyses = self.execute_query_one("SELECT COUNT(*) as count FROM ai_analyses")
434
+ analytics['total_analyses'] = total_analyses['count'] if total_analyses else 0
435
+
436
+ # Average processing time
437
+ avg_time = self.execute_query_one("SELECT AVG(processing_time) as avg_time FROM ai_analyses WHERE processing_time IS NOT NULL")
438
+ analytics['avg_processing_time'] = round(avg_time['avg_time'], 2) if avg_time and avg_time['avg_time'] else 0
439
+
440
+ # High risk count
441
+ high_risk = self.execute_query_one("SELECT COUNT(*) as count FROM ai_analyses WHERE risk_level = 'High'")
442
+ analytics['high_risk_count'] = high_risk['count'] if high_risk else 0
443
+
444
+ # Average risk score
445
+ avg_risk = self.execute_query_one("SELECT AVG(risk_score) as avg_risk FROM ai_analyses WHERE risk_score IS NOT NULL")
446
+ analytics['avg_risk_score'] = round(avg_risk['avg_risk'], 1) if avg_risk and avg_risk['avg_risk'] else 0
447
+
448
+ # Risk level distribution
449
+ risk_distribution = self.execute_query("""
450
+ SELECT risk_level, COUNT(*) as count
451
+ FROM ai_analyses
452
+ GROUP BY risk_level
453
+ """, fetch=True)
454
+
455
+ analytics['risk_level_distribution'] = {}
456
+ if risk_distribution:
457
+ for row in risk_distribution:
458
+ analytics['risk_level_distribution'][row['risk_level']] = row['count']
459
+
460
+ # Recent analyses
461
+ recent_analyses = self.execute_query("""
462
+ SELECT * FROM ai_analyses
463
+ ORDER BY created_at DESC
464
+ LIMIT 10
465
+ """, fetch=True)
466
+ analytics['recent_analyses'] = recent_analyses or []
467
+
468
+ # Model performance
469
+ model_performance = self.execute_query("""
470
+ SELECT
471
+ model_version,
472
+ COUNT(*) as count,
473
+ AVG(processing_time) as avg_processing_time,
474
+ AVG(risk_score) as avg_risk_score
475
+ FROM ai_analyses
476
+ WHERE model_version IS NOT NULL
477
+ GROUP BY model_version
478
+ """, fetch=True)
479
+ analytics['model_performance'] = model_performance or []
480
+
481
+ # Today's analyses
482
+ today_analyses = self.execute_query_one("""
483
+ SELECT COUNT(*) as count
484
+ FROM ai_analyses
485
+ WHERE DATE(created_at) = CURDATE()
486
+ """)
487
+ analytics['analyses_today'] = today_analyses['count'] if today_analyses else 0
488
+
489
+ # This week's analyses
490
+ week_analyses = self.execute_query_one("""
491
+ SELECT COUNT(*) as count
492
+ FROM ai_analyses
493
+ WHERE YEARWEEK(created_at) = YEARWEEK(NOW())
494
+ """)
495
+ analytics['analyses_this_week'] = week_analyses['count'] if week_analyses else 0
496
+
497
+ # Unique questionnaires
498
+ unique_questionnaires = self.execute_query_one("""
499
+ SELECT COUNT(DISTINCT questionnaire_id) as count
500
+ FROM ai_analyses
501
+ """)
502
+ analytics['unique_questionnaires'] = unique_questionnaires['count'] if unique_questionnaires else 0
503
+
504
+ # Analyses with images
505
+ with_images = self.execute_query_one("""
506
+ SELECT COUNT(*) as count
507
+ FROM ai_analyses
508
+ WHERE image_id IS NOT NULL
509
+ """)
510
+ analytics['analyses_with_images'] = with_images['count'] if with_images else 0
511
+
512
+ return analytics
513
+
514
+ except Exception as e:
515
+ logging.error(f"❌ Error getting analytics data: {e}")
516
+ return {}
517
+
518
+ def get_interaction_history(self, limit: int = 50) -> List[Dict[str, Any]]:
519
+ """
520
+ Get bot interaction history for dashboard
521
+ """
522
+ try:
523
+ query = """
524
+ SELECT bi.*, p.name as patient_name, u.name as practitioner_name
525
+ FROM bot_interactions bi
526
+ LEFT JOIN patients p ON bi.patient_id = p.id
527
+ LEFT JOIN users u ON bi.practitioner_id = u.id
528
+ ORDER BY bi.interacted_at DESC
529
+ LIMIT %s
530
+ """
531
+
532
+ interactions = self.execute_query(query, (limit,), fetch=True)
533
+ return interactions or []
534
+
535
+ except Exception as e:
536
+ logging.error(f"❌ Error getting interaction history: {e}")
537
+ return []
538
+
539
+ def get_session_analytics(self) -> Dict[str, Any]:
540
+ """
541
+ Get session analytics for dashboard
542
+ """
543
+ try:
544
+ analytics = {}
545
+
546
+ # Total sessions
547
+ total_sessions = self.execute_query_one("SELECT COUNT(*) as count FROM analysis_sessions")
548
+ analytics['total_sessions'] = total_sessions['count'] if total_sessions else 0
549
+
550
+ # Average session duration
551
+ avg_duration = self.execute_query_one("""
552
+ SELECT AVG(session_duration) as avg_duration
553
+ FROM analysis_sessions
554
+ WHERE session_duration IS NOT NULL
555
+ """)
556
+ analytics['avg_session_duration'] = round(avg_duration['avg_duration'], 2) if avg_duration and avg_duration['avg_duration'] else 0
557
+
558
+ # Sessions today
559
+ today_sessions = self.execute_query_one("""
560
+ SELECT COUNT(*) as count
561
+ FROM analysis_sessions
562
+ WHERE DATE(created_at) = CURDATE()
563
+ """)
564
+ analytics['sessions_today'] = today_sessions['count'] if today_sessions else 0
565
+
566
+ return analytics
567
+
568
+ except Exception as e:
569
+ logging.error(f"❌ Error getting session analytics: {e}")
570
+ return {}
571
+
src/eHealth in Wound Care.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dde88c1c1d7d295c7f2c53375b9cd69c0cef029e273c89374d92f1375c2c5038
3
+ size 907639
src/enhanced_ai_processor.py ADDED
@@ -0,0 +1,613 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import cv2
4
+ import numpy as np
5
+ from PIL import Image
6
+ import torch
7
+ import json
8
+ from datetime import datetime
9
+ import tensorflow as tf
10
+ from transformers import pipeline
11
+ from ultralytics import YOLO
12
+ from tensorflow.keras.models import load_model
13
+ from langchain_community.document_loaders import PyPDFLoader
14
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
15
+ from langchain_community.embeddings import HuggingFaceEmbeddings
16
+ from langchain_community.vectorstores import FAISS
17
+ from huggingface_hub import HfApi, HfFolder
18
+ import spaces
19
+ import time
20
+ from typing import Dict, Any, Optional, Tuple
21
+
22
+ from .config import Config
23
+
24
+ class EnhancedAIProcessor:
25
+ """Enhanced AI processor with dashboard integration and analytics tracking"""
26
+
27
+ def __init__(self):
28
+ self.models_cache = {}
29
+ self.knowledge_base_cache = {}
30
+ self.config = Config()
31
+ self.px_per_cm = self.config.PIXELS_PER_CM
32
+ self.model_version = "v1.2.0" # Version for tracking
33
+ self._initialize_models()
34
+
35
+ def _initialize_models(self):
36
+ """Initialize all AI models including real-time models"""
37
+ try:
38
+ # Set HuggingFace token
39
+ if self.config.HF_TOKEN:
40
+ HfFolder.save_token(self.config.HF_TOKEN)
41
+ logging.info("HuggingFace token set successfully")
42
+
43
+ # Initialize MedGemma pipeline for medical text generation
44
+ try:
45
+ self.models_cache["medgemma_pipe"] = pipeline(
46
+ "image-text-to-text",
47
+ model="google/medgemma-4b-it",
48
+ torch_dtype=torch.bfloat16,
49
+ offload_folder="offload",
50
+ device_map="auto",
51
+ token=self.config.HF_TOKEN
52
+ )
53
+ logging.info("✅ MedGemma pipeline loaded successfully")
54
+ except Exception as e:
55
+ logging.warning(f"MedGemma pipeline not available: {e}")
56
+
57
+ # Initialize YOLO model for wound detection
58
+ try:
59
+ self.models_cache["det"] = YOLO(self.config.YOLO_MODEL_PATH)
60
+ logging.info("✅ YOLO detection model loaded successfully")
61
+ except Exception as e:
62
+ logging.warning(f"YOLO model not available: {e}")
63
+
64
+ # Initialize segmentation model
65
+ try:
66
+ self.models_cache["seg"] = load_model(self.config.SEG_MODEL_PATH, compile=False)
67
+ logging.info("✅ Segmentation model loaded successfully")
68
+ except Exception as e:
69
+ logging.warning(f"Segmentation model not available: {e}")
70
+
71
+ # Initialize wound classification model
72
+ try:
73
+ self.models_cache["cls"] = pipeline(
74
+ "image-classification",
75
+ model="Hemg/Wound-classification",
76
+ token=self.config.HF_TOKEN,
77
+ device="cpu"
78
+ )
79
+ logging.info("✅ Wound classification model loaded successfully")
80
+ except Exception as e:
81
+ logging.warning(f"Wound classification model not available: {e}")
82
+
83
+ # Initialize embedding model for knowledge base
84
+ try:
85
+ self.models_cache["embedding_model"] = HuggingFaceEmbeddings(
86
+ model_name="sentence-transformers/all-MiniLM-L6-v2",
87
+ model_kwargs={'device': 'cpu'}
88
+ )
89
+ logging.info("✅ Embedding model loaded successfully")
90
+ except Exception as e:
91
+ logging.warning(f"Embedding model not available: {e}")
92
+
93
+ logging.info("✅ All models loaded.")
94
+ self._load_knowledge_base()
95
+
96
+ except Exception as e:
97
+ logging.error(f"Error initializing AI models: {e}")
98
+
99
+ def _load_knowledge_base(self):
100
+ """Load knowledge base from PDF guidelines"""
101
+ try:
102
+ documents = []
103
+ for pdf_path in self.config.GUIDELINE_PDFS:
104
+ if os.path.exists(pdf_path):
105
+ loader = PyPDFLoader(pdf_path)
106
+ docs = loader.load()
107
+ documents.extend(docs)
108
+ logging.info(f"Loaded PDF: {pdf_path}")
109
+
110
+ if documents and 'embedding_model' in self.models_cache:
111
+ # Split documents into chunks
112
+ text_splitter = RecursiveCharacterTextSplitter(
113
+ chunk_size=1000,
114
+ chunk_overlap=100
115
+ )
116
+ chunks = text_splitter.split_documents(documents)
117
+
118
+ # Create vector store
119
+ vectorstore = FAISS.from_documents(chunks, self.models_cache['embedding_model'])
120
+ self.knowledge_base_cache['vectorstore'] = vectorstore
121
+ logging.info(f"✅ Knowledge base loaded with {len(chunks)} chunks")
122
+ else:
123
+ self.knowledge_base_cache['vectorstore'] = None
124
+ logging.warning("Knowledge base not available - no PDFs found or embedding model unavailable")
125
+
126
+ except Exception as e:
127
+ logging.warning(f"Knowledge base loading error: {e}")
128
+ self.knowledge_base_cache['vectorstore'] = None
129
+
130
+ def perform_comprehensive_analysis(self, image_pil: Image.Image, patient_info: Dict[str, Any]) -> Dict[str, Any]:
131
+ """
132
+ Perform comprehensive analysis with enhanced tracking for dashboard integration
133
+ """
134
+ start_time = time.time()
135
+
136
+ try:
137
+ # Perform visual analysis
138
+ visual_results = self.perform_visual_analysis(image_pil)
139
+
140
+ # Query guidelines for context
141
+ guideline_query = f"wound care {visual_results.get('wound_type', 'general')} treatment recommendations"
142
+ guideline_context = self.query_guidelines(guideline_query)
143
+
144
+ # Generate comprehensive report
145
+ report = self.generate_final_report(patient_info, visual_results, guideline_context, image_pil)
146
+
147
+ # Calculate processing time
148
+ processing_time = round(time.time() - start_time, 2)
149
+
150
+ # Calculate risk score based on multiple factors
151
+ risk_score = self._calculate_risk_score(visual_results, patient_info)
152
+
153
+ # Prepare comprehensive analysis data
154
+ analysis_data = {
155
+ 'visual_results': visual_results,
156
+ 'patient_info': patient_info,
157
+ 'guideline_context': guideline_context,
158
+ 'report': report,
159
+ 'processing_time': processing_time,
160
+ 'risk_score': risk_score,
161
+ 'model_version': self.model_version,
162
+ 'analysis_timestamp': datetime.now().isoformat(),
163
+ 'analysis_metadata': {
164
+ 'models_used': list(self.models_cache.keys()),
165
+ 'image_dimensions': image_pil.size,
166
+ 'guideline_sources': len(guideline_context.split('\n\n')) if guideline_context else 0
167
+ }
168
+ }
169
+
170
+ logging.info(f"✅ Comprehensive analysis completed in {processing_time}s with risk score {risk_score}")
171
+ return analysis_data
172
+
173
+ except Exception as e:
174
+ processing_time = round(time.time() - start_time, 2)
175
+ logging.error(f"❌ Analysis failed after {processing_time}s: {e}")
176
+
177
+ # Return error analysis data
178
+ return {
179
+ 'error': str(e),
180
+ 'processing_time': processing_time,
181
+ 'risk_score': 0,
182
+ 'model_version': self.model_version,
183
+ 'analysis_timestamp': datetime.now().isoformat()
184
+ }
185
+
186
+ def perform_visual_analysis(self, image_pil: Image.Image) -> Dict[str, Any]:
187
+ """Perform comprehensive visual analysis of wound image with enhanced tracking"""
188
+ try:
189
+ # Convert PIL to OpenCV format
190
+ image_cv = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
191
+
192
+ # YOLO detection
193
+ if 'det' not in self.models_cache:
194
+ raise ValueError("YOLO detection model not available.")
195
+
196
+ results = self.models_cache['det'].predict(image_cv, verbose=False, device="cpu")
197
+
198
+ if not results or not results[0].boxes:
199
+ raise ValueError("No wound detected in the image.")
200
+
201
+ # Extract bounding box
202
+ box = results[0].boxes[0].xyxy[0].cpu().numpy().astype(int)
203
+ x1, y1, x2, y2 = box
204
+ region_cv = image_cv[y1:y2, x1:x2]
205
+
206
+ # Save detection image with timestamp
207
+ detection_image_cv = image_cv.copy()
208
+ cv2.rectangle(detection_image_cv, (x1, y1), (x2, y2), (0, 255, 0), 2)
209
+ os.makedirs(os.path.join(self.config.UPLOADS_DIR, "analysis"), exist_ok=True)
210
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
211
+ detection_image_path = os.path.join(self.config.UPLOADS_DIR, "analysis", f"detection_{timestamp}.png")
212
+ cv2.imwrite(detection_image_path, detection_image_cv)
213
+ detection_image_pil = Image.fromarray(cv2.cvtColor(detection_image_cv, cv2.COLOR_BGR2RGB))
214
+
215
+ # Initialize outputs
216
+ length = breadth = area = 0
217
+ segmentation_image_pil = None
218
+ segmentation_image_path = None
219
+ segmentation_confidence = 0.0
220
+
221
+ # Segmentation (optional)
222
+ if 'seg' in self.models_cache:
223
+ input_size = self.models_cache['seg'].input_shape[1:3] # (height, width)
224
+ resized_region = cv2.resize(region_cv, (input_size[1], input_size[0]))
225
+
226
+ seg_input = np.expand_dims(resized_region / 255.0, 0)
227
+ mask_pred = self.models_cache['seg'].predict(seg_input, verbose=0)[0]
228
+ mask_np = (mask_pred[:, :, 0] > 0.5).astype(np.uint8)
229
+
230
+ # Calculate segmentation confidence
231
+ segmentation_confidence = float(np.mean(mask_pred[:, :, 0]))
232
+
233
+ # Resize mask back to original region size
234
+ mask_resized = cv2.resize(mask_np, (region_cv.shape[1], region_cv.shape[0]), interpolation=cv2.INTER_NEAREST)
235
+
236
+ # Overlay mask on region for visualization
237
+ overlay = region_cv.copy()
238
+ overlay[mask_resized == 1] = [0, 0, 255] # Red overlay
239
+
240
+ # Blend overlay for final output
241
+ segmented_visual = cv2.addWeighted(region_cv, 0.7, overlay, 0.3, 0)
242
+
243
+ # Save segmentation image
244
+ segmentation_image_path = os.path.join(self.config.UPLOADS_DIR, "analysis", f"segmentation_{timestamp}.png")
245
+ cv2.imwrite(segmentation_image_path, segmented_visual)
246
+ segmentation_image_pil = Image.fromarray(cv2.cvtColor(segmented_visual, cv2.COLOR_BGR2RGB))
247
+
248
+ # Wound measurements from resized mask
249
+ contours, _ = cv2.findContours(mask_resized, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
250
+ if contours:
251
+ cnt = max(contours, key=cv2.contourArea)
252
+ x, y, w, h = cv2.boundingRect(cnt)
253
+ length = round(h / self.px_per_cm, 2)
254
+ breadth = round(w / self.px_per_cm, 2)
255
+ area = round(cv2.contourArea(cnt) / (self.px_per_cm ** 2), 2)
256
+
257
+ # Classification with confidence tracking
258
+ wound_type = "Unknown"
259
+ classification_confidence = 0.0
260
+ classification_scores = []
261
+
262
+ if 'cls' in self.models_cache:
263
+ try:
264
+ region_pil = Image.fromarray(cv2.cvtColor(region_cv, cv2.COLOR_BGR2RGB))
265
+ cls_result = self.models_cache['cls'](region_pil)
266
+
267
+ if cls_result:
268
+ best_result = max(cls_result, key=lambda x: x['score'])
269
+ wound_type = best_result['label']
270
+ classification_confidence = float(best_result['score'])
271
+ classification_scores = [{'label': r['label'], 'score': float(r['score'])} for r in cls_result]
272
+
273
+ except Exception as e:
274
+ logging.warning(f"Wound classification error: {e}")
275
+
276
+ return {
277
+ 'wound_type': wound_type,
278
+ 'length_cm': length,
279
+ 'breadth_cm': breadth,
280
+ 'surface_area_cm2': area,
281
+ 'detection_confidence': float(results[0].boxes[0].conf.cpu().item()),
282
+ 'segmentation_confidence': segmentation_confidence,
283
+ 'classification_confidence': classification_confidence,
284
+ 'classification_scores': classification_scores,
285
+ 'bounding_box': box.tolist(),
286
+ 'detection_image_path': detection_image_path,
287
+ 'detection_image_pil': detection_image_pil,
288
+ 'segmentation_image_path': segmentation_image_path,
289
+ 'segmentation_image_pil': segmentation_image_pil,
290
+ 'analysis_quality': {
291
+ 'detection_quality': 'high' if float(results[0].boxes[0].conf.cpu().item()) > 0.8 else 'medium',
292
+ 'segmentation_quality': 'high' if segmentation_confidence > 0.7 else 'medium',
293
+ 'classification_quality': 'high' if classification_confidence > 0.8 else 'medium'
294
+ }
295
+ }
296
+
297
+ except Exception as e:
298
+ logging.error(f"Visual analysis error: {e}")
299
+ raise ValueError(f"Visual analysis failed: {str(e)}")
300
+
301
+ def _calculate_risk_score(self, visual_results: Dict[str, Any], patient_info: Dict[str, Any]) -> int:
302
+ """
303
+ Calculate comprehensive risk score (0-100) based on visual analysis and patient data
304
+ """
305
+ try:
306
+ risk_score = 0
307
+
308
+ # Wound size risk (0-25 points)
309
+ area = visual_results.get('surface_area_cm2', 0)
310
+ if area > 10:
311
+ risk_score += 25
312
+ elif area > 5:
313
+ risk_score += 15
314
+ elif area > 2:
315
+ risk_score += 10
316
+ else:
317
+ risk_score += 5
318
+
319
+ # Wound type risk (0-20 points)
320
+ wound_type = visual_results.get('wound_type', '').lower()
321
+ high_risk_types = ['ulcer', 'necrotic', 'infected', 'diabetic']
322
+ medium_risk_types = ['pressure', 'venous', 'arterial']
323
+
324
+ if any(risk_type in wound_type for risk_type in high_risk_types):
325
+ risk_score += 20
326
+ elif any(risk_type in wound_type for risk_type in medium_risk_types):
327
+ risk_score += 15
328
+ else:
329
+ risk_score += 10
330
+
331
+ # Patient factors (0-30 points)
332
+ age = patient_info.get('patient_age', 0)
333
+ if age > 70:
334
+ risk_score += 15
335
+ elif age > 50:
336
+ risk_score += 10
337
+ else:
338
+ risk_score += 5
339
+
340
+ # Diabetic status
341
+ diabetic_status = patient_info.get('diabetic_status', '').lower()
342
+ if 'yes' in diabetic_status or 'diabetic' in diabetic_status:
343
+ risk_score += 15
344
+
345
+ # Pain level (0-10 points)
346
+ pain_level = patient_info.get('pain_level', 0)
347
+ if pain_level > 7:
348
+ risk_score += 10
349
+ elif pain_level > 4:
350
+ risk_score += 7
351
+ else:
352
+ risk_score += 3
353
+
354
+ # Infection signs (0-15 points)
355
+ infection_signs = patient_info.get('infection_signs', '').lower()
356
+ if 'yes' in infection_signs or 'present' in infection_signs:
357
+ risk_score += 15
358
+ elif 'possible' in infection_signs or 'mild' in infection_signs:
359
+ risk_score += 10
360
+ else:
361
+ risk_score += 5
362
+
363
+ # Ensure score is within 0-100 range
364
+ risk_score = min(max(risk_score, 0), 100)
365
+
366
+ logging.info(f"Calculated risk score: {risk_score}")
367
+ return risk_score
368
+
369
+ except Exception as e:
370
+ logging.error(f"Error calculating risk score: {e}")
371
+ return 50 # Default medium risk
372
+
373
+ def query_guidelines(self, query: str) -> str:
374
+ """Query the knowledge base for relevant guidelines with enhanced tracking"""
375
+ try:
376
+ vector_store = self.knowledge_base_cache.get("vectorstore")
377
+ if not vector_store:
378
+ return "Knowledge base unavailable - clinical guidelines not loaded"
379
+
380
+ # Retrieve relevant documents
381
+ retriever = vector_store.as_retriever(search_kwargs={"k": 10})
382
+ docs = retriever.invoke(query)
383
+
384
+ if not docs:
385
+ return "No relevant guidelines found for the query"
386
+
387
+ # Format the results with enhanced metadata
388
+ formatted_results = []
389
+ for i, doc in enumerate(docs):
390
+ source = doc.metadata.get('source', 'Unknown')
391
+ page = doc.metadata.get('page', 'N/A')
392
+ content = doc.page_content.strip()
393
+
394
+ # Add relevance indicator
395
+ relevance = f"Result {i+1}/10"
396
+ formatted_results.append(f"[{relevance}] Source: {source}, Page: {page}\nContent: {content}")
397
+
398
+ guideline_text = "\n\n".join(formatted_results)
399
+ logging.info(f"Retrieved {len(docs)} guideline documents for query: {query[:50]}...")
400
+ return guideline_text
401
+
402
+ except Exception as e:
403
+ logging.error(f"Guidelines query error: {e}")
404
+ return f"Error querying guidelines: {str(e)}"
405
+
406
+ def generate_final_report(self, patient_info: Dict[str, Any], visual_results: Dict[str, Any],
407
+ guideline_context: str, image_pil: Image.Image, max_new_tokens: int = None) -> str:
408
+ """Generate comprehensive medical report using MedGemma with enhanced tracking"""
409
+ try:
410
+ if 'medgemma_pipe' not in self.models_cache:
411
+ return self._generate_fallback_report(patient_info, visual_results, guideline_context)
412
+
413
+ max_tokens = max_new_tokens or self.config.MAX_NEW_TOKENS
414
+
415
+ # Get detection and segmentation images if available
416
+ detection_image = visual_results.get('detection_image_pil', None)
417
+ segmentation_image = visual_results.get('segmentation_image_pil', None)
418
+
419
+ # Create enhanced prompt with quality indicators
420
+ analysis_quality = visual_results.get('analysis_quality', {})
421
+ prompt = f"""
422
+ # SmartHeal AI Wound Care Report
423
+
424
+ ## Patient Information
425
+ {self._format_patient_info(patient_info)}
426
+
427
+ ## Visual Analysis Summary
428
+ - Wound Type: {visual_results.get('wound_type', 'Unknown')} (Confidence: {visual_results.get('classification_confidence', 0):.2f})
429
+ - Dimensions: {visual_results.get('length_cm', 0)} × {visual_results.get('breadth_cm', 0)} cm
430
+ - Surface Area: {visual_results.get('surface_area_cm2', 0)} cm²
431
+ - Detection Quality: {analysis_quality.get('detection_quality', 'medium')}
432
+ - Segmentation Quality: {analysis_quality.get('segmentation_quality', 'medium')}
433
+
434
+ ## Clinical Reference Guidelines
435
+ {guideline_context[:2000]}...
436
+
437
+ ## Analysis Request
438
+ You are SmartHeal-AI Agent, a specialized wound care AI with expertise in clinical assessment and evidence-based treatment planning.
439
+
440
+ Based on the comprehensive data provided (patient information, precise wound measurements, clinical guidelines, and visual analysis), generate a structured clinical report with the following sections:
441
+
442
+ ### 1. Clinical Assessment
443
+ - Detailed wound characterization based on visual analysis
444
+ - Tissue type assessment (granulation, slough, necrotic, epithelializing)
445
+ - Peri-wound skin condition evaluation
446
+ - Infection risk assessment
447
+
448
+ ### 2. Treatment Recommendations
449
+ - Specific wound care dressing recommendations based on wound characteristics
450
+ - Topical treatments if indicated
451
+ - Debridement recommendations if needed
452
+ - Pressure offloading strategies if applicable
453
+
454
+ ### 3. Risk Stratification
455
+ - Patient-specific risk factors analysis
456
+ - Healing prognosis assessment
457
+ - Complications to monitor
458
+
459
+ ### 4. Follow-up Plan
460
+ - Recommended assessment frequency
461
+ - Key monitoring parameters
462
+ - Escalation criteria for specialist referral
463
+
464
+ Generate a concise, evidence-based report suitable for clinical documentation.
465
+ """
466
+
467
+ # Prepare messages for MedGemma with all available images
468
+ content_list = [{"type": "text", "text": prompt}]
469
+
470
+ # Add images in order of importance
471
+ if image_pil:
472
+ content_list.insert(0, {"type": "image", "image": image_pil})
473
+
474
+ if detection_image:
475
+ content_list.insert(1, {"type": "image", "image": detection_image})
476
+
477
+ if segmentation_image:
478
+ content_list.insert(2, {"type": "image", "image": segmentation_image})
479
+
480
+ messages = [
481
+ {
482
+ "role": "system",
483
+ "content": [{"type": "text", "text": "You are a specialized medical AI assistant for wound care with expertise in clinical assessment, treatment planning, and evidence-based recommendations. Provide structured, actionable clinical reports."}],
484
+ },
485
+ {
486
+ "role": "user",
487
+ "content": content_list
488
+ }
489
+ ]
490
+
491
+ # Generate report using MedGemma
492
+ output = self.models_cache['medgemma_pipe'](
493
+ text=messages,
494
+ max_new_tokens=max_tokens,
495
+ do_sample=False,
496
+ )
497
+
498
+ generated_content = output[0]['generated_text']
499
+
500
+ # Extract the assistant's response
501
+ if isinstance(generated_content, list):
502
+ for message in generated_content:
503
+ if message.get('role') == 'assistant':
504
+ report_content = message.get('content', '')
505
+ if isinstance(report_content, list):
506
+ report_text = ''.join([item.get('text', '') for item in report_content if item.get('type') == 'text'])
507
+ else:
508
+ report_text = str(report_content)
509
+ break
510
+ else:
511
+ report_text = str(generated_content)
512
+ else:
513
+ report_text = str(generated_content)
514
+
515
+ # Add metadata to report
516
+ report_with_metadata = f"""
517
+ {report_text}
518
+
519
+ ---
520
+ **Report Metadata:**
521
+ - Generated by: SmartHeal AI v{self.model_version}
522
+ - Analysis Quality: Detection ({analysis_quality.get('detection_quality', 'medium')}), Segmentation ({analysis_quality.get('segmentation_quality', 'medium')})
523
+ - Generated at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
524
+ """
525
+
526
+ logging.info("✅ MedGemma report generated successfully")
527
+ return report_with_metadata
528
+
529
+ except Exception as e:
530
+ logging.error(f"MedGemma report generation error: {e}")
531
+ return self._generate_fallback_report(patient_info, visual_results, guideline_context)
532
+
533
+ def _format_patient_info(self, patient_info: Dict[str, Any]) -> str:
534
+ """Format patient information for report"""
535
+ formatted = f"""
536
+ - Name: {patient_info.get('patient_name', 'N/A')}
537
+ - Age: {patient_info.get('patient_age', 'N/A')} years
538
+ - Gender: {patient_info.get('patient_gender', 'N/A')}
539
+ - Wound Location: {patient_info.get('wound_location', 'N/A')}
540
+ - Wound Duration: {patient_info.get('wound_duration', 'N/A')}
541
+ - Pain Level: {patient_info.get('pain_level', 'N/A')}/10
542
+ - Diabetic Status: {patient_info.get('diabetic_status', 'N/A')}
543
+ - Infection Signs: {patient_info.get('infection_signs', 'N/A')}
544
+ - Previous Treatment: {patient_info.get('previous_treatment', 'N/A')}
545
+ - Medical History: {patient_info.get('medical_history', 'N/A')}
546
+ - Current Medications: {patient_info.get('medications', 'N/A')}
547
+ - Known Allergies: {patient_info.get('allergies', 'N/A')}
548
+ """
549
+ return formatted.strip()
550
+
551
+ def _generate_fallback_report(self, patient_info: Dict[str, Any], visual_results: Dict[str, Any],
552
+ guideline_context: str) -> str:
553
+ """Generate fallback report when MedGemma is not available"""
554
+
555
+ wound_type = visual_results.get('wound_type', 'Unknown')
556
+ length = visual_results.get('length_cm', 0)
557
+ breadth = visual_results.get('breadth_cm', 0)
558
+ area = visual_results.get('surface_area_cm2', 0)
559
+
560
+ # Basic risk assessment
561
+ risk_factors = []
562
+ if patient_info.get('patient_age', 0) > 65:
563
+ risk_factors.append("Advanced age")
564
+ if 'yes' in str(patient_info.get('diabetic_status', '')).lower():
565
+ risk_factors.append("Diabetes mellitus")
566
+ if patient_info.get('pain_level', 0) > 6:
567
+ risk_factors.append("High pain level")
568
+ if area > 5:
569
+ risk_factors.append("Large wound size")
570
+
571
+ report = f"""
572
+ # SmartHeal AI Wound Assessment Report
573
+
574
+ ## Clinical Summary
575
+ **Patient:** {patient_info.get('patient_name', 'N/A')}, {patient_info.get('patient_age', 'N/A')} years old {patient_info.get('patient_gender', '')}
576
+
577
+ **Wound Characteristics:**
578
+ - Type: {wound_type}
579
+ - Location: {patient_info.get('wound_location', 'N/A')}
580
+ - Dimensions: {length} × {breadth} cm (Area: {area} cm²)
581
+ - Duration: {patient_info.get('wound_duration', 'N/A')}
582
+ - Pain Level: {patient_info.get('pain_level', 'N/A')}/10
583
+
584
+ ## Risk Assessment
585
+ **Identified Risk Factors:**
586
+ {chr(10).join(f'- {factor}' for factor in risk_factors) if risk_factors else '- No significant risk factors identified'}
587
+
588
+ ## Treatment Recommendations
589
+ **Wound Care:**
590
+ - Regular wound assessment and documentation
591
+ - Appropriate dressing selection based on wound characteristics
592
+ - Maintain moist wound environment
593
+ - Monitor for signs of infection
594
+
595
+ **Patient Management:**
596
+ - Pain management as indicated
597
+ - Nutritional assessment and optimization
598
+ - Patient education on wound care
599
+
600
+ ## Follow-up Plan
601
+ - Reassess wound in 1-2 weeks
602
+ - Monitor for signs of healing or deterioration
603
+ - Consider specialist referral if no improvement in 4 weeks
604
+
605
+ ---
606
+ **Report Generated by:** SmartHeal AI Fallback System v{self.model_version}
607
+ **Generated at:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
608
+ **Note:** This is a basic assessment. For comprehensive analysis, ensure all AI models are properly loaded.
609
+ """
610
+
611
+ logging.info("✅ Fallback report generated")
612
+ return report
613
+
src/enhanced_ui_components.py ADDED
@@ -0,0 +1,890 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import logging
3
+ from datetime import datetime
4
+ import time
5
+ from typing import Dict, Any, Optional, Tuple
6
+ import json
7
+ import os
8
+ from PIL import Image
9
+
10
+ from .enhanced_ai_processor import EnhancedAIProcessor
11
+ from .dashboard_database_manager import DashboardDatabaseManager
12
+ from .dashboard_api import DashboardIntegrationManager
13
+ from .auth import AuthManager
14
+
15
+ class EnhancedUIComponents:
16
+ """Enhanced UI components with dashboard integration and analytics tracking"""
17
+
18
+ def __init__(self, auth_manager: AuthManager, database_manager: DashboardDatabaseManager,
19
+ ai_processor: EnhancedAIProcessor):
20
+ """Initialize enhanced UI components"""
21
+ self.auth_manager = auth_manager
22
+ self.database_manager = database_manager
23
+ self.ai_processor = ai_processor
24
+ self.dashboard_integration = DashboardIntegrationManager(database_manager)
25
+
26
+ # Start dashboard integration
27
+ self.dashboard_integration.start_integration()
28
+
29
+ # UI styling
30
+ self.theme = gr.themes.Soft()
31
+ self.custom_css = self._load_custom_css()
32
+
33
+ # Session tracking
34
+ self.current_session = {}
35
+
36
+ logging.info("✅ Enhanced UI Components initialized with dashboard integration")
37
+
38
+ def _load_custom_css(self):
39
+ """Load custom CSS for the application"""
40
+ return """
41
+ /* Enhanced SmartHeal Application Styling */
42
+ .main-header {
43
+ text-align: center;
44
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
45
+ color: white;
46
+ padding: 2rem;
47
+ border-radius: 10px;
48
+ margin-bottom: 2rem;
49
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
50
+ }
51
+
52
+ .main-header h1 {
53
+ margin: 0;
54
+ font-size: 2.5rem;
55
+ font-weight: bold;
56
+ }
57
+
58
+ .main-header p {
59
+ margin: 0.5rem 0 0 0;
60
+ font-size: 1.1rem;
61
+ opacity: 0.9;
62
+ }
63
+
64
+ .integration-status {
65
+ background-color: #e8f5e8;
66
+ border: 1px solid #4caf50;
67
+ border-radius: 8px;
68
+ padding: 1rem;
69
+ margin: 1rem 0;
70
+ color: #2e7d32;
71
+ }
72
+
73
+ .analytics-info {
74
+ background-color: #e3f2fd;
75
+ border: 1px solid #2196f3;
76
+ border-radius: 8px;
77
+ padding: 1rem;
78
+ margin: 1rem 0;
79
+ color: #1565c0;
80
+ }
81
+
82
+ .session-info {
83
+ background-color: #fff3e0;
84
+ border: 1px solid #ff9800;
85
+ border-radius: 8px;
86
+ padding: 1rem;
87
+ margin: 1rem 0;
88
+ color: #ef6c00;
89
+ }
90
+
91
+ /* Section Headers */
92
+ .section-header {
93
+ background-color: #f8f9fa;
94
+ padding: 1rem;
95
+ border-radius: 8px;
96
+ border-left: 4px solid #007bff;
97
+ margin: 1rem 0;
98
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
99
+ }
100
+
101
+ .section-header h2 {
102
+ margin: 0;
103
+ color: #495057;
104
+ font-size: 1.3rem;
105
+ }
106
+
107
+ .section-header h3 {
108
+ margin: 0;
109
+ color: #6c757d;
110
+ font-size: 1.1rem;
111
+ }
112
+
113
+ /* Result Boxes */
114
+ .result-box {
115
+ background-color: #e7f3ff;
116
+ border: 1px solid #b3d9ff;
117
+ border-radius: 8px;
118
+ padding: 1rem;
119
+ margin: 1rem 0;
120
+ }
121
+
122
+ .success-box {
123
+ background-color: #d4edda;
124
+ border: 1px solid #c3e6cb;
125
+ border-radius: 8px;
126
+ padding: 1rem;
127
+ margin: 1rem 0;
128
+ color: #155724;
129
+ }
130
+
131
+ .warning-box {
132
+ background-color: #fff3cd;
133
+ border: 1px solid #ffeaa7;
134
+ border-radius: 8px;
135
+ padding: 1rem;
136
+ margin: 1rem 0;
137
+ color: #856404;
138
+ }
139
+
140
+ .error-box {
141
+ background-color: #ffe7e7;
142
+ border: 1px solid #ffb3b3;
143
+ border-radius: 8px;
144
+ padding: 1rem 0;
145
+ margin: 1rem 0;
146
+ color: #721c24;
147
+ }
148
+
149
+ /* Enhanced form styling */
150
+ .form-group {
151
+ margin-bottom: 1rem;
152
+ }
153
+
154
+ .metrics-display {
155
+ background-color: #f8f9fa;
156
+ border-radius: 8px;
157
+ padding: 1rem;
158
+ margin: 1rem 0;
159
+ border: 1px solid #dee2e6;
160
+ }
161
+
162
+ .processing-indicator {
163
+ background-color: #fff8e1;
164
+ border: 1px solid #ffcc02;
165
+ border-radius: 8px;
166
+ padding: 1rem;
167
+ margin: 1rem 0;
168
+ color: #f57f17;
169
+ text-align: center;
170
+ }
171
+ """
172
+
173
+ def create_interface(self):
174
+ """Create the enhanced Gradio interface with dashboard integration"""
175
+
176
+ with gr.Blocks(theme=self.theme, css=self.custom_css, title="SmartHeal AI - Enhanced") as interface:
177
+
178
+ # Header
179
+ gr.HTML("""
180
+ <div class="main-header">
181
+ <h1>🏥 SmartHeal AI - Enhanced Edition</h1>
182
+ <p>Advanced Wound Care Analysis with Real-time Dashboard Integration</p>
183
+ </div>
184
+ """)
185
+
186
+ # Integration status display
187
+ integration_status = gr.HTML(self._get_integration_status_html())
188
+
189
+ # Session info
190
+ session_info = gr.HTML(self._get_session_info_html())
191
+
192
+ with gr.Tabs():
193
+
194
+ # Authentication Tab
195
+ with gr.Tab("🔐 Authentication"):
196
+ with gr.Row():
197
+ with gr.Column():
198
+ gr.HTML("""
199
+ <div class="section-header">
200
+ <h2>User Authentication</h2>
201
+ <h3>Login to access SmartHeal AI analysis features</h3>
202
+ </div>
203
+ """)
204
+
205
+ username_input = gr.Textbox(
206
+ label="Username",
207
+ placeholder="Enter your username",
208
+ interactive=True
209
+ )
210
+ password_input = gr.Textbox(
211
+ label="Password",
212
+ type="password",
213
+ placeholder="Enter your password",
214
+ interactive=True
215
+ )
216
+ login_btn = gr.Button("Login", variant="primary")
217
+ logout_btn = gr.Button("Logout", variant="secondary")
218
+
219
+ auth_status = gr.HTML(value="<div class='warning-box'>Please login to continue</div>")
220
+
221
+ # Enhanced Analysis Tab
222
+ with gr.Tab("🔬 Wound Analysis"):
223
+ with gr.Row():
224
+ with gr.Column(scale=1):
225
+ gr.HTML("""
226
+ <div class="section-header">
227
+ <h2>Patient Information</h2>
228
+ <h3>Complete patient details for comprehensive analysis</h3>
229
+ </div>
230
+ """)
231
+
232
+ # Patient Information
233
+ patient_name = gr.Textbox(label="Patient Name", placeholder="Enter patient name")
234
+ patient_age = gr.Number(label="Patient Age", value=0, minimum=0, maximum=120)
235
+ patient_gender = gr.Dropdown(
236
+ label="Gender",
237
+ choices=["Male", "Female", "Other"],
238
+ value="Male"
239
+ )
240
+
241
+ # Wound Details
242
+ gr.HTML("""
243
+ <div class="section-header">
244
+ <h2>Wound Information</h2>
245
+ </div>
246
+ """)
247
+
248
+ wound_location = gr.Textbox(label="Wound Location", placeholder="e.g., Left heel, Right forearm")
249
+ wound_duration = gr.Textbox(label="Wound Duration", placeholder="e.g., 2 weeks, 1 month")
250
+ pain_level = gr.Slider(label="Pain Level (0-10)", minimum=0, maximum=10, value=0, step=1)
251
+
252
+ # Clinical Assessment
253
+ moisture_level = gr.Dropdown(
254
+ label="Moisture Level",
255
+ choices=["Dry", "Moist", "Wet", "Macerated"],
256
+ value="Moist"
257
+ )
258
+ infection_signs = gr.Dropdown(
259
+ label="Signs of Infection",
260
+ choices=["None", "Mild", "Moderate", "Severe"],
261
+ value="None"
262
+ )
263
+ diabetic_status = gr.Dropdown(
264
+ label="Diabetic Status",
265
+ choices=["No", "Type 1", "Type 2", "Unknown"],
266
+ value="No"
267
+ )
268
+
269
+ # Medical History
270
+ gr.HTML("""
271
+ <div class="section-header">
272
+ <h2>Medical History</h2>
273
+ </div>
274
+ """)
275
+
276
+ previous_treatment = gr.Textbox(
277
+ label="Previous Treatment",
278
+ placeholder="Describe any previous treatments",
279
+ lines=2
280
+ )
281
+ medical_history = gr.Textbox(
282
+ label="Medical History",
283
+ placeholder="Relevant medical conditions",
284
+ lines=2
285
+ )
286
+ medications = gr.Textbox(
287
+ label="Current Medications",
288
+ placeholder="List current medications",
289
+ lines=2
290
+ )
291
+ allergies = gr.Textbox(
292
+ label="Known Allergies",
293
+ placeholder="List any known allergies",
294
+ lines=2
295
+ )
296
+ additional_notes = gr.Textbox(
297
+ label="Additional Notes",
298
+ placeholder="Any additional relevant information",
299
+ lines=3
300
+ )
301
+
302
+ with gr.Column(scale=1):
303
+ gr.HTML("""
304
+ <div class="section-header">
305
+ <h2>Wound Image Analysis</h2>
306
+ <h3>Upload wound image for AI analysis</h3>
307
+ </div>
308
+ """)
309
+
310
+ # Image Upload
311
+ wound_image = gr.Image(
312
+ label="Wound Image",
313
+ type="pil",
314
+ height=400
315
+ )
316
+
317
+ # Analysis Controls
318
+ analyze_btn = gr.Button("🔍 Analyze Wound", variant="primary", size="lg")
319
+
320
+ # Processing indicator
321
+ processing_status = gr.HTML(visible=False)
322
+
323
+ # Analysis Metrics
324
+ analysis_metrics = gr.HTML(visible=False)
325
+
326
+ # Results Section
327
+ with gr.Row():
328
+ with gr.Column():
329
+ gr.HTML("""
330
+ <div class="section-header">
331
+ <h2>Analysis Results</h2>
332
+ <h3>Comprehensive AI-powered wound assessment</h3>
333
+ </div>
334
+ """)
335
+
336
+ # Visual Analysis Results
337
+ with gr.Row():
338
+ detection_image = gr.Image(label="Wound Detection", visible=False)
339
+ segmentation_image = gr.Image(label="Wound Segmentation", visible=False)
340
+
341
+ # Analysis Report
342
+ analysis_report = gr.Markdown(visible=False)
343
+
344
+ # Download Options
345
+ with gr.Row():
346
+ download_report = gr.File(label="Download Report", visible=False)
347
+ download_images = gr.File(label="Download Analysis Images", visible=False)
348
+
349
+ # Dashboard Integration Tab
350
+ with gr.Tab("📊 Dashboard Integration"):
351
+ gr.HTML("""
352
+ <div class="section-header">
353
+ <h2>Dashboard Integration Status</h2>
354
+ <h3>Real-time connection to SmartHeal Dashboard</h3>
355
+ </div>
356
+ """)
357
+
358
+ dashboard_status = gr.HTML()
359
+
360
+ with gr.Row():
361
+ refresh_status_btn = gr.Button("🔄 Refresh Status", variant="secondary")
362
+ view_analytics_btn = gr.Button("📈 View Analytics", variant="primary")
363
+
364
+ # Analytics Summary
365
+ analytics_summary = gr.HTML()
366
+
367
+ # Recent Activity
368
+ recent_activity = gr.HTML()
369
+
370
+ # Event Handlers
371
+
372
+ # Authentication
373
+ login_btn.click(
374
+ fn=self._handle_login,
375
+ inputs=[username_input, password_input],
376
+ outputs=[auth_status, session_info]
377
+ )
378
+
379
+ logout_btn.click(
380
+ fn=self._handle_logout,
381
+ outputs=[auth_status, session_info]
382
+ )
383
+
384
+ # Analysis
385
+ analyze_btn.click(
386
+ fn=self._start_analysis,
387
+ inputs=[],
388
+ outputs=[processing_status, analysis_metrics]
389
+ ).then(
390
+ fn=self._perform_enhanced_analysis,
391
+ inputs=[
392
+ patient_name, patient_age, patient_gender, wound_location, wound_duration,
393
+ pain_level, moisture_level, infection_signs, diabetic_status,
394
+ previous_treatment, medical_history, medications, allergies,
395
+ additional_notes, wound_image
396
+ ],
397
+ outputs=[
398
+ analysis_report, detection_image, segmentation_image,
399
+ download_report, download_images, processing_status,
400
+ analysis_metrics, session_info
401
+ ]
402
+ )
403
+
404
+ # Dashboard Integration
405
+ refresh_status_btn.click(
406
+ fn=self._refresh_dashboard_status,
407
+ outputs=[dashboard_status, analytics_summary]
408
+ )
409
+
410
+ view_analytics_btn.click(
411
+ fn=self._get_analytics_summary,
412
+ outputs=[analytics_summary, recent_activity]
413
+ )
414
+
415
+ # Auto-refresh integration status on load
416
+ interface.load(
417
+ fn=self._refresh_dashboard_status,
418
+ outputs=[dashboard_status, analytics_summary]
419
+ )
420
+
421
+ return interface
422
+
423
+ def _get_integration_status_html(self) -> str:
424
+ """Get HTML for integration status display"""
425
+ status = self.dashboard_integration.get_integration_status()
426
+
427
+ if status['api_running'] and status['database_connected']:
428
+ return """
429
+ <div class="integration-status">
430
+ ✅ <strong>Dashboard Integration Active</strong><br>
431
+ API Server: Running | Database: Connected | Real-time Analytics: Enabled
432
+ </div>
433
+ """
434
+ else:
435
+ return """
436
+ <div class="error-box">
437
+ ❌ <strong>Dashboard Integration Issues</strong><br>
438
+ Please check API server and database connection
439
+ </div>
440
+ """
441
+
442
+ def _get_session_info_html(self) -> str:
443
+ """Get HTML for session information display"""
444
+ if self.current_session:
445
+ user_info = self.current_session.get('user_info', {})
446
+ return f"""
447
+ <div class="session-info">
448
+ 👤 <strong>Active Session</strong><br>
449
+ User: {user_info.get('name', 'Unknown')} |
450
+ Role: {user_info.get('role', 'Unknown')} |
451
+ Session Started: {self.current_session.get('start_time', 'Unknown')}
452
+ </div>
453
+ """
454
+ else:
455
+ return """
456
+ <div class="warning-box">
457
+ ⚠️ <strong>No Active Session</strong><br>
458
+ Please login to start tracking your analysis session
459
+ </div>
460
+ """
461
+
462
+ def _handle_login(self, username: str, password: str) -> Tuple[str, str]:
463
+ """Handle user login with session tracking"""
464
+ try:
465
+ if not username or not password:
466
+ return (
467
+ "<div class='error-box'>❌ Please enter both username and password</div>",
468
+ self._get_session_info_html()
469
+ )
470
+
471
+ # Authenticate user
472
+ user_info = self.auth_manager.authenticate_user(username, password)
473
+
474
+ if user_info:
475
+ # Start session tracking
476
+ self.current_session = {
477
+ 'user_info': user_info,
478
+ 'start_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
479
+ 'session_id': f"session_{int(time.time())}",
480
+ 'analyses_count': 0
481
+ }
482
+
483
+ return (
484
+ f"<div class='success-box'>✅ Welcome, {user_info.get('name', username)}! You are now logged in.</div>",
485
+ self._get_session_info_html()
486
+ )
487
+ else:
488
+ return (
489
+ "<div class='error-box'>❌ Invalid username or password</div>",
490
+ self._get_session_info_html()
491
+ )
492
+
493
+ except Exception as e:
494
+ logging.error(f"Login error: {e}")
495
+ return (
496
+ f"<div class='error-box'>❌ Login failed: {str(e)}</div>",
497
+ self._get_session_info_html()
498
+ )
499
+
500
+ def _handle_logout(self) -> Tuple[str, str]:
501
+ """Handle user logout"""
502
+ try:
503
+ if self.current_session:
504
+ # Log session end
505
+ session_duration = time.time() - datetime.strptime(
506
+ self.current_session['start_time'], '%Y-%m-%d %H:%M:%S'
507
+ ).timestamp()
508
+
509
+ session_data = {
510
+ 'user_id': self.current_session['user_info'].get('id'),
511
+ 'session_duration': round(session_duration / 60, 2), # Convert to minutes
512
+ 'analyses_count': self.current_session.get('analyses_count', 0)
513
+ }
514
+
515
+ # Clear session
516
+ self.current_session = {}
517
+
518
+ return (
519
+ "<div class='warning-box'>👋 You have been logged out successfully</div>",
520
+ self._get_session_info_html()
521
+ )
522
+ else:
523
+ return (
524
+ "<div class='warning-box'>⚠️ No active session to logout</div>",
525
+ self._get_session_info_html()
526
+ )
527
+
528
+ except Exception as e:
529
+ logging.error(f"Logout error: {e}")
530
+ return (
531
+ f"<div class='error-box'>❌ Logout error: {str(e)}</div>",
532
+ self._get_session_info_html()
533
+ )
534
+
535
+ def _start_analysis(self) -> Tuple[str, str]:
536
+ """Start analysis process with status indicators"""
537
+ return (
538
+ """
539
+ <div class="processing-indicator" style="display: block;">
540
+ 🔄 <strong>Analysis in Progress...</strong><br>
541
+ Please wait while we process your wound image and patient data
542
+ </div>
543
+ """,
544
+ """
545
+ <div class="metrics-display">
546
+ <strong>Analysis Metrics:</strong><br>
547
+ Status: Initializing...<br>
548
+ Processing Time: 0.0s<br>
549
+ Models Loading: ⏳
550
+ </div>
551
+ """
552
+ )
553
+
554
+ def _perform_enhanced_analysis(self, patient_name: str, patient_age: int, patient_gender: str,
555
+ wound_location: str, wound_duration: str, pain_level: int,
556
+ moisture_level: str, infection_signs: str, diabetic_status: str,
557
+ previous_treatment: str, medical_history: str, medications: str,
558
+ allergies: str, additional_notes: str, wound_image) -> Tuple:
559
+ """Perform enhanced analysis with dashboard integration"""
560
+
561
+ start_time = time.time()
562
+
563
+ try:
564
+ # Check authentication
565
+ if not self.current_session:
566
+ return (
567
+ "❌ **Authentication Required**\n\nPlease login before performing analysis.",
568
+ None, None, None, None,
569
+ "<div class='error-box'>❌ Authentication required</div>",
570
+ "<div class='error-box'>Please login to continue</div>",
571
+ self._get_session_info_html()
572
+ )
573
+
574
+ # Validate inputs
575
+ if not wound_image:
576
+ return (
577
+ "❌ **Image Required**\n\nPlease upload a wound image for analysis.",
578
+ None, None, None, None,
579
+ "<div class='error-box'>❌ Wound image required</div>",
580
+ "<div class='error-box'>Please upload an image</div>",
581
+ self._get_session_info_html()
582
+ )
583
+
584
+ if not patient_name.strip():
585
+ return (
586
+ "❌ **Patient Name Required**\n\nPlease enter the patient's name.",
587
+ None, None, None, None,
588
+ "<div class='error-box'>❌ Patient name required</div>",
589
+ "<div class='error-box'>Please enter patient name</div>",
590
+ self._get_session_info_html()
591
+ )
592
+
593
+ # Prepare patient information
594
+ patient_info = {
595
+ 'patient_name': patient_name,
596
+ 'patient_age': patient_age,
597
+ 'patient_gender': patient_gender,
598
+ 'wound_location': wound_location,
599
+ 'wound_duration': wound_duration,
600
+ 'pain_level': pain_level,
601
+ 'moisture_level': moisture_level,
602
+ 'infection_signs': infection_signs,
603
+ 'diabetic_status': diabetic_status,
604
+ 'previous_treatment': previous_treatment,
605
+ 'medical_history': medical_history,
606
+ 'medications': medications,
607
+ 'allergies': allergies,
608
+ 'additional_notes': additional_notes
609
+ }
610
+
611
+ # Save questionnaire response to dashboard database
612
+ user_id = self.current_session['user_info'].get('id')
613
+ questionnaire_id = self.database_manager.save_questionnaire_response(patient_info, user_id)
614
+
615
+ if not questionnaire_id:
616
+ logging.warning("Failed to save questionnaire response")
617
+
618
+ # Save wound image
619
+ image_id = None
620
+ if questionnaire_id:
621
+ image_id = self.database_manager.save_wound_image(questionnaire_id, wound_image, "wound_analysis.jpg")
622
+
623
+ # Perform comprehensive AI analysis
624
+ analysis_results = self.ai_processor.perform_comprehensive_analysis(wound_image, patient_info)
625
+
626
+ processing_time = analysis_results.get('processing_time', 0)
627
+
628
+ # Save AI analysis results to dashboard database
629
+ analysis_data = {
630
+ 'questionnaire_id': questionnaire_id,
631
+ 'image_id': image_id,
632
+ 'analysis_data': analysis_results,
633
+ 'summary': analysis_results.get('report', '')[:1000], # First 1000 chars as summary
634
+ 'recommendations': analysis_results.get('report', ''),
635
+ 'risk_score': analysis_results.get('risk_score', 0),
636
+ 'processing_time': processing_time,
637
+ 'model_version': analysis_results.get('model_version', 'v1.0'),
638
+ 'visual_results': analysis_results.get('visual_results', {})
639
+ }
640
+
641
+ analysis_id = self.database_manager.save_ai_analysis(analysis_data)
642
+
643
+ # Log analysis session
644
+ session_data = {
645
+ 'user_id': user_id,
646
+ 'questionnaire_id': questionnaire_id,
647
+ 'image_id': image_id,
648
+ 'analysis_id': analysis_id,
649
+ 'session_duration': processing_time
650
+ }
651
+
652
+ self.dashboard_integration.log_analysis_session(session_data)
653
+
654
+ # Log bot interaction
655
+ interaction_data = {
656
+ 'patient_id': None, # Would need to get from patients table
657
+ 'practitioner_id': user_id,
658
+ 'input_text': f"Wound analysis for {patient_name}",
659
+ 'output_text': analysis_results.get('report', '')[:500], # First 500 chars
660
+ 'wound_image_url': f"uploads/wound_analysis_{int(time.time())}.jpg",
661
+ 'interaction_type': 'wound_analysis'
662
+ }
663
+
664
+ self.dashboard_integration.log_bot_interaction(interaction_data)
665
+
666
+ # Update session count
667
+ self.current_session['analyses_count'] = self.current_session.get('analyses_count', 0) + 1
668
+
669
+ # Prepare results for display
670
+ visual_results = analysis_results.get('visual_results', {})
671
+ report = analysis_results.get('report', 'Analysis completed but no report generated.')
672
+
673
+ # Get analysis images
674
+ detection_image = visual_results.get('detection_image_pil')
675
+ segmentation_image = visual_results.get('segmentation_image_pil')
676
+
677
+ # Create downloadable report
678
+ report_file = self._create_report_file(analysis_results, patient_info)
679
+
680
+ # Create metrics display
681
+ metrics_html = f"""
682
+ <div class="metrics-display">
683
+ <strong>Analysis Completed Successfully!</strong><br>
684
+ Processing Time: {processing_time}s<br>
685
+ Risk Score: {analysis_results.get('risk_score', 0)}/100<br>
686
+ Wound Type: {visual_results.get('wound_type', 'Unknown')}<br>
687
+ Surface Area: {visual_results.get('surface_area_cm2', 0)} cm²<br>
688
+ Model Version: {analysis_results.get('model_version', 'v1.0')}<br>
689
+ Dashboard Integration: ✅ Active
690
+ </div>
691
+ """
692
+
693
+ success_status = f"""
694
+ <div class="success-box">
695
+ ✅ <strong>Analysis Completed Successfully!</strong><br>
696
+ Processing Time: {processing_time}s | Risk Score: {analysis_results.get('risk_score', 0)}/100<br>
697
+ Results saved to dashboard for real-time analytics
698
+ </div>
699
+ """
700
+
701
+ return (
702
+ report,
703
+ detection_image,
704
+ segmentation_image,
705
+ report_file,
706
+ None, # Images download placeholder
707
+ success_status,
708
+ metrics_html,
709
+ self._get_session_info_html()
710
+ )
711
+
712
+ except Exception as e:
713
+ processing_time = time.time() - start_time
714
+ error_message = str(e)
715
+ logging.error(f"Analysis error: {error_message}")
716
+
717
+ error_status = f"""
718
+ <div class="error-box">
719
+ ❌ <strong>Analysis Failed</strong><br>
720
+ Error: {error_message}<br>
721
+ Processing Time: {processing_time:.2f}s
722
+ </div>
723
+ """
724
+
725
+ error_metrics = f"""
726
+ <div class="error-box">
727
+ <strong>Analysis Error:</strong><br>
728
+ Status: Failed<br>
729
+ Processing Time: {processing_time:.2f}s<br>
730
+ Error: {error_message}
731
+ </div>
732
+ """
733
+
734
+ return (
735
+ f"❌ **Analysis Failed**\n\n**Error:** {error_message}\n\nPlease check your inputs and try again.",
736
+ None, None, None, None,
737
+ error_status,
738
+ error_metrics,
739
+ self._get_session_info_html()
740
+ )
741
+
742
+ def _create_report_file(self, analysis_results: Dict[str, Any], patient_info: Dict[str, Any]) -> str:
743
+ """Create downloadable report file"""
744
+ try:
745
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
746
+ filename = f"wound_analysis_report_{timestamp}.md"
747
+ filepath = os.path.join("uploads", filename)
748
+
749
+ # Ensure uploads directory exists
750
+ os.makedirs("uploads", exist_ok=True)
751
+
752
+ # Create comprehensive report
753
+ report_content = f"""# SmartHeal AI Wound Analysis Report
754
+
755
+ **Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
756
+ **Patient:** {patient_info.get('patient_name', 'N/A')}
757
+ **Analysis ID:** {timestamp}
758
+
759
+ ## Patient Information
760
+ - **Name:** {patient_info.get('patient_name', 'N/A')}
761
+ - **Age:** {patient_info.get('patient_age', 'N/A')} years
762
+ - **Gender:** {patient_info.get('patient_gender', 'N/A')}
763
+ - **Wound Location:** {patient_info.get('wound_location', 'N/A')}
764
+ - **Wound Duration:** {patient_info.get('wound_duration', 'N/A')}
765
+ - **Pain Level:** {patient_info.get('pain_level', 'N/A')}/10
766
+
767
+ ## Analysis Results
768
+ {analysis_results.get('report', 'No report generated')}
769
+
770
+ ## Technical Details
771
+ - **Processing Time:** {analysis_results.get('processing_time', 0)}s
772
+ - **Risk Score:** {analysis_results.get('risk_score', 0)}/100
773
+ - **Model Version:** {analysis_results.get('model_version', 'Unknown')}
774
+ - **Analysis Timestamp:** {analysis_results.get('analysis_timestamp', 'Unknown')}
775
+
776
+ ---
777
+ *Generated by SmartHeal AI Enhanced Edition with Dashboard Integration*
778
+ """
779
+
780
+ with open(filepath, 'w', encoding='utf-8') as f:
781
+ f.write(report_content)
782
+
783
+ return filepath
784
+
785
+ except Exception as e:
786
+ logging.error(f"Error creating report file: {e}")
787
+ return None
788
+
789
+ def _refresh_dashboard_status(self) -> Tuple[str, str]:
790
+ """Refresh dashboard integration status"""
791
+ try:
792
+ status = self.dashboard_integration.get_integration_status()
793
+ analytics_data = self.database_manager.get_analytics_data()
794
+
795
+ if status['api_running'] and status['database_connected']:
796
+ status_html = f"""
797
+ <div class="integration-status">
798
+ ✅ <strong>Dashboard Integration Active</strong><br>
799
+ API Server: Running on port 5001<br>
800
+ Database: Connected<br>
801
+ Last Updated: {status['timestamp']}<br>
802
+ <a href="http://localhost:5001/api/health" target="_blank">🔗 Test API Health</a>
803
+ </div>
804
+ """
805
+ else:
806
+ status_html = f"""
807
+ <div class="error-box">
808
+ ❌ <strong>Dashboard Integration Issues</strong><br>
809
+ API Running: {status['api_running']}<br>
810
+ Database Connected: {status['database_connected']}<br>
811
+ Last Checked: {status['timestamp']}
812
+ </div>
813
+ """
814
+
815
+ analytics_html = f"""
816
+ <div class="analytics-info">
817
+ 📊 <strong>Analytics Summary</strong><br>
818
+ Total Analyses: {analytics_data.get('total_analyses', 0)}<br>
819
+ Average Processing Time: {analytics_data.get('avg_processing_time', 0)}s<br>
820
+ High Risk Cases: {analytics_data.get('high_risk_count', 0)}<br>
821
+ Average Risk Score: {analytics_data.get('avg_risk_score', 0)}<br>
822
+ Analyses Today: {analytics_data.get('analyses_today', 0)}
823
+ </div>
824
+ """
825
+
826
+ return status_html, analytics_html
827
+
828
+ except Exception as e:
829
+ logging.error(f"Error refreshing dashboard status: {e}")
830
+ return (
831
+ f"<div class='error-box'>❌ Error refreshing status: {str(e)}</div>",
832
+ "<div class='error-box'>❌ Unable to load analytics</div>"
833
+ )
834
+
835
+ def _get_analytics_summary(self) -> Tuple[str, str]:
836
+ """Get comprehensive analytics summary"""
837
+ try:
838
+ analytics_data = self.database_manager.get_analytics_data()
839
+ interaction_history = self.database_manager.get_interaction_history(10)
840
+
841
+ # Create detailed analytics HTML
842
+ analytics_html = f"""
843
+ <div class="analytics-info">
844
+ <h3>📈 Comprehensive Analytics</h3>
845
+ <strong>Analysis Statistics:</strong><br>
846
+ • Total Analyses: {analytics_data.get('total_analyses', 0)}<br>
847
+ • Analyses Today: {analytics_data.get('analyses_today', 0)}<br>
848
+ • Analyses This Week: {analytics_data.get('analyses_this_week', 0)}<br>
849
+ • Average Processing Time: {analytics_data.get('avg_processing_time', 0)}s<br>
850
+ • Average Risk Score: {analytics_data.get('avg_risk_score', 0)}/100<br>
851
+ <br>
852
+ <strong>Risk Distribution:</strong><br>
853
+ • High Risk Cases: {analytics_data.get('high_risk_count', 0)}<br>
854
+ • Unique Questionnaires: {analytics_data.get('unique_questionnaires', 0)}<br>
855
+ • Analyses with Images: {analytics_data.get('analyses_with_images', 0)}<br>
856
+ </div>
857
+ """
858
+
859
+ # Create recent activity HTML
860
+ activity_html = "<div class='result-box'><h3>🕒 Recent Activity</h3>"
861
+
862
+ if interaction_history:
863
+ activity_html += "<ul>"
864
+ for interaction in interaction_history[:5]:
865
+ timestamp = interaction.get('interacted_at', 'Unknown')
866
+ if isinstance(timestamp, str):
867
+ try:
868
+ timestamp = datetime.fromisoformat(timestamp.replace('Z', '+00:00')).strftime('%Y-%m-%d %H:%M')
869
+ except:
870
+ pass
871
+
872
+ activity_html += f"""
873
+ <li><strong>{timestamp}</strong> - {interaction.get('interaction_type', 'Unknown')}
874
+ (Patient: {interaction.get('patient_name', 'Unknown')})</li>
875
+ """
876
+ activity_html += "</ul>"
877
+ else:
878
+ activity_html += "<p>No recent activity found.</p>"
879
+
880
+ activity_html += "</div>"
881
+
882
+ return analytics_html, activity_html
883
+
884
+ except Exception as e:
885
+ logging.error(f"Error getting analytics summary: {e}")
886
+ return (
887
+ f"<div class='error-box'>❌ Error loading analytics: {str(e)}</div>",
888
+ "<div class='error-box'>❌ Unable to load recent activity</div>"
889
+ )
890
+
src/evaluation.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:990fcd18367f71c1c4729b492d21c18a5b86108a6c86faa98ba63945f29f5816
3
+ size 651978
src/segmentation_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cd892723d03aa943b719f97f659bddbaf02ba2670606e2a7b0b553ce661d2f62
3
+ size 65345400
static/style.css ADDED
@@ -0,0 +1,391 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ================= FORCE LIGHT MODE AND TEXT VISIBILITY ================= */
2
+ /* Override Gradio's root variables and force light mode */
3
+ :root {
4
+ --background-fill-primary: #FFFFFF !important;
5
+ --background-fill-secondary: #F7FAFC !important;
6
+ --text-color-primary: #1A202C !important;
7
+ --text-color-secondary: #4A5568 !important;
8
+ --color-accent: #E53E3E !important;
9
+ --color-accent-soft: #FED7D7 !important;
10
+ --block-background-fill: #FFFFFF !important;
11
+ --block-border-color: #E2E8F0 !important;
12
+ --block-title-text-color: #1A202C !important;
13
+ --block-label-text-color: #1A202C !important;
14
+ --block-info-text-color: #4A5568 !important;
15
+ --input-background-fill: #FFFFFF !important;
16
+ --input-border-color: #CBD5E0 !important;
17
+ --input-text-color: #1A202C !important;
18
+ --button-primary-background-fill: #E53E3E !important;
19
+ --button-primary-text-color: #FFFFFF !important;
20
+ }
21
+
22
+ /* Force light theme on the entire application */
23
+ .gradio-container,
24
+ .gradio-container *,
25
+ .gr-app,
26
+ .gr-app * {
27
+ color-scheme: light !important;
28
+ }
29
+
30
+ /* ------------------- AGGRESSIVE TEXT VISIBILITY ------------------- */
31
+ /* Force text color on ALL elements */
32
+ .gradio-container,
33
+ .gradio-container *:not(.gr-button):not(.medical-header *),
34
+ .gr-app,
35
+ .gr-app *:not(.gr-button):not(.medical-header *) {
36
+ color: #1A202C !important;
37
+ background-color: transparent !important;
38
+ }
39
+
40
+ /* Specific overrides for common Gradio elements */
41
+ .gr-block,
42
+ .gr-form,
43
+ .gr-box,
44
+ .gr-panel {
45
+ background-color: #FFFFFF !important;
46
+ color: #1A202C !important;
47
+ border: 1px solid #E2E8F0 !important;
48
+ }
49
+
50
+ /* Input elements */
51
+ .gr-textbox,
52
+ .gr-textbox input,
53
+ .gr-dropdown,
54
+ .gr-dropdown select,
55
+ .gr-number,
56
+ .gr-number input,
57
+ .gr-file,
58
+ .gr-radio,
59
+ .gr-checkbox {
60
+ background-color: #FFFFFF !important;
61
+ color: #1A202C !important;
62
+ border: 2px solid #CBD5E0 !important;
63
+ border-radius: 8px !important;
64
+ }
65
+
66
+ /* Labels and text */
67
+ label,
68
+ .gr-input-label,
69
+ .gr-radio label,
70
+ .gr-checkbox label,
71
+ [data-testid="block-label"],
72
+ .gr-form label,
73
+ .gr-markdown,
74
+ .gr-markdown *,
75
+ .gr-output,
76
+ .gr-output * {
77
+ color: #1A202C !important;
78
+ background-color: transparent !important;
79
+ }
80
+
81
+ /* Buttons */
82
+ button.gr-button,
83
+ button.gr-button-primary {
84
+ background: linear-gradient(135deg, #E53E3E 0%, #C53030 100%) !important;
85
+ color: #FFFFFF !important;
86
+ border: none !important;
87
+ border-radius: 8px !important;
88
+ font-weight: 600 !important;
89
+ padding: 12px 24px !important;
90
+ min-height: 44px !important;
91
+ font-size: 1rem !important;
92
+ letter-spacing: 0.3px !important;
93
+ }
94
+
95
+ button.gr-button:hover,
96
+ button.gr-button-primary:hover {
97
+ background: linear-gradient(135deg, #C53030 0%, #B91C1C 100%) !important;
98
+ transform: translateY(-1px) !important;
99
+ box-shadow: 0 4px 12px rgba(229, 62, 62, 0.3) !important;
100
+ }
101
+
102
+ button.gr-button:disabled {
103
+ background: #CBD5E0 !important;
104
+ color: #1A202C !important;
105
+ }
106
+
107
+ /* ------------------- GENERAL LAYOUT ------------------- */
108
+ .gradio-container {
109
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif !important;
110
+ background: #FFFFFF !important;
111
+ color: #1A202C !important;
112
+ line-height: 1.6 !important;
113
+ font-size: 16px !important;
114
+ text-rendering: optimizeLegibility !important;
115
+ -webkit-font-smoothing: antialiased !important;
116
+ -moz-osx-font-smoothing: grayscale !important;
117
+ }
118
+
119
+ /* Hide default footer */
120
+ footer { display: none !important; }
121
+
122
+ /* ------------------- HEADER ------------------- */
123
+ .medical-header {
124
+ background: linear-gradient(135deg, #1b5fc1 0%, #174ea6 100%) !important;
125
+ padding: 24px !important;
126
+ text-align: left !important;
127
+ border-radius: 16px !important;
128
+ margin-bottom: 32px !important;
129
+ box-shadow: 0 8px 24px rgba(27, 95, 193, 0.2) !important;
130
+ display: flex !important;
131
+ align-items: center !important;
132
+ flex-wrap: wrap !important;
133
+ }
134
+
135
+ .medical-header h1 {
136
+ color: #FFFFFF !important;
137
+ font-size: 2.75rem !important;
138
+ font-weight: 700 !important;
139
+ margin: 0 !important;
140
+ line-height: 1.2 !important;
141
+ letter-spacing: -0.025em !important;
142
+ }
143
+
144
+ .medical-header p {
145
+ color: #FFFFFF !important;
146
+ font-size: 1.125rem !important;
147
+ margin: 8px 0 0 0 !important;
148
+ font-weight: 400 !important;
149
+ line-height: 1.5 !important;
150
+ opacity: 0.95 !important;
151
+ }
152
+
153
+ .logo-container {
154
+ display: flex !important;
155
+ align-items: center !important;
156
+ justify-content: flex-start !important;
157
+ flex-wrap: wrap !important;
158
+ }
159
+
160
+ .logo {
161
+ width: 64px !important;
162
+ height: 64px !important;
163
+ margin-right: 24px !important;
164
+ border-radius: 16px !important;
165
+ background: white !important;
166
+ padding: 8px !important;
167
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1) !important;
168
+ flex-shrink: 0 !important;
169
+ }
170
+
171
+ /* ------------------- DISCLAIMER BOX ------------------- */
172
+ .disclaimer-box {
173
+ border: 2px solid #FEB2B2 !important;
174
+ background-color: #FFF5F5 !important;
175
+ padding: 24px !important;
176
+ border-radius: 16px !important;
177
+ margin: 24px 0 !important;
178
+ box-shadow: 0 2px 8px rgba(254, 178, 178, 0.1) !important;
179
+ }
180
+
181
+ .disclaimer-box h3 {
182
+ margin-top: 0 !important;
183
+ margin-bottom: 12px !important;
184
+ color: #C53030 !important;
185
+ font-size: 1.25rem !important;
186
+ font-weight: 700 !important;
187
+ line-height: 1.4 !important;
188
+ }
189
+
190
+ .disclaimer-box p {
191
+ color: #1A202C !important;
192
+ margin-bottom: 0 !important;
193
+ font-size: 1rem !important;
194
+ line-height: 1.6 !important;
195
+ font-weight: 400 !important;
196
+ }
197
+
198
+ /* ------------------- CARDS & TITLES ------------------- */
199
+ .medical-card-title {
200
+ background: white !important;
201
+ border-radius: 16px !important;
202
+ padding: 24px !important;
203
+ box-shadow: 0 4px 16px rgba(0,0,0,0.08) !important;
204
+ border: 1px solid #E2E8F0 !important;
205
+ margin: 20px 0 !important;
206
+ color: #1A202C !important;
207
+ font-size: 1.5rem !important;
208
+ font-weight: 600 !important;
209
+ line-height: 1.4 !important;
210
+ text-align: center !important;
211
+ }
212
+
213
+ /* ------------------- MARKDOWN OUTPUT ------------------- */
214
+ .gr-markdown {
215
+ background-color: #FFFFFF !important;
216
+ color: #1A202C !important;
217
+ padding: 16px !important;
218
+ border-radius: 8px !important;
219
+ border: 1px solid #E2E8F0 !important;
220
+ }
221
+
222
+ .gr-markdown h1,
223
+ .gr-markdown h2,
224
+ .gr-markdown h3,
225
+ .gr-markdown h4,
226
+ .gr-markdown h5,
227
+ .gr-markdown h6 {
228
+ color: #1A202C !important;
229
+ font-weight: 700 !important;
230
+ line-height: 1.3 !important;
231
+ margin-top: 1.5em !important;
232
+ margin-bottom: 0.5em !important;
233
+ }
234
+
235
+ .gr-markdown h1 { font-size: 2rem !important; }
236
+ .gr-markdown h2 { font-size: 1.75rem !important; }
237
+ .gr-markdown h3 { font-size: 1.5rem !important; }
238
+ .gr-markdown h4 { font-size: 1.25rem !important; }
239
+
240
+ .gr-markdown p,
241
+ .gr-markdown li,
242
+ .gr-markdown span,
243
+ .gr-markdown div {
244
+ color: #1A202C !important;
245
+ font-size: 1rem !important;
246
+ line-height: 1.7 !important;
247
+ margin-bottom: 1em !important;
248
+ }
249
+
250
+ .gr-markdown ul,
251
+ .gr-markdown ol {
252
+ padding-left: 1.5em !important;
253
+ margin-bottom: 1em !important;
254
+ }
255
+
256
+ .gr-markdown li {
257
+ margin-bottom: 0.5em !important;
258
+ }
259
+
260
+ .gr-markdown strong,
261
+ .gr-markdown b {
262
+ color: #1A202C !important;
263
+ font-weight: 700 !important;
264
+ }
265
+
266
+ .gr-markdown em,
267
+ .gr-markdown i {
268
+ color: #1A202C !important;
269
+ font-style: italic !important;
270
+ }
271
+
272
+ /* ------------------- IMAGE UPLOAD ------------------- */
273
+ .gr-image {
274
+ border: 3px dashed #CBD5E0 !important;
275
+ border-radius: 16px !important;
276
+ background-color: #F7FAFC !important;
277
+ transition: all 0.2s ease !important;
278
+ }
279
+
280
+ .gr-image:hover {
281
+ border-color: #E53E3E !important;
282
+ background-color: #FFF5F5 !important;
283
+ }
284
+
285
+ /* ------------------- RADIO BUTTONS ------------------- */
286
+ .gr-radio input[type="radio"] {
287
+ margin-right: 8px !important;
288
+ transform: scale(1.2) !important;
289
+ }
290
+
291
+ .gr-radio label {
292
+ display: flex !important;
293
+ align-items: center !important;
294
+ padding: 8px 0 !important;
295
+ font-size: 1rem !important;
296
+ line-height: 1.5 !important;
297
+ cursor: pointer !important;
298
+ color: #1A202C !important;
299
+ }
300
+
301
+ /* ------------------- TABS ------------------- */
302
+ .gr-tab {
303
+ color: #1A202C !important;
304
+ font-weight: 500 !important;
305
+ font-size: 1rem !important;
306
+ padding: 12px 20px !important;
307
+ background-color: #F7FAFC !important;
308
+ }
309
+
310
+ .gr-tab.selected {
311
+ color: #E53E3E !important;
312
+ font-weight: 600 !important;
313
+ border-bottom: 2px solid #E53E3E !important;
314
+ background-color: #FFFFFF !important;
315
+ }
316
+
317
+ /* ------------------- FOOTER ------------------- */
318
+ .medical-footer {
319
+ text-align: center !important;
320
+ padding: 24px !important;
321
+ color: #4A5568 !important;
322
+ font-size: 0.95rem !important;
323
+ line-height: 1.6 !important;
324
+ border-top: 1px solid #E2E8F0 !important;
325
+ margin-top: 40px !important;
326
+ background-color: #F7FAFC !important;
327
+ border-radius: 12px !important;
328
+ }
329
+
330
+ /* ------------------- RESPONSIVE DESIGN ------------------- */
331
+ @media (max-width: 768px) {
332
+ .medical-header {
333
+ padding: 20px !important;
334
+ text-align: center !important;
335
+ }
336
+
337
+ .medical-header h1 {
338
+ font-size: 2.25rem !important;
339
+ }
340
+
341
+ .medical-header p {
342
+ font-size: 1rem !important;
343
+ }
344
+
345
+ .logo {
346
+ margin-right: 16px !important;
347
+ margin-bottom: 16px !important;
348
+ }
349
+
350
+ .medical-card-title {
351
+ font-size: 1.25rem !important;
352
+ padding: 20px !important;
353
+ }
354
+
355
+ .gr-button {
356
+ padding: 12px 24px !important;
357
+ font-size: 0.95rem !important;
358
+ }
359
+ }
360
+
361
+ /* ------------------- ACCESSIBILITY IMPROVEMENTS ------------------- */
362
+ .gr-button:focus {
363
+ outline: 3px solid rgba(229, 62, 62, 0.5) !important;
364
+ outline-offset: 2px !important;
365
+ }
366
+
367
+ .gr-textbox:focus,
368
+ .gr-dropdown:focus,
369
+ .gr-file:focus,
370
+ .gr-number:focus {
371
+ outline: 3px solid rgba(229, 62, 62, 0.3) !important;
372
+ outline-offset: 2px !important;
373
+ border-color: #E53E3E !important;
374
+ }
375
+
376
+ /* Success/Error status styling */
377
+ .status-success {
378
+ color: #22C55E !important;
379
+ background-color: #F0FDF4 !important;
380
+ border: 1px solid #BBF7D0 !important;
381
+ padding: 12px !important;
382
+ border-radius: 8px !important;
383
+ }
384
+
385
+ .status-error {
386
+ color: #EF4444 !important;
387
+ background-color: #FEF2F2 !important;
388
+ border: 1px solid #FECACA !important;
389
+ padding: 12px !important;
390
+ border-radius: 8px !important;
391
+ }