Y3K Lab · Field Work

Solar Strategy

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

connecting to the scoreboard…
Yesterday’s verdict
Tomorrow’s forecast
Yesterday’s battery run

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.

Part I

The battery strategy

01

The question

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.

02

Three things you need to know

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:

windowhourssummer (Jun–Sep)winter (Oct–May)
Off-peak00:00–15:00$0.25$0.25
Mid-peak15:00–16:00 + 21:00–24:00$0.45$0.41
Peak16:00–21:00$0.56$0.43
Fact 1 — the price spread is seasonal Summer peak costs 2.24× off-peak — a difference large enough to change when you use power. Winter peak is two cents above mid-peak — too small to act on. A price alert should read the price, not the period name: an alert keyed to the name “peak” fires all winter over a $0.02 difference.
Fact 2 — solar peaks when the house doesn’t need it On a sunny day the roof makes more power than the house can use at midday, and none by the evening peak. Under NEM 2.0 the buy price equals the sell price in every window, so an unnecessary off-peak grid charge costs close to nothing in dollars. That fact explains failure 3 below.
Fact 3 — time at full charge ages the battery High state-of-charge ages NMC battery cells. This wear never appears on a bill — it is the invisible cost in the battery-life half of every decision below.
03

The hypothesis — arrive full just in time

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:

  1. A full battery before the expensive window opens, every day — the pack must carry the whole peak evening on stored energy (here: 100% by 3 PM, ahead of a 4 PM peak).
  2. Charge from solar wherever possible.
  3. Never charge from the grid at elevated rates — when solar is not enough, buy the remaining energy while power is cheapest (here: off-peak, before 3 PM).
  4. Minimum time at 100% — high state-of-charge is what ages NMC cells.
  5. Keep a reserve floor as outage insurance — some charge is never spent on economics (here: 20%, whole-home backup).

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):

overnight
The battery holds at its 20% reserve — not full. The low overnight level is what protects battery life.
morning
Solar charges the battery. On a sunny day it reaches 100% with no grid power.
12:00
13:30
The fallback checks: if the battery will not reach 100% on solar alone, buy the remaining energy while the price is still low. There are two check times because charging slows sharply above 71% — failure 2 below.
15:00 sharp
Stop all grid charging, drop the reserve to 20%, and let the battery power the house through the expensive evening. This step produces the savings, and it must run exactly on time — energy released early replaces $0.25 power now instead of $0.56 power an hour later.
04

What failed first (and what it taught us)

The first automation ran flawlessly every night — and achieved none of its goals. Four findings, each measured on this system:

1

A backup reserve is a discharge floor, NOT a charge target.

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.

2

The last 30% of the battery costs two-thirds of the charging time.

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.

3

The costly failure was financially invisible.

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.

4

By noon, the battery’s charge level already measures the morning’s sun.

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.

05

What winter does to the strategy

The strategy must also work in winter. From 1,041 days of production records (Part II explains where they come from):

Winter fills the pack — thinly A typical December day: 17–18 kWh against a 10.4 kWh pack. Winter solar usually fills the battery. The fallback gates fire more often; that’s the design working, not failing.
On rainy days, buying is correct The ten worst days delivered 2–4 kWh before 3 PM. On those days solar cannot fill the battery, and buying the whole gap at off-peak is the correct move.
Summer evenings carry the cost risk On this rate, one badly-timed hot evening imported $0.51 of peak power; normal days import $0.00–0.02. The savings depend on the 15:00 release running on time.
Three more constraints A VPP guard — during a Virtual Power Plant event the utility pays the battery to discharge, so automations stand down (phrased as not-active, because the sensor reads 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.

Part II

The solar forecast

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.

06

The second question

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.

07

How the forecast works

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 roofdeterministic astronomy roof irradiance → AC kilowatt-hours measured system efficiency

The 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.

08

The results — this sky beats the textbook

The fitted transmission curve omfit_v1
T = 1 − 0.36 · (c/100)5.20
how much light this sky passes at cloud cover c
modelholdout MAE (all 218 days of 2026, never seen during fitting)
Fitted: T = 1 − 0.36·(c/100)5.203.97 kWh/day
Kasten–Czeplak textbook seed5.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.

Transmission — cloud cover in, light out fitted to this sky textbook, at full cover
At 100% cloud the textbook passes 25% of clear-sky light; the fitted curve passes 64% — a difference the textbook model had no way to know. Scored days accumulate against this curve as the record grows.
09

The honesty rules

The rules the pipeline enforces on itself — the part worth publishing. The live scoreboard below lets any reader check them.

Score against reality, never against a peer.

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.

Hold out a year.

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.

Version every prediction.

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.

Predict, record, and only then act.

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.

Say “not enough data” out loud.

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.

10

The scoreboard — live, graded twice daily

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.

Leaderboard — mean absolute error, kWh/day
connecting…
Running the day — the strategy score
connecting…
A score of 100 means: the battery reached full inside the on-time window with no shortfall. Earlier arrival loses points, because time at full charge ages the battery (the old automation’s 3 AM arrivals score 0). A day still short at 3 PM caps at 40 — a short day never beats a full one. “Needed grid?” is graded against a measurement — whether pre-3 PM solar could cover the morning deficit — not against what any automation chose. A dangerous miss said “no grid needed” on a day that needed it.
Predicted vs delivered — the last 30 days the sun delivered house model · met.no house model · OWM Forecast.Solar
connecting…
Ticks are the recorded predictions, day-ahead where the horizon is tagged; Forecast.Solar appears at day-ahead only — its same-day values are recorded after part of the day’s sunlight. Bars are what the roof produced. Days without a bar are not scored yet — each prediction is graded when its day completes.
Today, live — solar & battery solar W battery %
connecting…
Solar and battery state only, by design. A live household load reading reveals when the house is occupied, so home and grid appear on this page only as daily totals — a privacy decision, not a data gap.
11

What happens next

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.