Releases: yeaxi/solar-analytics
Release list
v2.3.0
Added
- Generalized the forecast source. Solar Analytics is no longer Forecast.Solar
only. The Energy Dashboard adapter binds to any integration that provides the
Home Assistant solar-forecast platform (Forecast.Solar, Solcast, ...),
resolving the helper through the Energy platform registry rather than a
core-only import. A newforecast_entitysource type reads a timestamped
Wh-per-period profile (wh_hours,wh_period, orwatt_hours_period) from a
chosen forecast entity, rejects restored states and non-Wh units, and admits
from profile horizon coverage plus a read-only state-change listener. Both
remain read-only and fail closed. An entity that exposes no timestamped
profile is rejected with the newunsupported_forecast_entity_contract
status; no energy is fabricated from a scalar. Config-entry version 6 adds
forecast_source_typeandforecast_entity_id. Forecast.Solar keeps its
exact model fingerprint, listener/wh_periodgates, and lineage key join;
non-Forecast.Solar Energy providers admit on a valid helper payload. - Soak checkpoint schema version 3. Collector envelopes must declare
forecast_source_type(energy_entrywithprovider_domain, or
forecast_entity); required fresh logs follow that source, and the soak
window must be at least 24 hours. - Imported historical actual production. On setup Solar Analytics reads the
configured actual PV energy sensor's long-term Recorder statistics
(hourly, never purged) throughstatistics_during_periodon the Recorder's
own executor, derives each local day's kWh from the cumulativesumdeltas,
and stores them in the newv2_imported_actual_dailytable. Counter resets
are recorded rather than turned into negative days, and each day carries the
fraction of its hours that were observed, measured against the real 23, 24 or
25 hours of that local day. - New
sensor.solar_analytics_imported_actual_historydiagnostic entity,
disabled by default, carrying the bounded daily points, the import status,
and an explicitreconstructed_from_recorder_statisticsprovenance label.
Imported actuals are not wired intovalid_paired_day, the rolling
accuracy window, or WAPE, and they do not shorten the 14-day accuracy
warm-up. Home Assistant never persists the timestamped forecast profile to
any state, so there is no recorded historical forecast to pair them against,
and splitting the one logged daily forecast scalar across hours is forbidden
by rule 4 of the recorder/forecast contract. scripts/verify_import_idempotency.py, a deterministic rerunnable check that
the import converges: it feeds a synthetic year of hourly statistics through
the real reconstruction and the real store write three times and compares the
row count and total kWh.- Read-only scanner coverage for the write paths this feature invites:
async_import_statistics,async_add_external_statistics,
async_adjust_statistics,hass.states.async_set,
hass.states.async_remove, any reference to the live Recorder database file,
and an assertion thatsqlite3stays imported only bystorage_v2.py. recorderinmanifest.jsonafter_dependencies(an ordering hint, not a
hard dependency: the entry still loads and reportsrecorder_unavailable).
Changed
-
Removed the always-
NoneVictron VRM forecast sensor and its payload keys.
User-facing labels, repair issues, and device model copy are now
provider-neutral ("Forecast power", "Forecast source status"); the underlying
entity ids, unique ids, status enum values, and payload keys are unchanged, so
existing dashboards and automations keep working. -
Generic Energy provider model identity is digest-only (domain + entry id);
foreign config scalars no longer appear in state, diagnostics, or SQLite.
Lineage reuse is scoped to the configured source, so switching forecast
source cannot keep writing accuracy onto the previous lineage. Payload
provenance (source_map.forecast,native_update_time_source) now names the
path that actually admitted the profile.forecast_solarmoved from
dependenciestoafter_dependencies. -
Shipping version
2.3.0participates in the lineagecontract_keyvia
NATIVE_ADAPTER_VERSION. Existing installs start a new accuracy lineage on
upgrade; the Forecast.Solar fingerprint and gate logic themselves are
unchanged.Evidence status for the import is PARTIAL. Home Assistant is not
installed in CI, so the Recorder call is exercised against a recording stub.
What is verified is the reconstruction arithmetic, the storage idempotency,
and that exactly one statistic id is requested through the read API with no
mutating call. What is not verified is live Recorder behaviour on a real
installation. Do not claim this path production-ready without that evidence. -
Pinned
requirements-dev.txtand Dependabot updates for GitHub Actions
and pip so CI tool versions stop floating. -
Hermetic tests that
strings.json,translations/en.json, and
translations/uk.jsonshare the same key tree, and that
pyproject.tomlversion matchesmanifest.json. -
GitHub Pages docs site (MkDocs Material) at https://yeaxi.github.io/solar-analytics/.
-
Home Assistant brand assets at
custom_components/solar_analytics/brand/
(icon.png256x256 andicon@2x.png512x512) so HACS renders a proper icon
for the integration and the "brands" validator passes without a
home-assistant/brands submission. -
Coordinator now emits a single warning log when the native Forecast.Solar
binding becomes unavailable and a matching info log when it recovers;
repeated identical statuses no longer log per 5-minute poll (silver-tier
"log-when-unavailable" pattern). -
CONTRIBUTING.mddocuments the one-time GitHub-side owner setup (repository
description, topics, and GitHub Pages source). -
Tag-triggered GitHub Release workflow.
scripts/release.pychecks that the
tag,manifest.json,pyproject.toml, and CHANGELOG heading agree, extracts
the changelog body as release notes, and attachessolar_analytics.zipfor
manual installs. HACS still installs from the tagged tree (zip_release
stays false).
Removed
- The six never-used
v2_backfill_*tables and their eight store APIs. They
had no callers and no tests, could not express the
observed_at_utc <= scheduled_at_utcadmissibility rule (noscheduled_at_utc
column), had a nullable column inside a PRIMARY KEY so theirON CONFLICT
never fired, and were never pruned. Storage schema version 5 drops all six on
first open; nothing read them, so nothing is lost. native.period_coverage_secondsand thenative.parse_native_profile
compatibility alias, both unreferenced.native._canonical_wh_hours, also unreferenced. Its one rule, dropping a
zero point at exact midnight, belonged to no shipped path;
normalize_native_wh_hourskeeps a non-midnight zero boundary and
quarantines the first cell instead.scripts/recorder_backfill_report.pyand its tworuff.tomlper-file
exceptions. The script opened an archived Recorder database directly and
rebuilt daily rows fromstates, which duplicated the shipped importer. It
could only recover a daily forecast scalar, and rule 4 of the recorder and
forecast contract forbids feeding that into accuracy. Thescripts/
inventory inCONTRIBUTING.mdnow namesverify_import_idempotency.py, the
one script left. The 2.2.1 note about the report stays as written; it was
true for that release.
Changed
- CI tests and lint now run on Python 3.14 (Home Assistant 2026.7 runtime)
with least-privilege permissions, concurrency cancellation, job
timeouts, and a split lint/pytest job. Docs Pages write permission is
limited to the deploy job. - README is now end-user only (install, setup, troubleshooting, bug reports).
Contributor, agent, and architecture docs stay inCONTRIBUTING.md,
AGENTS.md, anddocs/architecture/. manifest.jsondocumentationURL now points at the docs site.hacs.json: removed the invalid"hacs"key that made the HACS validator
reject the manifest; explicitly declared"zip_release": false.
Fixed
- Daily coverage could never clear its 95% / 90% gate, so
sensor.solar_analytics_analysis_statuswas stuck oninsufficient_data
and forecast accuracy never becameready. Forecast.Solar reports the whole
night as one zero-Wh period straddling local midnight, and every
boundary-crossing period was dropped, leaving only daylight in a numerator
divided by a full day. A zero-Wh period is now clipped at local midnight and
counted for both adjacent days; a boundary-crossing period that carries
energy is still excluded rather than apportioned by time. Existing days are
recomputed from the stored immutable snapshots, so the correction applies to
history already on disk. - Day length is now measured between two UTC instants instead of two local
datetimes sharing a timezone, which always reported 24 hours. DST transition
days are measured against their real 23 or 25 hours, and the three coverage
ratios are clamped at 1.0. - Every five-minute cycle re-read the whole accumulator table and rebuilt the
interval rows of every retained day, including days that finished weeks ago.
integrate_accumulatorsnow bounds its read on both sides of the requested
window, from one 30-minute bucket before the start through the end, and
interval rebuilding stops at a finalization marker inv2_runtime_stateso a
finished local day is built once. Measured over 17,520 buckets (365 days) with
a one-hour window, 25 runs: 17,520 rows fetched down to 3, median 21.682 ms
down to 0.008 ms, window total unchanged at 200.0 Wh and 3,600.0 covered
seconds. Rerun it withpython scripts/benchmark_accumulator_window.py. - A direct upgrade still repairs history. The marker carries its own
`INTERVAL_BUILD_REVISION...
v0.1.0
What's Changed
- Add multi-perspective-project-analysis skill by @yeaxi in #1
- Reusable, platinum-quality Solar Analytics refactor by @yeaxi in #2
- Platinum follow-ups: parallelism, translations, mypy, hassfest, repairs, payload split by @yeaxi in #3
- HACS validation fixes, brand assets, unavailable-transition logging, 2.2.1 by @yeaxi in #4
- Split user README from contributor docs and publish GitHub Pages by @yeaxi in #6
- Harden CI for Python 3.14 and pin the dev toolchain by @yeaxi in #7
- Fix the daily coverage gate so a valid paired day is reachable by @yeaxi in #16
- Import historical actual production from the Recorder, and delete the dead backfill scaffolding by @yeaxi in #17
- Remove the obsolete Recorder backfill path by @yeaxi in #18
- Bound accumulator reads to the requested window by @yeaxi in #19
- Stop rebuilding finalized interval history every five minutes by @yeaxi in #20
Full Changelog: https://github.com/yeaxi/solar-analytics/commits/v0.1.0