mgbam commited on
Commit
36986a7
·
verified ·
1 Parent(s): 874a822

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -114
app.py CHANGED
@@ -1,10 +1,8 @@
1
  #!/usr/bin/env python3
2
- # MedGenesis AI · CPU-only Streamlit front-end (OpenAI / Gemini)
3
 
4
- from __future__ import annotations
5
  import os, pathlib, asyncio, re
6
  from pathlib import Path
7
-
8
  import streamlit as st
9
  import pandas as pd
10
  import plotly.express as px
@@ -12,94 +10,76 @@ from fpdf import FPDF
12
  from streamlit_agraph import agraph
13
 
14
  from mcp.orchestrator import orchestrate_search, answer_ai_question
15
- from mcp.workspace import get_workspace, save_query, clear_workspace
16
  from mcp.knowledge_graph import build_agraph
17
- from mcp.graph_utils import build_nx, get_top_hubs, get_density
18
  from mcp.alerts import check_alerts
19
 
20
- # ── Streamlit telemetry dir fix ──────────────────────────────────────
21
  os.environ["STREAMLIT_DATA_DIR"] = "/tmp/.streamlit"
22
- os.environ["XDG_STATE_HOME"] = "/tmp"
23
  os.environ["STREAMLIT_BROWSER_GATHERUSAGESTATS"] = "false"
24
  pathlib.Path("/tmp/.streamlit").mkdir(parents=True, exist_ok=True)
25
 
26
  ROOT = Path(__file__).parent
27
  LOGO = ROOT / "assets" / "logo.png"
28
 
29
- # -------------------------------------------------------------------#
30
- # Utility helpers #
31
- # -------------------------------------------------------------------#
32
- def _latin1_safe(txt: str) -> str:
33
  return txt.encode("latin-1", "replace").decode("latin-1")
34
 
35
-
36
- def _pdf(papers: list[dict]) -> bytes:
37
  pdf = FPDF()
38
- pdf.set_auto_page_break(auto=True, margin=15)
39
  pdf.add_page()
40
  pdf.set_font("Helvetica", size=11)
41
- pdf.cell(200, 8, _latin1_safe("MedGenesis AI – Results"), ln=True, align="C")
42
  pdf.ln(3)
43
  for i, p in enumerate(papers, 1):
44
  pdf.set_font("Helvetica", "B", 11)
45
- pdf.multi_cell(0, 7, _latin1_safe(f"{i}. {p['title']}"))
46
  pdf.set_font("Helvetica", "", 9)
47
  body = f"{p['authors']}\n{p['summary']}\n{p['link']}\n"
48
- pdf.multi_cell(0, 6, _latin1_safe(body))
49
- pdf.ln(1)
50
  return pdf.output(dest="S").encode("latin-1", "replace")
51
 
52
-
53
- def _workspace_sidebar() -> None:
54
  with st.sidebar:
55
  st.header("🗂️ Workspace")
56
  ws = get_workspace()
57
  if not ws:
58
  st.info("Run a search then press **Save** to populate this list.")
59
  return
60
- if st.button("Clear workspace 🗑️"):
61
- clear_workspace()
62
- st.experimental_rerun()
63
  for i, item in enumerate(ws, 1):
64
  with st.expander(f"{i}. {item['query']}"):
65
  st.write(item["result"]["ai_summary"])
66
 
67
-
68
- # -------------------------------------------------------------------#
69
- # Streamlit main UI #
70
- # -------------------------------------------------------------------#
71
- def render_ui() -> None:
72
  st.set_page_config("MedGenesis AI", layout="wide")
73
 
74
- # ── session_state bootstrap ────────────────────────────────────
75
- for key, default in {
76
- "query_result" : None,
77
- "followup_input" : "",
78
- "followup_response" : None,
79
- "last_query" : "",
80
- "last_llm" : "",
81
- }.items():
82
- st.session_state.setdefault(key, default)
83
 
84
- _workspace_sidebar()
85
 
86
- # ── header ───────────────────────────────────────────────────��─
87
  c1, c2 = st.columns([0.15, 0.85])
88
- with c1:
89
- if LOGO.exists():
90
- st.image(str(LOGO), width=105)
91
- with c2:
92
- st.markdown("## 🧬 **MedGenesis AI**")
93
- st.caption("Multi-source biomedical assistant · OpenAI / Gemini")
94
 
95
  llm = st.radio("LLM engine", ["openai", "gemini"], horizontal=True)
96
  query = st.text_input("Enter biomedical question",
97
  placeholder="e.g. CRISPR glioblastoma therapy")
98
 
99
- # ── alerts for saved queries ───────────────────────────────────
100
- if ws := get_workspace():
101
  try:
102
- news = asyncio.run(check_alerts([w["query"] for w in ws]))
103
  if news:
104
  with st.sidebar:
105
  st.subheader("🔔 New papers")
@@ -108,51 +88,44 @@ def render_ui() -> None:
108
  except Exception:
109
  pass
110
 
111
- # ── primary search trigger ─────────────────────────────────────
112
  if st.button("Run Search 🚀") and query.strip():
113
  with st.spinner("Collecting literature & biomedical data …"):
114
  res = asyncio.run(orchestrate_search(query, llm=llm))
 
 
 
 
115
  st.success(f"Completed with **{res['llm_used'].title()}**")
116
- st.session_state.update({
117
- "query_result" : res,
118
- "last_query" : query,
119
- "last_llm" : llm,
120
- "followup_input" : "",
121
- "followup_response" : None,
122
- })
123
-
124
- res = st.session_state.query_result
125
  if not res:
126
  st.info("Enter a question and press **Run Search 🚀**")
127
  return
128
 
129
- # ----------------------------------------------------------------#
130
- # Tabs #
131
- # ----------------------------------------------------------------#
132
- tabs = st.tabs(["Results", "Genes", "Trials", "Graph", "Metrics", "Visuals"])
133
 
134
- # Results ---------------------------------------------------------
135
  with tabs[0]:
136
  for i, p in enumerate(res["papers"], 1):
137
  st.markdown(f"**{i}. [{p['title']}]({p['link']})** *{p['authors']}*")
138
  st.write(p["summary"])
139
-
140
  c1, c2 = st.columns(2)
141
- with c1:
142
- st.download_button("CSV",
143
- pd.DataFrame(res["papers"]).to_csv(index=False),
144
- "papers.csv", "text/csv")
145
- with c2:
146
- st.download_button("PDF", _pdf(res["papers"]),
147
- "papers.pdf", "application/pdf")
148
-
149
- if st.button("💾 Save this result"):
150
  save_query(st.session_state.last_query, res)
151
  st.success("Saved to workspace")
152
 
153
  st.subheader("UMLS concepts")
154
  for c in res["umls"]:
155
- if c.get("cui"):
156
  st.write(f"- **{c['name']}** ({c['cui']})")
157
 
158
  st.subheader("OpenFDA safety")
@@ -162,74 +135,71 @@ def render_ui() -> None:
162
  st.subheader("AI summary")
163
  st.info(res["ai_summary"])
164
 
165
- # Genes -----------------------------------------------------------
166
  with tabs[1]:
167
  st.header("Gene / Variant signals")
168
- if not res["genes"]:
 
 
 
 
169
  st.info("No gene hits (rate-limited or none found).")
170
- for g in res["genes"]:
171
- st.write(f"- **{g.get('symbol', g.get('name', ''))}** "
172
- f"{g.get('summary', '')[:120]}…")
173
 
174
- if res["gene_disease"]:
175
- st.markdown("### DisGeNET links")
176
- st.json(res["gene_disease"][:15])
 
 
177
 
178
- if res["mesh_defs"]:
179
- st.markdown("### MeSH definitions")
180
- for d in res["mesh_defs"]:
181
- if d:
182
- st.write("-", d)
183
 
184
- # Trials ----------------------------------------------------------
185
  with tabs[2]:
186
  st.header("Clinical trials")
187
- trials = res["clinical_trials"]
188
- if not trials:
189
  st.info("No trials (rate-limited or none found).")
190
- for t in trials:
191
- st.markdown(f"**{t['nctId']}** – {t['briefTitle']}")
192
- st.write(f"Phase {t.get('phase','')} | Status {t.get('status')}")
 
193
 
194
- # Graph -----------------------------------------------------------
195
  with tabs[3]:
196
  nodes, edges, cfg = build_agraph(
197
- res["papers"], res["umls"], res["drug_safety"],
198
- res["genes"], res["clinical_trials"], res.get("ot_associations", [])
199
  )
200
- hl = st.text_input("Highlight node:")
201
  if hl:
202
  pat = re.compile(re.escape(hl), re.I)
203
  for n in nodes:
204
  n.color = "#f1c40f" if pat.search(n.label) else "#d3d3d3"
205
  agraph(nodes, edges, cfg)
206
 
207
- # Metrics ---------------------------------------------------------
208
  with tabs[4]:
209
- G = build_nx([n.__dict__ for n in nodes], [e.__dict__ for e in edges])
 
210
  st.metric("Density", f"{get_density(G):.3f}")
211
  st.markdown("**Top hubs**")
212
  for nid, sc in get_top_hubs(G):
213
  lab = next((n.label for n in nodes if n.id == nid), nid)
214
  st.write(f"- {lab} {sc:.3f}")
215
 
216
- # Visuals ---------------------------------------------------------
217
  with tabs[5]:
218
  years = [p["published"] for p in res["papers"] if p.get("published")]
219
  if years:
220
  st.plotly_chart(px.histogram(years, nbins=12,
221
  title="Publication Year"))
222
 
223
- # ----------------------------------------------------------------#
224
- # Follow-up Q & A #
225
- # ----------------------------------------------------------------#
226
  st.markdown("---")
227
  st.text_input("Ask follow-up question:",
228
- key="followup_input",
229
- placeholder="e.g. Any phase III trials recruiting now?")
230
-
231
- def _on_ask() -> None:
232
- q = st.session_state.followup_input.strip()
233
  if not q:
234
  st.warning("Please type a question first.")
235
  return
@@ -237,14 +207,14 @@ def render_ui() -> None:
237
  ans = asyncio.run(
238
  answer_ai_question(q,
239
  context=st.session_state.last_query,
240
- llm=st.session_state.last_llm))
241
- st.session_state.followup_response = ans["answer"]
 
242
 
243
  st.button("Ask AI", on_click=_on_ask)
244
- if st.session_state.followup_response:
245
- st.write(st.session_state.followup_response)
246
-
247
 
248
- # -------------------------------------------------------------------#
249
  if __name__ == "__main__":
250
  render_ui()
 
1
  #!/usr/bin/env python3
2
+ # MedGenesis AI Streamlit frontend (OpenAI / Gemini)
3
 
 
4
  import os, pathlib, asyncio, re
5
  from pathlib import Path
 
6
  import streamlit as st
7
  import pandas as pd
8
  import plotly.express as px
 
10
  from streamlit_agraph import agraph
11
 
12
  from mcp.orchestrator import orchestrate_search, answer_ai_question
13
+ from mcp.workspace import get_workspace, save_query
14
  from mcp.knowledge_graph import build_agraph
15
+ from mcp.graph_metrics import build_nx, get_top_hubs, get_density
16
  from mcp.alerts import check_alerts
17
 
18
+ # ── Streamlit telemetry off ─────────────────────────────────────────
19
  os.environ["STREAMLIT_DATA_DIR"] = "/tmp/.streamlit"
20
+ os.environ["XDG_STATE_HOME"] = "/tmp"
21
  os.environ["STREAMLIT_BROWSER_GATHERUSAGESTATS"] = "false"
22
  pathlib.Path("/tmp/.streamlit").mkdir(parents=True, exist_ok=True)
23
 
24
  ROOT = Path(__file__).parent
25
  LOGO = ROOT / "assets" / "logo.png"
26
 
27
+ # ── PDF helper ──────────────────────────────────────────────────────
28
+ def _latin1(txt: str) -> str:
 
 
29
  return txt.encode("latin-1", "replace").decode("latin-1")
30
 
31
+ def _pdf(papers):
 
32
  pdf = FPDF()
 
33
  pdf.add_page()
34
  pdf.set_font("Helvetica", size=11)
35
+ pdf.cell(200, 8, _latin1("MedGenesis AI – Results"), ln=True, align="C")
36
  pdf.ln(3)
37
  for i, p in enumerate(papers, 1):
38
  pdf.set_font("Helvetica", "B", 11)
39
+ pdf.multi_cell(0, 7, _latin1(f"{i}. {p['title']}"))
40
  pdf.set_font("Helvetica", "", 9)
41
  body = f"{p['authors']}\n{p['summary']}\n{p['link']}\n"
42
+ pdf.multi_cell(0, 6, _latin1(body)); pdf.ln(1)
 
43
  return pdf.output(dest="S").encode("latin-1", "replace")
44
 
45
+ # ── Sidebar ────────────────────────────────────────────────────────
46
+ def _sidebar_workspace():
47
  with st.sidebar:
48
  st.header("🗂️ Workspace")
49
  ws = get_workspace()
50
  if not ws:
51
  st.info("Run a search then press **Save** to populate this list.")
52
  return
 
 
 
53
  for i, item in enumerate(ws, 1):
54
  with st.expander(f"{i}. {item['query']}"):
55
  st.write(item["result"]["ai_summary"])
56
 
57
+ # ── Main UI ────────────────────────────────────────────────────────
58
+ def render_ui():
 
 
 
59
  st.set_page_config("MedGenesis AI", layout="wide")
60
 
61
+ # session state
62
+ st.session_state.setdefault("result", None)
63
+ st.session_state.setdefault("last_query", "")
64
+ st.session_state.setdefault("last_llm", "")
65
+ st.session_state.setdefault("followup", "")
66
+ st.session_state.setdefault("answer", "")
 
 
 
67
 
68
+ _sidebar_workspace()
69
 
 
70
  c1, c2 = st.columns([0.15, 0.85])
71
+ if LOGO.exists(): c1.image(str(LOGO), width=105)
72
+ c2.markdown("## 🧬 **MedGenesis AI**")
73
+ c2.caption("Multi-source biomedical assistant · OpenAI / Gemini")
 
 
 
74
 
75
  llm = st.radio("LLM engine", ["openai", "gemini"], horizontal=True)
76
  query = st.text_input("Enter biomedical question",
77
  placeholder="e.g. CRISPR glioblastoma therapy")
78
 
79
+ # Alerts
80
+ if get_workspace():
81
  try:
82
+ news = asyncio.run(check_alerts([w["query"] for w in get_workspace()]))
83
  if news:
84
  with st.sidebar:
85
  st.subheader("🔔 New papers")
 
88
  except Exception:
89
  pass
90
 
91
+ # Run search
92
  if st.button("Run Search 🚀") and query.strip():
93
  with st.spinner("Collecting literature & biomedical data …"):
94
  res = asyncio.run(orchestrate_search(query, llm=llm))
95
+ st.session_state.update(
96
+ result=res, last_query=query, last_llm=llm,
97
+ followup="", answer=""
98
+ )
99
  st.success(f"Completed with **{res['llm_used'].title()}**")
100
+
101
+ res = st.session_state.result
 
 
 
 
 
 
 
102
  if not res:
103
  st.info("Enter a question and press **Run Search 🚀**")
104
  return
105
 
106
+ # ── Tabs ───────────────────────────────────────────────────────
107
+ tabs = st.tabs(
108
+ ["Results", "Genes", "Trials", "Graph", "Metrics", "Visuals"]
109
+ )
110
 
111
+ # Results
112
  with tabs[0]:
113
  for i, p in enumerate(res["papers"], 1):
114
  st.markdown(f"**{i}. [{p['title']}]({p['link']})** *{p['authors']}*")
115
  st.write(p["summary"])
 
116
  c1, c2 = st.columns(2)
117
+ c1.download_button("CSV",
118
+ pd.DataFrame(res["papers"]).to_csv(index=False),
119
+ "papers.csv", "text/csv")
120
+ c2.download_button("PDF", _pdf(res["papers"]),
121
+ "papers.pdf", "application/pdf")
122
+ if st.button("💾 Save"):
 
 
 
123
  save_query(st.session_state.last_query, res)
124
  st.success("Saved to workspace")
125
 
126
  st.subheader("UMLS concepts")
127
  for c in res["umls"]:
128
+ if isinstance(c, dict) and c.get("cui"):
129
  st.write(f"- **{c['name']}** ({c['cui']})")
130
 
131
  st.subheader("OpenFDA safety")
 
135
  st.subheader("AI summary")
136
  st.info(res["ai_summary"])
137
 
138
+ # Genes
139
  with tabs[1]:
140
  st.header("Gene / Variant signals")
141
+ if res["genes_rich"]:
142
+ for g in res["genes_rich"]:
143
+ st.write(f"- **{g.get('symbol', g.get('approvedSymbol','?'))}**"
144
+ f" – {g.get('summary','')[:160]}…")
145
+ else:
146
  st.info("No gene hits (rate-limited or none found).")
 
 
 
147
 
148
+ if res["expr_atlas"]:
149
+ st.plotly_chart(px.bar(
150
+ res["expr_atlas"][0].get("expressions", [])[:10],
151
+ x="assayName", y="value", title="Top tissues (Expression Atlas)"
152
+ ))
153
 
154
+ if res["cbio_variants"]:
155
+ st.markdown("### cBioPortal cohort variants")
156
+ st.json(res["cbio_variants"][0][:15])
 
 
157
 
158
+ # Trials
159
  with tabs[2]:
160
  st.header("Clinical trials")
161
+ if not res["clinical_trials"]:
 
162
  st.info("No trials (rate-limited or none found).")
163
+ for t in res["clinical_trials"]:
164
+ st.markdown(f"**{t['NCTId'][0]}** – {t['BriefTitle'][0]}")
165
+ st.write(f"Phase {t.get('Phase',[''])[0]} | "
166
+ f"Status {t['OverallStatus'][0]}")
167
 
168
+ # Graph
169
  with tabs[3]:
170
  nodes, edges, cfg = build_agraph(
171
+ res["papers"], res["umls"], res["drug_safety"]
 
172
  )
173
+ hl = st.text_input("Highlight node:", key="hl")
174
  if hl:
175
  pat = re.compile(re.escape(hl), re.I)
176
  for n in nodes:
177
  n.color = "#f1c40f" if pat.search(n.label) else "#d3d3d3"
178
  agraph(nodes, edges, cfg)
179
 
180
+ # Metrics
181
  with tabs[4]:
182
+ G = build_nx([n.__dict__ for n in nodes],
183
+ [e.__dict__ for e in edges])
184
  st.metric("Density", f"{get_density(G):.3f}")
185
  st.markdown("**Top hubs**")
186
  for nid, sc in get_top_hubs(G):
187
  lab = next((n.label for n in nodes if n.id == nid), nid)
188
  st.write(f"- {lab} {sc:.3f}")
189
 
190
+ # Visuals
191
  with tabs[5]:
192
  years = [p["published"] for p in res["papers"] if p.get("published")]
193
  if years:
194
  st.plotly_chart(px.histogram(years, nbins=12,
195
  title="Publication Year"))
196
 
197
+ # Follow-up QA
 
 
198
  st.markdown("---")
199
  st.text_input("Ask follow-up question:",
200
+ key="followup", placeholder="e.g. Any phase III trials recruiting now?")
201
+ def _on_ask():
202
+ q = st.session_state.followup.strip()
 
 
203
  if not q:
204
  st.warning("Please type a question first.")
205
  return
 
207
  ans = asyncio.run(
208
  answer_ai_question(q,
209
  context=st.session_state.last_query,
210
+ llm=st.session_state.last_llm)
211
+ )
212
+ st.session_state.answer = ans["answer"]
213
 
214
  st.button("Ask AI", on_click=_on_ask)
215
+ if st.session_state.answer:
216
+ st.write(st.session_state.answer)
 
217
 
218
+ # entry-point
219
  if __name__ == "__main__":
220
  render_ui()