Use fresh HA event state for behavior triggers
This commit is contained in:
@@ -187,7 +187,13 @@ class BehaviorEngine:
|
||||
results.append(record)
|
||||
return results
|
||||
|
||||
def evaluate(self, actuator_entity_id: str) -> ActuatorRecord:
|
||||
def evaluate(
|
||||
self,
|
||||
actuator_entity_id: str,
|
||||
*,
|
||||
context_state_overrides: dict[str, str | None] | None = None,
|
||||
context_changed_at_overrides: dict[str, datetime | None] | None = None,
|
||||
) -> ActuatorRecord:
|
||||
record = self._store.get(actuator_entity_id)
|
||||
now = datetime.now(timezone.utc)
|
||||
try:
|
||||
@@ -230,6 +236,22 @@ class BehaviorEngine:
|
||||
entity_id: entities[entity_id].last_changed
|
||||
for entity_id in current_context
|
||||
}
|
||||
selected_context_ids = {
|
||||
entity_id
|
||||
for entity_id in (
|
||||
[
|
||||
record.assignment.selected_numeric_entity_id,
|
||||
*record.assignment.selected_context_entity_ids,
|
||||
]
|
||||
)
|
||||
if entity_id
|
||||
}
|
||||
for entity_id, state in (context_state_overrides or {}).items():
|
||||
if entity_id in selected_context_ids and state is not None:
|
||||
current_context[entity_id] = state
|
||||
for entity_id, changed_at in (context_changed_at_overrides or {}).items():
|
||||
if entity_id in current_context:
|
||||
current_context_changed_at[entity_id] = changed_at or now
|
||||
prediction = predict_behavior(
|
||||
record.behavior.patterns,
|
||||
current_context=current_context,
|
||||
@@ -622,6 +644,8 @@ class BehaviorEngine:
|
||||
except Exception:
|
||||
logger.exception("Event-basierte Vorhersage fehlgeschlagen für %s", record.actuator_entity_id)
|
||||
return
|
||||
event_state = _event_state(new_state)
|
||||
event_changed_at = _event_changed_at(new_state) or datetime.now(timezone.utc)
|
||||
# Kontext-Entity: alle Aktoren finden, die diesen Kontext nutzen
|
||||
affected_actuators = [
|
||||
record.actuator_entity_id
|
||||
@@ -633,11 +657,37 @@ class BehaviorEngine:
|
||||
]
|
||||
for actuator_entity_id in affected_actuators:
|
||||
try:
|
||||
self.evaluate(actuator_entity_id)
|
||||
self.evaluate(
|
||||
actuator_entity_id,
|
||||
context_state_overrides={entity_id: event_state},
|
||||
context_changed_at_overrides={entity_id: event_changed_at},
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Event-basierte Vorhersage fehlgeschlagen für %s", actuator_entity_id)
|
||||
|
||||
|
||||
def _event_state(new_state: dict[str, object] | None) -> str | None:
|
||||
if not isinstance(new_state, dict):
|
||||
return None
|
||||
state = new_state.get("state")
|
||||
return state if isinstance(state, str) else None
|
||||
|
||||
|
||||
def _event_changed_at(new_state: dict[str, object] | None) -> datetime | None:
|
||||
if not isinstance(new_state, dict):
|
||||
return None
|
||||
value = new_state.get("last_changed") or new_state.get("last_updated")
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
try:
|
||||
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
return None
|
||||
if parsed.tzinfo is None:
|
||||
return parsed.replace(tzinfo=timezone.utc)
|
||||
return parsed
|
||||
|
||||
|
||||
def predict_behavior(
|
||||
patterns: list[BehaviorPattern],
|
||||
*,
|
||||
|
||||
@@ -100,7 +100,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
app = FastAPI(
|
||||
title="SillyHome Next API",
|
||||
description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.",
|
||||
version="0.7.8",
|
||||
version="0.7.9",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
app.state.settings = load_settings()
|
||||
|
||||
Reference in New Issue
Block a user