diff --git a/docs/annual-prediction.md b/docs/annual-prediction.md index 0fceebc3c..449c1c615 100644 --- a/docs/annual-prediction.md +++ b/docs/annual-prediction.md @@ -1,184 +1,29 @@ # What If Annual Prediction -The annual prediction tool projects a year of household electricity costs using the -real Predbat planning engine. For each month it reports four scenarios: +"What would solar and a battery actually save me?" — this answers that, by projecting a +year of household electricity costs through the real Predbat planning engine rather than +a rule of thumb. For each month it reports four scenarios: 1. **No PV, no battery** — the counterfactual bill. 2. **PV only** — solar with no battery to store or arbitrage it. 3. **PV and battery, without Predbat** — a battery charging on a static cheap-rate timer. 4. **PV and battery, with Predbat** — the optimiser's plan. -It is a standalone command line tool; it does not need Home Assistant or any hardware, -and it makes no changes to a running Predbat installation. +Most people will use it through the **WhatIf** tab in the Predbat web interface, which is +what the next section covers. It also runs as a standalone command line tool for anyone +who wants to script it — see [Running it from the command +line](#advanced-running-it-from-the-command-line) at the end. -## How it works - -For each month the tool picks sample days by irradiance percentile (ranked by actual PV -energy, not what was forecast), so the answer does not swing on whether the sampled days -happened to be sunny. Two samples per month is the default; each represents half the -month. On a battery-only run (no solar arrays configured) there is nothing to rank by, so -days are instead spread evenly across the calendar. - -Each sampled day gets a 48-hour plan starting at midnight, but only the first 24 hours -are billed — the second day exists so the optimiser does not artificially drain the -battery at the horizon. Whatever charge is left at the end is valued, so a scenario -cannot look cheap by finishing empty. - -Predbat plans against the **archived weather forecast** for that date and is costed -against **ERA5 actuals**. This matters: costing against the same series it planned from -would hand Predbat perfect foresight and overstate its savings. - -Solar uncertainty (P10) is derived from measured forecast error — for each month, the -10th percentile of the actual-over-forecast daily energy ratio, taken across every day in -that month with both a usable actual and forecast reading. A month needs at least seven -such day pairs before its measured P10 ratio is trusted; below that it falls back to a -flat derate (0.7 by default, `pv10_derate_fallback`), and the run's caveats say so. - -## Configuration - -```yaml -annual: - location: - postcode: "SW1A 1AA" # or latitude/longitude - year: 2025 # defaults to the most recent complete calendar year - - solar: # omit for a battery-only run - - kwp: 5.6 - declination: 35 # pitch in degrees, default 35 - azimuth: 180 # 180 = south, default 180 - efficiency: 0.95 # default 0.95, must be greater than 0 and at most 1 - - battery: # omit for a PV-only run - size_kwh: 9.5 - inverter_kw: 5.0 - export_limit_kw: 5.0 # defaults to inverter_kw - hybrid: true # false = AC coupled - charge_rate_kw: 3.6 # defaults to inverter_kw - discharge_rate_kw: 3.6 # defaults to inverter_kw +Either way it needs neither Home Assistant nor a configured Predbat instance, and it +changes nothing about a running installation: it is the tool a prospective buyer reaches +for before they have installed anything, as much as one for an existing user. - load: - annual_kwh: 3800 - shape: flat # night | day | flat - car_charging_kwh: 2500 # annual, 0 to disable - car_rate_kw: 7.4 # charger power, default 7.4, must be greater than 0 - - tariff: - import_octopus_url: "https://api.octopus.energy/v1/products/AGILE-24-10-01/electricity-tariffs/E-1R-AGILE-24-10-01-{dno_region}/standard-unit-rates/" - export_octopus_url: "..." - dno_region: "A" # required when a URL contains {dno_region} - standing_charge_p_per_day: 60.0 - - samples_per_month: 2 - debug: false # keep each sampled day's plan, see Debugging a run -``` - -At least one of `solar` or `battery` must be given — with neither there is nothing to -evaluate. `annual.location` needs either `postcode` or both `latitude` and `longitude`; -if both are given, latitude/longitude wins. - -Octopus product codes are region-suffixed. If your tariff URL contains `{dno_region}` -you must also set `dno_region` to your region letter (`A` for Eastern England, and so -on) — the config is rejected up front with an error naming the offending field, rather -than left to 404 at fetch time and reported as an unavailable month, which would look -like an Octopus outage rather than a config mistake. - -Numeric fields are range-checked and a bad value is rejected with an explanatory message -rather than silently producing a nonsense result: `kwp`, `size_kwh`, `inverter_kw`, -`charge_rate_kw` and `discharge_rate_kw` must all be greater than zero; `efficiency` and -`pv10_derate_fallback` must be greater than zero and at most one; `samples_per_month` -must be a whole number of at least one; and `year` must be between 1940 (the start of -the Open-Meteo ERA5 archive) and the most recently completed calendar year - the current, -still-in-progress year is rejected, since Open-Meteo answers a mid-year request with -short but internally-consistent data that looks complete, and it would then be cached -permanently as if it were. - -Instead of `annual_kwh`, `shape` and `car_charging_kwh` you may supply real consumption: - -```yaml - load: - octopus: - api_key: !secret octopus_key - account_id: A-1234ABCD -``` - -These two forms are **mutually exclusive** and supplying both is rejected. The Octopus -consumption series already includes any EV charging, so accepting both would -double-count it. The trade-off is that a car baked into the meter data cannot be -smart-planned separately. A day missing from the downloaded consumption falls back to a -synthetic UK-average profile rather than being billed as zero. `car_rate_kw` has no -effect on an Octopus load and is ignored there — there is no separately-tracked car -energy to apply a charging rate to. - -**Only use the Octopus option if you do not already have solar or a battery.** An import -meter records what you *bought from the grid*, not what your home used. If you already -generate or store your own energy, that self-consumption and battery discharge have -already been subtracted from every reading — so the series is your residual grid demand, -not your household load. Feeding it in and then modelling a solar and battery system on -top applies the same saving twice: the tool would credit you for displacing import that -your existing system had already displaced, and overstate what a new system is worth. - -For a home that already has a system, use `annual_kwh` instead, and give your **total -household consumption** — generation included — rather than the figure on your bill. The -web form says the same, and says it more loudly when it can see that your Predbat -instance already has a battery or an array configured. - -`car_rate_kw` is the charger's power, used to size both the dumb timer's charge window -(scenarios 1 and 2, which share the same fixed timer) and the smart plan's charging rate -(scenario 3): a smaller number (say 3.0 for a granny charger) spreads the same energy over -a longer window, while a larger one (up to a three-phase charger's 22 kW) charges it -faster. It must be greater than zero. - -Instead of an Octopus URL you may give a fixed rate structure: - -```yaml - tariff: - rates_import: - - start: "00:30:00" - end: "05:30:00" - rate: 7.0 - - start: "05:30:00" - end: "00:30:00" - rate: 28.0 - rates_export: - - rate: 15.0 -``` - -### How car charging is spread across the year - -The car does **not** charge a little every day. Spreading an annual figure evenly across -365 days would make every top-up short enough to fit inside the cheapest overnight -window, so a dumb timer would price just as well as Predbat and the car would contribute -almost nothing to the measured saving. Real sessions are large enough to overflow a short -cheap band, and that overflow is where smart charging earns its money. - -The schedule is derived from `car_charging_kwh` and `car_rate_kw` rather than configured -directly: - -- One session a week, carrying the whole week's energy. -- If that session would run longer than **six hours** at the configured rate, the week's - energy is split across as many sessions as needed to bring each under six hours, up to - a maximum of one a day. - -Each sampled day is then planned **twice** — once carrying a full session, once with no -car — and the two results are blended by how often charging actually happens that week. -Blending each sampled day, rather than setting aside separate "car" and "no car" sample -days, keeps the irradiance stratification intact instead of tying charging state to how -sunny the sampled day happened to be. - -Two consequences worth knowing. A configuration with a car doubles the number of plan -runs, so it takes roughly twice as long. And if even one session a day cannot get under -six hours (a very low charge rate against very high mileage), the tool still uses seven -sessions but logs a warning: the sessions run long, so the overflow effect — and -therefore Predbat's advantage — is understated. - -## Using the web interface +## Using the WhatIf tab The Predbat web UI has a **WhatIf** tab alongside Dash, Plan, Entities, Charts and -Compare. Its pages are titled **What If Annual Prediction**. Unlike the rest of the UI it needs neither Home Assistant nor a configured -Predbat instance — it is the tool a prospective buyer reaches for before they have -installed anything, as well as the quick option for an existing user. +Compare. Its pages are titled **What If Annual Prediction**. -Annual is actually three pages, not one — Configure (`/annual`), Results +WhatIf is three pages, not one — Configure (`/annual`), Results (`/annual_view`) and Compare (`/annual_compare`) — sharing a tab strip with previous/next arrows across the top of all three: @@ -232,7 +77,7 @@ with the field named in the error, rather than left to fail as a 404 partway thr run. Choosing **Custom** reveals the URL fields, pre-filled with whichever tariff you were looking at, so hand-entering one starts from something rather than from nothing. -Every field the CLI's `annual.yaml` accepts has a form equivalent, including the +Every field the [configuration file](#advanced-the-configuration-file) accepts has a form equivalent, including the manual-usage/Octopus-consumption choice under **Load** and the year, sample count and P10 fallback derate under **Advanced**. **Save settings** stores the configuration without running it, so you can park a half-adjusted setup and come back to it; **Run simulations** @@ -262,8 +107,8 @@ follows the completion. ### The results view -The Results page mirrors the CLI's output: an annual totals table, the chart below, a -month-by-month breakdown and the run's caveats. One thing carries over unchanged from how +The Results page shows an annual totals table, the chart below it, a month-by-month +breakdown and the run's caveats. One thing carries over unchanged from how the engine reports it, and matters for reading the numbers correctly: a month with `status: unavailable` is left out of the chart and the totals — it is never drawn as a zero-cost bar or counted as a free month. @@ -307,27 +152,13 @@ the start of the row. Treat this table as a comparison aid for the same reason t payback figures it shows are one: it is a quick way to see which of your stored runs looks better, not a substitute for reading a run's own caveats before quoting it. -## Running it +## What the numbers mean -```bash -cd apps/predbat -python3 annual_cli.py --config annual.yaml --out results.json -``` +Everything below applies whether you are reading the Results page or the raw JSON — the +web pages are a view onto the same results document, and the field names are given so the +two line up. -Other options: `--work-dir` (default `./annual_work`) sets where the headless Predbat -instance and the download cache live, and `--quiet` suppresses only the per-month -progress lines written to stderr. Warnings - a P10 fallback, missing rate data, a failed -sample day, a car-charging shortfall - are never suppressed, `--quiet` or not: failures -stay visible. A human-readable table is always printed to stdout; `--out` additionally -writes the full results document as JSON. - -A run takes roughly one to three minutes with the default two samples per month: 24 plan -calculations (12 months × 2 samples) plus the weather, tariff and (if configured) -Octopus consumption downloads, which are cached between runs in `--work-dir`. - -## Reading the results - -Each month in the results JSON has a `status`: +Each month carries a `status`: - `ok` — every sampled day planned successfully. - `degraded` — some sampled days failed to plan or cost; the survivors are reweighted @@ -453,6 +284,205 @@ to trust the numbers — a P10 fallback, a missing month's rate data, the `export_credit_p_estimate` note above, and a summary of the payback caveat too — and is worth reading before quoting the totals. +## How it works + +For each month the tool picks sample days by irradiance percentile (ranked by actual PV +energy, not what was forecast), so the answer does not swing on whether the sampled days +happened to be sunny. Two samples per month is the default; each represents half the +month. On a battery-only run (no solar arrays configured) there is nothing to rank by, so +days are instead spread evenly across the calendar. + +Each sampled day gets a 48-hour plan starting at midnight, but only the first 24 hours +are billed — the second day exists so the optimiser does not artificially drain the +battery at the horizon. Whatever charge is left at the end is valued, so a scenario +cannot look cheap by finishing empty. + +Predbat plans against the **archived weather forecast** for that date and is costed +against **ERA5 actuals**. This matters: costing against the same series it planned from +would hand Predbat perfect foresight and overstate its savings. + +Solar uncertainty (P10) is derived from measured forecast error — for each month, the +10th percentile of the actual-over-forecast daily energy ratio, taken across every day in +that month with both a usable actual and forecast reading. A month needs at least seven +such day pairs before its measured P10 ratio is trusted; below that it falls back to a +flat derate (0.7 by default, `pv10_derate_fallback`), and the run's caveats say so. + +### How car charging is spread across the year + +The car does **not** charge a little every day. Spreading an annual figure evenly across +365 days would make every top-up short enough to fit inside the cheapest overnight +window, so a dumb timer would price just as well as Predbat and the car would contribute +almost nothing to the measured saving. Real sessions are large enough to overflow a short +cheap band, and that overflow is where smart charging earns its money. + +The schedule is derived from `car_charging_kwh` and `car_rate_kw` rather than configured +directly: + +- One session a week, carrying the whole week's energy. +- If that session would run longer than **six hours** at the configured rate, the week's + energy is split across as many sessions as needed to bring each under six hours, up to + a maximum of one a day. + +Each sampled day is then planned **twice** — once carrying a full session, once with no +car — and the two results are blended by how often charging actually happens that week. +Blending each sampled day, rather than setting aside separate "car" and "no car" sample +days, keeps the irradiance stratification intact instead of tying charging state to how +sunny the sampled day happened to be. + +Two consequences worth knowing. A configuration with a car doubles the number of plan +runs, so it takes roughly twice as long. And if even one session a day cannot get under +six hours (a very low charge rate against very high mileage), the tool still uses seven +sessions but logs a warning: the sessions run long, so the overflow effect — and +therefore Predbat's advantage — is understated. + +## Limitations + +- The Open-Meteo forecast archive only reaches back to about 2021. For earlier years the + tool plans against actuals instead and P10 uses the flat fallback derate, which it + states in the results' caveats — savings are likely overstated for those years. +- Self-consumption is not reported, for the reason described above. +- The forecast-versus-ERA5 gap includes systematic model bias as well as genuine + forecast error, so measured solar uncertainty is slightly overstated. +- A month with no rate data, no usable weather days, or where every sampled day failed to + plan is reported as `unavailable` and excluded from the annual total, rather than + counted as zero. +- Heat pump, iBoost and gas modelling are not included. + +## Advanced: running it from the command line + +Everything above is available from the web tab. The command line exists for scripting a +run, or for running one somewhere without a browser; it produces the same results +document. + +```bash +cd apps/predbat +python3 annual_cli.py --config annual.yaml --out results.json +``` + +Other options: `--work-dir` (default `./annual_work`) sets where the headless Predbat +instance and the download cache live, and `--quiet` suppresses only the per-month +progress lines written to stderr. Warnings - a P10 fallback, missing rate data, a failed +sample day, a car-charging shortfall - are never suppressed, `--quiet` or not: failures +stay visible. A human-readable table is always printed to stdout; `--out` additionally +writes the full results document as JSON. + +A run takes roughly one to three minutes with the default two samples per month: 24 plan +calculations (12 months × 2 samples) plus the weather, tariff and (if configured) +Octopus consumption downloads, which are cached between runs in `--work-dir`. + +## Advanced: the configuration file + +The web form writes this file for you, and every field on it has an equivalent here. You +only need to read this section if you are running from the command line, or want to hand- +edit something the form does not expose. + +```yaml +annual: + location: + postcode: "SW1A 1AA" # or latitude/longitude + year: 2025 # defaults to the most recent complete calendar year + + solar: # omit for a battery-only run + - kwp: 5.6 + declination: 35 # pitch in degrees, default 35 + azimuth: 180 # 180 = south, default 180 + efficiency: 0.95 # default 0.95, must be greater than 0 and at most 1 + + battery: # omit for a PV-only run + size_kwh: 9.5 + inverter_kw: 5.0 + export_limit_kw: 5.0 # defaults to inverter_kw + hybrid: true # false = AC coupled + charge_rate_kw: 3.6 # defaults to inverter_kw + discharge_rate_kw: 3.6 # defaults to inverter_kw + + load: + annual_kwh: 3800 + shape: flat # night | day | flat + car_charging_kwh: 2500 # annual, 0 to disable + car_rate_kw: 7.4 # charger power, default 7.4, must be greater than 0 + + tariff: + import_octopus_url: "https://api.octopus.energy/v1/products/AGILE-24-10-01/electricity-tariffs/E-1R-AGILE-24-10-01-{dno_region}/standard-unit-rates/" + export_octopus_url: "..." + dno_region: "A" # required when a URL contains {dno_region} + standing_charge_p_per_day: 60.0 + + samples_per_month: 2 + debug: false # keep each sampled day's plan, see Debugging a run +``` + +At least one of `solar` or `battery` must be given — with neither there is nothing to +evaluate. `annual.location` needs either `postcode` or both `latitude` and `longitude`; +if both are given, latitude/longitude wins. + +Octopus product codes are region-suffixed. If your tariff URL contains `{dno_region}` +you must also set `dno_region` to your region letter (`A` for Eastern England, and so +on) — the config is rejected up front with an error naming the offending field, rather +than left to 404 at fetch time and reported as an unavailable month, which would look +like an Octopus outage rather than a config mistake. + +Numeric fields are range-checked and a bad value is rejected with an explanatory message +rather than silently producing a nonsense result: `kwp`, `size_kwh`, `inverter_kw`, +`charge_rate_kw` and `discharge_rate_kw` must all be greater than zero; `efficiency` and +`pv10_derate_fallback` must be greater than zero and at most one; `samples_per_month` +must be a whole number of at least one; and `year` must be between 1940 (the start of +the Open-Meteo ERA5 archive) and the most recently completed calendar year - the current, +still-in-progress year is rejected, since Open-Meteo answers a mid-year request with +short but internally-consistent data that looks complete, and it would then be cached +permanently as if it were. + +Instead of `annual_kwh`, `shape` and `car_charging_kwh` you may supply real consumption: + +```yaml + load: + octopus: + api_key: !secret octopus_key + account_id: A-1234ABCD +``` + +These two forms are **mutually exclusive** and supplying both is rejected. The Octopus +consumption series already includes any EV charging, so accepting both would +double-count it. The trade-off is that a car baked into the meter data cannot be +smart-planned separately. A day missing from the downloaded consumption falls back to a +synthetic UK-average profile rather than being billed as zero. `car_rate_kw` has no +effect on an Octopus load and is ignored there — there is no separately-tracked car +energy to apply a charging rate to. + +**Only use the Octopus option if you do not already have solar or a battery.** An import +meter records what you *bought from the grid*, not what your home used. If you already +generate or store your own energy, that self-consumption and battery discharge have +already been subtracted from every reading — so the series is your residual grid demand, +not your household load. Feeding it in and then modelling a solar and battery system on +top applies the same saving twice: the tool would credit you for displacing import that +your existing system had already displaced, and overstate what a new system is worth. + +For a home that already has a system, use `annual_kwh` instead, and give your **total +household consumption** — generation included — rather than the figure on your bill. The +web form says the same, and says it more loudly when it can see that your Predbat +instance already has a battery or an array configured. + +`car_rate_kw` is the charger's power, used to size both the dumb timer's charge window +(scenarios 1 and 2, which share the same fixed timer) and the smart plan's charging rate +(scenario 3): a smaller number (say 3.0 for a granny charger) spreads the same energy over +a longer window, while a larger one (up to a three-phase charger's 22 kW) charges it +faster. It must be greater than zero. + +Instead of an Octopus URL you may give a fixed rate structure: + +```yaml + tariff: + rates_import: + - start: "00:30:00" + end: "05:30:00" + rate: 7.0 + - start: "05:30:00" + end: "00:30:00" + rate: 28.0 + rates_export: + - rate: 15.0 +``` + ## Debugging a run When a figure looks wrong, a normal run gives you no way to see why: you get the monthly @@ -493,16 +523,3 @@ Two things to know before turning it on: - **It does not change the numbers.** The flag only retains plan data that the engine already computes; it does not enable Predbat's own `debug_enable`, which would disable the prediction kernel and slow every plan down substantially. - -## Limitations - -- The Open-Meteo forecast archive only reaches back to about 2021. For earlier years the - tool plans against actuals instead and P10 uses the flat fallback derate, which it - states in the results' caveats — savings are likely overstated for those years. -- Self-consumption is not reported, for the reason described above. -- The forecast-versus-ERA5 gap includes systematic model bias as well as genuine - forecast error, so measured solar uncertainty is slightly overstated. -- A month with no rate data, no usable weather days, or where every sampled day failed to - plan is reported as `unavailable` and excluded from the annual total, rather than - counted as zero. -- Heat pump, iBoost and gas modelling are not included. diff --git a/docs/index.md b/docs/index.md index bd11e5d78..372d751e7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,6 +2,8 @@ Home battery prediction and automatic charging for Home Assistant supporting multiple inverters including: +- Deye (Cloud) +- Enphase (Cloud) - Fox (Local & Cloud) - GivEnergy Hybrid, AC, AIO and EMS (Local & Cloud) - Huawei @@ -46,6 +48,14 @@ If you want to buy me a beer then please use [Paypal](https://paypal.me/predbat? If you are finding Home Assistant and Predbat too difficult to set up yourself there is now [PredBat Cloud](https://predbat.com/) which is a paid for version of Predbat hosted in the cloud. Please note that while I have given permission for PredBat Cloud to operate under license, PredBat will remain open source for personal use. +## Not sure whether it is worth it yet? + +[What If](annual-prediction.md) projects a full year of electricity costs through the same +planning engine, comparing no PV or battery against PV alone, PV with a battery on a +timer, and PV with a battery run by Predbat — then works out the install cost and how long +each takes to pay back. It needs no hardware and no configured Predbat, so you can try +sizes and tariffs before buying anything. + ## Taster Once you are up and running you will get a chart that predicts your battery levels over time: diff --git a/docs/what-does-predbat-do.md b/docs/what-does-predbat-do.md index e5b1257db..9cea1df47 100644 --- a/docs/what-does-predbat-do.md +++ b/docs/what-does-predbat-do.md @@ -9,15 +9,22 @@ Predbat will automatically decide when to charge and discharge your battery to a It uses the solar production forecast from Solcast, Open-Meteo, or Forecast.solar combined with your historical energy usage to make this prediction. - The output is a prediction of the battery levels, solar generation, house load, charging activity, discharging activity, costs and import and export amounts based on (by default) 30-minute slots. -- Costs are based on energy pricing data, either manually configured (e.g. 7p from 11pm-4pm and 35p otherwise) or by using the Octopus Energy integration +- Costs are based on energy pricing data, either manually configured (e.g. 7p from 11pm-4pm and 35p otherwise) or taken from your supplier + - Octopus Energy, either directly through their API or via the Home Assistant integration. + - Kraken, for EDF and E.ON Next. + - Energidataservice and Strømligning for Denmark, Nordpool and other spot-rate sensors elsewhere - see [Energy rates](energy-rates.md). - Both import and export rates are supported. - - Intelligent Octopus is also supported and takes into account allocated charging slots. + - Intelligent Octopus is also supported and takes into account allocated charging slots. + - [Axle VPP](energy-rates.md#axle-vpp) export events are supported, with Predbat standing aside for the duration of an event. - The solar forecast used is the central scenario from Solcast/Open-Meteo/Forecast.solar (50%) with a configurable weighting towards the more pessimistic (10%) scenario. - Predbat automatically programs your inverter with the appropriate charging and discharging controls. Both Hybrid inverters and AC-coupled inverters are supported by Predbat. - Automatic planning of export slots is also supported, when enabled Predbat can start a forced discharge of the battery if the export rates are high and you have spare capacity. - Historical load data is used to predict your consumption, optionally car charging load can be filtered out of this data. - Predbat can be configured to manage the charging of your EV or to use a Solar Diverter, and take into account these loads on the house during these periods. - Multiple inverter support is included but depends on all inverters running in lockstep. +- Load can be predicted from your history, or by a [neural network trained on it](load-ml.md) that also learns time-of-day and day-of-week patterns. +- [Compare](compare.md) prices your actual usage against different tariffs, so you can see whether switching would pay. +- [What If](annual-prediction.md) projects a whole year under four scenarios — no PV or battery, PV only, PV and battery on a timer, and PV and battery with Predbat — and works out install costs and payback. It needs no hardware and no configured Predbat, so it answers "is this worth buying?" as well as "was mine worth it?". ## Terminology diff --git a/mkdocs.yml b/mkdocs.yml index 99e0db2ca..a025b6f0c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,7 +26,7 @@ nav: - predbat-plan-card.md - creating-charts.md - compare.md - - annual-prediction.md + - 'What If Annual Prediction': annual-prediction.md - 'Predbat development': - todo-list.md - developing.md