Ashesh Srivastava
← home

Astrape — forecasts whether my month crosses 1,000 kWh, where my bill drops from $207 to $78

My Texas provider sells a bill-credit plan: use 1,000 kWh or more in a billing cycle and $125 comes off the bill, which means a month of 999 kWh costs $207 while 1,000 kWh costs $78. My problem was that I could never tell which side of that line a cycle would land on, because daily usage swings hard with the weather. So I built a tool that forecasts each cycle against the 1,000 kWh mark, and checked its math against a real bill to within seventeen cents. Around half a million Texas households are on plans like this one, so there may be a real market hiding in it too.

ForecastingBill-validatedNo LLM by designSolo build
Role
Designed, built, and shipped solo.
Timeline
May to June 2026 — one cooling season in Houston; last commit 14 Jun 2026.
Status
Model validated to $0.17 against a real bill. Open-sourced: github.com/as2621-dev/Astrape
Tech
Python · cooling-degree-day regression · Smart Meter Texas 15-minute interval data · Open-Meteo weather · Apps Script dashboard · bill reconciliation to a $1 tolerance.

The product decision

What I built. A projection tool, named Astrape after the Greek goddess of lightning. It pulls Smart Meter Texas’s 15-minute interval data from a daily email, pairs each day with free Open-Meteo weather, fits a cooling-degree-day regression, and answers one question: will this cycle clear 1,000 kWh?

Who it’s for. I built it for myself, to run against my own meter and my own plan through a single cooling season in Houston. But I got curious whether anyone else would want it, so I ran the numbers. Texas has about 11 million households. Roughly 1.9 million of them have a smart thermostat, about 1.33 million of those are on Smart Meter Texas, and around 530K are on a bill-credit plan. Of those, maybe 250–350K live close enough to a threshold that a forecast would actually change what they pay. The 530K is the shakiest number in that chain — it rests on meter counts I couldn’t verify, so I’d call it low-medium confidence, give or take 15 points.

Why this problem. The $125 credit exists because of how Texas makes people shop for electricity. Every plan has to publish its average price at three usage levels (500, 1,000, and 2,000 kWh), and shoppers compare plans on the 1,000 kWh price. A credit that kicks in right at 1,000 kWh makes that one advertised number look very cheap, so the plan wins the comparison — but the cheap price is only real if you actually reach 1,000. Land at 999 and the credit vanishes, and that is where the provider makes its money. It also means normal “save energy” advice backfires on a plan like this: near the cliff one extra kWh costs about $5 and unlocks about $100, so at 970 kWh the smart move is to turn the AC down.

How it’s built

The model. The logic is simple. Every home burns a fixed amount of electricity a day no matter the weather — fridge, router, lights. That’s the baseload. On hot days the AC adds more, roughly in proportion to how far the day’s average temperature climbs above 65°F. So to project the cycle, the tool learns two numbers from the days already metered — the baseload, and how many extra kWh each degree above 65 costs — then applies them to the weather forecast for the days remaining. In math, it’s ordinary least squares, hand-rolled in about 24 lines, no numpy:

kWh/day ≈ baseload + β · CDD65      where CDD65 = max(0, daily mean °F − 65)

It fits only on days with both metered usage and observed weather; the rest of the cycle is forecast ahead, reanalysis behind.

How I validated it — against money, not against itself. The bill module recomputes each bill from metered usage and the plan’s published terms, then reconciles it against the billing email to a $1 tolerance. On the 2026-05-13 bill — billed $91.65 — the model computed $91.82.

SMT email → Google Sheet → dashboard
The pipeline, plus the money check
data path — time trigger every 4 hmoney check — recompute vs the charged billbuilt + verified, never wired in
The shipped productSHEET + APPS SCRIPTTIME TRIGGER EVERY 4 H · OR THE DASHBOARD ↻STATE: ONE GOOGLE SHEETCONFIG · CYCLES · DAILYUSAGE · WEATHER · PROJECTION · SUMMARYThe money check — Python CLIreconcile_4change_bill.py — recompute the bill frommetered kWh + plan terms, vs the emailed amounttolerance max($1, 1.5%) · incomplete-usage gateSMT's daily interval CSV lands in the inboxGmail.gs reads the CSV attachments on every pipeline runrunDailyUpdate: import, then weatherWeather.gs fetches forecast + climatology from Open-MeteorunDailyUpdate: weather, then projectiondoGet serves Index.html; getDashboardData shapes any billing month's series on demand4Change's bill-ready notice lands in the same inboxBoth feeds leave Gmail over IMAP: reconcile_4change_bill.py pulls the bill notices; the SMT CSVs land in data/smt via fetch_smt_csv.py and are read from thereA 'match' validates the same bill math Projection.gs uses — $91.82 computed vs $91.65 billed on the 2026-05-13 billBILL + CSVS · IMAPMATCHED TO $0.17Smart Meter Texas — daily email with a 15-minute interval CSV attachment (96 rows = one service day)GRID DATASmart Meter Texas15-min interval CSVemailed daily4Change Energy — the transactional bill-ready email carries the charged amount and billing period; the portal is never scrapedRETAIL PROVIDER4Change Energy“bill’s ready” email:amount due + periodGmail — the only ingestion surface: SMT CSVs and 4Change notices are read from the inbox, nothing is scrapedONE INBOXGmailboth feedsarrive asplain emailIndex.html — Chart.js dashboard served by doGet to anyone with the link; a cycle picker per billing month, and a refresh button that runs the whole pipeline on demandSHARED LINKThe dashboarddoGet → Index.htmlChart.js line chartpicker + ↻ refreshOpen-Meteo — near-term daily forecast (≤16 days) plus ERA5 climatology normals beyond the horizon; ZIP resolved to coordinates via zippopotam.us; no API key anywhereWEATHER APIOpen-Meteoforecast + ERA5 archivefree · no API keythermostat/cloud_client.py — Aprilaire's aprilaire.io cloud via Cognito + REST + WebSocket; reading temps and writing the cool setpoint both verified live, but the automation was never connected to the projectionSIDE QUESTAprilaire cloudAWS Cognito authREST + WebSocketread + write verifiednever wired into the loopimportUsageFromGmail — searches from:smartmetertexas.com, parses CSV attachments, keeps the latest revision per day, upserts daily totals1 · Ingest — Gmail.gspull SMT CSVs from the inboxsum 96 intervals → daily kWh→ DailyUsage tabrefreshWeather — stitches observed + forecast days from Open-Meteo, fills beyond the 16-day horizon with 5-year calendar-day climatology2 · Weather — Weather.gsOpen-Meteo forecast aheadERA5 climatology past 16 d→ Weather tabcomputeProjection — OLS fit on days with both usage and weather (port of project_usage.py), trailing-7-day fallback when the fit is unreliable, verdict LIKELY CLEAR / CLOSE / AT RISK / MISS plus a projected bill in dollars3 · Project — Projection.gsOLS kWh ≈ baseload + β·CDD65verdict vs the 1,000 kWh cliff→ Projection + Summary tabs
the pipeline as it actually runs, from source — Gmail.gsWeather.gsProjection.gs into one Google Sheet, reconcile_4change_bill.py checking the math against real bills, the thermostat client verified but never wired in. no LLM anywhere in the loop, by design.

The dashboard. Dark, mobile-first, one screen, no scrolling: cumulative kWh in solid green, projection dotted, a dashed red line at 1,000.

real product — the deployed Apps Script dashboard, captured 2026-08-02 as a static DOM snapshot (scripts stripped, chart frozen from the live canvas). June cycle view.open full page ↗

Where this goes next

The plan is to close the loop. Today the tool only watches; the next step is to let it drive my thermostat. The projection already knows, days in advance, which side of 1,000 kWh a cycle will land on, so it can turn that into a concrete suggestion — you’re pacing toward 965 kWh, run the AC one degree cooler for the next few days and you’ll clear the minimum — and eventually make that adjustment itself.

The thermostat half already works, and it took a workaround. My thermostat is an Aprilaire, and Aprilaire publishes no developer API. But its phone app has to talk to something, so my client talks to the same aprilaire.io cloud the app does: it authenticates through the app’s own AWS Cognito pool, reads setpoints over REST and live temperature over a WebSocket, and writes a new setpoint with a PATCH. Read and write are both verified against the live unit. And Aprilaire is the hard case — Nest, Ecobee, and Honeywell all publish official APIs, so for most of the market the thermostat link is the easy part.

What I learned

Everything is an API. The thermostat workaround above is the proof: Aprilaire publishes no API, but its own phone app was riding one all along. Since this project, I check for the API first, everywhere — most “there’s no way to get the data” problems are really “nobody opened the network tab” problems.

Electric companies are playing a game, and now I can see it. I learned more about billing technicalities than I ever planned to — delivery charges, energy charges, how a bill-credit plan actually computes. And once you understand the mechanics, the design is hard to unsee: these plans are built around most customers missing the credit. It is in the provider’s best interest that you never hit 1,000 kWh. The $125 credit is the advertisement; the customer who lands at 970 is the margin.

Smart meters are amazing, and almost nobody uses them. Every home on Smart Meter Texas is already recording usage in 15-minute intervals, with on-demand reads available through a public platform. All the data this project needed was sitting there, free. The infrastructure is years ahead of the products built on top of it.

There might be a business in here one day. Precisely because it’s in the provider’s interest that you miss the threshold, the provider will never build this tool — a tracker that helps you hit 1,000 kWh can only come from a third party. I haven’t commercialized any of this, and the market sizing above is honest about how Texas-shaped it is. But it’s the rare side project where the incentives point at a real product: the users’ interest and the provider’s are directly opposed, and the data to serve the users is already public.