spriambada3 commited on
Commit
6fa1fee
·
1 Parent(s): 6e48304

save new rule integrated with cdss

Browse files
Files changed (5) hide show
  1. cdss.py +161 -124
  2. rules.py +10 -10
  3. rules_visualization.svg +12 -0
  4. validator.py +152 -0
  5. visualizer.py +0 -184
cdss.py CHANGED
@@ -307,12 +307,16 @@ def state_to_panels(state: PatientState) -> Tuple:
307
  )
308
 
309
 
310
- def inject_scenario(tag: str, cdss_on: bool, history_df: pd.DataFrame, historic_text: str):
 
 
311
  ps = SCENARIOS[tag]()
312
- if historic_text: # Add a newline if text already exists
313
  historic_text += f"\n[{datetime.now().strftime('%H:%M:%S')}] Scenario Injected: {ps.scenario}"
314
  else:
315
- historic_text = f"[{datetime.now().strftime('%H:%M:%S')}] Scenario Injected: {ps.scenario}"
 
 
316
  return process_and_update(ps, history_df, historic_text, cdss_on)
317
 
318
 
@@ -349,7 +353,7 @@ def manual_edit(
349
 
350
  def tick_timer(cdss_on, current_state, history_df, historic_text):
351
  if not current_state:
352
- return [gr.update()] * 18
353
  ps = PatientState(**current_state)
354
  ps.vitals = Vitals(**ps.vitals)
355
  ps = drift_vitals(ps)
@@ -383,9 +387,8 @@ def countdown_tick(last_tick_ts: float):
383
  import json
384
  import ast
385
 
386
- import graphviz
387
 
388
- def parse_rules(return_json=False):
389
  with open("rules.py", "r") as f:
390
  tree = ast.parse(f.read())
391
 
@@ -394,92 +397,132 @@ def parse_rules(return_json=False):
394
  for node in ast.walk(tree):
395
  if isinstance(node, ast.FunctionDef) and node.name == "rule_based_cdss":
396
  for body_item in node.body:
397
- if isinstance(body_item, ast.If) and hasattr(body_item.test, 'left') and hasattr(body_item.test.left, 'id') and body_item.test.left.id == 'state':
398
- patient_type = body_item.test.comparators[0].s
 
 
 
 
 
 
 
 
 
 
399
  if patient_type in rules:
400
  for rule_node in body_item.body:
401
  if isinstance(rule_node, ast.If):
402
  conditions = ast.unparse(rule_node.test)
403
  alert = ""
404
  for item in rule_node.body:
405
- if (isinstance(item, ast.Expr) and
406
- isinstance(item.value, ast.Call) and
407
- hasattr(item.value.func, 'value') and
408
- hasattr(item.value.func.value, 'id') and
409
- item.value.func.value.id == 'alerts' and
410
- item.value.func.attr == 'append'):
411
- alert = item.value.args[0].s
412
- rules[patient_type].append({"conditions": conditions, "alert": alert})
413
-
414
- if return_json:
415
- return rules
416
-
417
- dot = graphviz.Digraph(comment='Rule Based CDSS')
418
- dot.attr(rankdir='TB', splines='ortho')
419
-
420
- for patient_type, rule_list in rules.items():
421
- with dot.subgraph(name=f'cluster_{patient_type}') as c:
422
- c.attr(label=patient_type, style='filled', color='lightgrey')
423
- for i, rule in enumerate(rule_list):
424
- c.node(f'{patient_type}_{i}_cond', rule['conditions'], shape='box')
425
- c.node(f'{patient_type}_{i}_alert', rule['alert'], shape='plaintext')
426
- c.edge(f'{patient_type}_{i}_cond', f'{patient_type}_{i}_alert')
427
-
428
- return dot
429
-
430
- def save_rules(rules_json):
431
- rules = json.loads(rules_json)
432
-
433
- # Read the original file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  with open("rules.py", "r") as f:
435
  tree = ast.parse(f.read())
436
 
437
- # Find the function definition
438
  for node in ast.walk(tree):
439
  if isinstance(node, ast.FunctionDef) and node.name == "rule_based_cdss":
440
- # Clear the existing body
441
  node.body = []
442
-
443
- # Add the initial variable assignments
444
  node.body.append(ast.parse("v = state.vitals").body[0])
445
  node.body.append(ast.parse("labs = state.labs").body[0])
446
  node.body.append(ast.parse("alerts = []").body[0])
447
 
448
- # Add the rules
449
  for patient_type, rule_list in rules.items():
450
  if_patient_type_body = []
451
  for rule in rule_list:
452
- if_rule = ast.parse(f"if {rule['conditions']}:\n alerts.append(\"{rule['alert']}\")").body[0]
 
 
 
 
453
  if_patient_type_body.append(if_rule)
454
 
455
- if_patient_type = ast.If(
456
- test=ast.Compare(
457
- left=ast.Attribute(value=ast.Name(id='state', ctx=ast.Load()), attr='patient_type', ctx=ast.Load()),
458
- ops=[ast.Eq()],
459
- comparators=[ast.Constant(value=patient_type)]
460
- ),
461
- body=if_patient_type_body,
462
- orelse=[]
463
- )
464
- node.body.append(if_patient_type)
 
 
 
 
 
465
 
466
- # Add the return statement
467
- node.body.append(ast.parse("if not alerts:\n return \"Tidak ada alert prioritas tinggi. Lanjutkan pemantauan dan dokumentasi.\"").body[0])
468
- node.body.append(ast.parse("return \"\\n- \".join([\"ALERT:\"] + alerts)\"").body[0])
 
 
 
 
 
 
 
469
 
470
- # Unparse the modified AST
471
  new_code = ast.unparse(tree)
472
-
473
- # Write the new code back to the file
474
  with open("rules.py", "w") as f:
475
  f.write(new_code)
476
 
477
  return "Rules saved successfully."
478
 
479
- def generate_and_display_rules():
480
- dot = parse_rules()
481
- dot.render('rules_visualization', format='svg', cleanup=True)
482
- return 'rules_visualization.svg'
483
 
484
  # --- Build UI ---
485
  with gr.Blocks(
@@ -507,67 +550,57 @@ with gr.Blocks(
507
  temp_plot = gr.Plot()
508
  with gr.Tab("SpO₂"):
509
  spo2_plot = gr.Plot()
510
- with gr.Row():
511
- with gr.Column(scale=2):
512
- with gr.Accordion("Inject New Scenario (resets chart)", open=True):
513
- with gr.Row():
514
- btn_A0 = gr.Button("A0: Normal", elem_classes="small-btn")
515
- btn_A1 = gr.Button("A1: PPH", elem_classes="small-btn")
516
- btn_A2 = gr.Button("A2: Preeklampsia", elem_classes="small-btn")
517
- btn_A3 = gr.Button("A3: Sepsis", elem_classes="small-btn")
518
- with gr.Row():
519
- btn_B1 = gr.Button("B1: Prematuritas", elem_classes="small-btn")
520
- btn_B2 = gr.Button("B2: Asfiksia", elem_classes="small-btn")
521
- btn_B3 = gr.Button("B3: Sepsis", elem_classes="small-btn")
522
- with gr.Row():
523
- btn_C1 = gr.Button("C1: Bedah Komplikasi", elem_classes="small-btn")
524
- btn_C2 = gr.Button(
525
- "C2: Infeksi Pasca-Bedah", elem_classes="small-btn"
526
- )
527
- btn_C3 = gr.Button("C3: Kanker", elem_classes="small-btn")
528
- notes = gr.Textbox(label="Catatan Klinis", lines=2)
529
- labs_text = gr.Textbox(label="Lab (dict or text)", value="{}")
530
- labs_show = gr.Textbox(label="Labs (Parsed)", interactive=False)
531
- with gr.Column(scale=1):
532
- with gr.Group():
533
- patient_type_radio = gr.Radio(
534
- ["Mother", "Neonate", "Gyn"], label="Patient Type", value="Mother"
535
- )
536
- sbp = gr.Number(label="SBP")
537
- dbp = gr.Number(label="DBP")
538
- hr = gr.Number(label="HR")
539
- rr = gr.Number(label="RR")
540
- temp_c = gr.Number(label="Temp (°C)")
541
- spo2 = gr.Number(label="SpO₂ (%)")
542
- apply_manual = gr.Button("Apply Manual Edits", variant="secondary")
543
- with gr.Row():
544
- csv_file = gr.File(label="Load CSV to Graph")
545
- df_view = gr.Dataframe(label="History Table", wrap=True, interactive=False)
546
- cdss_toggle = gr.Checkbox(value=False, label="With CDSS (Gemini)")
547
- scenario_lbl = gr.Textbox(label="Active Scenario", interactive=False)
548
- countdown_lbl = gr.Label()
549
- historic_box = gr.Textbox(label="Historic Text", lines=12, interactive=False)
550
-
551
- with gr.TabItem("Rule Visualizer"):
552
- gr.Markdown("## CDSS Rule Visualizer and Editor")
553
-
554
- with gr.Row():
555
- with gr.Column():
556
- gr.Markdown("### Current Rules Visualization")
557
- rules_display = gr.Image(interactive=False)
558
- with gr.Column():
559
- gr.Markdown("### Edit Rules (JSON)")
560
- rules_editor = gr.Textbox(lines=20, label="Edit Rules in JSON format")
561
- save_button = gr.Button("Save Rules")
562
-
563
- def update_editor_and_rules():
564
- return generate_and_display_rules(), json.dumps(parse_rules(return_json=True), indent=2)
565
-
566
- demo.load(update_editor_and_rules, None, [rules_display, rules_editor])
567
- save_button.click(save_rules, inputs=rules_editor, outputs=gr.Textbox()).then(
568
- update_editor_and_rules, None, [rules_display, rules_editor]
569
- )
570
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
 
572
  with gr.Row():
573
  with gr.Column(scale=2):
@@ -669,7 +702,11 @@ with gr.Blocks(
669
  btn_C3,
670
  ],
671
  ):
672
- btn.click(inject_scenario, [gr.State(tag), cdss_toggle, history_df, historic_text], ui_outputs)
 
 
 
 
673
 
674
  csv_outputs = [history_df, df_view, bp_plot, hr_plot, rr_plot, temp_plot, spo2_plot]
675
  csv_file.change(load_csv, [csv_file, history_df], csv_outputs)
 
307
  )
308
 
309
 
310
+ def inject_scenario(
311
+ tag: str, cdss_on: bool, history_df: pd.DataFrame, historic_text: str
312
+ ):
313
  ps = SCENARIOS[tag]()
314
+ if historic_text: # Add a newline if text already exists
315
  historic_text += f"\n[{datetime.now().strftime('%H:%M:%S')}] Scenario Injected: {ps.scenario}"
316
  else:
317
+ historic_text = (
318
+ f"[{datetime.now().strftime('%H:%M:%S')}] Scenario Injected: {ps.scenario}"
319
+ )
320
  return process_and_update(ps, history_df, historic_text, cdss_on)
321
 
322
 
 
353
 
354
  def tick_timer(cdss_on, current_state, history_df, historic_text):
355
  if not current_state:
356
+ return [gr.update()] * 22
357
  ps = PatientState(**current_state)
358
  ps.vitals = Vitals(**ps.vitals)
359
  ps = drift_vitals(ps)
 
387
  import json
388
  import ast
389
 
 
390
 
391
+ def parse_rules():
392
  with open("rules.py", "r") as f:
393
  tree = ast.parse(f.read())
394
 
 
397
  for node in ast.walk(tree):
398
  if isinstance(node, ast.FunctionDef) and node.name == "rule_based_cdss":
399
  for body_item in node.body:
400
+ if (
401
+ isinstance(body_item, ast.If)
402
+ and isinstance(body_item.test, ast.Compare)
403
+ and isinstance(body_item.test.left, ast.Attribute)
404
+ and isinstance(body_item.test.left.value, ast.Name)
405
+ and body_item.test.left.value.id == "state"
406
+ and body_item.test.left.attr == "patient_type"
407
+ and isinstance(body_item.test.ops[0], ast.Eq)
408
+ and isinstance(body_item.test.comparators[0], ast.Constant)
409
+ ):
410
+
411
+ patient_type = body_item.test.comparators[0].value
412
  if patient_type in rules:
413
  for rule_node in body_item.body:
414
  if isinstance(rule_node, ast.If):
415
  conditions = ast.unparse(rule_node.test)
416
  alert = ""
417
  for item in rule_node.body:
418
+ if (
419
+ isinstance(item, ast.Expr)
420
+ and isinstance(item.value, ast.Call)
421
+ and hasattr(item.value.func, "value")
422
+ and hasattr(item.value.func.value, "id")
423
+ and item.value.func.value.id == "alerts"
424
+ and item.value.func.attr == "append"
425
+ and isinstance(item.value.args[0], ast.Constant)
426
+ ):
427
+ alert = item.value.args[0].value
428
+ rules[patient_type].append(
429
+ {"conditions": conditions, "alert": alert}
430
+ )
431
+ return rules
432
+
433
+
434
+ def rules_to_dataframes(rules):
435
+ dataframes = {}
436
+ for patient_type, rules_list in rules.items():
437
+ data = {"Conditions": [], "Alert": []}
438
+ for rule in rules_list:
439
+ data["Conditions"].append(rule["conditions"])
440
+ data["Alert"].append(rule["alert"])
441
+ df = pd.DataFrame(data)
442
+ dataframes[patient_type] = df
443
+ return dataframes
444
+
445
+
446
+ def dataframes_to_rules(dfs):
447
+ rules = {"Mother": [], "Neonate": [], "Gyn": []}
448
+ for patient_type, df in dfs.items():
449
+ if df is not None:
450
+ for index, row in df.iterrows():
451
+ if row["Conditions"] and row["Alert"]:
452
+ rules[patient_type].append(
453
+ {"conditions": row["Conditions"], "alert": row["Alert"]}
454
+ )
455
+ return rules
456
+
457
+
458
+ def add_row(df):
459
+ if df is None:
460
+ df = pd.DataFrame(columns=["Conditions", "Alert"])
461
+ df.loc[len(df)] = ["", ""]
462
+ return df
463
+
464
+
465
+ def save_rules(df_mother, df_neonate, df_gyn):
466
+ dfs = {"Mother": df_mother, "Neonate": df_neonate, "Gyn": df_gyn}
467
+ for patient_type, df in dfs.items():
468
+ if not isinstance(df, pd.DataFrame):
469
+ dfs[patient_type] = pd.DataFrame(df, columns=["Conditions", "Alert"])
470
+
471
+ rules = dataframes_to_rules(dfs)
472
+
473
  with open("rules.py", "r") as f:
474
  tree = ast.parse(f.read())
475
 
 
476
  for node in ast.walk(tree):
477
  if isinstance(node, ast.FunctionDef) and node.name == "rule_based_cdss":
 
478
  node.body = []
 
 
479
  node.body.append(ast.parse("v = state.vitals").body[0])
480
  node.body.append(ast.parse("labs = state.labs").body[0])
481
  node.body.append(ast.parse("alerts = []").body[0])
482
 
 
483
  for patient_type, rule_list in rules.items():
484
  if_patient_type_body = []
485
  for rule in rule_list:
486
+ conditions = (
487
+ rule["conditions"].replace("\r", " ").replace("\n", " ")
488
+ )
489
+ if_rule_str = f"if {conditions}:\n alerts.append({json.dumps(rule['alert'])})"
490
+ if_rule = ast.parse(if_rule_str).body[0]
491
  if_patient_type_body.append(if_rule)
492
 
493
+ if if_patient_type_body:
494
+ if_patient_type = ast.If(
495
+ test=ast.Compare(
496
+ left=ast.Attribute(
497
+ value=ast.Name(id="state", ctx=ast.Load()),
498
+ attr="patient_type",
499
+ ctx=ast.Load(),
500
+ ),
501
+ ops=[ast.Eq()],
502
+ comparators=[ast.Constant(value=patient_type)],
503
+ ),
504
+ body=if_patient_type_body,
505
+ orelse=[],
506
+ )
507
+ node.body.append(if_patient_type)
508
 
509
+ node.body.append(
510
+ ast.parse(
511
+ 'if not alerts:\n return "Tidak ada alert prioritas tinggi. Lanjutkan pemantauan dan dokumentasi."'
512
+ ).body[0]
513
+ )
514
+ node.body.append(
515
+ ast.parse(
516
+ 'return "\\n- ".join(["ALERT:"] + alerts)', mode="single"
517
+ ).body[0]
518
+ )
519
 
 
520
  new_code = ast.unparse(tree)
 
 
521
  with open("rules.py", "w") as f:
522
  f.write(new_code)
523
 
524
  return "Rules saved successfully."
525
 
 
 
 
 
526
 
527
  # --- Build UI ---
528
  with gr.Blocks(
 
550
  temp_plot = gr.Plot()
551
  with gr.Tab("SpO₂"):
552
  spo2_plot = gr.Plot()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
 
554
+ with gr.TabItem("Rule Editor"):
555
+ gr.Markdown("## CDSS Rule Editor")
556
+
557
+ initial_rules = parse_rules()
558
+ initial_dfs = rules_to_dataframes(initial_rules)
559
+
560
+ with gr.Tabs():
561
+ with gr.Tab("Mother"):
562
+ df_mother = gr.DataFrame(
563
+ value=initial_dfs["Mother"],
564
+ headers=["Conditions", "Alert"],
565
+ interactive=True,
566
+ row_count=(len(initial_dfs["Mother"]) + 1, "dynamic"),
567
+ type="pandas",
568
+ )
569
+ add_mother_btn = gr.Button("➕ Add Mother Rule")
570
+ add_mother_btn.click(add_row, inputs=df_mother, outputs=df_mother)
571
+
572
+ with gr.Tab("Neonate"):
573
+ df_neonate = gr.DataFrame(
574
+ value=initial_dfs["Neonate"],
575
+ headers=["Conditions", "Alert"],
576
+ interactive=True,
577
+ row_count=(len(initial_dfs["Neonate"]) + 1, "dynamic"),
578
+ type="pandas",
579
+ )
580
+ add_neonate_btn = gr.Button("➕ Add Neonate Rule")
581
+ add_neonate_btn.click(
582
+ add_row, inputs=df_neonate, outputs=df_neonate
583
+ )
584
+
585
+ with gr.Tab("Gyn"):
586
+ df_gyn = gr.DataFrame(
587
+ value=initial_dfs["Gyn"],
588
+ headers=["Conditions", "Alert"],
589
+ interactive=True,
590
+ row_count=(len(initial_dfs["Gyn"]) + 1, "dynamic"),
591
+ type="pandas",
592
+ )
593
+ add_gyn_btn = gr.Button("➕ Add Gyn Rule")
594
+ add_gyn_btn.click(add_row, inputs=df_gyn, outputs=df_gyn)
595
+
596
+ save_button = gr.Button("💾 Save Rules")
597
+ status_textbox = gr.Textbox(label="Status", interactive=False)
598
+
599
+ save_button.click(
600
+ save_rules,
601
+ inputs=[df_mother, df_neonate, df_gyn],
602
+ outputs=status_textbox,
603
+ )
604
 
605
  with gr.Row():
606
  with gr.Column(scale=2):
 
702
  btn_C3,
703
  ],
704
  ):
705
+ btn.click(
706
+ inject_scenario,
707
+ [gr.State(tag), cdss_toggle, history_df, historic_text],
708
+ ui_outputs,
709
+ )
710
 
711
  csv_outputs = [history_df, df_view, bp_plot, hr_plot, rr_plot, temp_plot, spo2_plot]
712
  csv_file.change(load_csv, [csv_file, history_df], csv_outputs)
rules.py CHANGED
@@ -5,25 +5,25 @@ def rule_based_cdss(state: PatientState) -> str:
5
  labs = state.labs
6
  alerts = []
7
  if state.patient_type == 'Mother':
8
- if v.sbp < 100 and v.hr > 110 and ('Hb' in labs and labs['Hb'] <= 9 or state.scenario.startswith('A1')):
9
  alerts.append('PPH suspected: lakukan uterotonik, massage uterus, siapkan transfusi, monitoring 5 menit.')
10
- if v.sbp >= 160 or v.dbp >= 110 or state.scenario.startswith('A2'):
11
- alerts.append('no alert')
12
- if v.temp_c >= 38.5 and v.sbp <= 100 or state.scenario.startswith('A3'):
13
  alerts.append('Sepsis maternal: kultur darah & luka, antibiotik spektrum luas dalam 1 jam, monitor MAP & urine.')
14
  if state.patient_type == 'Neonate':
15
- if labs.get('BB', 9999) < 2500 or labs.get('UsiaGestasi_mgg', 99) < 37 or state.scenario.startswith('B1'):
16
  alerts.append('Prematurity/BBLR')
17
- if v.spo2 < 90 or v.hr < 100 or state.scenario.startswith('B2'):
18
  alerts.append('Asfiksia: resusitasi neonatal sesuai NRP, ventilasi tekanan positif, evaluasi setiap 30 detik.')
19
- if v.temp_c >= 38.0 and labs.get('CRP', 0) >= 10 or state.scenario.startswith('B3'):
20
  alerts.append('Sepsis neonatal: kultur darah, antibiotik empiris NICU, monitor tanda vital ketat.')
21
  if state.patient_type == 'Gyn':
22
- if state.scenario.startswith('C1') or labs.get('UrineOutput_ml_hr', 9999) < 20:
23
  alerts.append('Komplikasi pasca bedah (curiga ureter): evaluasi segera, USG/CT urografi, konsult urologi.')
24
- if v.temp_c >= 38.0 and (state.scenario.startswith('C2') or labs.get('Luka') == 'bengkak+kemerahan'):
25
  alerts.append('Infeksi pasca-bedah: kultur luka, antibiotik, pertimbangkan debridement.')
26
- if state.scenario.startswith('C3') or labs.get('PapSmear', '').startswith('abnormal'):
27
  alerts.append('Pap smear abnormal tanpa follow-up: lakukan kolposkopi & biopsi, aktifkan notifikasi rujukan.')
28
  if not alerts:
29
  return 'Tidak ada alert prioritas tinggi. Lanjutkan pemantauan dan dokumentasi.'
 
5
  labs = state.labs
6
  alerts = []
7
  if state.patient_type == 'Mother':
8
+ if v.sbp < 100 and v.hr > 110 and ('Hb' in labs and labs['Hb'] <= 9):
9
  alerts.append('PPH suspected: lakukan uterotonik, massage uterus, siapkan transfusi, monitoring 5 menit.')
10
+ if v.sbp >= 150 or v.dbp >= 110:
11
+ alerts.append('Preeklampsia')
12
+ if v.temp_c >= 38.5 and v.sbp <= 100:
13
  alerts.append('Sepsis maternal: kultur darah & luka, antibiotik spektrum luas dalam 1 jam, monitor MAP & urine.')
14
  if state.patient_type == 'Neonate':
15
+ if labs.get('BB', 9999) < 2500 or labs.get('UsiaGestasi_mgg', 99) < 37:
16
  alerts.append('Prematurity/BBLR')
17
+ if v.spo2 < 90 or v.hr < 100:
18
  alerts.append('Asfiksia: resusitasi neonatal sesuai NRP, ventilasi tekanan positif, evaluasi setiap 30 detik.')
19
+ if v.temp_c >= 38.0 and labs.get('CRP', 0) >= 10:
20
  alerts.append('Sepsis neonatal: kultur darah, antibiotik empiris NICU, monitor tanda vital ketat.')
21
  if state.patient_type == 'Gyn':
22
+ if labs.get('UrineOutput_ml_hr', 9999) < 20:
23
  alerts.append('Komplikasi pasca bedah (curiga ureter): evaluasi segera, USG/CT urografi, konsult urologi.')
24
+ if v.temp_c >= 38.0 or labs.get('Luka') == 'bengkak+kemerahan':
25
  alerts.append('Infeksi pasca-bedah: kultur luka, antibiotik, pertimbangkan debridement.')
26
+ if labs.get('PapSmear', '').startswith('abnormal'):
27
  alerts.append('Pap smear abnormal tanpa follow-up: lakukan kolposkopi & biopsi, aktifkan notifikasi rujukan.')
28
  if not alerts:
29
  return 'Tidak ada alert prioritas tinggi. Lanjutkan pemantauan dan dokumentasi.'
rules_visualization.svg ADDED
validator.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ from models import PatientState, Vitals
4
+ from rules import rule_based_cdss
5
+ import json
6
+ import ast
7
+
8
+
9
+ def test_condition(
10
+ patient_type, sbp, dbp, hr, rr, temp_c, spo2, labs_text, condition, alert_text
11
+ ):
12
+ """
13
+ Tests a single condition against a manually defined patient state.
14
+ """
15
+ try:
16
+ labs = json.loads(labs_text)
17
+ except json.JSONDecodeError:
18
+ return "Error: Invalid JSON in Labs field."
19
+
20
+ vitals = Vitals(
21
+ sbp=int(sbp),
22
+ dbp=int(dbp),
23
+ hr=int(hr),
24
+ rr=int(rr),
25
+ temp_c=float(temp_c),
26
+ spo2=int(spo2),
27
+ )
28
+ state = PatientState(
29
+ scenario="Validation",
30
+ patient_type=patient_type,
31
+ notes="",
32
+ labs=labs,
33
+ vitals=vitals,
34
+ )
35
+
36
+ # Dynamically create a rule function for testing
37
+ rule_fnc_str = f"""
38
+ def dynamic_rule(state):
39
+ v = state.vitals
40
+ labs = state.labs
41
+ alerts = []
42
+ if {condition}:
43
+ alerts.append("{alert_text}")
44
+ if not alerts:
45
+ return "No alert triggered."
46
+ return "\n- ".join(["ALERT:"] + alerts)
47
+ """
48
+ try:
49
+ exec(rule_fnc_str, globals())
50
+ result = dynamic_rule(state)
51
+ return result
52
+ except Exception as e:
53
+ return f"Error in condition syntax: {e}"
54
+
55
+
56
+ def add_rule_to_set(patient_type, condition, alert_text):
57
+ """
58
+ Adds the new rule to the rules.py file.
59
+ """
60
+ if not condition or not alert_text:
61
+ return "Error: Condition and Alert text cannot be empty."
62
+
63
+ try:
64
+ with open("rules.py", "r") as f:
65
+ tree = ast.parse(f.read())
66
+
67
+ for node in ast.walk(tree):
68
+ if isinstance(node, ast.FunctionDef) and node.name == "rule_based_cdss":
69
+ for body_item in node.body:
70
+ if (
71
+ isinstance(body_item, ast.If)
72
+ and hasattr(body_item.test, "comparators")
73
+ and body_item.test.comparators
74
+ and isinstance(body_item.test.comparators[0], ast.Constant)
75
+ and body_item.test.comparators[0].value == patient_type
76
+ ):
77
+
78
+ new_rule_str = (
79
+ f'if {condition}:\n alerts.append("{alert_text}")'
80
+ )
81
+ new_rule_node = ast.parse(new_rule_str).body[0]
82
+ body_item.body.append(new_rule_node)
83
+ break
84
+
85
+ new_code = ast.unparse(tree)
86
+ with open("rules.py", "w") as f:
87
+ f.write(new_code)
88
+
89
+ return f"Rule added to {patient_type} ruleset and saved to rules.py."
90
+
91
+ except Exception as e:
92
+ return f"Failed to add rule: {e}"
93
+
94
+
95
+ def validator_tab():
96
+ with gr.TabItem("Rule Validator"):
97
+ gr.Markdown("## Validate and Add New Rules")
98
+ with gr.Row():
99
+ with gr.Column():
100
+ gr.Markdown("### 1. Define Patient State")
101
+ patient_type_validate = gr.Radio(
102
+ ["Mother", "Neonate", "Gyn"], label="Patient Type", value="Mother"
103
+ )
104
+ sbp_validate = gr.Number(label="SBP", value=120)
105
+ dbp_validate = gr.Number(label="DBP", value=80)
106
+ hr_validate = gr.Number(label="HR", value=80)
107
+ rr_validate = gr.Number(label="RR", value=18)
108
+ temp_c_validate = gr.Number(label="Temp (°C)", value=37.0)
109
+ spo2_validate = gr.Number(label="SpO₂ (%)", value=98)
110
+ labs_validate = gr.Textbox(
111
+ label="Labs (JSON format)", value='{"Hb": 12.0}', lines=3
112
+ )
113
+
114
+ with gr.Column():
115
+ gr.Markdown("### 2. Define and Test Rule")
116
+ condition_validate = gr.Textbox(
117
+ label="Condition (Python expression)", value="v.sbp > 140", lines=3
118
+ )
119
+ alert_validate = gr.Textbox(
120
+ label="Alert Message", value="Preeclampsia suspected", lines=3
121
+ )
122
+ test_button = gr.Button("Test Rule", variant="secondary")
123
+ validation_result = gr.Textbox(
124
+ label="Validation Result", interactive=False
125
+ )
126
+
127
+ gr.Markdown("### 3. Add Rule to Ruleset")
128
+ add_rule_button = gr.Button("Add Rule to Ruleset", variant="primary")
129
+ add_rule_status = gr.Textbox(label="Status", interactive=False)
130
+
131
+ test_button.click(
132
+ test_condition,
133
+ inputs=[
134
+ patient_type_validate,
135
+ sbp_validate,
136
+ dbp_validate,
137
+ hr_validate,
138
+ rr_validate,
139
+ temp_c_validate,
140
+ spo2_validate,
141
+ labs_validate,
142
+ condition_validate,
143
+ alert_validate,
144
+ ],
145
+ outputs=validation_result,
146
+ )
147
+
148
+ add_rule_button.click(
149
+ add_rule_to_set,
150
+ inputs=[patient_type_validate, condition_validate, alert_validate],
151
+ outputs=add_rule_status,
152
+ )
visualizer.py DELETED
@@ -1,184 +0,0 @@
1
- import gradio as gr
2
- import json
3
- import ast
4
- import pandas as pd
5
-
6
-
7
- def parse_rules():
8
- with open("rules.py", "r") as f:
9
- tree = ast.parse(f.read())
10
-
11
- rules = {"Mother": [], "Neonate": [], "Gyn": []}
12
-
13
- for node in ast.walk(tree):
14
- if isinstance(node, ast.FunctionDef) and node.name == "rule_based_cdss":
15
- for body_item in node.body:
16
- if (
17
- isinstance(body_item, ast.If)
18
- and isinstance(body_item.test, ast.Compare)
19
- and isinstance(body_item.test.left, ast.Attribute)
20
- and isinstance(body_item.test.left.value, ast.Name)
21
- and body_item.test.left.value.id == "state"
22
- and body_item.test.left.attr == "patient_type"
23
- and isinstance(body_item.test.ops[0], ast.Eq)
24
- and isinstance(body_item.test.comparators[0], ast.Constant)
25
- ):
26
-
27
- patient_type = body_item.test.comparators[0].value
28
- if patient_type in rules:
29
- for rule_node in body_item.body:
30
- if isinstance(rule_node, ast.If):
31
- conditions = ast.unparse(rule_node.test)
32
- alert = ""
33
- for item in rule_node.body:
34
- if (
35
- isinstance(item, ast.Expr)
36
- and isinstance(item.value, ast.Call)
37
- and hasattr(item.value.func, "value")
38
- and hasattr(item.value.func.value, "id")
39
- and item.value.func.value.id == "alerts"
40
- and item.value.func.attr == "append"
41
- and isinstance(item.value.args[0], ast.Constant)
42
- ):
43
- alert = item.value.args[0].value
44
- rules[patient_type].append(
45
- {"conditions": conditions, "alert": alert}
46
- )
47
- return rules
48
-
49
-
50
- def rules_to_dataframes(rules):
51
- dataframes = {}
52
- for patient_type, rules_list in rules.items():
53
- data = {"Conditions": [], "Alert": []}
54
- for rule in rules_list:
55
- data["Conditions"].append(rule["conditions"])
56
- data["Alert"].append(rule["alert"])
57
- df = pd.DataFrame(data)
58
- dataframes[patient_type] = df
59
- return dataframes
60
-
61
-
62
- def dataframes_to_rules(dfs):
63
- rules = {"Mother": [], "Neonate": [], "Gyn": []}
64
- for patient_type, df in dfs.items():
65
- if df is not None:
66
- for index, row in df.iterrows():
67
- if row["Conditions"] and row["Alert"]:
68
- rules[patient_type].append(
69
- {"conditions": row["Conditions"], "alert": row["Alert"]}
70
- )
71
- return rules
72
-
73
-
74
- def add_row(df):
75
- if df is None:
76
- df = pd.DataFrame(columns=["Conditions", "Alert"])
77
- df.loc[len(df)] = ["", ""]
78
- return df
79
-
80
-
81
- def save_dataframes(df_mother, df_neonate, df_gyn):
82
- dfs = {"Mother": df_mother, "Neonate": df_neonate, "Gyn": df_gyn}
83
- rules = dataframes_to_rules(dfs)
84
-
85
- with open("rules.py", "r") as f:
86
- tree = ast.parse(f.read())
87
-
88
- for node in ast.walk(tree):
89
- if isinstance(node, ast.FunctionDef) and node.name == "rule_based_cdss":
90
- node.body = []
91
- node.body.append(ast.parse("v = state.vitals").body[0])
92
- node.body.append(ast.parse("labs = state.labs").body[0])
93
- node.body.append(ast.parse("alerts = []").body[0])
94
-
95
- for patient_type, rule_list in rules.items():
96
- if_patient_type_body = []
97
- for rule in rule_list:
98
- conditions = (
99
- rule["conditions"].replace("\r", " ").replace("\n", " ")
100
- )
101
- if_rule_str = f"if {conditions}:\n alerts.append({json.dumps(rule['alert'])})"
102
- if_rule = ast.parse(if_rule_str).body[0]
103
- if_patient_type_body.append(if_rule)
104
-
105
- if if_patient_type_body:
106
- if_patient_type = ast.If(
107
- test=ast.Compare(
108
- left=ast.Attribute(
109
- value=ast.Name(id="state", ctx=ast.Load()),
110
- attr="patient_type",
111
- ctx=ast.Load(),
112
- ),
113
- ops=[ast.Eq()],
114
- comparators=[ast.Constant(value=patient_type)],
115
- ),
116
- body=if_patient_type_body,
117
- orelse=[],
118
- )
119
- node.body.append(if_patient_type)
120
-
121
- node.body.append(
122
- ast.parse(
123
- 'if not alerts:\n return "Tidak ada alert prioritas tinggi. Lanjutkan pemantauan dan dokumentasi."'
124
- ).body[0]
125
- )
126
- node.body.append(
127
- ast.parse(
128
- 'return "\\n- ".join(["ALERT:"] + alerts)', mode="single"
129
- ).body[0]
130
- )
131
-
132
- new_code = ast.unparse(tree)
133
- with open("rules.py", "w") as f:
134
- f.write(new_code)
135
-
136
- return "Rules saved successfully."
137
-
138
-
139
- with gr.Blocks() as demo:
140
- gr.Markdown("## CDSS Rule Editor")
141
-
142
- initial_rules = parse_rules()
143
- initial_dfs = rules_to_dataframes(initial_rules)
144
-
145
- with gr.Tabs():
146
- with gr.Tab("Mother"):
147
- df_mother = gr.DataFrame(
148
- value=initial_dfs["Mother"],
149
- headers=["Conditions", "Alert"],
150
- interactive=True,
151
- row_count=(len(initial_dfs["Mother"]) + 1, "dynamic"),
152
- )
153
- add_mother_btn = gr.Button("➕ Add Mother Rule")
154
- add_mother_btn.click(add_row, inputs=df_mother, outputs=df_mother)
155
-
156
- with gr.Tab("Neonate"):
157
- df_neonate = gr.DataFrame(
158
- value=initial_dfs["Neonate"],
159
- headers=["Conditions", "Alert"],
160
- interactive=True,
161
- row_count=(len(initial_dfs["Neonate"]) + 1, "dynamic"),
162
- )
163
- add_neonate_btn = gr.Button("➕ Add Neonate Rule")
164
- add_neonate_btn.click(add_row, inputs=df_neonate, outputs=df_neonate)
165
-
166
- with gr.Tab("Gyn"):
167
- df_gyn = gr.DataFrame(
168
- value=initial_dfs["Gyn"],
169
- headers=["Conditions", "Alert"],
170
- interactive=True,
171
- row_count=(len(initial_dfs["Gyn"]) + 1, "dynamic"),
172
- )
173
- add_gyn_btn = gr.Button("➕ Add Gyn Rule")
174
- add_gyn_btn.click(add_row, inputs=df_gyn, outputs=df_gyn)
175
-
176
- save_button = gr.Button("💾 Save Rules")
177
- status_textbox = gr.Textbox(label="Status", interactive=False)
178
-
179
- save_button.click(
180
- save_dataframes, inputs=[df_mother, df_neonate, df_gyn], outputs=status_textbox
181
- )
182
-
183
- if __name__ == "__main__":
184
- demo.launch()