Update app.py
Browse files
app.py
CHANGED
|
@@ -22,6 +22,7 @@ import matplotlib.colors as mcolors
|
|
| 22 |
from matplotlib import cm
|
| 23 |
import io
|
| 24 |
from PIL import Image
|
|
|
|
| 25 |
|
| 26 |
# ─────────────────────────────────────────────
|
| 27 |
# CACHE SYSTEM (TTL = 24 h)
|
|
@@ -50,29 +51,66 @@ def cache_set(endpoint: str, query: str, data):
|
|
| 50 |
with open(path, "w") as f:
|
| 51 |
json.dump(data, f)
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
# ─────────────────────────────────────────────
|
| 54 |
# LAB JOURNAL
|
| 55 |
# ─────────────────────────────────────────────
|
| 56 |
JOURNAL_FILE = "./lab_journal.csv"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
-
def journal_log(
|
|
|
|
| 59 |
ts = datetime.datetime.utcnow().isoformat()
|
| 60 |
-
row = [ts,
|
| 61 |
write_header = not os.path.exists(JOURNAL_FILE)
|
| 62 |
with open(JOURNAL_FILE, "a", newline="") as f:
|
| 63 |
w = csv.writer(f)
|
| 64 |
if write_header:
|
| 65 |
-
w.writerow(["timestamp", "
|
| 66 |
w.writerow(row)
|
| 67 |
return ts
|
| 68 |
|
| 69 |
-
def journal_read() -> str:
|
|
|
|
| 70 |
if not os.path.exists(JOURNAL_FILE):
|
| 71 |
return "No entries yet."
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
# ─────────────────────────────────────────────
|
| 78 |
# CONSTANTS
|
|
@@ -111,29 +149,38 @@ GNOMAD_GQL = "https://gnomad.broadinstitute.org/api"
|
|
| 111 |
CT_BASE = "https://clinicaltrials.gov/api/v2"
|
| 112 |
|
| 113 |
# ─────────────────────────────────────────────
|
| 114 |
-
# SHARED API HELPERS
|
| 115 |
# ─────────────────────────────────────────────
|
| 116 |
|
|
|
|
| 117 |
def pubmed_count(query: str) -> int:
|
| 118 |
"""Return paper count for a PubMed query (cached)."""
|
| 119 |
cached = cache_get("pubmed_count", query)
|
| 120 |
if cached is not None:
|
| 121 |
return cached
|
| 122 |
try:
|
| 123 |
-
time.sleep(0.34)
|
| 124 |
r = requests.get(
|
| 125 |
f"{PUBMED_BASE}/esearch.fcgi",
|
| 126 |
params={"db": "pubmed", "term": query, "rettype": "count", "retmode": "json"},
|
| 127 |
-
timeout=
|
| 128 |
)
|
| 129 |
r.raise_for_status()
|
| 130 |
-
|
|
|
|
| 131 |
cache_set("pubmed_count", query, count)
|
| 132 |
return count
|
| 133 |
-
except
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
return -1
|
| 135 |
|
| 136 |
-
|
| 137 |
def pubmed_search(query: str, retmax: int = 10) -> list:
|
| 138 |
"""Return list of PMIDs (cached)."""
|
| 139 |
cached = cache_get("pubmed_search", f"{query}_{retmax}")
|
|
@@ -144,16 +191,17 @@ def pubmed_search(query: str, retmax: int = 10) -> list:
|
|
| 144 |
r = requests.get(
|
| 145 |
f"{PUBMED_BASE}/esearch.fcgi",
|
| 146 |
params={"db": "pubmed", "term": query, "retmax": retmax, "retmode": "json"},
|
| 147 |
-
timeout=
|
| 148 |
)
|
| 149 |
r.raise_for_status()
|
| 150 |
-
|
|
|
|
| 151 |
cache_set("pubmed_search", f"{query}_{retmax}", ids)
|
| 152 |
return ids
|
| 153 |
except Exception:
|
| 154 |
return []
|
| 155 |
|
| 156 |
-
|
| 157 |
def pubmed_summary(pmids: list) -> list:
|
| 158 |
"""Fetch summaries for a list of PMIDs."""
|
| 159 |
if not pmids:
|
|
@@ -176,7 +224,7 @@ def pubmed_summary(pmids: list) -> list:
|
|
| 176 |
except Exception:
|
| 177 |
return []
|
| 178 |
|
| 179 |
-
|
| 180 |
def ot_query(gql: str, variables: dict = None) -> dict:
|
| 181 |
"""Run an OpenTargets GraphQL query (cached)."""
|
| 182 |
key = json.dumps({"q": gql, "v": variables}, sort_keys=True)
|
|
@@ -191,12 +239,14 @@ def ot_query(gql: str, variables: dict = None) -> dict:
|
|
| 191 |
)
|
| 192 |
r.raise_for_status()
|
| 193 |
data = r.json()
|
|
|
|
|
|
|
|
|
|
| 194 |
cache_set("ot_gql", key, data)
|
| 195 |
return data
|
| 196 |
except Exception as e:
|
|
|
|
| 197 |
return {"error": str(e)}
|
| 198 |
-
|
| 199 |
-
|
| 200 |
# ─────────────────────────────────────────────
|
| 201 |
# TAB A1 — GRAY ZONES EXPLORER
|
| 202 |
# ─────────────────────────────────────────────
|
|
@@ -219,7 +269,8 @@ def a1_run(cancer_type: str):
|
|
| 219 |
|
| 220 |
fig, ax = plt.subplots(figsize=(6, 8), facecolor="white")
|
| 221 |
valid = df[cancer_type].fillna(0).values.reshape(-1, 1)
|
| 222 |
-
|
|
|
|
| 223 |
cmap.set_bad("white")
|
| 224 |
masked = np.ma.masked_where(df[cancer_type].isna().values.reshape(-1, 1), valid)
|
| 225 |
im = ax.imshow(masked, aspect="auto", cmap=cmap, vmin=0)
|
|
@@ -270,7 +321,6 @@ def _load_depmap_sample() -> pd.DataFrame:
|
|
| 270 |
if "df" in _depmap_cache:
|
| 271 |
return _depmap_cache["df"]
|
| 272 |
# Use a curated list of known essential/cancer genes as fallback
|
| 273 |
-
# (full DepMap CSV is ~500 MB; we use the public summary endpoint instead)
|
| 274 |
genes = [
|
| 275 |
"MYC", "KRAS", "TP53", "EGFR", "PTEN", "RB1", "CDKN2A",
|
| 276 |
"PIK3CA", "AKT1", "BRAF", "NRAS", "IDH1", "IDH2", "ARID1A",
|
|
@@ -309,6 +359,9 @@ def a2_run(cancer_type: str):
|
|
| 309 |
}
|
| 310 |
"""
|
| 311 |
ot_data = ot_query(gql, {"efoId": efo, "size": 40})
|
|
|
|
|
|
|
|
|
|
| 312 |
rows_ot = []
|
| 313 |
try:
|
| 314 |
rows_ot = ot_data["data"]["disease"]["associatedTargets"]["rows"]
|
|
@@ -351,8 +404,6 @@ def a2_run(cancer_type: str):
|
|
| 351 |
depmap_dict = dict(zip(depmap_df["gene"], depmap_df["gene_effect"]))
|
| 352 |
|
| 353 |
# 5. Build result table
|
| 354 |
-
# Research gap index = |essentiality| / log(papers + 1)
|
| 355 |
-
# Per know-how: DepMap scores are negative for essential genes
|
| 356 |
records = []
|
| 357 |
for gene in genes_ot[:20]:
|
| 358 |
raw_ess = depmap_dict.get(gene, None)
|
|
@@ -362,12 +413,10 @@ def a2_run(cancer_type: str):
|
|
| 362 |
ess_display = "N/A"
|
| 363 |
gap_idx = 0.0
|
| 364 |
else:
|
| 365 |
-
# Invert: positive = more essential
|
| 366 |
ess_inverted = -raw_ess
|
| 367 |
ess_display = f"{ess_inverted:.3f}"
|
| 368 |
papers_safe = max(papers, 0)
|
| 369 |
-
# Use log(papers + 2) to guarantee denominator >= log(2) ≈ 0.693
|
| 370 |
-
# preventing division-by-near-zero for genes with 0 publications
|
| 371 |
gap_idx = ess_inverted / math.log(papers_safe + 2) if ess_inverted > 0 else 0.0
|
| 372 |
records.append({
|
| 373 |
"Gene": gene,
|
|
@@ -386,7 +435,10 @@ def a2_run(cancer_type: str):
|
|
| 386 |
f"For real analysis, download `CRISPR_gene_effect.csv` from [depmap.org](https://depmap.org/portal/download/all/) "
|
| 387 |
f"and replace `_load_depmap_sample()` in `app.py`."
|
| 388 |
)
|
| 389 |
-
|
|
|
|
|
|
|
|
|
|
| 390 |
return result_df, note
|
| 391 |
|
| 392 |
|
|
@@ -409,7 +461,7 @@ def a3_run(hgvs: str):
|
|
| 409 |
try:
|
| 410 |
time.sleep(0.34)
|
| 411 |
r = requests.get(
|
| 412 |
-
f"{PUBMED_BASE
|
| 413 |
params={"db": "clinvar", "term": hgvs, "retmode": "json", "retmax": 5},
|
| 414 |
timeout=10
|
| 415 |
)
|
|
@@ -425,7 +477,7 @@ def a3_run(hgvs: str):
|
|
| 425 |
try:
|
| 426 |
time.sleep(0.34)
|
| 427 |
r2 = requests.get(
|
| 428 |
-
f"{PUBMED_BASE
|
| 429 |
params={"db": "clinvar", "id": ",".join(clinvar_cached[:3]), "retmode": "json"},
|
| 430 |
timeout=10
|
| 431 |
)
|
|
@@ -458,8 +510,6 @@ def a3_run(hgvs: str):
|
|
| 458 |
)
|
| 459 |
|
| 460 |
# ── gnomAD ──
|
| 461 |
-
# gnomAD GraphQL expects rsID or gene-level; HGVS lookup is limited
|
| 462 |
-
# We attempt a search via the variant endpoint
|
| 463 |
gnomad_cached = cache_get("gnomad", hgvs)
|
| 464 |
if gnomad_cached is None:
|
| 465 |
try:
|
|
@@ -514,7 +564,7 @@ def a3_run(hgvs: str):
|
|
| 514 |
)
|
| 515 |
|
| 516 |
result_parts.append(f"\n*Source: ClinVar E-utilities + gnomAD GraphQL | Date: {today}*")
|
| 517 |
-
journal_log("A3-VariantLookup", f"hgvs={hgvs}", result_parts[0][:100])
|
| 518 |
return "\n\n".join(result_parts)
|
| 519 |
|
| 520 |
|
|
@@ -618,6 +668,9 @@ def a5_run(cancer_type: str):
|
|
| 618 |
}
|
| 619 |
"""
|
| 620 |
ot_data = ot_query(gql, {"efoId": efo, "size": 50})
|
|
|
|
|
|
|
|
|
|
| 621 |
rows_ot = []
|
| 622 |
try:
|
| 623 |
rows_ot = ot_data["data"]["disease"]["associatedTargets"]["rows"]
|
|
@@ -676,8 +729,6 @@ def a5_run(cancer_type: str):
|
|
| 676 |
)
|
| 677 |
journal_log("A5-DruggableOrphans", f"cancer={cancer_type}", f"orphans={len(df)}")
|
| 678 |
return df, note
|
| 679 |
-
|
| 680 |
-
|
| 681 |
# ─────────────────────────────────────────────
|
| 682 |
# GROUP B — LEARNING SANDBOX
|
| 683 |
# ─────────────────────────────────────────────
|
|
@@ -996,23 +1047,6 @@ body { font-family: 'Inter', sans-serif; }
|
|
| 996 |
footer { display: none !important; }
|
| 997 |
"""
|
| 998 |
|
| 999 |
-
def build_journal_sidebar():
|
| 1000 |
-
with gr.Column(scale=1, min_width=260):
|
| 1001 |
-
gr.Markdown("## 📓 Lab Journal")
|
| 1002 |
-
note_input = gr.Textbox(label="Add note", placeholder="Your observation...", lines=2)
|
| 1003 |
-
save_btn = gr.Button("💾 Save Note", size="sm")
|
| 1004 |
-
refresh_btn = gr.Button("🔄 Refresh Journal", size="sm")
|
| 1005 |
-
journal_display = gr.Markdown(value="*Click Refresh to load entries.*")
|
| 1006 |
-
|
| 1007 |
-
def save_note(note):
|
| 1008 |
-
if note.strip():
|
| 1009 |
-
journal_log("Manual", "note", note.strip(), note.strip())
|
| 1010 |
-
return journal_read()
|
| 1011 |
-
|
| 1012 |
-
save_btn.click(save_note, inputs=[note_input], outputs=[journal_display])
|
| 1013 |
-
refresh_btn.click(lambda: journal_read(), outputs=[journal_display])
|
| 1014 |
-
return note_input, journal_display
|
| 1015 |
-
|
| 1016 |
|
| 1017 |
def build_app():
|
| 1018 |
with gr.Blocks(css=CUSTOM_CSS, title="K R&D Lab — Cancer Research Suite") as demo:
|
|
@@ -1306,29 +1340,60 @@ def build_app():
|
|
| 1306 |
)
|
| 1307 |
b5_btn.click(b5_run, inputs=[b5_class], outputs=[b5_result])
|
| 1308 |
|
| 1309 |
-
# ── SIDEBAR ──
|
| 1310 |
with gr.Column(scale=1, min_width=260):
|
| 1311 |
gr.Markdown("## 📓 Lab Journal")
|
| 1312 |
-
|
| 1313 |
-
|
| 1314 |
-
|
| 1315 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1316 |
|
| 1317 |
-
def save_note(note):
|
| 1318 |
if note.strip():
|
| 1319 |
-
journal_log(
|
| 1320 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1321 |
|
| 1322 |
-
save_btn.click(
|
| 1323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1324 |
|
| 1325 |
# === Інтеграція з Learning Playground ===
|
| 1326 |
with gr.Row():
|
| 1327 |
gr.Markdown("""
|
|
|
|
| 1328 |
### 🧪 New to the concepts?
|
| 1329 |
Explore our **[Learning Playground](https://huggingface.co/spaces/K-RnD-Lab/Learning-Playground_03-2026)** with simulated environments.
|
| 1330 |
""")
|
| 1331 |
-
|
| 1332 |
gr.Markdown(
|
| 1333 |
"---\n"
|
| 1334 |
"*K R&D Lab Cancer Research Suite · "
|
|
@@ -1342,4 +1407,4 @@ def build_app():
|
|
| 1342 |
|
| 1343 |
if __name__ == "__main__":
|
| 1344 |
app = build_app()
|
| 1345 |
-
app.launch(share=False)
|
|
|
|
| 22 |
from matplotlib import cm
|
| 23 |
import io
|
| 24 |
from PIL import Image
|
| 25 |
+
from functools import wraps
|
| 26 |
|
| 27 |
# ─────────────────────────────────────────────
|
| 28 |
# CACHE SYSTEM (TTL = 24 h)
|
|
|
|
| 51 |
with open(path, "w") as f:
|
| 52 |
json.dump(data, f)
|
| 53 |
|
| 54 |
+
# ─────────────────────────────────────────────
|
| 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):
|
| 62 |
+
for attempt in range(max_attempts):
|
| 63 |
+
try:
|
| 64 |
+
return func(*args, **kwargs)
|
| 65 |
+
except Exception as e:
|
| 66 |
+
print(f"Attempt {attempt+1} failed for {func.__name__}: {e}")
|
| 67 |
+
if attempt == max_attempts - 1:
|
| 68 |
+
raise
|
| 69 |
+
time.sleep(delay * (attempt + 1))
|
| 70 |
+
return None
|
| 71 |
+
return wrapper
|
| 72 |
+
return decorator
|
| 73 |
+
|
| 74 |
# ─────────────────────────────────────────────
|
| 75 |
# LAB JOURNAL
|
| 76 |
# ─────────────────────────────────────────────
|
| 77 |
JOURNAL_FILE = "./lab_journal.csv"
|
| 78 |
+
JOURNAL_CATEGORIES = [
|
| 79 |
+
"A1-GrayZones", "A2-TargetFinder", "A3-VariantLookup", "A4-LitGap", "A5-Orphans", "A6-Chatbot",
|
| 80 |
+
"B1-miRNA", "B2-siRNA", "B3-LNPCorona", "B4-FlowCorona", "B5-VariantConcepts",
|
| 81 |
+
"Manual"
|
| 82 |
+
]
|
| 83 |
|
| 84 |
+
def journal_log(category: str, action: str, result: str, note: str = ""):
|
| 85 |
+
"""Log an entry with category."""
|
| 86 |
ts = datetime.datetime.utcnow().isoformat()
|
| 87 |
+
row = [ts, category, action, result[:200], note]
|
| 88 |
write_header = not os.path.exists(JOURNAL_FILE)
|
| 89 |
with open(JOURNAL_FILE, "a", newline="") as f:
|
| 90 |
w = csv.writer(f)
|
| 91 |
if write_header:
|
| 92 |
+
w.writerow(["timestamp", "category", "action", "result_summary", "note"])
|
| 93 |
w.writerow(row)
|
| 94 |
return ts
|
| 95 |
|
| 96 |
+
def journal_read(category: str = "All") -> str:
|
| 97 |
+
"""Read journal entries, optionally filtered by category. Returns markdown."""
|
| 98 |
if not os.path.exists(JOURNAL_FILE):
|
| 99 |
return "No entries yet."
|
| 100 |
+
try:
|
| 101 |
+
df = pd.read_csv(JOURNAL_FILE)
|
| 102 |
+
if df.empty:
|
| 103 |
+
return "No entries yet."
|
| 104 |
+
if category != "All":
|
| 105 |
+
df = df[df["category"] == category]
|
| 106 |
+
if df.empty:
|
| 107 |
+
return f"No entries for category: {category}"
|
| 108 |
+
# Format for better readability
|
| 109 |
+
df_display = df[["timestamp", "category", "action", "result_summary"]].tail(20)
|
| 110 |
+
return df_display.to_markdown(index=False)
|
| 111 |
+
except Exception as e:
|
| 112 |
+
print(f"Journal read error: {e}")
|
| 113 |
+
return "Error reading journal."
|
| 114 |
|
| 115 |
# ─────────────────────────────────────────────
|
| 116 |
# CONSTANTS
|
|
|
|
| 149 |
CT_BASE = "https://clinicaltrials.gov/api/v2"
|
| 150 |
|
| 151 |
# ─────────────────────────────────────────────
|
| 152 |
+
# SHARED API HELPERS (with retry)
|
| 153 |
# ─────────────────────────────────────────────
|
| 154 |
|
| 155 |
+
@retry(max_attempts=3, delay=1)
|
| 156 |
def pubmed_count(query: str) -> int:
|
| 157 |
"""Return paper count for a PubMed query (cached)."""
|
| 158 |
cached = cache_get("pubmed_count", query)
|
| 159 |
if cached is not None:
|
| 160 |
return cached
|
| 161 |
try:
|
| 162 |
+
time.sleep(0.34) # Rate limit compliance
|
| 163 |
r = requests.get(
|
| 164 |
f"{PUBMED_BASE}/esearch.fcgi",
|
| 165 |
params={"db": "pubmed", "term": query, "rettype": "count", "retmode": "json"},
|
| 166 |
+
timeout=15
|
| 167 |
)
|
| 168 |
r.raise_for_status()
|
| 169 |
+
data = r.json()
|
| 170 |
+
count = int(data.get("esearchresult", {}).get("count", 0))
|
| 171 |
cache_set("pubmed_count", query, count)
|
| 172 |
return count
|
| 173 |
+
except requests.exceptions.Timeout:
|
| 174 |
+
print(f"Timeout for query: {query}")
|
| 175 |
+
return -1
|
| 176 |
+
except requests.exceptions.RequestException as e:
|
| 177 |
+
print(f"Request error for {query}: {e}")
|
| 178 |
+
return -1
|
| 179 |
+
except (KeyError, ValueError) as e:
|
| 180 |
+
print(f"Parsing error for {query}: {e}")
|
| 181 |
return -1
|
| 182 |
|
| 183 |
+
@retry(max_attempts=3, delay=1)
|
| 184 |
def pubmed_search(query: str, retmax: int = 10) -> list:
|
| 185 |
"""Return list of PMIDs (cached)."""
|
| 186 |
cached = cache_get("pubmed_search", f"{query}_{retmax}")
|
|
|
|
| 191 |
r = requests.get(
|
| 192 |
f"{PUBMED_BASE}/esearch.fcgi",
|
| 193 |
params={"db": "pubmed", "term": query, "retmax": retmax, "retmode": "json"},
|
| 194 |
+
timeout=15
|
| 195 |
)
|
| 196 |
r.raise_for_status()
|
| 197 |
+
data = r.json()
|
| 198 |
+
ids = data.get("esearchresult", {}).get("idlist", [])
|
| 199 |
cache_set("pubmed_search", f"{query}_{retmax}", ids)
|
| 200 |
return ids
|
| 201 |
except Exception:
|
| 202 |
return []
|
| 203 |
|
| 204 |
+
@retry(max_attempts=3, delay=1)
|
| 205 |
def pubmed_summary(pmids: list) -> list:
|
| 206 |
"""Fetch summaries for a list of PMIDs."""
|
| 207 |
if not pmids:
|
|
|
|
| 224 |
except Exception:
|
| 225 |
return []
|
| 226 |
|
| 227 |
+
@retry(max_attempts=3, delay=1)
|
| 228 |
def ot_query(gql: str, variables: dict = None) -> dict:
|
| 229 |
"""Run an OpenTargets GraphQL query (cached)."""
|
| 230 |
key = json.dumps({"q": gql, "v": variables}, sort_keys=True)
|
|
|
|
| 239 |
)
|
| 240 |
r.raise_for_status()
|
| 241 |
data = r.json()
|
| 242 |
+
if "errors" in data:
|
| 243 |
+
print(f"GraphQL errors: {data['errors']}")
|
| 244 |
+
return {"error": data["errors"]}
|
| 245 |
cache_set("ot_gql", key, data)
|
| 246 |
return data
|
| 247 |
except Exception as e:
|
| 248 |
+
print(f"OT query error: {e}")
|
| 249 |
return {"error": str(e)}
|
|
|
|
|
|
|
| 250 |
# ─────────────────────────────────────────────
|
| 251 |
# TAB A1 — GRAY ZONES EXPLORER
|
| 252 |
# ─────────────────────────────────────────────
|
|
|
|
| 269 |
|
| 270 |
fig, ax = plt.subplots(figsize=(6, 8), facecolor="white")
|
| 271 |
valid = df[cancer_type].fillna(0).values.reshape(-1, 1)
|
| 272 |
+
# Виправлено deprecated get_cmap
|
| 273 |
+
cmap = plt.colormaps.get_cmap("YlOrRd")
|
| 274 |
cmap.set_bad("white")
|
| 275 |
masked = np.ma.masked_where(df[cancer_type].isna().values.reshape(-1, 1), valid)
|
| 276 |
im = ax.imshow(masked, aspect="auto", cmap=cmap, vmin=0)
|
|
|
|
| 321 |
if "df" in _depmap_cache:
|
| 322 |
return _depmap_cache["df"]
|
| 323 |
# Use a curated list of known essential/cancer genes as fallback
|
|
|
|
| 324 |
genes = [
|
| 325 |
"MYC", "KRAS", "TP53", "EGFR", "PTEN", "RB1", "CDKN2A",
|
| 326 |
"PIK3CA", "AKT1", "BRAF", "NRAS", "IDH1", "IDH2", "ARID1A",
|
|
|
|
| 359 |
}
|
| 360 |
"""
|
| 361 |
ot_data = ot_query(gql, {"efoId": efo, "size": 40})
|
| 362 |
+
if "error" in ot_data:
|
| 363 |
+
return None, f"⚠️ OpenTargets API error: {ot_data['error']}\n\n*Source: OpenTargets | Date: {today}*"
|
| 364 |
+
|
| 365 |
rows_ot = []
|
| 366 |
try:
|
| 367 |
rows_ot = ot_data["data"]["disease"]["associatedTargets"]["rows"]
|
|
|
|
| 404 |
depmap_dict = dict(zip(depmap_df["gene"], depmap_df["gene_effect"]))
|
| 405 |
|
| 406 |
# 5. Build result table
|
|
|
|
|
|
|
| 407 |
records = []
|
| 408 |
for gene in genes_ot[:20]:
|
| 409 |
raw_ess = depmap_dict.get(gene, None)
|
|
|
|
| 413 |
ess_display = "N/A"
|
| 414 |
gap_idx = 0.0
|
| 415 |
else:
|
| 416 |
+
# Invert: positive = more essential
|
| 417 |
ess_inverted = -raw_ess
|
| 418 |
ess_display = f"{ess_inverted:.3f}"
|
| 419 |
papers_safe = max(papers, 0)
|
|
|
|
|
|
|
| 420 |
gap_idx = ess_inverted / math.log(papers_safe + 2) if ess_inverted > 0 else 0.0
|
| 421 |
records.append({
|
| 422 |
"Gene": gene,
|
|
|
|
| 435 |
f"For real analysis, download `CRISPR_gene_effect.csv` from [depmap.org](https://depmap.org/portal/download/all/) "
|
| 436 |
f"and replace `_load_depmap_sample()` in `app.py`."
|
| 437 |
)
|
| 438 |
+
if not result_df.empty:
|
| 439 |
+
journal_log("A2-TargetFinder", f"cancer={cancer_type}", f"top_gap={result_df.iloc[0]['Gene']}")
|
| 440 |
+
else:
|
| 441 |
+
journal_log("A2-TargetFinder", f"cancer={cancer_type}", "no targets found")
|
| 442 |
return result_df, note
|
| 443 |
|
| 444 |
|
|
|
|
| 461 |
try:
|
| 462 |
time.sleep(0.34)
|
| 463 |
r = requests.get(
|
| 464 |
+
f"{PUBMED_BASE}/esearch.fcgi",
|
| 465 |
params={"db": "clinvar", "term": hgvs, "retmode": "json", "retmax": 5},
|
| 466 |
timeout=10
|
| 467 |
)
|
|
|
|
| 477 |
try:
|
| 478 |
time.sleep(0.34)
|
| 479 |
r2 = requests.get(
|
| 480 |
+
f"{PUBMED_BASE}/esummary.fcgi",
|
| 481 |
params={"db": "clinvar", "id": ",".join(clinvar_cached[:3]), "retmode": "json"},
|
| 482 |
timeout=10
|
| 483 |
)
|
|
|
|
| 510 |
)
|
| 511 |
|
| 512 |
# ── gnomAD ──
|
|
|
|
|
|
|
| 513 |
gnomad_cached = cache_get("gnomad", hgvs)
|
| 514 |
if gnomad_cached is None:
|
| 515 |
try:
|
|
|
|
| 564 |
)
|
| 565 |
|
| 566 |
result_parts.append(f"\n*Source: ClinVar E-utilities + gnomAD GraphQL | Date: {today}*")
|
| 567 |
+
journal_log("A3-VariantLookup", f"hgvs={hgvs}", result_parts[0][:100] if result_parts else "no results")
|
| 568 |
return "\n\n".join(result_parts)
|
| 569 |
|
| 570 |
|
|
|
|
| 668 |
}
|
| 669 |
"""
|
| 670 |
ot_data = ot_query(gql, {"efoId": efo, "size": 50})
|
| 671 |
+
if "error" in ot_data:
|
| 672 |
+
return None, f"⚠️ OpenTargets API error: {ot_data['error']}\n\n*Source: OpenTargets | Date: {today}*"
|
| 673 |
+
|
| 674 |
rows_ot = []
|
| 675 |
try:
|
| 676 |
rows_ot = ot_data["data"]["disease"]["associatedTargets"]["rows"]
|
|
|
|
| 729 |
)
|
| 730 |
journal_log("A5-DruggableOrphans", f"cancer={cancer_type}", f"orphans={len(df)}")
|
| 731 |
return df, note
|
|
|
|
|
|
|
| 732 |
# ─────────────────────────────────────────────
|
| 733 |
# GROUP B — LEARNING SANDBOX
|
| 734 |
# ─────────────────────────────────────────────
|
|
|
|
| 1047 |
footer { display: none !important; }
|
| 1048 |
"""
|
| 1049 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1050 |
|
| 1051 |
def build_app():
|
| 1052 |
with gr.Blocks(css=CUSTOM_CSS, title="K R&D Lab — Cancer Research Suite") as demo:
|
|
|
|
| 1340 |
)
|
| 1341 |
b5_btn.click(b5_run, inputs=[b5_class], outputs=[b5_result])
|
| 1342 |
|
| 1343 |
+
# ── SIDEBAR (JOURNAL) ──
|
| 1344 |
with gr.Column(scale=1, min_width=260):
|
| 1345 |
gr.Markdown("## 📓 Lab Journal")
|
| 1346 |
+
note_category = gr.Dropdown(
|
| 1347 |
+
choices=JOURNAL_CATEGORIES,
|
| 1348 |
+
value="Manual",
|
| 1349 |
+
label="Category"
|
| 1350 |
+
)
|
| 1351 |
+
note_input = gr.Textbox(
|
| 1352 |
+
label="Observation",
|
| 1353 |
+
placeholder="Type your note here...",
|
| 1354 |
+
lines=3
|
| 1355 |
+
)
|
| 1356 |
+
with gr.Row():
|
| 1357 |
+
save_btn = gr.Button("💾 Save Note", size="sm", variant="primary")
|
| 1358 |
+
clear_note_btn = gr.Button("🗑️ Clear", size="sm")
|
| 1359 |
+
save_status = gr.Markdown("")
|
| 1360 |
+
|
| 1361 |
+
gr.Markdown("---")
|
| 1362 |
+
gr.Markdown("## 🔍 Full Journal")
|
| 1363 |
+
journal_filter = gr.Dropdown(
|
| 1364 |
+
choices=["All"] + JOURNAL_CATEGORIES,
|
| 1365 |
+
value="All",
|
| 1366 |
+
label="Filter by category"
|
| 1367 |
+
)
|
| 1368 |
+
refresh_btn = gr.Button("🔄 Refresh", size="sm")
|
| 1369 |
+
journal_display = gr.Markdown(value=journal_read())
|
| 1370 |
|
| 1371 |
+
def save_note(category, note):
|
| 1372 |
if note.strip():
|
| 1373 |
+
journal_log(category, "manual note", note, note)
|
| 1374 |
+
return "✅ Note saved.", ""
|
| 1375 |
+
return "⚠️ Note is empty.", ""
|
| 1376 |
+
|
| 1377 |
+
def refresh_journal(category):
|
| 1378 |
+
return journal_read(category)
|
| 1379 |
|
| 1380 |
+
save_btn.click(
|
| 1381 |
+
save_note,
|
| 1382 |
+
inputs=[note_category, note_input],
|
| 1383 |
+
outputs=[save_status, note_input]
|
| 1384 |
+
)
|
| 1385 |
+
clear_note_btn.click(lambda: ("", ""), outputs=[note_input, save_status])
|
| 1386 |
+
refresh_btn.click(refresh_journal, inputs=[journal_filter], outputs=journal_display)
|
| 1387 |
+
journal_filter.change(refresh_journal, inputs=[journal_filter], outputs=journal_display)
|
| 1388 |
|
| 1389 |
# === Інтеграція з Learning Playground ===
|
| 1390 |
with gr.Row():
|
| 1391 |
gr.Markdown("""
|
| 1392 |
+
---
|
| 1393 |
### 🧪 New to the concepts?
|
| 1394 |
Explore our **[Learning Playground](https://huggingface.co/spaces/K-RnD-Lab/Learning-Playground_03-2026)** with simulated environments.
|
| 1395 |
""")
|
| 1396 |
+
|
| 1397 |
gr.Markdown(
|
| 1398 |
"---\n"
|
| 1399 |
"*K R&D Lab Cancer Research Suite · "
|
|
|
|
| 1407 |
|
| 1408 |
if __name__ == "__main__":
|
| 1409 |
app = build_app()
|
| 1410 |
+
app.launch(share=False)
|