mgbam commited on
Commit
994feb6
Β·
verified Β·
1 Parent(s): 08c0325

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -132
app.py CHANGED
@@ -1,8 +1,8 @@
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
@@ -12,38 +12,44 @@ from streamlit_agraph import agraph
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()
@@ -54,167 +60,107 @@ def _sidebar_workspace():
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")
86
- for q, lnks in news.items():
87
- st.write(f"**{q}** – {len(lnks)} new")
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")
132
- for d in res["drug_safety"]:
133
- st.json(d)
134
-
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
206
  with st.spinner("Querying LLM …"):
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()
 
1
+ # ──────────────────────────── app.py ─────────────────────────────────
2
+ """Streamlit UI – MedGenesis v2 with gene + variant + trial integration."""
 
3
  import os, pathlib, asyncio, re
4
  from pathlib import Path
5
+
6
  import streamlit as st
7
  import pandas as pd
8
  import plotly.express as px
 
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_utils import build_nx, get_top_hubs, get_density
16
  from mcp.alerts import check_alerts
17
 
18
+ # ---- Streamlit telemetry patch -------------------------------------
19
+ os.environ.update({
20
+ "STREAMLIT_DATA_DIR": "/tmp/.streamlit",
21
+ "XDG_STATE_HOME": "/tmp",
22
+ "STREAMLIT_BROWSER_GATHERUSAGESTATS": "false",
23
+ })
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
+ # ---------------- helpers -------------------------------------------
 
 
30
 
31
+ def _latin1_safe(t: str) -> str:
32
+ return t.encode("latin-1", "replace").decode("latin-1")
33
+
34
+ def _export_pdf(papers):
35
  pdf = FPDF()
36
+ pdf.set_auto_page_break(auto=True, margin=15)
37
  pdf.add_page()
38
  pdf.set_font("Helvetica", size=11)
39
+ pdf.cell(200, 8, _latin1_safe("MedGenesis AI – Results"), ln=True, align="C")
40
  pdf.ln(3)
41
  for i, p in enumerate(papers, 1):
42
  pdf.set_font("Helvetica", "B", 11)
43
+ pdf.multi_cell(0, 7, _latin1_safe(f"{i}. {p['title']}"))
44
+ pdf.set_font("Helvetica", size=9)
45
  body = f"{p['authors']}\n{p['summary']}\n{p['link']}\n"
46
+ pdf.multi_cell(0, 6, _latin1_safe(body))
47
+ pdf.ln(1)
48
  return pdf.output(dest="S").encode("latin-1", "replace")
49
 
50
+ # ---------------- sidebar -------------------------------------------
51
+
52
+ def _workspace_sidebar():
53
  with st.sidebar:
54
  st.header("πŸ—‚οΈ Workspace")
55
  ws = get_workspace()
 
60
  with st.expander(f"{i}. {item['query']}"):
61
  st.write(item["result"]["ai_summary"])
62
 
63
+ # ---------------- main ----------------------------------------------
64
+
65
  def render_ui():
66
  st.set_page_config("MedGenesis AI", layout="wide")
67
+ _workspace_sidebar()
68
 
69
+ # header ---------------------------------------------------------
 
 
 
 
 
 
 
 
70
  c1, c2 = st.columns([0.15, 0.85])
71
+ if LOGO.exists():
72
+ with c1: st.image(str(LOGO), width=105)
73
+ with c2:
74
+ st.markdown("## 🧬 **MedGenesis AI**")
75
+ st.caption("Multi‑source biomedical assistant Β· OpenAI / Gemini")
76
 
77
  llm = st.radio("LLM engine", ["openai", "gemini"], horizontal=True)
78
+ query = st.text_input("Enter biomedical question", "CRISPR glioblastoma therapy")
79
+
80
+ if st.button("Run Search πŸš€") and query:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  with st.spinner("Collecting literature & biomedical data …"):
82
  res = asyncio.run(orchestrate_search(query, llm=llm))
 
 
 
 
83
  st.success(f"Completed with **{res['llm_used'].title()}**")
84
+ st.session_state.result = res
85
+ st.session_state.last_query = query
86
+ st.session_state.last_llm = llm
87
 
88
+ res = st.session_state.get("result")
89
  if not res:
90
  st.info("Enter a question and press **Run Search πŸš€**")
91
  return
92
 
93
+ tabs = st.tabs(["Results", "Genes", "Trials", "Graph", "Metrics", "Visuals"])
 
 
 
94
 
95
+ # results --------------------------------------------------------
96
  with tabs[0]:
97
  for i, p in enumerate(res["papers"], 1):
98
  st.markdown(f"**{i}. [{p['title']}]({p['link']})** *{p['authors']}*")
99
  st.write(p["summary"])
100
  c1, c2 = st.columns(2)
101
+ with c1:
102
+ st.download_button("CSV", pd.DataFrame(res["papers"]).to_csv(index=False), "papers.csv")
103
+ with c2:
104
+ st.download_button("PDF", _export_pdf(res["papers"]), "papers.pdf", mime="application/pdf")
 
105
  if st.button("πŸ’Ύ Save"):
106
+ save_query(query, res)
107
  st.success("Saved to workspace")
 
 
 
 
 
 
 
 
 
 
108
  st.subheader("AI summary")
109
  st.info(res["ai_summary"])
110
 
111
+ # gene tab -------------------------------------------------------
112
  with tabs[1]:
113
+ if not res["genes"]:
114
+ st.info("No gene hits (rate‑limited or none found).")
115
+ for g in res["genes"]:
116
+ st.json(g)
117
+ if res["variants"]:
118
+ st.markdown("### Tumour variants (cBioPortal)")
119
+ for k, v in res["variants"].items():
120
+ st.write(f"**{k}** – {len(v)} variants")
121
+
122
+ # trials tab -----------------------------------------------------
 
 
 
 
 
 
 
 
 
123
  with tabs[2]:
124
  st.header("Clinical trials")
125
  if not res["clinical_trials"]:
126
+ st.info("No trials (rate‑limited or none found).")
127
  for t in res["clinical_trials"]:
128
+ st.markdown(f"**{t['nctId']}** – {t['briefTitle']}")
129
+ st.write(f"Phase {t.get('phase')} | Status {t.get('status')}")
 
130
 
131
+ # graph tab ------------------------------------------------------
132
  with tabs[3]:
133
+ nodes, edges, cfg = build_agraph(res["papers"], res["umls"], res["drug_safety"])
134
+ hl = st.text_input("Highlight node:")
 
 
135
  if hl:
136
  pat = re.compile(re.escape(hl), re.I)
137
  for n in nodes:
138
  n.color = "#f1c40f" if pat.search(n.label) else "#d3d3d3"
139
  agraph(nodes, edges, cfg)
140
 
141
+ # metrics tab ----------------------------------------------------
142
  with tabs[4]:
143
+ G = build_nx([n.__dict__ for n in nodes], [e.__dict__ for e in edges])
 
144
  st.metric("Density", f"{get_density(G):.3f}")
 
145
  for nid, sc in get_top_hubs(G):
146
  lab = next((n.label for n in nodes if n.id == nid), nid)
147
  st.write(f"- {lab} {sc:.3f}")
148
 
149
+ # visuals --------------------------------------------------------
150
  with tabs[5]:
151
+ years = [p.get("published", "")[:4] for p in res["papers"] if p.get("published")]
152
  if years:
153
+ fig = px.histogram(years, nbins=12, title="Publication Year")
154
+ st.plotly_chart(fig)
155
 
156
+ # follow‑up QA ---------------------------------------------------
157
  st.markdown("---")
158
+ q = st.text_input("Ask follow‑up question:")
159
+ if st.button("Ask AI"):
 
 
 
 
 
160
  with st.spinner("Querying LLM …"):
161
+ ans = asyncio.run(answer_ai_question(q, context=st.session_state.last_query, llm=st.session_state.last_llm))
162
+ st.write(ans["answer"])
163
+
164
+
 
 
 
 
 
 
 
 
165
  if __name__ == "__main__":
166
+ render_ui()