main: HeatingRule auf heizungsrelevante Sensoren begrenzen
This commit is contained in:
@@ -7,9 +7,28 @@ from app.rules.recommender import Rule
|
|||||||
|
|
||||||
|
|
||||||
class HeatingRule(Rule):
|
class HeatingRule(Rule):
|
||||||
|
"""Heizungsregel: Nur auf heizungsrelevante Entitäten reagieren.
|
||||||
|
|
||||||
|
Triggert bei:
|
||||||
|
- `climate`-Entitäten direkt
|
||||||
|
- `sensor` mit `device_class` in {temperature, humidity}
|
||||||
|
- `binary_sensor` mit `device_class` in {occupancy, presence}
|
||||||
|
|
||||||
|
Alle anderen Domains/Device-Klassen bleiben ohne Effekt.
|
||||||
|
"""
|
||||||
|
|
||||||
|
HEATING_SENSOR_CLASSES: frozenset[str] = frozenset({"temperature", "humidity"})
|
||||||
|
HEATING_PRESENCE_CLASSES: frozenset[str] = frozenset({"occupancy", "presence"})
|
||||||
|
|
||||||
def matches(self, entities: Sequence[HaEntitySummary]) -> bool:
|
def matches(self, entities: Sequence[HaEntitySummary]) -> bool:
|
||||||
domains = {item.domain for item in entities}
|
for item in entities:
|
||||||
return "climate" in domains or "sensor" in domains
|
if item.domain == "climate":
|
||||||
|
return True
|
||||||
|
if item.domain == "sensor" and item.device_class in self.HEATING_SENSOR_CLASSES:
|
||||||
|
return True
|
||||||
|
if item.domain == "binary_sensor" and item.device_class in self.HEATING_PRESENCE_CLASSES:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def recommendation(self, entities: Sequence[HaEntitySummary]) -> str:
|
def recommendation(self, entities: Sequence[HaEntitySummary]) -> str:
|
||||||
return "Prüfe Heizungsregelung: Aktiviere energiesparenden Modus bei Abwesenheit."
|
return "Prüfe Heizungsregelung: Aktiviere energiesparenden Modus bei Abwesenheit."
|
||||||
@@ -1,26 +1,64 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from app.ha.models import HaEntitySummary
|
from app.ha.models import HaEntitySummary
|
||||||
from app.rules.heating import HeatingRule
|
from app.rules.heating import HeatingRule
|
||||||
from app.rules.recommender import Recommender
|
|
||||||
|
|
||||||
|
|
||||||
def _sensor(entity_id: str) -> HaEntitySummary:
|
def _entity(entity_id: str, domain: str, device_class: str | None = None) -> HaEntitySummary:
|
||||||
return HaEntitySummary(entity_id=entity_id, domain="sensor")
|
return HaEntitySummary(entity_id=entity_id, domain=domain, device_class=device_class)
|
||||||
|
|
||||||
|
|
||||||
def _climate(entity_id: str) -> HaEntitySummary:
|
# --- positive cases --------------------------------------------------------
|
||||||
return HaEntitySummary(entity_id=entity_id, domain="climate")
|
@pytest.mark.parametrize(
|
||||||
|
"entity",
|
||||||
|
[
|
||||||
def test_heating_rule_triggers() -> None:
|
_entity("climate.living_room", "climate"),
|
||||||
|
_entity("sensor.temperature_living", "sensor", "temperature"),
|
||||||
|
_entity("sensor.humidity_bathroom", "sensor", "humidity"),
|
||||||
|
_entity("binary_sensor.living_room_occupancy", "binary_sensor", "occupancy"),
|
||||||
|
_entity("binary_sensor.entrance_presence", "binary_sensor", "presence"),
|
||||||
|
],
|
||||||
|
ids=lambda e: e.entity_id,
|
||||||
|
)
|
||||||
|
def test_heating_rule_triggers_for_relevant_entities(entity: HaEntitySummary) -> None:
|
||||||
rule = HeatingRule()
|
rule = HeatingRule()
|
||||||
assert rule.matches([_climate("climate.living_room")])
|
assert rule.matches([entity]) is True
|
||||||
assert rule.matches([_sensor("sensor.temperature_living")])
|
|
||||||
|
|
||||||
|
|
||||||
def test_recommender_uses_rule() -> None:
|
# --- negative cases -------------------------------------------------------
|
||||||
recommender = Recommender(rules=[HeatingRule()])
|
@pytest.mark.parametrize(
|
||||||
assert recommender.run([_climate("climate.living_room")]) == [
|
"entity",
|
||||||
"Prüfe Heizungsregelung: Aktiviere energiesparenden Modus bei Abwesenheit."
|
[
|
||||||
]
|
_entity("sensor.power_consumption", "sensor", "power"),
|
||||||
|
_entity("sensor.door", "sensor", "door"),
|
||||||
|
_entity("sensor.energy", "sensor", "energy"),
|
||||||
|
_entity("binary_sensor.door_window", "binary_sensor", "door"),
|
||||||
|
_entity("binary_sensor.motion", "binary_sensor", "motion"),
|
||||||
|
_entity("light.living_room", "light"),
|
||||||
|
_entity("switch.plug", "switch"),
|
||||||
|
_entity("sensor.some_random", "sensor"),
|
||||||
|
_entity("binary_sensor.some_binary", "binary_sensor"),
|
||||||
|
],
|
||||||
|
ids=lambda e: e.entity_id,
|
||||||
|
)
|
||||||
|
def test_heating_rule_ignores_non_heating_entities(entity: HaEntitySummary) -> None:
|
||||||
|
rule = HeatingRule()
|
||||||
|
assert rule.matches([entity]) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_heating_rule_mixed_list_returns_true() -> None:
|
||||||
|
rule = HeatingRule()
|
||||||
|
entities = [
|
||||||
|
_entity("sensor.power", "sensor", "power"),
|
||||||
|
_entity("climate.living_room", "climate"),
|
||||||
|
_entity("light.ceiling", "light"),
|
||||||
|
]
|
||||||
|
assert rule.matches(entities) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_heating_rule_recommendation_is_stable() -> None:
|
||||||
|
rule = HeatingRule()
|
||||||
|
expected = "Prüfe Heizungsregelung: Aktiviere energiesparenden Modus bei Abwesenheit."
|
||||||
|
assert rule.recommendation([_entity("climate.living_room", "climate")]) == expected
|
||||||
Reference in New Issue
Block a user