TEZv commited on
Commit
56e818a
·
verified ·
1 Parent(s): 36ca7d2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -37
app.py CHANGED
@@ -75,6 +75,7 @@ def retry(max_attempts=3, delay=1):
75
  # LAB JOURNAL
76
  # ─────────────────────────────────────────────
77
  JOURNAL_FILE = "./lab_journal.csv"
 
78
  JOURNAL_CATEGORIES = [
79
  # Real Data Tools (S2-A)
80
  "S2-A·R1a", # Gray Zones Explorer
@@ -82,7 +83,7 @@ JOURNAL_CATEGORIES = [
82
  "S2-A·R1c", # Real Variant Lookup
83
  "S2-A·R1d", # Literature Gap Finder
84
  "S2-A·R1e", # Druggable Orphans
85
- "S2-A·R1f", # Research Assistant
86
  # Learning Sandbox (S2-B)
87
  "S2-B·R1a", # miRNA Explorer
88
  "S2-B·R1b", # siRNA Targets
@@ -117,12 +118,22 @@ def journal_read(category: str = "All") -> str:
117
  if df.empty:
118
  return f"No entries for category: {category}"
119
  # Format for better readability
120
- df_display = df[["timestamp", "category", "action", "result_summary"]].tail(20)
121
  return df_display.to_markdown(index=False)
122
  except Exception as e:
123
  print(f"Journal read error: {e}")
124
  return "Error reading journal."
125
 
 
 
 
 
 
 
 
 
 
 
126
  # ─────────────────────────────────────────────
127
  # CONSTANTS
128
  # ─────────────────────────────────────────────
@@ -258,6 +269,7 @@ def ot_query(gql: str, variables: dict = None) -> dict:
258
  except Exception as e:
259
  print(f"OT query error: {e}")
260
  return {"error": str(e)}
 
261
  # ─────────────────────────────────────────────
262
  # TAB A1 — GRAY ZONES EXPLORER
263
  # ─────────────────────────────────────────────
@@ -280,7 +292,6 @@ def a1_run(cancer_type: str):
280
 
281
  fig, ax = plt.subplots(figsize=(6, 8), facecolor="white")
282
  valid = df[cancer_type].fillna(0).values.reshape(-1, 1)
283
- # Виправлено deprecated get_cmap
284
  cmap = plt.colormaps.get_cmap("YlOrRd")
285
  cmap.set_bad("white")
286
  masked = np.ma.masked_where(df[cancer_type].isna().values.reshape(-1, 1), valid)
@@ -313,7 +324,7 @@ def a1_run(cancer_type: str):
313
  )
314
 
315
  gaps_md = "\n\n---\n\n".join(gap_cards) if gap_cards else "No data available."
316
- journal_log("A1-GrayZones", f"cancer={cancer_type}", f"gaps={[p for p,_ in sorted_procs[:5]]}")
317
  source_note = f"*Source: PubMed E-utilities | Date: {today}*"
318
  return img, gaps_md + "\n\n" + source_note
319
 
@@ -447,9 +458,9 @@ def a2_run(cancer_type: str):
447
  f"and replace `_load_depmap_sample()` in `app.py`."
448
  )
449
  if not result_df.empty:
450
- journal_log("A2-TargetFinder", f"cancer={cancer_type}", f"top_gap={result_df.iloc[0]['Gene']}")
451
  else:
452
- journal_log("A2-TargetFinder", f"cancer={cancer_type}", "no targets found")
453
  return result_df, note
454
 
455
 
@@ -575,7 +586,7 @@ def a3_run(hgvs: str):
575
  )
576
 
577
  result_parts.append(f"\n*Source: ClinVar E-utilities + gnomAD GraphQL | Date: {today}*")
578
- journal_log("A3-VariantLookup", f"hgvs={hgvs}", result_parts[0][:100] if result_parts else "no results")
579
  return "\n\n".join(result_parts)
580
 
581
 
@@ -641,7 +652,7 @@ def a4_run(cancer_type: str, keyword: str):
641
 
642
  summary = "\n\n".join(gap_text)
643
  summary += f"\n\n*Source: PubMed E-utilities | Date: {today}*"
644
- journal_log("A4-LitGap", f"cancer={cancer_type}, kw={keyword}", summary[:100])
645
  return img, summary
646
 
647
 
@@ -738,8 +749,10 @@ def a5_run(cancer_type: str):
738
  f"*Source: OpenTargets GraphQL + ClinicalTrials.gov v2 | Date: {today}*\n\n"
739
  f"*Orphan = no approved drug (OpenTargets knownDrugs.count = 0)*"
740
  )
741
- journal_log("A5-DruggableOrphans", f"cancer={cancer_type}", f"orphans={len(df)}")
742
  return df, note
 
 
743
  # ─────────────────────────────────────────────
744
  # GROUP B — LEARNING SANDBOX
745
  # ─────────────────────────────────────────────
@@ -819,7 +832,7 @@ def b1_run(gene: str):
819
  "Expression log2FC": changes,
820
  })
821
  context = f"\n\n**Cancer Context:** {db['cancer_context']}"
822
- journal_log("B1-miRNA", f"gene={gene}", f"top_miRNA={mirnas[0]}")
823
  return img, df.to_markdown(index=False) + context
824
 
825
 
@@ -881,7 +894,7 @@ def b2_run(cancer: str):
881
  "Off-target Risk": off_risk,
882
  "Delivery Challenge": delivery,
883
  })
884
- journal_log("B2-siRNA", f"cancer={cancer}", f"top={targets[0]}")
885
  return img, df.to_markdown(index=False)
886
 
887
 
@@ -937,7 +950,7 @@ def b3_run(peg_mol_pct: float, ionizable_pct: float, helper_pct: float,
937
  + ("High ApoE → enhanced brain/liver targeting via LDLR pathway." if apoe_pct > 25
938
  else "Low ApoE → reduced receptor-mediated uptake.")
939
  )
940
- journal_log("B3-LNPCorona", f"PEG={peg_mol_pct}%,size={particle_size_nm}nm", f"ApoE={apoe_pct:.1f}%")
941
  return img, interpretation
942
 
943
 
@@ -981,7 +994,7 @@ def b4_run(time_points: int, kon_albumin: float, kon_apoe: float,
981
  "The Vroman effect describes sequential protein displacement: "
982
  "abundant proteins (albumin) adsorb first, then are displaced by higher-affinity proteins (ApoE, fibrinogen)."
983
  )
984
- journal_log("B4-FlowCorona", f"kon_alb={kon_albumin},kon_apoe={kon_apoe}", note[:80])
985
  return img, note
986
 
987
 
@@ -1035,10 +1048,8 @@ def b5_run(classification: str):
1035
  f"> ⚠️ SIMULATED — This is a rule-based educational model only. "
1036
  f"Real variant classification requires expert review and full ACMG/AMP criteria evaluation."
1037
  )
1038
- journal_log("B5-VariantConcepts", f"class={classification}", output[:100])
1039
  return output
1040
-
1041
-
1042
  # ─────────────────────────────────────────────
1043
  # GRADIO UI ASSEMBLY
1044
  # ─────────────────────────────────────────────
@@ -1055,17 +1066,16 @@ body { font-family: 'Inter', sans-serif; }
1055
  background: #f8f9fa; border-left: 4px solid #d73027;
1056
  padding: 10px 14px; margin: 6px 0; border-radius: 4px;
1057
  }
1058
- footer { display: none !important; }
1059
-
1060
  /* Зміна курсора на pointer для всіх інтерактивних елементів */
1061
  .gr-dropdown, .gr-button, .gr-slider, .gr-radio, .gr-checkbox,
1062
- .tab-nav button, .gr-accordion, .gr-textbox, .gr-number, .gr-dataset {
1063
  cursor: pointer !important;
1064
  }
1065
  /* Для текстових полів залишаємо звичайний текстовий курсор */
1066
  .gr-textbox input, .gr-textarea textarea {
1067
  cursor: text !important;
1068
  }
 
1069
  """
1070
 
1071
 
@@ -1080,7 +1090,7 @@ def build_app():
1080
  with gr.Row():
1081
  # ── MAIN CONTENT ──
1082
  with gr.Column(scale=4):
1083
- with gr.Tabs():
1084
 
1085
  # ════════════════════════════════
1086
  # GROUP A — REAL DATA TOOLS
@@ -1361,9 +1371,33 @@ def build_app():
1361
  )
1362
  b5_btn.click(b5_run, inputs=[b5_class], outputs=[b5_result])
1363
 
1364
- # ── SIDEBAR (JOURNAL) ──
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1365
  with gr.Column(scale=1, min_width=260):
1366
- gr.Markdown("## 📓 Lab Journal")
1367
  note_category = gr.Dropdown(
1368
  choices=JOURNAL_CATEGORIES,
1369
  value="Manual",
@@ -1379,33 +1413,18 @@ def build_app():
1379
  clear_note_btn = gr.Button("🗑️ Clear", size="sm")
1380
  save_status = gr.Markdown("")
1381
 
1382
- gr.Markdown("---")
1383
- gr.Markdown("## 🔍 Full Journal")
1384
- journal_filter = gr.Dropdown(
1385
- choices=["All"] + JOURNAL_CATEGORIES,
1386
- value="All",
1387
- label="Filter by category"
1388
- )
1389
- refresh_btn = gr.Button("🔄 Refresh", size="sm")
1390
- journal_display = gr.Markdown(value=journal_read())
1391
-
1392
  def save_note(category, note):
1393
  if note.strip():
1394
  journal_log(category, "manual note", note, note)
1395
  return "✅ Note saved.", ""
1396
  return "⚠️ Note is empty.", ""
1397
 
1398
- def refresh_journal(category):
1399
- return journal_read(category)
1400
-
1401
  save_btn.click(
1402
  save_note,
1403
  inputs=[note_category, note_input],
1404
  outputs=[save_status, note_input]
1405
  )
1406
  clear_note_btn.click(lambda: ("", ""), outputs=[note_input, save_status])
1407
- refresh_btn.click(refresh_journal, inputs=[journal_filter], outputs=journal_display)
1408
- journal_filter.change(refresh_journal, inputs=[journal_filter], outputs=journal_display)
1409
 
1410
  # === Інтеграція з Learning Playground ===
1411
  with gr.Row():
 
75
  # LAB JOURNAL
76
  # ─────────────────────────────────────────────
77
  JOURNAL_FILE = "./lab_journal.csv"
78
+ # Уніфікована система кодування (як у Learning Playground)
79
  JOURNAL_CATEGORIES = [
80
  # Real Data Tools (S2-A)
81
  "S2-A·R1a", # Gray Zones Explorer
 
83
  "S2-A·R1c", # Real Variant Lookup
84
  "S2-A·R1d", # Literature Gap Finder
85
  "S2-A·R1e", # Druggable Orphans
86
+ "S2-A·R1f", # Research Assistant (Chatbot)
87
  # Learning Sandbox (S2-B)
88
  "S2-B·R1a", # miRNA Explorer
89
  "S2-B·R1b", # siRNA Targets
 
118
  if df.empty:
119
  return f"No entries for category: {category}"
120
  # Format for better readability
121
+ df_display = df[["timestamp", "category", "action", "result_summary"]].tail(50)
122
  return df_display.to_markdown(index=False)
123
  except Exception as e:
124
  print(f"Journal read error: {e}")
125
  return "Error reading journal."
126
 
127
+ def clear_journal():
128
+ """Delete the journal file."""
129
+ try:
130
+ if os.path.exists(JOURNAL_FILE):
131
+ os.remove(JOURNAL_FILE)
132
+ return "Journal cleared."
133
+ except Exception as e:
134
+ print(f"Clear journal error: {e}")
135
+ return "Error clearing journal."
136
+
137
  # ─────────────────────────────────────────────
138
  # CONSTANTS
139
  # ─────────────────────────────────────────────
 
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
  # ─────────────────────────────────────────────
 
292
 
293
  fig, ax = plt.subplots(figsize=(6, 8), facecolor="white")
294
  valid = df[cancer_type].fillna(0).values.reshape(-1, 1)
 
295
  cmap = plt.colormaps.get_cmap("YlOrRd")
296
  cmap.set_bad("white")
297
  masked = np.ma.masked_where(df[cancer_type].isna().values.reshape(-1, 1), valid)
 
324
  )
325
 
326
  gaps_md = "\n\n---\n\n".join(gap_cards) if gap_cards else "No data available."
327
+ journal_log("S2-A·R1a", f"cancer={cancer_type}", f"gaps={[p for p,_ in sorted_procs[:5]]}")
328
  source_note = f"*Source: PubMed E-utilities | Date: {today}*"
329
  return img, gaps_md + "\n\n" + source_note
330
 
 
458
  f"and replace `_load_depmap_sample()` in `app.py`."
459
  )
460
  if not result_df.empty:
461
+ journal_log("S2-A·R1b", f"cancer={cancer_type}", f"top_gap={result_df.iloc[0]['Gene']}")
462
  else:
463
+ journal_log("S2-A·R1b", f"cancer={cancer_type}", "no targets found")
464
  return result_df, note
465
 
466
 
 
586
  )
587
 
588
  result_parts.append(f"\n*Source: ClinVar E-utilities + gnomAD GraphQL | Date: {today}*")
589
+ journal_log("S2-A·R1c", f"hgvs={hgvs}", result_parts[0][:100] if result_parts else "no results")
590
  return "\n\n".join(result_parts)
591
 
592
 
 
652
 
653
  summary = "\n\n".join(gap_text)
654
  summary += f"\n\n*Source: PubMed E-utilities | Date: {today}*"
655
+ journal_log("S2-A·R1d", f"cancer={cancer_type}, kw={keyword}", summary[:100])
656
  return img, summary
657
 
658
 
 
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("S2-A·R1e", f"cancer={cancer_type}", f"orphans={len(df)}")
753
  return df, note
754
+
755
+
756
  # ─────────────────────────────────────────────
757
  # GROUP B — LEARNING SANDBOX
758
  # ─────────────────────────────────────────────
 
832
  "Expression log2FC": changes,
833
  })
834
  context = f"\n\n**Cancer Context:** {db['cancer_context']}"
835
+ journal_log("S2-B·R1a", f"gene={gene}", f"top_miRNA={mirnas[0]}")
836
  return img, df.to_markdown(index=False) + context
837
 
838
 
 
894
  "Off-target Risk": off_risk,
895
  "Delivery Challenge": delivery,
896
  })
897
+ journal_log("S2-B·R1b", f"cancer={cancer}", f"top={targets[0]}")
898
  return img, df.to_markdown(index=False)
899
 
900
 
 
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("S2-B·R1c", f"PEG={peg_mol_pct}%,size={particle_size_nm}nm", f"ApoE={apoe_pct:.1f}%")
954
  return img, interpretation
955
 
956
 
 
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("S2-B·R1d", f"kon_alb={kon_albumin},kon_apoe={kon_apoe}", note[:80])
998
  return img, note
999
 
1000
 
 
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("S2-B·R1e", f"class={classification}", output[:100])
1052
  return output
 
 
1053
  # ─────────────────────────────────────────────
1054
  # GRADIO UI ASSEMBLY
1055
  # ─────────────────────────────────────────────
 
1066
  background: #f8f9fa; border-left: 4px solid #d73027;
1067
  padding: 10px 14px; margin: 6px 0; border-radius: 4px;
1068
  }
 
 
1069
  /* Зміна курсора на pointer для всіх інтерактивних елементів */
1070
  .gr-dropdown, .gr-button, .gr-slider, .gr-radio, .gr-checkbox,
1071
+ .tab-nav button, .gr-accordion, .gr-dataset {
1072
  cursor: pointer !important;
1073
  }
1074
  /* Для текстових полів залишаємо звичайний текстовий курсор */
1075
  .gr-textbox input, .gr-textarea textarea {
1076
  cursor: text !important;
1077
  }
1078
+ footer { display: none !important; }
1079
  """
1080
 
1081
 
 
1090
  with gr.Row():
1091
  # ── MAIN CONTENT ──
1092
  with gr.Column(scale=4):
1093
+ with gr.Tabs() as main_tabs:
1094
 
1095
  # ════════════════════════════════
1096
  # GROUP A — REAL DATA TOOLS
 
1371
  )
1372
  b5_btn.click(b5_run, inputs=[b5_class], outputs=[b5_result])
1373
 
1374
+ # ════════════════════════════════
1375
+ # JOURNAL — окрема вкладка
1376
+ # ════════════════════════════════
1377
+ with gr.Tab("📓 Journal"):
1378
+ gr.Markdown("## Lab Journal — Full History")
1379
+ with gr.Row():
1380
+ journal_filter = gr.Dropdown(
1381
+ choices=["All"] + JOURNAL_CATEGORIES,
1382
+ value="All",
1383
+ label="Filter by category"
1384
+ )
1385
+ refresh_btn = gr.Button("🔄 Refresh", size="sm", variant="secondary")
1386
+ clear_btn = gr.Button("🗑️ Clear Journal", size="sm", variant="stop")
1387
+ journal_display = gr.Markdown(value=journal_read())
1388
+
1389
+ def refresh_journal(category):
1390
+ return journal_read(category)
1391
+
1392
+ refresh_btn.click(refresh_journal, inputs=[journal_filter], outputs=journal_display)
1393
+ clear_btn.click(clear_journal, [], journal_display).then(
1394
+ refresh_journal, inputs=[journal_filter], outputs=journal_display
1395
+ )
1396
+ journal_filter.change(refresh_journal, inputs=[journal_filter], outputs=journal_display)
1397
+
1398
+ # ── SIDEBAR (Quick Note) ──
1399
  with gr.Column(scale=1, min_width=260):
1400
+ gr.Markdown("## 📝 Quick Note")
1401
  note_category = gr.Dropdown(
1402
  choices=JOURNAL_CATEGORIES,
1403
  value="Manual",
 
1413
  clear_note_btn = gr.Button("🗑️ Clear", size="sm")
1414
  save_status = gr.Markdown("")
1415
 
 
 
 
 
 
 
 
 
 
 
1416
  def save_note(category, note):
1417
  if note.strip():
1418
  journal_log(category, "manual note", note, note)
1419
  return "✅ Note saved.", ""
1420
  return "⚠️ Note is empty.", ""
1421
 
 
 
 
1422
  save_btn.click(
1423
  save_note,
1424
  inputs=[note_category, note_input],
1425
  outputs=[save_status, note_input]
1426
  )
1427
  clear_note_btn.click(lambda: ("", ""), outputs=[note_input, save_status])
 
 
1428
 
1429
  # === Інтеграція з Learning Playground ===
1430
  with gr.Row():