Commit Graph
1021 Commits
Author SHA1 Message Date
Andreas 78f6dfeb84 fix(elecprice): do not shorten the forecast by the source's own lag
The price series went flat towards the end of the horizon: a constant value
repeated for the last hours, exactly as long as the day-ahead source was behind.

The ETS extrapolation is appended after the last known price, but its length was
computed as `prediction.hours * slots_per_hour - covered_slots`, and
covered_slots is zero once the last known price lies before the run start. The
forecast therefore spanned prediction.hours measured from the last known price
rather than from now, and ended that much too early. Callers reading past that
point got the last record held constant.

With SMARD published up to 2026-09-08 23:45 and a run at 2026-09-09 13:00, the
forecast covered 09-09 00:00 to 09-12 00:00 while the horizon needed 09-12
13:00: 52 quarter-hour slots of flat price, right inside the trailing window the
terminal value curve is derived from.

The length is now measured from the last known value through to
`ems_start + prediction.hours`, which reduces to the previous formula whenever
the source is current. Both the electricity price and the feed-in tariff
provider had the same calculation.
2026-09-09 14:15:05 +02:00
Andreas faed0fd9f9 fix(pvforecast): survive a transient Open-Meteo outage
A single 503 from Open-Meteo answered /v1/prediction/update with 400 and left
every provider after PVForecastAkkudoktorLocal unrun: the provider raised on the
first failed request, and PredictionContainer.update_data re-raises whatever an
enabled provider raises. Open-Meteo returns 503 while it rotates its model runs
and 429 when the free tier is briefly saturated; both clear within seconds.

Retryable responses (429, 500, 502, 503, 504) and connection errors are now
retried three times with a growing pause, matching what the SMARD provider
already does. If the fetch still fails and the stored forecast reaches past the
run start, that forecast is kept for one more run rather than failing the update
- a forecast one run old beats no forecast at all. A cold start with nothing
stored still fails, because then there really is no PV forecast.
2026-09-09 13:08:38 +02:00
Andreas 983a23f0af fix(elecprice): survive a day-ahead source that has not published yet
Every morning before the day-ahead auction is published, /v1/prediction/update
answered 400 and no prediction was produced at all.

The provider asks for prices starting at the run day, because an existing
history sets past_days to 0. The optimization horizon always reaches past the
last published price, so an update is always considered necessary - and SMARD
publishes the next day around midday. Between midnight and publication the
requested window therefore contains nothing, and ElecPriceSMARD raised
"SMARD response contains no usable day-ahead prices", which failed the whole
prediction update rather than only that provider.

ElecPriceEnergyCharts and its SMARD subclass now keep their existing history and
let the ETS/median branch extrapolate the remaining slots, the same fallback
FeedInTariffEnergyCharts already had. A cold start without any history stays
fatal. ElecPriceSMARD also separates the two cases it used to conflate: a period
the source has not published yet now reports the latest value it does have, and
only a response without a single price still reads as unusable.

Fixes the cache noise this produced as well. cache_in_file claimed its cache
entry before calling the wrapped function, so a raising function left an empty
file behind and every later call within the TTL logged "Read failed: Ran out of
input" before refetching. The entry is now created only after the call returns.
2026-09-09 12:57:39 +02:00
Andreas 280ee761e6 fix(optimization): make the genetic diversity boost an actual intervention
On a converged population the boost was permanently on, so there was nothing
left for it to intervene in. Its trigger, DIVERSITY_BOOST_THRESHOLD at 0.35, sat
above SELECTION_DIVERSITY_FLOOR at 0.30 - the floor the selection itself
guarantees - so `diversity < threshold` was true in every generation after
convergence. The threshold now sits below the floor.

The immigrants the boost injects are by construction the worst individuals in
the pool, and `_select_diverse` ran a plain tournament over parents and
offspring together, so they were removed in the very generation that created
them and their genes never recombined. A bounded share of seats
(IMMIGRANT_PROTECTION_FRACTION) is now reserved for them for
IMMIGRANT_PROTECTION_GENERATIONS selections. The incumbent is protected by
genome key, so no immigrant can evict the best solution or an equal-genome twin,
and offspring do not inherit the protection.

The log line was edge-triggered on `diversity_boost_active`, which was also
cleared on every fitness improvement, so a running boost re-announced itself
with "stagnation 0" while a boost that never stopped looked like several short
ones. It now tracks the boost alone, and the end of a boost is logged too.

Measured on a converged population: without protection 0 of 12 immigrants
survive the selection, with it all 12 do, and the incumbent is kept either way.
2026-09-09 07:57:10 +02:00
Andreas 6dc58c33e2 feat(pvforecast): keep outages out of the local provider's calibration
A battery or inverter failure limits PV to local demand for days. The
calibration read that as the plant's true output and learned it as a permanent
model loss, so one outage degraded the forecast long after the hardware was
fixed.

Calibration now estimates the healthy plant ratio over
`calibration_reference_days`, excludes days below `calibration_outage_threshold`
of it, and falls back to the most recent `calibration_min_healthy_days` when the
normal window is contaminated. `calibration_outage_filter_enabled` turns this
off for plants where measured curtailment, not available potential, is the
prediction target.

The fit also uses native 15-minute meter readings when every configured PV meter
supplies them - never interpolating hourly counters into an invented
quarter-hour profile - interpolates azimuth factors smoothly between bin centres
instead of stepping the EMS input curve, and normalizes the shape per forecast
day so it redistributes energy without changing that day's kWh correction. The
default azimuth bin widens from 15 to 45 degrees, which is what a typical
window actually supports.

Fixes the calibration window itself: it was derived from the measurement store
as a whole rather than from the configured PV production meters. A load meter
reaching further than the PV meter placed the window where no PV reading exists,
so calibration silently fell back to hourly fitting or skipped itself.

Also fixes `Measurement.load()`, which discarded every stored record. It
validated the file into a "temporary" Measurement, but Measurement is a
singleton, so that instance was the live one and the parsed records were
dropped.
2026-09-09 07:56:55 +02:00
Andreas a2f4ef6f54 feat(optimization): split the control horizon from the forecast tail
The optimizer treated the end of `optimization.horizon_hours` as the end of the
world: energy left in the battery there was worth a single configured price per
kWh, so it either dumped the battery into the last hours or hoarded it,
depending on that one number.

The horizon is now two spans. `horizon_hours` still receives every control
command. The new `optimization.tail_horizon_hours` (default 48 h) is a pure
lookahead that never produces a command. In AUTO terminal-value mode a
deterministic dynamic program solves that tail backwards on a 101-point SoC
grid using the production battery and inverter models - SoC bounds, power caps,
conversion losses, configured charge and export rates, direct-marketing
permission and LCOS on delivered DC energy - and the existing AUTO proxy
supplies the continuation value at the tail end. Genetic fitness reads the
resulting curve. `tail_horizon_hours: 0` restores the plain proxy at the control
end, FIXED is unchanged.

The forecast budget is reported, never enforced by refusal: a tail that does not
fit is shortened to what the forecast covers and reported as
`effective_tail_hours`, and a control horizon that does not fit is warned about
at configuration time and rejected by the optimizer at run time, which knows
which series ran out. `prediction.hours` defaults to 72 so the new defaults fit
out of the box; existing shorter configurations keep starting.

Control arrays and warm-start genomes now begin at the run timestamp rather than
midnight, flagged by `controls_start_at_now` so the adapters still read older
solutions. `forecast_interval_seconds` declares the resolution of shortened
native quarter-hour inputs.

Required forecasts are no longer silently replaced by demo providers. A missing
PV, price, load, feed-in or weather forecast used to rewrite the configured
provider and retry, so a run could quietly optimize against invented data.
Missing values now stay missing, and provider values are held only within their
own source interval instead of being extended indefinitely.

Also fixes a config update that could leave EOS half-updated: the merged
candidate is validated before the singleton is reinitialized.

Four provider tests that hard-coded the old 48 h prediction default are rewritten
to derive their expectations from the configured horizon.
2026-09-09 07:56:38 +02:00
Andreas 88150d46e3 chore: ignore the artifacts the test suite writes into the work tree
test_configmigrate leaves a <fixture>.new working copy behind whenever a
migration test fails, test_visualize writes example_report.pdf into the current
directory, and test_config creates tests/testdata/docs/. All three showed up as
untracked noise in every status and invited accidental commits.
2026-09-09 07:56:07 +02:00
Andreas f976335122 feat(pvforecast): local pvlib provider with measurement calibration
Add PVForecastAkkudoktorLocal, which runs the modelling chain inside EOS on
raw Open-Meteo irradiance instead of calling a forecast service: solar
position, horizon shading, plane transposition, incidence-angle modifier,
cell temperature, PVWatts DC and inverter AC.

It needs no API key and serves up to 16 days at 15-minute resolution from a
single hourly request, which is what keeps `optimization.tail_horizon_hours`
fed - services wrapping Open-Meteo cut the horizon much shorter. Several
Open-Meteo models can be listed in `weather_models` and are averaged per
variable at no extra request cost.

With `calibration_enabled` the provider fits itself against
`measurement.pv_production_emr_keys` over the past `calibration_days`: a
global scale factor plus optional per-solar-azimuth factors, each weighted by
modelled energy, shrunk toward the global factor by `calibration_prior_kwh`
and clamped to `[calibration_min_factor, calibration_max_factor]`. The
comparison runs on past intervals, where Open-Meteo serves analysed rather
than forecast weather, so it corrects the error of the PV model and not that
of the weather forecast.

Calibration is a scale factor on the output and never touches `userhorizon`,
`surface_tilt`, `surface_azimuth` or `peakpower`. The docs say so, and say
why a short window and a plant fault inside it are the two ways to end up
with a misleading factor.

Also add `Measurement.pv_production_total_kwh()` alongside the existing load
total, and `scripts/pvforecast_backtest.py`, which scores configuration
variants against the stored meter readings without waiting for new forecasts
to come true.
2026-09-06 18:21:20 +02:00
Andreas c0c9a1f669 feat(optimization): report the knee of the terminal value curve
Everything up to the knee is backed by residual load, everything beyond
it only by an export at the median feed-in tariff - the two halves have
very different confidence. The breakpoint list alone does not say where
the split is, so consumers had to guess it from the segment lengths.
2026-09-04 11:41:23 +02:00
Andreas 3074018bed fix(optimization): say why the terminal value fell back to the fixed scalar
A run whose price forecast is all zeros produces no priced residual load,
so AUTO cannot derive a curve and quietly credits the request scalar
instead. The reported mode was then "FIXED" - indistinguishable from a
run actually configured that way.

The solution now carries a reason, and the fallback is logged as a
warning instead of passing unnoticed.
2026-09-04 11:23:35 +02:00
Andreas 1c10ab83ad feat(optimization): concave terminal value for the energy left in the battery
The energy still stored when the horizon ends keeps its worth: it replaces
grid imports that are paid for afterwards. Crediting that with a single
price per kWh cannot describe it, because the value is not linear in the
amount stored. The first kWh replaces the most expensive hour that PV
cannot cover, the next one the second most expensive, and once every such
hour is served, further energy replaces nothing.

A scalar has to pick one slope for all of it. High enough for the first kWh
means hoarding a full battery; low enough for the last kWh means running it
empty by the end of the horizon - which is exactly what the previous default
of 0 EUR/kWh did.

terminal_value_mode = AUTO (the new default) builds the curve instead. There
is no forecast beyond the horizon, so its trailing window stands in for the
day that follows: residual load max(load - PV, 0) per slot, priced at its
import price, sorted and accumulated. LCOS is subtracted from every marginal
value so stored energy is not credited twice, and the tail beyond the
residual load is only credited when direct marketing allows an export. The
curve is built once per run; the search only interpolates on it.

The solution reports what a run used as terminal_value, curve included, so
the shape can be inspected instead of guessed. FIXED restores the previous
scalar behaviour.

In a 48 h scenario with two cheap slots at the end, AUTO keeps the battery
at 50 % and credits 3.85 EUR where FIXED with 0 EUR/kWh drains it to empty.
The stored optimization results move accordingly - the objective changed.
2026-09-04 10:37:48 +02:00
AndreasandClaude Opus 5 2a9543e710 fix(optimization): clear disabled AC charge in the reported plan
With grid charging switched off (inverter.max_ac_charge_power_w = 0) the
simulation zeroes the AC charge array before using it, but it did so by
rebinding a local name. The solution is read back from the original array,
so it still carried the optimizer's AC charge genes - genes the fitness
never evaluated, because the simulation ignored them, and which are
therefore arbitrary.

The effect was visible as a slot with ac_charge = 0.8 where the battery SoC
does not move. Harmless inside EOS, but a controller that follows the plan
would grid-charge the battery at a time nobody planned or paid for.

Zero the array in place so the reported plan matches what was simulated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 18:56:42 +02:00
AndreasandClaude Opus 5 a6b20e6de2 docs: clarify EV deadline weighting and export rate behaviour
Two questions that came up while using the new fields: how binding the EV
charging deadline actually is (soft, but the default penalty outweighs the
energy cost by roughly forty to one, and the target is met tightly), and
why grid_export_rates can vanish from the saved config file (values equal
to the default are omitted; /v1/config shows what is effective) together
with when a partial export level pays off at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 18:36:48 +02:00
AndreasandClaude Opus 5 f24d9ea0eb feat(optimization): deadlines for consumers and EV, graded grid export
Three related scheduling improvements, all opt-in and behaviour-preserving
when the new fields are not set.

Flexible consumers get absolute time bounds next to the recurring
time_windows: earliest_start_datetime and deadline_datetime, where the
deadline requires the complete run to have *finished* before that moment
("clean dishes by 03:00 tonight"). When no start can meet it,
deadline_policy decides between BEST_EFFORT (run as early as possible, so
the delay rather than the cost is minimized) and STRICT (keep the
deadline; a ONCE consumer then fails the optimization). The solution
reports appliance_deadline_missed per device.

The EV charging target can be given the same kind of deadline, as an
absolute min_soc_deadline_datetime and/or a relative min_soc_max_duration_h
("full in 6 hours"), the earlier of the two winning. The ev_soc_miss
penalty is then evaluated at that slot instead of at the end of the
horizon, and the seeding heuristic only proposes charge slots before it.

Battery-to-grid export under direct marketing is no longer all-or-nothing:
grid_export_rates configures the selectable export levels as a factor of
the rated discharge power (default [0.25, 0.5, 0.75, 1.0]). Each rate is
its own optimizer state, with the full-power state keeping its previous
index so existing seeds and heuristics are unaffected. The chosen level
per slot is reported in battery_grid_export_factor and as the
GRID_SUPPORT_EXPORT operation factor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 17:53:33 +02:00
AndreasandClaude Opus 5 8926cc7ae0 feat(pvforecast): send plane horizon to Forecast.Solar
A plane's userhorizon uses the same convention as the Forecast.Solar
horizon query parameter (evenly distributed heights in degrees, starting
north, clockwise), so pass it through instead of dropping it. Without it
a shaded plane is forecast as if it had a free horizon.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 17:53:22 +02:00
Andreas 57d2917c0c Fix seasonal SMARD retail price forecast 2026-08-01 13:05:37 +02:00
Andreas 69ef57d9c9 Add SMARD quarter-hour price provider 2026-08-01 12:21:06 +02:00
Andreas f7e2ac3619 Add adaptive genetic evolution 2026-07-16 15:14:13 +02:00
Andreas 4dfd4b275b Improve genetic optimizer convergence 2026-07-16 14:32:20 +02:00
Andreas 6465e22f07 fix(optimization): add battery self-consumption state 2026-07-16 12:59:02 +02:00
Andreas 5b8f7de113 Expand optimizer seeding and cache fitness 2026-07-16 10:35:52 +02:00
Andreas b0b437f1d7 Improve genetic optimizer seeding 2026-07-16 09:56:50 +02:00
Andreas d4056af0f6 Add resilient market feed-in tariff providers 2026-07-15 16:10:34 +02:00
AndreasandClaude Opus 4.8 67cf6f7d8a feat(optimization): schedule any number of flexible consumers
Replace the single hourly "dishwasher" home appliance with a list of
flexible consumers (home_appliances). Each consumer defines its load
either as an explicit power profile (energy-preservingly resampled onto
the optimization slot grid, incl. 15-min and non-integer interval ratios)
or the flat consumption_wh/duration_h fallback, and runs ONCE or DAILY
within its time windows and the optimization horizon.

- ConsumerScheduleMode + shared load-definition validation (XOR of
  profile/fallback, reject negative/NaN/inf, unique device_id)
- ApplianceGeneLayout: variable appliance gene block (index into
  allowed_start_slots), ONCE/DAILY calendar-day based, no snapping
- per-device output: result.home_appliance_energy_wh, appliance_starts
  (absolute local times), per-device solution columns and DDBC RUN/OFF
  instructions on state transitions only
- deprecate dishwasher/washingstart/Home_appliance_wh_per_hour with
  backward-compatible mapping and explicit conflict rejection
- max_home_appliances is now an upper bound only; no demo appliance and
  no on/off behaviour
- docs, openapi.json, CHANGELOG and optimize_result_2* fixtures updated;
  new tests/test_homeappliance.py covers the mandatory test matrix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 14:19:46 +02:00
Andreas c59bf1b486 docs: describe feed-in tariff forecast 2026-07-15 09:23:29 +02:00
Andreas bed1f0f275 feat(optimization): model battery LCOS and probabilistic bypass 2026-07-15 09:23:29 +02:00
AndreasandGitHub 8ddb7ce754 Update CHANGELOG with new features and fixes
Added new features and bug fixes related to PV forecasts and battery management.
2026-07-14 18:46:02 +02:00
Andreas a1b469fa38 perf: cache AC charge break-even prices 2026-07-14 17:54:15 +02:00
Andreas 92a8a093e8 feat: complete 15-minute optimization support 2026-07-14 17:00:07 +02:00
ChristinandAndreas 81a36cf355 feat(elecprice): serve native 15-minute Tibber prices for the quarter-hour grid
Request QUARTER_HOURLY exchange prices from Tibber and store them at their
native resolution instead of pre-averaging to hourly values. EOS resamples the
stored records onto the optimization grid on demand (key_to_array), so keeping
the native step size lets both the hourly (interval=3600) and the 15-minute
(interval=900) optimizer be fed the correct grid automatically.

- GraphQL: priceInfoRange resolution HOURLY -> QUARTER_HOURLY (last 960).
- _hourly_series -> _normalize_series: dedupe by timestamp (mean) + sort, no
  1h aggregation; add _resolution_seconds (median of timestamp diffs, fallback
  3600s).
- Resolution-agnostic ETS extrapolation: seasonal windows and history
  thresholds are scaled by slots_per_hour, needed forecast length and the
  prediction index step are computed in slots. Hourly behaviour is unchanged
  (slots_per_hour=1 -> 168/24 seasonal periods, hourly steps).
- Tests: replace the 1h-averaging test with resolution-preserving + dedup
  tests, assert QUARTER_HOURLY in the query, add a 15-min end-to-end test
  (native storage stays 15min, slot-based seasonal periods = 96, 15-min
  forecast index). Hourly backward-compat tests stay green unchanged.
2026-07-12 09:08:40 +02:00
ChristinandAndreas 3098605b0f feat(optimization): support a 15-minute optimization interval
The genetic optimizer was hard-wired to an hourly grid and forced
optimization.interval to 3600 s. Generalize it to a configurable slot grid
of length prediction.hours * (3600 / interval), accepting 900 (15 min) in
addition to the default 3600 (1 hour) so the optimizer can schedule on a
quarter-hour grid for 15-minute dynamic electricity tariffs.

- genetic.py: slot_duration_h / slots_per_hour / total_slots helpers; all GA
  vectors sized by total_slots; simulate()/evaluate() indexed by start slot.
- geneticparams.py: allow {900, 3600}; scale the load power series to per-slot
  energy, mirroring the PV series.
- battery.py / inverter.py: scale power caps to per-slot energy caps via
  slot_duration_h; homeappliance.py carries the hook.
- geneticsolution.py: serialize solution and plan on the slot grid (interval
  freq, start-slot offset, second-based instruction instants).

The default 3600 s interval keeps the previous hourly behaviour; the genetic
regression suite is unchanged. Adds tests for the 15-minute slot grid.
2026-07-12 09:08:39 +02:00
AndreasandClaude Fable 5 7f2ac9098c feat: Direktvermarktung mit Batterie-Netzeinspeisung
Fügt einen Direktvermarktungs-Modus (feedintariff.direct_marketing_enabled)
hinzu, der den Börsenpreis als Einspeisevergütung nutzt und aktive
Batterie-Entladung ins Netz (battery_grid_export_allowed) sowie
DC-Charge-Bypass optimiert.

- FeedInTariffEnergyCharts-Provider (Börsen-Einspeisetarif inkl. Prognose)
- Inverter: DC/AC-Wirkungsgrade und Batterie-Grid-Export in process_energy
- Genetik: Export-/DC-Charge-Zustände, Restwert-Bewertung des Akkus
- Solution-Result: neues Feld Feed_in_tariff (verwendeter Tarif je Stunde)
- Tests für neue Provider, Solution und Simulation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 09:01:33 +02:00
Andreas cc583600d8 fix: use stored history for Tibber price forecast 2026-07-09 11:20:46 +02:00
Andreas 15ba84b39c fix: add Tibber electricity price extrapolation 2026-07-09 10:38:21 +02:00
Andreas e381cbf542 feat: add Tibber price provider and PV forecast providers 2026-07-08 16:36:59 +02:00
Christin 32e3eb5c6e fix(prediction): drop module-level from urllib.parse import quote
tests/test_docstringrst.py scans every class/function member of each
module via inspect.getmembers() with no __module__ filter, so
`from urllib.parse import quote` pulled the stdlib quote() into the
pvnode and Solcast provider namespaces and its non-reST docstring failed
the docstring-compliance check.

Import `urllib.parse` as a module and call `urllib.parse.quote(...)`
instead: a module member is skipped by the isfunction/isclass scan, and
the fully-qualified call keeps mypy happy (the requests stubs lack quote,
which is why urllib.parse was chosen over requests.utils in the first place).

test_all_docstrings_rst_compliant now passes; isort/ruff/ruff-format/mypy
pre-commit hooks all green.
2026-07-07 06:27:01 +00:00
AndreasandGitHub d66a096d02 Elecprice Interpolation wrong
deprecated /strompreis API
2026-07-06 16:31:22 +02:00
NormannandGitHub 2c89e3fee9 Merge branch 'main' into feat/pvnode-forecast-provider 2026-07-06 01:21:29 +02:00
NormannandGitHub cd2992c18d Merge branch 'main' into codex/fehler-im-ladealgorithmus-untersuchen 2026-07-06 01:20:57 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1031c21f68 build(deps): bump fastapi from 0.138.2 to 0.139.0 (#1128)
Bump Version / Bump Version Workflow (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
Close stale pull requests/issues / Find Stale issues and PRs (push) Has been cancelled
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.138.2 to 0.139.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.138.2...0.139.0)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-version: 0.139.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 01:19:43 +02:00
NormannandGitHub 5a44dba663 Merge branch 'main' into feat/pvnode-forecast-provider 2026-07-05 15:03:42 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
36dcb269de build(deps-dev): bump coverage from 7.14.3 to 7.15.0 (#1129)
Bump Version / Bump Version Workflow (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
Close stale pull requests/issues / Find Stale issues and PRs (push) Has been cancelled
Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.14.3 to 7.15.0.
- [Release notes](https://github.com/coveragepy/coveragepy/releases)
- [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst)
- [Commits](https://github.com/coveragepy/coveragepy/compare/7.14.3...7.15.0)

---
updated-dependencies:
- dependency-name: coverage
  dependency-version: 7.15.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-05 15:03:08 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5ca7b39539 build(deps): bump fastapi-cli from 0.0.27 to 0.0.28 (#1130)
Bumps [fastapi-cli](https://github.com/fastapi/fastapi-cli) from 0.0.27 to 0.0.28.
- [Release notes](https://github.com/fastapi/fastapi-cli/releases)
- [Changelog](https://github.com/fastapi/fastapi-cli/blob/main/release-notes.md)
- [Commits](https://github.com/fastapi/fastapi-cli/compare/0.0.27...0.0.28)

---
updated-dependencies:
- dependency-name: fastapi-cli
  dependency-version: 0.0.28
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-05 15:02:48 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7774b9c4a9 build(deps): bump uvicorn from 0.49.0 to 0.50.0 (#1131)
Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.49.0 to 0.50.0.
- [Release notes](https://github.com/Kludex/uvicorn/releases)
- [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md)
- [Commits](https://github.com/Kludex/uvicorn/compare/0.49.0...0.50.0)

---
updated-dependencies:
- dependency-name: uvicorn
  dependency-version: 0.50.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-05 15:02:31 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
155929f10b build(deps): bump pillow from 12.1.1 to 12.2.0 (#1132)
Bumps [pillow](https://github.com/python-pillow/Pillow) from 12.1.1 to 12.2.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/12.1.1...12.2.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-version: 12.2.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-05 15:02:00 +02:00
Andreas cd5cdf8b47 fix(genetic): limit EV charging to unmet min SoC 2026-07-05 12:56:08 +02:00
Christin d4dc9fa662 fix(prediction): green up CI for the new PV providers (mypy + provider sequence)
- Use urllib.parse.quote instead of requests.utils.quote in the pvnode and
  Solcast providers: the runtime re-export exists, but the requests type stubs
  do not declare it, so the pre-commit mypy hook failed with
  'Module has no attribute "quote"'.
- Mark the force_update keyword in the new provider tests with `# type: ignore`
  — it is consumed by the cache_in_file decorator at runtime; same call
  convention and ignore style as pvforecastakkudoktor.py.
- Add PVForecastPVNode, PVForecastForecastSolar and PVForecastSolcast to the
  expected provider sequence in tests/test_prediction.py (fixture + index
  assertions) — the two sequence tests failed because the new providers were
  registered in prediction.py but missing from the hardcoded expectations.
2026-07-04 09:23:57 +00:00
NormannandGitHub 6f4a26a045 Merge branch 'main' into feat/pvnode-forecast-provider 2026-06-30 09:00:04 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
631d428e89 build(deps): bump fastapi from 0.138.0 to 0.138.2 (#1125)
Bump Version / Bump Version Workflow (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
Close stale pull requests/issues / Find Stale issues and PRs (push) Has been cancelled
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.138.0 to 0.138.2.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.138.0...0.138.2)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-version: 0.138.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 08:59:33 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f1157c0692 build(deps): bump python-fasthtml from 0.14.3 to 0.14.4 (#1126)
Bumps [python-fasthtml](https://github.com/AnswerDotAI/fasthtml) from 0.14.3 to 0.14.4.
- [Release notes](https://github.com/AnswerDotAI/fasthtml/releases)
- [Changelog](https://github.com/AnswerDotAI/fasthtml/blob/main/CHANGELOG.md)
- [Commits](https://github.com/AnswerDotAI/fasthtml/compare/0.14.3...0.14.4)

---
updated-dependencies:
- dependency-name: python-fasthtml
  dependency-version: 0.14.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 08:59:15 +02:00