from __future__ import annotations from datetime import datetime, timezone from app.core.decision import DecisionEngineV2 from app.core.handoff import HandoffMatrix from app.core.models import ( AuditEvent, ControlProfile, EntityState, LearningProfile, StateEvent, ) from app.core.stores import FutureStores class RoutingIndex: def affected_actuators(self, trigger_entity_id: str, learning: dict[str, LearningProfile]) -> list[str]: result = [ actuator_id for actuator_id, profile in learning.items() if any(pattern.trigger_entity_id == trigger_entity_id for pattern in profile.patterns) ] return sorted(set(result)) class EventCore: def __init__(self, stores: FutureStores) -> None: self._stores = stores self._router = RoutingIndex() self._decision = DecisionEngineV2() self._handoff = HandoffMatrix() def process_state_event(self, event: StateEvent) -> list[AuditEvent]: runtime = self._stores.runtime() learning = self._stores.learning() control = self._stores.control() runtime.entities[event.entity_id] = EntityState( entity_id=event.entity_id, domain=event.entity_id.split(".", 1)[0], state=event.new_state, changed_at=event.changed_at, area_name=event.attributes.get("area_name"), device_id=event.attributes.get("device_id"), friendly_name=event.attributes.get("friendly_name"), ) audit: list[AuditEvent] = [ AuditEvent( event_id=_event_id("state"), kind="state", entity_id=event.entity_id, message=f"{event.entity_id} -> {event.new_state}", ) ] for actuator_id in self._router.affected_actuators(event.entity_id, learning.profiles): learning_profile = learning.profiles[actuator_id] control_profile = control.profiles.get( actuator_id, ControlProfile(actuator_entity_id=actuator_id), ) control_profile = control_profile.model_copy( update={"handoff_mode": self._handoff.classify(control_profile)} ) control.profiles[actuator_id] = control_profile decision = self._decision.decide( actuator_entity_id=actuator_id, trigger_entity_id=event.entity_id, runtime=runtime, learning=learning_profile, control=control_profile, ) audit.append( AuditEvent( event_id=_event_id("decision"), kind="decision", entity_id=actuator_id, message=decision.reason, decision=decision, ) ) runtime.audit = [*runtime.audit, *audit][-200:] self._stores.save_runtime(runtime) self._stores.save_control(control) return audit def _event_id(prefix: str) -> str: return f"{prefix}-{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S%f')}"