Skip to content

fix(history): count unknown PV as 0 W after sunset - #1755

Merged
frahlg merged 5 commits into
masterfrom
1750-pv-night-zero
Oct 20, 2026
Merged

frahlg merged 5 commits into
masterfrom
1750-pv-night-zero

Conversation

@frahlg

@frahlg frahlg commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Stacked on #1753 (base 1750-bridge-short-history-gaps, rebuilt on 85100d6). Relates to #1750.

Fredrik, 19 October: Home use has two layers. Display is lenient: #1753 now holds an unknown source's last known value and marks every such minute EstimatedLoad. Training is strict: #1754 learns only from minutes without the mark. A sleeping inverter must not take every night out of the load models.

What this adds to #1753

Holding a PV value is wrong after sunset: an inverter that went silent at dusk would carry its last evening reading through the night. While the sun is below the horizon at the site's map pin, Core records an unknown PV source as 0 W instead. This is physics, not interpolation.

  • Past civil dusk (zenith over 96°, sun 6° down) the 0 W carries no mark. The snapshot is fully known, so fix(history): keep Home use through device dropouts and restarts #1753's writer records measured minutes, and they count in Home use and in training. No new bit records it.
  • In twilight (zenith 90.833–96°) diffuse light remains, so the 0 W is an estimate: the snapshot carries EstimatedLoad, and fix(history): keep Home use through device dropouts and restarts #1753's writer marks every interval it bounds. Home use counts those minutes; training skips them.
  • Topology. fix(history): keep Home use through device dropouts and restarts #1753's writer marks an interval across a topology change. When the watchdog takes a silent inverter offline (after about 60 s), its device drops out of the energy record and so out of the topology. While the sun is down the sleeping inverter keeps its place, so the night stays one topology and its minutes stay measured. The devices line moves into historyDevices, shared by persistTelemetryTick and the test.
  • The rule touches only the PV flow and its mark. Every other flow, and a configuration fault such as a PV array without a measurement source, keeps its own evidence.
  • No pin (unset, 0,0 or the untouched default pin) means nothing is inferred, and fix(history): keep Home use through device dropouts and restarts #1753's hold rule applies. The rule reuses siteLocation, which already rejects those.
  • By day nothing changes. Grid, battery and EV are not affected.

Sun positions come from the existing sunpos package. No new setting, table, API field or UI. In go/internal/state this PR adds only a comment and a test.

Evidence

TestDarkPVByTimeOfDay, Västerås on 19 October (sunset 17:38, civil dusk about 18:20), a snapshot with PV unknown:

Case Result
02:22, 18:45 PV 0 W, fully known
17:45 PV 0 W, EstimatedLoad
12:00, 17:30, or no pin unchanged
02:22, battery also unknown PV 0 W; battery stays unknown
02:22, PV array without a measurement source PV 0 W; Load stays unknown

TestSleepingInverterNightIsMeasured: eight hours on the equator, past civil dusk in every season. A 1000 W house; the inverter reports 0 W for ten minutes, then sleeps, and a minute later the watchdog takes it offline. Snapshots go through historySources, darkPV and historyDevices, as in persistTelemetryTick, into #1753's writer.

Snapshots Topologies Home use
With a pin all fully known 1 8000 Wh
Without a pin PV unknown after 10 min 2 8000 Wh, PV held as an estimate

TestSourceLoadMarksIntervalsOfAnEstimatedSnapshot pins the contract this relies on: a marked snapshot marks the minutes it bounds and no others.

Each part fails its test when switched off: no dark rule, a mark at every dark snapshot, no mark in twilight, no sleeping device (2 topologies), and Load cleared along with PV.

With #1754 (fix/load-models-use-history-intervals at fe42b90, merged with this branch outside the repo; go test ./internal/state ./internal/loadmodel ./cmd/ftw passes):

Scenario Home use Training labels
Full night above, with a pin 8000 Wh 32 of 32 quarters, 1000 W each
Full night, without a pin 8000 Wh, estimated 0 of 32
Västerås 18 October 17:00–21:00, inverter silent from 17:42 4000 Wh 17:00, 17:15 and 18:30–20:45; none for 17:30–18:15 (twilight)

These tests need HouseholdIntervals, so they can join whichever of #1754 and this lands second.

make verify: passed on cc892be. All 77 Go packages, protocol and script tests, vet and build. Nothing skipped.

Found on the way

historySources used to mark Load unknown whenever one source failed, which kept #1753's hold from working in Core. Reported here; #1753 fixed it in 85100d6. This PR's night test without a pin now checks Home use again.

Not verified

  • Not run on a box. Nothing was changed on any box.
  • Whether real inverters report PV as unknown, stale or offline at night varies by driver.
  • If a sleeping inverter cannot report its identity after a Core restart at night, its device is missing from the topology until it wakes. Not tested.

For Fredrik to decide

  • Civil dusk at 96°. Past it the 0 W trains. At midsummer the sun reaches only 7° down at 59.6° N, which leaves about two hours past civil dusk. North of about 60.5° N there are none, so summer nights there train only on measured PV.

Already answered: relax the day rule too (now in #1753, so #1756 is superseded); reuse EstimatedLoad with no new bit; pv_wh known at night.

🤖 Generated with Claude Code

@frahlg
frahlg force-pushed the 1750-pv-night-zero branch 2 times, most recently from 49311a3 to 84a4655 Compare October 19, 2026 18:50
frahlg added a commit that referenced this pull request Oct 19, 2026
historySources set MissingLoad for every reason except invalid_balance.
Telemetry puts a source's own failure there too, such as
"offline:sma:pv" or "stale:easee:ev", so a real snapshot with a silent
inverter or charger voided the whole balance and the source writer
held nothing. Only configuration and duplicate-flow reasons now set
MissingLoad; a failed source is unknown on its own flag.

Found by the #1755 session through historySources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg force-pushed the 1750-pv-night-zero branch from 84a4655 to cc892be Compare October 19, 2026 18:55
frahlg added a commit that referenced this pull request Oct 20, 2026
* fix(history): bridge short source gaps instead of losing the day's Home use

Since #1741 a Core restart (18-20 s), a source unknown for a few seconds
or a writer pause over 15 s made the whole day's load_wh null, and an
unknown PV also nulled pv_wh.

The source writer now holds an episode that starts at the last fully
known snapshot. If every source is known again with the same topology
within LoadEstimateRecoveryWindow (2 min), the episode is written as an
estimate marked EstimatedLoad: measured sources keep their snapshots,
unknown ones hold the anchor's values. Otherwise it is written exactly
as before. Pending time stays out of totals and expected coverage, and
the day waits for it before closing.

On open Core marks the cursor as stopped instead of deleting it, so a
brief stop is bridged as an estimate and a longer one starts a new
period as before. Older Cores still delete the cursor on open.

Relates to #1750.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(history): forgiving Home use display, strict marker for training

Follows Fredrik's two-layer direction (19 October). Display is
forgiving: an unknown PV, battery or EV source holds its last known
value; the site meter, a writer pause or a restart holds for at most
two minutes; a topology change no longer stops integration. A day's
flow is unknown only when it is unknown or unrecorded for more than a
tenth of the range.

Only two fully measured snapshots a safe tick apart, of one topology
and one process, make a measured interval. Every other interval keeps
EstimatedLoad, and quarter-hour retention now keeps that bit, so the
training layer can skip it.

Replaces the pending episodes of the first version. Closed source-era
days cached under the old rule are recomputed once.

Relates to #1750.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(history): stop holding a source that would make the house negative

A held value that turns the household residual negative cannot be
right: the source changed while unknown, such as a charger that
finished. That interval leaves the held flows unknown instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(history): a failed source leaves only its own flow unknown

historySources set MissingLoad for every reason except invalid_balance.
Telemetry puts a source's own failure there too, such as
"offline:sma:pv" or "stale:easee:ev", so a real snapshot with a silent
inverter or charger voided the whole balance and the source writer
held nothing. Only configuration and duplicate-flow reasons now set
MissingLoad; a failed source is unknown on its own flag.

Found by the #1755 session through historySources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
frahlg and others added 4 commits October 20, 2026 06:44
A PV inverter that sleeps at night is the most common remaining reason
for a day without Home use. Göran's SMA was silent for 145 s at 02:22 on
19 October with 0 W on both sides, longer than the 2-minute bridge.

While the sun is below the horizon at the site's map pin, Core now
records an unknown PV source as 0 W, marked EstimatedLoad, never as a
measurement. An inverter the watchdog took offline keeps its device in
the history topology, so the night is not cut out. The source writer
carries the marker into every interval an estimated snapshot bounds.

Without a pin (unset, 0,0 or the untouched default) nothing changes.
By day the gap stays unknown. Grid, battery and EV are not affected.

Relates to #1750.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Load models train only on fully measured intervals (#1754). Marking every
dark PV snapshot as an estimate would drop each night with a sleeping
inverter from training.

Past civil dusk (zenith over 96°) no panel produces, so an unknown PV
source now reads as a known 0 W without EstimatedLoad: the night counts
in Home use and in training. Between sunset and civil dusk diffuse light
remains, and the 0 W stays a marked estimate. No new bit records the
physical zero.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebased on the rewritten #1753, whose writer marks an interval as an
estimate at an unknown source, a stop, a long gap or a topology change.
A night past civil dusk therefore trains only if every snapshot is fully
known and the sleeping inverter keeps its device in the topology.

The devices line moves into historyDevices so the test drives the same
code as persistTelemetryTick. The test runs eight hours of a sleeping
inverter through it, with and without a map pin. TestDarkPVByTimeOfDay
now also checks that PV reads 0 W and that Load stays unknown for any
cause but PV; it replaces the two-hour series test, whose day-level
outcome the new writer decides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1753 (85100d6) no longer marks Load unknown when a single source fails,
so darkPV need not clear it. It now changes only the PV flow and its
marker; a configuration fault keeps Load unknown on its own. The night
test without a pin checks Home use again: the writer holds PV's last
value as an estimate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg force-pushed the 1750-pv-night-zero branch from cc892be to cc75e99 Compare October 20, 2026 04:45
@frahlg
frahlg changed the base branch from 1750-bridge-short-history-gaps to master October 20, 2026 04:45
@frahlg
frahlg marked this pull request as ready for review October 20, 2026 04:46
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T04:49:36.222164Z — Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@frahlg frahlg closed this Oct 20, 2026
@frahlg frahlg reopened this Oct 20, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +53 to +54
p.PVW = 0
p.Missing &^= state.MissingPV

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve output from PV sources that are still reporting

When a site has multiple PV flows and only one becomes unavailable during twilight, Known[DerPV] is false for the aggregate even though ForecastReading.PVW still contains output from the remaining fresh sources. historySources consequently zeroes the aggregate, and clearing MissingPV here records every inverter as 0 W; for example, one sleeping inverter plus another producing diffuse-light power makes Home use too low for the affected intervals. Apply the zero assumption only to the unavailable PV flows, while retaining the contributions from sources that are still reporting.

Useful? React with 👍 / 👎.

historyDevices read cached PV readings directly to find a sleeping
inverter. Move the health check into telemetry.OfflineDrivers, which
returns only driver names, so no caller handles a stale cached value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit 266b292 into master Oct 20, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant