Final app version before submission.
Browse files- README.md +6 -116
- Symptom-severity.csv +134 -0
- app.py +1042 -130
- database.py +5 -4
- dataset.csv +0 -0
- requirements.txt +5 -1
- symptom_Description.csv +42 -0
- symptom_precaution.csv +42 -0
- user_management.py +92 -49
- utils.py +56 -27
README.md
CHANGED
@@ -1,124 +1,14 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
colorFrom: blue
|
5 |
-
colorTo:
|
6 |
sdk: streamlit
|
7 |
-
sdk_version:
|
8 |
app_file: app.py
|
9 |
pinned: false
|
|
|
10 |
---
|
11 |
|
12 |
-
# MediBot - Health Assistant
|
13 |
|
14 |
-
|
15 |
-
|
16 |
-
**Disclaimer**: This app provides preliminary health information based on symptoms and AI analysis. It is not a substitute for professional medical advice, diagnosis, or treatment. Always consult a qualified healthcare provider for any health concerns.
|
17 |
-
|
18 |
-
## Features
|
19 |
-
- **Symptom-Based Diagnosis**: Users can select symptoms to receive potential disease matches, descriptions, and precautions based on provided datasets.
|
20 |
-
- **Health Question Answering**: Users can ask free-text health questions, answered via the Groq API.
|
21 |
-
- **User Management**: Optional registration and login to save symptom history, with a unique user ID.
|
22 |
-
- **History Tracking**: Registered users can view their past symptom searches and AI responses.
|
23 |
-
- **Database Integration**: Uses Neon PostgreSQL to store user data, symptoms, diseases, and history.
|
24 |
-
- **Responsive Design**: Built with Streamlit for a user-friendly interface.
|
25 |
-
|
26 |
-
## Setup Instructions
|
27 |
-
|
28 |
-
### Prerequisites
|
29 |
-
- A Hugging Face account to deploy the Space.
|
30 |
-
- A Neon PostgreSQL database with the schema set up (see `create_tables.sql`).
|
31 |
-
- A Groq API key for answering health questions.
|
32 |
-
|
33 |
-
### Deployment on Hugging Face
|
34 |
-
1. **Create a New Space**:
|
35 |
-
- Go to [Hugging Face Spaces](https://huggingface.co/spaces) and create a new Space.
|
36 |
-
- Choose the Streamlit SDK and name your Space (e.g., `MediBot`).
|
37 |
-
|
38 |
-
2. **Upload Files**:
|
39 |
-
- Upload the following files to the Space repository:
|
40 |
-
- `app.py`
|
41 |
-
- `database.py`
|
42 |
-
- `user_management.py`
|
43 |
-
- `requirements.txt`
|
44 |
-
- `create_tables.sql` (for reference)
|
45 |
-
- `README.md` (this file)
|
46 |
-
|
47 |
-
3. **Configure Secrets**:
|
48 |
-
- In the Space settings, add the following secrets:
|
49 |
-
- `DB_CONNECT`: Your Neon PostgreSQL connection string (e.g., `postgresql://neondb_owner:mypassword@ep-shiny-band-a2wzybro-pooler.eu-central-1.aws.neon.tech/medibot?sslmode=require&channel_binding=require`).
|
50 |
-
- `GROQ_API_KEY`: Your Groq API key.
|
51 |
-
|
52 |
-
4. **Set Up Database**:
|
53 |
-
- Connect to your Neon PostgreSQL database using a tool like `psql` or Neon's SQL editor.
|
54 |
-
- Run the SQL script in `create_tables.sql` to create and populate the tables (`mb_users`, `mb_history`, `mb_symptoms`, `mb_diseases`, `mb_disease_symptoms`).
|
55 |
-
|
56 |
-
5. **Deploy**:
|
57 |
-
- Commit the files and wait for Hugging Face to build and deploy the Space.
|
58 |
-
- Once deployed, access the app via the provided Space URL.
|
59 |
-
|
60 |
-
### Local Development
|
61 |
-
1. **Clone the Repository**:
|
62 |
-
```bash
|
63 |
-
git clone https://huggingface.co/spaces/<your-username>/MediBot
|
64 |
-
cd MediBot
|
65 |
-
```
|
66 |
-
|
67 |
-
2. **Install Dependencies**:
|
68 |
-
```bash
|
69 |
-
pip install -r requirements.txt
|
70 |
-
```
|
71 |
-
|
72 |
-
3. **Set Environment Variables**:
|
73 |
-
```bash
|
74 |
-
export DB_CONNECT="postgresql://neondb_owner:mypassword@ep-shiny-band-a2wzybro-pooler.eu-central-1.aws.neon.tech/medibot?sslmode=require&channel_binding=require"
|
75 |
-
export GROQ_API_KEY="your-groq-api-key"
|
76 |
-
```
|
77 |
-
|
78 |
-
4. **Run the App**:
|
79 |
-
```bash
|
80 |
-
streamlit run app.py
|
81 |
-
```
|
82 |
-
|
83 |
-
5. **Access the App**:
|
84 |
-
- Open your browser and navigate to `http://localhost:8501`.
|
85 |
-
|
86 |
-
## Project Structure
|
87 |
-
- `app.py`: Main Streamlit app handling symptom input, diagnosis, and health question queries.
|
88 |
-
- `database.py`: Manages database connections and queries for symptoms, diseases, and user history.
|
89 |
-
- `user_management.py`: Handles user registration, login, and history display.
|
90 |
-
- `create_tables.sql`: SQL script to set up the Neon PostgreSQL database.
|
91 |
-
- `requirements.txt`: Python dependencies for the app.
|
92 |
-
|
93 |
-
## Database Schema
|
94 |
-
- **mb_users**: Stores user information (user_id, date_of_birth, email, registration_date).
|
95 |
-
- **mb_history**: Stores user symptom history and predicted diseases.
|
96 |
-
- **mb_symptoms**: Stores symptom names and severity weights.
|
97 |
-
- **mb_diseases**: Stores disease names, descriptions, and precautions.
|
98 |
-
- **mb_disease_symptoms**: Maps diseases to symptoms for diagnosis.
|
99 |
-
|
100 |
-
## Datasets
|
101 |
-
The app uses the following datasets (populated in the database via `create_tables.sql`):
|
102 |
-
- `dataset.csv`: Maps symptoms to diseases.
|
103 |
-
- `Symptom-severity.csv`: Provides symptom weights.
|
104 |
-
- `symptom_Description.csv`: Disease descriptions.
|
105 |
-
- `symptom_precaution.csv`: Disease precautions.
|
106 |
-
|
107 |
-
## Technologies Used
|
108 |
-
- **Streamlit**: For the web interface.
|
109 |
-
- **Neon PostgreSQL**: For data storage.
|
110 |
-
- **Groq API**: For answering health questions.
|
111 |
-
- **psycopg2**: For PostgreSQL database connectivity.
|
112 |
-
- **Python**: Core programming language.
|
113 |
-
|
114 |
-
## Contributing
|
115 |
-
Contributions are welcome! Please fork the repository and submit a pull request with your changes.
|
116 |
-
|
117 |
-
## License
|
118 |
-
This project is licensed under the MIT License.
|
119 |
-
|
120 |
-
## Contact
|
121 |
-
For support or inquiries, contact the xAI team or open an issue in the repository.
|
122 |
-
|
123 |
-
---
|
124 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
+
title: Medibot
|
3 |
+
emoji: 📈
|
4 |
colorFrom: blue
|
5 |
+
colorTo: gray
|
6 |
sdk: streamlit
|
7 |
+
sdk_version: 1.44.1
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
+
license: mit
|
11 |
---
|
12 |
|
|
|
13 |
|
14 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Symptom-severity.csv
ADDED
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Symptom,weight
|
2 |
+
itching,1
|
3 |
+
skin_rash,3
|
4 |
+
nodal_skin_eruptions,4
|
5 |
+
continuous_sneezing,4
|
6 |
+
shivering,5
|
7 |
+
chills,3
|
8 |
+
joint_pain,3
|
9 |
+
stomach_pain,5
|
10 |
+
acidity,3
|
11 |
+
ulcers_on_tongue,4
|
12 |
+
muscle_wasting,3
|
13 |
+
vomiting,5
|
14 |
+
burning_micturition,6
|
15 |
+
spotting_urination,6
|
16 |
+
fatigue,4
|
17 |
+
weight_gain,3
|
18 |
+
anxiety,4
|
19 |
+
cold_hands_and_feets,5
|
20 |
+
mood_swings,3
|
21 |
+
weight_loss,3
|
22 |
+
restlessness,5
|
23 |
+
lethargy,2
|
24 |
+
patches_in_throat,6
|
25 |
+
irregular_sugar_level,5
|
26 |
+
cough,4
|
27 |
+
high_fever,7
|
28 |
+
sunken_eyes,3
|
29 |
+
breathlessness,4
|
30 |
+
sweating,3
|
31 |
+
dehydration,4
|
32 |
+
indigestion,5
|
33 |
+
headache,3
|
34 |
+
yellowish_skin,3
|
35 |
+
dark_urine,4
|
36 |
+
nausea,5
|
37 |
+
loss_of_appetite,4
|
38 |
+
pain_behind_the_eyes,4
|
39 |
+
back_pain,3
|
40 |
+
constipation,4
|
41 |
+
abdominal_pain,4
|
42 |
+
diarrhoea,6
|
43 |
+
mild_fever,5
|
44 |
+
yellow_urine,4
|
45 |
+
yellowing_of_eyes,4
|
46 |
+
acute_liver_failure,6
|
47 |
+
fluid_overload,6
|
48 |
+
swelling_of_stomach,7
|
49 |
+
swelled_lymph_nodes,6
|
50 |
+
malaise,6
|
51 |
+
blurred_and_distorted_vision,5
|
52 |
+
phlegm,5
|
53 |
+
throat_irritation,4
|
54 |
+
redness_of_eyes,5
|
55 |
+
sinus_pressure,4
|
56 |
+
runny_nose,5
|
57 |
+
congestion,5
|
58 |
+
chest_pain,7
|
59 |
+
weakness_in_limbs,7
|
60 |
+
fast_heart_rate,5
|
61 |
+
pain_during_bowel_movements,5
|
62 |
+
pain_in_anal_region,6
|
63 |
+
bloody_stool,5
|
64 |
+
irritation_in_anus,6
|
65 |
+
neck_pain,5
|
66 |
+
dizziness,4
|
67 |
+
cramps,4
|
68 |
+
bruising,4
|
69 |
+
obesity,4
|
70 |
+
swollen_legs,5
|
71 |
+
swollen_blood_vessels,5
|
72 |
+
puffy_face_and_eyes,5
|
73 |
+
enlarged_thyroid,6
|
74 |
+
brittle_nails,5
|
75 |
+
swollen_extremeties,5
|
76 |
+
excessive_hunger,4
|
77 |
+
extra_marital_contacts,5
|
78 |
+
drying_and_tingling_lips,4
|
79 |
+
slurred_speech,4
|
80 |
+
knee_pain,3
|
81 |
+
hip_joint_pain,2
|
82 |
+
muscle_weakness,2
|
83 |
+
stiff_neck,4
|
84 |
+
swelling_joints,5
|
85 |
+
movement_stiffness,5
|
86 |
+
spinning_movements,6
|
87 |
+
loss_of_balance,4
|
88 |
+
unsteadiness,4
|
89 |
+
weakness_of_one_body_side,4
|
90 |
+
loss_of_smell,3
|
91 |
+
bladder_discomfort,4
|
92 |
+
foul_smell_ofurine,5
|
93 |
+
continuous_feel_of_urine,6
|
94 |
+
passage_of_gases,5
|
95 |
+
internal_itching,4
|
96 |
+
toxic_look_(typhos),5
|
97 |
+
depression,3
|
98 |
+
irritability,2
|
99 |
+
muscle_pain,2
|
100 |
+
altered_sensorium,2
|
101 |
+
red_spots_over_body,3
|
102 |
+
belly_pain,4
|
103 |
+
abnormal_menstruation,6
|
104 |
+
dischromic_patches,6
|
105 |
+
watering_from_eyes,4
|
106 |
+
increased_appetite,5
|
107 |
+
polyuria,4
|
108 |
+
family_history,5
|
109 |
+
mucoid_sputum,4
|
110 |
+
rusty_sputum,4
|
111 |
+
lack_of_concentration,3
|
112 |
+
visual_disturbances,3
|
113 |
+
receiving_blood_transfusion,5
|
114 |
+
receiving_unsterile_injections,2
|
115 |
+
coma,7
|
116 |
+
stomach_bleeding,6
|
117 |
+
distention_of_abdomen,4
|
118 |
+
history_of_alcohol_consumption,5
|
119 |
+
fluid_overload,4
|
120 |
+
blood_in_sputum,5
|
121 |
+
prominent_veins_on_calf,6
|
122 |
+
palpitations,4
|
123 |
+
painful_walking,2
|
124 |
+
pus_filled_pimples,2
|
125 |
+
blackheads,2
|
126 |
+
scurring,2
|
127 |
+
skin_peeling,3
|
128 |
+
silver_like_dusting,2
|
129 |
+
small_dents_in_nails,2
|
130 |
+
inflammatory_nails,2
|
131 |
+
blister,4
|
132 |
+
red_sore_around_nose,2
|
133 |
+
yellow_crust_ooze,3
|
134 |
+
prognosis,5
|
app.py
CHANGED
@@ -1,159 +1,1071 @@
|
|
1 |
-
|
2 |
import os
|
|
|
|
|
|
|
|
|
|
|
3 |
import streamlit as st
|
|
|
|
|
4 |
from groq import Groq
|
5 |
-
from
|
6 |
-
from
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
load_dotenv(dotenv_path=".env.local")
|
11 |
|
|
|
|
|
|
|
12 |
# Initialize Groq client
|
13 |
-
groq_client =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
-
#
|
16 |
-
|
17 |
|
18 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
st.markdown("""
|
20 |
<style>
|
21 |
-
|
22 |
-
.
|
23 |
-
background-color: #
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
.
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
margin-bottom: 10px;
|
40 |
}
|
41 |
-
.sidebar .sidebar
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
}
|
47 |
-
|
48 |
-
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
|
51 |
-
#
|
52 |
-
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
"<strong>Disclaimer</strong>: This app provides preliminary health information based on symptoms and AI analysis. "
|
55 |
"It is not a substitute for professional medical advice, diagnosis, or treatment. Always consult a qualified healthcare provider."
|
|
|
56 |
"</div>", unsafe_allow_html=True)
|
57 |
|
58 |
-
#
|
59 |
-
|
|
|
|
|
|
|
60 |
|
61 |
-
|
62 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
|
64 |
-
#
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
|
|
|
|
|
|
|
|
74 |
else:
|
75 |
-
st.
|
76 |
-
selected_symptoms = st.multiselect("Select your symptoms:", symptoms, help="Choose one or more symptoms you are experiencing.")
|
77 |
-
except Exception as e:
|
78 |
-
st.error(f"Error loading symptoms: {str(e)}")
|
79 |
-
st.markdown("**Debugging Tips**: Check terminal logs for detailed errors, verify DB_CONNECT, and ensure mb_symptoms table exists.")
|
80 |
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
|
|
|
|
|
|
|
|
100 |
else:
|
101 |
-
st.
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
)
|
121 |
-
st.
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
146 |
else:
|
147 |
-
|
148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
149 |
else:
|
150 |
-
st.info("No history found.")
|
151 |
-
|
152 |
-
st.
|
153 |
-
|
154 |
-
|
155 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
|
157 |
-
# Footer
|
158 |
st.markdown("---")
|
159 |
-
st.markdown("
|
|
|
1 |
+
import io
|
2 |
import os
|
3 |
+
import re
|
4 |
+
from datetime import datetime, timedelta
|
5 |
+
import numpy as np
|
6 |
+
import pandas as pd
|
7 |
+
import requests
|
8 |
import streamlit as st
|
9 |
+
from dotenv import load_dotenv
|
10 |
+
from fuzzywuzzy import fuzz # Make sure fuzzywuzzy is installed (pip install fuzzywuzzy python-levenshtein)
|
11 |
from groq import Groq
|
12 |
+
from reportlab.lib.pagesizes import letter
|
13 |
+
from reportlab.pdfgen import canvas
|
14 |
+
|
15 |
+
# Import database functions directly here.
|
16 |
+
# These functions should NOT import anything from app.py or user_management.py
|
17 |
+
# If you don't have database.py, you'll need to create it with these functions.
|
18 |
+
# For demonstration, I'll assume they exist.
|
19 |
+
try:
|
20 |
+
from database import get_user_history, register_user, user_exists, check_user_credentials, save_user_history
|
21 |
+
# Assuming get_user_history, register_user, user_exists, check_user_credentials, save_user_history are in database.py
|
22 |
+
except ImportError:
|
23 |
+
st.error("Could not import database functions. Please ensure database.py exists and contains required functions.")
|
24 |
+
# Define dummy functions to allow the app to run without a real database.py for now
|
25 |
+
def get_user_history(user_id):
|
26 |
+
st.warning("Database function 'get_user_history' not implemented. History will not be persistent.")
|
27 |
+
return []
|
28 |
+
def register_user(user_id, full_name, dob, email, password):
|
29 |
+
st.warning("Database function 'register_user' not implemented. User registration will not be persistent.")
|
30 |
+
return True # Simulate success
|
31 |
+
def user_exists(user_id):
|
32 |
+
st.warning("Database function 'user_exists' not implemented. User existence check will not work.")
|
33 |
+
return False # Simulate user not existing
|
34 |
+
def check_user_credentials(user_id, password):
|
35 |
+
st.warning("Database function 'check_user_credentials' not implemented. Login will not work.")
|
36 |
+
return False # Simulate login failure
|
37 |
+
def save_user_history(user_id, symptoms, predicted_diseases):
|
38 |
+
st.warning("Database function 'save_user_history' not implemented. History saving will not be persistent.")
|
39 |
+
pass
|
40 |
+
|
41 |
|
42 |
+
# Import user management functions from user_management.py
|
43 |
+
# These functions will now receive st.session_state as an argument
|
44 |
+
from user_management import render_user_management_sidebar, save_history_to_db_if_logged_in
|
45 |
+
|
46 |
+
# Import utility functions
|
47 |
+
try:
|
48 |
+
from utils import extract_keyword # Ensure utils.py has the extract_keyword function
|
49 |
+
except ImportError:
|
50 |
+
st.error("Could not import utility functions. Please ensure utils.py exists and contains required functions.")
|
51 |
+
def extract_keyword(text, keywords):
|
52 |
+
# Dummy implementation if utils.py is missing
|
53 |
+
for kw in keywords:
|
54 |
+
if kw.lower() in text.lower():
|
55 |
+
return kw
|
56 |
+
return "Unknown"
|
57 |
+
|
58 |
+
|
59 |
+
# Load environment variables from .env.local for local development.
|
60 |
+
# On Hugging Face Spaces, st.secrets will be used.
|
61 |
load_dotenv(dotenv_path=".env.local")
|
62 |
|
63 |
+
# --- Configuration and Initial Setup (MUST BE FIRST) ---
|
64 |
+
st.set_page_config(page_title="MediBot - Health Assistant", page_icon="🏥", layout="wide")
|
65 |
+
|
66 |
# Initialize Groq client
|
67 |
+
groq_client = None
|
68 |
+
GROQ_AVAILABLE = False
|
69 |
+
try:
|
70 |
+
# Prefer st.secrets for Hugging Face Spaces deployment
|
71 |
+
GROQ_API_KEY = st.secrets.get("GROQ_API_KEY")
|
72 |
+
if not GROQ_API_KEY:
|
73 |
+
# Fallback to environment variable for local development if not in secrets
|
74 |
+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
75 |
+
if GROQ_API_KEY:
|
76 |
+
groq_client = Groq(api_key=GROQ_API_KEY)
|
77 |
+
GROQ_AVAILABLE = True
|
78 |
+
else:
|
79 |
+
st.error("Groq API Key not found. Groq chatbot will not be available.")
|
80 |
+
except Exception as e:
|
81 |
+
st.error(f"Error initializing Groq client: {e}. Groq chatbot will not be available.")
|
82 |
+
|
83 |
+
# Initialize Hugging Face Inference API client details for DATEXIS/CORe-clinical-diagnosis-prediction
|
84 |
+
HF_MODEL_AVAILABLE = False
|
85 |
+
HF_API_TOKEN = None
|
86 |
+
try:
|
87 |
+
# Assuming 'med_model' is the name of your Hugging Face API key in st.secrets
|
88 |
+
HF_API_TOKEN = st.secrets.get("med_model")
|
89 |
+
if not HF_API_TOKEN:
|
90 |
+
# Fallback to environment variable for local development
|
91 |
+
HF_API_TOKEN = os.getenv("MED_MODEL") # Using MED_MODEL for consistency with environment variables
|
92 |
+
if HF_API_TOKEN:
|
93 |
+
HF_MODEL_AVAILABLE = True
|
94 |
+
else:
|
95 |
+
st.warning("Hugging Face 'med_model' API Key not found. Clinical diagnosis assessment will not be available.")
|
96 |
+
except Exception as e:
|
97 |
+
st.warning(f"Error retrieving Hugging Face API key: {e}. Clinical diagnosis assessment will not be available.")
|
98 |
+
|
99 |
+
# Initialize session state variables
|
100 |
+
if "chat_history" not in st.session_state:
|
101 |
+
st.session_state.chat_history = []
|
102 |
+
if "feedback" not in st.session_state:
|
103 |
+
st.session_state.feedback = []
|
104 |
+
if "show_welcome" not in st.session_state:
|
105 |
+
st.session_state.show_welcome = True
|
106 |
+
if "chat_input_value" not in st.session_state: # To clear the text area after submission
|
107 |
+
st.session_state.chat_input_value = ""
|
108 |
+
if "last_chat_response" not in st.session_state: # To persist the last chat response
|
109 |
+
st.session_state.last_chat_response = ""
|
110 |
+
if "feedback_input" not in st.session_state: # For clearing feedback text area
|
111 |
+
st.session_state.feedback_input = ""
|
112 |
+
# Ensure user_id is initialized for saving history
|
113 |
+
if "user_id" not in st.session_state:
|
114 |
+
st.session_state.user_id = None
|
115 |
+
if "logged_in_user" not in st.session_state: # This will hold the user_id after successful login
|
116 |
+
st.session_state.logged_in_user = None
|
117 |
+
|
118 |
+
# --- HARDCODED DATA LOADING FROM CSVs ---
|
119 |
+
@st.cache_data # Cache this function to avoid reloading data on every rerun
|
120 |
+
def load_csv_data():
|
121 |
+
try:
|
122 |
+
# These paths assume the CSVs are directly in the same directory as app.py
|
123 |
+
dataset_df = pd.read_csv('dataset.csv').fillna('') # Fill NaN with empty string
|
124 |
+
description_df = pd.read_csv('symptom_Description.csv').fillna('')
|
125 |
+
precaution_df = pd.read_csv('symptom_precaution.csv').fillna('')
|
126 |
+
severity_df = pd.read_csv('Symptom-severity.csv').fillna('') # Load symptom severity
|
127 |
+
|
128 |
+
# Prepare data for quick lookup
|
129 |
+
# Dataset mapping diseases to their symptoms
|
130 |
+
disease_symptoms_map = {}
|
131 |
+
for index, row in dataset_df.iterrows():
|
132 |
+
disease = row['Disease']
|
133 |
+
# Get all symptoms for this disease, filtering out empty strings and 'Disease' column itself
|
134 |
+
symptoms = [s.strip().replace('_', ' ') for s in row.values[1:] if s.strip()]
|
135 |
+
disease_symptoms_map[disease] = symptoms
|
136 |
+
|
137 |
+
# Disease descriptions map
|
138 |
+
disease_description_map = {row['Disease']: row['Description'] for index, row in description_df.iterrows()}
|
139 |
|
140 |
+
# Disease precautions map
|
141 |
+
disease_precaution_map = {row['Disease']: [p.strip() for p in row.values[1:] if p.strip()] for index, row in precaution_df.iterrows()}
|
142 |
|
143 |
+
# Symptom severity map
|
144 |
+
# Ensure symptom names are consistent (e.g., lowercase and spaces instead of underscores)
|
145 |
+
symptom_severity_map = {row['Symptom'].strip().replace('_', ' ').lower(): row['weight'] for index, row in severity_df.iterrows()}
|
146 |
+
|
147 |
+
# Extract all unique symptoms for the multiselect
|
148 |
+
all_unique_symptoms = sorted(list(set(symptom for symptoms_list in disease_symptoms_map.values() for symptom in symptoms_list)))
|
149 |
+
return disease_symptoms_map, disease_description_map, disease_precaution_map, all_unique_symptoms, symptom_severity_map
|
150 |
+
except FileNotFoundError as e:
|
151 |
+
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}")
|
152 |
+
st.stop() # Stop the app if crucial files are missing
|
153 |
+
except Exception as e:
|
154 |
+
st.error(f"Error loading CSV data: {e}")
|
155 |
+
st.stop() # Stop the app if data loading fails
|
156 |
+
|
157 |
+
disease_symptoms_map, disease_description_map, disease_precaution_map, hardcoded_symptoms, symptom_severity_map = load_csv_data()
|
158 |
+
|
159 |
+
# --- Custom CSS for extensive UI improvements ---
|
160 |
st.markdown("""
|
161 |
<style>
|
162 |
+
/* Basic Page Layout & Background */
|
163 |
+
.stApp {
|
164 |
+
background-color: #f8f9fa; /* Very light grey */
|
165 |
+
font-family: 'Arial', sans-serif; /* Modern sans-serif font */
|
166 |
+
color: #343a40; /* Darker grey for primary text */
|
167 |
+
}
|
168 |
+
/* Main Container Styling (for sections like Home, History) */
|
169 |
+
/* Streamlit's main content block often has a data-testid="stVerticalBlock" */
|
170 |
+
.stApp > header, .stApp > div:first-child > div:first-child > div:first-child {
|
171 |
+
/* This targets the container that usually holds the main content in Streamlit */
|
172 |
+
padding: 2.5rem; /* Increased padding */
|
173 |
+
border-radius: 12px; /* More rounded corners */
|
174 |
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); /* Stronger, softer shadow */
|
175 |
+
margin-top: 2rem; /* Space from top elements */
|
176 |
+
margin-bottom: 2rem;
|
177 |
+
background-color: #ffffff; /* White background */
|
178 |
+
}
|
179 |
+
/* Headers */
|
180 |
+
h1, h2, h3, h4, h5, h6 {
|
181 |
+
color: #004d99; /* A professional darker blue */
|
182 |
+
font-weight: 700; /* Bold headers */
|
183 |
+
margin-top: 1.5em;
|
184 |
+
margin-bottom: 0.8em;
|
185 |
+
}
|
186 |
+
h1 { font-size: 2.8em; text-align: center; color: #003366; } /* Larger, darker blue for main title */
|
187 |
+
h2 { font-size: 2.2em; border-bottom: 2px solid #e0e7ee; padding-bottom: 0.5em; margin-bottom: 1em; } /* Subtle line under h2 */
|
188 |
+
h3 { font-size: 1.6em; }
|
189 |
+
/* Main Tabs Styling (Home, History, Feedback, About, Insights) */
|
190 |
+
.stTabs [data-baseweb="tab-list"] {
|
191 |
+
gap: 20px; /* More space between tabs */
|
192 |
+
margin-bottom: 30px; /* More space below tabs */
|
193 |
+
justify-content: center; /* Center the main tabs */
|
194 |
+
flex-wrap: wrap; /* Allow tabs to wrap on smaller screens */
|
195 |
+
}
|
196 |
+
.stTabs [data-baseweb="tab"] {
|
197 |
+
height: 60px; /* Taller tabs */
|
198 |
+
padding: 15px 30px; /* More padding */
|
199 |
+
background-color: #e9f0f6; /* Light blue-grey background for tabs */
|
200 |
+
border-radius: 12px 12px 0 0; /* Rounded top corners */
|
201 |
+
font-size: 1.25em; /* Larger font size */
|
202 |
+
font-weight: 600; /* Semi-bold */
|
203 |
+
color: #4a6a8c; /* Muted blue text color */
|
204 |
+
border: none; /* Remove default border */
|
205 |
+
border-bottom: 4px solid transparent; /* Thicker highlight bottom border */
|
206 |
+
transition: all 0.3s ease-in-out; /* Smooth transitions */
|
207 |
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05); /* Subtle shadow */
|
208 |
+
outline: none !important; /* Explicitly remove outline */
|
209 |
+
text-align: center; /* Center text within tab */
|
210 |
+
min-width: 120px; /* Ensure a minimum width for tabs on mobile */
|
211 |
+
}
|
212 |
+
.stTabs [data-baseweb="tab"]:hover {
|
213 |
+
background-color: #dbe4ee; /* Slightly darker blue-grey on hover */
|
214 |
+
color: #004d99; /* Darker blue text on hover */
|
215 |
+
transform: translateY(-2px); /* Slight lift effect */
|
216 |
+
outline: none !important; /* Ensure no outline on hover either */
|
217 |
+
}
|
218 |
+
/* Updated styling for selected main tabs */
|
219 |
+
.stTabs [data-baseweb="tab"][aria-selected="true"] {
|
220 |
+
background-color: #004d99; /* Sober darker blue for selected tab */
|
221 |
+
color: white; /* White text for selected tab */
|
222 |
+
border-bottom: 4px solid #004d99; /* Keep the consistent bottom border */
|
223 |
+
box-shadow: 0 -4px 15px rgba(0, 0, 0, 0.15); /* More pronounced shadow for selected */
|
224 |
+
outline: none !important; /* Ensure no outline when selected */
|
225 |
+
}
|
226 |
+
/* Ensure no outline on focus for tabs */
|
227 |
+
.stTabs [data-baseweb="tab"]:focus {
|
228 |
+
outline: none !important;
|
229 |
+
box-shadow: 0 0 0 0.25rem rgba(0, 123, 255, 0.25) !important; /* Subtle focus glow if needed */
|
230 |
+
}
|
231 |
+
/* Nested Tabs Styling (Symptom Checker, Chat with MediBot) */
|
232 |
+
/* This targets tabs that are children of other tabs, making them smaller */
|
233 |
+
.stTabs [data-baseweb="tab-list"]:has(.stTabs [data-baseweb="tab-list"]) [data-baseweb="tab"],
|
234 |
+
.stTabs [data-baseweb="tab-list"] [role="tablist"] [data-baseweb="tab"] { /* More robust selector for nested tabs */
|
235 |
+
height: 45px; /* Slightly smaller height for sub-tabs */
|
236 |
+
padding: 10px 20px; /* Slightly smaller padding */
|
237 |
+
font-size: 1.1em;
|
238 |
+
background-color: #f0f5f9; /* Lighter blue-grey for sub-tabs */
|
239 |
+
border-radius: 10px; /* Fully rounded corners for sub-tabs */
|
240 |
+
border: 1px solid #d8e2ed;
|
241 |
+
margin: 0 8px; /* Small margin between sub-tabs */
|
242 |
+
box-shadow: none; /* No individual shadow for sub-tabs */
|
243 |
+
color: #5f7a96;
|
244 |
+
outline: none !important; /* Remove outline on focus */
|
245 |
+
min-width: unset; /* Remove min-width for sub-tabs */
|
246 |
+
}
|
247 |
+
.stTabs [data-baseweb="tab-list"]:has(.stTabs [data-baseweb="tab-list"]) [data-baseweb="tab"]:hover,
|
248 |
+
.stTabs [data-baseweb="tab-list"] [role="tablist"] [data-baseweb="tab"]:hover {
|
249 |
+
background-color: #e3ecf5;
|
250 |
+
color: #004d99;
|
251 |
+
transform: none; /* No lift for sub-tabs */
|
252 |
+
outline: none !important; /* Ensure no outline on hover either */
|
253 |
+
}
|
254 |
+
/* Updated styling for selected nested tabs */
|
255 |
+
.stTabs [data-baseweb="tab-list"]:has(.stTabs [data-baseweb="tab-list"]) [aria-selected="true"],
|
256 |
+
.stTabs [data-baseweb="tab-list"] [role="tablist"] [aria-selected="true"] {
|
257 |
+
background-color: #0056b3; /* Slightly darker blue for selected sub-tab */
|
258 |
+
color: white;
|
259 |
+
border: 1px solid #0056b3;
|
260 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15); /* Subtle shadow for selected sub-tab */
|
261 |
+
outline: none !important; /* Ensure no outline when selected */
|
262 |
+
}
|
263 |
+
/* General Button Styling */
|
264 |
+
.stButton>button {
|
265 |
+
background-color: #007bff; /* Primary blue button */
|
266 |
+
color: white;
|
267 |
+
border-radius: 10px; /* More rounded */
|
268 |
+
padding: 12px 28px; /* More padding */
|
269 |
+
font-weight: 600; /* Semi-bold */
|
270 |
+
border: none;
|
271 |
+
cursor: pointer;
|
272 |
+
box-shadow: 0 5px 15px rgba(0,0,0,0.1); /* Softer, more diffuse shadow */
|
273 |
+
transition: all 0.3s ease-in-out; /* Smooth transitions */
|
274 |
+
font-size: 1.05em; /* Slightly larger text */
|
275 |
+
outline: none; /* Remove outline on focus */
|
276 |
+
width: auto; /* Allow buttons to size naturally, but consider max-width on mobile */
|
277 |
+
min-width: 100px; /* Ensure minimum tappable area */
|
278 |
+
}
|
279 |
+
.stButton>button:hover {
|
280 |
+
background-color: #0056b3; /* Darker blue on hover */
|
281 |
+
box-shadow: 0 8px 20px rgba(0,0,0,0.15); /* Larger shadow on hover */
|
282 |
+
transform: translateY(-3px); /* More pronounced lift */
|
283 |
+
outline: none; /* Ensure no outline on hover either */
|
284 |
+
}
|
285 |
+
/* Full width buttons in sidebar for better mobile experience */
|
286 |
+
.sidebar .stButton>button {
|
287 |
+
width: 100%; /* Full width buttons in sidebar */
|
288 |
+
margin-bottom: 10px;
|
289 |
+
}
|
290 |
+
/* Input Fields, Selects, etc. */
|
291 |
+
.stTextInput>div>div>input,
|
292 |
+
.stTextArea>div>div>textarea, /* Target textarea as well */
|
293 |
+
.stDateInput>div>div>input,
|
294 |
+
.stMultiSelect>div>div {
|
295 |
+
border-radius: 10px; /* More rounded */
|
296 |
+
border: 1px solid #ced4da; /* Light grey border */
|
297 |
+
padding: 10px 15px; /* More padding */
|
298 |
+
box-shadow: inset 0 1px 4px rgba(0,0,0,0.05); /* Inner shadow */
|
299 |
+
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
300 |
+
background-color: #fefefe; /* Slightly off-white background */
|
301 |
+
outline: none; /* Remove default outline */
|
302 |
+
color: #343a40; /* Explicitly set text color to dark grey */
|
303 |
+
}
|
304 |
+
.stTextInput>div>div>input:focus,
|
305 |
+
.stTextArea>div>div>textarea:focus,
|
306 |
+
.stDateInput>div>div>input:focus,
|
307 |
+
.stMultiSelect>div>div:focus-within {
|
308 |
+
border-color: #007bff; /* Primary blue border on focus */
|
309 |
+
box-shadow: 0 0 0 0.25rem rgba(0,123,255,.25); /* Focus glow */
|
310 |
+
outline: none; /* Remove default outline */
|
311 |
+
}
|
312 |
+
/* Specific styling for the multiselect selected items */
|
313 |
+
.stMultiSelect div[data-baseweb="tag"] {
|
314 |
+
background-color: #e0f2f7 !important; /* Light blue tag background */
|
315 |
+
color: #004d99 !important; /* Dark blue tag text */
|
316 |
+
border-radius: 5px !important;
|
317 |
+
padding: 5px 10px !important;
|
318 |
+
margin: 2px !important;
|
319 |
+
}
|
320 |
+
/* Expander (for disease info) */
|
321 |
+
.stExpander {
|
322 |
+
border: 1px solid #e0e7ee; /* Lighter grey border */
|
323 |
+
border-radius: 12px; /* More rounded */
|
324 |
+
margin-bottom: 15px; /* More space below */
|
325 |
+
background-color: #ffffff; /* White background */
|
326 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.05); /* Softer shadow */
|
327 |
+
overflow: hidden; /* Ensure rounded corners clip content */
|
328 |
+
}
|
329 |
+
.stExpander > div > div > .streamlit-expanderHeader {
|
330 |
+
background-color: #f0f5f9; /* Light blue header */
|
331 |
+
padding: 15px 20px;
|
332 |
+
font-weight: 600;
|
333 |
+
font-size: 1.1em;
|
334 |
+
color: #004d99;
|
335 |
+
border-bottom: 1px solid #e0e7ee;
|
336 |
+
outline: none !important; /* Remove outline on focus */
|
337 |
+
}
|
338 |
+
.stExpander > div > div > .streamlit-expanderHeader:focus {
|
339 |
+
outline: none !important; /* Ensure no outline on focus */
|
340 |
+
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important; /* Subtle focus ring */
|
341 |
+
}
|
342 |
+
.stExpander > div > div > .streamlit-expanderContent {
|
343 |
+
padding: 20px; /* Increased padding inside */
|
344 |
+
}
|
345 |
+
/* Sidebar Styling */
|
346 |
+
/* Streamlit's sidebar elements are usually inside a div with role="complementary" */
|
347 |
+
.st-emotion-cache-vk33as { /* This targets the Streamlit sidebar div with padding */
|
348 |
+
background-color: #ffffff; /* White sidebar background */
|
349 |
+
padding: 25px;
|
350 |
+
border-radius: 12px;
|
351 |
+
box-shadow: 0 8px 24px rgba(0,0,0,0.08); /* Consistent shadow */
|
352 |
+
margin-top: 2rem;
|
353 |
+
}
|
354 |
+
.sidebar .stButton>button {
|
355 |
+
width: 100%; /* Full width buttons in sidebar */
|
356 |
margin-bottom: 10px;
|
357 |
}
|
358 |
+
.sidebar h2, .sidebar h3 {
|
359 |
+
color: #004d99;
|
360 |
+
text-align: center;
|
361 |
+
margin-bottom: 1.5em;
|
362 |
+
}
|
363 |
+
.sidebar .stTextInput, .sidebar .stDateInput, .sidebar .stSelectbox {
|
364 |
+
margin-bottom: 1em; /* Space between sidebar inputs */
|
365 |
+
}
|
366 |
+
/* Alerts */
|
367 |
+
.stAlert {
|
368 |
+
border-radius: 10px; /* Rounded corners for alerts */
|
369 |
+
padding: 15px;
|
370 |
+
font-size: 1.05em;
|
371 |
+
}
|
372 |
+
.stAlert.st-success { background-color: #d4edda; color: #155724; border-color: #c3e6cb; }
|
373 |
+
.stAlert.st-info { background-color: #d1ecf1; color: #0c5460; border-color: #bee5eb; }
|
374 |
+
.stAlert.st-warning { background-color: #fff3cd; color: #856404; border-color: #ffeeba; }
|
375 |
+
.stAlert.st-error { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; }
|
376 |
+
/* Spinners */
|
377 |
+
.stSpinner { color: #007bff; } /* Spinner color matching primary button */
|
378 |
+
/* Disclaimer Box - using a more reliable target */
|
379 |
+
div[data-testid="stVerticalBlock"] > div:first-child > div:has(p > strong:contains("Disclaimer")) {
|
380 |
+
background-color: #fff8e1; /* Light yellow for warnings/disclaimers */
|
381 |
+
border: 1px solid #ffeeba;
|
382 |
+
padding: 1.5rem;
|
383 |
+
border-radius: 10px;
|
384 |
+
margin-bottom: 2rem;
|
385 |
+
color: #856404;
|
386 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
387 |
+
}
|
388 |
+
/* This targets the specific disclaimer block by looking for its content */
|
389 |
+
.stAlert[data-testid="stAlert"] p strong:contains("Important Disclaimer") {
|
390 |
+
color: #664d03;
|
391 |
+
}
|
392 |
+
/* General text improvements */
|
393 |
+
p {
|
394 |
+
line-height: 1.7;
|
395 |
+
margin-bottom: 1em;
|
396 |
}
|
397 |
+
ul, ol {
|
398 |
+
margin-bottom: 1em;
|
399 |
+
padding-left: 25px;
|
400 |
+
}
|
401 |
+
li {
|
402 |
+
margin-bottom: 0.5em;
|
403 |
+
}
|
404 |
+
/* Increased font size for history expander titles */
|
405 |
+
.stExpander > div > div > .streamlit-expanderHeader {
|
406 |
+
font-size: 1.2em; /* Increased font size */
|
407 |
+
}
|
408 |
+
/* Media queries for specific mobile adjustments if needed (example) */
|
409 |
+
@media (max-width: 768px) {
|
410 |
+
h1 {
|
411 |
+
font-size: 2.2em; /* Slightly smaller H1 on mobile */
|
412 |
+
}
|
413 |
+
h2 {
|
414 |
+
font-size: 1.8em; /* Slightly smaller H2 on mobile */
|
415 |
+
}
|
416 |
+
.stTabs [data-baseweb="tab"] {
|
417 |
+
font-size: 1em; /* Smaller tab font size on mobile */
|
418 |
+
padding: 10px 15px; /* Reduced padding for tabs on mobile */
|
419 |
+
height: auto; /* Allow height to adjust */
|
420 |
+
min-width: unset; /* Remove min-width to allow more flexibility */
|
421 |
+
flex: 1 1 auto; /* Allow tabs to grow and shrink more flexibly */
|
422 |
+
}
|
423 |
+
.stTabs [data-baseweb="tab-list"] {
|
424 |
+
gap: 10px; /* Smaller gap between tabs on mobile */
|
425 |
+
}
|
426 |
+
/* Make multiselect items slightly smaller on mobile for better fit */
|
427 |
+
.stMultiSelect div[data-baseweb="tag"] {
|
428 |
+
font-size: 0.9em !important;
|
429 |
+
padding: 3px 8px !important;
|
430 |
+
}
|
431 |
+
}
|
432 |
+
</style>""", unsafe_allow_html=True)
|
433 |
+
|
434 |
+
# --- Groq API Response Function (updated system prompt) ---
|
435 |
+
def get_groq_response(user_query, severity_label="Undetermined Severity", model="llama-3.3-70b-versatile"):
|
436 |
+
"""
|
437 |
+
Function to get a response from Groq API for health questions.
|
438 |
+
Augments the system prompt with severity information if available.
|
439 |
+
"""
|
440 |
+
if not GROQ_AVAILABLE or groq_client is None:
|
441 |
+
return "AI assistant is not available."
|
442 |
+
# Augment the system prompt with severity information from clinical diagnosis model
|
443 |
+
# Note: 'severity_label' comes from the symptom checker. If asking a direct question,
|
444 |
+
# it might default to "Undetermined Severity" unless explicitly passed from a prior analysis.
|
445 |
+
system_prompt = (
|
446 |
+
"You are an experienced physician specialized in diagnosis and clinical decision-making. "
|
447 |
+
"Your primary role is to analyze presented symptoms or health queries and provide a differential diagnosis along with evidence-based recommendations for next steps. "
|
448 |
+
f"Based on initial analysis, the perceived severity of the user's condition is: {severity_label}. "
|
449 |
+
"Adjust your tone and recommendations accordingly, emphasizing urgency if severity is 'High Severity'.\n\n"
|
450 |
+
"When a user describes symptoms or asks a health question, you should:\n\n"
|
451 |
+
"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"
|
452 |
+
"2. **Reasoning**: Briefly explain the clinical reasoning for each diagnosis, referencing common clinical features, pathophysiology, or typical presentations.\n"
|
453 |
+
"3. **Recommended Investigations**: Suggest appropriate next diagnostic tests or investigations to confirm or rule out these conditions.\n\n"
|
454 |
+
"4. **Initial Management/Treatment**: Propose evidence-based initial management or general treatment options (e.g., symptomatic relief, lifestyle changes, over-the-counter suggestions).\n"
|
455 |
+
"5. **Red Flags/Urgency**: Clearly highlight any red flags or warning signs that would require immediate emergency care or urgent specialist referral.\n\n"
|
456 |
+
"Always maintain a professional, empathetic, and medically accurate tone. Present information clearly, using bullet points or numbered lists where appropriate. "
|
457 |
+
"Crucially, always conclude your response by strongly advising the user to consult a qualified healthcare professional for a definitive diagnosis and personalized treatment plan, "
|
458 |
+
"as this AI is for informational purposes only and not a substitute for professional medical advice."
|
459 |
+
)
|
460 |
+
try:
|
461 |
+
chat_completion = groq_client.chat.completions.create(
|
462 |
+
messages=[
|
463 |
+
{"role": "system", "content": system_prompt},
|
464 |
+
{"role": "user", "content": user_query},
|
465 |
+
],
|
466 |
+
model=model,
|
467 |
+
temperature=0.7,
|
468 |
+
max_tokens=800, # Increased max_tokens to allow for more detailed responses
|
469 |
+
top_p=1,
|
470 |
+
stop=None,
|
471 |
+
stream=False,
|
472 |
+
)
|
473 |
+
return chat_completion.choices[0].message.content
|
474 |
+
except Exception as e:
|
475 |
+
st.error(f"Groq API call failed: {e}")
|
476 |
+
return "Sorry, I am unable to process that request at the moment due to an AI service error."
|
477 |
+
|
478 |
+
# --- Clinical Diagnosis Model Integration (DATEXIS/CORe-clinical-diagnosis-prediction) ---
|
479 |
+
def get_diagnosis_and_severity_from_model(symptoms_text):
|
480 |
+
"""
|
481 |
+
Function to get diagnosis predictions and infer severity from DATEXIS/CORe-clinical-diagnosis-prediction
|
482 |
+
using Hugging Face Inference API.
|
483 |
+
"""
|
484 |
+
if not HF_MODEL_AVAILABLE or not HF_API_TOKEN:
|
485 |
+
st.warning("Hugging Face API key not available. Cannot assess severity.")
|
486 |
+
return "Severity Assessment Unavailable (API Key Missing)", []
|
487 |
+
API_URL = "https://api-inference.huggingface.co/models/DATEXIS/CORe-clinical-diagnosis-prediction"
|
488 |
+
headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
|
489 |
+
payload = {"inputs": symptoms_text}
|
490 |
+
try:
|
491 |
+
response = requests.post(API_URL, headers=headers, json=payload)
|
492 |
+
response.raise_for_status()
|
493 |
+
result = response.json()
|
494 |
+
raw_predicted_diagnoses = []
|
495 |
+
threshold = 0.5
|
496 |
+
if result and isinstance(result, list) and len(result) > 0 and isinstance(result[0], list):
|
497 |
+
for prediction_set in result:
|
498 |
+
for item in prediction_set:
|
499 |
+
if item['score'] >= threshold:
|
500 |
+
raw_predicted_diagnoses.append(item['label'])
|
501 |
+
|
502 |
+
filtered_diagnoses = []
|
503 |
+
# EXPANDED AND REFINED GENERIC FILTER KEYWORDS
|
504 |
+
generic_filter_keywords = [
|
505 |
+
'unspecified', 'acute', 'chronic', 'use', 'status', 'other', 'not elsewhere classified',
|
506 |
+
'no diagnosis', 'history of', 'finding', 'problem', 'syndrome', 'disease',
|
507 |
+
'disorder', 'condition', 'code', 'category', 'episode', 'complication',
|
508 |
+
'sequelae', 'factor', 'manifestation', 'procedure', 'examination', 'observation',
|
509 |
+
'symptoms', 'sign', 'unconfirmed', 'type', 'group', 'normal', 'unknown', 'level',
|
510 |
+
'positive', 'negative', 'patient', 'value', 'test', 'result', 'diagnosis',
|
511 |
+
'kidney', 'stage', 'without', 'essential', 'with', 'due to', 'related to', 'of',
|
512 |
+
'organ', 'function', 'system', 'body', 'region',
|
513 |
+
'clinical', 'consideration', 'presence', 'absence', 'mild', 'moderate', 'severe',
|
514 |
+
'manifesting', 'affecting', 'affect', 'area', 'part', 'general', 'specific',
|
515 |
+
'diagnosis of', 'history of', 'finding of', 'problem of', 'type of', 'group of',
|
516 |
+
'unlikely', 'possible', 'likely', 'primary', 'secondary', 'and', 'or', 'by', 'for', 'in', 'on',
|
517 |
+
'symptom', 'sign', 'pain', 'fever', 'cough', 'vomiting', 'nausea', 'rash', 'headache', 'fatigue', 'diarrhea', 'sore throat', 'hemorrhage',
|
518 |
+
# Additional common non-diagnostic terms from ICD or general medical language
|
519 |
+
'i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix', 'x', # Roman numerals
|
520 |
+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', # Single letters
|
521 |
+
'longterm', 'shortterm', 'controlled', 'uncontrolled', 'recurrent', 'intermittent', 'persistent',
|
522 |
+
'follow-up', 'observation', 'screening', 'encounter', 'admission', 'discharge',
|
523 |
+
'acute on chronic', 'other specified', 'not otherwise specified'
|
524 |
+
]
|
525 |
+
|
526 |
+
for diagnosis_label in raw_predicted_diagnoses:
|
527 |
+
lower_diag = diagnosis_label.lower().strip()
|
528 |
+
is_generic = False
|
529 |
+
|
530 |
+
# Rule 1: Check against expanded generic filter keywords
|
531 |
+
for generic_kw in generic_filter_keywords:
|
532 |
+
# Use regex for whole word matching to avoid partial matches (e.g., 'use' matching 'house')
|
533 |
+
if re.fullmatch(r'\b' + re.escape(generic_kw) + r'\b', lower_diag):
|
534 |
+
is_generic = True
|
535 |
+
break
|
536 |
+
if is_generic:
|
537 |
+
continue # Skip to next diagnosis if it's a generic keyword
|
538 |
+
|
539 |
+
# Rule 2: Filter out very short numerical or alphanumeric strings (e.g., 'ii', 'a1')
|
540 |
+
if len(lower_diag) <= 2 and (lower_diag.replace('.', '').isdigit() or lower_diag.isalnum()):
|
541 |
+
is_generic = True
|
542 |
+
if is_generic:
|
543 |
+
continue
|
544 |
+
|
545 |
+
# Rule 3: Filter out terms that are purely numerical codes (e.g., '250.00', 'E11.9')
|
546 |
+
# This is a heuristic, as some numbers might be part of a valid diagnosis
|
547 |
+
if re.fullmatch(r'[\d\.]+', lower_diag) and len(lower_diag) < 7: # e.g., "250.00" or "E11.9"
|
548 |
+
is_generic = True
|
549 |
+
if is_generic:
|
550 |
+
continue
|
551 |
+
|
552 |
+
# Rule 4: Filter out terms that are single words and are very common, non-specific symptoms
|
553 |
+
# (already covered by generic_filter_keywords, but an explicit check for robustness)
|
554 |
+
if len(lower_diag.split()) == 1 and lower_diag in ['pain', 'fever', 'cough', 'vomiting', 'nausea', 'rash', 'headache', 'fatigue', 'diarrhea', 'sore throat', 'hemorrhage']:
|
555 |
+
is_generic = True
|
556 |
+
if is_generic:
|
557 |
+
continue
|
558 |
+
|
559 |
+
# If none of the above rules flagged it as generic, add to filtered list
|
560 |
+
filtered_diagnoses.append(diagnosis_label)
|
561 |
|
562 |
+
filtered_diagnoses = list(dict.fromkeys(filtered_diagnoses)) # Remove duplicates
|
563 |
+
if len(filtered_diagnoses) > 5:
|
564 |
+
filtered_diagnoses = filtered_diagnoses[:5] # Limit to top 5
|
565 |
+
|
566 |
+
if not filtered_diagnoses:
|
567 |
+
return "Overall Symptom Severity (AI Assessment): Undetermined Severity", []
|
568 |
+
|
569 |
+
severity_map_keywords = {
|
570 |
+
"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'],
|
571 |
+
"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'],
|
572 |
+
"Low Severity": ['headache', 'pain', 'mild', 'allergy', 'fever', 'cough', 'common cold', 'dermatitis', 'arthritis', 'influenza', 'viral', 'sprain', 'strain', 'gastritis', 'sore throat', 'conjunctivitis', 'sinusitis', 'bruise', 'rash', 'minor']
|
573 |
+
}
|
574 |
+
|
575 |
+
overall_severity = "Low Severity"
|
576 |
+
for diagnosis_label in filtered_diagnoses:
|
577 |
+
diag_lower = diagnosis_label.lower()
|
578 |
+
if any(keyword in diag_lower for keyword in severity_map_keywords["High Severity"]):
|
579 |
+
return "Overall Symptom Severity (AI Assessment): High Severity", filtered_diagnoses
|
580 |
+
if any(keyword in diag_lower for keyword in severity_map_keywords["Moderate Severity"]):
|
581 |
+
if overall_severity == "Low Severity":
|
582 |
+
overall_severity = "Moderate Severity"
|
583 |
+
# Keep looping to see if a High Severity diagnosis is found later in the filtered list
|
584 |
+
# Don't return here if a Moderate is found, continue to check for High
|
585 |
+
return f"Overall Symptom Severity (AI Assessment): {overall_severity}", filtered_diagnoses
|
586 |
+
except requests.exceptions.RequestException as e:
|
587 |
+
st.error(f"Network or API Error during clinical diagnosis model call: {e}. Check API key and internet connection.")
|
588 |
+
return "Severity Assessment Unavailable (Network Error)", []
|
589 |
+
except Exception as e:
|
590 |
+
st.error(f"Error processing clinical diagnosis model response: {e}. Unexpected data format or mapping issue.")
|
591 |
+
return "Severity Assessment Unavailable (Processing Error)", []
|
592 |
+
|
593 |
+
# --- Functions for PDF generation (retained and adjusted for DB history) ---
|
594 |
+
def generate_pdf_report(history_data):
|
595 |
+
buffer = io.BytesIO()
|
596 |
+
c = canvas.Canvas(buffer, pagesize=letter)
|
597 |
+
width, height = letter
|
598 |
+
c.setFont("Helvetica-Bold", 16)
|
599 |
+
c.drawString(50, height - 50, "MediBot Health Report")
|
600 |
+
c.setFont("Helvetica", 10)
|
601 |
+
c.drawString(50, height - 70, f"Report Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
602 |
+
y = height - 100
|
603 |
+
for entry in history_data:
|
604 |
+
# history_data comes as list of tuples: (history_id, symptoms, predicted_diseases, query_timestamp)
|
605 |
+
timestamp, symptoms_text, predicted_diseases = entry[3], entry[1], entry[2]
|
606 |
+
if y < 100:
|
607 |
+
c.showPage()
|
608 |
+
c.setFont("Helvetica-Bold", 16)
|
609 |
+
c.drawString(50, height - 50, "MediBot Health Report (Continued)")
|
610 |
+
c.setFont("Helvetica", 10)
|
611 |
+
y = height - 100
|
612 |
+
|
613 |
+
c.setFont("Helvetica-Bold", 10)
|
614 |
+
c.drawString(50, y, f"Timestamp: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
|
615 |
+
y -= 15
|
616 |
+
c.setFont("Helvetica", 10)
|
617 |
+
symptoms_line = f"Symptoms/Question: {symptoms_text}"
|
618 |
+
insight_line = f"Insight/Response: {predicted_diseases}"
|
619 |
+
|
620 |
+
textobject = c.beginText(50, y)
|
621 |
+
textobject.setFont("Helvetica", 10)
|
622 |
+
|
623 |
+
# Helper to wrap text within PDF
|
624 |
+
def draw_wrapped_text(text_obj, text, max_width):
|
625 |
+
nonlocal y
|
626 |
+
words = text.split(' ')
|
627 |
+
current_line_words = []
|
628 |
+
for word_idx, word in enumerate(words):
|
629 |
+
temp_line = ' '.join(current_line_words + [word])
|
630 |
+
if c.stringWidth(temp_line, "Helvetica", 10) < max_width:
|
631 |
+
current_line_words.append(word)
|
632 |
+
else:
|
633 |
+
text_obj.textLine(' '.join(current_line_words))
|
634 |
+
y -= 12 # Line height
|
635 |
+
current_line_words = [word]
|
636 |
+
# Handle very long words that exceed max_width
|
637 |
+
if c.stringWidth(word, "Helvetica", 10) >= max_width:
|
638 |
+
# If a single word is too long, break it
|
639 |
+
chunk_size = 50 # Arbitrary chunk size for breaking long words
|
640 |
+
for i in range(0, len(word), chunk_size):
|
641 |
+
text_obj.textLine(word[i:i + chunk_size])
|
642 |
+
y -= 12
|
643 |
+
current_line_words = [] # Reset after a long word is broken
|
644 |
+
continue
|
645 |
+
if current_line_words: # Draw any remaining words
|
646 |
+
text_obj.textLine(' '.join(current_line_words))
|
647 |
+
y -= 12 # Line height
|
648 |
+
|
649 |
+
draw_wrapped_text(textobject, symptoms_line, width - 100)
|
650 |
+
draw_wrapped_text(textobject, insight_line, width - 100)
|
651 |
+
c.drawText(textobject)
|
652 |
+
y -= 20
|
653 |
+
c.save()
|
654 |
+
buffer.seek(0)
|
655 |
+
return buffer
|
656 |
+
|
657 |
+
# --- Symptom Checker Logic (Now using hardcoded data and severity) ---
|
658 |
+
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]]]:
|
659 |
+
"""
|
660 |
+
Finds potential diseases, descriptions, and precautions based on selected symptoms
|
661 |
+
using the hardcoded CSV data, incorporating symptom severity.
|
662 |
+
Returns a list of tuples: (disease_name, description, [precautions])
|
663 |
+
"""
|
664 |
+
matching_diseases_with_scores = {} # Stores disease -> weighted_score
|
665 |
+
# Normalize selected symptoms for consistent matching
|
666 |
+
normalized_selected_symptoms = [s.strip().lower() for s in selected_symptoms]
|
667 |
+
|
668 |
+
for disease, symptoms_list in disease_symptoms_map.items():
|
669 |
+
# Convert the symptoms_list from map to lowercase for comparison
|
670 |
+
normalized_disease_symptoms = [s.lower() for s in symptoms_list]
|
671 |
+
weighted_score = 0
|
672 |
+
for selected_symptom in normalized_selected_symptoms:
|
673 |
+
if selected_symptom in normalized_disease_symptoms:
|
674 |
+
# Add severity weight if available, otherwise a default of 1
|
675 |
+
# The .get() method is safe if a symptom is in dataset.csv but not in Symptom-severity.csv
|
676 |
+
weight = symptom_severity_map.get(selected_symptom, 1)
|
677 |
+
weighted_score += weight
|
678 |
+
if weighted_score > 0:
|
679 |
+
matching_diseases_with_scores[disease] = weighted_score
|
680 |
+
|
681 |
+
# Sort diseases by the weighted score (highest first)
|
682 |
+
sorted_matching_diseases = sorted(matching_diseases_with_scores.items(), key=lambda item: item[1], reverse=True)
|
683 |
+
|
684 |
+
results = []
|
685 |
+
# Limit to top 5 most matching diseases (or fewer if less than 5 matches)
|
686 |
+
for disease_name, _ in sorted_matching_diseases[:5]:
|
687 |
+
description = disease_description_map.get(disease_name, "No description available.")
|
688 |
+
precautions = disease_precaution_map.get(disease_name, ["No precautions available."])
|
689 |
+
results.append((disease_name, description, precautions))
|
690 |
+
return results
|
691 |
+
|
692 |
+
# --- Function to parse insight text for analytics ---
|
693 |
+
def parse_insight_text_for_conditions(insight_text: str) -> list[str]:
|
694 |
+
"""
|
695 |
+
Parses the combined insight text from history to extract condition names.
|
696 |
+
Expected format: "Severity. Dataset Conditions: Cond1, Cond2; AI Suggestions: AICond1, AIC2"
|
697 |
+
or just "Severity. No specific insights found."
|
698 |
+
"""
|
699 |
+
conditions = []
|
700 |
+
# Regex to find "Dataset Conditions: ..." and "AI Suggestions: ..." parts
|
701 |
+
dataset_match = re.search(r"Dataset Conditions:\s*([^;]+)", insight_text)
|
702 |
+
ai_match = re.search(r"AI Suggestions:\s*(.+)", insight_text)
|
703 |
+
|
704 |
+
if dataset_match:
|
705 |
+
dataset_str = dataset_match.group(1).strip()
|
706 |
+
if dataset_str and dataset_str.lower() != "no specific insights found":
|
707 |
+
conditions.extend([c.strip() for c in dataset_str.split(',') if c.strip()])
|
708 |
+
|
709 |
+
if ai_match:
|
710 |
+
ai_str = ai_match.group(1).strip()
|
711 |
+
if ai_str and ai_str.lower() != "no specific insights found" and ai_str.lower() != "ai assistant is not available.":
|
712 |
+
conditions.extend([c.strip() for c in ai_str.split(',') if c.strip()])
|
713 |
+
|
714 |
+
# Remove duplicates and return
|
715 |
+
return list(set(conditions))
|
716 |
+
|
717 |
+
# --- Main Application Logic ---
|
718 |
+
# Ensure feedback_input is initialized if not already (redundant if done at start, but harmless)
|
719 |
+
if "feedback_input" not in st.session_state:
|
720 |
+
st.session_state.feedback_input = ""
|
721 |
+
|
722 |
+
if st.session_state.show_welcome:
|
723 |
+
# A more visually appealing welcome page
|
724 |
+
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)
|
725 |
+
st.markdown("""
|
726 |
+
<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);'>
|
727 |
+
<p style='font-size: 1.2em; line-height: 1.8; color: #336699;'>
|
728 |
+
Explore possible causes and precautions for your symptoms and get answers to health-related questions using advanced AI.
|
729 |
+
Your health journey, simplified and supported.
|
730 |
+
</p>
|
731 |
+
<ul style='list-style-type: none; padding: 0; margin-top: 25px; display: inline-block; text-align: left;'>
|
732 |
+
<li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>✅ <b>Symptom Checker:</b> Get insights from a comprehensive symptom list.</li>
|
733 |
+
<li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>💬 <b>AI Chatbot:</b> Ask general health questions.</li>
|
734 |
+
<li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>👤 <b>User Management:</b> Save your health history.</li>
|
735 |
+
<li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>📜 <b>Persistent History:</b> View and download past records.</li>
|
736 |
+
<li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>📈 <b>Usage Insights:</b> See trends in conditions.</li>
|
737 |
+
</ul>
|
738 |
+
</div>
|
739 |
+
""", unsafe_allow_html=True)
|
740 |
+
st.markdown("<div style='text-align: center; margin-top: 40px;'>", unsafe_allow_html=True)
|
741 |
+
if st.button("Get Started", key="welcome_button"):
|
742 |
+
st.session_state.show_welcome = False
|
743 |
+
st.rerun() # Force rerun to ensure app reloads to main page
|
744 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
745 |
+
st.markdown("""
|
746 |
+
<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);'>
|
747 |
+
<p style='font-size: 0.9em; margin-bottom: 0;'>
|
748 |
+
<b>Important Disclaimer:</b> This app provides preliminary health information based on symptoms and AI analysis.
|
749 |
+
<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.
|
750 |
+
</p>
|
751 |
+
</div>
|
752 |
+
""", unsafe_allow_html=True)
|
753 |
+
else:
|
754 |
+
# Displaying disclaimer at the top of the main app view
|
755 |
+
st.markdown("<div class='stContainer' style='background-color: #fff8e1; border: 1px solid #ffeeba;'>"
|
756 |
+
"<p style='margin-bottom: 0; font-size: 0.95em; color: #856404;'>"
|
757 |
"<strong>Disclaimer</strong>: This app provides preliminary health information based on symptoms and AI analysis. "
|
758 |
"It is not a substitute for professional medical advice, diagnosis, or treatment. Always consult a qualified healthcare provider."
|
759 |
+
"</p>"
|
760 |
"</div>", unsafe_allow_html=True)
|
761 |
|
762 |
+
# Sidebar for User Management
|
763 |
+
with st.sidebar:
|
764 |
+
st.header("User Management")
|
765 |
+
# Call the refactored user management function, passing st.session_state
|
766 |
+
render_user_management_sidebar(st.session_state)
|
767 |
|
768 |
+
st.markdown("---")
|
769 |
+
st.header("Navigation")
|
770 |
+
# Add a button to clear chat history
|
771 |
+
if st.button("Clear Chat History", help="Clears all messages from the current session."):
|
772 |
+
st.session_state.chat_history = []
|
773 |
+
st.session_state.last_chat_response = ""
|
774 |
+
st.session_state.chat_input_value = ""
|
775 |
+
st.success("Chat history cleared!")
|
776 |
|
777 |
+
# PDF Download Button - available if user is logged in and has history
|
778 |
+
if st.session_state.get("user_id"):
|
779 |
+
user_history = get_user_history(st.session_state["user_id"]) # Directly call get_user_history
|
780 |
+
if user_history:
|
781 |
+
pdf_buffer = generate_pdf_report(user_history)
|
782 |
+
st.download_button(
|
783 |
+
label="Download Health Report (PDF)",
|
784 |
+
data=pdf_buffer,
|
785 |
+
file_name="medibot_health_report.pdf",
|
786 |
+
mime="application/pdf",
|
787 |
+
help="Download your chat and symptom checker history as a PDF report."
|
788 |
+
)
|
789 |
+
else:
|
790 |
+
st.info("No history to download yet. Interact with MediBot to generate a report.")
|
791 |
else:
|
792 |
+
st.info("Log in to download your health report.")
|
|
|
|
|
|
|
|
|
793 |
|
794 |
+
st.markdown("<h1 style='text-align: center; color: #0056b3;'>MediBot - Your Health Assistant 🩺</h1>", unsafe_allow_html=True)
|
795 |
+
# Top-level tabs: Home, History, Feedback, About, Insights
|
796 |
+
# Re-ordered tabs slightly to put Insights closer to History
|
797 |
+
main_tab_home, main_tab_history, main_tab_insights, main_tab_feedback, main_tab_about = st.tabs(["Home", "History", "Insights", "Feedback", "About"])
|
798 |
+
|
799 |
+
with main_tab_home:
|
800 |
+
st.markdown("<h2>How can I help you today?</h2>", unsafe_allow_html=True)
|
801 |
+
# Nested tabs for Symptom Checker and Chat with MediBot
|
802 |
+
tab_symptom_checker, tab_chatbot = st.tabs(["Symptom Checker", "Chat with MediBot"])
|
803 |
+
|
804 |
+
with tab_symptom_checker:
|
805 |
+
st.markdown("<h3>Select from common symptoms to get insights:</h3>", unsafe_allow_html=True)
|
806 |
+
selected_symptoms = st.multiselect(
|
807 |
+
"Select your symptoms:",
|
808 |
+
options=hardcoded_symptoms, # Use symptoms derived from CSV
|
809 |
+
default=[],
|
810 |
+
key="symptom_select",
|
811 |
+
help="Choose one or more symptoms you are experiencing."
|
812 |
+
)
|
813 |
+
|
814 |
+
if st.button("Get Health Insight", key="diagnose_button"):
|
815 |
+
if not selected_symptoms:
|
816 |
+
st.error("Please select at least one symptom.")
|
817 |
else:
|
818 |
+
with st.spinner("Analyzing symptoms..."):
|
819 |
+
# Get overall severity and predicted diagnoses from the Hugging Face model
|
820 |
+
combined_symptoms_text = ", ".join(selected_symptoms)
|
821 |
+
severity_assessment_text, predicted_diagnoses_from_model = get_diagnosis_and_severity_from_model(combined_symptoms_text)
|
822 |
+
|
823 |
+
# Get relevant disease info from your CSV datasets
|
824 |
+
diseases_from_csv = get_disease_info_from_csv(selected_symptoms, disease_symptoms_map, disease_description_map, disease_precaution_map, symptom_severity_map)
|
825 |
+
|
826 |
+
st.markdown(f"**{severity_assessment_text}**", unsafe_allow_html=True)
|
827 |
+
st.markdown("<h2>Possible Conditions and Insights:</h2>", unsafe_allow_html=True) # Unified Heading
|
828 |
+
|
829 |
+
# --- Process and display CSV-based diseases first ---
|
830 |
+
if diseases_from_csv:
|
831 |
+
st.markdown("<h4>From our Medical Knowledge Base:</h4>", unsafe_allow_html=True)
|
832 |
+
for disease_name, description, precautions_list in diseases_from_csv:
|
833 |
+
with st.expander(disease_name):
|
834 |
+
st.write(f"**Description**: {description}")
|
835 |
+
st.write("**Precautions**:")
|
836 |
+
if precautions_list:
|
837 |
+
for precaution in precautions_list:
|
838 |
+
if precaution: # Only display non-empty precautions
|
839 |
+
st.write(f"- {precaution}")
|
840 |
+
else:
|
841 |
+
st.write("- No specific precautions listed.")
|
842 |
+
st.markdown("---") # Separator for each disease in expander
|
843 |
+
else:
|
844 |
+
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.")
|
845 |
+
|
846 |
+
# --- Integrate relevant AI Model diagnoses not covered by CSV ---
|
847 |
+
additional_ai_diagnoses = []
|
848 |
+
# Create a set of normalized disease names from CSV for quick lookup and fuzzy matching
|
849 |
+
csv_disease_names_normalized = {d[0].lower() for d in diseases_from_csv}
|
850 |
+
|
851 |
+
for model_diag in predicted_diagnoses_from_model:
|
852 |
+
model_diag_normalized = model_diag.lower()
|
853 |
+
is_covered_by_csv = False
|
854 |
+
# Check for strong fuzzy match with CSV diseases
|
855 |
+
for csv_diag_name in csv_disease_names_normalized:
|
856 |
+
# Use token_set_ratio for better matching of multi-word terms, less sensitive to word order
|
857 |
+
if fuzz.token_set_ratio(model_diag_normalized, csv_diag_name) > 85: # High threshold for a strong match
|
858 |
+
is_covered_by_csv = True
|
859 |
+
break
|
860 |
+
if not is_covered_by_csv:
|
861 |
+
additional_ai_diagnoses.append(model_diag)
|
862 |
+
|
863 |
+
if additional_ai_diagnoses:
|
864 |
+
st.markdown("<h4>Additional AI-Suggested Clinical Considerations:</h4>", unsafe_allow_html=True)
|
865 |
+
st.write("The AI model suggests the following clinical terms based on your symptoms:")
|
866 |
+
for diag in additional_ai_diagnoses:
|
867 |
+
st.write(f"- **{diag}**: This is a medical term that the AI found relevant. Please consult a healthcare professional for more details.")
|
868 |
+
st.info("These are general medical terms and require professional interpretation. They may not have specific descriptions or precautions available in our dataset.")
|
869 |
+
elif not diseases_from_csv: # Only if no CSV diseases AND no unique AI diagnoses
|
870 |
+
st.info("The AI model could not confidently predict specific or unique diagnoses from the provided symptoms. Try different symptoms or consult a doctor.")
|
871 |
+
|
872 |
+
st.write("---") # Final Separator
|
873 |
+
|
874 |
+
# --- Save to history (adjusting content to reflect unified response) ---
|
875 |
+
history_content_parts = []
|
876 |
+
if diseases_from_csv:
|
877 |
+
history_content_parts.append("Dataset Conditions: " + ", ".join([d[0] for d in diseases_from_csv]))
|
878 |
+
if additional_ai_diagnoses:
|
879 |
+
history_content_parts.append("AI Suggestions: " + ", ".join(additional_ai_diagnoses))
|
880 |
+
|
881 |
+
# Combine all insight for history
|
882 |
+
predicted_diseases_str_for_history = f"{severity_assessment_text.replace('Overall Symptom Severity (AI Assessment): ', '')}. " + "; ".join(history_content_parts)
|
883 |
+
if not history_content_parts:
|
884 |
+
predicted_diseases_str_for_history = f"{severity_assessment_text.replace('Overall Symptom Severity (AI Assessment): ', '')}. No specific insights found."
|
885 |
+
|
886 |
+
if st.session_state.get("user_id"):
|
887 |
+
# Call the refactored save history function
|
888 |
+
save_history_to_db_if_logged_in(st.session_state.user_id, ", ".join(selected_symptoms), predicted_diseases_str_for_history)
|
889 |
+
st.success("Analysis saved to your history.")
|
890 |
+
else:
|
891 |
+
st.info("Log in to save this analysis to your history.")
|
892 |
+
|
893 |
+
with tab_chatbot: # Ask a Health Question (Chatbot)
|
894 |
+
st.markdown("<h3>Describe your symptoms or ask a general health question:</h3>", unsafe_allow_html=True)
|
895 |
+
# Use st.form without clear_on_submit=True to keep text in the area
|
896 |
+
with st.form("chat_form"): # Removed clear_on_submit=True
|
897 |
+
user_question = st.text_area(
|
898 |
+
"Input your issue:",
|
899 |
+
value=st.session_state.chat_input_value, # Use session state for value
|
900 |
+
key="chat_input_widget", # Unique widget key
|
901 |
+
placeholder="e.g., I am having a severe fever and body aches. Or, what are the causes of high blood pressure?",
|
902 |
+
height=120 # Slightly taller text area
|
903 |
)
|
904 |
+
chat_submit_button = st.form_submit_button("Ask MediBot")
|
905 |
+
|
906 |
+
if chat_submit_button:
|
907 |
+
if user_question:
|
908 |
+
# For the chatbot, if it's a direct question, severity is undetermined
|
909 |
+
# unless passed from a preceding symptom check.
|
910 |
+
# For simplicity, if not from symptom checker, pass "Undetermined Severity".
|
911 |
+
severity_for_groq_prompt = "Undetermined Severity"
|
912 |
+
with st.spinner("MediBot is thinking..."):
|
913 |
+
groq_answer = get_groq_response(user_question, severity_label=severity_for_groq_prompt)
|
914 |
+
st.markdown("**MediBot's Answer:**")
|
915 |
+
st.write(groq_answer)
|
916 |
+
# Save Q&A to chat history (if desired, not necessarily DB)
|
917 |
+
st.session_state.chat_history.append({"user": user_question, "bot": groq_answer})
|
918 |
+
|
919 |
+
# Save to database history if logged in
|
920 |
+
if st.session_state.get("user_id"):
|
921 |
+
# Prepend "Question: " to the input for history tracking
|
922 |
+
save_history_to_db_if_logged_in(st.session_state.user_id, f"Question: {user_question}", groq_answer)
|
923 |
+
st.success("Your question and answer have been saved to history.")
|
924 |
+
else:
|
925 |
+
st.info("Log in to save this Q&A to your history.")
|
926 |
+
else:
|
927 |
+
st.warning("Please type your question to get an answer.")
|
928 |
+
|
929 |
+
with main_tab_history:
|
930 |
+
st.header("Your Health History")
|
931 |
+
user_id = st.session_state.get("user_id")
|
932 |
+
if user_id:
|
933 |
+
st.info("This section shows your saved symptom analyses and health questions.")
|
934 |
+
history_data = get_user_history(user_id) # Fetch history from database
|
935 |
+
if history_data:
|
936 |
+
st.subheader("Past Interactions:")
|
937 |
+
# Display history in reverse chronological order using expanders
|
938 |
+
for entry in reversed(history_data):
|
939 |
+
timestamp, symptoms_text, insight_text = entry[3], entry[1], entry[2]
|
940 |
+
# Determine summary title based on the nature of the input
|
941 |
+
summary_title_prefix = ""
|
942 |
+
expander_icon = "" # Initialize icon here
|
943 |
+
if symptoms_text.startswith("Question: "):
|
944 |
+
keyword = extract_keyword(symptoms_text, hardcoded_symptoms)
|
945 |
+
summary_title_prefix = f"Question: {keyword}" if keyword != "Unknown" else "General Question"
|
946 |
+
expander_icon = "💬"
|
947 |
+
else: # Symptom checker entry (🩺)
|
948 |
+
summary_conditions = parse_insight_text_for_conditions(insight_text)
|
949 |
+
if summary_conditions:
|
950 |
+
summary_title_prefix = ", ".join(summary_conditions[:3])
|
951 |
+
if len(summary_conditions) > 3:
|
952 |
+
summary_title_prefix += "..."
|
953 |
else:
|
954 |
+
# MODIFIED LOGIC: If no conditions identified, try to use input symptoms
|
955 |
+
input_symptoms_match = re.search(r"Symptoms/Question: (.*)", symptoms_text) # Adjusted regex for "Symptoms/Question:"
|
956 |
+
if input_symptoms_match:
|
957 |
+
extracted_input_symptoms = input_symptoms_match.group(1).strip()
|
958 |
+
# Clean up potential extra spaces/commas, take first few if too many
|
959 |
+
clean_symptoms = [s.strip() for s in extracted_input_symptoms.split(',') if s.strip()]
|
960 |
+
if clean_symptoms:
|
961 |
+
summary_title_prefix = ", ".join(clean_symptoms[:3])
|
962 |
+
if len(clean_symptoms) > 3:
|
963 |
+
summary_title_prefix += "..."
|
964 |
+
else:
|
965 |
+
summary_title_prefix = "No conditions identified" # Fallback if no symptoms extracted
|
966 |
+
else:
|
967 |
+
summary_title_prefix = "No conditions identified" # Fallback if input format is unexpected
|
968 |
+
expander_icon = "🩺"
|
969 |
+
|
970 |
+
# MODIFIED LINE: Use Markdown heading (e.g., ###) for larger font and bolding
|
971 |
+
expander_label = f"### **{timestamp.strftime('%Y-%m-%d %H:%M')}** - {expander_icon} *{summary_title_prefix}*"
|
972 |
+
with st.expander(expander_label):
|
973 |
+
st.write(f"**Your Input:** {symptoms_text}")
|
974 |
+
st.write(f"**MediBot's Insight:** {insight_text}")
|
975 |
+
|
976 |
+
# PDF Download button
|
977 |
+
pdf_buffer = generate_pdf_report(history_data)
|
978 |
+
st.download_button(
|
979 |
+
label="Download History as PDF",
|
980 |
+
data=pdf_buffer,
|
981 |
+
file_name="MediBot_Health_Report.pdf",
|
982 |
+
mime="application/pdf"
|
983 |
+
)
|
984 |
else:
|
985 |
+
st.info("No history found for your account. Start interacting with MediBot!")
|
986 |
+
else:
|
987 |
+
st.warning("Please log in to view your health history.")
|
988 |
+
|
989 |
+
with main_tab_insights:
|
990 |
+
st.header("Your Health Insights")
|
991 |
+
user_id = st.session_state.get("user_id")
|
992 |
+
if user_id:
|
993 |
+
st.info("This section provides insights into the conditions identified in your past interactions.")
|
994 |
+
history_data = get_user_history(user_id)
|
995 |
+
if history_data:
|
996 |
+
# Filter data for the last 30 days
|
997 |
+
thirty_days_ago = datetime.now() - timedelta(days=30)
|
998 |
+
recent_history = [
|
999 |
+
entry for entry in history_data
|
1000 |
+
if entry[3] and entry[3] > thirty_days_ago # entry[3] is query_timestamp
|
1001 |
+
]
|
1002 |
+
if recent_history:
|
1003 |
+
all_conditions = []
|
1004 |
+
for entry in recent_history:
|
1005 |
+
# entry[2] is predicted_diseases string
|
1006 |
+
conditions_from_entry = parse_insight_text_for_conditions(entry[2])
|
1007 |
+
all_conditions.extend(conditions_from_entry)
|
1008 |
+
|
1009 |
+
if all_conditions:
|
1010 |
+
# Count occurrences of each condition
|
1011 |
+
condition_counts = pd.Series(all_conditions).value_counts().reset_index()
|
1012 |
+
condition_counts.columns = ['Condition', 'Count']
|
1013 |
+
st.subheader("Most Frequent Conditions in Last 30 Days:")
|
1014 |
+
# Display as a bar chart
|
1015 |
+
st.bar_chart(condition_counts, x='Condition', y='Count', use_container_width=True)
|
1016 |
+
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.")
|
1017 |
+
else:
|
1018 |
+
st.info("No specific conditions were identified in your recent history (last 30 days).")
|
1019 |
+
else:
|
1020 |
+
st.info("You have no health interactions in the last 30 days to generate insights.")
|
1021 |
+
else:
|
1022 |
+
st.info("No history found for your account. Start interacting with MediBot to see insights!")
|
1023 |
+
else:
|
1024 |
+
st.warning("Please log in to view your health insights.")
|
1025 |
+
|
1026 |
+
with main_tab_feedback:
|
1027 |
+
st.header("Share Your Feedback")
|
1028 |
+
st.write("We appreciate your feedback to improve MediBot.")
|
1029 |
+
with st.form("feedback_form", clear_on_submit=True):
|
1030 |
+
feedback_text = st.text_area("Your feedback:", height=150, key="feedback_text_area")
|
1031 |
+
rating = st.slider("Rate your experience (1-5 stars):", 1, 5, 3, key="feedback_rating")
|
1032 |
+
feedback_submit_button = st.form_submit_button("Submit Feedback")
|
1033 |
+
|
1034 |
+
if feedback_submit_button:
|
1035 |
+
if feedback_text:
|
1036 |
+
# In a real app, you would save this to a database
|
1037 |
+
st.session_state.feedback.append({"text": feedback_text, "rating": rating, "timestamp": datetime.now()})
|
1038 |
+
st.success("Thank you for your feedback! It has been submitted.")
|
1039 |
+
st.session_state.feedback_input = "" # Clear after submission
|
1040 |
+
else:
|
1041 |
+
st.warning("Please enter your feedback before submitting.")
|
1042 |
+
|
1043 |
+
with main_tab_about:
|
1044 |
+
st.header("About MediBot")
|
1045 |
+
st.write("""
|
1046 |
+
MediBot is a health assistant designed to provide general information based on your symptoms and answer health-related questions.
|
1047 |
+
It utilizes various AI models and medical datasets to offer insights.
|
1048 |
+
|
1049 |
+
**Technology Stack:**
|
1050 |
+
- **Streamlit**: For the interactive web application interface.
|
1051 |
+
- **Groq API**: Powers the conversational AI for health questions using high-performance language models (e.g., Llama-3), enabling rapid and relevant responses.
|
1052 |
+
- **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.
|
1053 |
+
- **Pandas**: For efficient data handling and processing of local CSV medical datasets (symptoms, descriptions, precautions).
|
1054 |
+
- **ReportLab**: For generating downloadable PDF reports of your personalized health history.
|
1055 |
+
- **Database**: PostgreSQL is used for efficient handling of user's data, interaction history and diagnosis record keeping.
|
1056 |
+
|
1057 |
+
|
1058 |
+
**Datasets Used:**
|
1059 |
+
- Symptom-to-Disease mapping, Descriptions, and Precautions sourced from:
|
1060 |
+
"Disease Symptom Prediction" dataset available on Kaggle.
|
1061 |
+
[https://www.kaggle.com/datasets/itachi9604/disease-symptom-description-dataset](https://www.kaggle.com/datasets/itachi9604/disease-symptom-description-dataset)
|
1062 |
+
*Credit to Itachi9604 for compiling this valuable dataset.*
|
1063 |
+
|
1064 |
+
**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.
|
1065 |
+
""")
|
1066 |
+
st.markdown("[Learn more about Streamlit](https://streamlit.io/)")
|
1067 |
+
st.markdown("[Learn more about Groq](https://groq.com/)")
|
1068 |
+
st.markdown("[Learn more about Hugging Face](https://huggingface.co/)")
|
1069 |
|
|
|
1070 |
st.markdown("---")
|
1071 |
+
st.markdown("For professional medical advice, always consult a qualified healthcare provider.")
|
database.py
CHANGED
@@ -5,6 +5,7 @@ from contextlib import contextmanager
|
|
5 |
from typing import List, Tuple, Optional
|
6 |
import bcrypt
|
7 |
import logging
|
|
|
8 |
|
9 |
# Set up logging
|
10 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
@@ -65,18 +66,18 @@ def get_disease_info(symptoms: List[str]) -> List[Tuple[str, str, List[str]]]:
|
|
65 |
|
66 |
# Check mappings in mb_disease_symptoms
|
67 |
placeholders = ','.join(['%s'] * len(symptoms))
|
68 |
-
query = """
|
69 |
SELECT d.disease_name, d.description,
|
70 |
ARRAY[COALESCE(d.precaution_1, ''), COALESCE(d.precaution_2, ''),
|
71 |
COALESCE(d.precaution_3, ''), COALESCE(d.precaution_4, '')] as precautions
|
72 |
FROM mb_diseases d
|
73 |
JOIN mb_disease_symptoms ds ON d.disease_id = ds.disease_id
|
74 |
JOIN mb_symptoms s ON ds.symptom_id = s.symptom_id
|
75 |
-
WHERE s.symptom_name IN (
|
76 |
GROUP BY d.disease_id, d.disease_name, d.description, d.precaution_1, d.precaution_2, d.precaution_3, d.precaution_4
|
77 |
ORDER BY COUNT(*) DESC
|
78 |
LIMIT 5
|
79 |
-
"""
|
80 |
cur.execute(query, symptoms)
|
81 |
results = cur.fetchall()
|
82 |
if not results:
|
@@ -102,7 +103,7 @@ def save_user_history(user_id: str, symptoms: str, predicted_diseases: str) -> N
|
|
102 |
logging.error(f"Error saving user history: {str(e)}")
|
103 |
raise Exception(f"Error saving user history: {str(e)}")
|
104 |
|
105 |
-
def get_user_history(user_id: str) -> List[Tuple[int, str, str,
|
106 |
"""Retrieve user history from database."""
|
107 |
try:
|
108 |
with get_db_connection() as conn:
|
|
|
5 |
from typing import List, Tuple, Optional
|
6 |
import bcrypt
|
7 |
import logging
|
8 |
+
from datetime import datetime # <--- ADD THIS IMPORT
|
9 |
|
10 |
# Set up logging
|
11 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
66 |
|
67 |
# Check mappings in mb_disease_symptoms
|
68 |
placeholders = ','.join(['%s'] * len(symptoms))
|
69 |
+
query = f"""
|
70 |
SELECT d.disease_name, d.description,
|
71 |
ARRAY[COALESCE(d.precaution_1, ''), COALESCE(d.precaution_2, ''),
|
72 |
COALESCE(d.precaution_3, ''), COALESCE(d.precaution_4, '')] as precautions
|
73 |
FROM mb_diseases d
|
74 |
JOIN mb_disease_symptoms ds ON d.disease_id = ds.disease_id
|
75 |
JOIN mb_symptoms s ON ds.symptom_id = s.symptom_id
|
76 |
+
WHERE s.symptom_name IN ({placeholders})
|
77 |
GROUP BY d.disease_id, d.disease_name, d.description, d.precaution_1, d.precaution_2, d.precaution_3, d.precaution_4
|
78 |
ORDER BY COUNT(*) DESC
|
79 |
LIMIT 5
|
80 |
+
"""
|
81 |
cur.execute(query, symptoms)
|
82 |
results = cur.fetchall()
|
83 |
if not results:
|
|
|
103 |
logging.error(f"Error saving user history: {str(e)}")
|
104 |
raise Exception(f"Error saving user history: {str(e)}")
|
105 |
|
106 |
+
def get_user_history(user_id: str) -> List[Tuple[int, str, str, datetime]]:
|
107 |
"""Retrieve user history from database."""
|
108 |
try:
|
109 |
with get_db_connection() as conn:
|
dataset.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
requirements.txt
CHANGED
@@ -4,4 +4,8 @@ psycopg2-binary
|
|
4 |
python-dotenv
|
5 |
bcrypt
|
6 |
fuzzywuzzy
|
7 |
-
python-Levenshtein
|
|
|
|
|
|
|
|
|
|
4 |
python-dotenv
|
5 |
bcrypt
|
6 |
fuzzywuzzy
|
7 |
+
python-Levenshtein
|
8 |
+
reportlab==4.2.2
|
9 |
+
httpx>=0.28.0
|
10 |
+
pydantic>=2.0.0
|
11 |
+
requests
|
symptom_Description.csv
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Disease,Description
|
2 |
+
Drug Reaction,An adverse drug reaction (ADR) is an injury caused by taking medication. ADRs may occur following a single dose or prolonged administration of a drug or result from the combination of two or more drugs.
|
3 |
+
Malaria,An infectious disease caused by protozoan parasites from the Plasmodium family that can be transmitted by the bite of the Anopheles mosquito or by a contaminated needle or transfusion. Falciparum malaria is the most deadly type.
|
4 |
+
Allergy,"An allergy is an immune system response to a foreign substance that's not typically harmful to your body.They can include certain foods, pollen, or pet dander. Your immune system's job is to keep you healthy by fighting harmful pathogens."
|
5 |
+
Hypothyroidism,"Hypothyroidism, also called underactive thyroid or low thyroid, is a disorder of the endocrine system in which the thyroid gland does not produce enough thyroid hormone."
|
6 |
+
Psoriasis,"Psoriasis is a common skin disorder that forms thick, red, bumpy patches covered with silvery scales. They can pop up anywhere, but most appear on the scalp, elbows, knees, and lower back. Psoriasis can't be passed from person to person. It does sometimes happen in members of the same family."
|
7 |
+
GERD,"Gastroesophageal reflux disease, or GERD, is a digestive disorder that affects the lower esophageal sphincter (LES), the ring of muscle between the esophagus and stomach. Many people, including pregnant women, suffer from heartburn or acid indigestion caused by GERD."
|
8 |
+
Chronic cholestasis,"Chronic cholestatic diseases, whether occurring in infancy, childhood or adulthood, are characterized by defective bile acid transport from the liver to the intestine, which is caused by primary damage to the biliary epithelium in most cases"
|
9 |
+
hepatitis A,Hepatitis A is a highly contagious liver infection caused by the hepatitis A virus. The virus is one of several types of hepatitis viruses that cause inflammation and affect your liver's ability to function.
|
10 |
+
Osteoarthristis,"Osteoarthritis is the most common form of arthritis, affecting millions of people worldwide. It occurs when the protective cartilage that cushions the ends of your bones wears down over time."
|
11 |
+
(vertigo) Paroymsal Positional Vertigo,Benign paroxysmal positional vertigo (BPPV) is one of the most common causes of vertigo — the sudden sensation that you're spinning or that the inside of your head is spinning. Benign paroxysmal positional vertigo causes brief episodes of mild to intense dizziness.
|
12 |
+
Hypoglycemia, Hypoglycemia is a condition in which your blood sugar (glucose) level is lower than normal. Glucose is your body's main energy source. Hypoglycemia is often related to diabetes treatment. But other drugs and a variety of conditions — many rare — can cause low blood sugar in people who don't have diabetes.
|
13 |
+
Acne,"Acne vulgaris is the formation of comedones, papules, pustules, nodules, and/or cysts as a result of obstruction and inflammation of pilosebaceous units (hair follicles and their accompanying sebaceous gland). Acne develops on the face and upper trunk. It most often affects adolescents."
|
14 |
+
Diabetes,"Diabetes is a disease that occurs when your blood glucose, also called blood sugar, is too high. Blood glucose is your main source of energy and comes from the food you eat. Insulin, a hormone made by the pancreas, helps glucose from food get into your cells to be used for energy."
|
15 |
+
Impetigo,"Impetigo (im-puh-TIE-go) is a common and highly contagious skin infection that mainly affects infants and children. Impetigo usually appears as red sores on the face, especially around a child's nose and mouth, and on hands and feet. The sores burst and develop honey-colored crusts."
|
16 |
+
Hypertension,"Hypertension (HTN or HT), also known as high blood pressure (HBP), is a long-term medical condition in which the blood pressure in the arteries is persistently elevated. High blood pressure typically does not cause symptoms."
|
17 |
+
Peptic ulcer diseae,"Peptic ulcer disease (PUD) is a break in the inner lining of the stomach, the first part of the small intestine, or sometimes the lower esophagus. An ulcer in the stomach is called a gastric ulcer, while one in the first part of the intestines is a duodenal ulcer."
|
18 |
+
Dimorphic hemorrhoids(piles),"Hemorrhoids, also spelled haemorrhoids, are vascular structures in the anal canal. In their ... Other names, Haemorrhoids, piles, hemorrhoidal disease ."
|
19 |
+
Common Cold,"The common cold is a viral infection of your nose and throat (upper respiratory tract). It's usually harmless, although it might not feel that way. Many types of viruses can cause a common cold."
|
20 |
+
Chicken pox,"Chickenpox is a highly contagious disease caused by the varicella-zoster virus (VZV). It can cause an itchy, blister-like rash. The rash first appears on the chest, back, and face, and then spreads over the entire body, causing between 250 and 500 itchy blisters."
|
21 |
+
Cervical spondylosis,"Cervical spondylosis is a general term for age-related wear and tear affecting the spinal disks in your neck. As the disks dehydrate and shrink, signs of osteoarthritis develop, including bony projections along the edges of bones (bone spurs)."
|
22 |
+
Hyperthyroidism,"Hyperthyroidism (overactive thyroid) occurs when your thyroid gland produces too much of the hormone thyroxine. Hyperthyroidism can accelerate your body's metabolism, causing unintentional weight loss and a rapid or irregular heartbeat."
|
23 |
+
Urinary tract infection,"Urinary tract infection: An infection of the kidney, ureter, bladder, or urethra. Abbreviated UTI. Not everyone with a UTI has symptoms, but common symptoms include a frequent urge to urinate and pain or burning when urinating."
|
24 |
+
Varicose veins,"A vein that has enlarged and twisted, often appearing as a bulging, blue blood vessel that is clearly visible through the skin. Varicose veins are most common in older adults, particularly women, and occur especially on the legs."
|
25 |
+
AIDS,"Acquired immunodeficiency syndrome (AIDS) is a chronic, potentially life-threatening condition caused by the human immunodeficiency virus (HIV). By damaging your immune system, HIV interferes with your body's ability to fight infection and disease."
|
26 |
+
Paralysis (brain hemorrhage),"Intracerebral hemorrhage (ICH) is when blood suddenly bursts into brain tissue, causing damage to your brain. Symptoms usually appear suddenly during ICH. They include headache, weakness, confusion, and paralysis, particularly on one side of your body."
|
27 |
+
Typhoid,"An acute illness characterized by fever caused by infection with the bacterium Salmonella typhi. Typhoid fever has an insidious onset, with fever, headache, constipation, malaise, chills, and muscle pain. Diarrhea is uncommon, and vomiting is not usually severe."
|
28 |
+
Hepatitis B,"Hepatitis B is an infection of your liver. It can cause scarring of the organ, liver failure, and cancer. It can be fatal if it isn't treated. It's spread when people come in contact with the blood, open sores, or body fluids of someone who has the hepatitis B virus."
|
29 |
+
Fungal infection,"In humans, fungal infections occur when an invading fungus takes over an area of the body and is too much for the immune system to handle. Fungi can live in the air, soil, water, and plants. There are also some fungi that live naturally in the human body. Like many microbes, there are helpful fungi and harmful fungi."
|
30 |
+
Hepatitis C,"Inflammation of the liver due to the hepatitis C virus (HCV), which is usually spread via blood transfusion (rare), hemodialysis, and needle sticks. The damage hepatitis C does to the liver can lead to cirrhosis and its complications as well as cancer."
|
31 |
+
Migraine,"A migraine can cause severe throbbing pain or a pulsing sensation, usually on one side of the head. It's often accompanied by nausea, vomiting, and extreme sensitivity to light and sound. Migraine attacks can last for hours to days, and the pain can be so severe that it interferes with your daily activities."
|
32 |
+
Bronchial Asthma,"Bronchial asthma is a medical condition which causes the airway path of the lungs to swell and narrow. Due to this swelling, the air path produces excess mucus making it hard to breathe, which results in coughing, short breath, and wheezing. The disease is chronic and interferes with daily working."
|
33 |
+
Alcoholic hepatitis,"Alcoholic hepatitis is a diseased, inflammatory condition of the liver caused by heavy alcohol consumption over an extended period of time. It's also aggravated by binge drinking and ongoing alcohol use. If you develop this condition, you must stop drinking alcohol"
|
34 |
+
Jaundice,"Yellow staining of the skin and sclerae (the whites of the eyes) by abnormally high blood levels of the bile pigment bilirubin. The yellowing extends to other tissues and body fluids. Jaundice was once called the ""morbus regius"" (the regal disease) in the belief that only the touch of a king could cure it"
|
35 |
+
Hepatitis E,A rare form of liver inflammation caused by infection with the hepatitis E virus (HEV). It is transmitted via food or drink handled by an infected person or through infected water supplies in areas where fecal matter may get into the water. Hepatitis E does not cause chronic liver disease.
|
36 |
+
Dengue,"an acute infectious disease caused by a flavivirus (species Dengue virus of the genus Flavivirus), transmitted by aedes mosquitoes, and characterized by headache, severe joint pain, and a rash. — called also breakbone fever, dengue fever."
|
37 |
+
Hepatitis D,"Hepatitis D, also known as the hepatitis delta virus, is an infection that causes the liver to become inflamed. This swelling can impair liver function and cause long-term liver problems, including liver scarring and cancer. The condition is caused by the hepatitis D virus (HDV)."
|
38 |
+
Heart attack,"The death of heart muscle due to the loss of blood supply. The loss of blood supply is usually caused by a complete blockage of a coronary artery, one of the arteries that supplies blood to the heart muscle."
|
39 |
+
Pneumonia,"Pneumonia is an infection in one or both lungs. Bacteria, viruses, and fungi cause it. The infection causes inflammation in the air sacs in your lungs, which are called alveoli. The alveoli fill with fluid or pus, making it difficult to breathe."
|
40 |
+
Arthritis,"Arthritis is the swelling and tenderness of one or more of your joints. The main symptoms of arthritis are joint pain and stiffness, which typically worsen with age. The most common types of arthritis are osteoarthritis and rheumatoid arthritis."
|
41 |
+
Gastroenteritis,"Gastroenteritis is an inflammation of the digestive tract, particularly the stomach, and large and small intestines. Viral and bacterial gastroenteritis are intestinal infections associated with symptoms of diarrhea , abdominal cramps, nausea , and vomiting ."
|
42 |
+
Tuberculosis,"Tuberculosis (TB) is an infectious disease usually caused by Mycobacterium tuberculosis (MTB) bacteria. Tuberculosis generally affects the lungs, but can also affect other parts of the body. Most infections show no symptoms, in which case it is known as latent tuberculosis."
|
symptom_precaution.csv
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Disease,Precaution_1,Precaution_2,Precaution_3,Precaution_4
|
2 |
+
Drug Reaction,stop irritation,consult nearest hospital,stop taking drug,follow up
|
3 |
+
Malaria,Consult nearest hospital,avoid oily food,avoid non veg food,keep mosquitos out
|
4 |
+
Allergy,apply calamine,cover area with bandage,,use ice to compress itching
|
5 |
+
Hypothyroidism,reduce stress,exercise,eat healthy,get proper sleep
|
6 |
+
Psoriasis,wash hands with warm soapy water,stop bleeding using pressure,consult doctor,salt baths
|
7 |
+
GERD,avoid fatty spicy food,avoid lying down after eating,maintain healthy weight,exercise
|
8 |
+
Chronic cholestasis,cold baths,anti itch medicine,consult doctor,eat healthy
|
9 |
+
hepatitis A,Consult nearest hospital,wash hands through,avoid fatty spicy food,medication
|
10 |
+
Osteoarthristis,acetaminophen,consult nearest hospital,follow up,salt baths
|
11 |
+
(vertigo) Paroymsal Positional Vertigo,lie down,avoid sudden change in body,avoid abrupt head movment,relax
|
12 |
+
Hypoglycemia,lie down on side,check in pulse,drink sugary drinks,consult doctor
|
13 |
+
Acne,bath twice,avoid fatty spicy food,drink plenty of water,avoid too many products
|
14 |
+
Diabetes ,have balanced diet,exercise,consult doctor,follow up
|
15 |
+
Impetigo,soak affected area in warm water,use antibiotics,remove scabs with wet compressed cloth,consult doctor
|
16 |
+
Hypertension ,meditation,salt baths,reduce stress,get proper sleep
|
17 |
+
Peptic ulcer diseae,avoid fatty spicy food,consume probiotic food,eliminate milk,limit alcohol
|
18 |
+
Dimorphic hemmorhoids(piles),avoid fatty spicy food,consume witch hazel,warm bath with epsom salt,consume alovera juice
|
19 |
+
Common Cold,drink vitamin c rich drinks,take vapour,avoid cold food,keep fever in check
|
20 |
+
Chicken pox,use neem in bathing ,consume neem leaves,take vaccine,avoid public places
|
21 |
+
Cervical spondylosis,use heating pad or cold pack,exercise,take otc pain reliver,consult doctor
|
22 |
+
Hyperthyroidism,eat healthy,massage,use lemon balm,take radioactive iodine treatment
|
23 |
+
Urinary tract infection,drink plenty of water,increase vitamin c intake,drink cranberry juice,take probiotics
|
24 |
+
Varicose veins,lie down flat and raise the leg high,use oinments,use vein compression,dont stand still for long
|
25 |
+
AIDS,avoid open cuts,wear ppe if possible,consult doctor,follow up
|
26 |
+
Paralysis (brain hemorrhage),massage,eat healthy,exercise,consult doctor
|
27 |
+
Typhoid,eat high calorie vegitables,antiboitic therapy,consult doctor,medication
|
28 |
+
Hepatitis B,consult nearest hospital,vaccination,eat healthy,medication
|
29 |
+
Fungal infection,bath twice,use detol or neem in bathing water,keep infected area dry,use clean cloths
|
30 |
+
Hepatitis C,Consult nearest hospital,vaccination,eat healthy,medication
|
31 |
+
Migraine,meditation,reduce stress,use poloroid glasses in sun,consult doctor
|
32 |
+
Bronchial Asthma,switch to loose cloothing,take deep breaths,get away from trigger,seek help
|
33 |
+
Alcoholic hepatitis,stop alcohol consumption,consult doctor,medication,follow up
|
34 |
+
Jaundice,drink plenty of water,consume milk thistle,eat fruits and high fiberous food,medication
|
35 |
+
Hepatitis E,stop alcohol consumption,rest,consult doctor,medication
|
36 |
+
Dengue,drink papaya leaf juice,avoid fatty spicy food,keep mosquitos away,keep hydrated
|
37 |
+
Hepatitis D,consult doctor,medication,eat healthy,follow up
|
38 |
+
Heart attack,call ambulance,chew or swallow asprin,keep calm,
|
39 |
+
Pneumonia,consult doctor,medication,rest,follow up
|
40 |
+
Arthritis,exercise,use hot and cold therapy,try acupuncture,massage
|
41 |
+
Gastroenteritis,stop eating solid food for while,try taking small sips of water,rest,ease back into eating
|
42 |
+
Tuberculosis,cover mouth,consult doctor,medication,rest
|
user_management.py
CHANGED
@@ -1,62 +1,105 @@
|
|
1 |
import streamlit as st
|
2 |
from datetime import datetime
|
3 |
-
from database import register_user, user_exists, check_user_credentials, save_user_history
|
4 |
|
5 |
-
|
6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
st.sidebar.header("User Management 🧑⚕️")
|
8 |
-
|
9 |
-
if
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
st.session_state.user_id = None
|
16 |
st.success("Logged out successfully!")
|
|
|
17 |
else:
|
18 |
-
# Toggle between sign-in and register
|
19 |
-
form_type = st.sidebar.selectbox("Choose Action", ["Sign In", "Register"], key="
|
20 |
-
|
21 |
if form_type == "Sign In":
|
22 |
-
with st.sidebar.form("
|
23 |
st.markdown("### Sign In")
|
24 |
-
login_user_id = st.text_input("User ID", key="
|
25 |
-
login_password = st.text_input("Password", type="password", key="
|
26 |
submit_login = st.form_submit_button("Sign In")
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
39 |
st.markdown("### Register")
|
40 |
-
reg_user_id = st.text_input("Choose a unique User ID", key="
|
41 |
-
full_name = st.text_input("Full Name", key="
|
42 |
-
dob = st.date_input("Date of Birth", min_value=datetime(1900, 1, 1), max_value=datetime.today(), key="
|
43 |
-
email = st.text_input("Email (optional)", key="
|
44 |
-
reg_password = st.text_input("Password", type="password", key="
|
45 |
submit_register = st.form_submit_button("Register")
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
elif user_exists(reg_user_id):
|
51 |
-
st.sidebar.error("User ID already taken. Please choose another.")
|
52 |
-
else:
|
53 |
-
if register_user(reg_user_id, full_name, dob, email or None, reg_password):
|
54 |
-
st.session_state.user_id = reg_user_id
|
55 |
-
st.sidebar.success("Registered successfully!")
|
56 |
else:
|
57 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
-
def save_history_if_logged_in(symptoms: str, predicted_diseases: str):
|
60 |
-
"""Save history if user is logged in."""
|
61 |
-
if st.session_state.get("user_id"):
|
62 |
-
save_user_history(st.session_state.user_id, symptoms, predicted_diseases)
|
|
|
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 |
|
|
|
|
|
|
|
|
utils.py
CHANGED
@@ -1,15 +1,13 @@
|
|
|
|
1 |
from fuzzywuzzy import fuzz
|
2 |
-
|
3 |
-
|
4 |
-
def extract_keyword(text: str) -> str:
|
5 |
-
"""Extract a meaningful keyword from symptoms, predicted diseases, or question text, using fuzzy matching with mb_symptoms."""
|
6 |
-
# Load symptoms from mb_symptoms table
|
7 |
-
try:
|
8 |
-
symptoms = get_symptoms()
|
9 |
-
except Exception as e:
|
10 |
-
symptoms = [] # Fallback to empty list if database query fails
|
11 |
-
print(f"Error loading symptoms for keyword extraction: {str(e)}")
|
12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
if text.startswith("Question: "):
|
14 |
# Remove "Question: " prefix and process question text
|
15 |
question = text[10:].strip()
|
@@ -17,31 +15,62 @@ def extract_keyword(text: str) -> str:
|
|
17 |
if not words:
|
18 |
return "Unknown"
|
19 |
|
20 |
-
# Common words to skip
|
21 |
-
common_words = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
|
23 |
-
# Fuzzy matching against
|
24 |
if symptoms:
|
25 |
best_match = None
|
26 |
highest_score = 0
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
if best_match:
|
37 |
return best_match.capitalize()
|
38 |
|
39 |
-
# Fallback: pick the first non-common word longer than
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
for word in words:
|
41 |
-
|
|
|
42 |
return word.capitalize()
|
|
|
43 |
return words[0].capitalize() if words else "Unknown"
|
44 |
else:
|
45 |
-
# For
|
46 |
-
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# utils.py
|
2 |
from fuzzywuzzy import fuzz
|
3 |
+
import re
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
+
def extract_keyword(text: str, symptoms: list = None) -> str:
|
6 |
+
"""
|
7 |
+
Extracts a primary keyword from a given text, prioritizing symptom matches if available,
|
8 |
+
otherwise, the first relevant word.
|
9 |
+
Handles both "Question: " prefixed strings and direct symptom strings.
|
10 |
+
"""
|
11 |
if text.startswith("Question: "):
|
12 |
# Remove "Question: " prefix and process question text
|
13 |
question = text[10:].strip()
|
|
|
15 |
if not words:
|
16 |
return "Unknown"
|
17 |
|
18 |
+
# Common words to skip - expanded list
|
19 |
+
common_words = {
|
20 |
+
'what', 'is', 'are', 'how', 'why', 'can', 'do', 'does', 'i', 'have', 'my', 'a', 'an', 'the',
|
21 |
+
'in', 'of', 'and', 'or', 'for', 'with', 'from', 'about', 'some', 'any', 'this', 'that',
|
22 |
+
'there', 'be', 'to', 'me', 'am', 'feel', 'feeling', 'experiencing', 'symptoms', 'issue',
|
23 |
+
'problem', 'cause', 'causes', 'tell', 'me', 'more', 'information', 'on', 'about', 'a',
|
24 |
+
'an', 'the', 'my', 'your', 'its', 'their', 'our', 'his', 'her', 'its', 'them', 'us', 'you',
|
25 |
+
'i', 'we', 'he', 'she', 'it', 'they', 'this', 'that', 'these', 'those', 'which', 'who',
|
26 |
+
'whom', 'whose', 'where', 'when', 'why', 'how', 'what', 'if', 'then', 'else', 'or', 'and',
|
27 |
+
'but', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against',
|
28 |
+
'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from',
|
29 |
+
'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once',
|
30 |
+
'here', 'there', 'when', 'where', 'why', 'all', 'any', 'both', 'each', 'few', 'more', 'most',
|
31 |
+
'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too',
|
32 |
+
'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now'
|
33 |
+
}
|
34 |
|
35 |
+
# Fuzzy matching against symptoms (if provided)
|
36 |
if symptoms:
|
37 |
best_match = None
|
38 |
highest_score = 0
|
39 |
+
# Prioritize multi-word symptoms if they match well
|
40 |
+
for symptom_phrase in sorted(symptoms, key=len, reverse=True):
|
41 |
+
for i in range(len(words)):
|
42 |
+
for j in range(i + 1, len(words) + 1):
|
43 |
+
phrase_from_question = " ".join(words[i:j]).lower()
|
44 |
+
score = fuzz.token_sort_ratio(phrase_from_question, symptom_phrase.lower())
|
45 |
+
if score > 85 and score > highest_score:
|
46 |
+
best_match = symptom_phrase
|
47 |
+
highest_score = score
|
48 |
if best_match:
|
49 |
return best_match.capitalize()
|
50 |
|
51 |
+
# Fallback: pick the first non-common word longer than 2 characters
|
52 |
+
for word in words:
|
53 |
+
word_lower = word.lower()
|
54 |
+
if word_lower not in common_words and len(word_lower) > 2:
|
55 |
+
if not re.match(r'^\d+$', word_lower) and not re.match(r'^\w$', word_lower):
|
56 |
+
return word.capitalize()
|
57 |
+
|
58 |
+
# Last resort: if no good keyword, take the first non-common word
|
59 |
for word in words:
|
60 |
+
word_lower = word.lower()
|
61 |
+
if word_lower not in common_words:
|
62 |
return word.capitalize()
|
63 |
+
|
64 |
return words[0].capitalize() if words else "Unknown"
|
65 |
else:
|
66 |
+
# For symptom checker inputs (comma-separated symptoms)
|
67 |
+
# The history stores the raw selected symptoms here, so we just return the first one or the full list if short
|
68 |
+
symptom_list_str = text.strip()
|
69 |
+
if symptom_list_str:
|
70 |
+
# If it's a short list of symptoms, return the whole thing
|
71 |
+
if len(symptom_list_str.split(',')) <= 3:
|
72 |
+
return symptom_list_str.capitalize()
|
73 |
+
else: # Otherwise, just the first symptom
|
74 |
+
first_symptom = symptom_list_str.split(',')[0].strip()
|
75 |
+
return first_symptom.capitalize()
|
76 |
+
return "Unknown"
|