Co-authored-by: Diogo Gomes <diogogomes@gmail.com>
This commit is contained in:
parent
3884b4b6bf
commit
768b83139f
4 changed files with 156 additions and 0 deletions
|
@ -34,6 +34,7 @@ from homeassistant.helpers.template import result_as_boolean
|
|||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import DOMAIN, PLATFORMS
|
||||
from .repairs import raise_mirrored_entries
|
||||
|
||||
ATTR_OBSERVATIONS = "observations"
|
||||
ATTR_OCCURRED_OBSERVATION_ENTITIES = "occurred_observation_entities"
|
||||
|
@ -245,6 +246,22 @@ class BayesianBinarySensor(BinarySensorEntity):
|
|||
self.probability = self._calculate_new_probability()
|
||||
self._attr_is_on = bool(self.probability >= self._probability_threshold)
|
||||
|
||||
# detect mirrored entries
|
||||
for entity, observations in self.observations_by_entity.items():
|
||||
raise_mirrored_entries(
|
||||
self.hass, observations, text=f"{self._attr_name}/{entity}"
|
||||
)
|
||||
|
||||
all_template_observations = []
|
||||
for value in self.observations_by_template.values():
|
||||
all_template_observations.append(value[0])
|
||||
if len(all_template_observations) == 2:
|
||||
raise_mirrored_entries(
|
||||
self.hass,
|
||||
all_template_observations,
|
||||
text=f"{self._attr_name}/{all_template_observations[0]['value_template']}",
|
||||
)
|
||||
|
||||
@callback
|
||||
def _recalculate_and_write_state(self):
|
||||
self.probability = self._calculate_new_probability()
|
||||
|
|
39
homeassistant/components/bayesian/repairs.py
Normal file
39
homeassistant/components/bayesian/repairs.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
"""Helpers for generating repairs."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry
|
||||
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
def raise_mirrored_entries(hass: HomeAssistant, observations, text: str = "") -> None:
|
||||
"""If there are mirrored entries, the user is probably using a workaround for a patched bug."""
|
||||
if len(observations) != 2:
|
||||
return
|
||||
true_sums_1: bool = (
|
||||
round(
|
||||
observations[0]["prob_given_true"] + observations[1]["prob_given_true"], 1
|
||||
)
|
||||
== 1.0
|
||||
)
|
||||
false_sums_1: bool = (
|
||||
round(
|
||||
observations[0]["prob_given_false"] + observations[1]["prob_given_false"], 1
|
||||
)
|
||||
== 1.0
|
||||
)
|
||||
same_states: bool = observations[0]["platform"] == observations[1]["platform"]
|
||||
if true_sums_1 & false_sums_1 & same_states:
|
||||
issue_registry.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"mirrored_entry/" + text,
|
||||
breaks_in_ha_version="2022.10.0",
|
||||
is_fixable=False,
|
||||
is_persistent=False,
|
||||
severity=issue_registry.IssueSeverity.WARNING,
|
||||
translation_key="manual_migration",
|
||||
translation_placeholders={"entity": text},
|
||||
learn_more_url="https://github.com/home-assistant/core/pull/67631",
|
||||
)
|
8
homeassistant/components/bayesian/translations/en.json
Normal file
8
homeassistant/components/bayesian/translations/en.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"issues": {
|
||||
"manual_migration": {
|
||||
"description": "The Bayesian integration now also updates the probability if the observed `to_state`, `above`, `below`, or `value_template` evaluates to `False` rather than only `True`. So it is no longer required to have duplicate, complementary entries for each binary state. Please remove the mirrored entry for `{entity}`.",
|
||||
"title": "Manual YAML fix required for Bayesian"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ from homeassistant.const import (
|
|||
)
|
||||
from homeassistant.core import Context, callback
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.helpers.issue_registry import async_get
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import get_fixture_path
|
||||
|
@ -184,6 +185,8 @@ async def test_sensor_numeric_state(hass):
|
|||
|
||||
assert state.state == "off"
|
||||
|
||||
assert len(async_get(hass).issues) == 0
|
||||
|
||||
|
||||
async def test_sensor_state(hass):
|
||||
"""Test sensor on state platform observations."""
|
||||
|
@ -341,6 +344,7 @@ async def test_threshold(hass):
|
|||
assert round(abs(1.0 - state.attributes.get("probability")), 7) == 0
|
||||
|
||||
assert state.state == "on"
|
||||
assert len(async_get(hass).issues) == 0
|
||||
|
||||
|
||||
async def test_multiple_observations(hass):
|
||||
|
@ -495,6 +499,94 @@ async def test_multiple_numeric_observations(hass):
|
|||
assert state.attributes.get("observations")[1]["platform"] == "numeric_state"
|
||||
|
||||
|
||||
async def test_mirrored_observations(hass):
|
||||
"""Test whether mirrored entries are detected and appropriate issues are created."""
|
||||
|
||||
config = {
|
||||
"binary_sensor": {
|
||||
"platform": "bayesian",
|
||||
"name": "Test_Binary",
|
||||
"observations": [
|
||||
{
|
||||
"platform": "state",
|
||||
"entity_id": "binary_sensor.test_monitored",
|
||||
"to_state": "on",
|
||||
"prob_given_true": 0.8,
|
||||
"prob_given_false": 0.4,
|
||||
},
|
||||
{
|
||||
"platform": "state",
|
||||
"entity_id": "binary_sensor.test_monitored",
|
||||
"to_state": "off",
|
||||
"prob_given_true": 0.2,
|
||||
"prob_given_false": 0.59,
|
||||
},
|
||||
{
|
||||
"platform": "numeric_state",
|
||||
"entity_id": "sensor.test_monitored1",
|
||||
"above": 5,
|
||||
"prob_given_true": 0.7,
|
||||
"prob_given_false": 0.4,
|
||||
},
|
||||
{
|
||||
"platform": "numeric_state",
|
||||
"entity_id": "sensor.test_monitored1",
|
||||
"below": 5,
|
||||
"prob_given_true": 0.3,
|
||||
"prob_given_false": 0.6,
|
||||
},
|
||||
{
|
||||
"platform": "template",
|
||||
"value_template": "{{states('sensor.test_monitored2') == 'off'}}",
|
||||
"prob_given_true": 0.79,
|
||||
"prob_given_false": 0.4,
|
||||
},
|
||||
{
|
||||
"platform": "template",
|
||||
"value_template": "{{states('sensor.test_monitored2') == 'on'}}",
|
||||
"prob_given_true": 0.2,
|
||||
"prob_given_false": 0.6,
|
||||
},
|
||||
{
|
||||
"platform": "state",
|
||||
"entity_id": "sensor.colour",
|
||||
"to_state": "blue",
|
||||
"prob_given_true": 0.33,
|
||||
"prob_given_false": 0.8,
|
||||
},
|
||||
{
|
||||
"platform": "state",
|
||||
"entity_id": "sensor.colour",
|
||||
"to_state": "green",
|
||||
"prob_given_true": 0.3,
|
||||
"prob_given_false": 0.15,
|
||||
},
|
||||
{
|
||||
"platform": "state",
|
||||
"entity_id": "sensor.colour",
|
||||
"to_state": "red",
|
||||
"prob_given_true": 0.4,
|
||||
"prob_given_false": 0.05,
|
||||
},
|
||||
],
|
||||
"prior": 0.1,
|
||||
}
|
||||
}
|
||||
assert len(async_get(hass).issues) == 0
|
||||
assert await async_setup_component(hass, "binary_sensor", config)
|
||||
await hass.async_block_till_done()
|
||||
hass.states.async_set("sensor.test_monitored2", "on")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(async_get(hass).issues) == 3
|
||||
assert (
|
||||
async_get(hass).issues[
|
||||
("bayesian", "mirrored_entry/Test_Binary/sensor.test_monitored1")
|
||||
]
|
||||
is not None
|
||||
)
|
||||
|
||||
|
||||
async def test_probability_updates(hass):
|
||||
"""Test probability update function."""
|
||||
prob_given_true = [0.3, 0.6, 0.8]
|
||||
|
|
Loading…
Add table
Reference in a new issue