mgbam commited on
Commit
4372b0a
·
verified ·
1 Parent(s): 5e95a20

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -66
app.py CHANGED
@@ -1,4 +1,14 @@
1
- # app.py ── MedGenesis AI · dual-LLM · CPU-only
 
 
 
 
 
 
 
 
 
 
2
  import asyncio, re
3
  from pathlib import Path
4
 
@@ -14,158 +24,156 @@ from mcp.knowledge_graph import build_agraph
14
  from mcp.graph_metrics import build_nx, get_top_hubs, get_density
15
  from mcp.alerts import check_alerts
16
 
17
- ROOT = Path(__file__).parent
18
- LOGO = ROOT / "assets" / "logo.png"
19
 
20
- # ─────────────────────────── helpers ────────────────────────────
21
  def _pdf(papers):
22
  pdf = FPDF(); pdf.add_page(); pdf.set_font("Arial", size=11)
23
  pdf.cell(200, 8, "MedGenesis AI – Results", ln=True, align="C"); pdf.ln(3)
24
  for i, p in enumerate(papers, 1):
25
- pdf.set_font("Arial", "B", 11); pdf.multi_cell(0, 7, f"{i}. {p['title']}")
 
26
  pdf.set_font("Arial", "", 9)
27
  pdf.multi_cell(0, 6, f"{p['authors']}\n{p['summary']}\n{p['link']}\n")
28
  pdf.ln(1)
29
  return pdf.output(dest="S").encode("latin-1")
30
 
31
- def _workspace_sidebar():
32
  with st.sidebar:
33
  st.header("🗂️ Workspace")
34
- for i, item in enumerate(get_workspace(), 1):
 
 
 
 
35
  with st.expander(f"{i}. {item['query']}"):
36
  st.write(item["result"]["ai_summary"])
37
- df = pd.DataFrame(item["result"]["papers"])
38
- st.download_button("CSV", df.to_csv(index=False),
39
- f"ws_{i}.csv", "text/csv")
40
- if not get_workspace():
41
- st.info("Run a search and press **Save** to fill your workspace.")
42
 
43
- # ─────────────────────────── UI ────────────────────────────────
44
  def render_ui():
45
  st.set_page_config("MedGenesis AI", layout="wide")
46
- _workspace_sidebar()
47
 
48
- # Logo & title
49
- col1, col2 = st.columns([0.15, 0.85])
50
- with col1:
51
  if LOGO.exists():
52
  st.image(str(LOGO), width=105)
53
- with col2:
54
  st.markdown("## 🧬 **MedGenesis AI**")
55
  st.caption("Multi-source biomedical assistant · OpenAI / Gemini")
56
 
57
- llm = st.radio("LLM Engine", ["openai", "gemini"], horizontal=True)
58
- query = st.text_input("Enter biomedical question",
59
- placeholder="e.g. CRISPR for glioblastoma")
60
 
61
- # 🔔 alert check
62
  if get_workspace():
63
  try:
64
  news = asyncio.run(check_alerts([w["query"] for w in get_workspace()]))
65
  if news:
66
- with st.sidebar:
67
- st.subheader("🔔 New Papers")
68
- for q, links in news.items():
69
- st.write(f"**{q}** – {len(links)} new")
70
  except Exception as e:
71
  st.sidebar.warning(f"Alert check failed: {e}")
72
 
73
- # Run
74
  if st.button("Run Search 🚀") and query:
75
- with st.spinner("Gathering literature & databases …"):
76
  res = asyncio.run(orchestrate_search(query, llm=llm))
77
- st.success(f"Finished with **{res['llm_used'].title()}**")
78
 
79
- tabs = st.tabs(["Results", "Genes", "Trials", "Graph", "Metrics", "Visuals"])
 
80
 
81
- # ── Results
82
  with tabs[0]:
83
  for i, p in enumerate(res["papers"], 1):
84
  st.markdown(f"**{i}. [{p['title']}]({p['link']})** *{p['authors']}*")
85
- st.markdown(f"<span style='color:gray'>{p['summary']}</span>",
86
- unsafe_allow_html=True)
87
 
88
- col_a, col_b = st.columns(2)
89
- with col_a:
90
- st.download_button("📥 CSV",
91
  pd.DataFrame(res["papers"]).to_csv(index=False),
92
  "papers.csv", "text/csv")
93
- with col_b:
94
- st.download_button("📄 PDF", _pdf(res["papers"]),
95
  "papers.pdf", "application/pdf")
96
 
97
- if st.button("💾 Save to Workspace"):
98
  save_query(query, res)
99
- st.success("Saved!")
100
 
101
- st.subheader("🧠 UMLS Concepts")
102
  for c in res["umls"]:
103
  if c.get("cui"):
104
  st.write(f"- **{c['name']}** ({c['cui']})")
105
 
106
- st.subheader("💊 OpenFDA Safety")
107
  for d in res["drug_safety"]:
108
  st.json(d)
109
 
110
- st.subheader("🤖 AI Summary")
111
  st.info(res["ai_summary"])
112
 
113
- # ── Genes
114
  with tabs[1]:
115
- st.header("Gene Signals")
116
  for g in res["genes"]:
117
- st.write(f"- **{g.get('name', g.get('geneid'))}** "
118
  f"{g.get('description', '')}")
119
  if res["gene_disease"]:
120
- st.markdown("### DisGeNET Links")
121
  st.json(res["gene_disease"][:15])
122
  if res["mesh_defs"]:
123
- st.markdown("### MeSH Definitions")
124
  for d in res["mesh_defs"]:
125
  if d:
126
  st.write("-", d)
127
 
128
- # ── Trials
129
  with tabs[2]:
130
- st.header("Clinical Trials")
131
  if not res["clinical_trials"]:
132
  st.info("No trials (rate-limited or none found).")
133
  for t in res["clinical_trials"]:
134
  st.markdown(f"**{t['NCTId'][0]}** – {t['BriefTitle'][0]}")
135
- st.write(f"Phase: {t.get('Phase', [''])[0]} | "
136
- f"Status: {t['OverallStatus'][0]}")
137
 
138
- # ── Graph
139
  with tabs[3]:
140
  nodes, edges, cfg = build_agraph(res["papers"],
141
- res["umls"], res["drug_safety"])
142
- highlight = st.text_input("Highlight node name:")
143
- if highlight:
144
- pat = re.compile(re.escape(highlight), re.I)
 
145
  for n in nodes:
146
  n.color = "#f1c40f" if pat.search(n.label) else "#d3d3d3"
147
- agraph(nodes=nodes, edges=edges, config=cfg)
148
 
149
- # ── Metrics
150
  with tabs[4]:
151
  G = build_nx([n.__dict__ for n in nodes],
152
  [e.__dict__ for e in edges])
153
  st.metric("Density", f"{get_density(G):.3f}")
154
- st.markdown("#### Top Hub Nodes")
155
  for nid, sc in get_top_hubs(G):
156
  lab = next((n.label for n in nodes if n.id == nid), nid)
157
- st.write(f"- **{lab}** – {sc:.3f}")
158
 
159
- # ── Visuals
160
  with tabs[5]:
161
  years = [p["published"] for p in res["papers"] if p.get("published")]
162
  if years:
163
  st.plotly_chart(px.histogram(years, nbins=12,
164
  title="Publication Year"))
165
 
166
- # Follow-up Q-A
167
  st.markdown("---")
168
- follow = st.text_input("Ask follow-up question:")
169
  if st.button("Ask AI"):
170
  ans = asyncio.run(answer_ai_question(follow,
171
  context=query,
@@ -175,7 +183,6 @@ def render_ui():
175
  else:
176
  st.info("Enter a question and press **Run Search 🚀**")
177
 
178
-
179
- # ────────────────────────────────────────────────────────────────
180
  if __name__ == "__main__":
181
  render_ui()
 
1
+ #!/usr/bin/env python3
2
+ """MedGenesis AI — CPU-only, dual-LLM (OpenAI / Gemini)"""
3
+
4
+ # ────────────── FIX: create a writable Streamlit data dir ──────────────
5
+ import os, pathlib
6
+ os.environ["STREAMLIT_DATA_DIR"] = "/tmp/.streamlit"
7
+ os.environ["XDG_STATE_HOME"] = "/tmp"
8
+ os.environ["STREAMLIT_BROWSER_GATHERUSAGESTATS"] = "false"
9
+ pathlib.Path("/tmp/.streamlit").mkdir(parents=True, exist_ok=True)
10
+ # ───────────────────────────────────────────────────────────────────────
11
+
12
  import asyncio, re
13
  from pathlib import Path
14
 
 
24
  from mcp.graph_metrics import build_nx, get_top_hubs, get_density
25
  from mcp.alerts import check_alerts
26
 
27
+ ROOT = Path(__file__).parent
28
+ LOGO = ROOT / "assets" / "logo.png"
29
 
30
+ # ──────────────── small helpers ─────────────────
31
  def _pdf(papers):
32
  pdf = FPDF(); pdf.add_page(); pdf.set_font("Arial", size=11)
33
  pdf.cell(200, 8, "MedGenesis AI – Results", ln=True, align="C"); pdf.ln(3)
34
  for i, p in enumerate(papers, 1):
35
+ pdf.set_font("Arial", "B", 11)
36
+ pdf.multi_cell(0, 7, f"{i}. {p['title']}")
37
  pdf.set_font("Arial", "", 9)
38
  pdf.multi_cell(0, 6, f"{p['authors']}\n{p['summary']}\n{p['link']}\n")
39
  pdf.ln(1)
40
  return pdf.output(dest="S").encode("latin-1")
41
 
42
+ def _sidebar_workspace():
43
  with st.sidebar:
44
  st.header("🗂️ Workspace")
45
+ ws = get_workspace()
46
+ if not ws:
47
+ st.info("Run a search then press **Save** to populate this list.")
48
+ return
49
+ for i, item in enumerate(ws, 1):
50
  with st.expander(f"{i}. {item['query']}"):
51
  st.write(item["result"]["ai_summary"])
 
 
 
 
 
52
 
53
+ # ──────────────── Streamlit UI ──────────────────
54
  def render_ui():
55
  st.set_page_config("MedGenesis AI", layout="wide")
56
+ _sidebar_workspace()
57
 
58
+ # header
59
+ c1, c2 = st.columns([0.15, 0.85])
60
+ with c1:
61
  if LOGO.exists():
62
  st.image(str(LOGO), width=105)
63
+ with c2:
64
  st.markdown("## 🧬 **MedGenesis AI**")
65
  st.caption("Multi-source biomedical assistant · OpenAI / Gemini")
66
 
67
+ llm = st.radio("LLM engine", ["openai", "gemini"], horizontal=True)
68
+ query = st.text_input("Enter biomedical question",
69
+ placeholder="e.g. CRISPR glioblastoma therapy")
70
 
71
+ # alert check
72
  if get_workspace():
73
  try:
74
  news = asyncio.run(check_alerts([w["query"] for w in get_workspace()]))
75
  if news:
76
+ st.sidebar.subheader("🔔 New papers")
77
+ for q, lst in news.items():
78
+ st.sidebar.write(f"**{q}** {len(lst)} new")
 
79
  except Exception as e:
80
  st.sidebar.warning(f"Alert check failed: {e}")
81
 
 
82
  if st.button("Run Search 🚀") and query:
83
+ with st.spinner("Collecting literature & biomedical data …"):
84
  res = asyncio.run(orchestrate_search(query, llm=llm))
85
+ st.success(f"Completed with **{res['llm_used'].title()}**")
86
 
87
+ tabs = st.tabs(["Results", "Genes", "Trials", "Graph",
88
+ "Metrics", "Visuals"])
89
 
90
+ # Results -----------------------------------------------------
91
  with tabs[0]:
92
  for i, p in enumerate(res["papers"], 1):
93
  st.markdown(f"**{i}. [{p['title']}]({p['link']})** *{p['authors']}*")
94
+ st.write(p["summary"])
 
95
 
96
+ col1, col2 = st.columns(2)
97
+ with col1:
98
+ st.download_button("CSV",
99
  pd.DataFrame(res["papers"]).to_csv(index=False),
100
  "papers.csv", "text/csv")
101
+ with col2:
102
+ st.download_button("PDF", _pdf(res["papers"]),
103
  "papers.pdf", "application/pdf")
104
 
105
+ if st.button("💾 Save"):
106
  save_query(query, res)
107
+ st.success("Saved to workspace")
108
 
109
+ st.subheader("UMLS concepts")
110
  for c in res["umls"]:
111
  if c.get("cui"):
112
  st.write(f"- **{c['name']}** ({c['cui']})")
113
 
114
+ st.subheader("OpenFDA safety")
115
  for d in res["drug_safety"]:
116
  st.json(d)
117
 
118
+ st.subheader("AI summary")
119
  st.info(res["ai_summary"])
120
 
121
+ # Genes -------------------------------------------------------
122
  with tabs[1]:
123
+ st.header("Gene / Variant signals")
124
  for g in res["genes"]:
125
+ st.write(f"- **{g.get('name', g.get('geneid'))}** "
126
  f"{g.get('description', '')}")
127
  if res["gene_disease"]:
128
+ st.markdown("### DisGeNET links")
129
  st.json(res["gene_disease"][:15])
130
  if res["mesh_defs"]:
131
+ st.markdown("### MeSH definitions")
132
  for d in res["mesh_defs"]:
133
  if d:
134
  st.write("-", d)
135
 
136
+ # Trials ------------------------------------------------------
137
  with tabs[2]:
138
+ st.header("Clinical trials")
139
  if not res["clinical_trials"]:
140
  st.info("No trials (rate-limited or none found).")
141
  for t in res["clinical_trials"]:
142
  st.markdown(f"**{t['NCTId'][0]}** – {t['BriefTitle'][0]}")
143
+ st.write(f"Phase {t.get('Phase', [''])[0]} | "
144
+ f"Status {t['OverallStatus'][0]}")
145
 
146
+ # Graph -------------------------------------------------------
147
  with tabs[3]:
148
  nodes, edges, cfg = build_agraph(res["papers"],
149
+ res["umls"],
150
+ res["drug_safety"])
151
+ hl = st.text_input("Highlight node:", key="hl")
152
+ if hl:
153
+ pat = re.compile(re.escape(hl), re.I)
154
  for n in nodes:
155
  n.color = "#f1c40f" if pat.search(n.label) else "#d3d3d3"
156
+ agraph(nodes, edges, cfg)
157
 
158
+ # Metrics -----------------------------------------------------
159
  with tabs[4]:
160
  G = build_nx([n.__dict__ for n in nodes],
161
  [e.__dict__ for e in edges])
162
  st.metric("Density", f"{get_density(G):.3f}")
163
+ st.markdown("**Top hubs**")
164
  for nid, sc in get_top_hubs(G):
165
  lab = next((n.label for n in nodes if n.id == nid), nid)
166
+ st.write(f"- {lab} {sc:.3f}")
167
 
168
+ # Visuals -----------------------------------------------------
169
  with tabs[5]:
170
  years = [p["published"] for p in res["papers"] if p.get("published")]
171
  if years:
172
  st.plotly_chart(px.histogram(years, nbins=12,
173
  title="Publication Year"))
174
 
 
175
  st.markdown("---")
176
+ follow = st.text_input("Ask follow-up:")
177
  if st.button("Ask AI"):
178
  ans = asyncio.run(answer_ai_question(follow,
179
  context=query,
 
183
  else:
184
  st.info("Enter a question and press **Run Search 🚀**")
185
 
186
+ # entry-point
 
187
  if __name__ == "__main__":
188
  render_ui()