hassan773 commited on
Commit
4dab95a
Β·
verified Β·
1 Parent(s): 039b2b9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -104
app.py CHANGED
@@ -11,80 +11,93 @@ from PIL import Image
11
  from groq import Groq
12
  from fpdf import FPDF
13
  from datetime import datetime
 
14
 
15
- # --- 1. CORE SYSTEM CONFIG ---
16
  GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
17
  GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
18
 
19
  if not GROQ_API_KEY or not GOOGLE_API_KEY:
20
- st.error("⚠️ API Keys Missing! Please add them to Space Secrets.")
21
  st.stop()
22
 
23
  genai.configure(api_key=GOOGLE_API_KEY)
24
  vision_model = genai.GenerativeModel('gemini-2.0-flash')
25
 
26
- st.set_page_config(page_title="MediScan Pro | Hassan Naseer", layout="wide", page_icon="πŸ₯")
27
 
28
- # --- 2. PERSISTENT DATABASE ENGINE ---
29
- # Shared file for both Doctor and Patient portals
30
- RECORDS_FILE = "clinical_records.csv"
31
 
32
- def save_clinical_record(doctor, room, chat_history):
33
- # Convert chat history into a readable summary string
34
- chat_summary = " | ".join([f"{m['role'].upper()}: {m['content'][:50]}..." for m in chat_history])
35
- df = pd.read_csv(RECORDS_FILE) if os.path.exists(RECORDS_FILE) else pd.DataFrame(columns=["Timestamp", "Doctor", "RoomID", "Summary"])
36
-
37
- new_entry = pd.DataFrame([{
38
- "Timestamp": datetime.now().strftime("%Y-%m-%d %H:%M"),
39
- "Doctor": doctor,
40
- "RoomID": room,
41
- "Summary": chat_summary
42
- }])
43
- pd.concat([df, new_entry]).to_csv(RECORDS_FILE, index=False)
44
 
 
 
 
 
 
 
 
 
45
  @st.cache_resource
46
- def get_chroma_db():
47
  client = chromadb.PersistentClient(path="./med_vector_db")
48
  emb_fn = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2")
49
- # Medical Knowledge Base
50
- main_col = client.get_or_create_collection(name="nutech_final_prod", embedding_function=emb_fn)
51
- # Live Signaling for Video Calls
52
- signal_col = client.get_or_create_collection(name="call_signals_prod", embedding_function=emb_fn)
53
- return main_col, signal_col
54
-
55
- main_col, signal_col = get_chroma_db()
56
-
57
- # --- 3. SESSION STATE ---
58
- if "auth" not in st.session_state: st.session_state.auth = False
59
- if "msgs" not in st.session_state: st.session_state.msgs = []
60
- if "v_hash" not in st.session_state: st.session_state.v_hash = None
61
-
62
- # --- 4. AUTHENTICATION GATE ---
63
- if not st.session_state.auth:
64
- st.markdown("<h1 style='text-align: center;'>πŸ₯ MediScan Enterprise</h1>", unsafe_allow_html=True)
65
- c1, c2, c3 = st.columns([1, 1.5, 1])
66
- with c2:
67
- st.markdown('<div style="background:white; padding:20px; border-radius:10px; border:1px solid #ddd;">', unsafe_allow_html=True)
68
- role = st.selectbox("I am a:", ["Patient", "Doctor"])
69
- pwd = st.text_input("Access Key", type="password")
70
- if st.button("Enter Portal"):
71
- if (role == "Patient" and pwd == "p123") or (role == "Doctor" and pwd == "d123"):
72
- st.session_state.auth = True; st.session_state.role = role; st.rerun()
73
- else: st.error("Incorrect Password.")
74
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
75
  st.stop()
76
 
77
- # --- 5. NAVIGATION ---
78
  with st.sidebar:
79
- st.title(f"πŸ‘€ {st.session_state.role} Portal")
80
- if st.button("Logout"): st.session_state.auth = False; st.rerun()
 
 
81
  st.divider()
82
  if st.session_state.role == "Patient":
83
- nav = st.radio("Services", ["πŸ’¬ AI Chat & Docs", "πŸ§ͺ Diagnostics", "πŸ“ž Contact Doctor", "πŸ“Έ Vision Scanner", "πŸ“œ My Records"])
84
  else:
85
- nav = st.radio("Doctor Desk", ["πŸ–₯️ Live Tele-Consult", "πŸ“‹ Global Patient Logs"])
86
  st.divider()
87
- st.info("System Status: Synchronized (V3.0)")
88
 
89
  # --- 6. PATIENT PORTAL MODULE ---
90
  if st.session_state.role == "Patient":
@@ -92,72 +105,76 @@ if st.session_state.role == "Patient":
92
  st.header("πŸ’¬ Clinical AI Assistant")
93
  for m in st.session_state.msgs: st.chat_message(m["role"]).write(m["content"])
94
 
95
- q = st.chat_input("Explain your symptoms...")
96
  c1, c2 = st.columns(2)
97
- with c1: v_in = st.audio_input("Voice Input", key=f"v_{len(st.session_state.msgs)}")
98
- with c2: doc_in = st.file_uploader("Upload Lab Report (PDF/IMG)", type=['pdf', 'png', 'jpg'])
99
 
100
- final_q = q if q else None
101
- if doc_in:
102
- with st.spinner("Analyzing Document..."):
103
- if doc_in.type == "application/pdf":
104
- with pdfplumber.open(doc_in) as pdf: final_q = "Summarize: " + " ".join([p.extract_text() for p in pdf.pages if p.extract_text()])
105
- else: final_q = "Analyze Scan: " + " ".join(easyocr.Reader(['en']).readtext(np.array(Image.open(doc_in)), detail=0))
106
- elif v_in and v_in.size > 0:
107
- vh = hash(v_in.getvalue())
108
- if st.session_state.v_hash != vh:
109
- final_q = Groq(api_key=GROQ_API_KEY).audio.transcriptions.create(file=("a.wav", v_in.getvalue()), model="whisper-large-v3", response_format="text")
110
- st.session_state.v_hash = vh
111
-
112
- if final_q:
113
- st.session_state.msgs.append({"role": "user", "content": final_q})
114
- res = main_col.query(query_texts=[final_q], n_results=1)
115
- ctx = res['documents'][0][0] if res['documents'] else "N/A"
 
 
116
  ans = Groq(api_key=GROQ_API_KEY).chat.completions.create(model="llama-3.3-70b-versatile", messages=[{"role": "system", "content": f"Context: {ctx}"}] + st.session_state.msgs)
117
  st.session_state.msgs.append({"role": "assistant", "content": ans.choices[0].message.content})
118
  st.rerun()
119
 
120
- elif nav == "πŸ§ͺ Diagnostics":
121
- st.header("πŸ§ͺ Interactive Health Metrics")
122
- col1, col2 = st.columns(2)
123
- with col1:
124
- bpm = st.slider("Current Heart Rate (BPM)", 40, 200, 72); st.metric("Pulse", bpm)
125
- with col2:
126
- sugar = st.slider("Blood Glucose (mg/dL)", 50, 400, 95); st.metric("Glucose", sugar)
127
- st.image("https://www.verywellfit.com/thmb/p2O_L3uS9m7O3r4E1uG2F3G_Y3I=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc()/HeartRateChart-5b8d5a1bc9e77c00507a2a1a.jpg", use_container_width=True)
128
 
129
  elif nav == "πŸ“ž Contact Doctor":
130
- st.header("πŸ“ž Start Video Consultation")
131
- doc_name = st.selectbox("Available Specialist", ["Dr. Ahmed", "Dr. Sara"])
132
- if st.button("Request Meeting"):
133
- room = f"NUTECH-{np.random.randint(100,999)}"
134
- signal_col.upsert(documents=[f"CALL|{doc_name}|{room}|{datetime.now().strftime('%H:%M')}"], ids=["latest"])
135
- st.session_state.active_room = room
136
- st.success("Calling Specialist...")
137
- if "active_room" in st.session_state:
138
- st.components.v1.html(f'<iframe src="https://meet.jit.si/{st.session_state.active_room}" width="100%" height="550px" allow="camera; microphone;"></iframe>', height=600)
 
 
 
 
 
139
 
140
  elif nav == "πŸ“œ My Records":
141
- st.header("πŸ“œ My Permanent Visit Records")
142
- if os.path.exists(RECORDS_FILE): st.table(pd.read_csv(RECORDS_FILE))
143
- else: st.info("No records found.")
144
 
145
  # --- 7. DOCTOR PORTAL MODULE ---
146
  elif st.session_state.role == "Doctor":
147
- if nav == "πŸ–₯️ Live Tele-Consult":
148
- st.header("πŸ–₯️ Consultation Dashboard")
149
  res = signal_col.get(ids=["latest"])
150
  if res['documents']:
151
  parts = res['documents'][0].split("|")
152
- st.warning(f"πŸ”” INCOMING CALL: {parts[1]} is requesting a session.")
153
- if st.button("Accept & Join"):
154
- st.components.v1.html(f'<iframe src="https://meet.jit.si/{parts[2]}" width="100%" height="600px" allow="camera; microphone;"></iframe>', height=650)
155
- if st.button("βœ… Archive Session & Save Patient Logs"):
156
- save_clinical_record(parts[1], parts[2], st.session_state.msgs)
157
- signal_col.delete(ids=["latest"]); st.success("Session Archived!"); st.rerun()
158
- else: st.info("Waiting for patient requests...")
159
-
160
- elif nav == "πŸ“‹ Global Patient Logs":
161
- st.header("πŸ“‹ Global Consultation History")
162
- if os.path.exists(RECORDS_FILE): st.dataframe(pd.read_csv(RECORDS_FILE), use_container_width=True)
163
- else: st.info("No global records found.")
 
 
11
  from groq import Groq
12
  from fpdf import FPDF
13
  from datetime import datetime
14
+ import hashlib
15
 
16
+ # --- 1. SECURE CONFIGURATION ---
17
  GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
18
  GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
19
 
20
  if not GROQ_API_KEY or not GOOGLE_API_KEY:
21
+ st.error("⚠️ API Keys Missing! Add them to Space Secrets.")
22
  st.stop()
23
 
24
  genai.configure(api_key=GOOGLE_API_KEY)
25
  vision_model = genai.GenerativeModel('gemini-2.0-flash')
26
 
27
+ st.set_page_config(page_title="HealthConnect | Hassan Naseer", layout="wide", page_icon="πŸ₯")
28
 
29
+ # --- 2. PERSISTENT STORAGE (USER & CLINICAL DB) ---
30
+ USER_DB = "users_secure.csv"
31
+ HISTORY_DB = "permanent_clinical_records.csv"
32
 
33
+ def hash_pass(password):
34
+ return hashlib.sha256(str.encode(password)).hexdigest()
35
+
36
+ def load_db(file, cols):
37
+ if os.path.exists(file): return pd.read_csv(file)
38
+ return pd.DataFrame(columns=cols)
 
 
 
 
 
 
39
 
40
+ def save_user(username, password, role):
41
+ df = load_db(USER_DB, ["username", "password", "role"])
42
+ if username in df['username'].values: return False
43
+ new_user = pd.DataFrame([{"username": username, "password": hash_pass(password), "role": role}])
44
+ pd.concat([df, new_user]).to_csv(USER_DB, index=False)
45
+ return True
46
+
47
+ # --- 3. VECTOR DATABASE ENGINE ---
48
  @st.cache_resource
49
+ def get_db():
50
  client = chromadb.PersistentClient(path="./med_vector_db")
51
  emb_fn = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2")
52
+ main = client.get_or_create_collection(name="nutech_final_rag", embedding_function=emb_fn)
53
+ signal = client.get_or_create_collection(name="global_call_signals", embedding_function=emb_fn)
54
+ return main, signal
55
+
56
+ main_col, signal_col = get_db()
57
+
58
+ # --- 4. AUTHENTICATION GATEWAY ---
59
+ if "logged_in" not in st.session_state: st.session_state.logged_in = False
60
+
61
+ if not st.session_state.logged_in:
62
+ st.markdown("<h1 style='text-align: center;'>πŸ₯ HealthConnect Enterprise</h1>", unsafe_allow_html=True)
63
+ tab1, tab2 = st.tabs(["πŸ” Login", "πŸ“ Create Account"])
64
+
65
+ with tab2:
66
+ new_u = st.text_input("Choose Username")
67
+ new_p = st.text_input("Choose Password", type="password")
68
+ new_role = st.selectbox("I am a:", ["Patient", "Doctor"])
69
+ if st.button("Register Account"):
70
+ if save_user(new_u, new_p, new_role): st.success("Account Created! Please Login.")
71
+ else: st.error("Username already taken.")
72
+
73
+ with tab1:
74
+ u = st.text_input("Username")
75
+ p = st.text_input("Password", type="password")
76
+ if st.button("Login"):
77
+ users = load_db(USER_DB, ["username", "password", "role"])
78
+ match = users[(users['username'] == u) & (users['password'] == hash_pass(p))]
79
+ if not match.empty:
80
+ st.session_state.logged_in = True
81
+ st.session_state.username = u
82
+ st.session_state.role = match.iloc[0]['role']
83
+ st.session_state.msgs = [] # Fresh session chat
84
+ st.rerun()
85
+ else: st.error("Invalid credentials.")
86
  st.stop()
87
 
88
+ # --- 5. PORTAL NAVIGATION ---
89
  with st.sidebar:
90
+ st.markdown(f"### Welcome, **{st.session_state.username}**")
91
+ st.caption(f"Role: {st.session_state.role}")
92
+ if st.button("Sign Out"):
93
+ st.session_state.logged_in = False; st.rerun()
94
  st.divider()
95
  if st.session_state.role == "Patient":
96
+ nav = st.radio("Menu", ["πŸ’¬ AI Chat & Docs", "πŸ§ͺ Health Lab", "πŸ“ž Contact Doctor", "πŸ“Έ Vision Scanner", "πŸ“œ My Records"])
97
  else:
98
+ nav = st.radio("Menu", ["πŸ–₯️ Tele-Consult Desk", "πŸ“‹ Patient Archives"])
99
  st.divider()
100
+ st.info("System: Production Ready")
101
 
102
  # --- 6. PATIENT PORTAL MODULE ---
103
  if st.session_state.role == "Patient":
 
105
  st.header("πŸ’¬ Clinical AI Assistant")
106
  for m in st.session_state.msgs: st.chat_message(m["role"]).write(m["content"])
107
 
108
+ q = st.chat_input("Enter symptoms...")
109
  c1, c2 = st.columns(2)
110
+ with c1: v = st.audio_input("Voice Input", key=f"v_{len(st.session_state.msgs)}")
111
+ with c2: up = st.file_uploader("Upload Medical PDF/IMG", type=['pdf', 'png', 'jpg'])
112
 
113
+ fq = q if q else None
114
+ if up:
115
+ with st.spinner("Processing..."):
116
+ if up.type == "application/pdf":
117
+ with pdfplumber.open(up) as f: fq = "Review PDF: " + " ".join([p.extract_text() for p in f.pages if p.extract_text()])
118
+ else: fq = "Review Scan: " + " ".join(easyocr.Reader(['en']).readtext(np.array(Image.open(up)), detail=0))
119
+ elif v:
120
+ fq = Groq(api_key=GROQ_API_KEY).audio.transcriptions.create(file=("a.wav", v.getvalue()), model="whisper-large-v3", response_format="text")
121
+
122
+ if fq:
123
+ st.session_state.msgs.append({"role": "user", "content": fq})
124
+ # --- INDEX ERROR SAFETY FIX ---
125
+ res = main_col.query(query_texts=[fq], n_results=1)
126
+ if res.get('documents') and len(res['documents']) > 0 and len(res['documents'][0]) > 0:
127
+ ctx = res['documents'][0][0]
128
+ else:
129
+ ctx = "No specific match in local database."
130
+ # ------------------------------
131
  ans = Groq(api_key=GROQ_API_KEY).chat.completions.create(model="llama-3.3-70b-versatile", messages=[{"role": "system", "content": f"Context: {ctx}"}] + st.session_state.msgs)
132
  st.session_state.msgs.append({"role": "assistant", "content": ans.choices[0].message.content})
133
  st.rerun()
134
 
135
+ elif nav == "πŸ§ͺ Health Lab":
136
+ st.header("πŸ§ͺ Interactive Diagnostics")
137
+ bpm = st.slider("Heart Rate (BPM)", 40, 200, 72); st.metric("Pulse", bpm)
138
+ sugar = st.slider("Blood Glucose (mg/dL)", 50, 400, 95); st.metric("Level", sugar)
 
 
 
 
139
 
140
  elif nav == "πŸ“ž Contact Doctor":
141
+ st.header("πŸ“ž Request Live Consultation")
142
+ doc = st.selectbox("Choose Doctor", ["Dr_Ahmed", "Dr_Sara"])
143
+ if st.button("Start Connection"):
144
+ room = f"Room-{st.session_state.username}-{np.random.randint(100,999)}"
145
+ signal_col.upsert(documents=[f"CALL|{doc}|{room}|{st.session_state.username}"], ids=["latest"])
146
+ st.session_state.room = room
147
+ if "room" in st.session_state:
148
+ st.components.v1.html(f'<iframe src="https://meet.jit.si/{st.session_state.room}" width="100%" height="550px"></iframe>', height=600)
149
+
150
+ elif nav == "πŸ“Έ Vision Scanner":
151
+ cam = st.camera_input("Scanner")
152
+ if cam:
153
+ rep = vision_model.generate_content(["ACT AS PHARMACIST: Salt, Dosage.", Image.open(cam)])
154
+ st.write(rep.text)
155
 
156
  elif nav == "πŸ“œ My Records":
157
+ st.header("πŸ“œ My Visit History")
158
+ df = load_db(HISTORY_DB, ["Time", "Patient", "Doctor", "Status"])
159
+ st.table(df[df['Patient'] == st.session_state.username])
160
 
161
  # --- 7. DOCTOR PORTAL MODULE ---
162
  elif st.session_state.role == "Doctor":
163
+ if nav == "πŸ–₯️ Tele-Consult Desk":
164
+ st.header("πŸ–₯️ Active Incoming Desk")
165
  res = signal_col.get(ids=["latest"])
166
  if res['documents']:
167
  parts = res['documents'][0].split("|")
168
+ st.warning(f"πŸ”” CALL: Incoming from {parts[3]}")
169
+ if st.button("Join Room"):
170
+ st.components.v1.html(f'<iframe src="https://meet.jit.si/{parts[2]}" width="100%" height="600px"></iframe>', height=650)
171
+ if st.button("βœ… Archive Session"):
172
+ df = load_db(HISTORY_DB, ["Time", "Patient", "Doctor", "Status"])
173
+ new_row = pd.DataFrame([{"Time": datetime.now().strftime("%Y-%m-%d %H:%M"), "Patient": parts[3], "Doctor": st.session_state.username, "Status": "Completed"}])
174
+ pd.concat([df, new_row]).to_csv(HISTORY_DB, index=False)
175
+ signal_col.delete(ids=["latest"]); st.success("Session Saved!"); st.rerun()
176
+ else: st.info("Monitoring for patient requests...")
177
+
178
+ elif nav == "πŸ“‹ Patient Archives":
179
+ st.header("πŸ“‹ Global Clinical Records")
180
+ st.dataframe(load_db(HISTORY_DB, ["Time", "Patient", "Doctor", "Status"]), use_container_width=True)