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

save new rule

Browse files
Files changed (5) hide show
  1. .gitignore +1 -0
  2. cdss.py +178 -14
  3. requirements.txt +3 -0
  4. rules.py +23 -77
  5. visualizer.py +184 -0
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ __pycache__
cdss.py CHANGED
@@ -380,6 +380,107 @@ def countdown_tick(last_tick_ts: float):
380
  return f"Next update in {max(0, 30 - int(time.time() - last_tick_ts))}s"
381
 
382
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  # --- Build UI ---
384
  with gr.Blocks(
385
  css=".gradio-container { max-width: 1200px !important; margin: auto !important; }"
@@ -389,21 +490,84 @@ with gr.Blocks(
389
  history_df = gr.State(pd.DataFrame())
390
  historic_text = gr.State("")
391
  last_tick_ts = gr.State(time.time())
392
-
393
  interpretation = gr.Textbox(label="CDSS Interpretation", lines=2, interactive=False)
394
- with gr.Accordion("History, Trends, and Data Loading", open=True):
395
- with gr.Row():
396
- with gr.Tabs():
397
- with gr.Tab("Blood Pressure"):
398
- bp_plot = gr.Plot()
399
- with gr.Tab("Heart Rate"):
400
- hr_plot = gr.Plot()
401
- with gr.Tab("Respiration"):
402
- rr_plot = gr.Plot()
403
- with gr.Tab("Temperature"):
404
- temp_plot = gr.Plot()
405
- with gr.Tab("SpO₂"):
406
- spo2_plot = gr.Plot()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
  with gr.Row():
409
  with gr.Column(scale=2):
 
380
  return f"Next update in {max(0, 30 - int(time.time() - last_tick_ts))}s"
381
 
382
 
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
+
392
+ rules = {"Mother": [], "Neonate": [], "Gyn": []}
393
+
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(
486
  css=".gradio-container { max-width: 1200px !important; margin: auto !important; }"
 
490
  history_df = gr.State(pd.DataFrame())
491
  historic_text = gr.State("")
492
  last_tick_ts = gr.State(time.time())
 
493
  interpretation = gr.Textbox(label="CDSS Interpretation", lines=2, interactive=False)
494
+
495
+ with gr.Tabs():
496
+ with gr.TabItem("CDSS Simulator"):
497
+ with gr.Accordion("History, Trends, and Data Loading", open=True):
498
+ with gr.Row():
499
+ with gr.Tabs():
500
+ with gr.Tab("Blood Pressure"):
501
+ bp_plot = gr.Plot()
502
+ with gr.Tab("Heart Rate"):
503
+ hr_plot = gr.Plot()
504
+ with gr.Tab("Respiration"):
505
+ rr_plot = gr.Plot()
506
+ with gr.Tab("Temperature"):
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):
requirements.txt CHANGED
@@ -1,4 +1,7 @@
 
 
1
  gradio
 
2
  google-generativeai
3
  pandas
4
  plotly
 
1
+ fastapi
2
+ uvicorn
3
  gradio
4
+ graphviz
5
  google-generativeai
6
  pandas
7
  plotly
rules.py CHANGED
@@ -1,84 +1,30 @@
1
  from models import PatientState
2
 
3
-
4
  def rule_based_cdss(state: PatientState) -> str:
5
  v = state.vitals
6
  labs = state.labs
7
  alerts = []
8
-
9
- if state.patient_type == "Mother":
10
- # PPH-like
11
- if (
12
- v.sbp < 100
13
- and v.hr > 110
14
- and ("Hb" in labs and labs["Hb"] <= 9 or state.scenario.startswith("A1"))
15
- ):
16
- alerts.append(
17
- "PPH suspected: lakukan uterotonik, massage uterus, siapkan transfusi, monitoring 5 menit."
18
- )
19
- # Preeclampsia
20
- if v.sbp >= 160 or v.dbp >= 110 or state.scenario.startswith("A2"):
21
- if labs.get("Proteinuria") in (
22
- "2+",
23
- "3+",
24
- "4+",
25
- ) or state.scenario.startswith("A2"):
26
- alerts.append(
27
- "Preeklampsia berat: MgSO4 loading + maintenance, kontrol TD, pertimbangkan terminasi ≥34 minggu."
28
- )
29
- # Maternal sepsis
30
- if v.temp_c >= 38.5 and v.sbp <= 100 or state.scenario.startswith("A3"):
31
- alerts.append(
32
- "Sepsis maternal: kultur darah & luka, antibiotik spektrum luas dalam 1 jam, monitor MAP & urine."
33
- )
34
-
35
- if state.patient_type == "Neonate":
36
- # Prematurity/BBLR
37
- if (
38
- labs.get("BB", 9999) < 2500
39
- or labs.get("UsiaGestasi_mgg", 99) < 37
40
- or state.scenario.startswith("B1")
41
- ):
42
- if v.temp_c < 36.5 or v.spo2 < 92:
43
- alerts.append(
44
- "Prematur/BBLR: inkubator/skin-to-skin, monitor SpO2 & suhu, pertimbangkan CPAP jika SpO2<92%."
45
- )
46
- # Asphyxia
47
- if v.spo2 < 90 or v.hr < 100 or state.scenario.startswith("B2"):
48
- alerts.append(
49
- "Asfiksia: resusitasi neonatal sesuai NRP, ventilasi tekanan positif, evaluasi setiap 30 detik."
50
- )
51
- # Neonatal sepsis
52
- if (
53
- v.temp_c >= 38.0
54
- and labs.get("CRP", 0) >= 10
55
- or state.scenario.startswith("B3")
56
- ):
57
- alerts.append(
58
- "Sepsis neonatal: kultur darah, antibiotik empiris NICU, monitor tanda vital ketat."
59
- )
60
-
61
- if state.patient_type == "Gyn":
62
- # Post-op complication (ureter)
63
- if state.scenario.startswith("C1") or labs.get("UrineOutput_ml_hr", 9999) < 20:
64
- alerts.append(
65
- "Komplikasi pasca bedah (curiga ureter): evaluasi segera, USG/CT urografi, konsult urologi."
66
- )
67
- # Post-op infection
68
- if v.temp_c >= 38.0 and (
69
- state.scenario.startswith("C2") or labs.get("Luka") == "bengkak+kemerahan"
70
- ):
71
- alerts.append(
72
- "Infeksi pasca-bedah: kultur luka, antibiotik, pertimbangkan debridement."
73
- )
74
- # Delayed cancer follow-up
75
- if state.scenario.startswith("C3") or labs.get("PapSmear", "").startswith(
76
- "abnormal"
77
- ):
78
- alerts.append(
79
- "Pap smear abnormal tanpa follow-up: lakukan kolposkopi & biopsi, aktifkan notifikasi rujukan."
80
- )
81
-
82
  if not alerts:
83
- return "Tidak ada alert prioritas tinggi. Lanjutkan pemantauan dan dokumentasi."
84
- return "\n- ".join(["ALERT:"] + alerts)
 
1
  from models import PatientState
2
 
 
3
  def rule_based_cdss(state: PatientState) -> str:
4
  v = state.vitals
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.'
30
+ return '\n- '.join(['ALERT:'] + alerts)
visualizer.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()