mgbam commited on
Commit
5e95a20
Β·
verified Β·
1 Parent(s): 12007d6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -71
app.py CHANGED
@@ -1,13 +1,15 @@
1
- # app.py – MedGenesis AI (CPU-only, dual-LLM)
2
-
3
  import asyncio, re
4
  from pathlib import Path
5
- import streamlit as st, pandas as pd, plotly.express as px
 
 
 
6
  from fpdf import FPDF
7
  from streamlit_agraph import agraph
8
 
9
- from mcp.orchestrator import orchestrate_search, answer_ai_question
10
- from mcp.workspace import get_workspace, save_query
11
  from mcp.knowledge_graph import build_agraph
12
  from mcp.graph_metrics import build_nx, get_top_hubs, get_density
13
  from mcp.alerts import check_alerts
@@ -15,108 +17,165 @@ from mcp.alerts import check_alerts
15
  ROOT = Path(__file__).parent
16
  LOGO = ROOT / "assets" / "logo.png"
17
 
18
- # ────────────────────────────────────────────────────────────────
19
  def _pdf(papers):
20
- pdf=FPDF(); pdf.add_page(); pdf.set_font("Arial",size=11)
21
- for i,p in enumerate(papers,1):
22
- pdf.multi_cell(0,7,f"{i}. {p['title']} – {p['authors']}\n{p['summary']}\n{p['link']}\n")
 
 
 
23
  pdf.ln(1)
24
  return pdf.output(dest="S").encode("latin-1")
25
 
26
- def _sidebar_workspace():
27
  with st.sidebar:
28
  st.header("πŸ—‚οΈ Workspace")
29
- for i,item in enumerate(get_workspace(),1):
30
  with st.expander(f"{i}. {item['query']}"):
31
  st.write(item["result"]["ai_summary"])
 
 
 
 
 
32
 
33
- # ────────────────────────────────────────────────────────────────
34
  def render_ui():
35
  st.set_page_config("MedGenesis AI", layout="wide")
36
- _sidebar_workspace()
37
 
38
- # ── Header
39
- col1,col2 = st.columns([0.15,0.85])
40
  with col1:
41
- if LOGO.exists(): st.image(str(LOGO), width=100)
 
42
  with col2:
43
  st.markdown("## 🧬 **MedGenesis AI**")
44
- st.caption("Multi-source biomedical assistant Β· OpenAI / Gemini LLMs")
45
 
46
- llm = st.radio("Choose LLM Engine", ["openai","gemini"], horizontal=True)
47
- query = st.text_input("Enter biomedical question…",
48
- placeholder="e.g. CRISPR glioblastoma therapy")
49
 
50
- # πŸ”” Alert check
51
  if get_workspace():
52
  try:
53
- news = asyncio.run(check_alerts([q["query"] for q in get_workspace()]))
54
  if news:
55
- st.sidebar.subheader("πŸ”” New Papers")
56
- for q, lnks in news.items(): st.sidebar.write(f"**{q}** – {len(lnks)} new")
57
- except Exception: pass
58
-
59
- # ── Run
 
 
 
60
  if st.button("Run Search πŸš€") and query:
61
- with st.spinner("Gathering literature & biomedical data…"):
62
- res = asyncio.run(orchestrate_search(query, llm))
63
- st.success(f"Completed with **{res['llm_used'].title()}**")
 
 
64
 
65
- # Tabs
66
- tabs = st.tabs(["Results","Genes","Trials","Graph","Metrics","Visuals"])
67
- # 1) Results
68
  with tabs[0]:
69
- for i,p in enumerate(res["papers"],1):
70
- st.markdown(f"**{i}. [{p['title']}]({p['link']})** – *{p['authors']}*")
71
- st.write(p["summary"])
72
- st.download_button("πŸ“₯ CSV", pd.DataFrame(res["papers"]).to_csv(index=False),
73
- "papers.csv","text/csv")
74
- st.download_button("πŸ“„ PDF", _pdf(res["papers"]), "papers.pdf","application/pdf")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  st.info(res["ai_summary"])
76
- if st.button("Save to Workspace"): save_query(query,res); st.success("Saved!")
77
 
78
- # 2) Genes
79
  with tabs[1]:
80
- st.write("### Gene hits")
81
- for g in res["genes"]: st.write("-", g.get("name",g.get("geneid")))
82
-
83
- # 3) Trials
 
 
 
 
 
 
 
 
 
 
84
  with tabs[2]:
85
- if res["clinical_trials"]:
86
- for t in res["clinical_trials"]:
87
- st.write(f"**{t['NCTId'][0]}** – {t['BriefTitle'][0]}")
88
- else:
89
  st.info("No trials (rate-limited or none found).")
 
 
 
 
90
 
91
- # 4) Graph
92
  with tabs[3]:
93
- nodes,edges,cfg = build_agraph(res["papers"],res["umls"],res["drug_safety"])
94
- hl = st.text_input("Highlight node…", key="hl")
95
- if hl:
96
- pat=re.compile(re.escape(hl),re.I)
97
- for n in nodes:
98
- n.color="#f1c40f" if pat.search(n.label) else "#d3d3d3"
99
- agraph(nodes=nodes,edges=edges,config=cfg)
100
-
101
- # 5) Metrics
 
102
  with tabs[4]:
103
- G=build_nx([n.__dict__ for n in nodes],[e.__dict__ for e in edges])
104
- st.metric("Density",f"{get_density(G):.3f}")
105
- st.write("**Top hubs**")
106
- for nid,sc in get_top_hubs(G): st.write(nid, f"{sc:.3f}")
107
-
108
- # 6) Visuals
 
 
 
109
  with tabs[5]:
110
- yrs=[p["published"] for p in res["papers"] if p.get("published")]
111
- if yrs: st.plotly_chart(px.histogram(yrs,title="Publication Year"))
 
 
112
 
113
  # Follow-up Q-A
114
- q2=st.text_input("Ask follow-up:")
115
- if st.button("Ask AI"):
116
- ans=asyncio.run(answer_ai_question(q2,context=query,llm=llm))
 
 
 
117
  st.write(ans["answer"])
 
118
  else:
119
  st.info("Enter a question and press **Run Search πŸš€**")
120
 
121
- if __name__=="__main__":
 
 
122
  render_ui()
 
1
+ # app.py ── MedGenesis AI Β· dual-LLM Β· CPU-only
 
2
  import asyncio, re
3
  from pathlib import Path
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import plotly.express as px
8
  from fpdf import FPDF
9
  from streamlit_agraph import agraph
10
 
11
+ from mcp.orchestrator import orchestrate_search, answer_ai_question
12
+ from mcp.workspace import get_workspace, save_query
13
  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
 
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,
172
+ llm=llm))
173
  st.write(ans["answer"])
174
+
175
  else:
176
  st.info("Enter a question and press **Run Search πŸš€**")
177
 
178
+
179
+ # ────────────────────────────────────────────────────────────────
180
+ if __name__ == "__main__":
181
  render_ui()