Two experiments: Part I is the battery strategy — the rules that run the day. Part II is a solar forecast built to improve those rules. The scoreboard grades both, every day.
an experiment in progress
Electricity prices change with the time of day. At this house cheap power ends at 3 PM: in summer the price rises from $0.25 per kilowatt-hour to $0.45 at 3 PM, then to $0.56 — 2.24× the cheap rate — at 4 PM, just as solar production is falling. At noon, the panels make more power than the house can use. The strategy stores the extra midday solar in a battery and runs the house from that battery during the expensive evening hours — automatically, every day, without shortening the battery’s life.
Can a home battery save the most money and last the longest — automatically, every day, even in December?
These two goals conflict. Saving money requires a full battery before every expensive evening. A long battery life requires minimum time at full charge, because time spent at 100% is what ages lithium cells. This experiment tests whether one automatic strategy can meet both goals at once — including on rainy winter days.
Everything below follows from three measured facts. First, the price of power changes with the time of day. The house is on PG&E’s EV2-A rate (NEM 2.0) — the hours never change, only the prices:
| window | hours | summer (Jun–Sep) | winter (Oct–May) |
|---|---|---|---|
| Off-peak | 00:00–15:00 | $0.25 | $0.25 |
| Mid-peak | 15:00–16:00 + 21:00–24:00 | $0.45 | $0.41 |
| Peak | 16:00–21:00 | $0.56 | $0.43 |
The hypothesis: charge the battery from solar, reach 100% just before the expensive evening starts — never earlier — hold the battery near its 20% reserve overnight, and buy grid power only when solar will not fill the battery in time.
Stated as a spec, in priority order:
Goals 1 and 4 conflict; the strategy resolves them with timing. Here is one working day — measured on this system, where the evening peak drains almost exactly one full battery (100% at 15:00 falls to the 20% reserve at ~21:20, minutes after peak ends):
The first automation ran flawlessly every night — and achieved none of its goals. Four findings, each measured on this system:
The first automation set the reserve to 60% and enabled grid charging, believing the pack would stop there. It doesn’t — with grid charging on, a Powerwall charges to 100% regardless. The result: ~10.4 kWh bought nightly (twice the intent), 13.3 hours a day parked at 100% — identical to the behaviour it was written to eliminate. Trace your automations against measured state; a clean run log proves nothing.
Measured: 20 → 71% runs flat at 5.7 kW and takes one hour; 71 → 100% tapers hard and takes two more. So “top up just before 3 PM” cannot work as a guarantee — a worst-case fill must start by noon. Hence the two check times: 12:00 catches the deep deficit (state of charge below 70%), 13:30 catches the shallow one (below 95%) without committing early to power the afternoon sun was about to deliver anyway.
Under NEM 2.0 the broken overnight charge cost roughly $0 — the midnight purchase and the displaced midday export were both $0.25. The real cost was battery wear: a full extra cycle plus 13 h/day at high SoC, the exact calendar-ageing the design existed to avoid. No dashboard tile will ever show that number. A cost tracked only in dollars never shows this failure.
The obvious-sounding design is a solar forecast deciding the morning’s charge. But at 12:00 the battery’s own state of charge is a measurement of how much sun arrived — no forecast is needed for the fallback decision. A forecast is useful elsewhere (Part II), and only if it proves it decides better than this simple rule.
The strategy must also work in winter. From 1,041 days of production records (Part II explains where they come from):
unknown until the first event ever fires). The 20% reserve is
untested outage insurance by design. And NEM 3.0 changes the arithmetic
entirely — export credits collapse, unnecessary charges stop being free, and a
forecast-driven strategy starts to pay.
Part I’s result is measured, not written. The strategy has run automatically since 2026-08-05, and every completed day receives a 0–100 strategy score in the live scoreboard below. That daily grade is the answer to the question in section 01, updated for as long as this page exists.
The rules above are simple on purpose. Part II builds a forecast that could replace them — after it proves, in public, that it decides better.
Can a model built for this exact roof predict tomorrow’s solar better than a commercial forecasting service?
The approach: replace a textbook solar-forecast formula with a curve fitted to this roof, this weather station, and 2.8 years of this house’s production history. The model — called the house model on this page — runs twice a day on a Mac mini, predicts tomorrow’s solar in kWh, writes every prediction to a database beside what actually happened, and scores itself. A commercial forecast (Forecast.Solar) runs beside it on the same leaderboard, as a rival — never a target.
Forecasting solar production splits into three steps — and two of them were already solved on this homelab:
forecast cloud % → forecast sunlight (GHI) ← the ONLY empirical unknown
forecast GHI → irradiance on THIS roof ← deterministic astronomy
roof irradiance → AC kilowatt-hours ← measured system efficiencyThe astronomy (NOAA solar position → Haurwitz clear-sky → Erbs diffuse split → Hay–Davies transposition onto the real 23° / 245° + 155° roof planes) already ran at ingest, validated to 0.0000% against an identity control.
The efficiency is measured end to end — actual kWh ÷ clear-model kWh over recent verified-clear days — one number that absorbs every systematic bias in the chain and re-calibrates itself as seasons change.
The transmission arrow — how much light this valley’s clouds actually block — is the only thing that had to be learned, and it is exactly the thing a generic vendor model cannot know about one microclimate. Hourly cloud forecasts come free from two providers already in Home Assistant (met.no and OpenWeatherMap, 48 h ahead), which also makes them competing inputs, scored against each other.
The shortcut — 2.8 years of data the house already had. The Tesla app renders
production history back to install day, and the Fleet API serves the same data:
1,165 days (2023-05-30 → present), pulled in one run, verified three independent
ways — the lifetime total agrees with the documented lifetime counter to 0.1%; overlap
days match the house meter to a consistent ~3%; and the record starts on exactly the
install date, with empty months before it. Each day was labelled
actual ÷ clear-sky ceiling — its transmission ratio — and paired with
ERA5’s hourly observed cloud for the same dates. That’s the training set that would
otherwise have taken months of waiting for cloudy days to accumulate. One
boundary was measured, not assumed: the first four months ran export-capped pending
PG&E permission-to-operate, so those 124 days were deleted — they measure the
cap, not the sky. The 1,041 days that remain are the 2.8 years.
| model | holdout MAE (all 218 days of 2026, never seen during fitting) |
|---|---|
| Fitted: T = 1 − 0.36·(c/100)5.20 | 3.97 kWh/day |
| Kasten–Czeplak textbook seed | 5.14 |
| Cloud-blind (ignore clouds entirely) | 5.28 |
23% better than the textbook, 25% better than ignoring clouds — on data the fit never touched. Training was 823 post-PTO days paired with hourly observed cloud from ERA5 reanalysis, efficiency always solved from the train set so the holdout stays honest.
a = 0.36 is the genuinely interesting part: the textbook curve (fit to
1980s Hamburg) assumes clouds block 75% of light at full cover — this sky, as ERA5 sees
it, passes about two and a half times as much (64% vs 25% at full cover).
Coastal marine-layer cloud is thin, so a grid cell reported as “overcast” often still
delivers usable light at this site. That one number is why the seed was
systematically wrong here — and it is information no vendor model can know about one
roof’s microclimate.
The first cloudy forecast day was the first real test: it split the sources for the first time (met.no 29.2 kWh vs OWM 24.6). Until then, every prediction had matched because every day was sunny — agreement that tested nothing. One caveat stands: the curve was trained on reanalysis cloud (recorded, after-the-fact cloud data) and runs on forecast cloud — that gap is exactly what the daily scoring loop exists to measure.
The rules the pipeline enforces on itself — the part worth publishing. The live scoreboard below lets any reader check them.
Forecast.Solar runs on the same leaderboard, configured honestly with the real roof geometry — but only actuals ever tune the house model. Tuning to a rival teaches imitation, not prediction.
The curve was fitted on 2023–2025 and judged on 2026 only. The efficiency scale was always solved from the train set — the holdout stayed untouched until the final comparison.
Rows are tagged with the model that made them (kc_seed_v1,
omfit_v1, vendor), so no average ever mixes eras. When the
model is refit, this page shows the era boundary — never a merged score.
The forecast controls nothing. The battery-charging automation keeps its simple, static rules until the projection demonstrably beats them on decision accuracy — “did this day need the grid?” — not just kWh error. The standing rule: a new job reports; it does not act. Authority is earned.
Until the record contains genuinely cloudy days, accuracy numbers look good without proving anything — sunny-day agreement tests the astronomy, not the forecast. The daily report states this itself, and so does the scoreboard below.
This is the results section that never stops updating. Every run writes predictions for today and tomorrow from three sources — the house model on met.no clouds, the house model on OWM clouds, Forecast.Solar — then back-fills what actually happened onto every completed day and reports MAE per source and per horizon. Day one of the benchmark: for a forecast-sunny tomorrow, the house model said 39.3 kWh, Forecast.Solar said 27.7 — against recent sunny-day actuals of 39–41. The leaderboard records which source stays closest.
The forecast controls nothing today — that’s rule 04, not an accident. The battery keeps its simple, static rules until the projection demonstrably beats them on decision accuracy — “did this day need the grid?” — across a window that includes genuinely cloudy days. If it wins, it earns the noon decision, with the static rule kept as the fallback. Either way, the scoreboard above keeps score in public.