Plan / Optimization Output Schema#
This page documents the columns of the optimization-result DataFrame returned by
publish_data (see API Reference).
Downstream consumers — Home Assistant cards, Node-RED flows, EVCC adapters, third-party
automations — can rely on this schema as a versioned contract.
Schema version#
Every result DataFrame carries the schema version as a pandas attribute:
result = await publish_data(...)
result.attrs["emhass_schema_version"]
# "1.0"
The version string is also exposed as a module-level constant:
from emhass.command_line import EMHASS_SCHEMA_VERSION
Semver rules#
Bump kind |
Trigger |
|---|---|
Patch ( |
Documentation clarification, sign convention confirmed, description sharpened. No consumer code change required. |
Minor ( |
New column added without removing or renaming existing columns. Consumers using |
Major ( |
Column removed, column renamed, sign convention flipped, unit changed, or HA-scaling factor changed for a column. |
Consumers pin to a major version. Code that requires schema ≥ 1.0 should check:
major = int(result.attrs.get("emhass_schema_version", "0").split(".")[0])
if major != 1:
raise RuntimeError(f"Unexpected EMHASS schema major {major}")
Column table#
11 fixed columns plus four variable groups: P_deferrable{k},
predicted_temp_heater{k}, heating_demand_heater{k} (for each configured
deferrable / thermal load), and cost_fun_<name> (one column per cost-function
component the chosen costfun decomposes into).
Column |
Source helper |
Unit |
Sign convention |
Conditional |
HA scaling |
|
Notes |
|---|---|---|---|---|---|---|---|
|
|
W |
positive = consumption |
always |
1:1 |
|
DataFrame column read directly |
|
|
W |
positive = production (PV → DC bus) |
when |
1:1 |
|
|
|
|
W |
positive = curtailed delta (subtracts from gross PV in DC balance) |
|
1:1 |
|
|
|
|
W |
AC-side power; positive = DC → AC delivery, negative = AC → DC (charging-from-grid via DC bus) |
|
1:1 |
|
|
|
|
W |
positive = consumption |
|
1:1 |
|
|
|
|
°C |
n/a (state) |
per |
1:1 |
|
|
|
|
kWh |
thermal energy delivered to the storage (heat in), not electrical input |
only set for |
1:1 |
|
|
|
|
W |
positive = discharge (battery → house), negative = charge (house/grid → battery) |
|
1:1 |
|
|
|
|
fraction (0..1) in CSV; ×100 in HA |
n/a (state) |
|
×100 |
|
|
|
|
W |
positive = import (grid → house), negative = export (house → grid) |
always |
1:1 |
|
|
|
|
€ |
n/a (cost) |
always; multi-column (filter on substring |
1:1 |
|
Components depend on |
|
|
text |
n/a |
always (defaulted to |
n/a |
|
CVXPY status strings: |
|
|
€/kWh |
n/a (price) |
always |
1:1 |
|
per-timestep tariff series for load |
|
|
€/kWh |
n/a (price) |
always |
1:1 |
|
per-timestep sell price series |
SOC_opt scaling callout#
⚠️
SOC_optis the most common consumer bug.The DataFrame value and the CSV export carry the state-of-charge as a fraction in [0, 1]. When the same column is published to Home Assistant it is multiplied by 100 to display as a percentage. Consumers reading the result DataFrame or the exported CSV must therefore multiply by 100 themselves if they expect percent; consumers reading the HA entity get percent already and must not double-scale.
Sign conventions#
All sign conventions are derived from src/emhass/optimization.py (the CVXPY model
definition). Per-column citations are inline in the column table above. Summary:
Quantity |
Positive means |
Negative means |
|---|---|---|
|
PV production (panels → DC bus) |
(always ≥ 0; PV does not consume) |
|
curtailed delta (subtracted from gross PV) |
(always ≥ 0; CVXPY |
|
DC → AC flow (delivered to house/grid) |
AC → DC flow (charging via DC bus) |
|
load consumption |
(always ≥ 0; loads do not export) |
|
battery discharge (battery → house) |
battery charge (house/grid → battery) |
|
import (grid → house) |
export (house → grid) |
Consumer recipes#
Python (pandas)#
from emhass.command_line import publish_data, EMHASS_SCHEMA_VERSION
result = await publish_data(input_data_dict, logger)
assert result.attrs["emhass_schema_version"].startswith("1.")
if "SOC_opt" in result.columns:
soc_percent = result["SOC_opt"] * 100 # CSV holds fraction
Node-RED#
The published HA entities carry the result columns by their friendly names (set per
custom_*_id config). A flow that depends on the schema should hard-code the major
version it was authored against and refuse to run if the EMHASS instance advertises a
different major (read from the EMHASS log line at startup, or via the /api/last-run
endpoint once that lands — see board item AC-3).
Source helpers#
Per-column source helpers in src/emhass/command_line.py:
Helper |
Approximate line range |
Columns published |
|---|---|---|
|
2152-2214 |
|
|
2215-2259 |
|
|
2260-2304 |
|
|
2305-2341 |
|
|
2342-2405 |
|
The aggregator publish_data (~line 2408) calls these in order and subsets the result
DataFrame to the published columns before returning.
Version history#
Version |
Date |
Change |
|---|---|---|
1.0 |
2026-05-10 |
Initial published schema. Sign conventions derived from |