ripple-down-rules 0.6.28__py3-none-any.whl → 0.6.30__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ripple_down_rules/rdr.py CHANGED
@@ -13,7 +13,6 @@ from . import logger
13
13
 
14
14
  try:
15
15
  from matplotlib import pyplot as plt
16
-
17
16
  Figure = plt.Figure
18
17
  except ImportError as e:
19
18
  logger.debug(f"{e}: matplotlib is not installed")
@@ -90,23 +89,13 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
90
89
  :param save_dir: The directory to save the classifier to.
91
90
  """
92
91
  self.model_name: Optional[str] = model_name
93
- self.save_dir = save_dir
94
- self.start_rule = start_rule
92
+ self.save_dir: Optional[str] = save_dir
93
+ self.start_rule: Optional[Rule] = start_rule
95
94
  self.fig: Optional[Figure] = None
96
95
  self.viewer: Optional[RDRCaseViewer] = RDRCaseViewer.instances[0]\
97
96
  if RDRCaseViewer and any(RDRCaseViewer.instances) else None
98
97
  self.input_node: Optional[Rule] = None
99
98
 
100
- @property
101
- def viewer(self):
102
- return self._viewer
103
-
104
- @viewer.setter
105
- def viewer(self, viewer):
106
- self._viewer = viewer
107
- if viewer:
108
- viewer.set_save_function(self.save)
109
-
110
99
  def render_evaluated_rule_tree(self, filename: str, show_full_tree: bool = False) -> None:
111
100
  if show_full_tree:
112
101
  start_rule = self.start_rule if self.input_node is None else self.input_node
@@ -117,6 +106,26 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
117
106
  render_tree(evaluated_rules[0], use_dot_exporter=True, filename=filename,
118
107
  only_nodes=evaluated_rules)
119
108
 
109
+ def get_contributing_rules(self) -> Optional[List[Rule]]:
110
+ """
111
+ Get the contributing rules of the classifier.
112
+
113
+ :return: The contributing rules.
114
+ """
115
+ if self.start_rule is None:
116
+ return None
117
+ return [r for r in self.get_fired_rule_tree() if r.contributed]
118
+
119
+ def get_fired_rule_tree(self) -> Optional[List[Rule]]:
120
+ """
121
+ Get the fired rule tree of the classifier.
122
+
123
+ :return: The fired rule tree.
124
+ """
125
+ if self.start_rule is None:
126
+ return None
127
+ return [r for r in self.get_evaluated_rule_tree() if r.fired]
128
+
120
129
  def get_evaluated_rule_tree(self) -> Optional[List[Rule]]:
121
130
  """
122
131
  Get the evaluated rule tree of the classifier.
@@ -196,16 +205,6 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
196
205
  """
197
206
  pass
198
207
 
199
- def set_viewer(self, viewer: RDRCaseViewer):
200
- """
201
- Set the viewer for the classifier.
202
-
203
- :param viewer: The viewer to set.
204
- """
205
- self.viewer = viewer
206
- if self.viewer is not None:
207
- self.viewer.set_save_function(self.save)
208
-
209
208
  def fit(self, case_queries: List[CaseQuery],
210
209
  expert: Optional[Expert] = None,
211
210
  n_iter: int = None,
@@ -230,7 +229,8 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
230
229
  num_rules: int = 0
231
230
  while not stop_iterating:
232
231
  for case_query in case_queries:
233
- pred_cat = self.fit_case(case_query, expert=expert, **kwargs_for_fit_case)
232
+ pred_cat = self.fit_case(case_query, expert=expert, clear_expert_answers=False,
233
+ **kwargs_for_fit_case)
234
234
  if case_query.target is None:
235
235
  continue
236
236
  target = {case_query.attribute_name: case_query.target(case_query.case)}
@@ -272,8 +272,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
272
272
  """
273
273
  if self.start_rule is not None:
274
274
  for rule in [self.start_rule] + list(self.start_rule.descendants):
275
- rule.evaluated = False
276
- rule.fired = False
275
+ rule.reset()
277
276
  if self.start_rule is not None and self.start_rule.parent is None:
278
277
  if self.input_node is None:
279
278
  self.input_node = type(self.start_rule)(parent=None, uid='0')
@@ -308,6 +307,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
308
307
  update_existing_rules: bool = True,
309
308
  scenario: Optional[Callable] = None,
310
309
  ask_now: Callable = lambda _: True,
310
+ clear_expert_answers: bool = True,
311
311
  **kwargs) \
312
312
  -> Union[CallableExpression, Dict[str, CallableExpression]]:
313
313
  """
@@ -319,7 +319,8 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
319
319
  :param update_existing_rules: Whether to update the existing same conclusion type rules that already gave
320
320
  some conclusions with the type required by the case query.
321
321
  :param scenario: The scenario at which the case was created, this is used to recreate the case if needed.
322
- :ask_now: Whether to ask the expert for refinements or alternatives.
322
+ :param ask_now: Whether to ask the expert for refinements or alternatives.
323
+ :param clear_expert_answers: Whether to clear expert answers after saving the new rule.
323
324
  :return: The category that the case belongs to.
324
325
  """
325
326
  if case_query is None:
@@ -329,6 +330,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
329
330
  self.case_type = case_query.case_type if self.case_type is None else self.case_type
330
331
  self.case_name = case_query.case_name if self.case_name is None else self.case_name
331
332
  case_query.scenario = scenario if case_query.scenario is None else case_query.scenario
333
+ case_query.rdr = self
332
334
 
333
335
  expert = expert or Human(answers_save_path=self.save_dir + '/expert_answers'
334
336
  if self.save_dir else None)
@@ -348,7 +350,8 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
348
350
 
349
351
  if self.save_dir is not None:
350
352
  self.save()
351
- expert.clear_answers()
353
+ if clear_expert_answers:
354
+ expert.clear_answers()
352
355
 
353
356
  return fit_case_result
354
357
 
@@ -767,6 +770,11 @@ class SingleClassRDR(RDRWithCodeWriter):
767
770
  """
768
771
  pred = self.evaluate(case)
769
772
  conclusion = pred.conclusion(case) if pred is not None and pred.fired else self.default_conclusion
773
+ if pred is not None and pred.fired:
774
+ pred.contributed = True
775
+ pred.last_conclusion = conclusion
776
+ if case_query is not None:
777
+ pred.contributed_to_case_query = True
770
778
  if pred is not None and pred.fired and case_query is not None:
771
779
  if pred.corner_case_metadata is None and conclusion is not None \
772
780
  and type(conclusion) in case_query.core_attribute_type:
@@ -884,6 +892,13 @@ class MultiClassRDR(RDRWithCodeWriter):
884
892
  and any(
885
893
  ct in case_query.core_attribute_type for ct in map(type, make_list(rule_conclusion))):
886
894
  evaluated_rule.corner_case_metadata = CaseFactoryMetaData.from_case_query(case_query)
895
+ if rule_conclusion is not None and any(make_list(rule_conclusion)):
896
+ evaluated_rule.contributed = True
897
+ evaluated_rule.last_conclusion = rule_conclusion
898
+ if case_query is not None:
899
+ rule_conclusion_types = set(map(type, make_list(rule_conclusion)))
900
+ if any(rule_conclusion_types.intersection(set(case_query.core_attribute_type))):
901
+ evaluated_rule.contributed_to_case_query = True
887
902
  self.add_conclusion(rule_conclusion)
888
903
  evaluated_rule = next_rule
889
904
  return make_set(self.conclusions)
@@ -978,7 +993,7 @@ class MultiClassRDR(RDRWithCodeWriter):
978
993
  """
979
994
  if not self.start_rule:
980
995
  conditions = expert.ask_for_conditions(case_query)
981
- self.start_rule = MultiClassTopRule.from_case_query(case_query)
996
+ self.start_rule: MultiClassTopRule = MultiClassTopRule.from_case_query(case_query)
982
997
 
983
998
  @property
984
999
  def last_top_rule(self) -> Optional[MultiClassTopRule]:
@@ -63,6 +63,9 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
63
63
  self.uid: str = uid if uid else str(uuid4().int)
64
64
  self.evaluated: bool = False
65
65
  self._user_defined_name: Optional[str] = None
66
+ self.last_conclusion: Optional[Any] = None
67
+ self.contributed: bool = False
68
+ self.contributed_to_case_query: bool = False
66
69
 
67
70
  def get_an_updated_case_copy(self, case: Case) -> Case:
68
71
  """
@@ -72,11 +75,22 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
72
75
  return get_an_updated_case_copy(case, self.conclusion, self.conclusion_name, self.conclusion.conclusion_type,
73
76
  self.mutually_exclusive)
74
77
 
78
+ def reset(self):
79
+ self.evaluated = False
80
+ self.fired = False
81
+ self.contributed = False
82
+ self.contributed_to_case_query = False
83
+ self.last_conclusion = None
84
+
75
85
  @property
76
86
  def color(self) -> str:
77
87
  if self.evaluated:
78
- if self.fired:
88
+ if self.contributed_to_case_query:
79
89
  return "green"
90
+ elif self.contributed:
91
+ return "yellow"
92
+ elif self.fired:
93
+ return "orange"
80
94
  else:
81
95
  return "red"
82
96
  else:
@@ -1,15 +1,19 @@
1
- from __future__ import annotations
1
+ from __future__ import annotations
2
2
 
3
3
  import inspect
4
4
  import logging
5
+ import os.path
5
6
  from types import MethodType
6
7
 
8
+ from graphviz import Digraph, Source
9
+
7
10
  try:
8
11
  from PyQt6.QtCore import Qt
9
12
  from PyQt6.QtGui import QPixmap, QPainter, QPalette
10
13
  from PyQt6.QtWidgets import (
11
14
  QWidget, QVBoxLayout, QLabel, QScrollArea,
12
- QSizePolicy, QToolButton, QHBoxLayout, QPushButton, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem
15
+ QSizePolicy, QToolButton, QHBoxLayout, QPushButton, QMainWindow, QGraphicsView, QGraphicsScene,
16
+ QGraphicsPixmapItem
13
17
  )
14
18
  from qtconsole.inprocess import QtInProcessKernelManager
15
19
  from qtconsole.rich_jupyter_widget import RichJupyterWidget
@@ -120,7 +124,7 @@ class BackgroundWidget(QWidget):
120
124
 
121
125
 
122
126
  class CollapsibleBox(QWidget):
123
- def __init__(self, title="", parent=None, viewer: Optional[RDRCaseViewer]=None, chain_name: Optional[str] = None,
127
+ def __init__(self, title="", parent=None, viewer: Optional[RDRCaseViewer] = None, chain_name: Optional[str] = None,
124
128
  main_obj: Optional[Dict[str, Any]] = None):
125
129
  super().__init__(parent)
126
130
 
@@ -259,12 +263,14 @@ def color_name_to_html(color_name):
259
263
  'g': 'green',
260
264
  'b': 'blue',
261
265
  'o': 'orange',
266
+ 'm': 'magenta',
262
267
  }
263
268
  color_map = {
264
269
  'red': '#d6336c',
265
270
  'green': '#2eb872',
266
271
  'blue': '#007acc',
267
272
  'orange': '#FFA07A',
273
+ 'magenta': '#a22bcf',
268
274
  }
269
275
  if len(color_name) == 1:
270
276
  color_name = single_char_to_name.get(color_name, color_name)
@@ -285,16 +291,10 @@ class RDRCaseViewer(QMainWindow):
285
291
  instances: List[RDRCaseViewer] = []
286
292
  exit_status: ExitStatus = ExitStatus.CLOSE
287
293
 
288
- def __init__(self, parent=None,
289
- save_dir: Optional[str] = None,
290
- save_model_name: Optional[str] = None):
294
+ def __init__(self, parent=None):
291
295
  super().__init__(parent)
292
- self.exit_status = ExitStatus.CLOSE
293
296
  self.instances.clear()
294
297
  self.instances.append(self)
295
- self.save_dir = save_dir
296
- self.save_model_name = save_model_name
297
-
298
298
  self.setWindowTitle("RDR Case Viewer")
299
299
 
300
300
  self.setBaseSize(1600, 600) # or your preferred initial size
@@ -333,35 +333,28 @@ class RDRCaseViewer(QMainWindow):
333
333
  main_layout.addWidget(middle_widget, stretch=2)
334
334
  main_layout.addWidget(self.obj_diagram_viewer, stretch=2)
335
335
 
336
- def set_save_function(self, save_function: Callable[[str, str], None]) -> None:
337
- """
338
- Set the function to save the file.
339
-
340
- :param save_function: The function to save the file.
341
- """
342
- self.save_function = save_function
343
- self.save_btn.clicked.connect(lambda: self.save_function(self.save_dir, self.save_model_name))
344
-
345
336
  def print(self, msg):
346
337
  """
347
338
  Print a message to the console.
348
339
  """
349
340
  self.ipython_console._append_plain_text(msg + '\n', True)
350
341
 
351
- def update_for_case_query(self, case_query: CaseQuery, title_txt: Optional[str] = None,
352
- prompt_for: Optional[PromptFor] = None, code_to_modify: Optional[str] = None):
342
+ def update_for_case_query(self, case_query: CaseQuery, prompt_str: Optional[str] = None,
343
+ prompt_for: Optional[PromptFor] = None, code_to_modify: Optional[str] = None,
344
+ title: Optional[str] = None):
353
345
  self.case_query = case_query
354
346
  self.prompt_for = prompt_for
355
347
  self.code_to_modify = code_to_modify
356
- title_text = title_txt or ""
348
+ title_text = title or ""
357
349
  case_attr_type = ', '.join([t.__name__ for t in case_query.core_attribute_type])
358
350
  case_attr_type = style(f"{case_attr_type}", 'g', 28, 'bold')
359
351
  case_name = style(f"{case_query.name}", 'b', 28, 'bold')
360
352
  title_text = style(f"{title_text} {case_name} of type {case_attr_type}", 'o', 28, 'bold')
361
- self.update_for_object(case_query.case, case_query.case_name, case_query.scope, title_text)
353
+ self.update_for_object(case_query.case, case_query.case_name, scope=case_query.scope, title_text=title_text,
354
+ header=prompt_str)
362
355
 
363
356
  def update_for_object(self, obj: Any, name: str, scope: Optional[dict] = None,
364
- title_text: Optional[str] = None):
357
+ title_text: Optional[str] = None, header: Optional[str] = None):
365
358
  self.update_main_obj(obj, name)
366
359
  title_text = title_text or style(f"{name}", 'o', 28, 'bold')
367
360
  scope = scope or {}
@@ -370,6 +363,9 @@ class RDRCaseViewer(QMainWindow):
370
363
  self.update_attribute_layout(obj, name)
371
364
  self.title_label.setText(title_text)
372
365
  self.ipython_console.update_namespace(scope)
366
+ if header is not None and len(header) > 0:
367
+ self.ipython_console.print(header)
368
+ self.exit_status = ExitStatus.CLOSE
373
369
 
374
370
  def update_main_obj(self, obj, name):
375
371
  self.main_obj = {name: obj}
@@ -441,8 +437,6 @@ class RDRCaseViewer(QMainWindow):
441
437
  if isinstance(item.widget(), CollapsibleBox):
442
438
  self.expand_collapse_all(item.widget(), expand=True, curr_depth=curr_depth + 1, max_depth=max_depth)
443
439
 
444
-
445
-
446
440
  def create_buttons_widget(self):
447
441
  button_widget = QWidget()
448
442
  button_widget_layout = QVBoxLayout(button_widget)
@@ -466,13 +460,19 @@ class RDRCaseViewer(QMainWindow):
466
460
  load_btn.clicked.connect(self._load)
467
461
  load_btn.setStyleSheet(f"background-color: {color_name_to_html('b')}; color: white;") # Blue button
468
462
 
469
- self.save_btn = QPushButton("Save")
470
- self.save_btn.setStyleSheet(f"background-color: {color_name_to_html('b')}; color: white;") # Blue button
463
+ current_value_btn = QPushButton("Current Value")
464
+ current_value_btn.clicked.connect(self._show_current_value)
465
+ current_value_btn.setStyleSheet(f"background-color: {color_name_to_html('m')}; color: white;")
466
+
467
+ rule_tree_btn = QPushButton("Rule Tree")
468
+ rule_tree_btn.clicked.connect(self._show_rule_tree) # Placeholder for rule tree functionality
469
+ rule_tree_btn.setStyleSheet(f"background-color: {color_name_to_html('r')}; color: white;")
471
470
 
472
471
  row_1_button_widget_layout.addWidget(edit_btn)
473
472
  row_1_button_widget_layout.addWidget(load_btn)
474
- row_1_button_widget_layout.addWidget(accept_btn)
475
- row_2_button_widget_layout.addWidget(self.save_btn)
473
+ row_1_button_widget_layout.addWidget(current_value_btn)
474
+ row_2_button_widget_layout.addWidget(rule_tree_btn)
475
+ row_2_button_widget_layout.addWidget(accept_btn)
476
476
  return button_widget
477
477
 
478
478
  def _accept(self):
@@ -481,16 +481,15 @@ class RDRCaseViewer(QMainWindow):
481
481
  self.close()
482
482
 
483
483
  def _edit(self):
484
- self.template_file_creator = TemplateFileCreator(self.case_query, self.prompt_for, self.code_to_modify,
485
- self.print)
484
+ self.template_file_creator = self.create_template_file_creator()
486
485
  self.template_file_creator.edit()
487
486
 
488
487
  def _load(self):
489
488
  if not self.template_file_creator:
490
489
  return
491
490
  self.code_lines, updates = self.template_file_creator.load(self.template_file_creator.temp_file_path,
492
- self.template_file_creator.func_name,
493
- self.template_file_creator.print_func)
491
+ self.template_file_creator.func_name,
492
+ self.template_file_creator.print_func)
494
493
  self.ipython_console.kernel.shell.user_ns.update(updates)
495
494
  if self.code_lines is not None:
496
495
  self.user_input = encapsulate_code_lines_into_a_function(
@@ -498,7 +497,22 @@ class RDRCaseViewer(QMainWindow):
498
497
  self.template_file_creator.function_signature,
499
498
  self.template_file_creator.func_doc, self.case_query)
500
499
  self.case_query.scope.update(updates)
501
- self.template_file_creator = None
500
+
501
+ def _show_current_value(self):
502
+ self.ipython_console.print(self.case_query.current_value_str)
503
+
504
+ def _show_rule_tree(self):
505
+ if self.case_query is None:
506
+ self.ipython_console.print("No case query provided.")
507
+ return
508
+ if not self.case_query.rdr:
509
+ self.ipython_console.print("No rule tree available for this case query.")
510
+ return
511
+ self.case_query.render_rule_tree(view=True)
512
+
513
+ def create_template_file_creator(self) -> TemplateFileCreator:
514
+ return TemplateFileCreator(self.case_query, self.prompt_for, self.code_to_modify,
515
+ self.print)
502
516
 
503
517
  def update_attribute_layout(self, obj, name: str):
504
518
  # Clear the existing layout
@@ -547,7 +561,8 @@ class RDRCaseViewer(QMainWindow):
547
561
  attr = f"{attr}"
548
562
  try:
549
563
  if is_iterable(value) or hasattr(value, "__dict__") and not inspect.isfunction(value):
550
- self.add_collapsible(attr, value, layout, current_depth + 1, max_depth, chain_name=f"{chain_name}.{attr}")
564
+ self.add_collapsible(attr, value, layout, current_depth + 1, max_depth,
565
+ chain_name=f"{chain_name}.{attr}")
551
566
  else:
552
567
  self.add_non_collapsible(attr, value, layout)
553
568
  except Exception as e:
@@ -559,7 +574,7 @@ class RDRCaseViewer(QMainWindow):
559
574
  type_name = type(value) if not isinstance(value, type) else value
560
575
  collapsible = CollapsibleBox(
561
576
  f'<b><span style="color:#FFA07A;">{attr}</span></b> {python_colored_repr(type_name)}', viewer=self,
562
- chain_name=chain_name, main_obj=self.main_obj)
577
+ chain_name=chain_name, main_obj=self.main_obj)
563
578
  self.add_attributes(value, attr, collapsible.content_layout, current_depth, max_depth, chain_name=chain_name)
564
579
  layout.addWidget(collapsible)
565
580
 
@@ -587,7 +602,6 @@ class IPythonConsole(RichJupyterWidget):
587
602
 
588
603
  # Monkey patch its run_cell method
589
604
  def custom_run_cell(this, raw_cell, **kwargs):
590
- print(raw_cell)
591
605
  if contains_return_statement(raw_cell) and 'def ' not in raw_cell:
592
606
  if self.parent.template_file_creator and self.parent.template_file_creator.func_name in raw_cell:
593
607
  self.command_log = self.parent.code_lines
@@ -601,7 +615,6 @@ class IPythonConsole(RichJupyterWidget):
601
615
 
602
616
  original_run_cell = self.kernel.shell.run_cell
603
617
  self.kernel.shell.run_cell = MethodType(custom_run_cell, self.kernel.shell)
604
-
605
618
  self.kernel_client = self.kernel_manager.client()
606
619
  self.kernel_client.start_channels()
607
620
 
@@ -630,7 +643,7 @@ class IPythonConsole(RichJupyterWidget):
630
643
  .in-prompt { font-weight: bold; color: %(in_prompt_color)s }
631
644
  .out-prompt-number { font-weight: bold; color: %(out_prompt_number_color)s }
632
645
  .out-prompt { font-weight: bold; color: %(out_prompt_color)s }
633
- '''%dict(
646
+ ''' % dict(
634
647
  bgcolor='#0b0d0b', fgcolor='#47d9cc', select="#555",
635
648
  in_prompt_number_color='lime', in_prompt_color='lime',
636
649
  out_prompt_number_color='red', out_prompt_color='red'
@@ -648,6 +661,12 @@ class IPythonConsole(RichJupyterWidget):
648
661
  """
649
662
  self.kernel.shell.user_ns.update(namespace)
650
663
 
664
+ def print(self, msg):
665
+ """
666
+ Custom print function to append messages to the command log.
667
+ """
668
+ self.execute(f"print(\"\\n\\n{msg}\")", hidden=True)
669
+
651
670
  def execute(self, source=None, hidden=False, interactive=False):
652
671
  # Log the command before execution
653
672
  source = source if source is not None else self.input_buffer
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import os
2
3
  from typing import Optional, List
3
4
 
4
5
  from IPython.core.magic import magics_class, Magics, line_magic
@@ -6,9 +7,9 @@ from IPython.terminal.embed import InteractiveShellEmbed
6
7
  from colorama import Fore, Style
7
8
  from traitlets.config import Config
8
9
 
10
+ from .template_file_creator import TemplateFileCreator
9
11
  from ..datastructures.dataclasses import CaseQuery
10
12
  from ..datastructures.enums import PromptFor
11
- from .template_file_creator import TemplateFileCreator
12
13
  from ..utils import contains_return_statement, extract_dependencies, encapsulate_code_lines_into_a_function
13
14
 
14
15
 
@@ -20,6 +21,7 @@ class MyMagics(Magics):
20
21
  prompt_for: Optional[PromptFor] = None,
21
22
  case_query: Optional[CaseQuery] = None):
22
23
  super().__init__(shell)
24
+ self.case_query: Optional[CaseQuery] = case_query
23
25
  self.rule_editor = TemplateFileCreator(case_query, prompt_for=prompt_for, code_to_modify=code_to_modify)
24
26
  self.all_code_lines: Optional[List[str]] = None
25
27
 
@@ -34,6 +36,29 @@ class MyMagics(Magics):
34
36
  self.rule_editor.print_func)
35
37
  self.shell.user_ns.update(updates)
36
38
 
39
+ @line_magic
40
+ def current_value(self, line):
41
+ """
42
+ Display the current value of the attribute of the case.
43
+ """
44
+ if self.case_query is None:
45
+ print(f"{Fore.RED}No case query provided.{Style.RESET_ALL}")
46
+ return
47
+ print(self.case_query.current_value_str)
48
+
49
+ @line_magic
50
+ def show_rule_tree(self, line):
51
+ """
52
+ Display the rule tree for the current case query.
53
+ """
54
+ if self.case_query is None:
55
+ print(f"{Fore.RED}No case query provided.{Style.RESET_ALL}")
56
+ return
57
+ if self.case_query.rdr is None:
58
+ print(f"{Fore.RED}No RDR available for the current case query.{Style.RESET_ALL}")
59
+ return
60
+ self.case_query.render_rule_tree(view=True)
61
+
37
62
  @line_magic
38
63
  def help(self, line):
39
64
  """
@@ -42,12 +67,16 @@ class MyMagics(Magics):
42
67
  help_text = f"""
43
68
  Directly write python code in the shell, and then `{Fore.GREEN}return {Fore.RESET}output`. Or use
44
69
  the magic commands to write the code in a temporary file and edit it in PyCharm:
45
- {Fore.MAGENTA}Usage: %edit{Style.RESET_ALL}
70
+ {Fore.MAGENTA}%edit{Style.RESET_ALL}
46
71
  Opens a temporary file in PyCharm for editing a function (conclusion or conditions for case)
47
72
  that will be executed on the case object.
48
- {Fore.MAGENTA}Usage: %load{Style.RESET_ALL}
73
+ {Fore.MAGENTA}%load{Style.RESET_ALL}
49
74
  Loads the function defined in the temporary file into the user namespace, that can then be used inside the
50
75
  Ipython shell. You can then do `{Fore.GREEN}return {Fore.RESET}function_name(case)`.
76
+ {Fore.MAGENTA}%current_value{Style.RESET_ALL}
77
+ Shows the current value of the case attribute on which the rule are being fit.
78
+ {Fore.MAGENTA}%show_rule_tree{Style.RESET_ALL}
79
+ Displays the rule tree for the current case query.
51
80
  """
52
81
  print(help_text)
53
82
 
@@ -144,7 +173,7 @@ class IPythonShell:
144
173
  self.user_input = None
145
174
  else:
146
175
  self.user_input = encapsulate_code_lines_into_a_function(self.all_code_lines,
147
- function_name=self.shell.my_magics.rule_editor.func_name,
148
- function_signature=self.shell.my_magics.rule_editor.function_signature,
149
- func_doc=self.shell.my_magics.rule_editor.func_doc,
150
- case_query=self.case_query)
176
+ function_name=self.shell.my_magics.rule_editor.func_name,
177
+ function_signature=self.shell.my_magics.rule_editor.function_signature,
178
+ func_doc=self.shell.my_magics.rule_editor.func_doc,
179
+ case_query=self.case_query)