PyDecisionGraph 0.1.0__py3-none-any.whl → 0.1.2__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.

Potentially problematic release.


This version of PyDecisionGraph might be problematic. Click here for more details.

@@ -0,0 +1,16 @@
1
+ __version__ = "0.1.2"
2
+
3
+ import logging
4
+ import sys
5
+
6
+ LOGGER = logging.getLogger("DecisionGraph")
7
+ LOGGER.setLevel(logging.INFO)
8
+
9
+ if not LOGGER.hasHandlers():
10
+ ch = logging.StreamHandler(sys.stdout)
11
+ ch.setLevel(logging.INFO) # Set handler level
12
+ formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
13
+ ch.setFormatter(formatter)
14
+ LOGGER.addHandler(ch)
15
+
16
+
@@ -0,0 +1,44 @@
1
+ import logging
2
+
3
+ from .. import LOGGER
4
+
5
+ LOGGER = LOGGER.getChild("DecisionTree")
6
+
7
+ __all__ = [
8
+ 'LOGGER', 'set_logger', 'activate_expression_model', 'activate_node_model',
9
+ 'NodeError', 'TooManyChildren', 'TooFewChildren', 'NodeNotFountError', 'NodeValueError', 'EdgeValueError', 'ResolutionError', 'ExpressFalse', 'ContextsNotFound',
10
+ 'LGM', 'LogicGroup', 'SkipContextsBlock', 'LogicExpression', 'ExpressionCollection', 'LogicNode', 'ActionNode', 'ELSE_CONDITION',
11
+ 'NoAction', 'LongAction', 'ShortAction', 'RootLogicNode', 'ContextLogicExpression', 'AttrExpression', 'MathExpression', 'ComparisonExpression', 'LogicalExpression',
12
+ 'LogicMapping', 'LogicGenerator'
13
+ ]
14
+
15
+ from .exc import *
16
+ from .abc import *
17
+ from .node import *
18
+ from .collection import *
19
+
20
+
21
+ def set_logger(logger: logging.Logger):
22
+ global LOGGER
23
+ LOGGER = logger
24
+
25
+ abc.LOGGER = logger.getChild('abc')
26
+
27
+
28
+ def activate_expression_model():
29
+ import importlib
30
+ importlib.import_module('decision_graph.decision_tree.expression')
31
+ importlib.reload(collection)
32
+ collection.LogicMapping.AttrExpression = AttrExpression
33
+ collection.LogicGenerator.AttrExpression = AttrExpression
34
+ # importlib.reload(logic_group)
35
+
36
+
37
+ def activate_node_model():
38
+ import importlib
39
+
40
+ importlib.import_module('decision_graph.decision_tree.node')
41
+ importlib.reload(collection)
42
+ collection.LogicMapping.AttrExpression = AttrExpression
43
+ collection.LogicGenerator.AttrExpression = AttrExpression
44
+ # importlib.reload(logic_group)
@@ -11,6 +11,8 @@ from typing import Any, Self, final
11
11
  from . import LOGGER
12
12
  from .exc import TooFewChildren, TooManyChildren, EdgeValueError, NodeValueError, NodeNotFountError
13
13
 
14
+ LOGGER = LOGGER.getChild('abc')
15
+
14
16
  __all__ = ['LGM', 'LogicGroup', 'SkipContextsBlock', 'LogicExpression', 'ExpressionCollection', 'LogicNode', 'ActionNode', 'ELSE_CONDITION']
15
17
 
16
18
 
@@ -46,9 +48,11 @@ class LogicGroupManager(metaclass=Singleton):
46
48
  # Cursor to track the currently active LogicGroups
47
49
  self._active_groups: list[LogicGroup] = []
48
50
  self._active_nodes: list[LogicNode] = []
49
- self._exit_nodes: list[ActionNode] = [] # action nodes, usually NoAction() nodes, marked as an early-exit of a logic group
50
- self._pending_connection_nodes: list[ActionNode] = [] # for those exit-nodes, they will be activated when the corresponding logic group is finalized.
51
+ self._breakpoint_nodes: list[ActionNode] = [] # action nodes, usually NoAction() nodes, marked as an early-exit (breakpoint) of a logic group
52
+ self._pending_connection_nodes: list[ActionNode] = [] # for those breakpoint-nodes, they will be activated when the corresponding logic group is finalized.
53
+ self._shelved_state = [] # shelve state to support temporally initialize a separate node-graph
51
54
  self.inspection_mode = False
55
+ self.vigilant_mode = False
52
56
 
53
57
  def __call__(self, name: str, cls: type[LogicGroup], **kwargs) -> LogicGroup:
54
58
  """
@@ -95,7 +99,7 @@ class LogicGroupManager(metaclass=Singleton):
95
99
 
96
100
  self._active_groups.pop(-1)
97
101
 
98
- for node in self._exit_nodes:
102
+ for node in self._breakpoint_nodes:
99
103
  if getattr(node, 'break_from') is logic_group:
100
104
  self._pending_connection_nodes.append(node)
101
105
 
@@ -129,6 +133,38 @@ class LogicGroupManager(metaclass=Singleton):
129
133
 
130
134
  self._active_nodes.pop(-1)
131
135
 
136
+ def shelve(self):
137
+ shelved_state = dict(
138
+ active_nodes=self._active_nodes.copy(),
139
+ breakpoint_nodes=self._breakpoint_nodes.copy(),
140
+ pending_connection_nodes=self._pending_connection_nodes.copy()
141
+ )
142
+
143
+ self._active_nodes.clear()
144
+ self._breakpoint_nodes.clear()
145
+ self._pending_connection_nodes.clear()
146
+
147
+ self._shelved_state.append(shelved_state)
148
+ return shelved_state
149
+
150
+ def unshelve(self, reset_active: bool = True, reset_breakpoints: bool = True, reset_pending: bool = True):
151
+ shelved_state = self._shelved_state.pop(-1)
152
+
153
+ if reset_active:
154
+ self._active_nodes.clear()
155
+
156
+ if reset_breakpoints:
157
+ self._breakpoint_nodes.clear()
158
+
159
+ if reset_pending:
160
+ self._pending_connection_nodes.clear()
161
+
162
+ self._active_nodes[:0] = shelved_state['active_nodes']
163
+ self._breakpoint_nodes[:0] = shelved_state['breakpoint_nodes']
164
+ self._pending_connection_nodes[:0] = shelved_state['pending_connection_nodes']
165
+
166
+ return shelved_state
167
+
132
168
  def clear(self):
133
169
  """
134
170
  Clear the cache of LogicGroup instances and reset active groups.
@@ -242,12 +278,17 @@ class LogicGroup(object, metaclass=LogicGroupMeta):
242
278
  if active_node is not None:
243
279
  active_node: LogicNode
244
280
  if not active_node.nodes:
245
- raise TooFewChildren()
246
- else:
247
- last_node = active_node.last_leaf
248
- assert isinstance(last_node, ActionNode), NodeValueError('An ActionNode is required before breaking a LogicGroup.')
249
- last_node.break_from = scope
250
- LGM._exit_nodes.append(last_node)
281
+ if LGM.vigilant_mode:
282
+ raise TooFewChildren()
283
+ else:
284
+ LOGGER.warning('Must have at least one action node before breaking from logic group. A NoAction node will be automatically assigned.')
285
+ from .node import NoAction
286
+ NoAction()
287
+
288
+ last_node = active_node.last_leaf
289
+ assert isinstance(last_node, ActionNode), NodeValueError('An ActionNode is required before breaking a LogicGroup.')
290
+ last_node.break_from = scope
291
+ LGM._breakpoint_nodes.append(last_node)
251
292
  return
252
293
 
253
294
  raise scope.Break()
@@ -542,14 +583,14 @@ class LogicExpression(SkipContextsBlock):
542
583
 
543
584
 
544
585
  class ExpressionCollection(LogicGroup):
545
- def __init__(self, data: Any, name: str, repr: str = None, **kwargs):
586
+ def __init__(self, data: Any, name: str, **kwargs):
546
587
  if 'logic_group' not in kwargs:
547
588
  logic_group = kwargs.get("logic_group")
548
589
  else:
549
590
  logic_group = LGM.active_logic_group
550
591
 
551
592
  super().__init__(
552
- name=repr if repr is not None else name if logic_group is None else f'{logic_group.name}.{name}',
593
+ name=name if name is not None else f'{logic_group.name}.{self.__class__.__name__}',
553
594
  parent=logic_group
554
595
  )
555
596
 
@@ -842,7 +883,7 @@ class LogicNode(LogicExpression):
842
883
 
843
884
  return root
844
885
 
845
- def to_html(self, with_group=True, dry_run=True, filename="decision_tree.html", **kwargs):
886
+ def to_html(self, with_group=True, dry_run=True, filename="decision_graph.html", **kwargs):
846
887
  """
847
888
  Visualizes the decision tree using PyVis.
848
889
  If dry_run=True, shows structure without highlighting active path.
@@ -1098,8 +1139,7 @@ class LogicNode(LogicExpression):
1098
1139
  class ActionNode(LogicNode):
1099
1140
  def __init__(
1100
1141
  self,
1101
- action: float | int | bool | None | Exception | Callable[[], Any],
1102
- dtype: type = None,
1142
+ action: Callable[[], Any] | None = None,
1103
1143
  repr: str = None,
1104
1144
  auto_connect: bool = True
1105
1145
  ):
@@ -1108,10 +1148,10 @@ class ActionNode(LogicNode):
1108
1148
 
1109
1149
  Args:
1110
1150
  action (Union[Any, Callable[[], Any]]): The action to execute.
1111
- dtype (type, optional): The expected type of the evaluated value (float, int, or bool).
1112
1151
  repr (str, optional): A string representation of the expression.
1152
+ auto_connect: auto-connect to the current active decision graph.
1113
1153
  """
1114
- super().__init__(expression=True, dtype=dtype, repr=repr)
1154
+ super().__init__(expression=True, repr=repr)
1115
1155
  self.action = action
1116
1156
 
1117
1157
  if auto_connect:
@@ -1123,6 +1163,14 @@ class ActionNode(LogicNode):
1123
1163
  def _on_exit(self):
1124
1164
  pass
1125
1165
 
1166
+ def _post_eval(self):
1167
+ """
1168
+ override this method to perform clean up functions.
1169
+ """
1170
+
1171
+ if self.action is not None:
1172
+ self.action()
1173
+
1126
1174
  def eval_recursively(self, path=None):
1127
1175
  """
1128
1176
  Evaluates the decision tree from this node based on the given state.
@@ -1134,10 +1182,10 @@ class ActionNode(LogicNode):
1134
1182
 
1135
1183
  value = self.eval()
1136
1184
 
1137
- if self.action is not None:
1138
- self.action()
1185
+ self._post_eval()
1139
1186
 
1140
1187
  for condition, child in self.nodes.items():
1188
+ LOGGER.warning(f'{self.__class__.__name__} should not have any sub-nodes.')
1141
1189
  if condition == value or condition is NO_CONDITION:
1142
1190
  return child.eval_recursively(path=path)
1143
1191
 
@@ -3,16 +3,14 @@ from __future__ import annotations
3
3
  from collections.abc import Mapping, Sequence
4
4
  from typing import Any
5
5
 
6
- from . import AttrExpression as _AE
6
+ from . import AttrExpression
7
7
  from .abc import LogicGroup, ExpressionCollection
8
8
 
9
9
  __all__ = ['LogicMapping', 'LogicGenerator']
10
10
 
11
11
 
12
12
  class LogicMapping(ExpressionCollection):
13
- AttrExpression = _AE
14
-
15
- def __init__(self, data: dict, name: str, repr: str = None, logic_group: LogicGroup = None):
13
+ def __init__(self, data: dict, name: str, logic_group: LogicGroup = None):
16
14
  if data is None:
17
15
  data = {}
18
16
 
@@ -22,7 +20,6 @@ class LogicMapping(ExpressionCollection):
22
20
  super().__init__(
23
21
  data=data,
24
22
  name=name,
25
- repr=repr,
26
23
  logic_group=logic_group
27
24
  )
28
25
 
@@ -33,10 +30,10 @@ class LogicMapping(ExpressionCollection):
33
30
  return self.data.__len__()
34
31
 
35
32
  def __getitem__(self, key: str):
36
- return self.AttrExpression(attr=key, logic_group=self)
33
+ return AttrExpression(attr=key, logic_group=self)
37
34
 
38
35
  def __getattr__(self, key: str):
39
- return self.AttrExpression(attr=key, logic_group=self)
36
+ return AttrExpression(attr=key, logic_group=self)
40
37
 
41
38
  def reset(self):
42
39
  pass
@@ -52,9 +49,7 @@ class LogicMapping(ExpressionCollection):
52
49
 
53
50
 
54
51
  class LogicGenerator(ExpressionCollection):
55
- AttrExpression = _AE
56
-
57
- def __init__(self, data: list[Any], name: str, repr: str = None, logic_group: LogicGroup = None):
52
+ def __init__(self, data: list[Any], name: str, logic_group: LogicGroup = None):
58
53
  if data is None:
59
54
  data = []
60
55
 
@@ -64,7 +59,6 @@ class LogicGenerator(ExpressionCollection):
64
59
  super().__init__(
65
60
  data=data,
66
61
  name=name,
67
- repr=repr,
68
62
  logic_group=logic_group
69
63
  )
70
64
 
@@ -75,13 +69,13 @@ class LogicGenerator(ExpressionCollection):
75
69
  # if isinstance(value, ContextLogicExpression):
76
70
  # yield value
77
71
 
78
- yield self.AttrExpression(attr=index, logic_group=self)
72
+ yield AttrExpression(attr=index, logic_group=self)
79
73
 
80
74
  def __len__(self) -> int:
81
75
  return len(self.data)
82
76
 
83
77
  def __getitem__(self, index: int):
84
- return self.AttrExpression(attr=index, logic_group=self)
78
+ return AttrExpression(attr=index, logic_group=self)
85
79
 
86
80
  def append(self, value):
87
81
  self.data.append(value)
@@ -14,11 +14,12 @@ __all__ = ['NoAction', 'LongAction', 'ShortAction', 'RootLogicNode', 'ContextLog
14
14
  class NoAction(ActionNode):
15
15
  def __init__(self, auto_connect: bool = True):
16
16
  super().__init__(
17
- action=None,
18
17
  repr='<NoAction>',
19
18
  auto_connect=auto_connect
20
19
  )
21
20
 
21
+ self.sig = 0
22
+
22
23
  def eval(self, enforce_dtype: bool = False) -> ActionNode:
23
24
  return self
24
25
 
@@ -26,11 +27,12 @@ class NoAction(ActionNode):
26
27
  class LongAction(ActionNode):
27
28
  def __init__(self, sig: int = 1, auto_connect: bool = True):
28
29
  super().__init__(
29
- action=sig,
30
30
  repr=f'<LongAction>(sig = {sig})',
31
31
  auto_connect=auto_connect
32
32
  )
33
33
 
34
+ self.sig = sig
35
+
34
36
  def eval(self, enforce_dtype: bool = False) -> ActionNode:
35
37
  return self
36
38
 
@@ -38,11 +40,12 @@ class LongAction(ActionNode):
38
40
  class ShortAction(ActionNode):
39
41
  def __init__(self, sig: int = -1, auto_connect: bool = True):
40
42
  super().__init__(
41
- action=sig,
42
43
  repr=f'<ShortAction>(sig = {sig})',
43
44
  auto_connect=auto_connect
44
45
  )
45
46
 
47
+ self.sig = sig
48
+
46
49
  def eval(self, enforce_dtype: bool = False) -> ActionNode:
47
50
  return self
48
51
 
@@ -58,17 +61,28 @@ class RootLogicNode(LogicNode):
58
61
  return True
59
62
 
60
63
  def _on_enter(self):
61
- self._inspection_mode = LGM.inspection_mode
62
- LGM.inspection_mode = True
63
- LGM._active_nodes.clear()
64
+ # pre-shelve call
64
65
  LGM.enter_expression(node=self)
65
66
 
67
+ state = LGM.shelve()
68
+
69
+ state['inspection_mode'] = LGM.inspection_mode
70
+
71
+ LGM.inspection_mode = True
72
+
73
+ # post-shelve call
74
+ LGM._active_nodes.append(self)
75
+
66
76
  def _on_exit(self):
77
+ # pre-unshelve call
78
+ # LGM.exit_expression(node=self)
79
+
80
+ state = LGM.unshelve()
81
+
82
+ # post-unshelve call
67
83
  LGM.exit_expression(node=self)
68
- if hasattr(self, '_inspection_mode'):
69
- LGM.inspection_mode = self._inspection_mode
70
- else:
71
- LGM.inspection_mode = False
84
+
85
+ LGM.inspection_mode = state['inspection_mode']
72
86
 
73
87
  def append(self, expression: Self, edge_condition: Any = None):
74
88
  if self.nodes:
@@ -78,7 +92,7 @@ class RootLogicNode(LogicNode):
78
92
  def eval_recursively(self, **kwargs):
79
93
  return self.child.eval_recursively(**kwargs)
80
94
 
81
- def to_html(self, with_group=True, dry_run=True, filename="decision_tree.html", **kwargs):
95
+ def to_html(self, with_group=True, dry_run=True, filename="decision_graph.html", **kwargs):
82
96
  return self.child.to_html(with_group=with_group, dry_run=dry_run, filename=filename, **kwargs)
83
97
 
84
98
  @property
@@ -0,0 +1,22 @@
1
+ import logging
2
+
3
+ from .. import LOGGER
4
+
5
+ LOGGER = LOGGER.getChild("LogicGroup")
6
+
7
+ __all__ = [
8
+ 'SignalLogicGroup', 'InstantConfirmationLogicGroup',
9
+ 'StateMapping', 'RequestAction', 'PendingRequest', 'RequestConfirmed', 'RequestDenied', 'RequestRegistered', 'DelayedConfirmationLogicGroup',
10
+ ]
11
+
12
+
13
+ def set_logger(logger: logging.Logger):
14
+ global LOGGER
15
+ LOGGER = logger
16
+
17
+ base.LOGGER = logger.getChild('base')
18
+ pending_request.LOGGER = logger.getChild('delayed')
19
+
20
+
21
+ from .base import *
22
+ from .pending_request import *
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, Any, Self, overload
4
+
5
+ from . import LOGGER
6
+ from ..decision_tree import AttrExpression, LogicGroup, ActionNode, LGM, LongAction, ShortAction, NoAction
7
+
8
+ LOGGER = LOGGER.getChild('base')
9
+
10
+ __all__ = ['SignalLogicGroup', 'InstantConfirmationLogicGroup']
11
+
12
+
13
+ class SignalLogicGroup(LogicGroup):
14
+ def __init__(self, name: str, parent: Self = None, contexts: dict[str, Any] = None):
15
+ super().__init__(name=name, parent=parent, contexts=contexts)
16
+
17
+ def get(self, attr: str, dtype: type = None, repr: str = None):
18
+ """
19
+ Retrieve an attribute as a LogicExpression.
20
+ """
21
+ return AttrExpression(attr=attr, logic_group=self, dtype=dtype, repr=repr)
22
+
23
+ def reset(self):
24
+ self.signal = 0
25
+
26
+ @property
27
+ def signal(self):
28
+ return self.contexts.get('signal', 0)
29
+
30
+ @signal.setter
31
+ def signal(self, value: int):
32
+ self.contexts['signal'] = value
33
+
34
+
35
+ class InstantConfirmationLogicGroup(SignalLogicGroup):
36
+ def __init__(self, parent: SignalLogicGroup, name: str = None):
37
+ super().__init__(
38
+ name=f'{parent.name}.Instant' if name is None else name,
39
+ parent=parent
40
+ )
41
+
42
+ def reset(self):
43
+ pass
44
+
45
+ @overload
46
+ def confirm(self, sig: Literal[1]) -> LongAction:
47
+ ...
48
+
49
+ @overload
50
+ def confirm(self, sig: Literal[-1]) -> ShortAction:
51
+ ...
52
+
53
+ def confirm(self, sig: Literal[-1, 1]) -> ActionNode:
54
+ self.signal = sig
55
+
56
+ if sig > 0:
57
+ return LongAction(sig=sig)
58
+ elif sig < 0:
59
+ return ShortAction(sig=sig)
60
+
61
+ if not LGM.inspection_mode:
62
+ LOGGER.warning(f'{self} received a confirmation of {sig=}! Which is not expected.')
63
+
64
+ return NoAction()
65
+
66
+ @property
67
+ def signal(self):
68
+ return self.parent.signal
69
+
70
+ @signal.setter
71
+ def signal(self, value: int):
72
+ self.parent.signal = value
@@ -0,0 +1,253 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import time
5
+ import uuid
6
+ from collections.abc import Mapping
7
+ from typing import Literal, Self, TypedDict
8
+
9
+ from . import LOGGER
10
+ from .base import SignalLogicGroup
11
+ from ..decision_tree import ActionNode, LogicMapping, LogicGroup, NodeValueError, LongAction, ShortAction, LGM, NoAction
12
+
13
+ LOGGER = LOGGER.getChild('request')
14
+
15
+ __all__ = ['RequestAction', 'PendingRequest', 'RequestConfirmed', 'RequestDenied', 'RequestRegistered', 'DelayedConfirmationLogicGroup']
16
+
17
+
18
+ class StateMapping(TypedDict):
19
+ timestamp: float
20
+
21
+
22
+ class RequestAction(enum.StrEnum):
23
+ open = enum.auto()
24
+ unwind = enum.auto()
25
+ idle = enum.auto()
26
+
27
+
28
+ class PendingRequest(dict):
29
+
30
+ def __init__(
31
+ self,
32
+ name: str,
33
+ timestamp: float,
34
+ sig: Literal[-1, 1] | int,
35
+ action: str,
36
+ timeout: float,
37
+ logic_group: LogicGroup = None,
38
+ uid: uuid.UUID = None,
39
+ **kwargs
40
+ ):
41
+ super().__init__(
42
+ name=name,
43
+ timestamp=timestamp,
44
+ sig=sig,
45
+ timeout=timeout,
46
+ action=RequestAction(action),
47
+ uid=uuid.uuid4() if uid is None else uid,
48
+ **kwargs
49
+ )
50
+
51
+ self.logic_group = logic_group
52
+
53
+ def reset(self) -> PendingRequest:
54
+ self.update(
55
+ name='DummyRequest',
56
+ timestamp=0,
57
+ sig=0,
58
+ action=RequestAction.idle,
59
+ timeout=0,
60
+ uid=uuid.UUID(int=0)
61
+ )
62
+ return self
63
+
64
+ @classmethod
65
+ def empty(cls) -> PendingRequest:
66
+ return PendingRequest(
67
+ name='DummyRequest',
68
+ timestamp=0,
69
+ sig=0,
70
+ action=RequestAction.idle,
71
+ timeout=0,
72
+ uid=uuid.UUID(int=0)
73
+ )
74
+
75
+ def __bool__(self):
76
+ if not self.sig:
77
+ return False
78
+ return True
79
+
80
+ @property
81
+ def name(self) -> str:
82
+ return self['name']
83
+
84
+ @property
85
+ def timestamp(self) -> float:
86
+ return self['timestamp']
87
+
88
+ @property
89
+ def sig(self) -> int:
90
+ return self['sig']
91
+
92
+ @property
93
+ def timeout(self) -> float:
94
+ return self['timeout']
95
+
96
+ @property
97
+ def action(self) -> RequestAction:
98
+ return self['action']
99
+
100
+ @property
101
+ def uid(self) -> uuid.UUID:
102
+ return self['uid']
103
+
104
+
105
+ class RequestConfirmed(ActionNode):
106
+ def __init__(self, sig: Literal[-1, 1], req: PendingRequest, auto_connect: bool = True):
107
+ super().__init__(
108
+ repr=f'<Pending Request Confirmed {sig=}>',
109
+ auto_connect=auto_connect
110
+ )
111
+
112
+ self.sig = sig
113
+ self.req = req
114
+
115
+ def eval(self, enforce_dtype: bool = False) -> ActionNode:
116
+ sig = self.sig
117
+
118
+ if sig > 0:
119
+ return LongAction(sig=sig, auto_connect=False)
120
+ elif sig < 0:
121
+ return ShortAction(sig=sig, auto_connect=False)
122
+
123
+ if not LGM.inspection_mode:
124
+ LOGGER.warning(f'{self} received a confirmation of {sig=}! Which is not expected.')
125
+
126
+ return NoAction(auto_connect=False)
127
+
128
+ def _post_eval(self) -> Self:
129
+ self.req.reset()
130
+
131
+
132
+ class RequestDenied(ActionNode):
133
+ def __init__(self, req: PendingRequest, auto_connect: bool = True):
134
+ super().__init__(
135
+ repr='<Pending Request Denied>',
136
+ auto_connect=auto_connect
137
+ )
138
+
139
+ self.req = req
140
+
141
+ def eval(self, enforce_dtype: bool = False) -> ActionNode:
142
+ return NoAction(auto_connect=False)
143
+
144
+ def _post_eval(self) -> Self:
145
+ self.req.reset()
146
+
147
+
148
+ class RequestRegistered(ActionNode):
149
+ def __init__(self, sig: Literal[-1, 1], req: PendingRequest, state: StateMapping | Mapping | LogicMapping = None, action: RequestAction = RequestAction.open, timeout: float = float('inf'), auto_connect: bool = True):
150
+ super().__init__(
151
+ action=self._registered,
152
+ repr=f'<Pending Request Registered {sig=}>',
153
+ auto_connect=auto_connect
154
+ )
155
+
156
+ self.state = state
157
+ self.req = req
158
+
159
+ self.sig = sig
160
+ self.action = action
161
+ self.timeout = timeout
162
+
163
+ def eval(self, enforce_dtype: bool = False) -> ActionNode:
164
+ return NoAction(auto_connect=False)
165
+
166
+ def _registered(self) -> Self:
167
+ sig = self.sig
168
+ timestamp = time.time() if self.state is None else self.state['timestamp']
169
+ action = self.action
170
+ timeout = self.timeout
171
+ uid = uuid.uuid4()
172
+
173
+ if self.sig > 0:
174
+ name = 'state.long'
175
+ elif self.sig < 0:
176
+ name = 'state.short'
177
+ else:
178
+ raise NodeValueError('Signal Must not be zero.')
179
+
180
+ self.req.update(
181
+ name=name,
182
+ timestamp=timestamp,
183
+ sig=sig,
184
+ timeout=timeout,
185
+ action=action,
186
+ uid=uid
187
+ )
188
+
189
+ return self
190
+
191
+
192
+ class DelayedConfirmationLogicGroup(SignalLogicGroup):
193
+ def __init__(self, parent: SignalLogicGroup, name: str = None):
194
+ super().__init__(
195
+ name=f'{parent.name}.Delayed' if name is None else name,
196
+ parent=parent
197
+ )
198
+
199
+ self.req = self.contexts['pending_request'] = PendingRequest.empty()
200
+
201
+ def register(self, sig: Literal[1, -1], state: StateMapping | Mapping | LogicMapping = None, timeout: float = float('inf'), action: RequestAction | str = RequestAction.open) -> RequestRegistered:
202
+ action_register = RequestRegistered(
203
+ sig=sig,
204
+ req=self.req,
205
+ state=state,
206
+ action=RequestAction(action),
207
+ timeout=timeout
208
+ )
209
+
210
+ return action_register
211
+
212
+ def confirm(self, sig: Literal[1, -1]) -> RequestConfirmed:
213
+ action_confirm = RequestConfirmed(req=self.req, sig=sig)
214
+ return action_confirm
215
+
216
+ def deny(self) -> RequestDenied:
217
+ action_deny = RequestDenied(req=self.req)
218
+ return action_deny
219
+
220
+ def reset(self):
221
+ self.pending_request.reset()
222
+ super().reset()
223
+
224
+ @property
225
+ def action(self) -> RequestAction:
226
+ return self.pending_request.action
227
+
228
+ @property
229
+ def pending_request(self) -> LogicMapping:
230
+ req = self.req
231
+
232
+ m = LogicMapping(
233
+ data=req,
234
+ name=f'{self.name}.PendingRequest',
235
+ logic_group=self
236
+ )
237
+
238
+ return m
239
+
240
+ @pending_request.setter
241
+ def pending_request(self, value: PendingRequest):
242
+ LOGGER.warning('Assigning pending request will break the reference of previous generated decision graph. Use with caution.')
243
+ assert isinstance(value, PendingRequest)
244
+ self.req = self.contexts['pending_request'] = value
245
+
246
+ @property
247
+ def signal(self):
248
+ return self.parent.signal
249
+
250
+ @signal.setter
251
+ def signal(self, value: Literal[-1, 0, 1]):
252
+ assert isinstance(value, (int, float))
253
+ self.parent.signal = value
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.2
2
+ Name: PyDecisionGraph
3
+ Version: 0.1.2
4
+ Summary: A rule-based decision tree implementation for Python
5
+ Home-page: https://github.com/BolunHan/PyDecisionGraph
6
+ Author: Han Bolun
7
+ Author-email: Han Bolun <Bolun.Han@outlook.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/BolunHan/PyDecisionGraph
10
+ Project-URL: Repository, https://github.com/BolunHan/PyDecisionGraph
11
+ Project-URL: Issues, https://github.com/BolunHan/PyDecisionGraph/issues
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: docs
20
+ Requires-Dist: sphinx; extra == "docs"
21
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
22
+ Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
23
+ Provides-Extra: visualization
24
+ Requires-Dist: pyvis; extra == "visualization"
25
+ Requires-Dist: networkx; extra == "visualization"
26
+
27
+ # PyDecisionGraph
28
+
29
+ `PyDecisionGraph` is an easy-to-use library to create custom decision trees, primarily designed for trading and financial decision-making. This package helps you build rule-based decision processes and visualize them effectively.
30
+
31
+ ## Installation
32
+
33
+ To install the package, run:
34
+
35
+ ```bash
36
+ pip install PyDecisionGraph
37
+ ```
38
+
39
+ # Requirements
40
+
41
+ - Python 3.12 or higher
42
+
43
+ # Documentation
44
+
45
+ For detailed documentation, visit https://pydecisiongraph.readthedocs.io/.
46
+
47
+ # Quick Start
48
+
49
+ Here is a quick demo on how to use `PyDecisionGraph` for building a decision tree based on various conditions:
50
+
51
+ ```python
52
+ from decision_graph.decision_tree import LogicNode, LOGGER, AttrExpression, LongAction, ShortAction, NoAction, RootLogicNode, LogicMapping
53
+
54
+ # Mapping of attribute names to their values
55
+ LogicMapping.AttrExpression = AttrExpression
56
+
57
+ state = {
58
+ "exposure": 0, # Current exposure
59
+ "working_order": 0, # Current working order
60
+ "up_prob": 0.8, # Probability of price going up
61
+ "down_prob": 0.2, # Probability of price going down
62
+ "volatility": 0.24, # Current market volatility
63
+ "ttl": 15.3 # Time to live (TTL) of the decision tree
64
+ }
65
+
66
+ # Root of the logic tree
67
+ with RootLogicNode() as root:
68
+ # Define root logic mapping with state data
69
+ with LogicMapping(name='Root', data=state) as lg_root:
70
+ lg_root: LogicMapping
71
+
72
+ # Condition for zero exposure
73
+ with lg_root.exposure == 0:
74
+ root: LogicNode
75
+ with LogicMapping(name='check_open', data=state) as lg:
76
+ with lg.working_order != 0:
77
+ break_point = NoAction() # No action if there's a working order
78
+ lg.break_(scope=lg) # Exit the current scope
79
+
80
+ with lg.volatility > 0.25: # Check if volatility is high
81
+ with lg.down_prob > 0.1: # Action for down probability
82
+ LongAction()
83
+
84
+ with lg.up_prob < -0.1: # Action for up probability
85
+ ShortAction()
86
+
87
+ # Condition when TTL is greater than 30
88
+ with lg_root.ttl > 30:
89
+ with lg_root.working_order > 0:
90
+ ShortAction() # Action to short if working order exists
91
+ LongAction() # Always take long action
92
+ lg_root.break_(scope=lg_root) # Exit scope
93
+
94
+ # Closing logic based on exposure and probabilities
95
+ with LogicMapping(name='check_close', data=state) as lg:
96
+ with (lg.exposure > 0) & (lg.down_prob > 0.):
97
+ ShortAction() # Short action for positive exposure and down probability
98
+
99
+ with (lg.exposure < 0) & (lg.up_prob > 0.):
100
+ LongAction() # Long action for negative exposure and up probability
101
+
102
+ # Visualize the decision tree
103
+ root.to_html()
104
+
105
+ # Log the evaluation result
106
+ LOGGER.info(root())
107
+ ```
108
+
109
+ ## Explanation of the Script:
110
+
111
+ - LogicNode & LogicMapping:
112
+ - LogicNode: Represents a node in the decision tree where conditions are evaluated.
113
+ - LogicMapping: Associates logical conditions with the state (data) used in decision-making.
114
+
115
+ - State:
116
+ - A dictionary containing the variables used for decision-making, such as exposure, working_order, up_prob, etc.
117
+
118
+ - RootLogicNode:
119
+ - The entry point for the decision tree where all logical decisions are linked.
120
+
121
+ - Decision Conditions:
122
+ - Inside each with block, logical conditions are evaluated (e.g., lg.volatility > 0.25, lg.up_prob < -0.1) to determine which action to take.
123
+ - Actions like LongAction() or ShortAction() are taken based on the conditions.
124
+
125
+ - Action Handling:
126
+ - LongAction(), ShortAction(), and NoAction() represent different actions you can trigger in the decision tree based on the conditions.
127
+
128
+ - Logging:
129
+ - The result of the tree evaluation is logged using the LOGGER object, which outputs to the console.
130
+
131
+ - Visualization:
132
+ - root.to_html() generates an HTML representation of the decision tree for visualization.
133
+
134
+ # Features
135
+
136
+ - Easily define custom decision rules.
137
+ - Actionable outcomes like LongAction, ShortAction, and NoAction.
138
+ - Log outputs for debugging and tracking.
139
+ - Visualize decision paths through HTML export.
140
+
141
+ ---
142
+
143
+ # Contributing
144
+
145
+ Feel free to fork, submit issues, and create pull requests. Contributions are always welcome!
146
+
147
+ # License
148
+
149
+ This project is licensed under the Mozilla Public License 2.0 - see the LICENSE file for details.
@@ -0,0 +1,15 @@
1
+ decision_graph/__init__.py,sha256=4QpoJ4PAJKXqLFITUOq55uOILaN60NWWz6q-FIBhYH4,386
2
+ decision_graph/decision_tree/__init__.py,sha256=S1UVgQ_wj1QQ9SkVkVvVOLKA_kjulvuo56EcBIt6spM,1512
3
+ decision_graph/decision_tree/abc.py,sha256=28tGBgXicJ802oLCPQm0Gvcm54mB1pMpbHimnt2L1MU,42114
4
+ decision_graph/decision_tree/collection.py,sha256=PLb58A_SS7vLRJ0l-T1OaZFHYhAHjdwij68uP_HpL70,2242
5
+ decision_graph/decision_tree/exc.py,sha256=THgIIP8S2PyJ9HIyUbNRhKT1umHfmf3Nh4rYrV8ywXU,646
6
+ decision_graph/decision_tree/expression.py,sha256=_bd1U2tjHnCydoC1Ya6RwTasRbKweMzUsghXsDe4ZpA,16777
7
+ decision_graph/decision_tree/node.py,sha256=x69Fa3izFQZRU_4urwYGorTR_eNAOr2IbKU7P5eeCnI,8721
8
+ decision_graph/logic_group/__init__.py,sha256=7lnQfCbVdgP3DtsCwFJ3Ht1Ig9LKONgdX2zvHT3gT1M,528
9
+ decision_graph/logic_group/base.py,sha256=F3eUO5nE4mB11f_KiSMpiYGFUAf947-2ESxbzskHT2U,1942
10
+ decision_graph/logic_group/pending_request.py,sha256=0Caip4FGFqdqiw1VaRlASDJv57ExQnAvlq3PQB1NCr8,6888
11
+ pydecisiongraph-0.1.2.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
12
+ pydecisiongraph-0.1.2.dist-info/METADATA,sha256=pjalVP0bT4Z1Rkx9Qki7f-SED8NxedVl27Qfhz1hWNU,5456
13
+ pydecisiongraph-0.1.2.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
14
+ pydecisiongraph-0.1.2.dist-info/top_level.txt,sha256=3_p81U9qj9sTh1K6a1ZE-JNv7LKHaCeavuzci28VjFk,15
15
+ pydecisiongraph-0.1.2.dist-info/RECORD,,
@@ -0,0 +1 @@
1
+ decision_graph
decision_tree/__init__.py DELETED
@@ -1,36 +0,0 @@
1
- __version__ = "0.1.0"
2
-
3
- import logging
4
- import sys
5
-
6
- LOGGER = logging.getLogger("DecisionTree")
7
- LOGGER.setLevel(logging.INFO)
8
-
9
- if not LOGGER.hasHandlers():
10
- ch = logging.StreamHandler(sys.stdout)
11
- ch.setLevel(logging.INFO) # Set handler level
12
- formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
13
- ch.setFormatter(formatter)
14
- LOGGER.addHandler(ch)
15
-
16
-
17
- def set_logger(logger: logging.Logger):
18
- global LOGGER
19
- LOGGER = logger
20
-
21
- exc.LOGGER = logger.getChild('TradeUtils')
22
- abc.LOGGER = logger.getChild('TA')
23
-
24
-
25
- NODE_MODEL = True
26
-
27
- from .exc import *
28
- from .abc import *
29
-
30
- if NODE_MODEL:
31
- from .node import *
32
- else:
33
- from .expression import *
34
-
35
- from .collection import *
36
- from .logic_group import *
@@ -1,307 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import enum
4
- import uuid
5
- from typing import Literal, Any, Self
6
-
7
- from . import AttrExpression, LogicMapping
8
- from .abc import LogicGroup, SkipContextsBlock
9
-
10
- __all__ = ['SignalLogicGroup', 'InstantConfirmationLogicGroup', 'RequestAction', 'PendingRequest', 'DelayedConfirmationLogicGroup', 'RacingConfirmationLogicGroup', 'BarrierConfirmationLogicGroup']
11
-
12
-
13
- class SignalLogicGroup(LogicGroup):
14
- def __init__(self, name: str, parent: Self = None, contexts: dict[str, Any] = None):
15
- super().__init__(name=name, parent=parent, contexts=contexts)
16
-
17
- def get(self, attr: str, dtype: type = None, repr: str = None):
18
- """
19
- Retrieve an attribute as a LogicExpression.
20
- """
21
- return AttrExpression(attr=attr, logic_group=self, dtype=dtype, repr=repr)
22
-
23
- def reset(self):
24
- self.signal = 0
25
-
26
- @property
27
- def signal(self):
28
- return self.contexts.get('signal', 0)
29
-
30
- @signal.setter
31
- def signal(self, value: int):
32
- self.contexts['signal'] = value
33
-
34
-
35
- class InstantConfirmationLogicGroup(SignalLogicGroup):
36
- def __init__(self, parent: SignalLogicGroup, name: str = None):
37
- super().__init__(
38
- name=f'{parent.name}.Instant' if name is None else name,
39
- parent=parent
40
- )
41
-
42
- def reset(self):
43
- pass
44
-
45
- def confirm(self, sig: Literal[-1, 1]):
46
- self.signal = sig
47
- return
48
-
49
- @property
50
- def signal(self):
51
- return self.parent.signal
52
-
53
- @signal.setter
54
- def signal(self, value: int):
55
- self.parent.signal = value
56
-
57
-
58
- class RequestAction(enum.StrEnum):
59
- open = enum.auto()
60
- unwind = enum.auto()
61
- idle = enum.auto()
62
-
63
-
64
- class PendingRequest(dict):
65
- class Skip(Exception):
66
- pass
67
-
68
- def __init__(
69
- self,
70
- name: str | RequestAction,
71
- timestamp: float,
72
- sig: Literal[-1, 1] | int,
73
- action: str,
74
- timeout: float,
75
- logic_group: LogicGroup = None,
76
- uid: uuid.UUID = None,
77
- **kwargs
78
- ):
79
- super().__init__(
80
- name=name,
81
- timestamp=timestamp,
82
- sig=sig,
83
- timeout=timeout,
84
- action=RequestAction(action),
85
- uid=uuid.uuid4() if uid is None else uid,
86
- **kwargs
87
- )
88
-
89
- self.logic_group = logic_group
90
-
91
- @classmethod
92
- def empty(cls) -> PendingRequest:
93
- return PendingRequest(
94
- name='DummyRequest',
95
- timestamp=0,
96
- sig=0,
97
- action=RequestAction.idle,
98
- timeout=0,
99
- uid=uuid.UUID(int=0)
100
- )
101
-
102
- def __bool__(self):
103
- if not self.sig:
104
- return False
105
- return True
106
-
107
- @property
108
- def name(self) -> str:
109
- return self['name']
110
-
111
- @property
112
- def timestamp(self) -> float:
113
- return self['timestamp']
114
-
115
- @property
116
- def sig(self) -> int:
117
- return self['sig']
118
-
119
- @property
120
- def timeout(self) -> float:
121
- return self['timeout']
122
-
123
- @property
124
- def action(self) -> RequestAction:
125
- return self['action']
126
-
127
- @property
128
- def uid(self) -> uuid.UUID:
129
- return self['uid']
130
-
131
-
132
- class DelayedConfirmationLogicGroup(SignalLogicGroup):
133
- def __init__(self, parent: SignalLogicGroup, name: str = None):
134
- super().__init__(
135
- name=f'{parent.name}.Delayed' if name is None else name,
136
- parent=parent
137
- )
138
-
139
- def register(self, name: str, timestamp: float, sig: Literal[1, -1], timeout: float | None, action: Literal['open', 'unwind'] = 'open', uid: uuid.UUID = None, **kwargs):
140
- req = self.pending_request = PendingRequest(
141
- name=name,
142
- logic_group=self,
143
- timestamp=timestamp,
144
- sig=sig,
145
- timeout=timeout,
146
- action=action,
147
- uid=uid,
148
- **kwargs
149
- )
150
-
151
- return req
152
-
153
- def confirm(self):
154
- req = self.contexts.get('pending_request')
155
- sig = 0 if req is None else req.sig
156
- self.reset()
157
- self.signal = sig
158
- return sig
159
-
160
- def deny(self):
161
- # denying all the pending request
162
- self.reset()
163
- return 0
164
-
165
- def reset(self):
166
- # self.pending_request = PendingRequest.empty()
167
- self.contexts.pop('pending_request', None)
168
- super().reset()
169
-
170
- @property
171
- def action(self) -> RequestAction:
172
- if 'pending_request' in self.contexts:
173
- return self.pending_request.action
174
-
175
- return RequestAction.idle
176
-
177
- @property
178
- def pending_request(self) -> LogicMapping | SkipContextsBlock:
179
-
180
- if (req := self.contexts.get('pending_request')) is None:
181
- return SkipContextsBlock(True)
182
-
183
- m = LogicMapping(
184
- data=req,
185
- name=f'{self.name}.PendingRequest.{req.uid.hex}',
186
- logic_group=self
187
- )
188
-
189
- return m
190
-
191
- @pending_request.setter
192
- def pending_request(self, value: PendingRequest):
193
- assert isinstance(value, PendingRequest)
194
- self.contexts['pending_request'] = value
195
-
196
- @property
197
- def signal(self):
198
- return self.parent.signal
199
-
200
- @signal.setter
201
- def signal(self, value: Literal[-1, 0, 1]):
202
- assert isinstance(value, (int, float))
203
- self.parent.signal = value
204
-
205
-
206
- class RacingConfirmationLogicGroup(DelayedConfirmationLogicGroup):
207
-
208
- def __init__(self, parent: SignalLogicGroup, name: str = None):
209
- super().__init__(
210
- name=f'{parent.name}.Racing' if name is None else name,
211
- parent=parent
212
- )
213
-
214
- def __getitem__(self, uid: uuid.UUID | str | bytes | int) -> PendingRequest:
215
- if not (request_pool := self.pending_request):
216
- raise KeyError(f'uid {uid} not found!')
217
-
218
- match uid:
219
- case uuid.UUID():
220
- for _pending_request in request_pool:
221
- if _pending_request.uid == uid:
222
- return _pending_request
223
- raise KeyError(f'uid {uid} not found!')
224
- case str():
225
- for _pending_request in request_pool:
226
- if _pending_request.uid.hex == uid:
227
- return _pending_request
228
- raise KeyError(f'uid {uid} not found!')
229
- case bytes():
230
- for _pending_request in request_pool:
231
- if _pending_request.uid.bytes == uid:
232
- return _pending_request
233
- raise KeyError(f'uid {uid} not found!')
234
- case int():
235
- return request_pool[uid]
236
- case _:
237
- raise TypeError(f'Invalid uid {uid}! Expected UUID or bytes or str!')
238
-
239
- def register(self, name: str, timestamp: float, sig: Literal[1, -1], timeout: float, action: Literal['open', 'unwind'] = 'open', uid: uuid.UUID = None, **kwargs):
240
- self.pending_request.append(
241
- PendingRequest(
242
- name=name,
243
- timestamp=timestamp,
244
- sig=sig,
245
- timeout=timeout,
246
- action=action,
247
- uid=uid,
248
- **kwargs
249
- )
250
- )
251
-
252
- def confirm(self, pending_request: PendingRequest = None, uid: uuid.UUID = None):
253
- if pending_request is None and uid is None:
254
- assert len(self.pending_request) == 1, ValueError('Multiple pending requests found! Must assign uid or pending_request instance!')
255
- pending_request = self.pending_request[0]
256
- elif pending_request is None:
257
- pending_request = self.__getitem__(uid=uid)
258
-
259
- sig = pending_request.sig
260
- self.reset()
261
- self.signal = sig
262
- return sig
263
-
264
- def deny(self, pending_request: PendingRequest = None, uid: uuid.UUID = None):
265
- # denying all the pending request
266
- if pending_request is None and uid is None:
267
- self.pending_request.clear()
268
- self.signal = 0
269
- return
270
-
271
- if pending_request is not None:
272
- self.pending_request.remove(pending_request)
273
- self.signal = 0
274
-
275
- if uid is not None:
276
- pending_request = self.__getitem__(uid=uid)
277
- self.pending_request.remove(pending_request)
278
- self.signal = 0
279
-
280
- @property
281
- def pending_request(self) -> list[PendingRequest]:
282
- return self.contexts.setdefault('pending_request', [])
283
-
284
-
285
- class BarrierConfirmationLogicGroup(RacingConfirmationLogicGroup):
286
-
287
- def __init__(self, parent: SignalLogicGroup, name: str = None):
288
- super().__init__(
289
- name=f'{parent.name}.Barrier' if name is None else name,
290
- parent=parent
291
- )
292
-
293
- def confirm(self, pending_request: PendingRequest = None, uid: uuid.UUID = None):
294
- if pending_request is None and uid is None:
295
- assert len(self.pending_request) == 1, ValueError('Multiple pending requests found! Must assign uid or pending_request instance!')
296
- pending_request = self.pending_request[0]
297
- elif pending_request is None:
298
- pending_request = self.__getitem__(uid=uid)
299
-
300
- self.pending_request.remove(pending_request)
301
-
302
- if self.pending_request:
303
- return 0
304
-
305
- sig = pending_request.sig
306
- self.signal = sig
307
- return sig
@@ -1,21 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: PyDecisionGraph
3
- Version: 0.1.0
4
- Summary: A rule-based decision tree implementation for Python
5
- Home-page: https://github.com/BolunHan/PyDecisionTree
6
- Author: Han Bolun
7
- Author-email: Han Bolun <Bolun.Han@outlook.com>
8
- License: MIT
9
- Project-URL: Homepage, https://github.com/BolunHan/PyDecisionTree
10
- Project-URL: Repository, https://github.com/BolunHan/PyDecisionTree
11
- Project-URL: Issues, https://github.com/BolunHan/PyDecisionTree/issues
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.12
14
- Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
15
- Classifier: Operating System :: OS Independent
16
- Requires-Python: >=3.12
17
- Description-Content-Type: text/markdown
18
- License-File: LICENSE
19
-
20
- # PyDecisionTree
21
- A easy way to create custom decision tree
@@ -1,12 +0,0 @@
1
- decision_tree/__init__.py,sha256=AFHLZX4P9b6woOM78Uw5HIo83iqk9IP87quzxgsCVUM,739
2
- decision_tree/abc.py,sha256=yV9EraSbsDAAT01_TQVf0iuCbcE9hFCQKvQzui5CuKs,40439
3
- decision_tree/collection.py,sha256=K9fob0e7493BSXik0zfPzVSFupQezz1zvABYvMAo27k,2403
4
- decision_tree/exc.py,sha256=THgIIP8S2PyJ9HIyUbNRhKT1umHfmf3Nh4rYrV8ywXU,646
5
- decision_tree/expression.py,sha256=_bd1U2tjHnCydoC1Ya6RwTasRbKweMzUsghXsDe4ZpA,16777
6
- decision_tree/logic_group.py,sha256=-PkZJ9VzJf3WXRNmlepGeSRXBdf0K0o9yBr6xxeqvzI,9219
7
- decision_tree/node.py,sha256=eGxI3l7_zQMhbTtXgNnrFhKMDVvkKtJOR0_d9vfzyyY,8598
8
- pydecisiongraph-0.1.0.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
9
- pydecisiongraph-0.1.0.dist-info/METADATA,sha256=LDfkD50rVrkYoSbnxwVuaiLbFjY3G2hOkhiMfp7VQQE,827
10
- pydecisiongraph-0.1.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
11
- pydecisiongraph-0.1.0.dist-info/top_level.txt,sha256=bpPqmvSipqlIOvoSo-_Vc2muvqyhrfKbE6Zp-Km0J7o,14
12
- pydecisiongraph-0.1.0.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- decision_tree
File without changes