enshicoolsoda commited on
Commit
cbfbd67
·
verified ·
1 Parent(s): 823845b

Evaluation_zero shot

Browse files
Files changed (1) hide show
  1. app.py +97 -526
app.py CHANGED
@@ -2,6 +2,7 @@ import json
2
  import os
3
  import requests
4
  import gradio as gr
 
5
 
6
  # -----------------------------
7
  # 1. Configuration & Data Mapping
@@ -14,560 +15,130 @@ CANCER_MAP = {
14
  "Head and Neck Cancer": "data/hnsc_combined_data.json",
15
  }
16
 
17
- COMMON_AGENTS = [
18
- "Carboplatin", "Paclitaxel", "Cisplatin", "Gemcitabine", "Doxorubicin",
19
- "Cyclophosphamide", "Docetaxel", "5-Fluorouracil", "Pemetrexed",
20
- "Radiation: External Beam", "Radiation: Brachytherapy", "Tamoxifen",
21
- "Anastrozole", "Pembrolizumab", "Trastuzumab", "Other"
22
- ]
 
 
 
 
23
 
24
  # -----------------------------
25
  # 2. AI Backend Function
26
  # -----------------------------
27
- def ollama_chat(messages, temperature=0.2):
28
  endpoint = os.getenv("OLLAMA_ENDPOINT")
29
- if not endpoint:
30
- return "Error: OLLAMA_ENDPOINT Secret not set in Hugging Face."
31
 
32
  url = f"{endpoint}/api/chat"
33
- headers = {
34
- "Content-Type": "application/json",
35
- "ngrok-skip-browser-warning": "true"
36
- }
37
-
38
  payload = {
39
  "model": "qwen2.5:7b",
40
  "messages": messages,
41
  "stream": False,
42
  "options": {"temperature": float(temperature), "num_ctx": 8192}
43
  }
44
-
45
  try:
46
  r = requests.post(url, json=payload, headers=headers, timeout=120)
47
- if r.status_code == 200:
48
- return r.json().get("message", {}).get("content", "Error: Empty response")
49
- return f"Server Error {r.status_code}: {r.text}"
50
- except Exception as e:
51
- return f"Connection Error: {str(e)}"
52
 
53
  # -----------------------------
54
- # 3. Helper Functions & UI Logic
55
  # -----------------------------
56
- def load_new_cancer_data(cancer_type):
57
- file_path = CANCER_MAP.get(cancer_type)
58
- if not file_path or not os.path.exists(file_path):
59
- return gr.update(choices=[], value=None), {}, f"Error: {file_path} not found."
60
 
61
- try:
62
- with open(file_path, "r") as f:
63
- data = json.load(f)
64
- patient_ids = sorted([str(k) for k in data.keys() if k is not None])
65
- return gr.update(choices=patient_ids, value=patient_ids[0] if patient_ids else None), data, "Data loaded successfully."
66
- except Exception as e:
67
- return gr.update(choices=[], value=None), {}, f"Error reading file: {str(e)}"
68
-
69
- def get_patient_json(p_id, current_data):
70
- if not current_data:
71
- return "No data loaded."
72
- patient_data = current_data.get(p_id)
73
- if patient_data:
74
- return json.dumps(patient_data, indent=2)
75
- return "Patient not found."
76
-
77
- def build_multiphase_simulation(age, sex, stage, c_type,
78
- t1_agent, t1_dose, t1_start, t1_end,
79
- t2_agent, t2_dose, t2_start, t2_end):
80
- treatment_list = []
81
- def add_phase(agent, dose, start, end):
82
- if agent:
83
- treatment_list.append({
84
- "AGENT": agent, "TOTAL_DOSE": dose,
85
- "START_DAY_RELATIVE": start, "STOP_DAY_RELATIVE": end,
86
- "STATUS": "SIMULATED"
87
- })
88
- add_phase(t1_agent, t1_dose, t1_start, t1_end)
89
- add_phase(t2_agent, t2_dose, t2_start, t2_end)
90
-
91
- sim_data = {
92
- "demographics": {"AGE": int(age), "SEX": sex, "STAGE": stage},
93
- "sample": {"CANCER_TYPE": c_type},
94
- "treatments": treatment_list
95
- }
96
- return json.dumps(sim_data, indent=2)
97
 
98
- def normalize_history(history):
99
- api_history = []
100
- if not history: return api_history
101
 
102
- for item in history:
103
- role = "user"
104
- content = ""
105
-
106
- # Extract role and content depending on the format Gradio provides
107
- if isinstance(item, dict):
108
- role = item.get("role", "user")
109
- content = item.get("content", "")
110
- elif hasattr(item, "role") and hasattr(item, "content"):
111
- role = getattr(item, "role")
112
- content = getattr(item, "content")
113
- elif isinstance(item, (list, tuple)) and len(item) == 2:
114
- if item[0]: api_history.append({"role": "user", "content": str(item[0])})
115
- if item[1]: api_history.append({"role": "assistant", "content": str(item[1])})
116
- continue
117
-
118
- # THE FIX: Force Gradio's multimodal array format into a plain string for Ollama
119
- if isinstance(content, list):
120
- content = " ".join([str(p.get("text", p)) if isinstance(p, dict) else str(p) for p in content])
121
- elif isinstance(content, tuple):
122
- content = str(content[0])
123
- else:
124
- content = str(content)
125
-
126
- api_history.append({"role": role, "content": content})
127
-
128
- return api_history
129
- # def normalize_history(history):
130
- # api_history = []
131
- # if not history: return api_history
132
 
133
- # for item in history:
134
- # if isinstance(item, dict):
135
- # api_history.append({"role": item.get("role", "user"), "content": item.get("content", "")})
136
- # elif hasattr(item, "role") and hasattr(item, "content"):
137
- # api_history.append({"role": getattr(item, "role"), "content": getattr(item, "content")})
138
- # elif isinstance(item, (list, tuple)) and len(item) == 2:
139
- # if item[0]: api_history.append({"role": "user", "content": str(item[0])})
140
- # if item[1]: api_history.append({"role": "assistant", "content": str(item[1])})
141
-
142
- # return api_history
143
-
144
- def safe_parse_json(text: str):
145
- try: return json.loads(text)
146
- except:
147
- start = text.find('{'); end = text.rfind('}')
148
- if start != -1 and end != -1:
149
- try: return json.loads(text[start:end+1])
150
- except: pass
151
- return None
152
-
153
- def compute_missing_fields(patient_json):
154
- if not isinstance(patient_json, dict): return [("JSON Parse Failed", "error")]
155
- missing = []
156
- fields = ["age", "sex", "cancer_type", "stage"]
157
- for k in fields:
158
- if patient_json.get(k) in (None, "", [], "null"):
159
- missing.append((str(k), "missing"))
160
- return missing if missing else [("(none detected)", "ok")]
161
-
162
- def extract_case_snapshot(history_list):
163
- history_text = ""
164
- for item in normalize_history(history_list):
165
- role = item["role"].upper()
166
- content = item["content"]
167
- history_text += f"{role}: {content}\n"
168
-
169
- instr = "Extract a JSON object for the patient summary. Return ONLY valid JSON."
170
- msgs = [{"role": "system", "content": instr}, {"role": "user", "content": history_text}]
171
- raw = ollama_chat(msgs, temperature=0.0)
172
- parsed = safe_parse_json(raw)
173
 
174
- if parsed is None:
175
- return str(raw), [("Failed", "error")]
176
- return json.dumps(parsed, indent=2), compute_missing_fields(parsed)
177
-
178
- def respond(message, history):
179
- if history is None: history = []
180
- api_msgs = normalize_history(history)
181
 
182
- # sys_prompt = {"role": "system", "content": "You are an oncology research assistant. Format: Summary, Prediction, Factors, Missing Info."}
183
- SYSTEM_PROMPT = """You are an oncology research assistant. Analyze the patient data and output strictly in this exact format.
184
-
185
- CRUCIAL TIMELINE RULES:
186
- - For 1-year predictions: ONLY consider baseline clinical data at diagnosis AND treatments administered within the first year (Days 0 to 365).
187
- - For 5-year predictions: Consider baseline clinical data AND all treatments administered up to the 5-year mark (Days 0 to 1825).
188
-
189
- === Summary ===
190
- The patient is a [Age] year-old [Sex] with Stage [Stage], [Cancer Type] at diagnosis.
191
 
192
- === Prediction ===
193
- - 1-year cancer survival: [Estimated %]
194
- - 1-year cancer recurrence: [Estimated %]
195
- - 5-year cancer survival: [Estimated %]
196
- - 5-year cancer recurrence: [Estimated %]
197
-
198
- === Factors affecting prediction ===
199
- - 1-year survival: [1-2 concise reasons based on baseline factors and treatments from Days 0-365]
200
- - 1-year recurrence: [1-2 concise reasons based on baseline factors and treatments from Days 0-365]
201
- - 5-year survival: [1-2 concise reasons based on baseline factors and treatments from Days 0-1825]
202
- - 5-year recurrence: [1-2 concise reasons based on baseline factors and treatments from Days 0-1825]
 
 
 
 
 
 
 
 
 
 
 
203
 
204
- === Missing Information ===
205
- - [List any missing key clinical fields, or state 'None detected']
206
- """
 
207
 
208
- sys_prompt = {"role": "system", "content": SYSTEM_PROMPT}
209
- current_user_msg = {"role": "user", "content": str(message)}
 
 
 
 
210
 
211
- response_text = ollama_chat([sys_prompt] + api_msgs + [current_user_msg])
212
-
213
- history.append({"role": "user", "content": str(message)})
214
- history.append({"role": "assistant", "content": response_text})
215
-
216
- # We still run the snapshot logic behind the scenes to calculate what is missing
217
- snapshot, missing = extract_case_snapshot(history)
218
-
219
- # But we no longer return the 'snapshot' variable to the UI
220
- return "", history, missing
221
 
222
  # -----------------------------
223
- # 4. UI Layout
224
  # -----------------------------
225
- CSS = """
226
- #titlebar { text-align: center; margin-bottom: 20px; }
227
- .title-text { font-size: 2.5rem; font-weight: 900; color: #2c3e50; }
228
- .phase-header { font-weight: bold; color: #2980b9; margin-top: 15px; border-bottom: 1px solid #eee; }
229
- """
230
-
231
- with gr.Blocks(css=CSS, title="OncoRisk Public Demo") as demo:
232
- gr.HTML('<div id="titlebar"><div class="title-text">Oncology Chat (developing)</div></div>')
233
- full_data_state = gr.State({})
234
-
235
- with gr.Row():
236
- # LEFT COLUMN: Data Entry & Simulation Tabs
237
- with gr.Column(scale=1):
238
- with gr.Tabs():
239
- with gr.TabItem("Data Selection"):
240
- cancer_select = gr.Dropdown(label="1. Select Cancer Type", choices=list(CANCER_MAP.keys()), value="Uterine Cancer")
241
- patient_select = gr.Dropdown(label="2. Select Patient ID", choices=[])
242
- status_text = gr.Markdown()
243
-
244
- with gr.TabItem("Simulation Tool"):
245
- gr.Markdown("### 1. Patient Profile")
246
- with gr.Row():
247
- s_age = gr.Number(label="Age", value=60)
248
- s_sex = gr.Radio(["Female", "Male"], label="Sex", value="Female")
249
- s_type = gr.Dropdown(["Uterine Cancer", "Breast Cancer", "Lung Cancer", "Head/Neck Cancer"], label="Cancer Type")
250
- s_stage = gr.Dropdown(["Stage I", "Stage II", "Stage III", "Stage IV"], label="Pathologic Stage")
251
-
252
- gr.HTML('<div class="phase-header">Treatment #1</div>')
253
- with gr.Row():
254
- s_t1_agent = gr.Dropdown(choices=COMMON_AGENTS, label="Agent")
255
- s_t1_dose = gr.Textbox(label="Dosage (e.g. 150mg/m2)")
256
- with gr.Row():
257
- s_t1_start = gr.Number(label="Start Day", value=30)
258
- s_t1_end = gr.Number(label="End Day", value=60)
259
-
260
- gr.HTML('<div class="phase-header">Treatment #2</div>')
261
- with gr.Row():
262
- s_t2_agent = gr.Dropdown(choices=COMMON_AGENTS, label="Agent")
263
- s_t2_dose = gr.Textbox(label="Dosage")
264
- with gr.Row():
265
- s_t2_start = gr.Number(label="Start Day", value=90)
266
- s_t2_end = gr.Number(label="End Day", value=180)
267
-
268
- sim_btn = gr.Button("Generate Simulation", variant="secondary")
269
-
270
- # JSON code box has been completely removed from the layout here
271
- missing_output = gr.HighlightedText(label="Data Completeness")
272
-
273
- # RIGHT COLUMN: Chat & Analysis
274
- with gr.Column(scale=2):
275
- chatbot = gr.Chatbot(height=550)
276
- msg_input = gr.Textbox(label="Input Box", lines=6, placeholder="Ask anything oncology related")
277
- submit_btn = gr.Button("Analyze Case", variant="primary")
278
- gr.ClearButton([msg_input, chatbot])
279
-
280
- # --- Event Logic ---
281
- cancer_select.change(
282
- fn=load_new_cancer_data,
283
- inputs=[cancer_select],
284
- outputs=[patient_select, full_data_state, status_text]
285
- )
286
-
287
- patient_select.change(
288
- fn=get_patient_json,
289
- inputs=[patient_select, full_data_state],
290
- outputs=[msg_input]
291
- )
292
-
293
- sim_btn.click(
294
- fn=build_multiphase_simulation,
295
- inputs=[s_age, s_sex, s_stage, s_type, s_t1_agent, s_t1_dose, s_t1_start, s_t1_end, s_t2_agent, s_t2_dose, s_t2_start, s_t2_end],
296
- outputs=msg_input
297
- )
298
-
299
- # Removed json_output from the outputs list here
300
- submit_btn.click(
301
- fn=respond,
302
- inputs=[msg_input, chatbot],
303
- outputs=[msg_input, chatbot, missing_output]
304
- )
305
-
306
- # Load initial data on startup
307
- demo.load(
308
- fn=load_new_cancer_data,
309
- inputs=[cancer_select],
310
- outputs=[patient_select, full_data_state, status_text]
311
- )
312
-
313
- demo.launch()
314
-
315
- # import json
316
- # import os
317
- # import requests
318
- # import gradio as gr
319
-
320
- # # -----------------------------
321
- # # 1. Configuration & Data Mapping
322
- # # -----------------------------
323
- # CANCER_MAP = {
324
- # "Uterine Cancer": "data/ucec_combined_data.json",
325
- # "Breast Cancer": "data/brca_combined_data.json",
326
- # "Lung Cancer": "data/luad_combined_data.json",
327
- # "Bladder Cancer": "data/blca_combined_data.json",
328
- # "Head and Neck Cancer": "data/hnsc_combined_data.json",
329
- # }
330
-
331
- # COMMON_AGENTS = [
332
- # "Carboplatin", "Paclitaxel", "Cisplatin", "Gemcitabine", "Doxorubicin",
333
- # "Cyclophosphamide", "Docetaxel", "5-Fluorouracil", "Pemetrexed",
334
- # "Radiation: External Beam", "Radiation: Brachytherapy", "Tamoxifen",
335
- # "Anastrozole", "Pembrolizumab", "Trastuzumab", "Other"
336
- # ]
337
-
338
- # # -----------------------------
339
- # # 2. AI Backend Function
340
- # # -----------------------------
341
- # def ollama_chat(messages, temperature=0.2):
342
- # endpoint = os.getenv("OLLAMA_ENDPOINT")
343
- # if not endpoint:
344
- # return "Error: OLLAMA_ENDPOINT Secret not set in Hugging Face."
345
-
346
- # url = f"{endpoint}/api/chat"
347
- # headers = {
348
- # "Content-Type": "application/json",
349
- # "ngrok-skip-browser-warning": "true"
350
- # }
351
-
352
- # payload = {
353
- # "model": "qwen2.5:7b",
354
- # "messages": messages,
355
- # "stream": False,
356
- # "options": {"temperature": float(temperature), "num_ctx": 8192}
357
- # }
358
-
359
- # try:
360
- # r = requests.post(url, json=payload, headers=headers, timeout=120)
361
- # if r.status_code == 200:
362
- # return r.json().get("message", {}).get("content", "Error: Empty response")
363
- # return f"Server Error {r.status_code}: {r.text}"
364
- # except Exception as e:
365
- # return f"Connection Error: {str(e)}"
366
-
367
- # # -----------------------------
368
- # # 3. Helper Functions & UI Logic
369
- # # -----------------------------
370
- # def load_new_cancer_data(cancer_type):
371
- # file_path = CANCER_MAP.get(cancer_type)
372
- # if not file_path or not os.path.exists(file_path):
373
- # return gr.update(choices=[], value=None), {}, f"Error: {file_path} not found."
374
-
375
- # try:
376
- # with open(file_path, "r") as f:
377
- # data = json.load(f)
378
- # patient_ids = sorted([str(k) for k in data.keys() if k is not None])
379
- # return gr.update(choices=patient_ids, value=patient_ids[0] if patient_ids else None), data, "Data loaded successfully."
380
- # except Exception as e:
381
- # return gr.update(choices=[], value=None), {}, f"Error reading file: {str(e)}"
382
-
383
- # def get_patient_json(p_id, current_data):
384
- # if not current_data:
385
- # return "No data loaded."
386
- # patient_data = current_data.get(p_id)
387
- # if patient_data:
388
- # return json.dumps(patient_data, indent=2)
389
- # return "Patient not found."
390
-
391
- # def build_multiphase_simulation(age, sex, stage, c_type,
392
- # t1_agent, t1_dose, t1_start, t1_end,
393
- # t2_agent, t2_dose, t2_start, t2_end):
394
- # treatment_list = []
395
- # def add_phase(agent, dose, start, end):
396
- # if agent:
397
- # treatment_list.append({
398
- # "AGENT": agent, "TOTAL_DOSE": dose,
399
- # "START_DAY_RELATIVE": start, "STOP_DAY_RELATIVE": end,
400
- # "STATUS": "SIMULATED"
401
- # })
402
- # add_phase(t1_agent, t1_dose, t1_start, t1_end)
403
- # add_phase(t2_agent, t2_dose, t2_start, t2_end)
404
-
405
- # sim_data = {
406
- # "demographics": {"AGE": int(age), "SEX": sex, "STAGE": stage},
407
- # "sample": {"CANCER_TYPE": c_type},
408
- # "treatments": treatment_list
409
- # }
410
- # return json.dumps(sim_data, indent=2)
411
-
412
- # def normalize_history(history):
413
- # """Safely converts any Gradio history format into Ollama's exact dictionary format."""
414
- # api_history = []
415
- # if not history: return api_history
416
-
417
- # for item in history:
418
- # # If it's the new Gradio 5 format (dictionaries)
419
- # if isinstance(item, dict):
420
- # api_history.append({"role": item.get("role", "user"), "content": item.get("content", "")})
421
- # # If it's a Gradio ChatMessage Object
422
- # elif hasattr(item, "role") and hasattr(item, "content"):
423
- # api_history.append({"role": getattr(item, "role"), "content": getattr(item, "content")})
424
- # # If it's the old Gradio tuple/list format
425
- # elif isinstance(item, (list, tuple)) and len(item) == 2:
426
- # if item[0]: api_history.append({"role": "user", "content": str(item[0])})
427
- # if item[1]: api_history.append({"role": "assistant", "content": str(item[1])})
428
-
429
- # return api_history
430
-
431
- # def safe_parse_json(text: str):
432
- # try: return json.loads(text)
433
- # except:
434
- # start = text.find('{'); end = text.rfind('}')
435
- # if start != -1 and end != -1:
436
- # try: return json.loads(text[start:end+1])
437
- # except: pass
438
- # return None
439
-
440
- # def compute_missing_fields(patient_json):
441
- # if not isinstance(patient_json, dict): return [("JSON Parse Failed", "error")]
442
- # missing = []
443
- # fields = ["age", "sex", "cancer_type", "stage"]
444
- # for k in fields:
445
- # if patient_json.get(k) in (None, "", [], "null"):
446
- # missing.append((str(k), "missing"))
447
- # return missing if missing else [("(none detected)", "ok")]
448
-
449
- # def extract_case_snapshot(history_list):
450
- # history_text = ""
451
- # # Use normalized history to extract plain text
452
- # for item in normalize_history(history_list):
453
- # role = item["role"].upper()
454
- # content = item["content"]
455
- # history_text += f"{role}: {content}\n"
456
-
457
- # instr = "Extract a JSON object for the patient summary. Return ONLY valid JSON."
458
- # msgs = [{"role": "system", "content": instr}, {"role": "user", "content": history_text}]
459
- # raw = ollama_chat(msgs, temperature=0.0)
460
- # parsed = safe_parse_json(raw)
461
-
462
- # if parsed is None:
463
- # return str(raw), [("Failed", "error")]
464
- # return json.dumps(parsed, indent=2), compute_missing_fields(parsed)
465
-
466
- # def respond(message, history):
467
- # if history is None: history = []
468
- # api_msgs = normalize_history(history)
469
-
470
- # sys_prompt = {"role": "system", "content": "You are an oncology research assistant. Format: Summary, Prediction, Factors, Missing Info."}
471
- # current_user_msg = {"role": "user", "content": str(message)}
472
-
473
- # response_text = ollama_chat([sys_prompt] + api_msgs + [current_user_msg])
474
-
475
- # # NEW GRADIO 5 FORMAT: Append dictionaries, not a list of two strings!
476
- # history.append({"role": "user", "content": str(message)})
477
- # history.append({"role": "assistant", "content": response_text})
478
-
479
- # snapshot, missing = extract_case_snapshot(history)
480
- # return "", history, snapshot, missing
481
-
482
- # # -----------------------------
483
- # # 4. UI Layout
484
- # # -----------------------------
485
- # CSS = """
486
- # #titlebar { text-align: center; margin-bottom: 20px; }
487
- # .title-text { font-size: 2.5rem; font-weight: 900; color: #2c3e50; }
488
- # .phase-header { font-weight: bold; color: #2980b9; margin-top: 15px; border-bottom: 1px solid #eee; }
489
- # """
490
-
491
- # with gr.Blocks(css=CSS, title="OncoRisk Public Demo") as demo:
492
- # gr.HTML('<div id="titlebar"><div class="title-text">Oncology Chat (Developing)</div></div>')
493
- # full_data_state = gr.State({})
494
-
495
- # with gr.Row():
496
- # # LEFT COLUMN: Data Entry & Simulation Tabs
497
- # with gr.Column(scale=1):
498
- # with gr.Tabs():
499
- # with gr.TabItem("Data Selection"):
500
- # cancer_select = gr.Dropdown(label="1. Select Cancer Type", choices=list(CANCER_MAP.keys()), value="Uterine Cancer")
501
- # patient_select = gr.Dropdown(label="2. Select Patient ID", choices=[])
502
- # status_text = gr.Markdown()
503
-
504
- # with gr.TabItem("Simulation Tool"):
505
- # gr.Markdown("### 1. Patient Profile")
506
- # with gr.Row():
507
- # s_age = gr.Number(label="Age", value=60)
508
- # s_sex = gr.Radio(["Female", "Male"], label="Sex", value="Female")
509
- # s_type = gr.Dropdown(["Uterine Cancer", "Breast Cancer", "Lung Cancer", "Head/Neck Cancer"], label="Cancer Type")
510
- # s_stage = gr.Dropdown(["Stage I", "Stage II", "Stage III", "Stage IV"], label="Pathologic Stage")
511
-
512
- # gr.HTML('<div class="phase-header">Treatment #1</div>')
513
- # with gr.Row():
514
- # s_t1_agent = gr.Dropdown(choices=COMMON_AGENTS, label="Agent")
515
- # s_t1_dose = gr.Textbox(label="Dosage (e.g. 150mg/m2)")
516
- # with gr.Row():
517
- # s_t1_start = gr.Number(label="Start Day", value=30)
518
- # s_t1_end = gr.Number(label="End Day", value=60)
519
-
520
- # gr.HTML('<div class="phase-header">Treatment #2</div>')
521
- # with gr.Row():
522
- # s_t2_agent = gr.Dropdown(choices=COMMON_AGENTS, label="Agent")
523
- # s_t2_dose = gr.Textbox(label="Dosage")
524
- # with gr.Row():
525
- # s_t2_start = gr.Number(label="Start Day", value=90)
526
- # s_t2_end = gr.Number(label="End Day", value=180)
527
-
528
- # sim_btn = gr.Button("Generate Simulation", variant="secondary")
529
-
530
- # json_output = gr.Code(label="Extracted Case State (JSON)", language="json", lines=10)
531
- # missing_output = gr.HighlightedText(label="Data Completeness")
532
-
533
- # # RIGHT COLUMN: Chat & Analysis
534
- # with gr.Column(scale=2):
535
- # chatbot = gr.Chatbot(height=550)
536
- # msg_input = gr.Textbox(label="Clinical Input (JSON)", lines=6, placeholder="Data will appear here...")
537
- # submit_btn = gr.Button("Analyze Case", variant="primary")
538
- # gr.ClearButton([msg_input, chatbot])
539
-
540
- # # --- Event Logic ---
541
- # cancer_select.change(
542
- # fn=load_new_cancer_data,
543
- # inputs=[cancer_select],
544
- # outputs=[patient_select, full_data_state, status_text]
545
- # )
546
-
547
- # patient_select.change(
548
- # fn=get_patient_json,
549
- # inputs=[patient_select, full_data_state],
550
- # outputs=[msg_input]
551
- # )
552
-
553
- # sim_btn.click(
554
- # fn=build_multiphase_simulation,
555
- # inputs=[s_age, s_sex, s_stage, s_type, s_t1_agent, s_t1_dose, s_t1_start, s_t1_end, s_t2_agent, s_t2_dose, s_t2_start, s_t2_end],
556
- # outputs=msg_input
557
- # )
558
-
559
- # submit_btn.click(
560
- # fn=respond,
561
- # inputs=[msg_input, chatbot],
562
- # outputs=[msg_input, chatbot, json_output, missing_output]
563
- # )
564
-
565
- # # Load initial data on startup
566
- # demo.load(
567
- # fn=load_new_cancer_data,
568
- # inputs=[cancer_select],
569
- # outputs=[patient_select, full_data_state, status_text]
570
- # )
571
-
572
- # demo.launch()
573
-
 
2
  import os
3
  import requests
4
  import gradio as gr
5
+ import pandas as pd
6
 
7
  # -----------------------------
8
  # 1. Configuration & Data Mapping
 
15
  "Head and Neck Cancer": "data/hnsc_combined_data.json",
16
  }
17
 
18
+ # Map for the Ground Truth JSON keys
19
+ GT_MAP = {
20
+ "Uterine Cancer": "UCEC",
21
+ "Breast Cancer": "BRCA",
22
+ "Lung Cancer": "LUAD",
23
+ "Bladder Cancer": "BLCA",
24
+ "Head and Neck Cancer": "HNSC",
25
+ }
26
+
27
+ COMMON_AGENTS = ["Carboplatin", "Paclitaxel", "Cisplatin", "Gemcitabine", "Doxorubicin", "Other"]
28
 
29
  # -----------------------------
30
  # 2. AI Backend Function
31
  # -----------------------------
32
+ def ollama_chat(messages, temperature=0.1):
33
  endpoint = os.getenv("OLLAMA_ENDPOINT")
34
+ if not endpoint: return "Error: Endpoint not set."
 
35
 
36
  url = f"{endpoint}/api/chat"
37
+ headers = {"Content-Type": "application/json", "ngrok-skip-browser-warning": "true"}
 
 
 
 
38
  payload = {
39
  "model": "qwen2.5:7b",
40
  "messages": messages,
41
  "stream": False,
42
  "options": {"temperature": float(temperature), "num_ctx": 8192}
43
  }
 
44
  try:
45
  r = requests.post(url, json=payload, headers=headers, timeout=120)
46
+ return r.json().get("message", {}).get("content", "")
47
+ except: return "Connection Error"
 
 
 
48
 
49
  # -----------------------------
50
+ # 3. Evaluation Logic
51
  # -----------------------------
52
+ def run_evaluation(cancer_type):
53
+ # 1. Load Data
54
+ data_path = CANCER_MAP.get(cancer_type)
55
+ gt_path = "data/ground_truth_5yr_recurrence.json"
56
 
57
+ if not os.path.exists(data_path) or not os.path.exists(gt_path):
58
+ return "Error: Missing data or ground truth files."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ with open(data_path, 'r') as f: patient_db = json.load(f)
61
+ with open(gt_path, 'r') as f: all_gt = json.load(f)
 
62
 
63
+ gt_labels = all_gt.get(GT_MAP[cancer_type], {})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ # 2. Filter patients present in both
66
+ eval_ids = [pid for pid in gt_labels.keys() if pid in patient_db]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ results = []
69
+ tp, tn, fp, fn = 0, 0, 0, 0
 
 
 
 
 
70
 
71
+ yield f"Starting inference for {len(eval_ids)} patients in {cancer_type}..."
 
 
 
 
 
 
 
 
72
 
73
+ for i, pid in enumerate(eval_ids):
74
+ actual = gt_labels[pid] # "Yes" or "No"
75
+ patient_json = json.dumps(patient_db[pid])
76
+
77
+ # Zero-shot prompt
78
+ eval_prompt = [
79
+ {"role": "system", "content": "You are an oncology expert. Predict 5-year recurrence based ONLY on the provided JSON. Respond strictly with 'Yes' or 'No' and nothing else."},
80
+ {"role": "user", "content": f"Patient Data: {patient_json}"}
81
+ ]
82
+
83
+ prediction_raw = ollama_chat(eval_prompt).strip()
84
+ # Simple parser to find Yes/No in response
85
+ prediction = "Yes" if "yes" in prediction_raw.lower() else "No"
86
+
87
+ # Calculate Metrics
88
+ if prediction == "Yes" and actual == "Yes": tp += 1
89
+ elif prediction == "No" and actual == "No": tn += 1
90
+ elif prediction == "Yes" and actual == "No": fp += 1
91
+ elif prediction == "No" and actual == "Yes": fn += 1
92
+
93
+ if i % 5 == 0:
94
+ yield f"Processed {i+1}/{len(eval_ids)} patients..."
95
 
96
+ # 3. Final Metric Calculation
97
+ acc = (tp + tn) / len(eval_ids) if eval_ids else 0
98
+ sens = tp / (tp + fn) if (tp + fn) > 0 else 0
99
+ spec = tn / (tn + fp) if (tn + fp) > 0 else 0
100
 
101
+ summary = f"""
102
+ ### Evaluation Results: {cancer_type}
103
+ - **Total Patients Processed:** {len(eval_ids)}
104
+ - **Unweighted Accuracy:** {acc:.2%}
105
+ - **Sensitivity (Recall):** {sens:.2%}
106
+ - **Specificity:** {spec:.2%}
107
 
108
+ *Confusion Matrix: TP={tp}, TN={tn}, FP={fp}, FN={fn}*
109
+ """
110
+ yield summary
 
 
 
 
 
 
 
111
 
112
  # -----------------------------
113
+ # 4. UI Layout (Modified)
114
  # -----------------------------
115
+ with gr.Blocks(title="OncoRisk Eval & Demo") as demo:
116
+ gr.HTML('<div style="text-align:center"><h1>Oncology Risk Assistant</h1></div>')
117
+
118
+ with gr.Tabs():
119
+ # Tab 1: Your original Chat/Simulation UI
120
+ with gr.TabItem("Clinical Assistant"):
121
+ with gr.Row():
122
+ with gr.Column(scale=1):
123
+ cancer_select = gr.Dropdown(label="Select Cancer Type", choices=list(CANCER_MAP.keys()))
124
+ patient_select = gr.Dropdown(label="Select Patient ID")
125
+ submit_btn = gr.Button("Analyze Case", variant="primary")
126
+ missing_output = gr.HighlightedText(label="Completeness")
127
+ with gr.Column(scale=2):
128
+ chatbot = gr.Chatbot(height=500)
129
+ msg_input = gr.Textbox(label="Input Box", lines=5)
130
+
131
+ # Tab 2: NEW Evaluation Engine
132
+ with gr.TabItem("Performance Metrics (Zero-Shot)"):
133
+ gr.Markdown("### Run Zero-Shot Inference on Ground Truth")
134
+ eval_cancer_type = gr.Dropdown(label="Select Cancer for Evaluation", choices=list(CANCER_MAP.keys()))
135
+ run_eval_btn = gr.Button("Start Experiment", variant="secondary")
136
+ eval_results = gr.Markdown("Results will appear here after inference...")
137
+
138
+ # Logic for Evaluation
139
+ run_eval_btn.click(fn=run_evaluation, inputs=eval_cancer_type, outputs=eval_results)
140
+
141
+ # (Keep your existing Event Logic for Chat/Data Selection here...)
142
+ # ... [Same as your provided code] ...
143
+
144
+ demo.launch()