composite.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. # -*- coding: utf-8 -*-
  2. """
  3. Composite Conditions - Kombiniert mehrere Conditions.
  4. """
  5. from typing import Any
  6. from trixy_core.scheduler.condition.base import Condition
  7. class AndCondition(Condition):
  8. """
  9. Prüft ob ALLE Sub-Conditions erfüllt sind.
  10. Beispiel:
  11. - AndCondition([TimeRangeCondition(...), DayOfWeekCondition(...)])
  12. """
  13. def __init__(
  14. self,
  15. conditions: list[Condition],
  16. name: str | None = None,
  17. invert: bool = False,
  18. ):
  19. """
  20. Initialisiert die Condition.
  21. Args:
  22. conditions: Liste von Sub-Conditions
  23. name: Condition-Name
  24. invert: Ergebnis invertieren
  25. """
  26. super().__init__(name, invert)
  27. if not conditions:
  28. raise ValueError("AndCondition benötigt mindestens eine Condition")
  29. self._conditions = list(conditions)
  30. @property
  31. def conditions(self) -> list[Condition]:
  32. """Sub-Conditions."""
  33. return list(self._conditions)
  34. def add_condition(self, condition: Condition) -> None:
  35. """Fügt Condition hinzu."""
  36. self._conditions.append(condition)
  37. def remove_condition(self, condition: Condition) -> bool:
  38. """Entfernt Condition."""
  39. try:
  40. self._conditions.remove(condition)
  41. return True
  42. except ValueError:
  43. return False
  44. def _evaluate(self, context: dict) -> bool:
  45. """Prüft ob alle Conditions erfüllt sind."""
  46. if not self._conditions:
  47. return True
  48. return all(c.evaluate(context) for c in self._conditions)
  49. @classmethod
  50. def display_name(cls) -> str:
  51. """Anzeigename fuer die TUI."""
  52. return "UND-Bedingung"
  53. def to_dict(self) -> dict:
  54. """Konvertiert zu Dictionary."""
  55. return {
  56. **self._base_dict(),
  57. "conditions": [c.to_dict() for c in self._conditions],
  58. }
  59. class OrCondition(Condition):
  60. """
  61. Prüft ob MINDESTENS EINE Sub-Condition erfüllt ist.
  62. Beispiel:
  63. - OrCondition([StateCondition(...), BooleanCondition(...)])
  64. """
  65. def __init__(
  66. self,
  67. conditions: list[Condition],
  68. name: str | None = None,
  69. invert: bool = False,
  70. ):
  71. """
  72. Initialisiert die Condition.
  73. Args:
  74. conditions: Liste von Sub-Conditions
  75. name: Condition-Name
  76. invert: Ergebnis invertieren
  77. """
  78. super().__init__(name, invert)
  79. if not conditions:
  80. raise ValueError("OrCondition benötigt mindestens eine Condition")
  81. self._conditions = list(conditions)
  82. self._matched_condition: Condition | None = None
  83. @property
  84. def conditions(self) -> list[Condition]:
  85. """Sub-Conditions."""
  86. return list(self._conditions)
  87. @property
  88. def matched_condition(self) -> Condition | None:
  89. """Zuletzt gematchte Condition."""
  90. return self._matched_condition
  91. def add_condition(self, condition: Condition) -> None:
  92. """Fügt Condition hinzu."""
  93. self._conditions.append(condition)
  94. def remove_condition(self, condition: Condition) -> bool:
  95. """Entfernt Condition."""
  96. try:
  97. self._conditions.remove(condition)
  98. return True
  99. except ValueError:
  100. return False
  101. def _evaluate(self, context: dict) -> bool:
  102. """Prüft ob mindestens eine Condition erfüllt ist."""
  103. if not self._conditions:
  104. return False
  105. for condition in self._conditions:
  106. if condition.evaluate(context):
  107. self._matched_condition = condition
  108. return True
  109. self._matched_condition = None
  110. return False
  111. @classmethod
  112. def display_name(cls) -> str:
  113. """Anzeigename fuer die TUI."""
  114. return "ODER-Bedingung"
  115. def to_dict(self) -> dict:
  116. """Konvertiert zu Dictionary."""
  117. return {
  118. **self._base_dict(),
  119. "conditions": [c.to_dict() for c in self._conditions],
  120. }
  121. class NotCondition(Condition):
  122. """
  123. Invertiert eine Condition.
  124. Beispiel:
  125. - NotCondition(StateCondition("system_mode", "maintenance"))
  126. → Nur wenn NICHT im Wartungsmodus
  127. Hinweis: Kann auch über invert=True bei anderen Conditions erreicht werden.
  128. """
  129. def __init__(
  130. self,
  131. condition: Condition,
  132. name: str | None = None,
  133. ):
  134. """
  135. Initialisiert die Condition.
  136. Args:
  137. condition: Zu invertierende Condition
  138. name: Condition-Name
  139. """
  140. # NOT Condition hat kein eigenes invert
  141. super().__init__(name, invert=False)
  142. self._condition = condition
  143. @property
  144. def condition(self) -> Condition:
  145. """Innere Condition."""
  146. return self._condition
  147. def _evaluate(self, context: dict) -> bool:
  148. """Invertiert das Ergebnis der inneren Condition."""
  149. return not self._condition.evaluate(context)
  150. @classmethod
  151. def display_name(cls) -> str:
  152. """Anzeigename fuer die TUI."""
  153. return "NICHT-Bedingung"
  154. def to_dict(self) -> dict:
  155. """Konvertiert zu Dictionary."""
  156. return {
  157. **self._base_dict(),
  158. "condition": self._condition.to_dict(),
  159. }
  160. class XorCondition(Condition):
  161. """
  162. Prüft ob GENAU EINE Sub-Condition erfüllt ist.
  163. Beispiel:
  164. - XorCondition([ConditionA, ConditionB])
  165. → Nur wenn A oder B, aber nicht beide
  166. """
  167. def __init__(
  168. self,
  169. conditions: list[Condition],
  170. name: str | None = None,
  171. invert: bool = False,
  172. ):
  173. """
  174. Initialisiert die Condition.
  175. Args:
  176. conditions: Liste von Sub-Conditions
  177. name: Condition-Name
  178. invert: Ergebnis invertieren
  179. """
  180. super().__init__(name, invert)
  181. if len(conditions) < 2:
  182. raise ValueError("XorCondition benötigt mindestens zwei Conditions")
  183. self._conditions = list(conditions)
  184. @property
  185. def conditions(self) -> list[Condition]:
  186. """Sub-Conditions."""
  187. return list(self._conditions)
  188. @classmethod
  189. def display_name(cls) -> str:
  190. """Anzeigename fuer die TUI."""
  191. return "XOR-Bedingung"
  192. def _evaluate(self, context: dict) -> bool:
  193. """Prüft ob genau eine Condition erfüllt ist."""
  194. results = [c.evaluate(context) for c in self._conditions]
  195. return sum(results) == 1
  196. def to_dict(self) -> dict:
  197. """Konvertiert zu Dictionary."""
  198. return {
  199. **self._base_dict(),
  200. "conditions": [c.to_dict() for c in self._conditions],
  201. }