Skip to content

feat(backend): model the PV forecast in-process - #1041

Merged
eetu merged 1 commit into
mainfrom
pv-forecast-rust-port
Sep 8, 2026
Merged

eetu merged 1 commit into
mainfrom
pv-forecast-rust-port

Conversation

@eetu

@eetu eetu commented Sep 8, 2026 •

Copy link
Copy Markdown
Owner

Replaces the external Python CLI and its three-hourly timer with the model
itself, in backend/src/pv/forecast/. It adds three parameters to the Harmonie
stored query the weather module already calls, then runs Perez-Driesse
transposition onto the plane of the array, Martin & Ruiz reflection losses, King
module temperature and Huld DC output. parse_timeseries_xml and the
pv_forecast_points storage are reused as they stand. One new dependency,
solar-positioning (MIT, chrono-only).

Configuration is the array's mounting only: PV_TILT, PV_AZIMUTH,
PV_KW. Position is the house pin already in user_settings — the one
LocationForm writes and the weather and sunrise views use. Until it is set the
loop idles.

Breaking: POST /api/pv/forecast is removed, along with
scripts/refresh-pv-forecast.sh. Nothing here consumed it; the frontend only
read. ../raspi/tasks/halo.py still provisions a timer that POSTs to it, which
needs retiring if that host has not yet moved to keel.

Verification

73 tests, clippy and fmt clean. Every model step is checked against pvlib
reference values committed in pv/forecast/testdata/: splines and Perez-Driesse
to 1e-9, reflection to 1e-12, and a synthetic clear-sky day through the whole
chain at 1e-4 relative — two NREL SPA implementations agree only to about 1e-5
degrees, which grazing incidence amplifies.

FMI stamps each accumulation at the end of the hour it covers, so sun position
is taken at the hour's midpoint and the point labelled with its start. A fixture
test pins the labels, since getting it wrong shifts every value by an hour
without any value-level test noticing.

scripts/compare-pv-forecast.py diffs this against the Python implementation on
live FMI, neither touching the database:

shared    : 65
reference only: 5, of which 0 non-zero

field          mean rel  worst rel
outputW        8.35e-07   7.97e-06
temperature    0.00e+00   0.00e+00
moduleTemp     1.59e-07   1.87e-06

shared energy: 20.247 kWh vs 20.246 kWh (-0.0003%)

The five reference-only hours are ones whose radiation FMI did not supply. The
Python implementation reports those as 0 W, including hours when the sun was
up; this omits them.

Replaces the external Python CLI and its timer. `pv/forecast/` reads three
more parameters from the Harmonie stored query the weather module already
calls, then runs Perez-Driesse transposition, Martin & Ruiz reflection
losses, King module temperature and Huld DC output. Every step is checked
against pvlib reference values in `pv/forecast/testdata/`.

Configuration is the array's mounting only — PV_TILT, PV_AZIMUTH, PV_KW.
Position is the house pin already in `user_settings`.

BREAKING CHANGE: `POST /api/pv/forecast` is removed. Hours whose radiation
is missing from the FMI response are omitted rather than published as 0 W.
@eetu
eetu force-pushed the pv-forecast-rust-port branch from 6226b92 to d653640 Compare September 8, 2026 19:39
@eetu
eetu merged commit c651aaa into main Sep 8, 2026
3 checks passed
@eetu
eetu deleted the pv-forecast-rust-port branch September 8, 2026 19:42
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