185 files
This commit is contained in:
1 parent
5f9330abd7
commit
a718af6423
185 files changed
+16868
-3088
No files matched your search
@@ -102,6 +102,11 @@ def register_action_listener(hass: HomeAssistant) -> Callable[[], None]:
|
||||
task_id = event.data.get("task_id")
|
||||
if not entry_id or not task_id:
|
||||
return
|
||||
# #133: a pure backfill records maintenance that happened long ago —
|
||||
# running the on_complete_action NOW (reset a device counter, toggle
|
||||
# a helper) would act on the live device for stale work.
|
||||
if event.data.get("backfill"):
|
||||
return
|
||||
action = _resolve_task_action(hass, entry_id, task_id)
|
||||
if action is None:
|
||||
return
|
||||
|
||||
@@ -35,13 +35,23 @@ def compute_status_from_task_dict(task: dict[str, Any]) -> str:
|
||||
# between refreshes.
|
||||
if task.get("_paused"):
|
||||
return MaintenanceStatus.PAUSED
|
||||
if task.get("_trigger_active", False):
|
||||
days = task.get("_days_until_due")
|
||||
|
||||
# "all" combinator: trigger AND elapsed safety interval must both be met
|
||||
# before the task actions (model twin: MaintenanceTask.status).
|
||||
all_mode = (task.get("trigger_config") or {}).get("trigger_combinator") == "all"
|
||||
time_met = days is None or days <= 0
|
||||
trigger_active = task.get("_trigger_active", False)
|
||||
|
||||
if trigger_active and (not all_mode or time_met):
|
||||
return MaintenanceStatus.TRIGGERED
|
||||
|
||||
days = task.get("_days_until_due")
|
||||
if days is None:
|
||||
return MaintenanceStatus.OK
|
||||
|
||||
if all_mode and not trigger_active:
|
||||
return MaintenanceStatus.OK
|
||||
|
||||
warning_days = task.get("warning_days", DEFAULT_WARNING_DAYS)
|
||||
if days < 0:
|
||||
return MaintenanceStatus.OVERDUE
|
||||
|
||||
@@ -14,6 +14,7 @@ every rule is individually testable.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any
|
||||
@@ -44,6 +45,30 @@ def _aggregate(per_entity: list[bool], entity_logic: str) -> bool:
|
||||
return all(per_entity) if entity_logic == "all" else any(per_entity)
|
||||
|
||||
|
||||
def threshold_exceeds(
|
||||
value: float,
|
||||
*,
|
||||
above: float | None,
|
||||
below: float | None,
|
||||
equals: float | None = None,
|
||||
not_equals: float | None = None,
|
||||
) -> bool:
|
||||
"""Whether *value* violates any configured threshold limit.
|
||||
|
||||
The single predicate shared by the event-driven ThresholdTrigger and the
|
||||
refresh-time fallback below — keep both surfaces on this one rule.
|
||||
Equality uses ``math.isclose`` so float round-trips (state strings, JSON)
|
||||
can't miss a discrete level like 3.0.
|
||||
"""
|
||||
if above is not None and value > above:
|
||||
return True
|
||||
if below is not None and value < below:
|
||||
return True
|
||||
if equals is not None and math.isclose(value, equals, rel_tol=1e-9, abs_tol=1e-9):
|
||||
return True
|
||||
return not_equals is not None and not math.isclose(value, not_equals, rel_tol=1e-9, abs_tol=1e-9)
|
||||
|
||||
|
||||
def _numeric_entity_value(get_state: StateGetter, entity_id: str, attribute: str | None) -> float | None:
|
||||
"""Read a numeric value from an entity state/attribute (None when unusable)."""
|
||||
state = get_state(entity_id)
|
||||
@@ -69,6 +94,8 @@ def evaluate_threshold(
|
||||
for_minutes = trigger_config.get("trigger_for_minutes", 0)
|
||||
above = trigger_config.get("trigger_above")
|
||||
below = trigger_config.get("trigger_below")
|
||||
equals = trigger_config.get("trigger_equals")
|
||||
not_equals = trigger_config.get("trigger_not_equals")
|
||||
|
||||
per_entity: list[bool] = []
|
||||
last_value: float | None = None
|
||||
@@ -78,8 +105,7 @@ def evaluate_threshold(
|
||||
per_entity.append(False)
|
||||
continue
|
||||
last_value = value
|
||||
exceeds = (above is not None and value > above) or (below is not None and value < below)
|
||||
per_entity.append(exceeds)
|
||||
per_entity.append(threshold_exceeds(value, above=above, below=below, equals=equals, not_equals=not_equals))
|
||||
|
||||
aggregated = _aggregate(per_entity, entity_logic) if per_entity else False
|
||||
|
||||
|
||||
Reference in new issue
Block a user