From 9eaf8bd21bb0e1f29f88511e0d9174a9a806c833 Mon Sep 17 00:00:00 2001
From: Thomas Dietrich <Thomas@Nurzen.de>
Date: Mon, 8 Nov 2021 23:26:00 +0100
Subject: [PATCH] Fix statistics precision handling (#59202)

* Fix statistics precision error when configured 0, fix #42547

* Add tests for statistics precision

* Apply precision=0 logic to float numbers only

* Implement contextlib way of exception handling
---
 homeassistant/components/statistics/sensor.py |  8 +-
 tests/components/statistics/test_sensor.py    | 76 ++++++++++++++++++-
 2 files changed, 79 insertions(+), 5 deletions(-)

diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py
index 3d5857e2b03..7b2951a1ab6 100644
--- a/homeassistant/components/statistics/sensor.py
+++ b/homeassistant/components/statistics/sensor.py
@@ -1,5 +1,6 @@
 """Support for statistics for sensor values."""
 from collections import deque
+import contextlib
 import logging
 import statistics
 
@@ -203,7 +204,12 @@ class StatisticsSensor(SensorEntity):
     @property
     def native_value(self):
         """Return the state of the sensor."""
-        return self.mean if not self.is_binary else self.count
+        if self.is_binary:
+            return self.count
+        if self._precision == 0:
+            with contextlib.suppress(TypeError, ValueError):
+                return int(self.mean)
+        return self.mean
 
     @property
     def native_unit_of_measurement(self):
diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py
index a2eaa9add03..c3b14ba360d 100644
--- a/tests/components/statistics/test_sensor.py
+++ b/tests/components/statistics/test_sensor.py
@@ -103,7 +103,9 @@ class TestStatisticsSensor(unittest.TestCase):
 
         for value in self.values:
             self.hass.states.set(
-                "sensor.test_monitored", value, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}
+                "sensor.test_monitored",
+                value,
+                {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
             )
             self.hass.block_till_done()
 
@@ -163,7 +165,9 @@ class TestStatisticsSensor(unittest.TestCase):
 
         for value in self.values:
             self.hass.states.set(
-                "sensor.test_monitored", value, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}
+                "sensor.test_monitored",
+                value,
+                {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
             )
             self.hass.block_till_done()
 
@@ -193,7 +197,9 @@ class TestStatisticsSensor(unittest.TestCase):
 
         for value in self.values[-3:]:  # just the last 3 will do
             self.hass.states.set(
-                "sensor.test_monitored", value, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}
+                "sensor.test_monitored",
+                value,
+                {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
             )
             self.hass.block_till_done()
 
@@ -363,6 +369,66 @@ class TestStatisticsSensor(unittest.TestCase):
         ) == state.attributes.get("max_age")
         assert self.change_rate == state.attributes.get("change_rate")
 
+    def test_precision_0(self):
+        """Test correct result with precision=0 as integer."""
+        assert setup_component(
+            self.hass,
+            "sensor",
+            {
+                "sensor": {
+                    "platform": "statistics",
+                    "name": "test",
+                    "entity_id": "sensor.test_monitored",
+                    "precision": 0,
+                }
+            },
+        )
+
+        self.hass.block_till_done()
+        self.hass.start()
+        self.hass.block_till_done()
+
+        for value in self.values:
+            self.hass.states.set(
+                "sensor.test_monitored",
+                value,
+                {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
+            )
+            self.hass.block_till_done()
+
+        state = self.hass.states.get("sensor.test")
+        assert state.state == str(int(state.attributes.get("mean")))
+
+    def test_precision_1(self):
+        """Test correct result with precision=1 rounded to one decimal."""
+        assert setup_component(
+            self.hass,
+            "sensor",
+            {
+                "sensor": {
+                    "platform": "statistics",
+                    "name": "test",
+                    "entity_id": "sensor.test_monitored",
+                    "precision": 1,
+                }
+            },
+        )
+
+        self.hass.block_till_done()
+        self.hass.start()
+        self.hass.block_till_done()
+
+        for value in self.values:
+            self.hass.states.set(
+                "sensor.test_monitored",
+                value,
+                {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
+            )
+            self.hass.block_till_done()
+
+        state = self.hass.states.get("sensor.test")
+        assert state.state == str(round(sum(self.values) / len(self.values), 1))
+
     def test_initialize_from_database(self):
         """Test initializing the statistics from the database."""
         # enable the recorder
@@ -372,7 +438,9 @@ class TestStatisticsSensor(unittest.TestCase):
         # store some values
         for value in self.values:
             self.hass.states.set(
-                "sensor.test_monitored", value, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}
+                "sensor.test_monitored",
+                value,
+                {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
             )
             self.hass.block_till_done()
         # wait for the recorder to really store the data