diff --git a/apps/predbat/annual.py b/apps/predbat/annual.py index 8dbdce9f9..ffdae3fbd 100644 --- a/apps/predbat/annual.py +++ b/apps/predbat/annual.py @@ -985,6 +985,9 @@ def _billed_result(predbat, end_record, pv_step): "export_kwh": export_kwh, "pv_generated_kwh": pv_generated, "battery_throughput_kwh": battery_cycle, + # One full cycle is a full discharge plus a full charge, so throughput is + # divided by 2 * usable capacity to count equivalent full cycles. + "battery_cycles": round((battery_cycle / (2 * predbat.soc_max)) if predbat.soc_max else 0.0, 4), } @@ -1247,7 +1250,7 @@ def _run_scenarios(predbat, config, weather, tariff, load_source, day, midnight_ SCENARIO_KEYS = ["no_pvbat", "pv_only", "without_predbat", "with_predbat"] -SCENARIO_FIELDS = ["cost_p", "import_kwh", "export_kwh", "pv_generated_kwh", "battery_throughput_kwh"] +SCENARIO_FIELDS = ["cost_p", "import_kwh", "export_kwh", "pv_generated_kwh", "battery_throughput_kwh", "battery_cycles"] def _blend_results(with_car, without_car, fraction): diff --git a/apps/predbat/tests/test_annual_cli.py b/apps/predbat/tests/test_annual_cli.py index 450814c80..855eadb18 100644 --- a/apps/predbat/tests/test_annual_cli.py +++ b/apps/predbat/tests/test_annual_cli.py @@ -63,10 +63,10 @@ async def run(self, progress=None): def sample_results(): """Return a small results document covering an ok month and an unavailable one.""" scenarios = { - "no_pvbat": {"cost_p": 12000.0, "import_kwh": 400.0, "export_kwh": 0.0, "pv_generated_kwh": 0.0, "battery_throughput_kwh": 0.0, "export_credit_p_estimate": 0.0}, - "pv_only": {"cost_p": 10000.0, "import_kwh": 350.0, "export_kwh": 60.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 0.0, "export_credit_p_estimate": 180.0}, - "without_predbat": {"cost_p": 8000.0, "import_kwh": 300.0, "export_kwh": 20.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 90.0, "export_credit_p_estimate": 300.0}, - "with_predbat": {"cost_p": 6000.0, "import_kwh": 280.0, "export_kwh": 45.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 140.0, "export_credit_p_estimate": 675.0}, + "no_pvbat": {"cost_p": 12000.0, "import_kwh": 400.0, "export_kwh": 0.0, "pv_generated_kwh": 0.0, "battery_throughput_kwh": 0.0, "battery_cycles": 0.0, "export_credit_p_estimate": 0.0}, + "pv_only": {"cost_p": 10000.0, "import_kwh": 350.0, "export_kwh": 60.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 0.0, "battery_cycles": 0.0, "export_credit_p_estimate": 180.0}, + "without_predbat": {"cost_p": 8000.0, "import_kwh": 300.0, "export_kwh": 20.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 90.0, "battery_cycles": 2.0, "export_credit_p_estimate": 300.0}, + "with_predbat": {"cost_p": 6000.0, "import_kwh": 280.0, "export_kwh": 45.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 140.0, "battery_cycles": 3.0, "export_credit_p_estimate": 675.0}, } return { "year": 2025, diff --git a/apps/predbat/tests/test_web_annual.py b/apps/predbat/tests/test_web_annual.py index 9f51789b2..0fbe9dfdb 100644 --- a/apps/predbat/tests/test_web_annual.py +++ b/apps/predbat/tests/test_web_annual.py @@ -1421,10 +1421,10 @@ def test_web_annual_store_failure_surfaces(my_predbat): def sample_run_results(): """Return a results document covering an ok, a degraded and an unavailable month.""" scenarios = { - "no_pvbat": {"cost_p": 18000.0, "import_kwh": 400.0, "export_kwh": 0.0, "pv_generated_kwh": 0.0, "battery_throughput_kwh": 0.0, "export_credit_p_estimate": 0.0}, - "pv_only": {"cost_p": 13000.0, "import_kwh": 340.0, "export_kwh": 70.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 0.0, "export_credit_p_estimate": 210.0}, - "without_predbat": {"cost_p": 9000.0, "import_kwh": 300.0, "export_kwh": 20.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 90.0, "export_credit_p_estimate": 300.0}, - "with_predbat": {"cost_p": 6600.0, "import_kwh": 280.0, "export_kwh": 145.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 140.0, "export_credit_p_estimate": 675.0}, + "no_pvbat": {"cost_p": 18000.0, "import_kwh": 400.0, "export_kwh": 0.0, "pv_generated_kwh": 0.0, "battery_throughput_kwh": 0.0, "battery_cycles": 0.0, "export_credit_p_estimate": 0.0}, + "pv_only": {"cost_p": 13000.0, "import_kwh": 340.0, "export_kwh": 70.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 0.0, "battery_cycles": 0.0, "export_credit_p_estimate": 210.0}, + "without_predbat": {"cost_p": 9000.0, "import_kwh": 300.0, "export_kwh": 20.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 90.0, "battery_cycles": 2.0, "export_credit_p_estimate": 300.0}, + "with_predbat": {"cost_p": 6600.0, "import_kwh": 280.0, "export_kwh": 145.0, "pv_generated_kwh": 120.0, "battery_throughput_kwh": 140.0, "battery_cycles": 3.0, "export_credit_p_estimate": 675.0}, } return { "year": 2025, @@ -1488,6 +1488,14 @@ def test_web_annual_results(my_predbat): print(" ERROR: the Predbat saving (2400p = £24.00) should be shown") failed = True + print("Test: the battery cycle metric appears in the payback table") + if "
| Scenario | Cost | Import | Export |
|---|---|---|---|
| {} | {} | {} kWh | {} kWh |
| {} | {} | {} kWh | {} kWh |
PV and battery save {} against no system.
\n".format(self._pounds(savings.get("pv_battery_vs_none_p", 0))) @@ -1365,7 +1370,8 @@ def _render_payback(self, results): text += "No PV or battery is configured, so there is nothing to price a payback for.
\n" return text - text += "| Option | Capital | Saving a year | Pays back in | ||||
|---|---|---|---|---|---|---|---|
| Option | Capital | Saving a year | Pays back in | Battery cycles |
|---|---|---|---|---|
| {} | £{:,.0f} | {} | {} | |
| {} | £{:,.0f} | {} | {} | {} |
Simple payback: capital divided by the modelled annual saving. It ignores panel degradation, price inflation, battery replacement and finance costs.
\n" + text += "Predbat uses your battery to store cheap off-peak electricity and then sell it during peak times, which will increase the number of battery cycles. This could reduce the lifespan of your batteries, so check your battery manufacturer's guidelines for battery lifespan.
\n" + return text def _render_chart(self, results): @@ -1442,12 +1471,12 @@ def _render_month_table(self, results): energy" rather than "this scenario was never modelled for this month". """ text = "| Month | Scenario | Cost | Import | Export | PV | Battery | |
|---|---|---|---|---|---|---|---|
| Month | Scenario | Cost | Import | Export | PV | Battery | Battery cycles |
| {} | unavailable — {} | ||||||
| {} | unavailable — {} | ||||||
| {}{} | {} | {} | {} kWh | {} kWh | {} kWh | {} kWh | |
| {}{} | {} | {} | {} kWh | {} kWh | {} kWh | {} kWh | {} |