Update app.py
Browse files
app.py
CHANGED
|
@@ -25,7 +25,7 @@ from PIL import Image
|
|
| 25 |
from functools import wraps
|
| 26 |
|
| 27 |
# ─────────────────────────────────────────────
|
| 28 |
-
# CACHE SYSTEM
|
| 29 |
# ─────────────────────────────────────────────
|
| 30 |
CACHE_DIR = "./cache"
|
| 31 |
os.makedirs(CACHE_DIR, exist_ok=True)
|
|
@@ -55,7 +55,6 @@ def cache_set(endpoint: str, query: str, data):
|
|
| 55 |
# RETRY DECORATOR
|
| 56 |
# ─────────────────────────────────────────────
|
| 57 |
def retry(max_attempts=3, delay=1):
|
| 58 |
-
"""Decorator to retry a function on exception."""
|
| 59 |
def decorator(func):
|
| 60 |
@wraps(func)
|
| 61 |
def wrapper(*args, **kwargs):
|
|
@@ -72,24 +71,23 @@ def retry(max_attempts=3, delay=1):
|
|
| 72 |
return decorator
|
| 73 |
|
| 74 |
# ─────────────────────────────────────────────
|
| 75 |
-
# LAB JOURNAL
|
| 76 |
# ─────────────────────────────────────────────
|
| 77 |
JOURNAL_FILE = "./lab_journal.csv"
|
| 78 |
-
# Уніфікована система кодування (як у Learning Playground)
|
| 79 |
JOURNAL_CATEGORIES = [
|
| 80 |
-
# Real Data Tools (
|
| 81 |
-
"
|
| 82 |
-
"
|
| 83 |
-
"
|
| 84 |
-
"
|
| 85 |
-
"
|
| 86 |
-
"
|
| 87 |
-
# Learning Sandbox
|
| 88 |
-
"
|
| 89 |
-
"
|
| 90 |
-
"
|
| 91 |
-
"
|
| 92 |
-
"
|
| 93 |
"Manual"
|
| 94 |
]
|
| 95 |
|
|
@@ -176,12 +174,11 @@ CT_BASE = "https://clinicaltrials.gov/api/v2"
|
|
| 176 |
|
| 177 |
@retry(max_attempts=3, delay=1)
|
| 178 |
def pubmed_count(query: str) -> int:
|
| 179 |
-
"""Return paper count for a PubMed query (cached)."""
|
| 180 |
cached = cache_get("pubmed_count", query)
|
| 181 |
if cached is not None:
|
| 182 |
return cached
|
| 183 |
try:
|
| 184 |
-
time.sleep(0.34)
|
| 185 |
r = requests.get(
|
| 186 |
f"{PUBMED_BASE}/esearch.fcgi",
|
| 187 |
params={"db": "pubmed", "term": query, "rettype": "count", "retmode": "json"},
|
|
@@ -192,19 +189,11 @@ def pubmed_count(query: str) -> int:
|
|
| 192 |
count = int(data.get("esearchresult", {}).get("count", 0))
|
| 193 |
cache_set("pubmed_count", query, count)
|
| 194 |
return count
|
| 195 |
-
except
|
| 196 |
-
print(f"Timeout for query: {query}")
|
| 197 |
-
return -1
|
| 198 |
-
except requests.exceptions.RequestException as e:
|
| 199 |
-
print(f"Request error for {query}: {e}")
|
| 200 |
-
return -1
|
| 201 |
-
except (KeyError, ValueError) as e:
|
| 202 |
-
print(f"Parsing error for {query}: {e}")
|
| 203 |
return -1
|
| 204 |
|
| 205 |
@retry(max_attempts=3, delay=1)
|
| 206 |
def pubmed_search(query: str, retmax: int = 10) -> list:
|
| 207 |
-
"""Return list of PMIDs (cached)."""
|
| 208 |
cached = cache_get("pubmed_search", f"{query}_{retmax}")
|
| 209 |
if cached is not None:
|
| 210 |
return cached
|
|
@@ -225,7 +214,6 @@ def pubmed_search(query: str, retmax: int = 10) -> list:
|
|
| 225 |
|
| 226 |
@retry(max_attempts=3, delay=1)
|
| 227 |
def pubmed_summary(pmids: list) -> list:
|
| 228 |
-
"""Fetch summaries for a list of PMIDs."""
|
| 229 |
if not pmids:
|
| 230 |
return []
|
| 231 |
cached = cache_get("pubmed_summary", ",".join(pmids))
|
|
@@ -248,7 +236,6 @@ def pubmed_summary(pmids: list) -> list:
|
|
| 248 |
|
| 249 |
@retry(max_attempts=3, delay=1)
|
| 250 |
def ot_query(gql: str, variables: dict = None) -> dict:
|
| 251 |
-
"""Run an OpenTargets GraphQL query (cached)."""
|
| 252 |
key = json.dumps({"q": gql, "v": variables}, sort_keys=True)
|
| 253 |
cached = cache_get("ot_gql", key)
|
| 254 |
if cached is not None:
|
|
@@ -262,16 +249,13 @@ def ot_query(gql: str, variables: dict = None) -> dict:
|
|
| 262 |
r.raise_for_status()
|
| 263 |
data = r.json()
|
| 264 |
if "errors" in data:
|
| 265 |
-
print(f"GraphQL errors: {data['errors']}")
|
| 266 |
return {"error": data["errors"]}
|
| 267 |
cache_set("ot_gql", key, data)
|
| 268 |
return data
|
| 269 |
except Exception as e:
|
| 270 |
-
print(f"OT query error: {e}")
|
| 271 |
return {"error": str(e)}
|
| 272 |
-
|
| 273 |
# ─────────────────────────────────────────────
|
| 274 |
-
# TAB A1 — GRAY ZONES EXPLORER
|
| 275 |
# ─────────────────────────────────────────────
|
| 276 |
|
| 277 |
def a1_run(cancer_type: str):
|
|
@@ -324,13 +308,13 @@ def a1_run(cancer_type: str):
|
|
| 324 |
)
|
| 325 |
|
| 326 |
gaps_md = "\n\n---\n\n".join(gap_cards) if gap_cards else "No data available."
|
| 327 |
-
journal_log("
|
| 328 |
source_note = f"*Source: PubMed E-utilities | Date: {today}*"
|
| 329 |
return img, gaps_md + "\n\n" + source_note
|
| 330 |
|
| 331 |
|
| 332 |
# ─────────────────────────────────────────────
|
| 333 |
-
# TAB A2 — UNDERSTUDIED TARGET FINDER
|
| 334 |
# ─────────────────────────────────────────────
|
| 335 |
|
| 336 |
DEPMAP_URL = "https://ndownloader.figshare.com/files/40448549" # CRISPR_gene_effect.csv (public)
|
|
@@ -458,14 +442,14 @@ def a2_run(cancer_type: str):
|
|
| 458 |
f"and replace `_load_depmap_sample()` in `app.py`."
|
| 459 |
)
|
| 460 |
if not result_df.empty:
|
| 461 |
-
journal_log("
|
| 462 |
else:
|
| 463 |
-
journal_log("
|
| 464 |
return result_df, note
|
| 465 |
|
| 466 |
|
| 467 |
# ─────────────────────────────────────────────
|
| 468 |
-
# TAB A3 — REAL VARIANT LOOKUP
|
| 469 |
# ─────────────────────────────────────────────
|
| 470 |
|
| 471 |
def a3_run(hgvs: str):
|
|
@@ -586,12 +570,12 @@ def a3_run(hgvs: str):
|
|
| 586 |
)
|
| 587 |
|
| 588 |
result_parts.append(f"\n*Source: ClinVar E-utilities + gnomAD GraphQL | Date: {today}*")
|
| 589 |
-
journal_log("
|
| 590 |
return "\n\n".join(result_parts)
|
| 591 |
|
| 592 |
|
| 593 |
# ─────────────────────────────────────────────
|
| 594 |
-
# TAB A4 — LITERATURE GAP FINDER
|
| 595 |
# ─────────────────────────────────────────────
|
| 596 |
|
| 597 |
def a4_run(cancer_type: str, keyword: str):
|
|
@@ -652,12 +636,12 @@ def a4_run(cancer_type: str, keyword: str):
|
|
| 652 |
|
| 653 |
summary = "\n\n".join(gap_text)
|
| 654 |
summary += f"\n\n*Source: PubMed E-utilities | Date: {today}*"
|
| 655 |
-
journal_log("
|
| 656 |
return img, summary
|
| 657 |
|
| 658 |
|
| 659 |
# ─────────────────────────────────────────────
|
| 660 |
-
# TAB A5 — DRUGGABLE ORPHANS
|
| 661 |
# ─────────────────────────────────────────────
|
| 662 |
|
| 663 |
def a5_run(cancer_type: str):
|
|
@@ -749,7 +733,7 @@ def a5_run(cancer_type: str):
|
|
| 749 |
f"*Source: OpenTargets GraphQL + ClinicalTrials.gov v2 | Date: {today}*\n\n"
|
| 750 |
f"*Orphan = no approved drug (OpenTargets knownDrugs.count = 0)*"
|
| 751 |
)
|
| 752 |
-
journal_log("
|
| 753 |
return df, note
|
| 754 |
|
| 755 |
|
|
@@ -762,7 +746,7 @@ SIMULATED_BANNER = (
|
|
| 762 |
"for educational purposes only. Results do NOT reflect real experimental outcomes."
|
| 763 |
)
|
| 764 |
|
| 765 |
-
# ── TAB B1 — miRNA Explorer ──────────────────
|
| 766 |
|
| 767 |
MIRNA_DB = {
|
| 768 |
"BRCA2": {
|
|
@@ -832,11 +816,11 @@ def b1_run(gene: str):
|
|
| 832 |
"Expression log2FC": changes,
|
| 833 |
})
|
| 834 |
context = f"\n\n**Cancer Context:** {db['cancer_context']}"
|
| 835 |
-
journal_log("
|
| 836 |
return img, df.to_markdown(index=False) + context
|
| 837 |
|
| 838 |
|
| 839 |
-
# ── TAB B2 — siRNA Targets ───────────────────
|
| 840 |
|
| 841 |
SIRNA_DB = {
|
| 842 |
"LUAD": {
|
|
@@ -894,11 +878,11 @@ def b2_run(cancer: str):
|
|
| 894 |
"Off-target Risk": off_risk,
|
| 895 |
"Delivery Challenge": delivery,
|
| 896 |
})
|
| 897 |
-
journal_log("
|
| 898 |
return img, df.to_markdown(index=False)
|
| 899 |
|
| 900 |
|
| 901 |
-
# ── TAB B3 — LNP Corona Simulator ───────────────
|
| 902 |
|
| 903 |
def b3_run(peg_mol_pct: float, ionizable_pct: float, helper_pct: float,
|
| 904 |
chol_pct: float, particle_size_nm: float, serum_pct: float):
|
|
@@ -950,11 +934,11 @@ def b3_run(peg_mol_pct: float, ionizable_pct: float, helper_pct: float,
|
|
| 950 |
+ ("High ApoE → enhanced brain/liver targeting via LDLR pathway." if apoe_pct > 25
|
| 951 |
else "Low ApoE → reduced receptor-mediated uptake.")
|
| 952 |
)
|
| 953 |
-
journal_log("
|
| 954 |
return img, interpretation
|
| 955 |
|
| 956 |
|
| 957 |
-
# ── TAB B4 — Flow Corona (Vroman Kinetics) ──────
|
| 958 |
|
| 959 |
def b4_run(time_points: int, kon_albumin: float, kon_apoe: float,
|
| 960 |
koff_albumin: float, koff_apoe: float):
|
|
@@ -994,11 +978,11 @@ def b4_run(time_points: int, kon_albumin: float, kon_apoe: float,
|
|
| 994 |
"The Vroman effect describes sequential protein displacement: "
|
| 995 |
"abundant proteins (albumin) adsorb first, then are displaced by higher-affinity proteins (ApoE, fibrinogen)."
|
| 996 |
)
|
| 997 |
-
journal_log("
|
| 998 |
return img, note
|
| 999 |
|
| 1000 |
|
| 1001 |
-
# ── TAB B5 — Variant Concepts ───────────────────
|
| 1002 |
|
| 1003 |
VARIANT_RULES = {
|
| 1004 |
"Pathogenic": {
|
|
@@ -1048,7 +1032,7 @@ def b5_run(classification: str):
|
|
| 1048 |
f"> ⚠️ SIMULATED — This is a rule-based educational model only. "
|
| 1049 |
f"Real variant classification requires expert review and full ACMG/AMP criteria evaluation."
|
| 1050 |
)
|
| 1051 |
-
journal_log("
|
| 1052 |
return output
|
| 1053 |
# ─────────────────────────────────────────────
|
| 1054 |
# GRADIO UI ASSEMBLY
|
|
@@ -1099,7 +1083,7 @@ def build_app():
|
|
| 1099 |
with gr.Tabs():
|
| 1100 |
|
| 1101 |
# ── A1 ──
|
| 1102 |
-
with gr.Tab("
|
| 1103 |
gr.Markdown(
|
| 1104 |
"Identify underexplored biological processes in a cancer type "
|
| 1105 |
"using live PubMed + OpenTargets data."
|
|
@@ -1120,7 +1104,7 @@ def build_app():
|
|
| 1120 |
a1_btn.click(a1_run, inputs=[a1_cancer], outputs=[a1_heatmap, a1_gaps])
|
| 1121 |
|
| 1122 |
# ── A2 ──
|
| 1123 |
-
with gr.Tab("
|
| 1124 |
gr.Markdown(
|
| 1125 |
"Find essential genes with high research gap index "
|
| 1126 |
"(high essentiality, low publication coverage)."
|
|
@@ -1150,7 +1134,7 @@ def build_app():
|
|
| 1150 |
a2_btn.click(a2_run, inputs=[a2_cancer], outputs=[a2_table, a2_note])
|
| 1151 |
|
| 1152 |
# ── A3 ──
|
| 1153 |
-
with gr.Tab("
|
| 1154 |
gr.Markdown(
|
| 1155 |
"Look up a variant in **ClinVar** and **gnomAD**. "
|
| 1156 |
"Results are fetched live — never hallucinated."
|
|
@@ -1177,7 +1161,7 @@ def build_app():
|
|
| 1177 |
a3_btn.click(a3_run, inputs=[a3_hgvs], outputs=[a3_result])
|
| 1178 |
|
| 1179 |
# ── A4 ──
|
| 1180 |
-
with gr.Tab("
|
| 1181 |
gr.Markdown(
|
| 1182 |
"Visualize publication trends over 10 years and detect "
|
| 1183 |
"years with low research activity."
|
|
@@ -1200,7 +1184,7 @@ def build_app():
|
|
| 1200 |
a4_btn.click(a4_run, inputs=[a4_cancer, a4_kw], outputs=[a4_chart, a4_gaps])
|
| 1201 |
|
| 1202 |
# ── A5 ──
|
| 1203 |
-
with gr.Tab("
|
| 1204 |
gr.Markdown(
|
| 1205 |
"Identify cancer-associated essential genes with **no approved drug** "
|
| 1206 |
"and **no active clinical trial**."
|
|
@@ -1221,8 +1205,8 @@ def build_app():
|
|
| 1221 |
)
|
| 1222 |
a5_btn.click(a5_run, inputs=[a5_cancer], outputs=[a5_table, a5_note])
|
| 1223 |
|
| 1224 |
-
# ── A6 (RAG Chatbot
|
| 1225 |
-
with gr.Tab("
|
| 1226 |
gr.Markdown(
|
| 1227 |
"**RAG-powered research assistant** indexed on 20 curated papers "
|
| 1228 |
"on LNP delivery, protein corona, and cancer variants.\n\n"
|
|
@@ -1248,7 +1232,7 @@ def build_app():
|
|
| 1248 |
with gr.Tabs():
|
| 1249 |
|
| 1250 |
# ── B1 ──
|
| 1251 |
-
with gr.Tab("
|
| 1252 |
gr.Markdown(SIMULATED_BANNER)
|
| 1253 |
b1_gene = gr.Dropdown(["BRCA2", "BRCA1", "TP53"], label="Gene", value="TP53")
|
| 1254 |
b1_btn = gr.Button("🔬 Explore miRNA Interactions", variant="primary")
|
|
@@ -1267,7 +1251,7 @@ def build_app():
|
|
| 1267 |
b1_btn.click(b1_run, inputs=[b1_gene], outputs=[b1_plot, b1_table])
|
| 1268 |
|
| 1269 |
# ── B2 ──
|
| 1270 |
-
with gr.Tab("
|
| 1271 |
gr.Markdown(SIMULATED_BANNER)
|
| 1272 |
b2_cancer = gr.Dropdown(["LUAD", "BRCA", "COAD"], label="Cancer Type", value="LUAD")
|
| 1273 |
b2_btn = gr.Button("🎯 Simulate siRNA Efficacy", variant="primary")
|
|
@@ -1285,7 +1269,7 @@ def build_app():
|
|
| 1285 |
b2_btn.click(b2_run, inputs=[b2_cancer], outputs=[b2_plot, b2_table])
|
| 1286 |
|
| 1287 |
# ── B3 ──
|
| 1288 |
-
with gr.Tab("
|
| 1289 |
gr.Markdown(SIMULATED_BANNER)
|
| 1290 |
with gr.Row():
|
| 1291 |
b3_peg = gr.Slider(0.5, 5.0, value=1.5, step=0.1, label="PEG mol% (lipid)")
|
|
@@ -1316,7 +1300,7 @@ def build_app():
|
|
| 1316 |
)
|
| 1317 |
|
| 1318 |
# ── B4 ──
|
| 1319 |
-
with gr.Tab("
|
| 1320 |
gr.Markdown(SIMULATED_BANNER)
|
| 1321 |
with gr.Row():
|
| 1322 |
b4_time = gr.Slider(10, 120, value=60, step=5, label="Time range (min)")
|
|
@@ -1348,7 +1332,7 @@ def build_app():
|
|
| 1348 |
)
|
| 1349 |
|
| 1350 |
# ── B5 ──
|
| 1351 |
-
with gr.Tab("
|
| 1352 |
gr.Markdown(SIMULATED_BANNER)
|
| 1353 |
b5_class = gr.Dropdown(
|
| 1354 |
list(VARIANT_RULES.keys()),
|
|
|
|
| 25 |
from functools import wraps
|
| 26 |
|
| 27 |
# ─────────────────────────────────────────────
|
| 28 |
+
# CACHE SYSTEM (TTL = 24 h)
|
| 29 |
# ─────────────────────────────────────────────
|
| 30 |
CACHE_DIR = "./cache"
|
| 31 |
os.makedirs(CACHE_DIR, exist_ok=True)
|
|
|
|
| 55 |
# RETRY DECORATOR
|
| 56 |
# ─────────────────────────────────────────────
|
| 57 |
def retry(max_attempts=3, delay=1):
|
|
|
|
| 58 |
def decorator(func):
|
| 59 |
@wraps(func)
|
| 60 |
def wrapper(*args, **kwargs):
|
|
|
|
| 71 |
return decorator
|
| 72 |
|
| 73 |
# ─────────────────────────────────────────────
|
| 74 |
+
# LAB JOURNAL (уніфікована система кодування S1)
|
| 75 |
# ─────────────────────────────────────────────
|
| 76 |
JOURNAL_FILE = "./lab_journal.csv"
|
|
|
|
| 77 |
JOURNAL_CATEGORIES = [
|
| 78 |
+
# Real Data Tools (S1-A)
|
| 79 |
+
"S1-A·R2a", # Gray Zones Explorer
|
| 80 |
+
"S1-A·R2b", # Understudied Target Finder
|
| 81 |
+
"S1-A·R1a", # Real Variant Lookup
|
| 82 |
+
"S1-A·R2c", # Literature Gap Finder
|
| 83 |
+
"S1-A·R2d", # Druggable Orphans
|
| 84 |
+
"S1-A·R2e", # Research Assistant (Chatbot)
|
| 85 |
+
# Learning Sandbox
|
| 86 |
+
"S1-B·R1a", # miRNA Explorer
|
| 87 |
+
"S1-B·R2a", # siRNA Targets
|
| 88 |
+
"S1-D·R1a", # LNP Corona
|
| 89 |
+
"S1-D·R2a", # Flow Corona
|
| 90 |
+
"S1-A·R1b", # Variant Concepts
|
| 91 |
"Manual"
|
| 92 |
]
|
| 93 |
|
|
|
|
| 174 |
|
| 175 |
@retry(max_attempts=3, delay=1)
|
| 176 |
def pubmed_count(query: str) -> int:
|
|
|
|
| 177 |
cached = cache_get("pubmed_count", query)
|
| 178 |
if cached is not None:
|
| 179 |
return cached
|
| 180 |
try:
|
| 181 |
+
time.sleep(0.34)
|
| 182 |
r = requests.get(
|
| 183 |
f"{PUBMED_BASE}/esearch.fcgi",
|
| 184 |
params={"db": "pubmed", "term": query, "rettype": "count", "retmode": "json"},
|
|
|
|
| 189 |
count = int(data.get("esearchresult", {}).get("count", 0))
|
| 190 |
cache_set("pubmed_count", query, count)
|
| 191 |
return count
|
| 192 |
+
except Exception:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
return -1
|
| 194 |
|
| 195 |
@retry(max_attempts=3, delay=1)
|
| 196 |
def pubmed_search(query: str, retmax: int = 10) -> list:
|
|
|
|
| 197 |
cached = cache_get("pubmed_search", f"{query}_{retmax}")
|
| 198 |
if cached is not None:
|
| 199 |
return cached
|
|
|
|
| 214 |
|
| 215 |
@retry(max_attempts=3, delay=1)
|
| 216 |
def pubmed_summary(pmids: list) -> list:
|
|
|
|
| 217 |
if not pmids:
|
| 218 |
return []
|
| 219 |
cached = cache_get("pubmed_summary", ",".join(pmids))
|
|
|
|
| 236 |
|
| 237 |
@retry(max_attempts=3, delay=1)
|
| 238 |
def ot_query(gql: str, variables: dict = None) -> dict:
|
|
|
|
| 239 |
key = json.dumps({"q": gql, "v": variables}, sort_keys=True)
|
| 240 |
cached = cache_get("ot_gql", key)
|
| 241 |
if cached is not None:
|
|
|
|
| 249 |
r.raise_for_status()
|
| 250 |
data = r.json()
|
| 251 |
if "errors" in data:
|
|
|
|
| 252 |
return {"error": data["errors"]}
|
| 253 |
cache_set("ot_gql", key, data)
|
| 254 |
return data
|
| 255 |
except Exception as e:
|
|
|
|
| 256 |
return {"error": str(e)}
|
|
|
|
| 257 |
# ─────────────────────────────────────────────
|
| 258 |
+
# TAB A1 — GRAY ZONES EXPLORER (S1-A·R2a)
|
| 259 |
# ─────────────────────────────────────────────
|
| 260 |
|
| 261 |
def a1_run(cancer_type: str):
|
|
|
|
| 308 |
)
|
| 309 |
|
| 310 |
gaps_md = "\n\n---\n\n".join(gap_cards) if gap_cards else "No data available."
|
| 311 |
+
journal_log("S1-A·R2a", f"cancer={cancer_type}", f"gaps={[p for p,_ in sorted_procs[:5]]}")
|
| 312 |
source_note = f"*Source: PubMed E-utilities | Date: {today}*"
|
| 313 |
return img, gaps_md + "\n\n" + source_note
|
| 314 |
|
| 315 |
|
| 316 |
# ─────────────────────────────────────────────
|
| 317 |
+
# TAB A2 — UNDERSTUDIED TARGET FINDER (S1-A·R2b)
|
| 318 |
# ─────────────────────────────────────────────
|
| 319 |
|
| 320 |
DEPMAP_URL = "https://ndownloader.figshare.com/files/40448549" # CRISPR_gene_effect.csv (public)
|
|
|
|
| 442 |
f"and replace `_load_depmap_sample()` in `app.py`."
|
| 443 |
)
|
| 444 |
if not result_df.empty:
|
| 445 |
+
journal_log("S1-A·R2b", f"cancer={cancer_type}", f"top_gap={result_df.iloc[0]['Gene']}")
|
| 446 |
else:
|
| 447 |
+
journal_log("S1-A·R2b", f"cancer={cancer_type}", "no targets found")
|
| 448 |
return result_df, note
|
| 449 |
|
| 450 |
|
| 451 |
# ─────────────────────────────────────────────
|
| 452 |
+
# TAB A3 — REAL VARIANT LOOKUP (S1-A·R1a)
|
| 453 |
# ─────────────────────────────────────────────
|
| 454 |
|
| 455 |
def a3_run(hgvs: str):
|
|
|
|
| 570 |
)
|
| 571 |
|
| 572 |
result_parts.append(f"\n*Source: ClinVar E-utilities + gnomAD GraphQL | Date: {today}*")
|
| 573 |
+
journal_log("S1-A·R1a", f"hgvs={hgvs}", result_parts[0][:100] if result_parts else "no results")
|
| 574 |
return "\n\n".join(result_parts)
|
| 575 |
|
| 576 |
|
| 577 |
# ─────────────────────────────────────────────
|
| 578 |
+
# TAB A4 — LITERATURE GAP FINDER (S1-A·R2c)
|
| 579 |
# ─────────────────────────────────────────────
|
| 580 |
|
| 581 |
def a4_run(cancer_type: str, keyword: str):
|
|
|
|
| 636 |
|
| 637 |
summary = "\n\n".join(gap_text)
|
| 638 |
summary += f"\n\n*Source: PubMed E-utilities | Date: {today}*"
|
| 639 |
+
journal_log("S1-A·R2c", f"cancer={cancer_type}, kw={keyword}", summary[:100])
|
| 640 |
return img, summary
|
| 641 |
|
| 642 |
|
| 643 |
# ─────────────────────────────────────────────
|
| 644 |
+
# TAB A5 — DRUGGABLE ORPHANS (S1-A·R2d)
|
| 645 |
# ─────────────────────────────────────────────
|
| 646 |
|
| 647 |
def a5_run(cancer_type: str):
|
|
|
|
| 733 |
f"*Source: OpenTargets GraphQL + ClinicalTrials.gov v2 | Date: {today}*\n\n"
|
| 734 |
f"*Orphan = no approved drug (OpenTargets knownDrugs.count = 0)*"
|
| 735 |
)
|
| 736 |
+
journal_log("S1-A·R2d", f"cancer={cancer_type}", f"orphans={len(df)}")
|
| 737 |
return df, note
|
| 738 |
|
| 739 |
|
|
|
|
| 746 |
"for educational purposes only. Results do NOT reflect real experimental outcomes."
|
| 747 |
)
|
| 748 |
|
| 749 |
+
# ── TAB B1 — miRNA Explorer (S1-B·R1a) ──────────────────
|
| 750 |
|
| 751 |
MIRNA_DB = {
|
| 752 |
"BRCA2": {
|
|
|
|
| 816 |
"Expression log2FC": changes,
|
| 817 |
})
|
| 818 |
context = f"\n\n**Cancer Context:** {db['cancer_context']}"
|
| 819 |
+
journal_log("S1-B·R1a", f"gene={gene}", f"top_miRNA={mirnas[0]}")
|
| 820 |
return img, df.to_markdown(index=False) + context
|
| 821 |
|
| 822 |
|
| 823 |
+
# ── TAB B2 — siRNA Targets (S1-B·R2a) ───────────────────
|
| 824 |
|
| 825 |
SIRNA_DB = {
|
| 826 |
"LUAD": {
|
|
|
|
| 878 |
"Off-target Risk": off_risk,
|
| 879 |
"Delivery Challenge": delivery,
|
| 880 |
})
|
| 881 |
+
journal_log("S1-B·R2a", f"cancer={cancer}", f"top={targets[0]}")
|
| 882 |
return img, df.to_markdown(index=False)
|
| 883 |
|
| 884 |
|
| 885 |
+
# ── TAB B3 — LNP Corona Simulator (S1-D·R1a) ───────────────
|
| 886 |
|
| 887 |
def b3_run(peg_mol_pct: float, ionizable_pct: float, helper_pct: float,
|
| 888 |
chol_pct: float, particle_size_nm: float, serum_pct: float):
|
|
|
|
| 934 |
+ ("High ApoE → enhanced brain/liver targeting via LDLR pathway." if apoe_pct > 25
|
| 935 |
else "Low ApoE → reduced receptor-mediated uptake.")
|
| 936 |
)
|
| 937 |
+
journal_log("S1-D·R1a", f"PEG={peg_mol_pct}%,size={particle_size_nm}nm", f"ApoE={apoe_pct:.1f}%")
|
| 938 |
return img, interpretation
|
| 939 |
|
| 940 |
|
| 941 |
+
# ── TAB B4 — Flow Corona (Vroman Kinetics) (S1-D·R2a) ──────
|
| 942 |
|
| 943 |
def b4_run(time_points: int, kon_albumin: float, kon_apoe: float,
|
| 944 |
koff_albumin: float, koff_apoe: float):
|
|
|
|
| 978 |
"The Vroman effect describes sequential protein displacement: "
|
| 979 |
"abundant proteins (albumin) adsorb first, then are displaced by higher-affinity proteins (ApoE, fibrinogen)."
|
| 980 |
)
|
| 981 |
+
journal_log("S1-D·R2a", f"kon_alb={kon_albumin},kon_apoe={kon_apoe}", note[:80])
|
| 982 |
return img, note
|
| 983 |
|
| 984 |
|
| 985 |
+
# ── TAB B5 — Variant Concepts (S1-A·R1b) ───────────────────
|
| 986 |
|
| 987 |
VARIANT_RULES = {
|
| 988 |
"Pathogenic": {
|
|
|
|
| 1032 |
f"> ⚠️ SIMULATED — This is a rule-based educational model only. "
|
| 1033 |
f"Real variant classification requires expert review and full ACMG/AMP criteria evaluation."
|
| 1034 |
)
|
| 1035 |
+
journal_log("S1-A·R1b", f"class={classification}", output[:100])
|
| 1036 |
return output
|
| 1037 |
# ─────────────────────────────────────────────
|
| 1038 |
# GRADIO UI ASSEMBLY
|
|
|
|
| 1083 |
with gr.Tabs():
|
| 1084 |
|
| 1085 |
# ── A1 ──
|
| 1086 |
+
with gr.Tab("S1-A·R2a · Gray Zones Explorer"):
|
| 1087 |
gr.Markdown(
|
| 1088 |
"Identify underexplored biological processes in a cancer type "
|
| 1089 |
"using live PubMed + OpenTargets data."
|
|
|
|
| 1104 |
a1_btn.click(a1_run, inputs=[a1_cancer], outputs=[a1_heatmap, a1_gaps])
|
| 1105 |
|
| 1106 |
# ── A2 ──
|
| 1107 |
+
with gr.Tab("S1-A·R2b · Understudied Target Finder"):
|
| 1108 |
gr.Markdown(
|
| 1109 |
"Find essential genes with high research gap index "
|
| 1110 |
"(high essentiality, low publication coverage)."
|
|
|
|
| 1134 |
a2_btn.click(a2_run, inputs=[a2_cancer], outputs=[a2_table, a2_note])
|
| 1135 |
|
| 1136 |
# ── A3 ──
|
| 1137 |
+
with gr.Tab("S1-A·R1a · Real Variant Lookup"):
|
| 1138 |
gr.Markdown(
|
| 1139 |
"Look up a variant in **ClinVar** and **gnomAD**. "
|
| 1140 |
"Results are fetched live — never hallucinated."
|
|
|
|
| 1161 |
a3_btn.click(a3_run, inputs=[a3_hgvs], outputs=[a3_result])
|
| 1162 |
|
| 1163 |
# ── A4 ──
|
| 1164 |
+
with gr.Tab("S1-A·R2c · Literature Gap Finder"):
|
| 1165 |
gr.Markdown(
|
| 1166 |
"Visualize publication trends over 10 years and detect "
|
| 1167 |
"years with low research activity."
|
|
|
|
| 1184 |
a4_btn.click(a4_run, inputs=[a4_cancer, a4_kw], outputs=[a4_chart, a4_gaps])
|
| 1185 |
|
| 1186 |
# ── A5 ──
|
| 1187 |
+
with gr.Tab("S1-A·R2d · Druggable Orphans"):
|
| 1188 |
gr.Markdown(
|
| 1189 |
"Identify cancer-associated essential genes with **no approved drug** "
|
| 1190 |
"and **no active clinical trial**."
|
|
|
|
| 1205 |
)
|
| 1206 |
a5_btn.click(a5_run, inputs=[a5_cancer], outputs=[a5_table, a5_note])
|
| 1207 |
|
| 1208 |
+
# ── A6 (RAG Chatbot) ──
|
| 1209 |
+
with gr.Tab("S1-A·R2e · Research Assistant"):
|
| 1210 |
gr.Markdown(
|
| 1211 |
"**RAG-powered research assistant** indexed on 20 curated papers "
|
| 1212 |
"on LNP delivery, protein corona, and cancer variants.\n\n"
|
|
|
|
| 1232 |
with gr.Tabs():
|
| 1233 |
|
| 1234 |
# ── B1 ──
|
| 1235 |
+
with gr.Tab("S1-B·R1a · miRNA Explorer"):
|
| 1236 |
gr.Markdown(SIMULATED_BANNER)
|
| 1237 |
b1_gene = gr.Dropdown(["BRCA2", "BRCA1", "TP53"], label="Gene", value="TP53")
|
| 1238 |
b1_btn = gr.Button("🔬 Explore miRNA Interactions", variant="primary")
|
|
|
|
| 1251 |
b1_btn.click(b1_run, inputs=[b1_gene], outputs=[b1_plot, b1_table])
|
| 1252 |
|
| 1253 |
# ── B2 ──
|
| 1254 |
+
with gr.Tab("S1-B·R2a · siRNA Targets"):
|
| 1255 |
gr.Markdown(SIMULATED_BANNER)
|
| 1256 |
b2_cancer = gr.Dropdown(["LUAD", "BRCA", "COAD"], label="Cancer Type", value="LUAD")
|
| 1257 |
b2_btn = gr.Button("🎯 Simulate siRNA Efficacy", variant="primary")
|
|
|
|
| 1269 |
b2_btn.click(b2_run, inputs=[b2_cancer], outputs=[b2_plot, b2_table])
|
| 1270 |
|
| 1271 |
# ── B3 ──
|
| 1272 |
+
with gr.Tab("S1-D·R1a · LNP Corona"):
|
| 1273 |
gr.Markdown(SIMULATED_BANNER)
|
| 1274 |
with gr.Row():
|
| 1275 |
b3_peg = gr.Slider(0.5, 5.0, value=1.5, step=0.1, label="PEG mol% (lipid)")
|
|
|
|
| 1300 |
)
|
| 1301 |
|
| 1302 |
# ── B4 ──
|
| 1303 |
+
with gr.Tab("S1-D·R2a · Flow Corona"):
|
| 1304 |
gr.Markdown(SIMULATED_BANNER)
|
| 1305 |
with gr.Row():
|
| 1306 |
b4_time = gr.Slider(10, 120, value=60, step=5, label="Time range (min)")
|
|
|
|
| 1332 |
)
|
| 1333 |
|
| 1334 |
# ── B5 ──
|
| 1335 |
+
with gr.Tab("S1-A·R1b · Variant Concepts"):
|
| 1336 |
gr.Markdown(SIMULATED_BANNER)
|
| 1337 |
b5_class = gr.Dropdown(
|
| 1338 |
list(VARIANT_RULES.keys()),
|