# -*- coding: utf-8 -*- """ State Conditions - Zustandsbasierte Bedingungen. """ from typing import Any from trixy_core.scheduler.condition.base import Condition from trixy_core.scheduler.form_fields import FieldType, FormField, FormValidationError class StateCondition(Condition): """ Prüft ob System/Komponente in bestimmtem Zustand ist. Beispiel: - StateCondition("satellite_kitchen", "connected") - StateCondition("system_mode", ["active", "standby"]) """ def __init__( self, state_key: str, expected_state: str | list[str], name: str | None = None, invert: bool = False, ): """ Initialisiert die Condition. Args: state_key: Schlüssel für Zustand im Kontext expected_state: Erwarteter Zustand oder Liste name: Condition-Name invert: Ergebnis invertieren """ super().__init__(name, invert) self._state_key = state_key if isinstance(expected_state, str): self._expected_states = [expected_state] else: self._expected_states = list(expected_state) @property def state_key(self) -> str: """Zustands-Schlüssel.""" return self._state_key @property def expected_states(self) -> list[str]: """Erwartete Zustände.""" return list(self._expected_states) def _evaluate(self, context: dict) -> bool: """Prüft ob Zustand erwartet ist.""" current_state = context.get(self._state_key) if current_state is None: return False # String-Vergleich (case-insensitive) current_str = str(current_state).lower() return any( s.lower() == current_str for s in self._expected_states ) def to_dict(self) -> dict: """Konvertiert zu Dictionary.""" return { **self._base_dict(), "state_key": self._state_key, "expected_states": self._expected_states, } @classmethod def from_dict(cls, data: dict) -> "StateCondition": """Erstellt Condition aus Dictionary.""" return cls( state_key=data["state_key"], expected_state=data["expected_states"], name=data.get("name"), invert=data.get("invert", False), ) @classmethod def display_name(cls) -> str: """Anzeigename fuer die TUI.""" return "Status-Pruefung" @classmethod def form_fields(cls) -> list[FormField]: """Gibt die Formular-Felder fuer die TUI zurueck.""" return [ FormField( name="state_key", label="Status-Schluessel", required=True, placeholder="satellite_kitchen", help_text="Schluessel fuer den Zustand im Kontext", ), FormField( name="expected_value", label="Erwarteter Wert", placeholder="connected", help_text="Erwarteter Zustandswert", ), FormField( name="comparison", label="Vergleich", field_type=FieldType.SELECT, default="eq", choices=[ ("eq", "Gleich"), ("ne", "Ungleich"), ("gt", "Groesser"), ("lt", "Kleiner"), ("gte", "Groesser/Gleich"), ("lte", "Kleiner/Gleich"), ], ), FormField( name="invert", label="Invertieren", field_type=FieldType.CHECKBOX, default=False, ), ] @classmethod def validate_form(cls, data: dict) -> list[FormValidationError]: """Validiert Formular-Daten. Gibt Liste von Fehlern zurueck.""" errors: list[FormValidationError] = [] if not data.get("state_key", "").strip(): errors.append(FormValidationError("state_key", "Status-Schluessel ist erforderlich")) return errors class BooleanCondition(Condition): """ Prüft ob Boolean-Flag gesetzt ist. Beispiel: - BooleanCondition("is_home") - Nur wenn zu Hause - BooleanCondition("debug_mode", expected=False) - Nur wenn kein Debug """ def __init__( self, flag_key: str, expected: bool = True, name: str | None = None, invert: bool = False, ): """ Initialisiert die Condition. Args: flag_key: Schlüssel für Flag im Kontext expected: Erwarteter Wert (default: True) name: Condition-Name invert: Ergebnis invertieren """ super().__init__(name, invert) self._flag_key = flag_key self._expected = expected @property def flag_key(self) -> str: """Flag-Schlüssel.""" return self._flag_key @property def expected(self) -> bool: """Erwarteter Wert.""" return self._expected def _evaluate(self, context: dict) -> bool: """Prüft ob Flag erwartet ist.""" value = context.get(self._flag_key) # None behandeln if value is None: return not self._expected # Boolean konvertieren if isinstance(value, bool): return value == self._expected elif isinstance(value, str): truthy = value.lower() in ("true", "1", "yes", "on") return truthy == self._expected elif isinstance(value, (int, float)): return bool(value) == self._expected return False def to_dict(self) -> dict: """Konvertiert zu Dictionary.""" return { **self._base_dict(), "flag_key": self._flag_key, "expected": self._expected, } @classmethod def from_dict(cls, data: dict) -> "BooleanCondition": """Erstellt Condition aus Dictionary.""" return cls( flag_key=data["flag_key"], expected=data.get("expected", True), name=data.get("name"), invert=data.get("invert", False), ) @classmethod def display_name(cls) -> str: """Anzeigename fuer die TUI.""" return "Boolean-Pruefung" @classmethod def form_fields(cls) -> list[FormField]: """Gibt die Formular-Felder fuer die TUI zurueck.""" return [ FormField( name="state_key", label="Flag-Schluessel", required=True, placeholder="is_home", help_text="Schluessel fuer das Boolean-Flag im Kontext", ), FormField( name="invert", label="Invertieren", field_type=FieldType.CHECKBOX, default=False, ), ] @classmethod def validate_form(cls, data: dict) -> list[FormValidationError]: """Validiert Formular-Daten. Gibt Liste von Fehlern zurueck.""" errors: list[FormValidationError] = [] if not data.get("state_key", "").strip(): errors.append(FormValidationError("state_key", "Flag-Schluessel ist erforderlich")) return errors class ExistsCondition(Condition): """ Prüft ob Schlüssel im Kontext existiert. Beispiel: - ExistsCondition("user_preferences") - ExistsCondition("error_message", exists=False) """ def __init__( self, key: str, exists: bool = True, allow_none: bool = False, name: str | None = None, invert: bool = False, ): """ Initialisiert die Condition. Args: key: Schlüssel im Kontext exists: Soll existieren (True) oder nicht (False) allow_none: None als existent zählen name: Condition-Name invert: Ergebnis invertieren """ super().__init__(name, invert) self._key = key self._exists = exists self._allow_none = allow_none @property def key(self) -> str: """Schlüssel.""" return self._key def _evaluate(self, context: dict) -> bool: """Prüft ob Schlüssel existiert.""" key_exists = self._key in context if key_exists and not self._allow_none: key_exists = context[self._key] is not None return key_exists == self._exists def to_dict(self) -> dict: """Konvertiert zu Dictionary.""" return { **self._base_dict(), "key": self._key, "exists": self._exists, "allow_none": self._allow_none, } @classmethod def from_dict(cls, data: dict) -> "ExistsCondition": """Erstellt Condition aus Dictionary.""" return cls( key=data["key"], exists=data.get("exists", True), allow_none=data.get("allow_none", False), name=data.get("name"), invert=data.get("invert", False), ) @classmethod def display_name(cls) -> str: """Anzeigename fuer die TUI.""" return "Existenz-Pruefung" @classmethod def form_fields(cls) -> list[FormField]: """Gibt die Formular-Felder fuer die TUI zurueck.""" return [ FormField( name="state_key", label="Schluessel", required=True, placeholder="user_preferences", help_text="Schluessel der im Kontext existieren soll", ), FormField( name="invert", label="Invertieren", field_type=FieldType.CHECKBOX, default=False, ), ] @classmethod def validate_form(cls, data: dict) -> list[FormValidationError]: """Validiert Formular-Daten. Gibt Liste von Fehlern zurueck.""" errors: list[FormValidationError] = [] if not data.get("state_key", "").strip(): errors.append(FormValidationError("state_key", "Schluessel ist erforderlich")) return errors