Update app.py
Browse files
app.py
CHANGED
@@ -1,12 +1,14 @@
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
-
MedGenesis AI β Streamlit UI
|
4 |
|
5 |
β’ Dual-LLM selector (OpenAI | Gemini)
|
6 |
-
β’ Tabs:
|
|
|
7 |
β’ Robust PDF export (all Unicode β Latin-1 safe)
|
8 |
-
β’ Null-safe handling of
|
9 |
-
|
|
|
10 |
"""
|
11 |
|
12 |
from __future__ import annotations
|
@@ -16,7 +18,7 @@ from pathlib import Path
|
|
16 |
import streamlit as st
|
17 |
import pandas as pd
|
18 |
import plotly.express as px
|
19 |
-
from streamlit_agraph import agraph
|
20 |
from fpdf import FPDF
|
21 |
|
22 |
from mcp.orchestrator import orchestrate_search, answer_ai_question
|
@@ -65,7 +67,7 @@ def _workspace_sidebar():
|
|
65 |
with st.expander(f"{i}. {item['query']}"):
|
66 |
st.write(item["result"]["ai_summary"])
|
67 |
|
68 |
-
# ββ Main UI
|
69 |
def render_ui() -> None:
|
70 |
st.set_page_config("MedGenesis AI", layout="wide")
|
71 |
|
@@ -112,7 +114,7 @@ def render_ui() -> None:
|
|
112 |
st.info("Enter a question and press **Run Search π**")
|
113 |
return
|
114 |
|
115 |
-
# Guarantee
|
116 |
for k in (
|
117 |
"papers", "umls", "drug_safety", "genes", "mesh_defs",
|
118 |
"gene_disease", "clinical_trials", "variants"
|
@@ -125,7 +127,7 @@ def render_ui() -> None:
|
|
125 |
"Graph", "Metrics", "Visuals"
|
126 |
])
|
127 |
|
128 |
-
#
|
129 |
with tabs[0]:
|
130 |
st.subheader("Literature")
|
131 |
for i, p in enumerate(res["papers"], 1):
|
@@ -158,7 +160,7 @@ def render_ui() -> None:
|
|
158 |
st.subheader("AI summary")
|
159 |
st.info(res["ai_summary"])
|
160 |
|
161 |
-
#
|
162 |
with tabs[1]:
|
163 |
st.header("Gene / Variant signals")
|
164 |
clean = [g for g in res["genes"] if isinstance(g, dict)]
|
@@ -179,7 +181,7 @@ def render_ui() -> None:
|
|
179 |
if d:
|
180 |
st.write("-", d)
|
181 |
|
182 |
-
#
|
183 |
with tabs[2]:
|
184 |
st.header("Clinical trials")
|
185 |
if not res["clinical_trials"]:
|
@@ -189,7 +191,7 @@ def render_ui() -> None:
|
|
189 |
st.markdown(f"**{t['nctId']}** β {t['briefTitle']}")
|
190 |
st.write(f"Phase {t.get('phase')} | Status {t.get('status')}")
|
191 |
|
192 |
-
#
|
193 |
with tabs[3]:
|
194 |
st.header("Cancer variants (cBioPortal)")
|
195 |
if not res["variants"]:
|
@@ -197,7 +199,7 @@ def render_ui() -> None:
|
|
197 |
else:
|
198 |
st.json(res["variants"][:50])
|
199 |
|
200 |
-
#
|
201 |
with tabs[4]:
|
202 |
nodes, edges, cfg = build_agraph(
|
203 |
res["papers"], res["umls"], res["drug_safety"]
|
@@ -209,11 +211,19 @@ def render_ui() -> None:
|
|
209 |
n.color = "#f1c40f" if pat.search(n.label) else "#d3d3d3"
|
210 |
agraph(nodes, edges, cfg)
|
211 |
|
212 |
-
#
|
213 |
with tabs[5]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
G = build_nx(
|
215 |
[n.__dict__ for n in nodes],
|
216 |
-
|
217 |
)
|
218 |
st.metric("Density", f"{get_density(G):.3f}")
|
219 |
st.markdown("**Top hubs**")
|
@@ -221,14 +231,14 @@ def render_ui() -> None:
|
|
221 |
lab = next((n.label for n in nodes if n.id == nid), nid)
|
222 |
st.write(f"- {lab} {sc:.3f}")
|
223 |
|
224 |
-
#
|
225 |
with tabs[6]:
|
226 |
years = [p.get("published", "")[:4] for p in res["papers"] if p.get("published")]
|
227 |
if years:
|
228 |
st.plotly_chart(px.histogram(years, nbins=12,
|
229 |
title="Publication Year"))
|
230 |
|
231 |
-
#
|
232 |
st.markdown("---")
|
233 |
st.text_input("Ask follow-up question:", key="followup_input")
|
234 |
|
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
+
MedGenesis AI β Streamlit UI (v3.1 β’ June 2025)
|
4 |
|
5 |
β’ Dual-LLM selector (OpenAI | Gemini)
|
6 |
+
β’ Tabs:
|
7 |
+
Results | Genes | Trials | Variants | Graph | Metrics | Visuals
|
8 |
β’ Robust PDF export (all Unicode β Latin-1 safe)
|
9 |
+
β’ Null-safe handling of RuntimeError / HTTPStatusError placeholders
|
10 |
+
β’ Metrics tab now converts Edge objects β {'source', 'target'} safely,
|
11 |
+
preventing the KeyError you just saw.
|
12 |
"""
|
13 |
|
14 |
from __future__ import annotations
|
|
|
18 |
import streamlit as st
|
19 |
import pandas as pd
|
20 |
import plotly.express as px
|
21 |
+
from streamlit_agraph import agraph, Node, Edge
|
22 |
from fpdf import FPDF
|
23 |
|
24 |
from mcp.orchestrator import orchestrate_search, answer_ai_question
|
|
|
67 |
with st.expander(f"{i}. {item['query']}"):
|
68 |
st.write(item["result"]["ai_summary"])
|
69 |
|
70 |
+
# ββ Main UI ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
71 |
def render_ui() -> None:
|
72 |
st.set_page_config("MedGenesis AI", layout="wide")
|
73 |
|
|
|
114 |
st.info("Enter a question and press **Run Search π**")
|
115 |
return
|
116 |
|
117 |
+
# Guarantee keys
|
118 |
for k in (
|
119 |
"papers", "umls", "drug_safety", "genes", "mesh_defs",
|
120 |
"gene_disease", "clinical_trials", "variants"
|
|
|
127 |
"Graph", "Metrics", "Visuals"
|
128 |
])
|
129 |
|
130 |
+
# Results tab -----------------------------------------------------
|
131 |
with tabs[0]:
|
132 |
st.subheader("Literature")
|
133 |
for i, p in enumerate(res["papers"], 1):
|
|
|
160 |
st.subheader("AI summary")
|
161 |
st.info(res["ai_summary"])
|
162 |
|
163 |
+
# Genes tab -------------------------------------------------------
|
164 |
with tabs[1]:
|
165 |
st.header("Gene / Variant signals")
|
166 |
clean = [g for g in res["genes"] if isinstance(g, dict)]
|
|
|
181 |
if d:
|
182 |
st.write("-", d)
|
183 |
|
184 |
+
# Trials tab ------------------------------------------------------
|
185 |
with tabs[2]:
|
186 |
st.header("Clinical trials")
|
187 |
if not res["clinical_trials"]:
|
|
|
191 |
st.markdown(f"**{t['nctId']}** β {t['briefTitle']}")
|
192 |
st.write(f"Phase {t.get('phase')} | Status {t.get('status')}")
|
193 |
|
194 |
+
# Variants tab ----------------------------------------------------
|
195 |
with tabs[3]:
|
196 |
st.header("Cancer variants (cBioPortal)")
|
197 |
if not res["variants"]:
|
|
|
199 |
else:
|
200 |
st.json(res["variants"][:50])
|
201 |
|
202 |
+
# Graph tab -------------------------------------------------------
|
203 |
with tabs[4]:
|
204 |
nodes, edges, cfg = build_agraph(
|
205 |
res["papers"], res["umls"], res["drug_safety"]
|
|
|
211 |
n.color = "#f1c40f" if pat.search(n.label) else "#d3d3d3"
|
212 |
agraph(nodes, edges, cfg)
|
213 |
|
214 |
+
# Metrics tab -----------------------------------------------------
|
215 |
with tabs[5]:
|
216 |
+
# Convert Edge objects β dicts with guaranteed 'source'/'target'
|
217 |
+
edge_dicts = [
|
218 |
+
{"source": getattr(e, "source", getattr(e, "from", "")),
|
219 |
+
"target": getattr(e, "target", getattr(e, "to", ""))}
|
220 |
+
for e in edges if isinstance(e, Edge)
|
221 |
+
if getattr(e, "source", getattr(e, "from", None))
|
222 |
+
and getattr(e, "target", getattr(e, "to", None))
|
223 |
+
]
|
224 |
G = build_nx(
|
225 |
[n.__dict__ for n in nodes],
|
226 |
+
edge_dicts,
|
227 |
)
|
228 |
st.metric("Density", f"{get_density(G):.3f}")
|
229 |
st.markdown("**Top hubs**")
|
|
|
231 |
lab = next((n.label for n in nodes if n.id == nid), nid)
|
232 |
st.write(f"- {lab} {sc:.3f}")
|
233 |
|
234 |
+
# Visuals tab -----------------------------------------------------
|
235 |
with tabs[6]:
|
236 |
years = [p.get("published", "")[:4] for p in res["papers"] if p.get("published")]
|
237 |
if years:
|
238 |
st.plotly_chart(px.histogram(years, nbins=12,
|
239 |
title="Publication Year"))
|
240 |
|
241 |
+
# Follow-up QA ----------------------------------------------------
|
242 |
st.markdown("---")
|
243 |
st.text_input("Ask follow-up question:", key="followup_input")
|
244 |
|