Repository navigation
fix(app): history tiles draw measured power for grid, PV and battery - #1854
Merged
Merged
Conversation
Fredrik's box, 25 October 2026: the app's Solar history jumped between 0 and 1.2 kW while the sun gave about 0.4 kW. The tiles were built from the energy ledger, and a Sungrow's counters move in 0.1 kWh steps, so each step landed whole in the five minutes it ticked in (100 Wh over five minutes is 1.2 kW). The box's own charts draw the power rows and were right. appHistory.Window now takes grid, PV and battery from those rows (state.LoadPowerBuckets: the same tiers /api/history unions, averaged in fixed buckets from the tile's start, half-open). A flow some row did not measure is a hole in that bucket. Load stays the ledger's consumer use, which the source accounting balances from power. FTW Cloud's tiles use the same window, so they change with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
- LoadPowerBuckets means each flow over the rows that measured it and marks it missing only past maxSourceDayUnknownShare (10 %) of the bucket's rows, as a source-era day's total is. One stale reading no longer removes five minutes, and a PV inverter waking at dawn keeps its buckets. - Window skips the ledger query when no ledger series is asked for. - Comments in appproto and cloud_link no longer say the series come from the ledger. - Tests: a row at `to` stays outside, battery charging is positive, and one stale reading keeps its bucket (it fails under an all-or-nothing rule). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second review: Core flags Load on every tick, so all ticks are partial rows, and the day-old roll-up merges a stale reading's flag into its whole minute. One stale reading in five minutes grows from 3 % of the rows to 20 %, so under a tenth the closed bucket turned into a hole a day later. A flow now needs to miss more than half of a bucket's rows to lose its mean. TestPowerBucketSurvivesTheMinuteRollup writes ticks as Core does, rolls them up and fails under the old tenth. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 25, 2026
frahlg
added a commit
to srcfl/ftw-webapp
that referenced
this pull request
Oct 25, 2026
srcfl/ftw#1854 serves the app's grid, PV and battery tiles from the measured power rows /api/history draws, at 5m and 1h alike; load stays the ledger's consumer use. The registry said energy_ledger and hourly_rollup. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
frahlg
added a commit
to srcfl/ftw-app
that referenced
this pull request
Oct 25, 2026
What to Test leads with the stepped solar forecast (#118) and says the history for solar, grid and battery changes with the next box update (srcfl/ftw#1854). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
frahlg
added a commit
that referenced
this pull request
Oct 25, 2026
Copy of srcfl/ftw-webapp's registry change, with contract_gen.go regenerated. #1854 serves grid, PV and battery tiles from the measured power rows at 5m and 1h alike; load stays the ledger's consumer use. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Fredrik, on TestFlight 0.5.1 against his own box, 25 October 2026: the Solar view's history "hoppar mellan noll och osv". The figure under the finger read 0 W where the curve was higher.
The app's history tiles came from the energy ledger. On his box, the ledger's five-minute buckets for
pv_generationheld only 0, 1 200 or 2 400 W all day, andgrid_importonly 0, 1 200, 2 400 or 3 600 W (/api/energy/history?bucket=5m, sourcehardware_counter). The cause is the Sungrow's energy counters, which move in 0.1 kWh steps. The ledger saves the counter cursor on every reading, so each 100 Wh step lands whole in the second it ticks, and 100 Wh over five minutes reads as 1.2 kW.The box's own charts (
/api/history) draw the measured power rows, and those were right: about 450 W at 08:00 rising to 2 250 W at 09:15, with no zeros.consumer_useis a power balance (source_interval_balance_v1) and was right too.Change
state.LoadPowerBucketsreturns the mean measured grid, PV and battery power per fixed bucket of[from, to). It reads the same tiers/api/historyunions, now one package constanthistoryTierUnion, half-open, averaged with the samenweights. A bucket without rows is absent. Each flow's mean is over the rows that measured it. A flow isMissingwhen more than half of the bucket's rows, by weight, did not measure it, or when none did (maxPowerBucketUnknownShare). Rows roll up after a day into minutes that carry any tick's flag, so a tenth would turn a closed bucket into a hole a day later.appHistory.Windowservesgrid_w,pv_wandbattery_wfrom it. A missing flow is left out, so it becomes MISSING on the wire.load_wstays the ledger's consumer use, and the ledger is not queried when no load is asked for.cloudSite→appHistory.Window) change with it.The energy ledger itself is unchanged: kWh totals and costs still come from the counters.
Evidence
TestLoadPowerBucketsLaysFixedBuckets(state): buckets laid fromfrom, a row on a boundary counted once, an empty bucket absent, an empty range refused.TestAppHistoryDrawsMeasuredPower(cmd/ftw): measured −400 W PV, 300 W grid and +500 W battery charging reach the tiles with an empty ledger, which on master gives no PV at all. An inverter that falls silent six minutes in leaves a hole in its second bucket, and the grid stays.TestAppHistoryKeepsAFiveMinutesWithOneMissedReading: one stale reading in thirty keeps the bucket at −400 W. It fails under an all-or-nothing rule (checked).TestPowerBucketSurvivesTheMinuteRollup(state): ticks written as Core writes them (every tick flags Load), one stale PV reading, then the day-old roll-up. The bucket keeps −300 W before and after. Under a 10 % limit it becomes a hole after the roll-up (checked).tostays outside the range.go test ./internal/state ./cmd/ftw ./internal/appprotopass.make verifyat the head: vet, test and build clean.Notes
A review agent checked the SQL against the tiers. With aggregation on, which Core always enables, ticks land in
history_dashboardorhistory_dashboard_partialonly, so tiers do not overlap. Thenweights give the plain tick mean before and after the minute and five-minute roll-ups. Retention covers the 48 h the app and FTW Cloud ask for. Before the dashboard tier (17 September 2026), only 15-minute and daily rows exist, so old 5m tiles are sparse where the ledger had values.contract/registry.yamlstill lists'5m': { …, source: energy_ledger }. The registry is edited in ftw-webapp and copied, so that label goes in a paired follow-up.A tile with a stride above 1 still averages the buckets that survive, as it did with the ledger.
The app's "so far today" sums the tiles, so a bucket left out lowers that total, as a missing ledger bucket did. A bucket with few rows is not checked for coverage: its mean stands for the five minutes.
A site without PV or battery now reads 0 W for that series, where the ledger left it absent. I have not checked how the app draws a series of zeros for a flow the site lacks.
fix(history): repair Home use and Solar gaps with audited estimates #1751 changes
loadArchiveHistoryin hunks that do not overlap this one (source mode at the top,applyHistoricalLoadChartat the end). Its PV repairs reach/api/historybut notLoadPowerBuckets; carrying them into the tiles would be a follow-up after it merges.The app side (fix(ios): a step the box is still filling never reads as its mean ftw-app#116) already hides the step still filling. With measured power that step's mean so far is right, so nothing more is needed there.
🤖 Generated with Claude Code