feat(forecast): project irradiance onto each PV array's plane - #718
feat(forecast): project irradiance onto each PV array's plane#718HuggeK wants to merge 1 commit into
Conversation
|
This has a merge conflict against Not touching the branch myself: it is yours, and rebasing someone else's open work is how two people end up building the same thing twice. Worth saying while you are in there: at +1747/-26 across 20 files this is large for one review, and it will keep conflicting while it waits. The description already splits cleanly in two — the POA-per-array wiring is live and user-visible, the STRÅNG client is tested but not wired to anything. The first half improves every radiation-bearing provider today and could go in on its own; the second half has no user-visible effect yet and can follow. Two PRs would land faster than one. For anyone else reading: the RFC is Discussion #717, not an issue. |
1d756db to
3229d3a
Compare
Radiation-bearing providers previously produced one flat estimate, rated x (W/m2 / 1000), which ignored panel orientation entirely: a south-facing 35 deg roof and a flat one got the same forecast. When per-plane geometry is configured (Weather tab: tilt/azimuth/kWp), global horizontal irradiance is now projected onto each plane via the existing sunpos physics and summed. Providers publishing only GHI get an Erbs correlation to split direct from diffuse first. Sites with no arrays keep the previous behaviour, and Forecast.Solar - which already returns site-calibrated watts - is deliberately left untouched so its numbers are not scaled twice. Split out of the original srcfl#718 per review: this half is user-visible and improves every radiation-bearing provider today. The STRANG client and performance scoring that shared that branch move to a follow-up PR. Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.com> Signed-off-by: Hugo Karlsson <48095810+HuggeK@users.noreply.github.com>
3229d3a to
13bad76
Compare
|
Rebased onto This PR is now the POA-per-array wiring only: +341/-22 across 5 files, down from +1747/-26 across 20. It is the half that improves every radiation-bearing provider today, with no new config, endpoint or dependency. The STRÅNG client and the scoring package have been lifted out entirely — they are not in this diff. They move to a follow-up that actually wires STRÅNG as an irradiance source rather than leaving it unreferenced. Two things worth recording from the rebase, both of which turned out to be trivially additive: master's On the follow-up: I checked whether STRÅNG can also serve cloud cover, since that would let it back a forward path too. It cannot — the model publishes exactly seven parameters (116-122) and all are radiation quantities. Confirmed against the live API rather than the docs, which 404. Cloud cover is derivable though, and I verified the identity that makes it cheap: at solar noon params 121 + 122 = 723.0 + 87.5 = 810.5, exactly param 117, so the direct/diffuse split is internally consistent. Param 119 is sunshine duration capped at 60 min/h, which is a direct observational proxy for cloudiness. |
Projects forecast irradiance onto each PV array's plane instead of assuming a
flat panel.
Radiation-bearing providers previously produced one estimate,
rated × (W/m² / 1000), which ignores orientation entirely — a south-facing 35°roof and a flat one received the same forecast. When per-plane geometry is
configured (the Weather tab's PV arrays: tilt/azimuth/kWp), that GHI is now
projected onto each plane via the
sunposphysics already in the tree andsummed.
This is the first half of the original #718, split per @frahlg's review. It is
the user-visible half: it improves every radiation-bearing provider today, with
no new config, no new endpoint and no new dependency.
What changed
sunpos—POAFromComponents(sun, ghi, dhi, tilt, az)extracted so plane-of-arrayirradiance can be driven by measured components rather than only the built-in
clear-sky model.
POAnow delegates to it, so existing behaviour is unchanged.sunpos.POAFromGHI— Erbs correlation splitting GHI into direct + diffuse, forproviders that publish only global horizontal.
forecast—Service.Arrays, filled fromweather.pv_arrays; when arrays existthe per-plane sum replaces the flat estimate.
What is deliberately unchanged
the same geometry; projecting again would scale its numbers twice.
number those paths already consume.
Why Erbs, and what it costs when the provider gives no diffuse component
Projecting GHI onto a tilted plane requires knowing how much of it is beam and how
much is diffuse: the beam part depends on the angle of incidence, the diffuse part
largely does not. Providers like Open-Meteo publish only global horizontal, so the
split has to be inferred.
Erbs is the standard correlation for this — it estimates the diffuse fraction from
the clearness index
kt(GHI over extraterrestrial horizontal). It is empiricaland imperfect on partly-cloudy hours, but it is far closer than the implicit
alternative, which was to treat every panel as horizontal. Where a provider does
publish a diffuse component,
POAFromComponentstakes it directly and Erbs is notused at all.
Scope of the split, and where the rest went
The original branch also carried an SMHI STRÅNG client and an expected-vs-actual
scoring package. Those were tested but not wired to anything user-visible, which
is what made the diff hard to review at +1747/-26.
They now move to a follow-up PR that wires STRÅNG as an actual irradiance source
(and derives cloud cover from it), adds geographic-coverage metadata to every
weather provider, and swaps the location picker to MapLibre GL JS. This PR no
longer depends on any of that.
Testing
sunpos:POAis asserted identical toPOAFromComponentsdriven with theclear-sky components, so the refactor is provably behaviour-preserving; a
south-facing 35° plane at solar noon is checked to receive more than a horizontal
one; Erbs is checked at both clear and overcast
kt.forecast: arrays move the predicted watts in the expected direction, and a sitewith no arrays is asserted byte-identical to the old estimate.
Part of the roadmap in discussion #717.