A PUT body that pydantic validates as PydanticDateTimeDataFrame (or
PydanticDateTimeData) reached json.dumps(data), which cannot serialize
BaseModel instances - every such import failed with HTTP 400
"Object of type PydanticDateTimeDataFrame is not JSON serializable".
Serialize models via model_dump_json(), keep json.dumps for plain dicts.
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>
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.
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.
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>
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.
- 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.
Add provider descriptions and configuration examples for the three new PV
forecast providers to the prediction guide, a CHANGELOG entry, and regenerate
the affected auto-generated config docs.
Add PVForecastSolcast for the Solcast rooftop-site API. The operator registers a
site in the Solcast web app and enters the API key + resource (site) id:
GET /rooftop_sites/{site_id}/forecasts. pv_estimate (kW) is converted to watts
and fed as pvforecast_ac_power; the timestamp is normalised to the period start
(period_end - period) so it aligns with the resample axis. period_end is UTC.
Completes the set of selectable cloud PV forecast providers
(Akkudoktor, VRM, Import, pvnode, Forecast.Solar, Solcast). Adds tests for the
kW->W conversion, period-start normalisation, ISO-8601 period parsing, the
request URL/auth and HTTP-error handling.
Add PVForecastForecastSolar, a PV forecast provider for the free Forecast.Solar
API (https://forecast.solar), giving operators a no-account forecast source in
addition to Akkudoktor, VRM, Import and pvnode. An optional API key raises the
rate limit.
result.watts is the instantaneous AC power per timestamp, fed directly as
pvforecast_ac_power. Plants with several roof planes issue one request per plane
(Forecast.Solar is single-plane) and the powers are summed per timestamp.
Forecast.Solar azimuth (-180=N..0=S..90=W) is converted from EOS surface_azimuth
(north=0..south=180); local wall-clock timestamps are resolved via the response
timezone before resampling.
Registered in pvforecast.py and prediction.py. Adds tests for timezone
resolution, azimuth conversion, multi-plane summation and HTTP-error handling.
Add PVForecastPVNode, a native 15-minute PV forecast provider for the
pvnode.com V2 API, giving operators another forecast source to choose from
alongside Akkudoktor, VRM and Import.
Two request modes, selected by configuration:
* site_id set -> GET /v2/forecast/{site_id} (a saved, possibly calibrated
site managed on pvnode.com — the operator enters site id + API key)
* site_id empty -> POST /v2/forecast/inline (geometry sent inline from the
configured pvforecast.planes; no web-app setup required)
V2 response timestamps are site-local wall-clock accompanied by an IANA
timezone; they are resolved to absolute instants before EOS resamples them.
Nullable pv_power (e.g. at night) is treated as 0 W so the optimizer's linear
resampling does not interpolate phantom production across the night.
Registered in pvforecast.py (provider settings + id list) and prediction.py
(singleton, factory list, container union). Adds tests covering timezone
resolution, null handling, both request modes and HTTP-error propagation.