BEHAVIOR-001: learn and predict actuator actions
This commit is contained in:
@@ -4,8 +4,9 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.actuators.lifecycle import ActuatorReconciliationService
|
||||
from app.actuators.models import ActuatorRecord, ManualOverride, ReconciliationState
|
||||
from app.actuators.models import ActuatorRecord, ReconciliationState
|
||||
from app.actuators.store import ActuatorStore
|
||||
from app.behavior.engine import BehaviorEngine
|
||||
from app.dependencies import get_ha_reader
|
||||
from app.ha.discovery import EntityRole
|
||||
from app.ha.models import HaEntitySummary
|
||||
@@ -19,11 +20,8 @@ class ConfigureActuatorRequest(BaseModel):
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class OverrideRequest(BaseModel):
|
||||
numeric_entity_id: str | None = Field(default=None, pattern=r"^[a-z0-9_]+\.[a-z0-9_]+$")
|
||||
context_entity_ids: list[str] = Field(default_factory=list)
|
||||
note: str | None = Field(default=None, max_length=300)
|
||||
clear: bool = False
|
||||
class ActivationRequest(BaseModel):
|
||||
active: bool
|
||||
|
||||
|
||||
@router.get("/discovery", response_model=list[HaEntitySummary])
|
||||
@@ -44,10 +42,12 @@ def list_configured(request: Request) -> list[ActuatorRecord]:
|
||||
@router.post("", response_model=ActuatorRecord, status_code=201)
|
||||
def configure(payload: ConfigureActuatorRequest, request: Request) -> ActuatorRecord:
|
||||
try:
|
||||
return _service(request).configure_actuator(
|
||||
record = _service(request).configure_actuator(
|
||||
payload.actuator_entity_id,
|
||||
enabled=payload.enabled,
|
||||
)
|
||||
_behavior(request).train(record.actuator_entity_id)
|
||||
return _behavior(request).evaluate(record.actuator_entity_id)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
@@ -65,34 +65,44 @@ def delete_actuator(actuator_entity_id: str, request: Request) -> None:
|
||||
_service(request).delete_actuator(actuator_entity_id)
|
||||
|
||||
|
||||
@router.post("/{actuator_entity_id}/override", response_model=ActuatorRecord)
|
||||
def set_override(
|
||||
actuator_entity_id: str,
|
||||
payload: OverrideRequest,
|
||||
request: Request,
|
||||
) -> ActuatorRecord:
|
||||
override = None if payload.clear else ManualOverride(
|
||||
numeric_entity_id=payload.numeric_entity_id,
|
||||
context_entity_ids=payload.context_entity_ids,
|
||||
note=payload.note,
|
||||
)
|
||||
try:
|
||||
return _service(request).set_override(actuator_entity_id, override)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/{actuator_entity_id}/reconcile", response_model=ActuatorRecord)
|
||||
def reconcile_actuator(
|
||||
actuator_entity_id: str,
|
||||
request: Request,
|
||||
) -> ActuatorRecord:
|
||||
try:
|
||||
return _service(request).reconcile_actuator(actuator_entity_id, trigger="manual")
|
||||
_service(request).reconcile_actuator(actuator_entity_id, trigger="manual")
|
||||
_behavior(request).train(actuator_entity_id)
|
||||
return _behavior(request).evaluate(actuator_entity_id)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/{actuator_entity_id}/evaluate", response_model=ActuatorRecord)
|
||||
def evaluate_actuator(
|
||||
actuator_entity_id: str,
|
||||
request: Request,
|
||||
) -> ActuatorRecord:
|
||||
try:
|
||||
return _behavior(request).evaluate(actuator_entity_id)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/{actuator_entity_id}/activation", response_model=ActuatorRecord)
|
||||
def set_activation(
|
||||
actuator_entity_id: str,
|
||||
payload: ActivationRequest,
|
||||
request: Request,
|
||||
) -> ActuatorRecord:
|
||||
try:
|
||||
return _behavior(request).set_active(actuator_entity_id, active=payload.active)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.get("/reconciliation/state", response_model=ReconciliationState)
|
||||
def get_reconciliation_state(request: Request) -> ReconciliationState:
|
||||
store = getattr(request.app.state, "actuator_store", None)
|
||||
@@ -109,7 +119,10 @@ def run_reconciliation(
|
||||
request: Request,
|
||||
trigger: str = Query(default="manual", pattern=r"^[a-z0-9_-]{1,32}$"),
|
||||
) -> ReconciliationState:
|
||||
return _service(request).reconcile_all(trigger=trigger)
|
||||
state = _service(request).reconcile_all(trigger=trigger)
|
||||
_behavior(request).train_all()
|
||||
_behavior(request).evaluate_all()
|
||||
return state
|
||||
|
||||
|
||||
def _service(request: Request) -> ActuatorReconciliationService:
|
||||
@@ -120,3 +133,13 @@ def _service(request: Request) -> ActuatorReconciliationService:
|
||||
detail="Actuator-Reconciliation nicht initialisiert.",
|
||||
)
|
||||
return service
|
||||
|
||||
|
||||
def _behavior(request: Request) -> BehaviorEngine:
|
||||
engine = getattr(request.app.state, "behavior_engine", None)
|
||||
if not isinstance(engine, BehaviorEngine):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Verhaltenslernen ist nicht initialisiert.",
|
||||
)
|
||||
return engine
|
||||
|
||||
Reference in New Issue
Block a user