Skip to content

Commit 7b5fcdb

Browse files
chalfontchubbyclaudespringfall2008
authored
fix(web): use configured currency minor unit in Rates chart legend (#4307)
* fix(web): use configured currency minor unit in Rates chart legend The Rates chart's "Hourly"/"Today" series legends were hardcoded to "p/kWh" (pence) regardless of the configured currency_symbols, so non-GBP users (e.g. NZD, configured as "$c") saw "p/kWh" in the chart legend even though the surrounding chart title/axis already correctly used self.currency_symbols[1]. Fixes #4153. Added a test verifying the series-name formatting follows currency_symbols for £p/$c/€c - get_chart() itself requires a fully computed plan before reaching this branch, so the test mirrors the formatting logic directly rather than standing up that heavier machinery (matching this file's existing "Loading..." early-return gate and the lack of any content assertions in test_web_if.py's integration tests). 🤖 Implemented with Claude Code, disclosed per repo convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(web): address Copilot review feedback on the Rates chart currency test Two issues flagged on PR #4288: - The test mutated my_predbat.currency_symbols without restoring it, leaking state into later tests in the same suite process (the shared my_predbat fixture is reused across all tests). - The test never actually called WebInterface.get_chart()/web.py - it only re-tested the same inline string-formatting logic, so it would still pass even if web.py regressed back to a hardcoded "p/kWh". Now builds a minimal WebInterface (bypassing ComponentBase.__init__, which would stand up the real aiohttp app) and calls the real get_chart("Rates"), stubbing get_history_wrapper() directly to avoid reproducing realistic HA history formatting for data not under test. Verified this actually catches a regression: temporarily reintroducing the hardcoded "p/kWh" strings in web.py makes the test fail as expected. currency_symbols/dashboard_values are now saved and restored via try/finally. 🤖 Implemented with Claude Code, disclosed per repo convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(web): use my_predbat.prefix instead of hardcoded entity IDs Copilot review on #4307: the test stubbed dashboard_values using hardcoded "predbat.*" entity IDs, so it would silently pass if the test harness prefix ever differed from get_chart()'s own self.prefix lookup. Build the keys from my_predbat.prefix instead, matching how get_chart() actually resolves them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Trefor Southwell <48591903+springfall2008@users.noreply.github.com>
1 parent b6d3734 commit 7b5fcdb

3 files changed

Lines changed: 94 additions & 2 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# -----------------------------------------------------------------------------
2+
# Predbat Home Battery System
3+
# Copyright Trefor Southwell 2026 - All Rights Reserved
4+
# This application maybe used for personal use only and not for commercial use
5+
# -----------------------------------------------------------------------------
6+
# fmt off
7+
# pylint: disable=consider-using-f-string
8+
# pylint: disable=line-too-long
9+
# pylint: disable=attribute-defined-outside-init
10+
11+
"""
12+
Test that the Rates chart's per-series legend names (WebInterface.get_chart, web.py) use the
13+
configured currency's minor unit rather than a hardcoded "p/kWh" - see issue #4153, where a
14+
user configured for NZ dollars/cents saw "p/kWh" (pence) in the chart legend regardless.
15+
"""
16+
17+
import re
18+
19+
from web import WebInterface
20+
21+
22+
def _make_web(my_predbat):
23+
"""
24+
Build a minimal WebInterface bound to my_predbat, bypassing ComponentBase.__init__ (which
25+
would stand up the real aiohttp app). currency_symbols/now_utc/minutes_now/etc are all
26+
read-only properties on ComponentBase that delegate to self.base, so nothing else needs
27+
setting here for get_chart() to run.
28+
"""
29+
w = WebInterface.__new__(WebInterface)
30+
w.base = my_predbat
31+
w.log = my_predbat.log
32+
w.prefix = my_predbat.prefix
33+
return w
34+
35+
36+
def test_rates_chart_series_names_use_currency_symbol(my_predbat):
37+
"""
38+
Calls the real WebInterface.get_chart("Rates") and checks the rendered series names,
39+
rather than re-testing the formatting logic in isolation - a prior version of this test
40+
only duplicated the format string inline, so it would still pass even if web.py regressed
41+
back to a hardcoded "p/kWh" (Copilot review on PR #4288).
42+
43+
get_chart() gates on soc_kw_best being populated (dashboard_values) and reads the Hourly/
44+
Today series from get_history_wrapper() - both are stubbed directly rather than trying to
45+
reproduce realistic HA history formatting, since only the currency-dependent series *names*
46+
are under test here, not the underlying rate data.
47+
"""
48+
print("**** test_rates_chart_series_names_use_currency_symbol ****")
49+
50+
original_currency_symbols = my_predbat.currency_symbols
51+
original_dashboard_values = getattr(my_predbat, "dashboard_values", None)
52+
53+
try:
54+
w = _make_web(my_predbat)
55+
my_predbat.dashboard_values = {
56+
my_predbat.prefix + ".soc_kw_best": {"attributes": {"results": {"2026-01-01T00:00:00+00:00": 5.0}}},
57+
my_predbat.prefix + ".rates": {"attributes": {"results": {"2026-01-01T00:00:00+00:00": 10.0}}},
58+
}
59+
fake_history = [
60+
[
61+
{"state": 10.5, "last_updated": "2026-01-01T00:00:00+00:00"},
62+
{"state": 12.3, "last_updated": "2026-01-01T00:30:00+00:00"},
63+
]
64+
]
65+
w.get_history_wrapper = lambda *a, **kw: fake_history
66+
67+
for currency_symbols, expected_minor in [("£p", "p"), ("$c", "c"), ("€c", "c")]:
68+
my_predbat.currency_symbols = currency_symbols
69+
70+
result = w.get_chart("Rates")
71+
series_names = re.findall(r"name: '([^']*)'", result)
72+
73+
expected_hourly = "Hourly {}/kWh".format(expected_minor)
74+
expected_today = "Today {}/kWh".format(expected_minor)
75+
76+
assert expected_hourly in series_names, f"Expected series '{expected_hourly}' in {series_names} for currency_symbols={currency_symbols}"
77+
assert expected_today in series_names, f"Expected series '{expected_today}' in {series_names} for currency_symbols={currency_symbols}"
78+
if expected_minor != "p":
79+
assert not any("p/kWh" in name for name in series_names), f"Series names should not be hardcoded to pence for currency_symbols={currency_symbols}, got {series_names}"
80+
81+
print("✓ Rates chart series names correctly follow currency_symbols[1] (£p, $c, €c all verified against the real get_chart() output)")
82+
print("✓ Test passed")
83+
return False
84+
finally:
85+
my_predbat.currency_symbols = original_currency_symbols
86+
if original_dashboard_values is None:
87+
if hasattr(my_predbat, "dashboard_values"):
88+
del my_predbat.dashboard_values
89+
else:
90+
my_predbat.dashboard_values = original_dashboard_values

apps/predbat/unit_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
from tests.test_hainterface_lifecycle import run_hainterface_lifecycle_tests
6767
from tests.test_hainterface_websocket import run_hainterface_websocket_tests
6868
from tests.test_web_if import run_test_web_if
69+
from tests.test_web_chart_currency import test_rates_chart_series_names_use_currency_symbol
6970
from tests.test_metrics_dashboard_soc_refresh import test_soc_chart_center_text_reads_live_data
7071
from tests.test_web_functions import run_web_functions_tests
7172
from tests.test_web_history_table import run_web_history_table_tests
@@ -283,6 +284,7 @@ def main():
283284
("manual_times", run_test_manual_times, "Manual times tests", False),
284285
("manual_select", run_test_manual_select, "Manual select tests", False),
285286
("web_if", run_test_web_if, "Web interface tests", False),
287+
("web_chart_currency", test_rates_chart_series_names_use_currency_symbol, "Rates chart series names follow currency_symbols tests", False),
286288
("metrics_dashboard_soc_refresh", test_soc_chart_center_text_reads_live_data, "Metrics dashboard SoC chart live-refresh tests", False),
287289
("web_functions", run_web_functions_tests, "Web function unit tests", False),
288290
("web_history_table", run_web_history_table_tests, "Web /entity history table bucketing tests", False),

apps/predbat/web.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2964,8 +2964,8 @@ def get_chart(self, chart):
29642964
{"name": "Import", "data": rates, "opacity": "1.0", "stroke_width": "3", "stroke_curve": "stepline"},
29652965
{"name": "Export", "data": rates_export, "opacity": "0.2", "stroke_width": "2", "stroke_curve": "stepline", "chart_type": "area"},
29662966
{"name": "Gas", "data": rates_gas, "opacity": "0.2", "stroke_width": "2", "stroke_curve": "stepline", "chart_type": "area"},
2967-
{"name": "Hourly p/kWh", "data": cost_pkwh_hour, "opacity": "1.0", "stroke_width": "2", "stroke_curve": "stepline"},
2968-
{"name": "Today p/kWh", "data": cost_pkwh_today, "opacity": "1.0", "stroke_width": "2", "stroke_curve": "stepline"},
2967+
{"name": "Hourly {}/kWh".format(self.currency_symbols[1]), "data": cost_pkwh_hour, "opacity": "1.0", "stroke_width": "2", "stroke_curve": "stepline"},
2968+
{"name": "Today {}/kWh".format(self.currency_symbols[1]), "data": cost_pkwh_today, "opacity": "1.0", "stroke_width": "2", "stroke_curve": "stepline"},
29692969
]
29702970
text += self.render_chart(series_data, self.currency_symbols[1], "Energy Rates", now_str)
29712971
elif chart == "InDay":

0 commit comments

Comments
 (0)