Spaces:
Sleeping
Sleeping
Upload 19 files
Browse files- .gitattributes +15 -0
- chatbot.py +1240 -0
- data/Antibiotic Guidelines (2020)_0.pdf +3 -0
- data/EAU-Guidelines-on-Urological-Infections-2024.pdf +3 -0
- data/General Fever + Viral URI.pdf +3 -0
- data/Gyan-Vahini-11-Safe-Drugs-Use-In-Pregnancy-Lactation-Nov-25.pdf +3 -0
- data/Surgical Infection Society 2020 updated guidelines on the managem.pdf +3 -0
- data/The_WHO_AWaRe_Access_Watch_Reserve_antib.pdf +3 -0
- data/Treatment_Guidelines_2019_Final.pdf +3 -0
- data/WHO-Bacterial-Priority-2024.pdf +3 -0
- data/WHO-MHP-HPS-EML-2023.02-eng.pdf +3 -0
- data/dengue.pdf +3 -0
- data/executive_summary.pdf +3 -0
- data/influenza.pdf +3 -0
- data/malaria.pdf +3 -0
- data/practice-guidelines-for-the-diagnosis-and-management-of-skin-and-soft-tissue-infections-2014-update-by-the-infectious-diseases-society-of-america.pdf +3 -0
- data/typhoid.pdf +3 -0
- requirements.txt +0 -0
- templates/chatbot.html +0 -0
- templates/history.html +978 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,18 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
data/Antibiotic[[:space:]]Guidelines[[:space:]](2020)_0.pdf filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
data/dengue.pdf filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
data/EAU-Guidelines-on-Urological-Infections-2024.pdf filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
data/executive_summary.pdf filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
data/General[[:space:]]Fever[[:space:]]+[[:space:]]Viral[[:space:]]URI.pdf filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
data/Gyan-Vahini-11-Safe-Drugs-Use-In-Pregnancy-Lactation-Nov-25.pdf filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
data/influenza.pdf filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
data/malaria.pdf filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
data/practice-guidelines-for-the-diagnosis-and-management-of-skin-and-soft-tissue-infections-2014-update-by-the-infectious-diseases-society-of-america.pdf filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
data/Surgical[[:space:]]Infection[[:space:]]Society[[:space:]]2020[[:space:]]updated[[:space:]]guidelines[[:space:]]on[[:space:]]the[[:space:]]managem.pdf filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
data/The_WHO_AWaRe_Access_Watch_Reserve_antib.pdf filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
data/Treatment_Guidelines_2019_Final.pdf filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
data/typhoid.pdf filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
data/WHO-Bacterial-Priority-2024.pdf filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
data/WHO-MHP-HPS-EML-2023.02-eng.pdf filter=lfs diff=lfs merge=lfs -text
|
chatbot.py
ADDED
|
@@ -0,0 +1,1240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import sqlite3
|
| 4 |
+
import requests
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
from flask import Flask, request, jsonify, render_template, g
|
| 7 |
+
from flask_cors import CORS
|
| 8 |
+
from threading import Lock
|
| 9 |
+
|
| 10 |
+
# OpenMed NER β symptom entity extraction
|
| 11 |
+
try:
|
| 12 |
+
from transformers import pipeline
|
| 13 |
+
openmed_ner = pipeline(
|
| 14 |
+
"token-classification",
|
| 15 |
+
model="OpenMed/OpenMed-NER-DiseaseDetect-BioMed-335M",
|
| 16 |
+
aggregation_strategy="simple"
|
| 17 |
+
)
|
| 18 |
+
OPENMED_AVAILABLE = True
|
| 19 |
+
print("β
OpenMed NER loaded")
|
| 20 |
+
except Exception as e:
|
| 21 |
+
openmed_ner = None
|
| 22 |
+
OPENMED_AVAILABLE = False
|
| 23 |
+
print(f"β οΈ OpenMed NER not available: {e}")
|
| 24 |
+
|
| 25 |
+
# Load .env file
|
| 26 |
+
try:
|
| 27 |
+
from dotenv import load_dotenv
|
| 28 |
+
load_dotenv(dotenv_path=".env") # β yeh ek word change karo
|
| 29 |
+
except ImportError:
|
| 30 |
+
pass
|
| 31 |
+
|
| 32 |
+
from pypdf import PdfReader
|
| 33 |
+
from langchain_community.vectorstores import FAISS
|
| 34 |
+
from langchain_community.embeddings import HuggingFaceEmbeddings
|
| 35 |
+
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 36 |
+
from langchain_core.documents import Document
|
| 37 |
+
|
| 38 |
+
from gtts import gTTS
|
| 39 |
+
import io
|
| 40 |
+
import uuid
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
app = Flask(__name__)
|
| 44 |
+
CORS(app)
|
| 45 |
+
rag_lock = Lock()
|
| 46 |
+
VECTORSTORE = None
|
| 47 |
+
DATABASE = "safecure.db"
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
# ==============================
|
| 51 |
+
# DATABASE SETUP
|
| 52 |
+
# ==============================
|
| 53 |
+
def get_db():
|
| 54 |
+
db = getattr(g, '_database', None)
|
| 55 |
+
if db is None:
|
| 56 |
+
db = g._database = sqlite3.connect(DATABASE)
|
| 57 |
+
db.row_factory = sqlite3.Row
|
| 58 |
+
return db
|
| 59 |
+
|
| 60 |
+
@app.teardown_appcontext
|
| 61 |
+
def close_connection(exception):
|
| 62 |
+
db = getattr(g, '_database', None)
|
| 63 |
+
if db is not None:
|
| 64 |
+
db.close()
|
| 65 |
+
|
| 66 |
+
def init_db():
|
| 67 |
+
with app.app_context():
|
| 68 |
+
db = sqlite3.connect(DATABASE)
|
| 69 |
+
db.execute('''
|
| 70 |
+
CREATE TABLE IF NOT EXISTS patients (
|
| 71 |
+
id TEXT PRIMARY KEY,
|
| 72 |
+
timestamp TEXT NOT NULL,
|
| 73 |
+
condition TEXT,
|
| 74 |
+
allergies TEXT,
|
| 75 |
+
medications TEXT,
|
| 76 |
+
age TEXT,
|
| 77 |
+
assessment TEXT,
|
| 78 |
+
antibiotic_necessity TEXT,
|
| 79 |
+
first_line TEXT,
|
| 80 |
+
second_line TEXT,
|
| 81 |
+
contraindications TEXT,
|
| 82 |
+
recommended_tests TEXT,
|
| 83 |
+
additional_info_needed TEXT,
|
| 84 |
+
raw_response TEXT
|
| 85 |
+
)
|
| 86 |
+
''')
|
| 87 |
+
db.commit()
|
| 88 |
+
db.close()
|
| 89 |
+
|
| 90 |
+
def save_to_db(patient_id, data, parsed):
|
| 91 |
+
db = get_db()
|
| 92 |
+
# Migrate old schema if needed
|
| 93 |
+
try:
|
| 94 |
+
db.execute("SELECT recommended_tests FROM patients LIMIT 1")
|
| 95 |
+
except Exception:
|
| 96 |
+
for col in ["age TEXT", "recommended_tests TEXT", "additional_info_needed TEXT"]:
|
| 97 |
+
try:
|
| 98 |
+
db.execute(f"ALTER TABLE patients ADD COLUMN {col}")
|
| 99 |
+
except Exception:
|
| 100 |
+
pass
|
| 101 |
+
db.commit()
|
| 102 |
+
|
| 103 |
+
db.execute('''
|
| 104 |
+
INSERT INTO patients
|
| 105 |
+
(id, timestamp, condition, allergies, medications, age, assessment,
|
| 106 |
+
antibiotic_necessity, first_line, second_line, contraindications,
|
| 107 |
+
recommended_tests, additional_info_needed, raw_response)
|
| 108 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 109 |
+
''', (
|
| 110 |
+
patient_id,
|
| 111 |
+
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 112 |
+
data.get('condition', ''),
|
| 113 |
+
data.get('allergies', ''),
|
| 114 |
+
data.get('medications', ''),
|
| 115 |
+
data.get('age', ''),
|
| 116 |
+
parsed.get('assessment', ''),
|
| 117 |
+
parsed.get('antibiotic_necessity', ''),
|
| 118 |
+
parsed.get('first_line', ''),
|
| 119 |
+
parsed.get('second_line', ''),
|
| 120 |
+
parsed.get('contraindications', ''),
|
| 121 |
+
parsed.get('recommended_tests', ''),
|
| 122 |
+
parsed.get('additional_info_needed', ''),
|
| 123 |
+
parsed.get('raw', '')
|
| 124 |
+
))
|
| 125 |
+
db.commit()
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
# ==============================
|
| 129 |
+
# PARSE RESPONSE
|
| 130 |
+
# ==============================
|
| 131 |
+
def parse_response(response_text):
|
| 132 |
+
response_text = re.sub(r'\*+', '', response_text)
|
| 133 |
+
response_text = re.sub(r'#+\s*', '', response_text)
|
| 134 |
+
|
| 135 |
+
sections = {
|
| 136 |
+
'assessment': '',
|
| 137 |
+
'antibiotic_necessity': '',
|
| 138 |
+
'first_line': '',
|
| 139 |
+
'second_line': '',
|
| 140 |
+
'contraindications': '',
|
| 141 |
+
'recommended_tests': '',
|
| 142 |
+
'additional_info_needed': '',
|
| 143 |
+
'raw': response_text
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
# Multiple header variations mapped to same section key
|
| 147 |
+
section_map = [
|
| 148 |
+
(["clinical assessment:", "assessment:"], 'assessment'),
|
| 149 |
+
(["antibiotic necessity:", "antibiotic:"], 'antibiotic_necessity'),
|
| 150 |
+
(["first-line therapy:", "first-line:", "first line therapy:", "first line:"], 'first_line'),
|
| 151 |
+
(["second-line alternatives:", "second-line:", "second line alternatives:", "second line:"], 'second_line'),
|
| 152 |
+
(["contraindications & precautions:", "contraindications:", "precautions:"], 'contraindications'),
|
| 153 |
+
(["recommended tests:", "tests:", "investigations:"], 'recommended_tests'),
|
| 154 |
+
(["additional information needed:", "additional information:", "additional info:"], 'additional_info_needed'),
|
| 155 |
+
]
|
| 156 |
+
|
| 157 |
+
lines = response_text.split('\n')
|
| 158 |
+
current = None
|
| 159 |
+
buffer = []
|
| 160 |
+
|
| 161 |
+
def flush(key):
|
| 162 |
+
if key and buffer:
|
| 163 |
+
sections[key] = ' | '.join(buffer).strip()
|
| 164 |
+
buffer.clear()
|
| 165 |
+
|
| 166 |
+
for line in lines:
|
| 167 |
+
line = line.strip()
|
| 168 |
+
if not line:
|
| 169 |
+
continue
|
| 170 |
+
|
| 171 |
+
matched = False
|
| 172 |
+
line_lower = line.lower()
|
| 173 |
+
for headers, key in section_map:
|
| 174 |
+
for header in headers:
|
| 175 |
+
if line_lower.startswith(header):
|
| 176 |
+
flush(current)
|
| 177 |
+
current = key
|
| 178 |
+
val = line[len(header):].strip()
|
| 179 |
+
if val:
|
| 180 |
+
buffer.append(val)
|
| 181 |
+
matched = True
|
| 182 |
+
break
|
| 183 |
+
if matched:
|
| 184 |
+
break
|
| 185 |
+
|
| 186 |
+
if not matched and current:
|
| 187 |
+
item = line.lstrip('-β’0123456789.').strip()
|
| 188 |
+
if item:
|
| 189 |
+
buffer.append(item)
|
| 190 |
+
|
| 191 |
+
flush(current)
|
| 192 |
+
return sections
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
# ==============================
|
| 196 |
+
# LOAD PDFs
|
| 197 |
+
# ==============================
|
| 198 |
+
def load_pdfs(folder="data"):
|
| 199 |
+
docs = []
|
| 200 |
+
if not os.path.exists(folder):
|
| 201 |
+
os.makedirs(folder)
|
| 202 |
+
return docs
|
| 203 |
+
for file in os.listdir(folder):
|
| 204 |
+
if file.endswith(".pdf"):
|
| 205 |
+
try:
|
| 206 |
+
reader = PdfReader(os.path.join(folder, file))
|
| 207 |
+
for page in reader.pages:
|
| 208 |
+
text = page.extract_text()
|
| 209 |
+
if text:
|
| 210 |
+
docs.append(Document(page_content=text))
|
| 211 |
+
except Exception as e:
|
| 212 |
+
print(f"Error loading {file}: {e}")
|
| 213 |
+
return docs
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
# ==============================
|
| 217 |
+
# INIT RAG
|
| 218 |
+
# ==============================
|
| 219 |
+
def initialize_rag():
|
| 220 |
+
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
| 221 |
+
if os.path.exists("faiss_index"):
|
| 222 |
+
return FAISS.load_local("faiss_index", embeddings, allow_dangerous_deserialization=True)
|
| 223 |
+
docs = load_pdfs()
|
| 224 |
+
if not docs:
|
| 225 |
+
return FAISS.from_texts(["No clinical guidelines loaded."], embeddings)
|
| 226 |
+
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
|
| 227 |
+
chunks = splitter.split_documents(docs)
|
| 228 |
+
vs = FAISS.from_documents(chunks, embeddings)
|
| 229 |
+
vs.save_local("faiss_index")
|
| 230 |
+
return vs
|
| 231 |
+
|
| 232 |
+
def get_vectorstore():
|
| 233 |
+
global VECTORSTORE
|
| 234 |
+
if VECTORSTORE is None:
|
| 235 |
+
with rag_lock:
|
| 236 |
+
if VECTORSTORE is None:
|
| 237 |
+
VECTORSTORE = initialize_rag()
|
| 238 |
+
return VECTORSTORE
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
# ==============================
|
| 242 |
+
# LLM CALL
|
| 243 |
+
# ==============================
|
| 244 |
+
def call_llm(llm_prompt):
|
| 245 |
+
import os
|
| 246 |
+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 247 |
+
try:
|
| 248 |
+
res = requests.post(
|
| 249 |
+
"https://api.groq.com/openai/v1/chat/completions",
|
| 250 |
+
headers={"Authorization": f"Bearer {GROQ_API_KEY}", "Content-Type": "application/json"},
|
| 251 |
+
json={
|
| 252 |
+
"model": "llama-3.1-8b-instant",
|
| 253 |
+
"messages": [
|
| 254 |
+
{
|
| 255 |
+
"role": "system",
|
| 256 |
+
"content": (
|
| 257 |
+
"You are a senior physician and clinical decision support system. "
|
| 258 |
+
"Think like a board-certified doctor: diagnose precisely, treat conservatively, escalate when needed. "
|
| 259 |
+
"STRICT OUTPUT RULES: "
|
| 260 |
+
"Each section = maximum 2 lines. Do NOT repeat symptoms in assessment. "
|
| 261 |
+
"No full sentences β use structured labels only. No preamble. No conclusion. No filler text. "
|
| 262 |
+
"Do NOT use **, ##, or bullet dashes. Plain text only. "
|
| 263 |
+
"Antibiotics ONLY when bacterial infection is clearly and strongly indicated. "
|
| 264 |
+
"Patient safety is absolute β never suggest unsafe, contraindicated, or unnecessary drugs."
|
| 265 |
+
)
|
| 266 |
+
},
|
| 267 |
+
{"role": "user", "content": llm_prompt}
|
| 268 |
+
],
|
| 269 |
+
"max_tokens": 600,
|
| 270 |
+
"temperature": 0.15
|
| 271 |
+
},
|
| 272 |
+
timeout=45
|
| 273 |
+
)
|
| 274 |
+
if res.status_code == 200:
|
| 275 |
+
return res.json()["choices"][0]["message"]["content"]
|
| 276 |
+
else:
|
| 277 |
+
return f"ERROR: {res.text[:200]}"
|
| 278 |
+
except Exception as e:
|
| 279 |
+
print(f"β GROQ EXCEPTION: {str(e)}")
|
| 280 |
+
return f"ERROR: {str(e)}"
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
# ==============================
|
| 284 |
+
# SAFETY FILTER
|
| 285 |
+
# ==============================
|
| 286 |
+
def safety_filter(response):
|
| 287 |
+
# Remove any dosage numbers that sneak through
|
| 288 |
+
response = re.sub(r'\b\d+\s?(mg|g|ml|mcg|IU|kg)\b', '', response, flags=re.IGNORECASE)
|
| 289 |
+
response = re.sub(r'\b\d+\s?times?\s?(daily|a day)\b', '', response, flags=re.IGNORECASE)
|
| 290 |
+
response = re.sub(r'every\s+\d+\s+hours?', '', response, flags=re.IGNORECASE)
|
| 291 |
+
response = re.sub(r'for\s+\d+\s+days?', '', response, flags=re.IGNORECASE)
|
| 292 |
+
response = re.sub(r'once\s+daily|twice\s+daily|three\s+times\s+daily', '', response, flags=re.IGNORECASE)
|
| 293 |
+
# Clean up extra whitespace left behind
|
| 294 |
+
response = re.sub(r' +', ' ', response)
|
| 295 |
+
response = re.sub(r' ,', ',', response)
|
| 296 |
+
response = re.sub(r' \)', ')', response)
|
| 297 |
+
# Pregnancy warnings
|
| 298 |
+
if "pregnan" in response.lower():
|
| 299 |
+
response = re.sub(
|
| 300 |
+
r"\b(tetracycline|doxycycline|ciprofloxacin|levofloxacin|ibuprofen|naproxen|aspirin|trimethoprim|methotrexate|warfarin)\b",
|
| 301 |
+
lambda m: f"{m.group()} [AVOID IN PREGNANCY]",
|
| 302 |
+
response, flags=re.IGNORECASE
|
| 303 |
+
)
|
| 304 |
+
return response
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
# ==============================
|
| 308 |
+
# RESPONSE VALIDATION
|
| 309 |
+
# ==============================
|
| 310 |
+
def validate_response(response):
|
| 311 |
+
if not response or response.startswith("ERROR"):
|
| 312 |
+
return False
|
| 313 |
+
cleaned = re.sub(r'\*+', '', response).lower()
|
| 314 |
+
|
| 315 |
+
# Each list = acceptable variations of same section header
|
| 316 |
+
required_sections = [
|
| 317 |
+
["clinical assessment", "assessment:", "diagnosis:"],
|
| 318 |
+
["antibiotic necessity", "antibiotic:", "antibiotics:"],
|
| 319 |
+
["first-line therapy", "first-line", "first line", "first_line"],
|
| 320 |
+
["second-line", "second line", "second_line", "alternatives"],
|
| 321 |
+
["contraindication", "precaution", "avoid:"],
|
| 322 |
+
["recommended tests", "tests:", "investigations"],
|
| 323 |
+
["additional information", "additional info", "info needed"],
|
| 324 |
+
]
|
| 325 |
+
|
| 326 |
+
matched = 0
|
| 327 |
+
for section_variants in required_sections:
|
| 328 |
+
if any(variant in cleaned for variant in section_variants):
|
| 329 |
+
matched += 1
|
| 330 |
+
|
| 331 |
+
# Pass if at least 4 out of 7 sections found (lenient β avoids false fallback)
|
| 332 |
+
return matched >= 4
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
# ==============================
|
| 336 |
+
# RULE ENGINE
|
| 337 |
+
# ==============================
|
| 338 |
+
SYMPTOM_RULES = [
|
| 339 |
+
{
|
| 340 |
+
"name": "Malaria",
|
| 341 |
+
"keywords": ["chills", "rigors", "cyclical fever", "sweating", "malaria", "shivering"],
|
| 342 |
+
"required_any": ["fever", "chills", "sweating", "rigors"],
|
| 343 |
+
"antibiotic": "NO β antimalarial needed, not antibiotic",
|
| 344 |
+
"first_line": ["Artemether-Lumefantrine (Falciparum Malaria)", "Paracetamol (Fever)", "ORS (Hydration)"],
|
| 345 |
+
"second_line": ["IV Artesunate (Severe Malaria β hospital only)", "Chloroquine (Vivax Malaria)"],
|
| 346 |
+
"tests": ["Malaria RDT β rapid confirmation", "Blood smear β species identification", "CBC β severity check"],
|
| 347 |
+
"avoid": ["Aspirin β bleeding risk", "Ibuprofen β avoid in malaria", "Antibiotics β not indicated"],
|
| 348 |
+
},
|
| 349 |
+
{
|
| 350 |
+
"name": "Dengue Fever",
|
| 351 |
+
"keywords": ["dengue", "bone pain", "eye pain", "retro-orbital", "rash", "platelet", "breakbone"],
|
| 352 |
+
"required_any": ["fever", "rash", "bone pain", "joint pain", "eye pain"],
|
| 353 |
+
"antibiotic": "NO β viral disease, antibiotics strictly contraindicated",
|
| 354 |
+
"first_line": ["Paracetamol (Fever and Pain)", "ORS (Hydration)", "Rest"],
|
| 355 |
+
"second_line": ["IV Fluids (if severe dengue β hospital)"],
|
| 356 |
+
"tests": ["NS1 Antigen β early dengue confirmation", "CBC with Platelet β thrombocytopenia check", "Dengue IgM/IgG β serology"],
|
| 357 |
+
"avoid": ["Ibuprofen β severe bleeding risk", "Aspirin β bleeding risk", "Diclofenac β avoid", "Antibiotics β not indicated"],
|
| 358 |
+
},
|
| 359 |
+
{
|
| 360 |
+
"name": "Typhoid Fever",
|
| 361 |
+
"keywords": ["typhoid", "abdominal pain", "constipation", "rose spots", "enteric"],
|
| 362 |
+
"required_any": ["fever", "abdominal pain", "nausea", "vomiting"],
|
| 363 |
+
"antibiotic": "YES β bacterial infection, antibiotic required",
|
| 364 |
+
"first_line": ["Azithromycin (Typhoid β safest oral)", "Paracetamol (Fever)"],
|
| 365 |
+
"second_line": ["Ceftriaxone (Severe Typhoid β IV)", "Ciprofloxacin (if sensitive β avoid in pregnancy/children)"],
|
| 366 |
+
"tests": ["Widal Test β typhoid serology", "Blood Culture β gold standard", "CBC β infection screen"],
|
| 367 |
+
"avoid": ["NSAIDs β GI bleed risk in typhoid", "Ciprofloxacin β avoid in pregnancy and children"],
|
| 368 |
+
},
|
| 369 |
+
{
|
| 370 |
+
"name": "Urinary Tract Infection (UTI)",
|
| 371 |
+
"keywords": ["burning urination", "frequent urination", "dysuria", "uti", "urinary", "cloudy urine", "pelvic pain"],
|
| 372 |
+
"required_any": ["burning", "urination", "dysuria", "pelvic pain", "urinary"],
|
| 373 |
+
"antibiotic": "YES β bacterial infection confirmed",
|
| 374 |
+
"first_line": ["Nitrofurantoin (UTI β safest first line)", "Paracetamol (Pain relief)"],
|
| 375 |
+
"second_line": ["Cefixime (if resistance suspected)", "Co-amoxiclav (complicated UTI)"],
|
| 376 |
+
"tests": ["Urine Routine/Microscopy β UTI confirmation", "Urine Culture β sensitivity testing"],
|
| 377 |
+
"avoid": ["Trimethoprim β avoid in pregnancy and elderly", "Fluoroquinolones β avoid in pregnancy"],
|
| 378 |
+
},
|
| 379 |
+
{
|
| 380 |
+
"name": "Pyelonephritis (Upper UTI)",
|
| 381 |
+
"keywords": ["flank pain", "loin pain", "back pain", "rigors", "vomiting", "high fever"],
|
| 382 |
+
"required_any": ["burning urination", "dysuria", "urinary", "frequent urination"],
|
| 383 |
+
"antibiotic": "YES β upper UTI requires systemic antibiotic",
|
| 384 |
+
"first_line": ["Co-amoxiclav (Pyelonephritis β oral)", "Paracetamol (Fever)"],
|
| 385 |
+
"second_line": ["Ceftriaxone IV (Severe β hospital only)", "Ciprofloxacin (if sensitivities allow β avoid in pregnancy)"],
|
| 386 |
+
"tests": ["Urine Culture & Sensitivity β mandatory", "CBC β severity assessment", "Renal Function Tests β kidney involvement", "Ultrasound KUB β structural assessment"],
|
| 387 |
+
"avoid": ["Nitrofurantoin β NOT effective for upper UTI", "Trimethoprim β resistance risk"],
|
| 388 |
+
},
|
| 389 |
+
{
|
| 390 |
+
"name": "Community Acquired Pneumonia",
|
| 391 |
+
"keywords": ["pneumonia", "productive cough", "chest pain", "breathlessness", "sputum"],
|
| 392 |
+
"required_any": ["cough", "chest pain", "breathlessness", "fever", "sputum"],
|
| 393 |
+
"antibiotic": "YES β bacterial pneumonia likely",
|
| 394 |
+
"first_line": ["Amoxicillin (Pneumonia β first line)", "Paracetamol (Fever)", "ORS (Hydration)"],
|
| 395 |
+
"second_line": ["Azithromycin (Penicillin allergy or atypical)", "Amoxicillin-Clavulanate (Severe)"],
|
| 396 |
+
"tests": ["Chest X-Ray β consolidation confirmation", "CBC β infection severity", "Sputum Culture β pathogen identification"],
|
| 397 |
+
"avoid": ["NSAIDs β avoid if bleeding risk"],
|
| 398 |
+
},
|
| 399 |
+
{
|
| 400 |
+
"name": "Influenza / Viral Fever",
|
| 401 |
+
"keywords": ["flu", "influenza", "myalgia", "body ache", "fatigue", "runny nose", "sore throat", "viral"],
|
| 402 |
+
"required_any": ["fever", "body ache", "fatigue", "cough", "sore throat"],
|
| 403 |
+
"antibiotic": "NO β viral infection, antibiotics not indicated",
|
| 404 |
+
"first_line": ["Paracetamol (Fever and Body Ache)", "ORS (Hydration)", "Rest", "Vitamin C (Immune support)"],
|
| 405 |
+
"second_line": ["Oseltamivir (Within 48 hours of onset only)"],
|
| 406 |
+
"tests": ["CBC β rule out secondary bacterial infection", "Rapid Influenza Test β if available"],
|
| 407 |
+
"avoid": ["Aspirin β Reye's syndrome risk", "Antibiotics β not indicated"],
|
| 408 |
+
},
|
| 409 |
+
{
|
| 410 |
+
"name": "Cellulitis",
|
| 411 |
+
"keywords": ["cellulitis", "skin redness", "skin warmth", "skin infection", "erythema"],
|
| 412 |
+
"required_any": ["redness", "swelling", "warmth", "skin"],
|
| 413 |
+
"antibiotic": "YES β bacterial skin infection",
|
| 414 |
+
"first_line": ["Flucloxacillin (Cellulitis β first line)", "Paracetamol (Pain and Fever)"],
|
| 415 |
+
"second_line": ["Clindamycin (Penicillin allergy)", "Co-amoxiclav (Polymicrobial)"],
|
| 416 |
+
"tests": ["CBC β infection severity", "CRP β inflammatory marker"],
|
| 417 |
+
"avoid": ["Penicillin β if allergy reported"],
|
| 418 |
+
},
|
| 419 |
+
{
|
| 420 |
+
"name": "Otitis Media",
|
| 421 |
+
"keywords": ["ear pain", "earache", "ear discharge", "otitis", "ear infection"],
|
| 422 |
+
"required_any": ["ear pain", "earache", "ear"],
|
| 423 |
+
"antibiotic": "CONDITIONAL β observe 48-72hrs first, antibiotics if not improving",
|
| 424 |
+
"first_line": ["Paracetamol (Pain relief β watchful waiting 48-72hrs)", "Warm compress"],
|
| 425 |
+
"second_line": ["Amoxicillin (If not improving after 72hrs)"],
|
| 426 |
+
"tests": ["Otoscopy β tympanic membrane assessment"],
|
| 427 |
+
"avoid": ["Aspirin β children", "Ciprofloxacin ear drops β only for perforated drum"],
|
| 428 |
+
},
|
| 429 |
+
{
|
| 430 |
+
"name": "Gastroenteritis / Food Poisoning",
|
| 431 |
+
"keywords": ["diarrhea", "vomiting", "abdominal cramps", "loose stools", "food poisoning", "outside food", "gastroenteritis", "nausea"],
|
| 432 |
+
"required_any": ["vomiting", "diarrhea", "abdominal cramps", "loose stools", "nausea"],
|
| 433 |
+
"antibiotic": "NO β usually viral or self-limiting bacterial, antibiotics rarely needed",
|
| 434 |
+
"first_line": ["ORS (Rehydration β priority)", "Paracetamol (Fever)", "Rest", "Zinc (if child)"],
|
| 435 |
+
"second_line": ["Not applicable β supportive care sufficient in most cases"],
|
| 436 |
+
"tests": ["Stool Routine β only if bloody diarrhea or fever > 3 days", "Stool Culture β if Salmonella/Shigella suspected"],
|
| 437 |
+
"avoid": ["Antibiotics β not indicated for routine gastroenteritis", "Ibuprofen β GI irritation risk"],
|
| 438 |
+
},
|
| 439 |
+
{
|
| 440 |
+
"name": "Acute Sinusitis (Viral)",
|
| 441 |
+
"keywords": ["facial pain", "nasal congestion", "sinus", "sinusitis", "pressure around eyes", "blocked nose"],
|
| 442 |
+
"required_any": ["facial pain", "nasal congestion", "headache", "blocked nose", "sinus"],
|
| 443 |
+
"antibiotic": "NO β >90% viral, antibiotics not indicated in first 10 days",
|
| 444 |
+
"first_line": ["Paracetamol (Pain and Fever)", "Saline nasal rinse (Congestion)", "Steam inhalation", "Rest"],
|
| 445 |
+
"second_line": ["Amoxicillin (Only if symptoms worsen after 10 days or severe bacterial signs)"],
|
| 446 |
+
"tests": ["None required β clinical diagnosis sufficient for mild sinusitis"],
|
| 447 |
+
"avoid": ["Antibiotics in first 10 days β viral cause likely", "Decongestant sprays > 3 days β rebound congestion"],
|
| 448 |
+
},
|
| 449 |
+
{
|
| 450 |
+
"name": "Tonsillitis / Pharyngitis",
|
| 451 |
+
"keywords": ["sore throat", "throat pain", "difficulty swallowing", "tonsil", "pharyngitis", "strep"],
|
| 452 |
+
"required_any": ["sore throat", "throat pain", "difficulty swallowing"],
|
| 453 |
+
"antibiotic": "CONDITIONAL β viral in 70% of cases; antibiotic only if bacterial strep suspected",
|
| 454 |
+
"first_line": ["Paracetamol (Pain and Fever)", "Salt water gargles", "Rest", "ORS (Hydration)"],
|
| 455 |
+
"second_line": ["Amoxicillin (If Strep throat confirmed or strongly suspected β avoid if penicillin allergy)", "Azithromycin (Penicillin allergy)"],
|
| 456 |
+
"tests": ["Throat Swab Culture β only if bacterial strep strongly suspected", "Rapid Strep Test β if available"],
|
| 457 |
+
"avoid": ["Aspirin β children (Reye's syndrome)", "Amoxicillin β if penicillin allergy or EBV suspected (causes rash)"],
|
| 458 |
+
},
|
| 459 |
+
{
|
| 460 |
+
"name": "Tuberculosis (TB)",
|
| 461 |
+
"keywords": ["weight loss", "night sweats", "prolonged fever", "weeks", "tuberculosis", "tb", "haemoptysis", "blood in sputum"],
|
| 462 |
+
"required_any": ["cough", "weight loss", "night sweats", "fever", "fatigue"],
|
| 463 |
+
"antibiotic": "YES β anti-TB therapy required (specialist-supervised)",
|
| 464 |
+
"first_line": ["Refer to specialist β TB requires DOTS therapy (specialist-supervised)", "Paracetamol (Fever)"],
|
| 465 |
+
"second_line": ["DOTS Regimen (HRZE β Isoniazid, Rifampicin, Pyrazinamide, Ethambutol β specialist only)"],
|
| 466 |
+
"tests": ["Chest X-Ray β bilateral infiltrates/cavitation", "Sputum AFB Smear β TB confirmation", "Mantoux Test β TB exposure", "CBNAAT/GeneXpert β rapid TB and drug resistance"],
|
| 467 |
+
"avoid": ["Self-medication β incomplete treatment causes drug resistance", "Fluoroquinolones without TB ruled out β masks TB"],
|
| 468 |
+
},
|
| 469 |
+
{
|
| 470 |
+
"name": "Sepsis / Septic Shock",
|
| 471 |
+
"keywords": ["confusion", "low blood pressure", "hypotension", "fast breathing", "sepsis", "septic shock", "cold clammy", "organ failure"],
|
| 472 |
+
"required_any": ["fever", "confusion", "low blood pressure", "fast breathing", "weakness"],
|
| 473 |
+
"antibiotic": "YES β broad-spectrum IV antibiotics required immediately",
|
| 474 |
+
"first_line": ["EMERGENCY: Immediate hospital referral required", "IV Piperacillin-Tazobactam (Broad-spectrum β hospital only)", "IV Normal Saline bolus (Fluid resuscitation)", "Norepinephrine (Vasopressor β ICU only if BP unresponsive)"],
|
| 475 |
+
"second_line": ["IV Meropenem (If resistant organisms suspected)", "IV Vancomycin (If MRSA suspected)"],
|
| 476 |
+
"tests": ["Blood Culture x2 β before antibiotics if possible", "CBC β infection severity", "Lactate β sepsis severity marker", "Renal Function Tests β organ involvement", "CRP/Procalcitonin β sepsis confirmation"],
|
| 477 |
+
"avoid": ["Delay in antibiotics β every hour delay increases mortality", "Oral antibiotics β IV route required"],
|
| 478 |
+
},
|
| 479 |
+
]
|
| 480 |
+
|
| 481 |
+
CRITICAL_KEYWORDS = [
|
| 482 |
+
"altered consciousness", "confusion", "seizure", "fits",
|
| 483 |
+
"difficulty breathing", "severe breathlessness", "can't breathe",
|
| 484 |
+
"stiff neck", "photophobia", "neck stiffness",
|
| 485 |
+
"uncontrolled bleeding", "coughing blood", "blood in stool",
|
| 486 |
+
"crushing chest pain", "chest pain with sweating",
|
| 487 |
+
"unconscious", "not passing urine",
|
| 488 |
+
"high fever", "fever more than 5 days", "fever not responding to medication",
|
| 489 |
+
"severe dehydration", "unable to swallow", "persistent vomiting",
|
| 490 |
+
"severe headache with neck stiffness", "rash with fever",
|
| 491 |
+
# Septic shock triggers
|
| 492 |
+
"low blood pressure", "hypotension", "fast breathing", "rapid breathing",
|
| 493 |
+
"septic shock", "sepsis", "organ failure", "cold clammy",
|
| 494 |
+
]
|
| 495 |
+
|
| 496 |
+
def run_rule_engine(condition_text, allergies_text, pregnancy_status="Not mentioned", diabetes="Not mentioned", age="Not specified"):
|
| 497 |
+
text = condition_text.lower()
|
| 498 |
+
allergy_text = allergies_text.lower()
|
| 499 |
+
is_pregnant = pregnancy_status.lower() not in ["not mentioned", "no", "none", ""]
|
| 500 |
+
is_diabetic = diabetes.lower() not in ["not mentioned", "no", "none", ""]
|
| 501 |
+
is_child = any(w in text for w in ["child", "baby", "infant", "toddler", "kid"]) or \
|
| 502 |
+
(age.isdigit() and int(age) < 18)
|
| 503 |
+
|
| 504 |
+
is_critical = any(kw in text for kw in CRITICAL_KEYWORDS)
|
| 505 |
+
matched_rules = []
|
| 506 |
+
|
| 507 |
+
for rule in SYMPTOM_RULES:
|
| 508 |
+
keyword_hit = any(kw in text for kw in rule["keywords"])
|
| 509 |
+
required_hit = any(kw in text for kw in rule["required_any"])
|
| 510 |
+
if keyword_hit and required_hit:
|
| 511 |
+
matched_rules.append(rule)
|
| 512 |
+
|
| 513 |
+
# PYELONEPHRITIS PRIORITY: fever + vomiting + back/flank pain β override plain UTI
|
| 514 |
+
pyelonephritis_triggers = ["fever", "vomiting", "back pain", "flank pain", "loin pain", "rigors", "high fever"]
|
| 515 |
+
has_upper_uti_flags = any(kw in text for kw in pyelonephritis_triggers)
|
| 516 |
+
matched_names = [r["name"] for r in matched_rules]
|
| 517 |
+
if "Pyelonephritis (Upper UTI)" in matched_names and "Urinary Tract Infection (UTI)" in matched_names and has_upper_uti_flags:
|
| 518 |
+
matched_rules = [r for r in matched_rules if r["name"] != "Urinary Tract Infection (UTI)"]
|
| 519 |
+
|
| 520 |
+
allergy_warnings = []
|
| 521 |
+
if "penicillin" in allergy_text:
|
| 522 |
+
allergy_warnings.append("Penicillin allergy β avoid Amoxicillin, Flucloxacillin, Co-amoxiclav")
|
| 523 |
+
if "sulfa" in allergy_text or "sulphonamide" in allergy_text:
|
| 524 |
+
allergy_warnings.append("Sulfa allergy β avoid Trimethoprim-Sulfamethoxazole")
|
| 525 |
+
if "aspirin" in allergy_text or "nsaid" in allergy_text:
|
| 526 |
+
allergy_warnings.append("NSAID/Aspirin allergy β avoid all NSAIDs")
|
| 527 |
+
if "metformin" in allergy_text or is_diabetic:
|
| 528 |
+
allergy_warnings.append("Diabetic patient β flag Ciprofloxacin interaction risk; prefer safer alternatives")
|
| 529 |
+
allergy_warnings.append("Diabetic + UTI β Nitrofurantoin preferred over Fluoroquinolones")
|
| 530 |
+
|
| 531 |
+
# Pregnancy-specific warnings β only if explicitly mentioned
|
| 532 |
+
if is_pregnant:
|
| 533 |
+
allergy_warnings.append("PREGNANCY NOTED β avoid Fluoroquinolones, Tetracyclines, Trimethoprim, NSAIDs, Clarithromycin")
|
| 534 |
+
allergy_warnings.append("PREGNANCY + UTI β Nitrofurantoin safe only in 1st/2nd trimester; prefer Cefalexin if trimester unknown")
|
| 535 |
+
allergy_warnings.append("PREGNANCY + Pneumonia β Azithromycin (Category B) preferred; Clarithromycin (Category C) β AVOID")
|
| 536 |
+
|
| 537 |
+
# Child-specific warnings
|
| 538 |
+
if is_child:
|
| 539 |
+
allergy_warnings.append("CHILD PATIENT β avoid Aspirin (Reye's syndrome risk); avoid Fluoroquinolones under 18")
|
| 540 |
+
allergy_warnings.append("CHILD PATIENT β weight-based dosing needed; flag in Additional Information Needed")
|
| 541 |
+
|
| 542 |
+
return {"is_critical": is_critical, "matched_rules": matched_rules, "allergy_warnings": allergy_warnings}
|
| 543 |
+
|
| 544 |
+
|
| 545 |
+
# ==============================
|
| 546 |
+
# CORE CLINICAL ENGINE
|
| 547 |
+
# ==============================
|
| 548 |
+
def clinical_engine(data):
|
| 549 |
+
vs = get_vectorstore()
|
| 550 |
+
query = f"Symptoms: {data.get('condition')} Allergies: {data.get('allergies')} Medications: {data.get('medications')}"
|
| 551 |
+
try:
|
| 552 |
+
docs = vs.as_retriever(search_kwargs={"k": 3}).invoke(query)
|
| 553 |
+
context = "\n\n".join(d.page_content[:500] for d in docs)
|
| 554 |
+
except Exception:
|
| 555 |
+
context = "General medical guidelines apply."
|
| 556 |
+
|
| 557 |
+
condition = data.get('condition', '')
|
| 558 |
+
allergies = data.get('allergies', 'None')
|
| 559 |
+
medications = data.get('medications', 'None')
|
| 560 |
+
age = data.get('age', 'Not specified')
|
| 561 |
+
pregnancy_status = data.get('pregnancy', 'Not mentioned')
|
| 562 |
+
diabetes = data.get('diabetes', 'Not mentioned')
|
| 563 |
+
renal_issues = data.get('renal_issues', 'Not mentioned')
|
| 564 |
+
|
| 565 |
+
# Run rule engine β pass all patient factors
|
| 566 |
+
rules = run_rule_engine(condition, allergies, pregnancy_status, diabetes, age)
|
| 567 |
+
matched = rules["matched_rules"]
|
| 568 |
+
is_critical = rules["is_critical"]
|
| 569 |
+
allergy_warnings = rules["allergy_warnings"]
|
| 570 |
+
|
| 571 |
+
# Septic Shock override β if critical AND confusion + hypotension present β force emergency
|
| 572 |
+
septic_flags = ["confusion", "low blood pressure", "hypotension", "fast breathing", "sepsis", "septic"]
|
| 573 |
+
septic_hit = sum(1 for kw in septic_flags if kw in condition.lower())
|
| 574 |
+
is_septic_shock = septic_hit >= 2
|
| 575 |
+
|
| 576 |
+
# Build rule hints for LLM
|
| 577 |
+
rule_hint = ""
|
| 578 |
+
if is_critical:
|
| 579 |
+
rule_hint += "CRITICAL EMERGENCY DETECTED β Recommend immediate hospital referral.\n"
|
| 580 |
+
if is_septic_shock:
|
| 581 |
+
rule_hint += (
|
| 582 |
+
"SEPTIC SHOCK PATTERN DETECTED β MANDATORY RULES:\n"
|
| 583 |
+
" 1. Clinical Assessment MUST start with: EMERGENCY: Immediate hospital referral required.\n"
|
| 584 |
+
" 2. First-Line MUST include: IV Broad-Spectrum Antibiotics (Piperacillin-Tazobactam or Meropenem β hospital only)\n"
|
| 585 |
+
" 3. First-Line MUST include: IV Fluid Resuscitation (Normal Saline bolus)\n"
|
| 586 |
+
" 4. First-Line MUST include: Vasopressors if BP not responding (Norepinephrine β ICU only)\n"
|
| 587 |
+
" 5. Additional Information Needed MUST say: EMERGENCY β ICU admission required immediately.\n"
|
| 588 |
+
)
|
| 589 |
+
if matched:
|
| 590 |
+
rule_hint += "\nRule Engine Pre-Analysis (HIGH CONFIDENCE β follow unless contradicted):\n"
|
| 591 |
+
for r in matched:
|
| 592 |
+
rule_hint += f" Disease: {r['name']}\n"
|
| 593 |
+
rule_hint += f" Antibiotic: {r['antibiotic']}\n"
|
| 594 |
+
rule_hint += f" First-Line: {', '.join(r['first_line'][:3])}\n"
|
| 595 |
+
rule_hint += f" Key Tests: {', '.join(r['tests'][:3])}\n"
|
| 596 |
+
rule_hint += f" Avoid: {', '.join(r['avoid'][:3])}\n\n"
|
| 597 |
+
if allergy_warnings:
|
| 598 |
+
rule_hint += "Allergy Alerts:\n" + "\n".join(f" - {w}" for w in allergy_warnings) + "\n"
|
| 599 |
+
if not matched:
|
| 600 |
+
rule_hint += "No strong pattern match. Use clinical reasoning based on symptoms only.\n"
|
| 601 |
+
|
| 602 |
+
llm_prompt = f"""
|
| 603 |
+
You are a senior physician and clinical decision support system (CDS).
|
| 604 |
+
|
| 605 |
+
IMPORTANT: The Rule Engine below has pre-analyzed this case. You MUST follow it unless you have strong clinical reason to deviate.
|
| 606 |
+
|
| 607 |
+
========================
|
| 608 |
+
RULE ENGINE OUTPUT
|
| 609 |
+
========================
|
| 610 |
+
{rule_hint}
|
| 611 |
+
========================
|
| 612 |
+
INPUT DATA
|
| 613 |
+
========================
|
| 614 |
+
Symptoms: {condition}
|
| 615 |
+
Allergies: {allergies}
|
| 616 |
+
Current Medications: {medications}
|
| 617 |
+
Age: {age}
|
| 618 |
+
Pregnancy Status: {pregnancy_status}
|
| 619 |
+
Diabetes: {diabetes}
|
| 620 |
+
Renal Issues: {renal_issues}
|
| 621 |
+
|
| 622 |
+
Clinical Guidelines (PDF Context):
|
| 623 |
+
{context}
|
| 624 |
+
|
| 625 |
+
========================
|
| 626 |
+
REASONING FRAMEWORK (POORQA)
|
| 627 |
+
========================
|
| 628 |
+
|
| 629 |
+
Step 1: Pattern Recognition
|
| 630 |
+
- Analyze symptom combinations carefully
|
| 631 |
+
- Identify if symptoms strongly match a known clinical pattern
|
| 632 |
+
- If a strong pattern exists β prefer a specific diagnosis
|
| 633 |
+
- If no clear pattern β use general diagnosis cautiously
|
| 634 |
+
|
| 635 |
+
Step 2: Differential Diagnosis
|
| 636 |
+
- List top 1β3 most likely diseases
|
| 637 |
+
- Rank them by probability (most likely first)
|
| 638 |
+
- Do NOT include unrelated diseases
|
| 639 |
+
|
| 640 |
+
Step 3: Antibiotic Decision
|
| 641 |
+
- Decide: YES / NO / ALTERNATIVE (e.g., antiviral/antiparasitic)
|
| 642 |
+
- Antibiotics ONLY if strong bacterial evidence
|
| 643 |
+
- If unclear β DO NOT give antibiotics
|
| 644 |
+
|
| 645 |
+
Step 4: Treatment Selection
|
| 646 |
+
- First-line = safest effective option
|
| 647 |
+
- Second-line = only if needed
|
| 648 |
+
- Treatment MUST match primary diagnosis ONLY
|
| 649 |
+
- DO NOT mix treatments from different diseases
|
| 650 |
+
|
| 651 |
+
Step 5: Safety Check (CRITICAL)
|
| 652 |
+
- Check allergies
|
| 653 |
+
- Check drug safety
|
| 654 |
+
- Avoid:
|
| 655 |
+
- Unnecessary antibiotics
|
| 656 |
+
- NSAIDs in suspected bleeding-risk conditions
|
| 657 |
+
- Steroids unless clearly indicated
|
| 658 |
+
- Prefer safest drug (e.g., paracetamol over NSAIDs when uncertain)
|
| 659 |
+
|
| 660 |
+
Step 6: Test Justification
|
| 661 |
+
- Mild case + clear clinical pattern β 0 tests needed, write "None required"
|
| 662 |
+
- Moderate suspicion β 1β2 targeted confirmatory tests only
|
| 663 |
+
- Severe or uncertain diagnosis β targeted panel only, no blanket ordering
|
| 664 |
+
- DO NOT suggest CBC routinely for every case
|
| 665 |
+
- Each test MUST have a specific clinical reason stated after a dash
|
| 666 |
+
|
| 667 |
+
Step 7: Final Validation
|
| 668 |
+
Before answering, ensure:
|
| 669 |
+
- No hallucinated symptoms added
|
| 670 |
+
- No unsafe drug suggested
|
| 671 |
+
- No missing critical test
|
| 672 |
+
- No vague diagnosis if specific possible
|
| 673 |
+
|
| 674 |
+
========================
|
| 675 |
+
STRICT RULES
|
| 676 |
+
========================
|
| 677 |
+
- NEVER assume symptoms not provided
|
| 678 |
+
- NEVER give antibiotics without clear bacterial indication
|
| 679 |
+
- NEVER use vague diagnosis if a strong clinical pattern exists
|
| 680 |
+
- NEVER suggest contraindicated or harmful drugs
|
| 681 |
+
- ALWAYS prioritize patient safety above all else
|
| 682 |
+
- ALWAYS be clinically logical and consistent
|
| 683 |
+
- Viral fever / dengue / malaria / flu β antibiotics ABSOLUTELY PROHIBITED
|
| 684 |
+
- Mild URI or cold < 3 days β supportive care ONLY, no antibiotics
|
| 685 |
+
- If bacterial infection is UNCLEAR β do NOT give antibiotics; recommend 48hr monitoring
|
| 686 |
+
- If CRITICAL emergency (chest pain + sweating, unconscious, heavy bleeding, can't breathe, stiff neck) β
|
| 687 |
+
Clinical Assessment MUST start with "EMERGENCY: Immediate hospital referral required."
|
| 688 |
+
- Consider patient age, allergies, comorbidities (diabetes, renal issues) in every recommendation
|
| 689 |
+
- Include pregnancy contraindications ONLY if pregnancy_status is explicitly mentioned by user β do NOT assume
|
| 690 |
+
- Elderly or renal impairment β avoid Nitrofurantoin for upper UTI; flag renally-cleared drug risks
|
| 691 |
+
- Diabetic patients β flag Ciprofloxacin interaction risk where relevant
|
| 692 |
+
|
| 693 |
+
========================
|
| 694 |
+
OUTPUT FORMAT (STRICT)
|
| 695 |
+
========================
|
| 696 |
+
|
| 697 |
+
Use EXACTLY these section headers in this order β do not rename, skip, or reorder:
|
| 698 |
+
|
| 699 |
+
Clinical Assessment:
|
| 700 |
+
[Top 2β3 differential diagnoses ranked by probability. Format each on its own line:
|
| 701 |
+
1. Most Likely: [Disease] β [one clinical reason from given symptoms only]
|
| 702 |
+
2. Also Consider: [Disease] β [one clinical reason]
|
| 703 |
+
3. Less Likely: [Disease] β [only if genuinely relevant]
|
| 704 |
+
Do NOT repeat symptoms. Do NOT invent symptoms not provided.]
|
| 705 |
+
|
| 706 |
+
Antibiotic Necessity:
|
| 707 |
+
[YES / NO / ANTIMALARIAL / ANTIVIRAL β one short reason. Must match primary diagnosis.]
|
| 708 |
+
|
| 709 |
+
First-Line Therapy:
|
| 710 |
+
[One drug per line. Format: DrugName (Condition). No doses.
|
| 711 |
+
If NO antibiotic: supportive care only β Paracetamol, ORS, Rest, Vitamin C as appropriate.
|
| 712 |
+
If YES antibiotic: safest guideline-based antibiotic first.]
|
| 713 |
+
|
| 714 |
+
Second-Line Alternatives:
|
| 715 |
+
[If antibiotic YES: 1β2 guideline-based alternatives, one per line.
|
| 716 |
+
If antibiotic NO: write β Not applicable β [reason e.g. viral infection]
|
| 717 |
+
NEVER write an antibiotic here when Antibiotic Necessity = NO.]
|
| 718 |
+
|
| 719 |
+
Contraindications & Precautions:
|
| 720 |
+
[Drug to avoid β reason. One per line.
|
| 721 |
+
Check allergies and current medications.
|
| 722 |
+
Pregnancy warnings ONLY if pregnancy was explicitly mentioned.
|
| 723 |
+
Write None if nothing applicable.]
|
| 724 |
+
|
| 725 |
+
Recommended Tests:
|
| 726 |
+
[TestName β specific reason. One per line.
|
| 727 |
+
Mild case with clear diagnosis β write: None required β clinical diagnosis sufficient
|
| 728 |
+
Moderate/severe or uncertain β targeted confirmatory tests only, no blanket panels.]
|
| 729 |
+
|
| 730 |
+
Additional Information Needed:
|
| 731 |
+
[EMERGENCY cases β first line MUST be: EMERGENCY: Immediate hospital referral required.
|
| 732 |
+
Clear mild diagnosis β write: None
|
| 733 |
+
Otherwise β max 2 missing details that would change management.]
|
| 734 |
+
|
| 735 |
+
========================
|
| 736 |
+
MANDATORY CONSISTENCY RULES
|
| 737 |
+
========================
|
| 738 |
+
- Malaria / Dengue / Viral Fever / Influenza / Common Cold β Antibiotic MUST be NO
|
| 739 |
+
- If Antibiotic = NO β zero antibiotics in First-Line AND Second-Line
|
| 740 |
+
- Treatment must match primary diagnosis only β no mixing
|
| 741 |
+
- Never add symptoms not given by user
|
| 742 |
+
- Never leave a section empty β use None or Not applicable
|
| 743 |
+
- Never use ** ## or bullet dashes β plain text only
|
| 744 |
+
- Follow exact section headers above
|
| 745 |
+
|
| 746 |
+
PYELONEPHRITIS vs UTI (CRITICAL):
|
| 747 |
+
- Fever + vomiting + back pain/flank pain + urinary symptoms β PRIMARY = Pyelonephritis
|
| 748 |
+
- Nitrofurantoin PROHIBITED in Pyelonephritis β ineffective in kidney tissue
|
| 749 |
+
- Pyelonephritis β Co-amoxiclav oral or Ceftriaxone IV (severe)
|
| 750 |
+
- Simple UTI (no fever, no systemic symptoms) β Nitrofurantoin acceptable
|
| 751 |
+
|
| 752 |
+
PREGNANCY SAFETY (only if explicitly mentioned):
|
| 753 |
+
- Nitrofurantoin β trimester unknown β prefer Cefalexin instead
|
| 754 |
+
- Azithromycin β Category B β safe, preferred for pneumonia
|
| 755 |
+
- Clarithromycin β Category C β AVOID, flag in contraindications
|
| 756 |
+
- Fluoroquinolones, Tetracyclines, Trimethoprim, NSAIDs β AVOID
|
| 757 |
+
|
| 758 |
+
PNEUMONIA ANTIBIOTIC DECISION:
|
| 759 |
+
- High fever + chest pain + breathlessness + productive cough β bacterial β antibiotic YES
|
| 760 |
+
- Fatigue + body ache + mild cough only β viral first β no antibiotic without X-Ray confirmation
|
| 761 |
+
- Always recommend Chest X-Ray as first test for suspected pneumonia
|
| 762 |
+
|
| 763 |
+
SEPTIC SHOCK (MANDATORY):
|
| 764 |
+
- Fever + confusion + low BP + fast breathing β Septic Shock
|
| 765 |
+
- Assessment MUST start: EMERGENCY: Immediate hospital referral required.
|
| 766 |
+
- First-Line MUST include IV antibiotics + IV fluids + vasopressors if BP unresponsive
|
| 767 |
+
- Additional Info MUST say: EMERGENCY β ICU admission required immediately
|
| 768 |
+
|
| 769 |
+
DIABETIC PATIENTS:
|
| 770 |
+
- Flag Ciprofloxacin interaction risk if patient is diabetic on relevant medications
|
| 771 |
+
- Prefer safer alternatives where possible
|
| 772 |
+
|
| 773 |
+
CHILD PATIENTS:
|
| 774 |
+
- Avoid Aspirin β Reye's syndrome risk
|
| 775 |
+
- Avoid Fluoroquinolones in children under 18
|
| 776 |
+
- Dose adjustments may be needed β flag this in Additional Information Needed
|
| 777 |
+
"""
|
| 778 |
+
|
| 779 |
+
response = None
|
| 780 |
+
last_raw = None
|
| 781 |
+
for i in range(3):
|
| 782 |
+
raw = call_llm(llm_prompt)
|
| 783 |
+
if raw and not raw.startswith("ERROR"):
|
| 784 |
+
last_raw = raw
|
| 785 |
+
if validate_response(raw):
|
| 786 |
+
response = raw
|
| 787 |
+
break
|
| 788 |
+
print(f"β οΈ Retry {i+1}: validation failed")
|
| 789 |
+
else:
|
| 790 |
+
import time
|
| 791 |
+
time.sleep(10) # 10 second wait before retry
|
| 792 |
+
|
| 793 |
+
# Agar validation pass nahi hua lekin LLM ne kuch meaningful diya β use karo
|
| 794 |
+
if not response:
|
| 795 |
+
if last_raw and len(last_raw.strip()) > 100:
|
| 796 |
+
print("β οΈ Using last LLM response despite validation failure")
|
| 797 |
+
response = last_raw
|
| 798 |
+
else:
|
| 799 |
+
response = """Clinical Assessment:
|
| 800 |
+
Unable to determine diagnosis. Please consult a doctor immediately.
|
| 801 |
+
|
| 802 |
+
Antibiotic Necessity:
|
| 803 |
+
NO β Insufficient information to make a safe antibiotic decision.
|
| 804 |
+
|
| 805 |
+
First-Line Therapy:
|
| 806 |
+
Paracetamol (fever and pain relief) | ORS (hydration) | Rest
|
| 807 |
+
|
| 808 |
+
Second-Line Alternatives:
|
| 809 |
+
Not applicable β insufficient clinical information
|
| 810 |
+
|
| 811 |
+
Contraindications & Precautions:
|
| 812 |
+
Do not self-medicate without professional medical evaluation.
|
| 813 |
+
|
| 814 |
+
Recommended Tests:
|
| 815 |
+
Complete Blood Count (CBC) β Basic infection screening
|
| 816 |
+
|
| 817 |
+
Additional Information Needed:
|
| 818 |
+
Please provide full symptom history, duration, and severity to enable proper diagnosis.
|
| 819 |
+
"""
|
| 820 |
+
|
| 821 |
+
response = safety_filter(response)
|
| 822 |
+
return response
|
| 823 |
+
|
| 824 |
+
|
| 825 |
+
# ==============================
|
| 826 |
+
# API ENDPOINTS
|
| 827 |
+
# ==============================
|
| 828 |
+
@app.route("/analyze", methods=["POST"])
|
| 829 |
+
def analyze():
|
| 830 |
+
try:
|
| 831 |
+
data = request.get_json(force=True, silent=True)
|
| 832 |
+
if not data:
|
| 833 |
+
return jsonify({"status": "error", "message": "Invalid JSON"}), 400
|
| 834 |
+
|
| 835 |
+
# β
FIX: Empty symptoms check β fallback se bachao
|
| 836 |
+
condition = data.get('condition', '').strip()
|
| 837 |
+
if not condition:
|
| 838 |
+
return jsonify({
|
| 839 |
+
"status": "error",
|
| 840 |
+
"message": "Please enter symptoms before analyzing. The Symptoms / Condition field cannot be empty."
|
| 841 |
+
}), 400
|
| 842 |
+
result = clinical_engine(data)
|
| 843 |
+
parsed = parse_response(result)
|
| 844 |
+
|
| 845 |
+
|
| 846 |
+
db_temp = get_db()
|
| 847 |
+
count = db_temp.execute("SELECT COUNT(*) FROM patients").fetchone()[0]
|
| 848 |
+
year = datetime.now().strftime("%Y")
|
| 849 |
+
patient_id = f"P-{year}{str(count + 1).zfill(2)}"
|
| 850 |
+
|
| 851 |
+
result = clinical_engine(data)
|
| 852 |
+
parsed = parse_response(result)
|
| 853 |
+
save_to_db(patient_id, data, parsed)
|
| 854 |
+
|
| 855 |
+
def to_array(text):
|
| 856 |
+
if not text:
|
| 857 |
+
return []
|
| 858 |
+
items = []
|
| 859 |
+
for part in text.split(' | '):
|
| 860 |
+
part = part.strip().lstrip('-β’').strip()
|
| 861 |
+
if part:
|
| 862 |
+
items.append(part)
|
| 863 |
+
return items
|
| 864 |
+
summary_text = f"""
|
| 865 |
+
Clinical assessment: {parsed.get("assessment", "")}.
|
| 866 |
+
Antibiotic necessity: {parsed.get("antibiotic_necessity", "")}.
|
| 867 |
+
First line therapy: {parsed.get("first_line", "")}.
|
| 868 |
+
Recommended tests: {parsed.get("recommended_tests", "")}.
|
| 869 |
+
"""
|
| 870 |
+
return jsonify({
|
| 871 |
+
"status": "success",
|
| 872 |
+
"patient_id": patient_id,
|
| 873 |
+
"clinical_assessment": to_array(parsed.get("assessment", "")),
|
| 874 |
+
"antibiotic_necessity": parsed.get("antibiotic_necessity", "").strip(),
|
| 875 |
+
"first_line_therapy": to_array(parsed.get("first_line", "")),
|
| 876 |
+
"second_line_alternatives": to_array(parsed.get("second_line", "")),
|
| 877 |
+
"contraindications": [x for x in to_array(parsed.get("contraindications", "")) if x.lower() != "none"],
|
| 878 |
+
"recommended_tests": to_array(parsed.get("recommended_tests", "")),
|
| 879 |
+
"additional_info_needed": [x for x in to_array(parsed.get("additional_info_needed", "")) if x.lower() != "none"],
|
| 880 |
+
})
|
| 881 |
+
except Exception as e:
|
| 882 |
+
return jsonify({"status": "error", "message": str(e)})
|
| 883 |
+
|
| 884 |
+
|
| 885 |
+
@app.route("/patients", methods=["GET"])
|
| 886 |
+
def get_patients():
|
| 887 |
+
try:
|
| 888 |
+
db = get_db()
|
| 889 |
+
rows = db.execute("SELECT * FROM patients ORDER BY timestamp DESC").fetchall()
|
| 890 |
+
patients = [dict(row) for row in rows]
|
| 891 |
+
return jsonify({"status": "success", "patients": patients})
|
| 892 |
+
except Exception as e:
|
| 893 |
+
return jsonify({"status": "error", "message": str(e)})
|
| 894 |
+
|
| 895 |
+
|
| 896 |
+
|
| 897 |
+
|
| 898 |
+
# ==============================
|
| 899 |
+
# SMART TTS β OpenAI β ElevenLabs β gTTS fallback
|
| 900 |
+
# ==============================
|
| 901 |
+
def generate_tts(text, lang="en"):
|
| 902 |
+
"""
|
| 903 |
+
Tries TTS providers in order of quality:
|
| 904 |
+
1. OpenAI TTS (onyx = deep calm male, nova = female)
|
| 905 |
+
2. ElevenLabs
|
| 906 |
+
3. gTTS (fallback)
|
| 907 |
+
Returns: (filepath, filename) or raises Exception
|
| 908 |
+
"""
|
| 909 |
+
os.makedirs("static", exist_ok=True)
|
| 910 |
+
filename = f"tts_{uuid.uuid4().hex}.mp3"
|
| 911 |
+
path = os.path.join("static", filename)
|
| 912 |
+
|
| 913 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "").strip()
|
| 914 |
+
ELEVENLABS_KEY = os.getenv("ELEVENLABS_API_KEY", "").strip()
|
| 915 |
+
ELEVENLABS_VOICE = os.getenv("ELEVENLABS_VOICE_ID", "ErXwobaYiN019PkySvjV")
|
| 916 |
+
|
| 917 |
+
print(f"π TTS: OpenAI key present = {bool(OPENAI_API_KEY)}, ElevenLabs = {bool(ELEVENLABS_KEY)}")
|
| 918 |
+
|
| 919 |
+
# ββ 1. OpenAI TTS (best quality, natural, no pauses) ββ
|
| 920 |
+
if OPENAI_API_KEY:
|
| 921 |
+
try:
|
| 922 |
+
voice = "onyx" # Deep, calm male β Jarvis feel
|
| 923 |
+
if lang == "hi":
|
| 924 |
+
voice = "onyx" # onyx handles Hindi well too
|
| 925 |
+
resp = requests.post(
|
| 926 |
+
"https://api.openai.com/v1/audio/speech",
|
| 927 |
+
headers={
|
| 928 |
+
"Authorization": f"Bearer {OPENAI_API_KEY}",
|
| 929 |
+
"Content-Type": "application/json"
|
| 930 |
+
},
|
| 931 |
+
json={
|
| 932 |
+
"model": "tts-1-hd", # HD = higher quality, smoother
|
| 933 |
+
"input": text,
|
| 934 |
+
"voice": voice,
|
| 935 |
+
"speed": 0.92, # Calm, measured doctor pace
|
| 936 |
+
"response_format": "mp3"
|
| 937 |
+
},
|
| 938 |
+
timeout=20
|
| 939 |
+
)
|
| 940 |
+
if resp.status_code == 200:
|
| 941 |
+
with open(path, "wb") as f:
|
| 942 |
+
f.write(resp.content)
|
| 943 |
+
print("β
TTS: OpenAI")
|
| 944 |
+
return path, filename
|
| 945 |
+
else:
|
| 946 |
+
print(f"OpenAI TTS error {resp.status_code}: {resp.text[:100]}")
|
| 947 |
+
except Exception as e:
|
| 948 |
+
print(f"OpenAI TTS exception: {e}")
|
| 949 |
+
|
| 950 |
+
# ββ 2. ElevenLabs (most natural, best for Hindi too) ββ
|
| 951 |
+
if ELEVENLABS_KEY:
|
| 952 |
+
try:
|
| 953 |
+
resp = requests.post(
|
| 954 |
+
f"https://api.elevenlabs.io/v1/text-to-speech/{ELEVENLABS_VOICE}",
|
| 955 |
+
headers={
|
| 956 |
+
"xi-api-key": ELEVENLABS_KEY,
|
| 957 |
+
"Content-Type": "application/json"
|
| 958 |
+
},
|
| 959 |
+
json={
|
| 960 |
+
"text": text,
|
| 961 |
+
"model_id": "eleven_multilingual_v2",
|
| 962 |
+
"voice_settings": {
|
| 963 |
+
"stability": 0.55,
|
| 964 |
+
"similarity_boost": 0.80,
|
| 965 |
+
"style": 0.20,
|
| 966 |
+
"use_speaker_boost": True
|
| 967 |
+
}
|
| 968 |
+
},
|
| 969 |
+
timeout=20
|
| 970 |
+
)
|
| 971 |
+
if resp.status_code == 200:
|
| 972 |
+
with open(path, "wb") as f:
|
| 973 |
+
f.write(resp.content)
|
| 974 |
+
print("β
TTS: ElevenLabs")
|
| 975 |
+
return path, filename
|
| 976 |
+
else:
|
| 977 |
+
print(f"ElevenLabs TTS error {resp.status_code}: {resp.text[:100]}")
|
| 978 |
+
except Exception as e:
|
| 979 |
+
print(f"ElevenLabs TTS exception: {e}")
|
| 980 |
+
|
| 981 |
+
# ββ 3. gTTS fallback (basic but works) ββ
|
| 982 |
+
try:
|
| 983 |
+
tts_lang_code = "hi" if lang == "hi" else "en"
|
| 984 |
+
tts_obj = gTTS(text=text, lang=tts_lang_code, slow=False)
|
| 985 |
+
tts_obj.save(path)
|
| 986 |
+
print("β οΈ TTS: gTTS fallback")
|
| 987 |
+
return path, filename
|
| 988 |
+
except Exception as e:
|
| 989 |
+
raise Exception(f"All TTS providers failed. Last error: {e}")
|
| 990 |
+
|
| 991 |
+
|
| 992 |
+
@app.route("/tts", methods=["POST"])
|
| 993 |
+
def tts():
|
| 994 |
+
try:
|
| 995 |
+
data = request.get_json()
|
| 996 |
+
text = data.get("text", "").strip()
|
| 997 |
+
lang = data.get("lang", "en")
|
| 998 |
+
if not text:
|
| 999 |
+
return jsonify({"error": "Empty text"}), 400
|
| 1000 |
+
|
| 1001 |
+
path, filename = generate_tts(text, lang)
|
| 1002 |
+
return jsonify({"audio_url": f"/static/{filename}"})
|
| 1003 |
+
|
| 1004 |
+
except Exception as e:
|
| 1005 |
+
return jsonify({"error": str(e)})
|
| 1006 |
+
|
| 1007 |
+
|
| 1008 |
+
# ==============================
|
| 1009 |
+
# CONVERSATIONAL DOCTOR ENDPOINT
|
| 1010 |
+
# ==============================
|
| 1011 |
+
DOCTOR_SYSTEM_PROMPT = """You are Dr. Safecure β an intelligent AI doctor, like Jarvis or Siri but for medicine.
|
| 1012 |
+
You talk like a calm, confident, real human doctor. Natural. Direct. Never robotic or stiff.
|
| 1013 |
+
You have clinical guidelines from PDFs available as context β use them for all recommendations.
|
| 1014 |
+
|
| 1015 |
+
YOUR VOICE & STYLE:
|
| 1016 |
+
- Sound like a real doctor talking to a patient face to face. Warm but not over-the-top.
|
| 1017 |
+
- Short natural sentences. Like how a person actually speaks.
|
| 1018 |
+
- NO filler: never say "I understand your concern", "That sounds difficult", "Great question", "Certainly" etc.
|
| 1019 |
+
- NO markdown: no **, ##, dashes as bullets. Plain text only during conversation.
|
| 1020 |
+
- Respond in the SAME language the patient speaks (Hindi or English).
|
| 1021 |
+
|
| 1022 |
+
CONSULTATION FLOW:
|
| 1023 |
+
1. Greet once, naturally. Ask the main problem.
|
| 1024 |
+
2. Ask ONE follow-up question at a time. Max 3 follow-ups total. Be brief.
|
| 1025 |
+
3. Once you have enough info (symptom + duration + allergies/current meds) β immediately give FINAL ASSESSMENT.
|
| 1026 |
+
4. Use the special final assessment format below (EXACTLY).
|
| 1027 |
+
|
| 1028 |
+
EMERGENCY OVERRIDE: If patient says chest pain with sweating, can't breathe, unconscious, heavy bleeding β say:
|
| 1029 |
+
"This sounds like an emergency. Please call an ambulance or go to the nearest hospital right now. Don't wait."
|
| 1030 |
+
Then stop. Nothing else.
|
| 1031 |
+
|
| 1032 |
+
FINAL ASSESSMENT FORMAT (use EXACTLY when you have enough info):
|
| 1033 |
+
When ready to give your final assessment, output it in this exact format β each section on its own line with the label followed by a colon and the content. Do not use bullets or dashes:
|
| 1034 |
+
|
| 1035 |
+
DIAGNOSIS: [Most likely condition and brief reason why]
|
| 1036 |
+
FIRST LINE: [Primary medicines β drug names only, no doses, each separated by comma]
|
| 1037 |
+
SECOND LINE: [Alternative medicines if first line fails or is contraindicated β or write "Not needed"]
|
| 1038 |
+
TESTS: [Recommended tests β each separated by comma, with brief reason after a dash]
|
| 1039 |
+
AVOID: [Medicines or things to avoid β or write "None"]
|
| 1040 |
+
NOTE: [One short sentence of important advice for the patient]
|
| 1041 |
+
|
| 1042 |
+
ANTIBIOTIC RULES (NON-NEGOTIABLE):
|
| 1043 |
+
- Viral fever, dengue, malaria, flu, cold, sore throat β NEVER prescribe antibiotics under any circumstance
|
| 1044 |
+
- Antibiotics ONLY when bacterial infection is strongly and clearly suspected
|
| 1045 |
+
- Mild URI / cough < 3 days β supportive care ONLY (Paracetamol, ORS, Rest)
|
| 1046 |
+
- If bacterial vs viral is unclear β say "Monitor for 48 hours β antibiotics not needed yet"
|
| 1047 |
+
- Include pregnancy contraindications ONLY if patient explicitly mentions being pregnant
|
| 1048 |
+
|
| 1049 |
+
RULES:
|
| 1050 |
+
|
| 1051 |
+
Step 1: Pattern Recognition
|
| 1052 |
+
- Analyze symptom combinations carefully
|
| 1053 |
+
- Identify if symptoms strongly match a known clinical pattern
|
| 1054 |
+
- If a strong pattern exists β prefer a specific diagnosis
|
| 1055 |
+
- If no clear pattern β use general diagnosis cautiously
|
| 1056 |
+
|
| 1057 |
+
Step 2: Differential Diagnosis
|
| 1058 |
+
- List top 1 (or 2 if needed) most likely diseases
|
| 1059 |
+
- Rank them by probability (most likely first)
|
| 1060 |
+
- Do NOT include unrelated diseases
|
| 1061 |
+
|
| 1062 |
+
Step 3: Antibiotic Decision
|
| 1063 |
+
- Decide: YES / NO / ALTERNATIVE (e.g., antiviral/antiparasitic)
|
| 1064 |
+
- Antibiotics ONLY if strong bacterial evidence
|
| 1065 |
+
- If unclear β DO NOT give antibiotics
|
| 1066 |
+
|
| 1067 |
+
Step 4: Treatment Selection
|
| 1068 |
+
- First-line = safest effective option
|
| 1069 |
+
- Second-line = only if needed
|
| 1070 |
+
- Treatment MUST match primary diagnosis ONLY
|
| 1071 |
+
- DO NOT mix treatments from different diseases
|
| 1072 |
+
|
| 1073 |
+
Step 5: Safety Check (CRITICAL)
|
| 1074 |
+
- Check allergies
|
| 1075 |
+
- Check drug safety
|
| 1076 |
+
- Avoid:
|
| 1077 |
+
- Unnecessary antibiotics
|
| 1078 |
+
- NSAIDs in suspected bleeding-risk conditions
|
| 1079 |
+
- Steroids unless clearly indicated
|
| 1080 |
+
- Prefer safest drug (e.g., paracetamol over NSAIDs when uncertain)
|
| 1081 |
+
|
| 1082 |
+
Step 6: Test Justification
|
| 1083 |
+
- Suggest tests ONLY if clinically needed
|
| 1084 |
+
- If strong suspicion of specific disease β MUST suggest confirmatory test
|
| 1085 |
+
- DO NOT skip tests in moderate/high suspicion cases
|
| 1086 |
+
|
| 1087 |
+
Step 7: Final Validation
|
| 1088 |
+
Before answering, ensure:
|
| 1089 |
+
- No hallucinated symptoms added
|
| 1090 |
+
- No unsafe drug suggested
|
| 1091 |
+
- No missing critical test
|
| 1092 |
+
- No vague diagnosis if specific possible
|
| 1093 |
+
|
| 1094 |
+
========================
|
| 1095 |
+
STRICT RULES
|
| 1096 |
+
========================
|
| 1097 |
+
|
| 1098 |
+
- NEVER assume symptoms not provided
|
| 1099 |
+
- NEVER give antibiotics without clear indication
|
| 1100 |
+
- NEVER use vague diagnosis if a strong pattern exists
|
| 1101 |
+
- NEVER suggest harmful or contraindicated drugs
|
| 1102 |
+
- ALWAYS prioritize patient safety
|
| 1103 |
+
- ALWAYS be clinically logical and consistent
|
| 1104 |
+
========================
|
| 1105 |
+
MANDATORY CONSISTENCY RULES
|
| 1106 |
+
========================
|
| 1107 |
+
|
| 1108 |
+
- If diagnosis is Malaria/Dengue/Viral β Antibiotic MUST be NO
|
| 1109 |
+
- If Antibiotic = NO β ZERO antibiotics in First-Line or Second-Line
|
| 1110 |
+
- Treatment MUST match primary diagnosis
|
| 1111 |
+
- Do NOT add symptoms that were not given
|
| 1112 |
+
- Do NOT leave any section empty
|
| 1113 |
+
- NEVER use ** or ## or bullet points in output"""
|
| 1114 |
+
|
| 1115 |
+
@app.route("/chat", methods=["POST"])
|
| 1116 |
+
def chat():
|
| 1117 |
+
try:
|
| 1118 |
+
data = request.get_json(force=True, silent=True)
|
| 1119 |
+
if not data:
|
| 1120 |
+
return jsonify({"status": "error", "message": "Invalid JSON"}), 400
|
| 1121 |
+
|
| 1122 |
+
conversation_history = data.get("history", [])
|
| 1123 |
+
user_message = data.get("message", "").strip()
|
| 1124 |
+
lang = data.get("lang", "en")
|
| 1125 |
+
|
| 1126 |
+
if not user_message:
|
| 1127 |
+
return jsonify({"status": "error", "message": "Empty message"}), 400
|
| 1128 |
+
|
| 1129 |
+
# Build messages array for Groq
|
| 1130 |
+
messages = [{"role": "system", "content": DOCTOR_SYSTEM_PROMPT}]
|
| 1131 |
+
|
| 1132 |
+
# RAG context retrieve karo
|
| 1133 |
+
rag_context = "No clinical guidelines available."
|
| 1134 |
+
try:
|
| 1135 |
+
all_user_text = " ".join(
|
| 1136 |
+
t["content"] for t in conversation_history if t.get("role") == "user"
|
| 1137 |
+
) + " " + user_message
|
| 1138 |
+
vs = get_vectorstore()
|
| 1139 |
+
docs = vs.as_retriever(search_kwargs={"k": 4}).invoke(all_user_text)
|
| 1140 |
+
rag_context = "\n\n".join(d.page_content[:400] for d in docs)
|
| 1141 |
+
except Exception as e:
|
| 1142 |
+
print(f"RAG error in chat: {e}")
|
| 1143 |
+
|
| 1144 |
+
# RAG system context inject karo
|
| 1145 |
+
messages.append({
|
| 1146 |
+
"role": "system",
|
| 1147 |
+
"content": f"CLINICAL GUIDELINES FROM PDF (use these for recommendations):\n{rag_context}"
|
| 1148 |
+
})
|
| 1149 |
+
|
| 1150 |
+
# Pehle conversation history add karo
|
| 1151 |
+
for turn in conversation_history:
|
| 1152 |
+
if turn.get("role") in ("user", "assistant"):
|
| 1153 |
+
messages.append({"role": turn["role"], "content": turn["content"]})
|
| 1154 |
+
|
| 1155 |
+
# OpenMed NER β entities extract karo user message se
|
| 1156 |
+
openmed_context = ""
|
| 1157 |
+
if OPENMED_AVAILABLE and openmed_ner:
|
| 1158 |
+
try:
|
| 1159 |
+
entities = openmed_ner(user_message)
|
| 1160 |
+
detected = list(set([
|
| 1161 |
+
e['word'].strip() for e in entities
|
| 1162 |
+
if e['score'] > 0.7 and len(e['word'].strip()) > 2
|
| 1163 |
+
]))
|
| 1164 |
+
if detected:
|
| 1165 |
+
openmed_context = f"\n[OpenMed detected clinical entities: {', '.join(detected)}]"
|
| 1166 |
+
print(f"π¬ OpenMed entities: {detected}")
|
| 1167 |
+
except Exception as e:
|
| 1168 |
+
print(f"OpenMed NER error: {e}")
|
| 1169 |
+
|
| 1170 |
+
# Enriched user message aakhir mein append karo
|
| 1171 |
+
enriched_message = user_message + openmed_context
|
| 1172 |
+
messages.append({"role": "user", "content": enriched_message})
|
| 1173 |
+
|
| 1174 |
+
# Groq API call
|
| 1175 |
+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 1176 |
+
res = requests.post(
|
| 1177 |
+
"https://api.groq.com/openai/v1/chat/completions",
|
| 1178 |
+
headers={"Authorization": f"Bearer {GROQ_API_KEY}", "Content-Type": "application/json"},
|
| 1179 |
+
json={
|
| 1180 |
+
"model": "llama-3.1-8b-instant",
|
| 1181 |
+
"messages": messages,
|
| 1182 |
+
"max_tokens": 480,
|
| 1183 |
+
"temperature": 0.2,
|
| 1184 |
+
},
|
| 1185 |
+
timeout=30
|
| 1186 |
+
)
|
| 1187 |
+
|
| 1188 |
+
if res.status_code != 200:
|
| 1189 |
+
return jsonify({"status": "error", "message": f"LLM error: {res.text[:200]}"}), 500
|
| 1190 |
+
|
| 1191 |
+
reply = res.json()["choices"][0]["message"]["content"]
|
| 1192 |
+
reply = safety_filter(reply)
|
| 1193 |
+
|
| 1194 |
+
# TTS generate karo
|
| 1195 |
+
tts_url = None
|
| 1196 |
+
try:
|
| 1197 |
+
tts_text = reply
|
| 1198 |
+
import re as _re
|
| 1199 |
+
tts_text = _re.sub(r'(DIAGNOSIS|FIRST LINE|SECOND LINE|TESTS|AVOID|NOTE):\s*', '', tts_text)
|
| 1200 |
+
_, fname = generate_tts(tts_text.strip(), lang)
|
| 1201 |
+
tts_url = f"/static/{fname}"
|
| 1202 |
+
except Exception as e:
|
| 1203 |
+
print(f"TTS error in chat: {e}")
|
| 1204 |
+
|
| 1205 |
+
return jsonify({
|
| 1206 |
+
"status": "success",
|
| 1207 |
+
"reply": reply,
|
| 1208 |
+
"audio_url": tts_url
|
| 1209 |
+
})
|
| 1210 |
+
|
| 1211 |
+
except Exception as e:
|
| 1212 |
+
return jsonify({"status": "error", "message": str(e)})
|
| 1213 |
+
|
| 1214 |
+
|
| 1215 |
+
@app.route("/health", methods=["GET"])
|
| 1216 |
+
def health():
|
| 1217 |
+
return jsonify({"status": "healthy"})
|
| 1218 |
+
|
| 1219 |
+
|
| 1220 |
+
@app.route("/")
|
| 1221 |
+
def home():
|
| 1222 |
+
return render_template("chatbot.html")
|
| 1223 |
+
|
| 1224 |
+
|
| 1225 |
+
@app.route("/database")
|
| 1226 |
+
def database_page():
|
| 1227 |
+
return render_template("history.html")
|
| 1228 |
+
|
| 1229 |
+
|
| 1230 |
+
# ==============================
|
| 1231 |
+
# RUN
|
| 1232 |
+
# ==============================
|
| 1233 |
+
|
| 1234 |
+
if __name__ == '__main__':
|
| 1235 |
+
print("π Initializing Safecure AI...")
|
| 1236 |
+
init_db()
|
| 1237 |
+
get_vectorstore()
|
| 1238 |
+
print("β
Ready! Visit http://localhost:5000")
|
| 1239 |
+
port = int(os.environ.get('PORT', 5000))
|
| 1240 |
+
app.run(host='0.0.0.0', port=port,debug=False)
|
data/Antibiotic Guidelines (2020)_0.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1add4500e58dca4fd99838123f47a922a6a3feff11a7a607b9f27b46ef75ef24
|
| 3 |
+
size 526445
|
data/EAU-Guidelines-on-Urological-Infections-2024.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4f5be8b7509e9338818b639c712b118410ea7c67ee4fb37aaaa0f5287b9a006f
|
| 3 |
+
size 827122
|
data/General Fever + Viral URI.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e17581cf0829bea6dd4711dda95a727cc298a4ffbd16d37fa2bb89a7b4b0fb65
|
| 3 |
+
size 3103071
|
data/Gyan-Vahini-11-Safe-Drugs-Use-In-Pregnancy-Lactation-Nov-25.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:88e75853f479033ee319ce24001cd2fa32a8286ef2ebf129fd7dc3e0c845c8d8
|
| 3 |
+
size 3708230
|
data/Surgical Infection Society 2020 updated guidelines on the managem.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c39a3159d734ed0b0a9c3f48b38ee59a98e92db9e6759d9d7cf39382a3788b8f
|
| 3 |
+
size 311212
|
data/The_WHO_AWaRe_Access_Watch_Reserve_antib.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:37bfad4478fa0f8f25b9f5ee43c2b5e94703c53bd870837f7be8273fbd437714
|
| 3 |
+
size 48829332
|
data/Treatment_Guidelines_2019_Final.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:74e988ffe0603263ce131d043a187302d5845fbc4ff30aa74f9190ce99ff5016
|
| 3 |
+
size 4917213
|
data/WHO-Bacterial-Priority-2024.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5d883081dced7d404532d06e2a1a30c16c9528324c21c8b64f83230814a8e845
|
| 3 |
+
size 3560208
|
data/WHO-MHP-HPS-EML-2023.02-eng.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9617e1dee5f9772ec1154d4c4453c761fe53483c821525b9d7d9dee66a4850a1
|
| 3 |
+
size 926649
|
data/dengue.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b0ca16915451b163f47c7bbb2574423e7ecb873c7bb481e1a480e4d2d97d1067
|
| 3 |
+
size 6533834
|
data/executive_summary.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d79f88501db721412ed2282d1f7bcf3de30ed81f54f766188fd7dc2e4fe827f2
|
| 3 |
+
size 657667
|
data/influenza.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c845b39c8d6add9d5be54fb1ad7e7f1d806a3bb42d2f7465a147cd43dbab1f9b
|
| 3 |
+
size 3396859
|
data/malaria.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c5f43e97e43c1c79a344ed121d4daf8b98efc080a2dc56bab2c48a629f53b14e
|
| 3 |
+
size 2852047
|
data/practice-guidelines-for-the-diagnosis-and-management-of-skin-and-soft-tissue-infections-2014-update-by-the-infectious-diseases-society-of-america.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1a6b9ca7797fc9c81d68d993b1539201584b4d14d16d9ec7424d0386d8d5d4ad
|
| 3 |
+
size 1048509
|
data/typhoid.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d4f1f16ed8f847064953909afdad5048b735b64964d6f3a00db87a8f44201c51
|
| 3 |
+
size 230615
|
requirements.txt
ADDED
|
Binary file (3.66 kB). View file
|
|
|
templates/chatbot.html
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
templates/history.html
ADDED
|
@@ -0,0 +1,978 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Safecure AI | Patient Database</title>
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link
|
| 11 |
+
href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;14..32,400;14..32,500;14..32,600;14..32,700;14..32,800&display=swap"
|
| 12 |
+
rel="stylesheet">
|
| 13 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
| 14 |
+
<style>
|
| 15 |
+
* {
|
| 16 |
+
margin: 0;
|
| 17 |
+
padding: 0;
|
| 18 |
+
box-sizing: border-box;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
body {
|
| 22 |
+
font-family: 'Inter', sans-serif;
|
| 23 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 24 |
+
min-height: 100vh;
|
| 25 |
+
overflow-x: hidden;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.animated-bg {
|
| 29 |
+
position: fixed;
|
| 30 |
+
top: 0;
|
| 31 |
+
left: 0;
|
| 32 |
+
width: 100%;
|
| 33 |
+
height: 100%;
|
| 34 |
+
z-index: 0;
|
| 35 |
+
overflow: hidden;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.circles {
|
| 39 |
+
position: absolute;
|
| 40 |
+
top: 0;
|
| 41 |
+
left: 0;
|
| 42 |
+
width: 100%;
|
| 43 |
+
height: 100%;
|
| 44 |
+
overflow: hidden;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.circles li {
|
| 48 |
+
position: absolute;
|
| 49 |
+
display: block;
|
| 50 |
+
list-style: none;
|
| 51 |
+
width: 20px;
|
| 52 |
+
height: 20px;
|
| 53 |
+
background: rgba(255, 255, 255, 0.1);
|
| 54 |
+
animation: animate 25s linear infinite;
|
| 55 |
+
bottom: -150px;
|
| 56 |
+
border-radius: 50%;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
@keyframes animate {
|
| 60 |
+
0% {
|
| 61 |
+
transform: translateY(0) rotate(0deg);
|
| 62 |
+
opacity: 1;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
100% {
|
| 66 |
+
transform: translateY(-1000px) rotate(720deg);
|
| 67 |
+
opacity: 0;
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
.circles li:nth-child(1) {
|
| 72 |
+
left: 25%;
|
| 73 |
+
width: 80px;
|
| 74 |
+
height: 80px;
|
| 75 |
+
animation-delay: 0s;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.circles li:nth-child(2) {
|
| 79 |
+
left: 10%;
|
| 80 |
+
width: 20px;
|
| 81 |
+
height: 20px;
|
| 82 |
+
animation-delay: 2s;
|
| 83 |
+
animation-duration: 12s;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.circles li:nth-child(3) {
|
| 87 |
+
left: 70%;
|
| 88 |
+
width: 20px;
|
| 89 |
+
height: 20px;
|
| 90 |
+
animation-delay: 4s;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.circles li:nth-child(4) {
|
| 94 |
+
left: 40%;
|
| 95 |
+
width: 60px;
|
| 96 |
+
height: 60px;
|
| 97 |
+
animation-delay: 0s;
|
| 98 |
+
animation-duration: 18s;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
.circles li:nth-child(5) {
|
| 102 |
+
left: 65%;
|
| 103 |
+
width: 20px;
|
| 104 |
+
height: 20px;
|
| 105 |
+
animation-delay: 0s;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.circles li:nth-child(6) {
|
| 109 |
+
left: 75%;
|
| 110 |
+
width: 110px;
|
| 111 |
+
height: 110px;
|
| 112 |
+
animation-delay: 3s;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.circles li:nth-child(7) {
|
| 116 |
+
left: 35%;
|
| 117 |
+
width: 150px;
|
| 118 |
+
height: 150px;
|
| 119 |
+
animation-delay: 7s;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.circles li:nth-child(8) {
|
| 123 |
+
left: 50%;
|
| 124 |
+
width: 25px;
|
| 125 |
+
height: 25px;
|
| 126 |
+
animation-delay: 15s;
|
| 127 |
+
animation-duration: 45s;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
.circles li:nth-child(9) {
|
| 131 |
+
left: 20%;
|
| 132 |
+
width: 15px;
|
| 133 |
+
height: 15px;
|
| 134 |
+
animation-delay: 2s;
|
| 135 |
+
animation-duration: 35s;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.circles li:nth-child(10) {
|
| 139 |
+
left: 85%;
|
| 140 |
+
width: 150px;
|
| 141 |
+
height: 150px;
|
| 142 |
+
animation-delay: 0s;
|
| 143 |
+
animation-duration: 11s;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
.container {
|
| 147 |
+
position: relative;
|
| 148 |
+
z-index: 1;
|
| 149 |
+
max-width: 1300px;
|
| 150 |
+
margin: 0 auto;
|
| 151 |
+
padding: 20px;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.glass-card {
|
| 155 |
+
background: rgba(255, 255, 255, 0.97);
|
| 156 |
+
backdrop-filter: blur(10px);
|
| 157 |
+
border-radius: 30px;
|
| 158 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
| 159 |
+
overflow: hidden;
|
| 160 |
+
animation: slideIn 0.6s ease-out;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
@keyframes slideIn {
|
| 164 |
+
from {
|
| 165 |
+
opacity: 0;
|
| 166 |
+
transform: translateY(30px);
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
to {
|
| 170 |
+
opacity: 1;
|
| 171 |
+
transform: translateY(0);
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
.header {
|
| 176 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 177 |
+
padding: 25px 30px;
|
| 178 |
+
display: flex;
|
| 179 |
+
align-items: center;
|
| 180 |
+
justify-content: space-between;
|
| 181 |
+
position: relative;
|
| 182 |
+
overflow: hidden;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
.header::before {
|
| 186 |
+
content: '';
|
| 187 |
+
position: absolute;
|
| 188 |
+
top: -50%;
|
| 189 |
+
left: -50%;
|
| 190 |
+
width: 200%;
|
| 191 |
+
height: 200%;
|
| 192 |
+
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
|
| 193 |
+
animation: rotate 20s linear infinite;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
@keyframes rotate {
|
| 197 |
+
from {
|
| 198 |
+
transform: rotate(0deg);
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
to {
|
| 202 |
+
transform: rotate(360deg);
|
| 203 |
+
}
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
.header-left {
|
| 207 |
+
position: relative;
|
| 208 |
+
z-index: 1;
|
| 209 |
+
display: flex;
|
| 210 |
+
align-items: center;
|
| 211 |
+
gap: 15px;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
.header-left i {
|
| 215 |
+
font-size: 2.5rem;
|
| 216 |
+
color: white;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
.header-left h1 {
|
| 220 |
+
color: white;
|
| 221 |
+
font-size: 1.8rem;
|
| 222 |
+
font-weight: 800;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.header-left p {
|
| 226 |
+
color: rgba(255, 255, 255, 0.85);
|
| 227 |
+
font-size: 0.9rem;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
.back-btn {
|
| 231 |
+
position: relative;
|
| 232 |
+
z-index: 1;
|
| 233 |
+
background: rgba(255, 255, 255, 0.2);
|
| 234 |
+
border: 2px solid rgba(255, 255, 255, 0.5);
|
| 235 |
+
color: white;
|
| 236 |
+
padding: 10px 20px;
|
| 237 |
+
border-radius: 50px;
|
| 238 |
+
font-size: 0.9rem;
|
| 239 |
+
font-weight: 600;
|
| 240 |
+
cursor: pointer;
|
| 241 |
+
font-family: 'Inter', sans-serif;
|
| 242 |
+
transition: all 0.3s;
|
| 243 |
+
text-decoration: none;
|
| 244 |
+
display: flex;
|
| 245 |
+
align-items: center;
|
| 246 |
+
gap: 8px;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.back-btn:hover {
|
| 250 |
+
background: rgba(255, 255, 255, 0.35);
|
| 251 |
+
transform: translateY(-2px);
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
/* Stats Bar */
|
| 255 |
+
.stats-bar {
|
| 256 |
+
display: flex;
|
| 257 |
+
gap: 20px;
|
| 258 |
+
padding: 25px 30px;
|
| 259 |
+
background: linear-gradient(135deg, #f8f9ff, #f0f0ff);
|
| 260 |
+
border-bottom: 1px solid #e8e8f0;
|
| 261 |
+
flex-wrap: wrap;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.stat-card {
|
| 265 |
+
flex: 1;
|
| 266 |
+
min-width: 150px;
|
| 267 |
+
background: white;
|
| 268 |
+
border-radius: 15px;
|
| 269 |
+
padding: 15px 20px;
|
| 270 |
+
display: flex;
|
| 271 |
+
align-items: center;
|
| 272 |
+
gap: 12px;
|
| 273 |
+
box-shadow: 0 3px 15px rgba(102, 126, 234, 0.1);
|
| 274 |
+
border-left: 4px solid #667eea;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
.stat-card i {
|
| 278 |
+
font-size: 1.8rem;
|
| 279 |
+
color: #667eea;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.stat-card .stat-num {
|
| 283 |
+
font-size: 1.5rem;
|
| 284 |
+
font-weight: 800;
|
| 285 |
+
color: #333;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
.stat-card .stat-label {
|
| 289 |
+
font-size: 0.75rem;
|
| 290 |
+
color: #888;
|
| 291 |
+
font-weight: 500;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
/* Search & Filter */
|
| 295 |
+
.controls {
|
| 296 |
+
padding: 20px 30px;
|
| 297 |
+
display: flex;
|
| 298 |
+
gap: 15px;
|
| 299 |
+
flex-wrap: wrap;
|
| 300 |
+
border-bottom: 1px solid #f0f0f0;
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
.search-box {
|
| 304 |
+
flex: 1;
|
| 305 |
+
min-width: 200px;
|
| 306 |
+
position: relative;
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
.search-box i {
|
| 310 |
+
position: absolute;
|
| 311 |
+
left: 14px;
|
| 312 |
+
top: 50%;
|
| 313 |
+
transform: translateY(-50%);
|
| 314 |
+
color: #999;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
.search-box input {
|
| 318 |
+
width: 100%;
|
| 319 |
+
padding: 11px 15px 11px 40px;
|
| 320 |
+
border: 2px solid #e8e8f0;
|
| 321 |
+
border-radius: 12px;
|
| 322 |
+
font-family: 'Inter', sans-serif;
|
| 323 |
+
font-size: 0.9rem;
|
| 324 |
+
transition: all 0.3s;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
.search-box input:focus {
|
| 328 |
+
outline: none;
|
| 329 |
+
border-color: #667eea;
|
| 330 |
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
.filter-select {
|
| 334 |
+
padding: 11px 15px;
|
| 335 |
+
border: 2px solid #e8e8f0;
|
| 336 |
+
border-radius: 12px;
|
| 337 |
+
font-family: 'Inter', sans-serif;
|
| 338 |
+
font-size: 0.9rem;
|
| 339 |
+
color: #555;
|
| 340 |
+
background: white;
|
| 341 |
+
cursor: pointer;
|
| 342 |
+
transition: all 0.3s;
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
.filter-select:focus {
|
| 346 |
+
outline: none;
|
| 347 |
+
border-color: #667eea;
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
.refresh-btn {
|
| 351 |
+
padding: 11px 20px;
|
| 352 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 353 |
+
color: white;
|
| 354 |
+
border: none;
|
| 355 |
+
border-radius: 12px;
|
| 356 |
+
font-family: 'Inter', sans-serif;
|
| 357 |
+
font-size: 0.9rem;
|
| 358 |
+
font-weight: 600;
|
| 359 |
+
cursor: pointer;
|
| 360 |
+
display: flex;
|
| 361 |
+
align-items: center;
|
| 362 |
+
gap: 8px;
|
| 363 |
+
transition: all 0.3s;
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
.refresh-btn:hover {
|
| 367 |
+
transform: translateY(-2px);
|
| 368 |
+
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
/* Table */
|
| 372 |
+
.table-container {
|
| 373 |
+
padding: 0 30px 30px;
|
| 374 |
+
overflow-x: auto;
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
table {
|
| 378 |
+
width: 100%;
|
| 379 |
+
border-collapse: collapse;
|
| 380 |
+
margin-top: 15px;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
thead tr {
|
| 384 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 385 |
+
color: white;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
thead th {
|
| 389 |
+
padding: 14px 16px;
|
| 390 |
+
text-align: left;
|
| 391 |
+
font-size: 0.85rem;
|
| 392 |
+
font-weight: 600;
|
| 393 |
+
letter-spacing: 0.5px;
|
| 394 |
+
white-space: nowrap;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
thead th:first-child {
|
| 398 |
+
border-radius: 12px 0 0 0;
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
thead th:last-child {
|
| 402 |
+
border-radius: 0 12px 0 0;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
tbody tr {
|
| 406 |
+
border-bottom: 1px solid #f0f0f0;
|
| 407 |
+
transition: all 0.2s;
|
| 408 |
+
animation: rowSlide 0.4s ease-out;
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
@keyframes rowSlide {
|
| 412 |
+
from {
|
| 413 |
+
opacity: 0;
|
| 414 |
+
transform: translateX(-10px);
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
to {
|
| 418 |
+
opacity: 1;
|
| 419 |
+
transform: translateX(0);
|
| 420 |
+
}
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
tbody tr:hover {
|
| 424 |
+
background: #f8f8ff;
|
| 425 |
+
transform: scale(1.002);
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
tbody td {
|
| 429 |
+
padding: 14px 16px;
|
| 430 |
+
font-size: 0.85rem;
|
| 431 |
+
color: #444;
|
| 432 |
+
vertical-align: top;
|
| 433 |
+
max-width: 220px;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
.patient-id-cell {
|
| 437 |
+
font-weight: 700;
|
| 438 |
+
color: #667eea;
|
| 439 |
+
font-family: monospace;
|
| 440 |
+
font-size: 0.9rem;
|
| 441 |
+
white-space: nowrap;
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
.antibiotic-badge {
|
| 445 |
+
display: inline-block;
|
| 446 |
+
padding: 4px 12px;
|
| 447 |
+
border-radius: 20px;
|
| 448 |
+
font-size: 0.75rem;
|
| 449 |
+
font-weight: 700;
|
| 450 |
+
letter-spacing: 0.5px;
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
.badge-yes {
|
| 454 |
+
background: #ffe0e0;
|
| 455 |
+
color: #d32f2f;
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
.badge-no {
|
| 459 |
+
background: #e0f5e9;
|
| 460 |
+
color: #2e7d32;
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
.truncate {
|
| 464 |
+
max-width: 200px;
|
| 465 |
+
overflow: hidden;
|
| 466 |
+
text-overflow: ellipsis;
|
| 467 |
+
white-space: nowrap;
|
| 468 |
+
cursor: pointer;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
.truncate:hover {
|
| 472 |
+
white-space: normal;
|
| 473 |
+
overflow: visible;
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.timestamp {
|
| 477 |
+
color: #999;
|
| 478 |
+
font-size: 0.78rem;
|
| 479 |
+
white-space: nowrap;
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
/* Modal */
|
| 483 |
+
.modal-overlay {
|
| 484 |
+
position: fixed;
|
| 485 |
+
inset: 0;
|
| 486 |
+
background: rgba(0, 0, 0, 0.6);
|
| 487 |
+
backdrop-filter: blur(8px);
|
| 488 |
+
z-index: 2000;
|
| 489 |
+
display: none;
|
| 490 |
+
justify-content: center;
|
| 491 |
+
align-items: center;
|
| 492 |
+
padding: 20px;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
.modal-overlay.active {
|
| 496 |
+
display: flex;
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
.modal {
|
| 500 |
+
background: white;
|
| 501 |
+
border-radius: 20px;
|
| 502 |
+
max-width: 650px;
|
| 503 |
+
width: 100%;
|
| 504 |
+
max-height: 85vh;
|
| 505 |
+
overflow-y: auto;
|
| 506 |
+
animation: modalIn 0.3s ease-out;
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
@keyframes modalIn {
|
| 510 |
+
from {
|
| 511 |
+
opacity: 0;
|
| 512 |
+
transform: scale(0.9);
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
to {
|
| 516 |
+
opacity: 1;
|
| 517 |
+
transform: scale(1);
|
| 518 |
+
}
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
.modal-header {
|
| 522 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 523 |
+
padding: 20px 25px;
|
| 524 |
+
border-radius: 20px 20px 0 0;
|
| 525 |
+
display: flex;
|
| 526 |
+
align-items: center;
|
| 527 |
+
justify-content: space-between;
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
.modal-header h3 {
|
| 531 |
+
color: white;
|
| 532 |
+
font-size: 1.1rem;
|
| 533 |
+
display: flex;
|
| 534 |
+
align-items: center;
|
| 535 |
+
gap: 10px;
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
.modal-close {
|
| 539 |
+
background: rgba(255, 255, 255, 0.2);
|
| 540 |
+
border: none;
|
| 541 |
+
color: white;
|
| 542 |
+
width: 32px;
|
| 543 |
+
height: 32px;
|
| 544 |
+
border-radius: 50%;
|
| 545 |
+
font-size: 1rem;
|
| 546 |
+
cursor: pointer;
|
| 547 |
+
transition: all 0.2s;
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
.modal-close:hover {
|
| 551 |
+
background: rgba(255, 255, 255, 0.4);
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
.modal-body {
|
| 555 |
+
padding: 25px;
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
.modal-section {
|
| 559 |
+
background: #f8f9ff;
|
| 560 |
+
border-radius: 12px;
|
| 561 |
+
padding: 15px;
|
| 562 |
+
margin-bottom: 12px;
|
| 563 |
+
border-left: 4px solid #667eea;
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
.modal-section h4 {
|
| 567 |
+
color: #667eea;
|
| 568 |
+
font-size: 0.85rem;
|
| 569 |
+
font-weight: 700;
|
| 570 |
+
margin-bottom: 6px;
|
| 571 |
+
text-transform: uppercase;
|
| 572 |
+
letter-spacing: 0.5px;
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
.modal-section p {
|
| 576 |
+
font-size: 0.9rem;
|
| 577 |
+
color: #444;
|
| 578 |
+
line-height: 1.5;
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
/* Empty state */
|
| 582 |
+
.empty-state {
|
| 583 |
+
text-align: center;
|
| 584 |
+
padding: 60px 20px;
|
| 585 |
+
color: #aaa;
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
.empty-state i {
|
| 589 |
+
font-size: 4rem;
|
| 590 |
+
margin-bottom: 15px;
|
| 591 |
+
display: block;
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
.empty-state p {
|
| 595 |
+
font-size: 1rem;
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
+
/* Loading */
|
| 599 |
+
.loading {
|
| 600 |
+
text-align: center;
|
| 601 |
+
padding: 40px;
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
+
.loading-spinner {
|
| 605 |
+
width: 40px;
|
| 606 |
+
height: 40px;
|
| 607 |
+
border: 4px solid #f0f0ff;
|
| 608 |
+
border-top: 4px solid #667eea;
|
| 609 |
+
border-radius: 50%;
|
| 610 |
+
animation: spin 0.8s linear infinite;
|
| 611 |
+
margin: 0 auto 15px;
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
@keyframes spin {
|
| 615 |
+
to {
|
| 616 |
+
transform: rotate(360deg);
|
| 617 |
+
}
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
@media (max-width: 768px) {
|
| 621 |
+
.stats-bar {
|
| 622 |
+
gap: 10px;
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
.controls {
|
| 626 |
+
flex-direction: column;
|
| 627 |
+
}
|
| 628 |
+
|
| 629 |
+
.table-container {
|
| 630 |
+
padding: 0 15px 20px;
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
+
thead th,
|
| 634 |
+
tbody td {
|
| 635 |
+
padding: 10px 12px;
|
| 636 |
+
}
|
| 637 |
+
}
|
| 638 |
+
</style>
|
| 639 |
+
</head>
|
| 640 |
+
|
| 641 |
+
<body>
|
| 642 |
+
|
| 643 |
+
<div class="animated-bg">
|
| 644 |
+
<ul class="circles">
|
| 645 |
+
<li></li>
|
| 646 |
+
<li></li>
|
| 647 |
+
<li></li>
|
| 648 |
+
<li></li>
|
| 649 |
+
<li></li>
|
| 650 |
+
<li></li>
|
| 651 |
+
<li></li>
|
| 652 |
+
<li></li>
|
| 653 |
+
<li></li>
|
| 654 |
+
<li></li>
|
| 655 |
+
</ul>
|
| 656 |
+
</div>
|
| 657 |
+
|
| 658 |
+
<div class="container">
|
| 659 |
+
<div class="glass-card">
|
| 660 |
+
|
| 661 |
+
<!-- Header -->
|
| 662 |
+
<div class="header">
|
| 663 |
+
<div class="header-left">
|
| 664 |
+
<i class="bi bi-database-fill-gear"></i>
|
| 665 |
+
<div>
|
| 666 |
+
<h1>Patient Database</h1>
|
| 667 |
+
<p>Safecure AI β Clinical Records</p>
|
| 668 |
+
</div>
|
| 669 |
+
</div>
|
| 670 |
+
<a href="/" class="back-btn">
|
| 671 |
+
<i class="bi bi-arrow-left"></i> Back to Analysis
|
| 672 |
+
</a>
|
| 673 |
+
</div>
|
| 674 |
+
|
| 675 |
+
<!-- Stats -->
|
| 676 |
+
<div class="stats-bar" id="statsBar">
|
| 677 |
+
<div class="stat-card">
|
| 678 |
+
<i class="bi bi-people-fill"></i>
|
| 679 |
+
<div>
|
| 680 |
+
<div class="stat-num" id="totalCount">β</div>
|
| 681 |
+
<div class="stat-label">Total Patients</div>
|
| 682 |
+
</div>
|
| 683 |
+
</div>
|
| 684 |
+
<div class="stat-card" style="border-left-color:#d32f2f">
|
| 685 |
+
<i class="bi bi-capsule-pill" style="color:#d32f2f"></i>
|
| 686 |
+
<div>
|
| 687 |
+
<div class="stat-num" id="antibioticYes">β</div>
|
| 688 |
+
<div class="stat-label">Antibiotic Prescribed</div>
|
| 689 |
+
</div>
|
| 690 |
+
</div>
|
| 691 |
+
<div class="stat-card" style="border-left-color:#2e7d32">
|
| 692 |
+
<i class="bi bi-shield-check" style="color:#2e7d32"></i>
|
| 693 |
+
<div>
|
| 694 |
+
<div class="stat-num" id="antibioticNo">β</div>
|
| 695 |
+
<div class="stat-label">Antibiotic Not Required</div>
|
| 696 |
+
</div>
|
| 697 |
+
</div>
|
| 698 |
+
<div class="stat-card" style="border-left-color:#f57c00">
|
| 699 |
+
<i class="bi bi-clock-history" style="color:#f57c00"></i>
|
| 700 |
+
<div>
|
| 701 |
+
<div class="stat-num" id="todayCount">β</div>
|
| 702 |
+
<div class="stat-label">Today's Cases</div>
|
| 703 |
+
</div>
|
| 704 |
+
</div>
|
| 705 |
+
</div>
|
| 706 |
+
|
| 707 |
+
<!-- Controls -->
|
| 708 |
+
<div class="controls">
|
| 709 |
+
<div class="search-box">
|
| 710 |
+
<i class="bi bi-search"></i>
|
| 711 |
+
<input type="text" id="searchInput" placeholder="Search by Patient ID, symptoms, diagnosis..."
|
| 712 |
+
oninput="filterTable()">
|
| 713 |
+
</div>
|
| 714 |
+
<select class="filter-select" id="antibioticFilter" onchange="filterTable()">
|
| 715 |
+
<option value="">All Cases</option>
|
| 716 |
+
<option value="yes">Antibiotic: YES</option>
|
| 717 |
+
<option value="no">Antibiotic: NO</option>
|
| 718 |
+
</select>
|
| 719 |
+
<button class="refresh-btn" onclick="loadPatients()">
|
| 720 |
+
<i class="bi bi-arrow-clockwise"></i> Refresh
|
| 721 |
+
</button>
|
| 722 |
+
<button class="refresh-btn" style="background:linear-gradient(135deg,#28a745,#20c997);"
|
| 723 |
+
onclick="downloadCSV()">
|
| 724 |
+
<i class="bi bi-filetype-csv"></i> CSV
|
| 725 |
+
</button>
|
| 726 |
+
<button class="refresh-btn" style="background:linear-gradient(135deg,#217346,#1e6e3e);"
|
| 727 |
+
onclick="downloadExcel()">
|
| 728 |
+
<i class="bi bi-file-earmark-excel-fill"></i> Excel
|
| 729 |
+
</button>
|
| 730 |
+
</div>
|
| 731 |
+
|
| 732 |
+
<!-- Table -->
|
| 733 |
+
<div class="table-container">
|
| 734 |
+
<div id="loadingState" class="loading">
|
| 735 |
+
<div class="loading-spinner"></div>
|
| 736 |
+
<p style="color:#999">Loading patient records...</p>
|
| 737 |
+
</div>
|
| 738 |
+
|
| 739 |
+
<table id="patientsTable" style="display:none">
|
| 740 |
+
<thead>
|
| 741 |
+
<tr>
|
| 742 |
+
<th><i class="bi bi-person-badge"></i> Patient ID</th>
|
| 743 |
+
<th><i class="bi bi-clock"></i> Timestamp</th>
|
| 744 |
+
<th><i class="bi bi-activity"></i> Symptoms</th>
|
| 745 |
+
<th><i class="bi bi-clipboard2-pulse"></i> Diagnosis</th>
|
| 746 |
+
<th><i class="bi bi-capsule"></i> Antibiotics</th>
|
| 747 |
+
<th><i class="bi bi-star"></i> First-Line</th>
|
| 748 |
+
<th><i class="bi bi-eye"></i> Details</th>
|
| 749 |
+
</tr>
|
| 750 |
+
</thead>
|
| 751 |
+
<tbody id="tableBody"></tbody>
|
| 752 |
+
</table>
|
| 753 |
+
|
| 754 |
+
<div id="emptyState" class="empty-state" style="display:none">
|
| 755 |
+
<i class="bi bi-database-x"></i>
|
| 756 |
+
<p>No patient records found.</p>
|
| 757 |
+
</div>
|
| 758 |
+
</div>
|
| 759 |
+
|
| 760 |
+
</div>
|
| 761 |
+
</div>
|
| 762 |
+
|
| 763 |
+
<!-- Modal -->
|
| 764 |
+
<div class="modal-overlay" id="modalOverlay" onclick="closeModal(event)">
|
| 765 |
+
<div class="modal" id="modal">
|
| 766 |
+
<div class="modal-header">
|
| 767 |
+
<h3><i class="bi bi-file-medical-fill"></i> <span id="modalTitle">Patient Details</span></h3>
|
| 768 |
+
<button class="modal-close" onclick="closeModalDirect()">β</button>
|
| 769 |
+
</div>
|
| 770 |
+
<div class="modal-body" id="modalBody"></div>
|
| 771 |
+
</div>
|
| 772 |
+
</div>
|
| 773 |
+
|
| 774 |
+
<script>
|
| 775 |
+
let allPatients = [];
|
| 776 |
+
|
| 777 |
+
async function loadPatients() {
|
| 778 |
+
document.getElementById('loadingState').style.display = 'block';
|
| 779 |
+
document.getElementById('patientsTable').style.display = 'none';
|
| 780 |
+
document.getElementById('emptyState').style.display = 'none';
|
| 781 |
+
|
| 782 |
+
try {
|
| 783 |
+
const res = await fetch('/patients');
|
| 784 |
+
const data = await res.json();
|
| 785 |
+
|
| 786 |
+
if (data.status === 'success') {
|
| 787 |
+
allPatients = data.patients;
|
| 788 |
+
updateStats(allPatients);
|
| 789 |
+
renderTable(allPatients);
|
| 790 |
+
}
|
| 791 |
+
} catch (e) {
|
| 792 |
+
document.getElementById('loadingState').innerHTML = `<p style="color:#e74c3c">β Failed to load. Is the server running?</p>`;
|
| 793 |
+
}
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
function updateStats(patients) {
|
| 797 |
+
const today = new Date().toISOString().split('T')[0];
|
| 798 |
+
const yesCount = patients.filter(p => p.antibiotic_necessity && p.antibiotic_necessity.toUpperCase().startsWith('YES')).length;
|
| 799 |
+
const noCount = patients.filter(p => p.antibiotic_necessity && p.antibiotic_necessity.toUpperCase().startsWith('NO')).length;
|
| 800 |
+
const todayCount = patients.filter(p => p.timestamp && p.timestamp.startsWith(today)).length;
|
| 801 |
+
|
| 802 |
+
document.getElementById('totalCount').textContent = patients.length;
|
| 803 |
+
document.getElementById('antibioticYes').textContent = yesCount;
|
| 804 |
+
document.getElementById('antibioticNo').textContent = noCount;
|
| 805 |
+
document.getElementById('todayCount').textContent = todayCount;
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
function renderTable(patients) {
|
| 809 |
+
document.getElementById('loadingState').style.display = 'none';
|
| 810 |
+
|
| 811 |
+
if (!patients.length) {
|
| 812 |
+
document.getElementById('emptyState').style.display = 'block';
|
| 813 |
+
return;
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
document.getElementById('patientsTable').style.display = 'table';
|
| 817 |
+
const tbody = document.getElementById('tableBody');
|
| 818 |
+
tbody.innerHTML = '';
|
| 819 |
+
|
| 820 |
+
patients.forEach(p => {
|
| 821 |
+
const antiYes = p.antibiotic_necessity && p.antibiotic_necessity.toUpperCase().startsWith('YES');
|
| 822 |
+
const badge = antiYes
|
| 823 |
+
? `<span class="antibiotic-badge badge-yes">YES</span>`
|
| 824 |
+
: `<span class="antibiotic-badge badge-no">NO</span>`;
|
| 825 |
+
|
| 826 |
+
const diagnosis = (p.assessment || '').split('.')[0];
|
| 827 |
+
const firstLine = (p.first_line || 'Not required').split(' ')[0] + (p.first_line && p.first_line.length > 20 ? '...' : '');
|
| 828 |
+
|
| 829 |
+
const row = document.createElement('tr');
|
| 830 |
+
const idx = allPatients.indexOf(p);
|
| 831 |
+
row.innerHTML = `
|
| 832 |
+
<td class="patient-id-cell">${p.id}</td>
|
| 833 |
+
<td class="timestamp">${p.timestamp}</td>
|
| 834 |
+
<td><div class="truncate" title="${escHtml(p.condition)}">${escHtml(p.condition || 'β')}</div></td>
|
| 835 |
+
<td><div class="truncate" title="${escHtml(p.assessment)}">${escHtml(diagnosis || 'β')}</div></td>
|
| 836 |
+
<td>${badge}</td>
|
| 837 |
+
<td><div class="truncate" title="${escHtml(p.first_line)}">${escHtml(firstLine || 'β')}</div></td>
|
| 838 |
+
<td>
|
| 839 |
+
<button onclick='showModal(${idx})' style="
|
| 840 |
+
background: linear-gradient(135deg,#667eea,#764ba2);
|
| 841 |
+
color:white; border:none; border-radius:8px;
|
| 842 |
+
padding:6px 14px; cursor:pointer; font-size:0.8rem;
|
| 843 |
+
font-family:Inter,sans-serif; font-weight:600; transition:all 0.2s;">
|
| 844 |
+
<i class="bi bi-eye"></i> View
|
| 845 |
+
</button>
|
| 846 |
+
</td>
|
| 847 |
+
`;
|
| 848 |
+
tbody.appendChild(row);
|
| 849 |
+
});
|
| 850 |
+
}
|
| 851 |
+
|
| 852 |
+
function filterTable() {
|
| 853 |
+
const search = document.getElementById('searchInput').value.toLowerCase();
|
| 854 |
+
const abFilter = document.getElementById('antibioticFilter').value.toLowerCase();
|
| 855 |
+
|
| 856 |
+
const filtered = allPatients.filter(p => {
|
| 857 |
+
const matchSearch = !search ||
|
| 858 |
+
(p.id || '').toLowerCase().includes(search) ||
|
| 859 |
+
(p.condition || '').toLowerCase().includes(search) ||
|
| 860 |
+
(p.assessment || '').toLowerCase().includes(search) ||
|
| 861 |
+
(p.allergies || '').toLowerCase().includes(search);
|
| 862 |
+
|
| 863 |
+
const matchAb = !abFilter ||
|
| 864 |
+
(abFilter === 'yes' && p.antibiotic_necessity && p.antibiotic_necessity.toUpperCase().startsWith('YES')) ||
|
| 865 |
+
(abFilter === 'no' && p.antibiotic_necessity && p.antibiotic_necessity.toUpperCase().startsWith('NO'));
|
| 866 |
+
|
| 867 |
+
return matchSearch && matchAb;
|
| 868 |
+
});
|
| 869 |
+
|
| 870 |
+
renderTable(filtered);
|
| 871 |
+
}
|
| 872 |
+
|
| 873 |
+
function showModal(idx) {
|
| 874 |
+
const p = allPatients[idx];
|
| 875 |
+
document.getElementById('modalTitle').textContent = `Patient ${p.id}`;
|
| 876 |
+
document.getElementById('modalBody').innerHTML = `
|
| 877 |
+
<div class="modal-section">
|
| 878 |
+
<h4>πͺͺ Patient Info</h4>
|
| 879 |
+
<p><strong>ID:</strong> ${p.id} | <strong>Date:</strong> ${p.timestamp}</p>
|
| 880 |
+
</div>
|
| 881 |
+
<div class="modal-section">
|
| 882 |
+
<h4>π©Ί Symptoms / Condition</h4>
|
| 883 |
+
<p>${escHtml(p.condition || 'β')}</p>
|
| 884 |
+
</div>
|
| 885 |
+
<div class="modal-section">
|
| 886 |
+
<h4>β οΈ Allergies</h4>
|
| 887 |
+
<p>${escHtml(p.allergies || 'None reported')}</p>
|
| 888 |
+
</div>
|
| 889 |
+
<div class="modal-section">
|
| 890 |
+
<h4>π Current Medications</h4>
|
| 891 |
+
<p>${escHtml(p.medications || 'None reported')}</p>
|
| 892 |
+
</div>
|
| 893 |
+
<div class="modal-section">
|
| 894 |
+
<h4>π¬ Clinical Assessment</h4>
|
| 895 |
+
<p>${escHtml(p.assessment || 'β')}</p>
|
| 896 |
+
</div>
|
| 897 |
+
<div class="modal-section" style="border-left-color:${p.antibiotic_necessity && p.antibiotic_necessity.toUpperCase().startsWith('YES') ? '#d32f2f' : '#2e7d32'}">
|
| 898 |
+
<h4>π Antibiotic Necessity</h4>
|
| 899 |
+
<p>${escHtml(p.antibiotic_necessity || 'β')}</p>
|
| 900 |
+
</div>
|
| 901 |
+
<div class="modal-section">
|
| 902 |
+
<h4>β First-Line Therapy</h4>
|
| 903 |
+
<p>${escHtml(p.first_line || 'Not required')}</p>
|
| 904 |
+
</div>
|
| 905 |
+
<div class="modal-section">
|
| 906 |
+
<h4>π Second-Line Alternatives</h4>
|
| 907 |
+
<p>${escHtml(p.second_line || 'Not required')}</p>
|
| 908 |
+
</div>
|
| 909 |
+
<div class="modal-section">
|
| 910 |
+
<h4>π« Contraindications & Precautions</h4>
|
| 911 |
+
<p>${escHtml(p.contraindications || 'β')}</p>
|
| 912 |
+
</div>
|
| 913 |
+
`;
|
| 914 |
+
document.getElementById('modalOverlay').classList.add('active');
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
function closeModal(e) {
|
| 918 |
+
if (e.target === document.getElementById('modalOverlay')) closeModalDirect();
|
| 919 |
+
}
|
| 920 |
+
|
| 921 |
+
function closeModalDirect() {
|
| 922 |
+
document.getElementById('modalOverlay').classList.remove('active');
|
| 923 |
+
}
|
| 924 |
+
|
| 925 |
+
function downloadCSV() {
|
| 926 |
+
if (!allPatients.length) return alert('No data to download.');
|
| 927 |
+
const headers = ['Patient ID', 'Timestamp', 'Symptoms', 'Allergies', 'Medications', 'Diagnosis', 'Antibiotics', 'First-Line', 'Second-Line', 'Contraindications'];
|
| 928 |
+
const rows = allPatients.map(p => [
|
| 929 |
+
p.id, p.timestamp, p.condition, p.allergies, p.medications,
|
| 930 |
+
p.assessment, p.antibiotic_necessity, p.first_line, p.second_line, p.contraindications
|
| 931 |
+
].map(v => `"${(v || '').replace(/"/g, '""')}"`));
|
| 932 |
+
|
| 933 |
+
const csv = [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
|
| 934 |
+
const blob = new Blob([csv], { type: 'text/csv' });
|
| 935 |
+
const a = document.createElement('a');
|
| 936 |
+
a.href = URL.createObjectURL(blob);
|
| 937 |
+
a.download = `safecure_patients_${new Date().toISOString().split('T')[0]}.csv`;
|
| 938 |
+
a.click();
|
| 939 |
+
}
|
| 940 |
+
|
| 941 |
+
function downloadExcel() {
|
| 942 |
+
if (!allPatients.length) return alert('No data to download.');
|
| 943 |
+
const headers = ['Patient ID', 'Timestamp', 'Symptoms', 'Allergies', 'Medications', 'Diagnosis', 'Antibiotics', 'First-Line', 'Second-Line', 'Contraindications'];
|
| 944 |
+
const rows = allPatients.map(p => [
|
| 945 |
+
p.id, p.timestamp, p.condition, p.allergies, p.medications,
|
| 946 |
+
p.assessment, p.antibiotic_necessity, p.first_line, p.second_line, p.contraindications
|
| 947 |
+
]);
|
| 948 |
+
|
| 949 |
+
// Build XML-based Excel (xls)
|
| 950 |
+
let xml = `<?xml version="1.0"?>
|
| 951 |
+
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
| 952 |
+
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
|
| 953 |
+
<Worksheet ss:Name="Patients"><Table>`;
|
| 954 |
+
|
| 955 |
+
xml += '<Row>' + headers.map(h => `<Cell><Data ss:Type="String">${h}</Data></Cell>`).join('') + '</Row>';
|
| 956 |
+
rows.forEach(row => {
|
| 957 |
+
xml += '<Row>' + row.map(v => `<Cell><Data ss:Type="String">${(v || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</Data></Cell>`).join('') + '</Row>';
|
| 958 |
+
});
|
| 959 |
+
|
| 960 |
+
xml += '</Table></Worksheet></Workbook>';
|
| 961 |
+
const blob = new Blob([xml], { type: 'application/vnd.ms-excel' });
|
| 962 |
+
const a = document.createElement('a');
|
| 963 |
+
a.href = URL.createObjectURL(blob);
|
| 964 |
+
a.download = `safecure_patients_${new Date().toISOString().split('T')[0]}.xls`;
|
| 965 |
+
a.click();
|
| 966 |
+
}
|
| 967 |
+
|
| 968 |
+
function escHtml(str) {
|
| 969 |
+
if (!str) return '';
|
| 970 |
+
return str.replace(/[&<>"']/g, m => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m]));
|
| 971 |
+
}
|
| 972 |
+
|
| 973 |
+
// Load on start
|
| 974 |
+
loadPatients();
|
| 975 |
+
</script>
|
| 976 |
+
</body>
|
| 977 |
+
|
| 978 |
+
</html>
|