Skip to content

feat(forecast): project irradiance onto each PV array's plane - #718

Merged
frahlg merged 3 commits into
srcfl:masterfrom
HuggeK:worktree-strang-pvperf
Aug 4, 2026
Merged

feat(forecast): project irradiance onto each PV array's plane#718
frahlg merged 3 commits into
srcfl:masterfrom
HuggeK:worktree-strang-pvperf

Conversation

@HuggeK

@HuggeK HuggeK commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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 sunpos physics already in the tree and
summed.

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 endpoint and no new dependency.

What changed

  • sunposPOAFromComponents(sun, ghi, dhi, tilt, az) extracted so plane-of-array
    irradiance can be driven by measured components rather than only the built-in
    clear-sky model. POA now delegates to it, so existing behaviour is unchanged.
  • sunpos.POAFromGHI — Erbs correlation splitting GHI into direct + diffuse, for
    providers that publish only global horizontal.
  • forecastService.Arrays, filled from weather.pv_arrays; when complete
    arrays exist the per-plane sum replaces the flat estimate.
  • forecast — negative irradiance becomes zero; non-finite irradiance and PV
    estimates are omitted before they can become SQLite NULL.
  • config — array geometry keeps omitted tilt/azimuth distinct from explicit
    , and incomplete or invalid planes are ignored.
  • Weather help text — documents that Open-Meteo also uses the configured arrays.

What is deliberately unchanged

  • Sites with no complete arrays configured keep the previous flat estimate for
    GHI providers.
  • Forecast.Solar's site-calibrated response and request model remain unchanged;
    its configured arrays are now validated before request construction.
  • No control-tick, dispatch or optimizer-contract changes. This only sharpens a
    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 empirical
and 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, POAFromComponents takes it directly and Erbs is not
used 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 follow-up PR #734, which wires STRÅNG as an irradiance source,
adds geographic-coverage metadata, and updates the location UI. This PR no
longer depends on any of that.

Testing

sunpos: POA is asserted identical to POAFromComponents driven with the
clear-sky components, so the refactor is 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, a site
with no arrays is asserted identical to the old estimate, incomplete Stockholm
array metadata is ignored, and negative, zero, NaN and Inf GHI are covered with
and without arrays.

Part of the roadmap in discussion #717.

Current review state

P2 follow-up

The independent review of 1343ce8f identified three P2s. This head addresses
all three in the same branch:

  1. All GHI paths share one physical boundary: negative irradiance is stored and
    estimated as zero; non-finite irradiance is logged and the row is omitted;
    non-finite PV estimates are also omitted and negative PV is clamped to zero.
  2. Every configured array must have finite kWp, tilt and azimuth in range. A
    missing required field no longer becomes north. Complete arrays are
    projected; if none remain, GHI providers use the safe flat path.
  3. Config, Open-Meteo comments and the Weather tab now state that Open-Meteo
    uses per-array geometry. The rendered Weather tab was checked in a local
    browser; screenshot evidence was captured at /private/tmp/ftw-pr718-weather-tab.png.
    Human visual review is still required before merge.

Validation

  • go test ./internal/config ./internal/forecast ./internal/sunpos ./internal/mpc
  • make verify with Python 3.12.13, a project venv, and 37 drivers from
    drivers/BUNDLED_SOURCE.json
  • make verify-all: full Go and optimizer tests, external MPC tests, compose
    migration, container boundaries, vet, native build, and Linux ARM64, Linux
    AMD64, and Windows AMD64 builds
  • The pre-commit and pre-push hooks repeated the required verification; the
    pre-push check confirmed the branch was not behind origin/master.
  • All selected GitHub checks for head 55c2c64b are green; optimizer, drivers
    and module-boundary jobs are correctly path-skipped. Merge remains blocked by
    the draft state and independent review.

@frahlg

frahlg commented Jul 30, 2026

Copy link
Copy Markdown
Member

This has a merge conflict against master and cannot be reviewed as it stands (CONFLICTING / DIRTY). Several PRs landed today — #700, #706, #707, #715, #716, #721, #722 — so a rebase is needed before anyone can read the diff.

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.

@HuggeK
HuggeK force-pushed the worktree-strang-pvperf branch from 1d756db to 3229d3a Compare July 31, 2026 08:09
HuggeK added a commit to HuggeK/ftw that referenced this pull request Jul 31, 2026
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>
@HuggeK
HuggeK force-pushed the worktree-strang-pvperf branch from 3229d3a to 13bad76 Compare July 31, 2026 08:21
@HuggeK HuggeK changed the title feat(forecast): orientation-aware PV forecasts + STRÅNG scoring foundations (Phase 1 of #717) feat(forecast): project irradiance onto each PV array's plane Jul 31, 2026
@HuggeK

HuggeK commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto master and split, as suggested.

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 energy_ledger_* tables and this branch's pv_performance_daily were added to the same migration list slot, and #652 left ftw-history-card.js untouched so nothing in the UI work collided.

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.

frahlg pushed a commit to HuggeK/ftw that referenced this pull request Aug 4, 2026
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>
@frahlg
frahlg force-pushed the worktree-strang-pvperf branch from 13bad76 to a089ebc Compare August 4, 2026 08:03
frahlg pushed a commit to HuggeK/ftw that referenced this pull request Aug 4, 2026
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>
@frahlg
frahlg force-pushed the worktree-strang-pvperf branch from a089ebc to 1343ce8 Compare August 4, 2026 08:05
frahlg pushed a commit to HuggeK/ftw that referenced this pull request Aug 4, 2026
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>
@frahlg
frahlg force-pushed the worktree-strang-pvperf branch from 1343ce8 to 5e5c8d9 Compare August 4, 2026 08:50
frahlg pushed a commit to HuggeK/ftw that referenced this pull request Aug 4, 2026
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>
@frahlg
frahlg force-pushed the worktree-strang-pvperf branch from 5e5c8d9 to bdf1adb Compare August 4, 2026 08:55
frahlg pushed a commit to HuggeK/ftw that referenced this pull request Aug 4, 2026
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>
@frahlg
frahlg force-pushed the worktree-strang-pvperf branch from bdf1adb to f7c77e3 Compare August 4, 2026 08:58
frahlg pushed a commit to HuggeK/ftw that referenced this pull request Aug 4, 2026
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>
@frahlg
frahlg force-pushed the worktree-strang-pvperf branch from f7c77e3 to 6ab9da8 Compare August 4, 2026 08:59
frahlg pushed a commit to HuggeK/ftw that referenced this pull request Aug 4, 2026
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>
@frahlg
frahlg force-pushed the worktree-strang-pvperf branch from 6ab9da8 to 55c2c64 Compare August 4, 2026 09:01
frahlg pushed a commit to HuggeK/ftw that referenced this pull request Aug 4, 2026
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>
@frahlg
frahlg force-pushed the worktree-strang-pvperf branch from 55c2c64 to ecdeb87 Compare August 4, 2026 09:41
frahlg pushed a commit to HuggeK/ftw that referenced this pull request Aug 4, 2026
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>
@frahlg
frahlg force-pushed the worktree-strang-pvperf branch from ecdeb87 to 6e49b38 Compare August 4, 2026 09:46
claude and others added 3 commits August 4, 2026 11:56
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>
@frahlg
frahlg force-pushed the worktree-strang-pvperf branch from 6e49b38 to 2bd8eca Compare August 4, 2026 09:57
@frahlg
frahlg marked this pull request as ready for review August 4, 2026 11:36
@frahlg
frahlg merged commit 5d5f810 into srcfl:master Aug 4, 2026
13 checks passed

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

Reviewed commit: 2bd8eca94b

ℹ️ 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".

pvW = s.RatedPVW * (*r.SolarWm2) / 1000.0
case solarWm2 != nil:
var ok bool
pvW, ok = pvWFromGHI(s.Lat, s.Lon, r.HourStart, *solarWm2, s.RatedPVW, s.Arrays)

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 Project hourly GHI at the interval midpoint

When per-array geometry is enabled with Open-Meteo, this passes the provider's hour label directly to the instantaneous solar-position model. Open-Meteo's shortwave_radiation value is an average over the preceding hour, so near sunrise and sunset the projection uses the wrong elevation and azimuth; a nonzero interval ending just after sunset can even become zero because POAFromGHI sees the sun below the horizon. Preserve the radiation interval in RawForecast and project at its midpoint (and store it under the matching slot) rather than treating the label as an instantaneous HourStart.

Useful? React with 👍 / 👎.

Comment on lines +204 to +208
kt := 0.0
if i0h > 0 {
kt = ghi / i0h
}
dhi := ghi * ErbsDiffuseFraction(kt)

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 Bound the beam estimate at low solar elevation

When forecast GHI exceeds the instantaneous extraterrestrial-horizontal value—readily possible for an hourly average near sunrise or sunset—kt exceeds 1, Erbs fixes DHI at only 16.5% of GHI, and POAFromComponents divides the remaining 83.5% by a very small cosZ. For example, 100 W/m² at zenith 89° can become roughly 4.8 kW/m² on a sun-facing plane, yielding a many-times-nameplate PV forecast that can distort planner decisions. Cap the inferred beam/DNI to a physical bound or compute the clearness index from interval-averaged extraterrestrial irradiation before projecting it.

Useful? React with 👍 / 👎.

HuggeK added a commit to HuggeK/ftw that referenced this pull request Aug 5, 2026
…coverage

STRANG becomes a real irradiance source rather than an unreferenced client,
every external data source now says where in the world it works, and the
location picker moves off Leaflet.

STRANG's parameter set was mapped against the live API because SMHI's apidocs
pages 404: exactly 116-122 exist. Identification was confirmed by physics
rather than by guessing - at solar noon 121 + 122 = 723.0 + 87.5 = 810.5,
exactly parameter 117, and 119 caps at 60, i.e. minutes within the hour.

STRANG publishes no cloud cover; it is a radiation model. Cloudiness is
instead derived from sunshine duration as 1 - minutes/60, which is observed
rather than inferred but coarser: blind to thin cirrus, undefined at night.
CloudCover() therefore returns an explicit unknown instead of defaulting to
clear, because those two lead to opposite decisions.

The new coverage registry makes an existing silence explicit. STRANG is
Nordic-only and every price provider is European, so sites elsewhere were
getting empty results with no explanation (srcfl#726). GET /api/data-sources now
reports area, countries, licence and whether each source reaches this site,
and the Weather tab renders it under the map. Bounds are advisory: STRANG's
grid is rotated, so a lat/lon box can only ever be a superset - all four
in-box corners were probed and returned no data. False is definitive, true
means worth trying. Scoring now declines to start outside the domain instead
of retrying nightly forever.

Stacked on srcfl#718, which carries the plane-of-array wiring this builds on.

Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.com>
Signed-off-by: Hugo Karlsson <48095810+HuggeK@users.noreply.github.com>
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.

3 participants