Innovator89 commited on
Commit
0ffe2e1
·
verified ·
1 Parent(s): fe23844

Update user_management.py

Browse files
Files changed (1) hide show
  1. user_management.py +95 -1016
user_management.py CHANGED
@@ -1,1026 +1,105 @@
1
- import io
2
- import os
3
- import re
4
- from datetime import datetime, timedelta
5
-
6
- import numpy as np
7
- import pandas as pd
8
- import requests
9
  import streamlit as st
10
- from dotenv import load_dotenv
11
- from fuzzywuzzy import fuzz # Make sure fuzzywuzzy is installed (pip install fuzzywuzzy python-levenshtein)
12
- from groq import Groq
13
- from reportlab.lib.pagesizes import letter
14
- from reportlab.pdfgen import canvas
15
-
16
- # Import database and user management functions
17
- from database import get_user_history
18
- from user_management import show_user_management, save_history_if_logged_in
19
- from utils import extract_keyword # Ensure utils.py has the extract_keyword function
20
-
21
- # Load environment variables from .env.local for local development.
22
- # On Hugging Face Spaces, st.secrets will be used.
23
- load_dotenv(dotenv_path=".env.local")
24
-
25
- # --- Configuration and Initial Setup (MUST BE FIRST) ---
26
- st.set_page_config(page_title="MediBot - Health Assistant", page_icon="🏥", layout="wide")
27
-
28
- # Initialize Groq client
29
- groq_client = None
30
- GROQ_AVAILABLE = False
31
- try:
32
- # Prefer st.secrets for Hugging Face Spaces deployment
33
- GROQ_API_KEY = st.secrets.get("GROQ_API_KEY")
34
- if not GROQ_API_KEY:
35
- # Fallback to environment variable for local development if not in secrets
36
- # You might not want to write raw file content to the app in production
37
- # f = open(".env", "r")
38
- # st.write(f.read())
39
- # f.close()
40
- GROQ_API_KEY = os.getenv("GROQ_API_KEY")
41
- if GROQ_API_KEY:
42
- groq_client = Groq(api_key=GROQ_API_KEY)
43
- GROQ_AVAILABLE = True
44
- else:
45
- st.error("Groq API Key not found. Groq chatbot will not be available.")
46
- except Exception as e:
47
- st.error(f"Error initializing Groq client: {e}. Groq chatbot will not be available.")
48
 
49
- # Initialize Hugging Face Inference API client details for DATEXIS/CORe-clinical-diagnosis-prediction
50
- HF_MODEL_AVAILABLE = False
51
- HF_API_TOKEN = None
 
52
  try:
53
- # Assuming 'med_model' is the name of your Hugging Face API key in st.secrets
54
- HF_API_TOKEN = st.secrets.get("med_model")
55
- if not HF_API_TOKEN:
56
- # Fallback to environment variable for local development
57
- HF_API_TOKEN = os.getenv("MED_MODEL") # Using MED_MODEL for consistency with environment variables
58
- if HF_API_TOKEN:
59
- HF_MODEL_AVAILABLE = True
60
- else:
61
- st.warning("Hugging Face 'med_model' API Key not found. Clinical diagnosis assessment will not be available.")
62
- except Exception as e:
63
- st.warning(f"Error retrieving Hugging Face API key: {e}. Clinical diagnosis assessment will not be available.")
64
-
65
- # Initialize session state variables
66
- if "chat_history" not in st.session_state:
67
- st.session_state.chat_history = []
68
- if "feedback" not in st.session_state:
69
- st.session_state.feedback = []
70
- if "show_welcome" not in st.session_state:
71
- st.session_state.show_welcome = True
72
- if "chat_input_value" not in st.session_state: # To clear the text area after submission
73
- st.session_state.chat_input_value = ""
74
- if "last_chat_response" not in st.session_state: # To persist the last chat response
75
- st.session_state.last_chat_response = ""
76
- if "feedback_input" not in st.session_state: # For clearing feedback text area
77
- st.session_state.feedback_input = ""
78
- # Ensure user_id is initialized for saving history
79
- if "user_id" not in st.session_state:
80
- st.session_state.user_id = None
81
- if "logged_in_user" not in st.session_state:
82
- st.session_state.logged_in_user = None
83
-
84
- # --- HARDCODED DATA LOADING FROM CSVs ---
85
- @st.cache_data # Cache this function to avoid reloading data on every rerun
86
- def load_csv_data():
87
- try:
88
- # These paths assume the CSVs are directly in the same directory as app.py
89
- dataset_df = pd.read_csv('dataset.csv').fillna('') # Fill NaN with empty string
90
- description_df = pd.read_csv('symptom_Description.csv').fillna('')
91
- precaution_df = pd.read_csv('symptom_precaution.csv').fillna('')
92
- severity_df = pd.read_csv('Symptom-severity.csv').fillna('') # Load symptom severity
93
- # Prepare data for quick lookup
94
- # Dataset mapping diseases to their symptoms
95
- disease_symptoms_map = {}
96
- for index, row in dataset_df.iterrows():
97
- disease = row['Disease']
98
- # Get all symptoms for this disease, filtering out empty strings and 'Disease' column itself
99
- symptoms = [s.strip().replace('_', ' ') for s in row.values[1:] if s.strip()]
100
- disease_symptoms_map[disease] = symptoms
101
- # Disease descriptions map
102
- disease_description_map = {row['Disease']: row['Description'] for index, row in description_df.iterrows()}
103
- # Disease precautions map
104
- disease_precaution_map = {row['Disease']: [p.strip() for p in row.values[1:] if p.strip()] for index, row in precaution_df.iterrows()}
105
- # Symptom severity map
106
- # Ensure symptom names are consistent (e.g., lowercase and spaces instead of underscores)
107
- symptom_severity_map = {row['Symptom'].strip().replace('_', ' ').lower(): row['weight'] for index, row in severity_df.iterrows()}
108
- # Extract all unique symptoms for the multiselect
109
- all_unique_symptoms = sorted(list(set(symptom for symptoms_list in disease_symptoms_map.values() for symptom in symptoms_list)))
110
- return disease_symptoms_map, disease_description_map, disease_precaution_map, all_unique_symptoms, symptom_severity_map
111
- except FileNotFoundError as e:
112
- st.error(f"Error: Required CSV file not found. Make sure 'dataset.csv', 'symptom_Description.csv', 'symptom_precaution.csv', and 'Symptom-severity.csv' are in the correct directory. Details: {e}")
113
- st.stop() # Stop the app if crucial files are missing
114
- except Exception as e:
115
- st.error(f"Error loading CSV data: {e}")
116
- st.stop() # Stop the app if data loading fails
117
-
118
- disease_symptoms_map, disease_description_map, disease_precaution_map, hardcoded_symptoms, symptom_severity_map = load_csv_data()
119
-
120
- # --- Custom CSS for extensive UI improvements ---
121
- st.markdown("""
122
- <style>
123
- /* Basic Page Layout & Background */
124
- .stApp {
125
- background-color: #f8f9fa; /* Very light grey */
126
- font-family: 'Arial', sans-serif; /* Modern sans-serif font */
127
- color: #343a40; /* Darker grey for primary text */
128
- }
129
- /* Main Container Styling (for sections like Home, History) */
130
- /* Streamlit's main content block often has a data-testid="stVerticalBlock" */
131
- .stApp > header, .stApp > div:first-child > div:first-child > div:first-child {
132
- /* This targets the container that usually holds the main content in Streamlit */
133
- padding: 2.5rem; /* Increased padding */
134
- border-radius: 12px; /* More rounded corners */
135
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); /* Stronger, softer shadow */
136
- margin-top: 2rem; /* Space from top elements */
137
- margin-bottom: 2rem;
138
- background-color: #ffffff; /* White background */
139
- }
140
-
141
- /* Headers */
142
- h1, h2, h3, h4, h5, h6 {
143
- color: #004d99; /* A professional darker blue */
144
- font-weight: 700; /* Bold headers */
145
- margin-top: 1.5em;
146
- margin-bottom: 0.8em;
147
- }
148
- h1 { font-size: 2.8em; text-align: center; color: #003366; } /* Larger, darker blue for main title */
149
- h2 { font-size: 2.2em; border-bottom: 2px solid #e0e7ee; padding-bottom: 0.5em; margin-bottom: 1em; } /* Subtle line under h2 */
150
- h3 { font-size: 1.6em; }
151
-
152
- /* Main Tabs Styling (Home, History, Feedback, About, Insights) */
153
- .stTabs [data-baseweb="tab-list"] {
154
- gap: 20px; /* More space between tabs */
155
- margin-bottom: 30px; /* More space below tabs */
156
- justify-content: center; /* Center the main tabs */
157
- flex-wrap: wrap; /* Allow tabs to wrap on smaller screens */
158
- }
159
- .stTabs [data-baseweb="tab"] {
160
- height: 60px; /* Taller tabs */
161
- padding: 15px 30px; /* More padding */
162
- background-color: #e9f0f6; /* Light blue-grey background for tabs */
163
- border-radius: 12px 12px 0 0; /* Rounded top corners */
164
- font-size: 1.25em; /* Larger font size */
165
- font-weight: 600; /* Semi-bold */
166
- color: #4a6a8c; /* Muted blue text color */
167
- border: none; /* Remove default border */
168
- border-bottom: 4px solid transparent; /* Thicker highlight bottom border */
169
- transition: all 0.3s ease-in-out; /* Smooth transitions */
170
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05); /* Subtle shadow */
171
- outline: none !important; /* Explicitly remove outline */
172
- text-align: center; /* Center text within tab */
173
- min-width: 120px; /* Ensure a minimum width for tabs on mobile */
174
- }
175
- .stTabs [data-baseweb="tab"]:hover {
176
- background-color: #dbe4ee; /* Slightly darker blue-grey on hover */
177
- color: #004d99; /* Darker blue text on hover */
178
- transform: translateY(-2px); /* Slight lift effect */
179
- outline: none !important; /* Ensure no outline on hover either */
180
- }
181
- /* Updated styling for selected main tabs */
182
- .stTabs [data-baseweb="tab"][aria-selected="true"] {
183
- background-color: #004d99; /* Sober darker blue for selected tab */
184
- color: white; /* White text for selected tab */
185
- border-bottom: 4px solid #004d99; /* Keep the consistent bottom border */
186
- box-shadow: 0 -4px 15px rgba(0, 0, 0, 0.15); /* More pronounced shadow for selected */
187
- outline: none !important; /* Ensure no outline when selected */
188
- }
189
- /* Ensure no outline on focus for tabs */
190
- .stTabs [data-baseweb="tab"]:focus {
191
- outline: none !important;
192
- box-shadow: 0 0 0 0.25rem rgba(0, 123, 255, 0.25) !important; /* Subtle focus glow if needed */
193
- }
194
-
195
- /* Nested Tabs Styling (Symptom Checker, Chat with MediBot) */
196
- /* This targets tabs that are children of other tabs, making them smaller */
197
- .stTabs [data-baseweb="tab-list"]:has(.stTabs [data-baseweb="tab-list"]) [data-baseweb="tab"],
198
- .stTabs [data-baseweb="tab-list"] [role="tablist"] [data-baseweb="tab"] { /* More robust selector for nested tabs */
199
- height: 45px; /* Slightly smaller height for sub-tabs */
200
- padding: 10px 20px; /* Slightly smaller padding */
201
- font-size: 1.1em;
202
- background-color: #f0f5f9; /* Lighter blue-grey for sub-tabs */
203
- border-radius: 10px; /* Fully rounded corners for sub-tabs */
204
- border: 1px solid #d8e2ed;
205
- margin: 0 8px; /* Small margin between sub-tabs */
206
- box-shadow: none; /* No individual shadow for sub-tabs */
207
- color: #5f7a96;
208
- outline: none !important; /* Remove outline on focus */
209
- min-width: unset; /* Remove min-width for sub-tabs */
210
- }
211
- .stTabs [data-baseweb="tab-list"]:has(.stTabs [data-baseweb="tab-list"]) [data-baseweb="tab"]:hover,
212
- .stTabs [data-baseweb="tab-list"] [role="tablist"] [data-baseweb="tab"]:hover {
213
- background-color: #e3ecf5;
214
- color: #004d99;
215
- transform: none; /* No lift for sub-tabs */
216
- outline: none !important; /* Ensure no outline on hover either */
217
- }
218
- /* Updated styling for selected nested tabs */
219
- .stTabs [data-baseweb="tab-list"]:has(.stTabs [data-baseweb="tab-list"]) [aria-selected="true"],
220
- .stTabs [data-baseweb="tab-list"] [role="tablist"] [aria-selected="true"] {
221
- background-color: #0056b3; /* Slightly darker blue for selected sub-tab */
222
- color: white;
223
- border: 1px solid #0056b3;
224
- box-shadow: 0 2px 8px rgba(0,0,0,0.15); /* Subtle shadow for selected sub-tab */
225
- outline: none !important; /* Ensure no outline when selected */
226
- }
227
-
228
- /* General Button Styling */
229
- .stButton>button {
230
- background-color: #007bff; /* Primary blue button */
231
- color: white;
232
- border-radius: 10px; /* More rounded */
233
- padding: 12px 28px; /* More padding */
234
- font-weight: 600; /* Semi-bold */
235
- border: none;
236
- cursor: pointer;
237
- box-shadow: 0 5px 15px rgba(0,0,0,0.1); /* Softer, more diffuse shadow */
238
- transition: all 0.3s ease-in-out; /* Smooth transitions */
239
- font-size: 1.05em; /* Slightly larger text */
240
- outline: none; /* Remove outline on focus */
241
- width: auto; /* Allow buttons to size naturally, but consider max-width on mobile */
242
- min-width: 100px; /* Ensure minimum tappable area */
243
- }
244
- .stButton>button:hover {
245
- background-color: #0056b3; /* Darker blue on hover */
246
- box-shadow: 0 8px 20px rgba(0,0,0,0.15); /* Larger shadow on hover */
247
- transform: translateY(-3px); /* More pronounced lift */
248
- outline: none; /* Ensure no outline on hover either */
249
- }
250
- /* Full width buttons in sidebar for better mobile experience */
251
- .sidebar .stButton>button {
252
- width: 100%; /* Full width buttons in sidebar */
253
- margin-bottom: 10px;
254
- }
255
-
256
-
257
- /* Input Fields, Selects, etc. */
258
- .stTextInput>div>div>input,
259
- .stTextArea>div>div>textarea, /* Target textarea as well */
260
- .stDateInput>div>div>input,
261
- .stMultiSelect>div>div {
262
- border-radius: 10px; /* More rounded */
263
- border: 1px solid #ced4da; /* Light grey border */
264
- padding: 10px 15px; /* More padding */
265
- box-shadow: inset 0 1px 4px rgba(0,0,0,0.05); /* Inner shadow */
266
- transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
267
- background-color: #fefefe; /* Slightly off-white background */
268
- outline: none; /* Remove default outline */
269
- }
270
- .stTextInput>div>div>input:focus,
271
- .stTextArea>div>div>textarea:focus,
272
- .stDateInput>div>div>input:focus,
273
- .stMultiSelect>div>div:focus-within {
274
- border-color: #007bff; /* Primary blue border on focus */
275
- box-shadow: 0 0 0 0.25rem rgba(0,123,255,.25); /* Focus glow */
276
- outline: none; /* Remove default outline */
277
- }
278
- /* Specific styling for the multiselect selected items */
279
- .stMultiSelect div[data-baseweb="tag"] {
280
- background-color: #e0f2f7 !important; /* Light blue tag background */
281
- color: #004d99 !important; /* Dark blue tag text */
282
- border-radius: 5px !important;
283
- padding: 5px 10px !important;
284
- margin: 2px !important;
285
- }
286
- /* Expander (for disease info) */
287
- .stExpander {
288
- border: 1px solid #e0e7ee; /* Lighter grey border */
289
- border-radius: 12px; /* More rounded */
290
- margin-bottom: 15px; /* More space below */
291
- background-color: #ffffff; /* White background */
292
- box-shadow: 0 4px 12px rgba(0,0,0,0.05); /* Softer shadow */
293
- overflow: hidden; /* Ensure rounded corners clip content */
294
- }
295
- .stExpander > div > div > .streamlit-expanderHeader {
296
- background-color: #f0f5f9; /* Light blue header */
297
- padding: 15px 20px;
298
- font-weight: 600;
299
- font-size: 1.1em;
300
- color: #004d99;
301
- border-bottom: 1px solid #e0e7ee;
302
- outline: none !important; /* Remove outline on focus */
303
- }
304
- .stExpander > div > div > .streamlit-expanderHeader:focus {
305
- outline: none !important; /* Ensure no outline on focus */
306
- box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important; /* Subtle focus ring */
307
- }
308
- .stExpander > div > div > .streamlit-expanderContent {
309
- padding: 20px; /* Increased padding inside */
310
- }
311
- /* Sidebar Styling */
312
- /* Streamlit's sidebar elements are usually inside a div with role="complementary" */
313
- .st-emotion-cache-vk33as { /* This targets the Streamlit sidebar div with padding */
314
- background-color: #ffffff; /* White sidebar background */
315
- padding: 25px;
316
- border-radius: 12px;
317
- box-shadow: 0 8px 24px rgba(0,0,0,0.08); /* Consistent shadow */
318
- margin-top: 2rem;
319
- }
320
- .sidebar .stButton>button {
321
- width: 100%; /* Full width buttons in sidebar */
322
- margin-bottom: 10px;
323
- }
324
- .sidebar h2, .sidebar h3 {
325
- color: #004d99;
326
- text-align: center;
327
- margin-bottom: 1.5em;
328
- }
329
- .sidebar .stTextInput, .sidebar .stDateInput, .sidebar .stSelectbox {
330
- margin-bottom: 1em; /* Space between sidebar inputs */
331
- }
332
- /* Alerts */
333
- .stAlert {
334
- border-radius: 10px; /* Rounded corners for alerts */
335
- padding: 15px;
336
- font-size: 1.05em;
337
- }
338
- .stAlert.st-success { background-color: #d4edda; color: #155724; border-color: #c3e6cb; }
339
- .stAlert.st-info { background-color: #d1ecf1; color: #0c5460; border-color: #bee5eb; }
340
- .stAlert.st-warning { background-color: #fff3cd; color: #856404; border-color: #ffeeba; }
341
- .stAlert.st-error { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; }
342
- /* Spinners */
343
- .stSpinner { color: #007bff; } /* Spinner color matching primary button */
344
-
345
- /* Disclaimer Box - using a more reliable target */
346
- div[data-testid="stVerticalBlock"] > div:first-child > div:has(p > strong:contains("Disclaimer")) {
347
- background-color: #fff8e1; /* Light yellow for warnings/disclaimers */
348
- border: 1px solid #ffeeba;
349
- padding: 1.5rem;
350
- border-radius: 10px;
351
- margin-bottom: 2rem;
352
- color: #856404;
353
- box-shadow: 0 2px 8px rgba(0,0,0,0.05);
354
- }
355
- /* This targets the specific disclaimer block by looking for its content */
356
- .stAlert[data-testid="stAlert"] p strong:contains("Important Disclaimer") {
357
- color: #664d03;
358
- }
359
-
360
- /* General text improvements */
361
- p {
362
- line-height: 1.7;
363
- margin-bottom: 1em;
364
- }
365
- ul, ol {
366
- margin-bottom: 1em;
367
- padding-left: 25px;
368
- }
369
- li {
370
- margin-bottom: 0.5em;
371
- }
372
- /* Increased font size for history expander titles */
373
- .stExpander > div > div > .streamlit-expanderHeader {
374
- font-size: 1.2em; /* Increased font size */
375
- }
376
-
377
- /* Media queries for specific mobile adjustments if needed (example) */
378
- @media (max-width: 768px) {
379
- h1 {
380
- font-size: 2.2em; /* Slightly smaller H1 on mobile */
381
- }
382
- h2 {
383
- font-size: 1.8em; /* Slightly smaller H2 on mobile */
384
- }
385
- .stTabs [data-baseweb="tab"] {
386
- font-size: 1em; /* Smaller tab font size on mobile */
387
- padding: 10px 15px; /* Reduced padding for tabs on mobile */
388
- height: auto; /* Allow height to adjust */
389
- min-width: unset; /* Remove min-width to allow more flexibility */
390
- flex: 1 1 auto; /* Allow tabs to grow and shrink more flexibly */
391
- }
392
- .stTabs [data-baseweb="tab-list"] {
393
- gap: 10px; /* Smaller gap between tabs on mobile */
394
- }
395
- /* Make multiselect items slightly smaller on mobile for better fit */
396
- .stMultiSelect div[data-baseweb="tag"] {
397
- font-size: 0.9em !important;
398
- padding: 3px 8px !important;
399
- }
400
- }
401
- </style>
402
- """, unsafe_allow_html=True)
403
-
404
- # --- Groq API Response Function (updated system prompt) ---
405
- def get_groq_response(user_query, severity_label="Undetermined Severity", model="llama-3.3-70b-versatile"):
406
- """
407
- Function to get a response from Groq API for health questions.
408
- Augments the system prompt with severity information if available.
409
- """
410
- if not GROQ_AVAILABLE or groq_client is None:
411
- return "AI assistant is not available."
412
-
413
- # Augment the system prompt with severity information from clinical diagnosis model
414
- # Note: 'severity_label' comes from the symptom checker. If asking a direct question,
415
- # it might default to "Undetermined Severity" unless explicitly passed from a prior analysis.
416
- system_prompt = (
417
- "You are an experienced physician specialized in diagnosis and clinical decision-making. "
418
- "Your primary role is to analyze presented symptoms or health queries and provide a differential diagnosis along with evidence-based recommendations for next steps. "
419
- f"Based on initial analysis, the perceived severity of the user's condition is: {severity_label}. "
420
- "Adjust your tone and recommendations accordingly, emphasizing urgency if severity is 'High Severity'.\n\n"
421
- "When a user describes symptoms or asks a health question, you should:\n\n"
422
- "1. **Prioritized Differential Diagnosis**: Provide a prioritized list of possible diagnoses based on the information given, indicating their relative likelihood or confidence (e.g., 'most likely', 'possible', 'less likely').\n"
423
- "2. **Reasoning**: Briefly explain the clinical reasoning for each diagnosis, referencing common clinical features, pathophysiology, or typical presentations.\n"
424
- "3. **Recommended Investigations**: Suggest appropriate next diagnostic tests or investigations to confirm or rule out these conditions.\n"
425
- "4. **Initial Management/Treatment**: Propose evidence-based initial management or general treatment options (e.g., symptomatic relief, lifestyle changes, over-the-counter suggestions).\n"
426
- "5. **Red Flags/Urgency**: Clearly highlight any red flags or warning signs that would require immediate emergency care or urgent specialist referral.\n\n"
427
- "Always maintain a professional, empathetic, and medically accurate tone. Present information clearly, using bullet points or numbered lists where appropriate. "
428
- "Crucially, always conclude your response by strongly advising the user to consult a qualified healthcare professional for a definitive diagnosis and personalized treatment plan, "
429
- "as this AI is for informational purposes only and not a substitute for professional medical advice."
430
- )
431
- try:
432
- chat_completion = groq_client.chat.completions.create(
433
- messages=[
434
- {"role": "system", "content": system_prompt},
435
- {"role": "user", "content": user_query},
436
- ],
437
- model=model,
438
- temperature=0.7,
439
- max_tokens=800, # Increased max_tokens to allow for more detailed responses
440
- top_p=1,
441
- stop=None,
442
- stream=False,
443
- )
444
- return chat_completion.choices[0].message.content
445
- except Exception as e:
446
- st.error(f"Groq API call failed: {e}")
447
- return "Sorry, I am unable to process that request at the moment due to an AI service error."
448
-
449
- # --- Clinical Diagnosis Model Integration (DATEXIS/CORe-clinical-diagnosis-prediction) ---
450
- def get_diagnosis_and_severity_from_model(symptoms_text):
451
  """
452
- Function to get diagnosis predictions and infer severity from DATEXIS/CORe-clinical-diagnosis-prediction
453
- using Hugging Face Inference API.
454
  """
455
- if not HF_MODEL_AVAILABLE or not HF_API_TOKEN:
456
- st.warning("Hugging Face API key not available. Cannot assess severity.")
457
- return "Severity Assessment Unavailable (API Key Missing)", []
458
-
459
- API_URL = "https://api-inference.huggingface.co/models/DATEXIS/CORe-clinical-diagnosis-prediction"
460
- headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
461
- payload = {"inputs": symptoms_text}
462
- try:
463
- response = requests.post(API_URL, headers=headers, json=payload)
464
- response.raise_for_status()
465
- result = response.json()
466
-
467
- raw_predicted_diagnoses = []
468
- threshold = 0.5
469
- if result and isinstance(result, list) and len(result) > 0 and isinstance(result[0], list):
470
- for prediction_set in result:
471
- for item in prediction_set:
472
- if item['score'] >= threshold:
473
- raw_predicted_diagnoses.append(item['label'])
474
-
475
- filtered_diagnoses = []
476
- generic_filter_keywords = [
477
- 'unspecified', 'acute', 'chronic', 'use', 'status', 'other', 'not elsewhere classified',
478
- 'no diagnosis', 'history of', 'finding', 'problem', 'syndrome', 'disease',
479
- 'disorder', 'condition', 'code', 'category', 'episode', 'complication',
480
- 'sequelae', 'factor', 'manifestation', 'procedure', 'examination', 'observation',
481
- 'symptoms', 'sign', 'unconfirmed', 'type', 'group', 'normal', 'unknown', 'level',
482
- 'positive', 'negative', 'patient', 'value', 'test', 'result', 'diagnosis',
483
- 'kidney', 'stage', 'without', 'essential',
484
- 'organ', 'function', 'system', 'body', 'region', 'with', 'due to', 'related to',
485
- 'clinical', 'consideration', 'presence', 'absence', 'mild', 'moderate', 'severe',
486
- 'manifesting', 'affecting', 'affect', 'area', 'part', 'general', 'specific',
487
- 'diagnosis of', 'history of', 'finding of', 'problem of', 'type of', 'group of',
488
- 'unlikely', 'possible', 'likely',
489
- 'symptom', 'sign', 'pain', 'fever', 'cough', 'vomiting', 'nausea', 'rash', 'headache', 'fatigue', 'diarrhea', 'sore throat', 'hemorrhage'
490
- ]
491
-
492
- for diagnosis_label in raw_predicted_diagnoses:
493
- lower_diag = diagnosis_label.lower()
494
- is_generic = False
495
- for generic_kw in generic_filter_keywords:
496
- if re.search(r'\b' + re.escape(generic_kw) + r'\b', lower_diag) and len(lower_diag.split()) <= 2:
497
- is_generic = True
498
- break
499
- if lower_diag.replace('.', '').isdigit() and len(lower_diag.replace('.', '')) < 5:
500
- is_generic = True
501
- if len(lower_diag.split()) <= 2 and lower_diag in ['pain', 'fever', 'cough', 'vomiting', 'nausea', 'rash', 'headache', 'fatigue', 'diarrhea', 'sore throat', 'hemorrhage']:
502
- is_generic = True
503
-
504
- if not is_generic:
505
- filtered_diagnoses.append(diagnosis_label)
506
-
507
- filtered_diagnoses = list(dict.fromkeys(filtered_diagnoses))
508
- if len(filtered_diagnoses) > 5:
509
- filtered_diagnoses = filtered_diagnoses[:5]
510
-
511
- if not filtered_diagnoses:
512
- return "Overall Symptom Severity (AI Assessment): Undetermined Severity", []
513
-
514
- severity_map_keywords = {
515
- "High Severity": ['heart attack', 'stroke', 'failure', 'hemorrhage', 'cancer', 'acute respiratory', 'sepsis', 'cardiac arrest', 'severe', 'malignant', 'emergency', 'rupture', 'infarction', 'coma', 'shock', 'decompensation', 'crisis', 'perforation', 'ischemia', 'embolism', 'aneurysm', 'critical'],
516
- "Moderate Severity": ['hypertension', 'diabetes', 'pneumonia', 'infection', 'chronic', 'inflammation', 'moderate', 'insufficiency', 'fracture', 'ulcer', 'hepatitis', 'renal', 'vascular', 'disease', 'disorder', 'syndrome', 'acute', 'bronchitis', 'appendicitis', 'gallstones', 'pancreatitis', 'lupus', 'arthritis'],
517
- "Low Severity": ['headache', 'pain', 'mild', 'allergy', 'fever', 'cough', 'common cold', 'dermatitis', 'arthritis', 'influenza', 'viral', 'sprain', 'strain', 'gastritis', 'sore throat', 'conjunctivitis', 'sinusitis', 'bruise', 'rash', 'minor']
518
- }
519
-
520
- overall_severity = "Low Severity"
521
- for diagnosis_label in filtered_diagnoses:
522
- diag_lower = diagnosis_label.lower()
523
- if any(keyword in diag_lower for keyword in severity_map_keywords["High Severity"]):
524
- return "Overall Symptom Severity (AI Assessment): High Severity", filtered_diagnoses
525
- if any(keyword in diag_lower for keyword in severity_map_keywords["Moderate Severity"]):
526
- if overall_severity == "Low Severity":
527
- overall_severity = "Moderate Severity"
528
- # Keep looping to see if a High Severity diagnosis is found later in the filtered list
529
- # Don't return here if a Moderate is found, continue to check for High
530
-
531
- return f"Overall Symptom Severity (AI Assessment): {overall_severity}", filtered_diagnoses
532
-
533
- except requests.exceptions.RequestException as e:
534
- st.error(f"Network or API Error during clinical diagnosis model call: {e}. Check API key and internet connection.")
535
- return "Severity Assessment Unavailable (Network Error)", []
536
- except Exception as e:
537
- st.error(f"Error processing clinical diagnosis model response: {e}. Unexpected data format or mapping issue.")
538
- return "Severity Assessment Unavailable (Processing Error)", []
539
-
540
- # --- Functions for PDF generation (retained and adjusted for DB history) ---
541
- def generate_pdf_report(history_data):
542
- buffer = io.BytesIO()
543
- c = canvas.Canvas(buffer, pagesize=letter)
544
- width, height = letter
545
- c.setFont("Helvetica-Bold", 16)
546
- c.drawString(50, height - 50, "MediBot Health Report")
547
- c.setFont("Helvetica", 10)
548
- c.drawString(50, height - 70, f"Report Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
549
- y = height - 100
550
-
551
- for entry in history_data:
552
- # history_data comes as list of tuples: (history_id, symptoms, predicted_diseases, query_timestamp)
553
- timestamp, symptoms_text, predicted_diseases = entry[3], entry[1], entry[2]
554
-
555
- if y < 100:
556
- c.showPage()
557
- c.setFont("Helvetica-Bold", 16)
558
- c.drawString(50, height - 50, "MediBot Health Report (Continued)")
559
- c.setFont("Helvetica", 10)
560
- y = height - 100
561
-
562
- c.setFont("Helvetica-Bold", 10)
563
- c.drawString(50, y, f"Timestamp: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
564
- y -= 15
565
- c.setFont("Helvetica", 10)
566
-
567
- symptoms_line = f"Symptoms/Question: {symptoms_text}"
568
- insight_line = f"Insight/Response: {predicted_diseases}"
569
-
570
- textobject = c.beginText(50, y)
571
- textobject.setFont("Helvetica", 10)
572
-
573
- # Helper to wrap text within PDF
574
- def draw_wrapped_text(text_obj, text, max_width):
575
- nonlocal y
576
- words = text.split(' ')
577
- current_line_words = []
578
- for word_idx, word in enumerate(words):
579
- temp_line = ' '.join(current_line_words + [word])
580
- if c.stringWidth(temp_line, "Helvetica", 10) < max_width:
581
- current_line_words.append(word)
582
- else:
583
- text_obj.textLine(' '.join(current_line_words))
584
- y -= 12 # Line height
585
- current_line_words = [word]
586
- # Handle very long words that exceed max_width
587
- if c.stringWidth(word, "Helvetica", 10) >= max_width:
588
- # If a single word is too long, break it
589
- chunk_size = 50 # Arbitrary chunk size for breaking long words
590
- for i in range(0, len(word), chunk_size):
591
- text_obj.textLine(word[i:i + chunk_size])
592
- y -= 12
593
- current_line_words = [] # Reset after a long word is broken
594
- continue
595
- if current_line_words: # Draw any remaining words
596
- text_obj.textLine(' '.join(current_line_words))
597
- y -= 12 # Line height
598
-
599
- draw_wrapped_text(textobject, symptoms_line, width - 100)
600
- draw_wrapped_text(textobject, insight_line, width - 100)
601
- c.drawText(textobject)
602
- y -= 20
603
-
604
- c.save()
605
- buffer.seek(0)
606
- return buffer
607
-
608
- # --- Symptom Checker Logic (Now using hardcoded data and severity) ---
609
- def get_disease_info_from_csv(selected_symptoms: list, disease_symptoms_map, disease_description_map, disease_precaution_map, symptom_severity_map) -> list[tuple[str, str, list[str]]]:
610
- """
611
- Finds potential diseases, descriptions, and precautions based on selected symptoms
612
- using the hardcoded CSV data, incorporating symptom severity.
613
- Returns a list of tuples: (disease_name, description, [precautions])
614
- """
615
- matching_diseases_with_scores = {} # Stores disease -> weighted_score
616
- # Normalize selected symptoms for consistent matching
617
- normalized_selected_symptoms = [s.strip().lower() for s in selected_symptoms]
618
-
619
- for disease, symptoms_list in disease_symptoms_map.items():
620
- # Convert the symptoms_list from map to lowercase for comparison
621
- normalized_disease_symptoms = [s.lower() for s in symptoms_list]
622
- weighted_score = 0
623
- for selected_symptom in normalized_selected_symptoms:
624
- if selected_symptom in normalized_disease_symptoms:
625
- # Add severity weight if available, otherwise a default of 1
626
- # The .get() method is safe if a symptom is in dataset.csv but not in Symptom-severity.csv
627
- weight = symptom_severity_map.get(selected_symptom, 1)
628
- weighted_score += weight
629
- if weighted_score > 0:
630
- matching_diseases_with_scores[disease] = weighted_score
631
-
632
- # Sort diseases by the weighted score (highest first)
633
- sorted_matching_diseases = sorted(matching_diseases_with_scores.items(), key=lambda item: item[1], reverse=True)
634
-
635
- results = []
636
- # Limit to top 5 most matching diseases (or fewer if less than 5 matches)
637
- for disease_name, _ in sorted_matching_diseases[:5]:
638
- description = disease_description_map.get(disease_name, "No description available.")
639
- precautions = disease_precaution_map.get(disease_name, ["No precautions available."])
640
- results.append((disease_name, description, precautions))
641
- return results
642
-
643
- # --- Function to parse insight text for analytics ---
644
- def parse_insight_text_for_conditions(insight_text: str) -> list[str]:
645
- """
646
- Parses the combined insight text from history to extract condition names.
647
- Expected format: "Severity. Dataset Conditions: Cond1, Cond2; AI Suggestions: AICond1, AIC2"
648
- or just "Severity. No specific insights found."
649
- """
650
- conditions = []
651
- # Regex to find "Dataset Conditions: ..." and "AI Suggestions: ..." parts
652
- dataset_match = re.search(r"Dataset Conditions:\s*([^;]+)", insight_text)
653
- ai_match = re.search(r"AI Suggestions:\s*(.+)", insight_text)
654
-
655
- if dataset_match:
656
- dataset_str = dataset_match.group(1).strip()
657
- if dataset_str and dataset_str.lower() != "no specific insights found":
658
- conditions.extend([c.strip() for c in dataset_str.split(',') if c.strip()])
659
-
660
- if ai_match:
661
- ai_str = ai_match.group(1).strip()
662
- if ai_str and ai_str.lower() != "no specific insights found" and ai_str.lower() != "ai assistant is not available.":
663
- conditions.extend([c.strip() for c in ai_str.split(',') if c.strip()])
664
-
665
- # Remove duplicates and return
666
- return list(set(conditions))
667
-
668
- # --- Main Application Logic ---
669
- # Ensure feedback_input is initialized if not already (redundant if done at start, but harmless)
670
- if "feedback_input" not in st.session_state:
671
- st.session_state.feedback_input = ""
672
-
673
-
674
- if st.session_state.show_welcome:
675
- # A more visually appealing welcome page
676
- st.markdown("<h1 style='color: #0056b3; font-size: 3.5em; margin-top: 50px; text-shadow: 2px 2px 4px rgba(0,0,0,0.1);'>MediBot - Your Personal Health Assistant 🏥</h1>", unsafe_allow_html=True)
677
- st.markdown("""
678
- <div style='background-color: #e6f7ff; padding: 30px; border-radius: 15px; text-align: center; margin-top: 30px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);'>
679
- <p style='font-size: 1.2em; line-height: 1.8; color: #336699;'>
680
- Explore possible causes and precautions for your symptoms and get answers to health-related questions using advanced AI.
681
- Your health journey, simplified and supported.
682
- </p>
683
- <ul style='list-style-type: none; padding: 0; margin-top: 25px; display: inline-block; text-align: left;'>
684
- <li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>✅ <b>Symptom Checker:</b> Get insights from a comprehensive symptom list.</li>
685
- <li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>💬 <b>AI Chatbot:</b> Ask general health questions.</li>
686
- <li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>👤 <b>User Management:</b> Save your health history.</li>
687
- <li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>📜 <b>Persistent History:</b> View and download past records.</li>
688
- <li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>📈 <b>Usage Insights:</b> See trends in conditions.</li>
689
- </ul>
690
- </div>
691
- """, unsafe_allow_html=True)
692
- st.markdown("<div style='text-align: center; margin-top: 40px;'>", unsafe_allow_html=True)
693
- if st.button("Get Started", key="welcome_button"):
694
- st.session_state.show_welcome = False
695
- st.rerun() # Force rerun to ensure app reloads to main page
696
- st.markdown("</div>", unsafe_allow_html=True)
697
- st.markdown("""
698
- <div style='background-color: #fff8e1; padding: 20px; border-radius: 10px; margin-top: 50px; text-align: center; color: #8a6d3b; box-shadow: 0 4px 10px rgba(0,0,0,0.05);'>
699
- <p style='font-size: 0.9em; margin-bottom: 0;'>
700
- <b>Important Disclaimer:</b> This app provides preliminary health information based on symptoms and AI analysis.
701
- <b>It is not a substitute for professional medical advice, diagnosis, or treatment.</b> Always consult a qualified healthcare provider for a definitive diagnosis and personalized treatment plan, as this AI is for informational purposes only and not a substitute for professional medical advice.
702
- </p>
703
- </div>
704
- """, unsafe_allow_html=True)
705
- else:
706
- # Displaying disclaimer at the top of the main app view
707
- st.markdown("<div class='stContainer' style='background-color: #fff8e1; border: 1px solid #ffeeba;'>"
708
- "<p style='margin-bottom: 0; font-size: 0.95em; color: #856404;'>"
709
- "<strong>Disclaimer</strong>: This app provides preliminary health information based on symptoms and AI analysis. "
710
- "It is not a substitute for professional medical advice, diagnosis, or treatment. Always consult a qualified healthcare provider."
711
- "</p>"
712
- "</div>", unsafe_allow_html=True)
713
-
714
- # Sidebar for User Management (remains in sidebar)
715
- with st.sidebar:
716
- st.header("User Management")
717
- show_user_management()
718
- st.markdown("---")
719
- st.header("Navigation")
720
- # Add a button to clear chat history
721
- if st.button("Clear Chat History", help="Clears all messages from the current session."):
722
- st.session_state.chat_history = []
723
- st.session_state.last_chat_response = ""
724
- st.session_state.chat_input_value = ""
725
- st.success("Chat history cleared!")
726
- # PDF Download Button - available if user is logged in and has history
727
- if st.session_state.get("logged_in_user"):
728
- user_history = get_user_history(st.session_state["logged_in_user"])
729
- if user_history:
730
- pdf_buffer = generate_pdf_report(user_history)
731
- st.download_button(
732
- label="Download Health Report (PDF)",
733
- data=pdf_buffer,
734
- file_name="medibot_health_report.pdf",
735
- mime="application/pdf",
736
- help="Download your chat and symptom checker history as a PDF report."
737
- )
738
- else:
739
- st.info("No history to download yet. Interact with MediBot to generate a report.")
740
-
741
- st.markdown("<h1 style='text-align: center; color: #0056b3;'>MediBot - Your Health Assistant 🩺</h1>", unsafe_allow_html=True)
742
-
743
- # Top-level tabs: Home, History, Feedback, About, Insights
744
- # Re-ordered tabs slightly to put Insights closer to History
745
- main_tab_home, main_tab_history, main_tab_insights, main_tab_feedback, main_tab_about = st.tabs(["Home", "History", "Insights", "Feedback", "About"])
746
-
747
- with main_tab_home:
748
- st.markdown("<h2>How can I help you today?</h2>", unsafe_allow_html=True)
749
- # Nested tabs for Symptom Checker and Chat with MediBot
750
- tab_symptom_checker, tab_chatbot = st.tabs(["Symptom Checker", "Chat with MediBot"])
751
-
752
- with tab_symptom_checker:
753
- st.markdown("<h3>Select from common symptoms to get insights:</h3>", unsafe_allow_html=True)
754
- selected_symptoms = st.multiselect(
755
- "Select your symptoms:",
756
- options=hardcoded_symptoms, # Use symptoms derived from CSV
757
- default=[],
758
- key="symptom_select",
759
- help="Choose one or more symptoms you are experiencing."
760
- )
761
-
762
- if st.button("Get Health Insight", key="diagnose_button"):
763
- if not selected_symptoms:
764
- st.error("Please select at least one symptom.")
765
- else:
766
- with st.spinner("Analyzing symptoms..."):
767
- # Get overall severity and predicted diagnoses from the Hugging Face model
768
- combined_symptoms_text = ", ".join(selected_symptoms)
769
- severity_assessment_text, predicted_diagnoses_from_model = get_diagnosis_and_severity_from_model(combined_symptoms_text)
770
-
771
- # Get relevant disease info from your CSV datasets
772
- diseases_from_csv = get_disease_info_from_csv(selected_symptoms, disease_symptoms_map, disease_description_map, disease_precaution_map, symptom_severity_map)
773
-
774
- st.markdown(f"**{severity_assessment_text}**", unsafe_allow_html=True)
775
- st.markdown("<h2>Possible Conditions and Insights:</h2>", unsafe_allow_html=True) # Unified Heading
776
-
777
- # --- Process and display CSV-based diseases first ---
778
- if diseases_from_csv:
779
- st.markdown("<h4>From our Medical Knowledge Base:</h4>", unsafe_allow_html=True)
780
- for disease_name, description, precautions_list in diseases_from_csv:
781
- with st.expander(disease_name):
782
- st.write(f"**Description**: {description}")
783
- st.write("**Precautions**:")
784
- if precautions_list:
785
- for precaution in precautions_list:
786
- if precaution: # Only display non-empty precautions
787
- st.write(f"- {precaution}")
788
- else:
789
- st.write("- No specific precautions listed.")
790
- st.markdown("---") # Separator for each disease in expander
791
- else:
792
- st.info("No common conditions found for these symptoms in our dataset. Please try adding more specific symptoms or switch to 'Chat with MediBot' for a general query.")
793
-
794
- # --- Integrate relevant AI Model diagnoses not covered by CSV ---
795
- additional_ai_diagnoses = []
796
- # Create a set of normalized disease names from CSV for quick lookup and fuzzy matching
797
- csv_disease_names_normalized = {d[0].lower() for d in diseases_from_csv}
798
-
799
- for model_diag in predicted_diagnoses_from_model:
800
- model_diag_normalized = model_diag.lower()
801
- is_covered_by_csv = False
802
- # Check for strong fuzzy match with CSV diseases
803
- for csv_diag_name in csv_disease_names_normalized:
804
- # Use token_set_ratio for better matching of multi-word terms, less sensitive to word order
805
- if fuzz.token_set_ratio(model_diag_normalized, csv_diag_name) > 85: # High threshold for a strong match
806
- is_covered_by_csv = True
807
- break
808
- if not is_covered_by_csv:
809
- additional_ai_diagnoses.append(model_diag)
810
-
811
- if additional_ai_diagnoses:
812
- st.markdown("<h4>Additional AI-Suggested Clinical Considerations:</h4>", unsafe_allow_html=True)
813
- st.write("The AI model suggests the following clinical terms based on your symptoms:")
814
- for diag in additional_ai_diagnoses:
815
- st.write(f"- **{diag}**: This is a medical term that the AI found relevant. Please consult a healthcare professional for more details.")
816
- st.info("These are general medical terms and require professional interpretation. They may not have specific descriptions or precautions available in our dataset.")
817
- elif not diseases_from_csv: # Only if no CSV diseases AND no unique AI diagnoses
818
- st.info("The AI model could not confidently predict specific or unique diagnoses from the provided symptoms. Try different symptoms or consult a doctor.")
819
-
820
- st.write("---") # Final Separator
821
-
822
- # --- Save to history (adjusting content to reflect unified response) ---
823
- history_content_parts = []
824
- if diseases_from_csv:
825
- history_content_parts.append("Dataset Conditions: " + ", ".join([d[0] for d in diseases_from_csv]))
826
- if additional_ai_diagnoses:
827
- history_content_parts.append("AI Suggestions: " + ", ".join(additional_ai_diagnoses))
828
-
829
- # Combine all insight for history
830
- predicted_diseases_str_for_history = f"{severity_assessment_text.replace('Overall Symptom Severity (AI Assessment): ', '')}. " + "; ".join(history_content_parts)
831
- if not history_content_parts:
832
- predicted_diseases_str_for_history = f"{severity_assessment_text.replace('Overall Symptom Severity (AI Assessment): ', '')}. No specific insights found."
833
-
834
- if st.session_state.get("user_id"):
835
- # Corrected call: Removed the first argument (user_id)
836
- save_history_if_logged_in(", ".join(selected_symptoms), predicted_diseases_str_for_history)
837
- st.success("Analysis saved to your history.")
838
- else:
839
- st.info("Log in to save this analysis to your history.")
840
-
841
- with tab_chatbot: # Ask a Health Question (Chatbot)
842
- st.markdown("<h3>Describe your symptoms or ask a general health question:</h3>", unsafe_allow_html=True)
843
- # Use st.form with clear_on_submit=True to clear the text area
844
- with st.form("chat_form", clear_on_submit=True):
845
- user_question = st.text_area(
846
- "Input your issue:",
847
- value=st.session_state.chat_input_value, # Use session state for value
848
- key="chat_input_widget", # Unique widget key
849
- placeholder="e.g., I am having a severe fever and body aches. Or, what are the causes of high blood pressure?",
850
- height=120 # Slightly taller text area
851
- )
852
- chat_submit_button = st.form_submit_button("Ask MediBot")
853
-
854
- if chat_submit_button:
855
- if user_question:
856
- # For the chatbot, if it's a direct question, severity is undetermined
857
- # unless passed from a preceding symptom check.
858
- # For simplicity, if not from symptom checker, pass "Undetermined Severity".
859
- # You could enhance this to try to infer severity from the question itself if needed.
860
- severity_for_groq_prompt = "Undetermined Severity"
861
- # If you want to use the severity from a recent symptom check,
862
- # you'd need to store it in session_state and pass it here.
863
- # For now, we're keeping the Q&A separate.
864
- with st.spinner("MediBot is thinking..."):
865
- groq_answer = get_groq_response(user_question, severity_label=severity_for_groq_prompt)
866
- st.markdown("**MediBot's Answer:**")
867
- st.write(groq_answer)
868
-
869
- # Save Q&A to chat history (if desired, not necessarily DB)
870
- st.session_state.chat_history.append({"user": user_question, "bot": groq_answer})
871
-
872
- # Save to database history if logged in
873
- if st.session_state.get("user_id"):
874
- # Corrected call: Removed the first argument (user_id)
875
- save_history_if_logged_in(f"Question: {user_question}", groq_answer)
876
- st.success("Your question and answer have been saved to history.")
877
- else:
878
- st.info("Log in to save this Q&A to your history.")
879
  else:
880
- st.warning("Please type your question to get an answer.")
881
-
882
- with main_tab_history:
883
- st.header("Your Health History")
884
- user_id = st.session_state.get("user_id")
885
- if user_id:
886
- st.info("This section shows your saved symptom analyses and health questions.")
887
- history_data = get_user_history(user_id) # Fetch history from database
888
-
889
- if history_data:
890
- st.subheader("Past Interactions:")
891
- # Display history in reverse chronological order using expanders
892
- for entry in reversed(history_data):
893
- timestamp, symptoms_text, insight_text = entry[3], entry[1], entry[2]
894
-
895
- # Determine summary title based on the nature of the input
896
- summary_title_prefix = ""
897
- expander_icon = "" # Initialize icon here
898
- if symptoms_text.startswith("Question: "):
899
- keyword = extract_keyword(symptoms_text, hardcoded_symptoms)
900
- summary_title_prefix = f"Question: {keyword}" if keyword != "Unknown" else "General Question"
901
- expander_icon = "💬"
902
- else: # Symptom checker entry (🩺)
903
- summary_conditions = parse_insight_text_for_conditions(insight_text)
904
- if summary_conditions:
905
- summary_title_prefix = ", ".join(summary_conditions[:3])
906
- if len(summary_conditions) > 3:
907
- summary_title_prefix += "..."
908
- else:
909
- # MODIFIED LOGIC: If no conditions identified, try to use input symptoms
910
- input_symptoms_match = re.search(r"Symptoms/Question: (.*)", symptoms_text) # Adjusted regex for "Symptoms/Question:"
911
- if input_symptoms_match:
912
- extracted_input_symptoms = input_symptoms_match.group(1).strip()
913
- # Clean up potential extra spaces/commas, take first few if too many
914
- clean_symptoms = [s.strip() for s in extracted_input_symptoms.split(',') if s.strip()]
915
- if clean_symptoms:
916
- summary_title_prefix = ", ".join(clean_symptoms[:3])
917
- if len(clean_symptoms) > 3:
918
- summary_title_prefix += "..."
919
- else:
920
- summary_title_prefix = "No conditions identified" # Fallback if no symptoms extracted
921
  else:
922
- summary_title_prefix = "No conditions identified" # Fallback if input format is unexpected
923
- expander_icon = "🩺"
924
-
925
- # MODIFIED LINE: Use Markdown heading (e.g., ###) for larger font and bolding
926
- # Experiment with ## or ### to find the desired size.
927
- expander_label = f"### **{timestamp.strftime('%Y-%m-%d %H:%M')}** - {expander_icon} *{summary_title_prefix}*"
928
- with st.expander(expander_label):
929
- st.write(f"**Your Input:** {symptoms_text}")
930
- st.write(f"**MediBot's Insight:** {insight_text}")
931
-
932
- # PDF Download button
933
- pdf_buffer = generate_pdf_report(history_data)
934
- st.download_button(
935
- label="Download History as PDF",
936
- data=pdf_buffer,
937
- file_name="MediBot_Health_Report.pdf",
938
- mime="application/pdf"
939
- )
940
- else:
941
- st.info("No history found for your account. Start interacting with MediBot!")
942
- else:
943
- st.warning("Please log in to view your health history.")
944
-
945
- with main_tab_insights:
946
- st.header("Your Health Insights")
947
- user_id = st.session_state.get("user_id")
948
- if user_id:
949
- st.info("This section provides insights into the conditions identified in your past interactions.")
950
- history_data = get_user_history(user_id)
951
- if history_data:
952
- # Filter data for the last 30 days
953
- thirty_days_ago = datetime.now() - timedelta(days=30)
954
- recent_history = [
955
- entry for entry in history_data
956
- if entry[3] and entry[3] > thirty_days_ago # entry[3] is query_timestamp
957
- ]
958
-
959
- if recent_history:
960
- all_conditions = []
961
- for entry in recent_history:
962
- # entry[2] is predicted_diseases string
963
- conditions_from_entry = parse_insight_text_for_conditions(entry[2])
964
- all_conditions.extend(conditions_from_entry)
965
-
966
- if all_conditions:
967
- # Count occurrences of each condition
968
- condition_counts = pd.Series(all_conditions).value_counts().reset_index()
969
- condition_counts.columns = ['Condition', 'Count']
970
- st.subheader("Most Frequent Conditions in Last 30 Days:")
971
- # Display as a bar chart
972
- st.bar_chart(condition_counts, x='Condition', y='Count', use_container_width=True)
973
- st.write("This chart shows how many times each condition (from both dataset and AI suggestions) appeared in your analyses over the past 30 days.")
974
  else:
975
- st.info("No specific conditions were identified in your recent history (last 30 days).")
976
- else:
977
- st.info("You have no health interactions in the last 30 days to generate insights.")
978
- else:
979
- st.info("No history found for your account. Start interacting with MediBot to see insights!")
980
- else:
981
- st.warning("Please log in to view your health insights.")
982
-
983
- with main_tab_feedback:
984
- st.header("Share Your Feedback")
985
- st.write("We appreciate your feedback to improve MediBot.")
986
- with st.form("feedback_form", clear_on_submit=True):
987
- feedback_text = st.text_area("Your feedback:", height=150, key="feedback_text_area")
988
- rating = st.slider("Rate your experience (1-5 stars):", 1, 5, 3, key="feedback_rating")
989
- feedback_submit_button = st.form_submit_button("Submit Feedback")
990
-
991
- if feedback_submit_button:
992
- if feedback_text:
993
- # In a real app, you would save this to a database
994
- st.session_state.feedback.append({"text": feedback_text, "rating": rating, "timestamp": datetime.now()})
995
- st.success("Thank you for your feedback! It has been submitted.")
996
- st.session_state.feedback_input = "" # Clear after submission
997
- else:
998
- st.warning("Please enter your feedback before submitting.")
999
-
1000
- with main_tab_about:
1001
- st.header("About MediBot")
1002
- st.write("""
1003
- MediBot is a health assistant designed to provide general information based on your symptoms and answer health-related questions.
1004
- It utilizes various AI models and medical datasets to offer insights.
1005
-
1006
- **Technology Stack:**
1007
- - **Streamlit**: For the interactive web application interface.
1008
- - **Groq API**: Powers the conversational AI for health questions using high-performance language models (e.g., Llama-3), enabling rapid and relevant responses.
1009
- - **Hugging Face Inference API**: Integrates with specialized medical text classification models, specifically `DATEXIS/CORe-clinical-diagnosis-prediction`, to predict possible clinical diagnoses (often mapping to ICD-9 codes) from symptom inputs.
1010
- - **Pandas**: For efficient data handling and processing of local CSV medical datasets (symptoms, descriptions, precautions).
1011
- - **ReportLab**: For generating downloadable PDF reports of your personalized health history.
1012
-
1013
- **Datasets Used:**
1014
- - Symptom-to-Disease mapping, Descriptions, and Precautions sourced from:
1015
- "Disease Symptom Prediction" dataset available on Kaggle.
1016
- [https://www.kaggle.com/datasets/itachi9604/disease-symptom-description-dataset](https://www.kaggle.com/datasets/itachi9604/disease-symptom-description-dataset)
1017
- *Credit to Itachi9604 for compiling this valuable dataset.*
1018
 
1019
- **Important Disclaimer**: This application is for informational purposes only and should not be used as a substitute for professional medical advice, diagnosis, or treatment. Always consult with a qualified healthcare provider for any health concerns or before making any decisions related to your health.
1020
- """)
1021
- st.markdown("[Learn more about Streamlit](https://streamlit.io/)")
1022
- st.markdown("[Learn more about Groq](https://groq.com/)")
1023
- st.markdown("[Learn more about Hugging Face](https://huggingface.co/)")
 
 
 
 
1024
 
1025
- st.markdown("---")
1026
- st.markdown("For professional medical advice, always consult a qualified healthcare provider.")
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ from datetime import datetime
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ # Import database functions directly here.
5
+ # user_management.py is now responsible for handling its own database interactions.
6
+ # It should NOT import anything from app.py
7
+ # Assuming these functions are defined in database.py
8
  try:
9
+ from database import register_user, user_exists, check_user_credentials, save_user_history, get_user_history
10
+ except ImportError:
11
+ st.error("Could not import database functions in user_management.py. Please ensure database.py exists and contains required functions.")
12
+ # Define dummy functions to allow the app to run without a real database.py for now
13
+ def get_user_history(user_id):
14
+ st.warning("Database function 'get_user_history' not implemented. History will not be persistent.")
15
+ return []
16
+ def register_user(user_id, full_name, dob, email, password):
17
+ st.warning("Database function 'register_user' not implemented. User registration will not be persistent.")
18
+ return True # Simulate success
19
+ def user_exists(user_id):
20
+ st.warning("Database function 'user_exists' not implemented. User existence check will not work.")
21
+ return False # Simulate user not existing
22
+ def check_user_credentials(user_id, password):
23
+ st.warning("Database function 'check_user_credentials' not implemented. Login will not work.")
24
+ return False # Simulate login failure
25
+ def save_user_history(user_id, symptoms, predicted_diseases):
26
+ st.warning("Database function 'save_user_history' not implemented. History saving will not be persistent.")
27
+ pass
28
+
29
+
30
+ def render_user_management_sidebar(session_state):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  """
32
+ Handles user registration and login forms in the sidebar.
33
+ Receives the Streamlit session_state object to manage user login status.
34
  """
35
+ st.sidebar.header("User Management 🧑‍⚕️")
36
+
37
+ # Check if a user is currently logged in via session_state
38
+ if session_state.get("user_id"):
39
+ st.sidebar.write(f"Welcome, {session_state.user_id}!")
40
+ if st.sidebar.button("Logout", key="logout_button_sidebar"): # Unique key for sidebar button
41
+ session_state.user_id = None
42
+ session_state.logged_in_user = None # Clear this too upon logout
43
+ st.success("Logged out successfully!")
44
+ st.rerun() # Rerun to update UI immediately
45
+ else:
46
+ # Toggle between sign-in and register forms
47
+ form_type = st.sidebar.selectbox("Choose Action", ["Sign In", "Register"], key="form_type_sidebar")
48
+
49
+ if form_type == "Sign In":
50
+ with st.sidebar.form("login_form_sidebar"): # Unique form key
51
+ st.markdown("### Sign In")
52
+ login_user_id = st.text_input("User ID", key="login_user_id_input_sidebar") # Unique widget key
53
+ login_password = st.text_input("Password", type="password", key="login_password_input_sidebar") # Unique widget key
54
+ submit_login = st.form_submit_button("Sign In")
55
+
56
+ if submit_login:
57
+ if not login_user_id or not login_password:
58
+ st.sidebar.error("Please enter both User ID and Password.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  else:
60
+ with st.spinner("Signing in..."): # Spinner for login
61
+ if check_user_credentials(login_user_id, login_password):
62
+ session_state.user_id = login_user_id
63
+ session_state.logged_in_user = login_user_id # Set logged_in_user in session state
64
+ st.sidebar.success("Signed in successfully!")
65
+ st.rerun() # Rerun to update UI with logged-in state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  else:
67
+ st.sidebar.error("Invalid User ID or Password.")
68
+ else: # Register
69
+ with st.sidebar.form("register_form_sidebar"): # Unique form key
70
+ st.markdown("### Register")
71
+ reg_user_id = st.text_input("Choose a unique User ID", key="reg_user_id_input_sidebar") # Unique widget key
72
+ full_name = st.text_input("Full Name", key="reg_full_name_input_sidebar") # Unique widget key
73
+ dob = st.date_input("Date of Birth", min_value=datetime(1900, 1, 1), max_value=datetime.today(), key="reg_dob_input_sidebar") # Unique widget key
74
+ email = st.text_input("Email (optional)", key="reg_email_input_sidebar") # Unique widget key
75
+ reg_password = st.text_input("Password", type="password", key="reg_password_input_sidebar") # Unique widget key
76
+ submit_register = st.form_submit_button("Register")
77
+
78
+ if submit_register:
79
+ if not reg_user_id or not reg_password or not full_name:
80
+ st.sidebar.error("User ID, Full Name, and Password are required.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  else:
82
+ with st.spinner("Checking User ID..."): # Spinner for user_exists check
83
+ if user_exists(reg_user_id):
84
+ st.sidebar.error("User ID already taken. Please choose another.")
85
+ else:
86
+ with st.spinner("Registering user..."): # Spinner for registration
87
+ # Pass dob as a datetime.date object; database function should handle conversion if needed
88
+ if register_user(reg_user_id, full_name, dob, email or None, reg_password):
89
+ session_state.user_id = reg_user_id
90
+ session_state.logged_in_user = reg_user_id # Set logged_in_user in session state
91
+ st.sidebar.success("Registered successfully! You are now logged in.")
92
+ st.rerun() # Rerun to update UI with logged-in state
93
+ else:
94
+ st.sidebar.error("Registration failed. Please try again.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ def save_history_to_db_if_logged_in(user_id: str, symptoms: str, predicted_diseases: str):
97
+ """
98
+ Saves user interaction history to the database.
99
+ This function is called from app.py when a user is logged in.
100
+ It directly calls the database function to save data.
101
+ """
102
+ # This function assumes user_id is already validated (i.e., user is logged in)
103
+ with st.spinner("Saving history..."): # Spinner for saving history
104
+ save_user_history(user_id, symptoms, predicted_diseases)
105