BEHAVIOR-001: learn and predict actuator actions

This commit is contained in:
2026-06-14 10:37:59 +02:00
parent 6305f52cd2
commit fa250216be
34 changed files with 1614 additions and 489 deletions

View File

@@ -5,7 +5,6 @@ from pathlib import Path
from app.actuators.lifecycle import ActuatorReconciliationService
from app.actuators.models import (
AssignmentSource,
LifecycleStatus,
ManualOverride,
model_id_for_actuator,
@@ -142,7 +141,7 @@ def test_reconciliation_auto_assigns_and_trains_numeric_model(tmp_path: Path) ->
assert "binary_sensor.abstellkammer_motion" not in artifact.supported_sensors
def test_reconciliation_requires_review_for_ambiguous_sensor_mapping(tmp_path: Path) -> None:
def test_reconciliation_uses_best_automatic_mapping_when_ambiguous(tmp_path: Path) -> None:
start = datetime(2026, 6, 1, tzinfo=timezone.utc)
entities = [
HaEntitySummary(
@@ -182,10 +181,11 @@ def test_reconciliation_requires_review_for_ambiguous_sensor_mapping(tmp_path: P
record = service.configure_actuator("switch.garage_pump")
assert record.assignment.review_required is True
assert record.lifecycle.status is LifecycleStatus.REVIEW_REQUIRED
assert record.assignment.selected_numeric_entity_id == "sensor.garage_energy"
assert record.lifecycle.status is LifecycleStatus.TRAINED
def test_manual_override_persists_and_wins_after_restart(tmp_path: Path) -> None:
def test_legacy_manual_override_is_cleared_and_automatic_mapping_wins(tmp_path: Path) -> None:
start = datetime(2026, 6, 1, tzinfo=timezone.utc)
entities = [
HaEntitySummary(
@@ -218,21 +218,21 @@ def test_manual_override_persists_and_wins_after_restart(tmp_path: Path) -> None
"sensor.abstellkammer_power": _points(8, start, 30.0),
}
service = _service(tmp_path, entities, history)
service.configure_actuator("light.abstellkammer")
updated = service.set_override(
"light.abstellkammer",
ManualOverride(
numeric_entity_id="sensor.abstellkammer_power",
context_entity_ids=[],
note="Manuelle Leistungs-Zuordnung",
),
configured = service.configure_actuator("light.abstellkammer")
legacy = configured.model_copy(
update={
"manual_override": ManualOverride(
numeric_entity_id="sensor.abstellkammer_power",
context_entity_ids=[],
note="Alte manuelle Zuordnung",
)
}
)
service._store.upsert(legacy)
restarted = _service(tmp_path, entities, history)
record = restarted.reconcile_actuator("light.abstellkammer")
assert updated.assignment.source is AssignmentSource.MANUAL
assert record.assignment.selected_numeric_entity_id == "sensor.abstellkammer_power"
assert record.manual_override is not None
assert record.manual_override.numeric_entity_id == "sensor.abstellkammer_power"
assert record.assignment.selected_numeric_entity_id == "sensor.abstellkammer_illuminance"
assert record.assignment.source.value == "automatic"
assert record.manual_override is None