hassan773 commited on
Commit
039b2b9
Β·
verified Β·
1 Parent(s): 6d57b12

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -83
app.py CHANGED
@@ -12,131 +12,152 @@ from groq import Groq
12
  from fpdf import FPDF
13
  from datetime import datetime
14
 
15
- # --- 1. CORE 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!")
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")
27
-
28
- # --- 2. PERSISTENCE ENGINES (CSVs for Records) ---
29
- HISTORY_FILE = "clinical_history.csv"
30
-
31
- def save_visit(doctor, room, status):
32
- df = pd.read_csv(HISTORY_FILE) if os.path.exists(HISTORY_FILE) else pd.DataFrame(columns=["Time", "Doctor", "Room", "Status"])
33
- new_data = pd.DataFrame([{"Time": datetime.now().strftime("%H:%M:%S"), "Doctor": doctor, "Room": room, "Status": status}])
34
- pd.concat([df, new_data]).to_csv(HISTORY_FILE, index=False)
 
 
 
 
 
 
 
 
 
35
 
36
  @st.cache_resource
37
- def get_db():
38
  client = chromadb.PersistentClient(path="./med_vector_db")
39
  emb_fn = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2")
40
- main = client.get_or_create_collection(name="nutech_knowledge", embedding_function=emb_fn)
41
- signal = client.get_or_create_collection(name="call_signals", embedding_function=emb_fn)
42
- return main, signal
43
-
44
- main_col, signal_col = get_db()
45
-
46
- # --- 3. UI HELPERS ---
47
- def generate_pro_pdf(history):
48
- pdf = FPDF()
49
- pdf.add_page(); pdf.set_font("Arial", 'B', 16)
50
- pdf.cell(200, 10, txt="NUTECH MEDISCAN CLINICAL REPORT", ln=True, align='C')
51
- pdf.set_font("Arial", size=10); pdf.ln(10)
52
- for m in history:
53
- label = "PATIENT: " if m["role"] == "user" else "AI DOCTOR: "
54
- pdf.set_font("Arial", 'B', 11); pdf.cell(200, 8, txt=label, ln=True)
55
- pdf.set_font("Arial", size=10); pdf.multi_cell(0, 8, txt=m["content"].encode('latin-1', 'ignore').decode('latin-1'))
56
- return pdf.output(dest='S').encode('latin-1')
57
-
58
- # --- 4. AUTH & NAVIGATION ---
59
  if "auth" not in st.session_state: st.session_state.auth = False
60
  if "msgs" not in st.session_state: st.session_state.msgs = []
 
61
 
 
62
  if not st.session_state.auth:
63
- st.title("πŸ₯ MediScan Enterprise")
64
- role = st.selectbox("Role", ["Patient", "Doctor"])
65
- pwd = st.text_input("Password", type="password")
66
- if st.button("Login"):
67
- if (role == "Patient" and pwd == "p123") or (role == "Doctor" and pwd == "d123"):
68
- st.session_state.auth = True; st.session_state.role = role; st.rerun()
 
 
 
 
 
69
  st.stop()
70
 
 
71
  with st.sidebar:
72
- st.title(f"πŸ‘€ {st.session_state.role}")
73
  if st.button("Logout"): st.session_state.auth = False; st.rerun()
74
  st.divider()
75
  if st.session_state.role == "Patient":
76
- nav = st.radio("Menu", ["πŸ’¬ AI Chat", "πŸ§ͺ Diagnostics", "πŸ“ž Video Call", "πŸ“Έ Vision Scanner", "πŸ“œ My Records"])
77
  else:
78
- nav = st.radio("Menu", ["πŸ–₯️ Consultation Desk", "πŸ“‹ Patient Logs"])
 
 
79
 
80
- # --- 5. PATIENT PORTAL ---
81
  if st.session_state.role == "Patient":
82
- if nav == "πŸ’¬ AI Chat":
83
- st.header("πŸ’¬ AI Assistant & Document Uploader")
84
- if st.session_state.msgs:
85
- st.download_button("πŸ“„ Export Clinical PDF", data=generate_pro_pdf(st.session_state.msgs), file_name="MedReport.pdf")
86
  for m in st.session_state.msgs: st.chat_message(m["role"]).write(m["content"])
87
 
88
- q = st.chat_input("Enter symptoms...")
89
  c1, c2 = st.columns(2)
90
- with c1: v = st.audio_input("Voice Input", key=f"v_{len(st.session_state.msgs)}")
91
- with c2: up = st.file_uploader("Upload Lab PDF/IMG", type=['pdf', 'png', 'jpg'])
92
 
93
- fq = q if q else None
94
- if up:
95
- with st.spinner("Processing..."):
96
- if up.type == "application/pdf":
97
- with pdfplumber.open(up) as f: fq = "Review PDF: " + " ".join([p.extract_text() for p in f.pages if p.extract_text()])
98
- else: fq = "Review Scan: " + " ".join(easyocr.Reader(['en']).readtext(np.array(Image.open(up)), detail=0))
99
- elif v:
100
- fq = Groq(api_key=GROQ_API_KEY).audio.transcriptions.create(file=("a.wav", v.getvalue()), model="whisper-large-v3", response_format="text")
101
-
102
- if fq:
103
- st.session_state.msgs.append({"role": "user", "content": fq})
104
- res = main_col.query(query_texts=[fq], n_results=1)
105
- ctx = res['documents'][0][0] if res['documents'] else ""
 
 
 
106
  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)
107
  st.session_state.msgs.append({"role": "assistant", "content": ans.choices[0].message.content})
108
  st.rerun()
109
 
110
  elif nav == "πŸ§ͺ Diagnostics":
111
- st.header("πŸ§ͺ Lab Metrics")
112
- bpm = st.slider("BPM", 40, 200, 72); st.metric("Heart Rate", bpm)
113
- sug = st.slider("Sugar", 50, 400, 95); st.metric("Glucose", sug)
114
-
115
- elif nav == "πŸ“ž Video Call":
116
- st.header("πŸ“ž Request Live Call")
117
- doc = st.selectbox("Doctor", ["Dr. Ahmed", "Dr. Sara"])
118
- if st.button("Send Request"):
 
 
 
 
119
  room = f"NUTECH-{np.random.randint(100,999)}"
120
- signal_col.upsert(documents=[f"CALL|{doc}|{room}|{datetime.now().strftime('%H:%M')}"], ids=["latest"])
121
- st.session_state.room = room
122
- if "room" in st.session_state:
123
- st.components.v1.html(f'<iframe src="https://meet.jit.si/{st.session_state.room}" width="100%" height="550px" allow="camera; microphone;"></iframe>', height=600)
 
124
 
125
  elif nav == "πŸ“œ My Records":
126
- st.header("πŸ“œ Visit History")
127
- if os.path.exists(HISTORY_FILE): st.table(pd.read_csv(HISTORY_FILE))
128
  else: st.info("No records found.")
129
 
130
- # --- 6. DOCTOR PORTAL ---
131
  elif st.session_state.role == "Doctor":
132
- if nav == "πŸ–₯️ Consultation Desk":
133
- st.header("πŸ–₯️ Call Dashboard")
134
  res = signal_col.get(ids=["latest"])
135
  if res['documents']:
136
  parts = res['documents'][0].split("|")
137
- st.warning(f"πŸ”” CALL: For {parts[1]} at {parts[3]}")
138
- if st.button("Join"):
139
  st.components.v1.html(f'<iframe src="https://meet.jit.si/{parts[2]}" width="100%" height="600px" allow="camera; microphone;"></iframe>', height=650)
140
- if st.button("End & Archive"):
141
- save_visit(parts[1], parts[2], "Completed")
142
- signal_col.delete(ids=["latest"]); st.rerun()
 
 
 
 
 
 
 
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":
91
+ if nav == "πŸ’¬ AI Chat & Docs":
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.")