I built a CLI that answers one question on your own metered data: would a home battery have paid for itself? Not a forecast from a typical-year profile — a retrospective fact, computed from energy that actually flowed through your meter.
Two results came out of it that I did not expect, and they are the reason the tool is shaped the way it is.
The biggest battery saves the most and is the worst investment
On the household above: 15 kWh saves 261 EUR a year and takes 34.5 years to return its cost. 5 kWh saves 185 and takes 16.2. Savings and payback point in opposite directions, and every vendor quote I have seen shows the first one.
It is obvious once written down and almost impossible to see from a single number, which is why every run sweeps a range of capacities and prints both columns side by side. The tension is the output.
The export price swamps everything else
This is the one that changed how I think about home storage.
Same house, same battery, same year of data: payback of roughly 10 years when the utility pays 0.05 EUR/kWh for exported energy, and over 23 years when it pays 0.15. A factor of two, from a variable nobody shops on.
The mechanism is simple once stated. A battery earns its money by not exporting — every kWh you store and later self-consume is a kWh you would have sold cheaply and bought back expensively. The value of that swap is the spread.
Cut the spread and the battery has almost nothing to recover, no matter how big it is. Battery capacity is a second-order variable next to the tariff, and the tariff is the thing outside your control that changes when a net metering scheme ends.
Every report carries a sensitivity table across export prices for that reason.
There is a design decision behind that table I would defend in an interview. The strategy is greedy self-consumption: surplus charges, deficit discharges, and it never reads a price. That sounds like a limitation. What it buys is that the energy flows are identical at every export price — only the costing changes — so the entire sensitivity table is a closed-form re-costing of one simulation rather than three separate runs. Exact, not approximate. A price-aware strategy would have made that table more expensive to compute and harder to trust.
Sometimes no battery helps
A household already self-consuming most of its own production has very little surplus left to store. On one of the example homes, no capacity pays back inside a battery's lifetime, and the honest output is to say so rather than recommend the least-bad size.
That verdict is worth more to the reader than the other two combined, and it is the one a calculator on a vendor's site will never produce.
Under the hood
Deterministic end-to-end, no LLM anywhere in the compute path. Pandas-vectorized, so a multi-year file at 15-minute resolution is seconds. Pydantic at the boundaries, mypy --strict, 363 tests, Apache 2.0.
Ingest was most of the work, as it always is with metered data. Two column schemas, DST transitions including the ambiguous autumn hour, gap detection, and per-column auto-detection of cumulative meters versus per-interval energy — a reading of 41,238 kWh and a reading of 0.4 kWh are the same physical quantity expressed two ways, and guessing wrong silently produces a plausible answer.
That last phrase is the whole difficulty of this domain, and it caught me four times. Every one of those bugs was an entire layer uniformly wrong with a green test suite, because the test fixture concealed the defect through a property that made it convenient: it was exactly 365 days long, so anything dividing by a hardcoded 365 was invisible, and it had no gaps at all, so anything assuming continuity was invisible for a different reason. The rule I write down now is to enumerate every property that makes a fixture convenient and anchor a test against a shape it does not have. If that pattern is interesting on its own, I will write it up separately — it deserves more room than a launch post.
Try it
git clone https://github.com/contimarco77/battery-worth
cd battery-worth && pip install -e .
Home Assistant users: a standalone script exports your long-term statistics to CSV, and the analysis engine itself contains no network code at all.
The example cards are two real households from Open Power System Data, in Konstanz, Germany, CC BY 4.0. I do not have a PV system of my own, which is why the examples are somebody else's real data rather than mine — and why I would like to see what it says on yours.
Sibling project: solar-report, narrative reporting for PV production.















