Skip to main content
EnergyDataModel

🔋 Represent energy systems as Python data classes for improved modularity and readability

License: MIT PyPI version Join us on Slack All Contributors GitHub Repo stars

EnergyDataModel provides an open-source, Python-based data model that enables energy data scientists and modellers to write more modular and readable code. EnergyDataModel lets you:

  • 🧱 Modularity - Represent energy assets, energy systems and other relevant concepts as object-oriented building blocks;
  • 🏗️ Relationships - Structure your energy assets in graphs and hierarchies representing energy systems that can be serialized to files (e.g. .csv, .json, and .geojson files);
  • 🤓 Readability - Write more explicit Python code through human-readable expressions and built-in convenience methods;
  • 🧩 Interoperability - Convert data format to other energy-relevant data models and ontologies; and
  • 💬 Communicate - Communicate effectively in teams with a common energy system data vocabulary.

⬇️ Installation  |  📖 Documentation  |  🚀 Try out now in Colab  |  👋 Join Slack Community

Class hierarchy

Everything in EnergyDataModel inherits from a single root, Element, which carries identity (id: UUID, name), a list of attached TimeSeries (typically metadata-only — declarations of the series this element exposes), an optional shapely geometry, and an extra dict for ad-hoc JSON-scalar fields. Identity is a UUID7 generated at construction time — stable across renames, round-trips through JSON, and (when paired with EnergyDB) sits as the row primary key in PostgreSQL.

Three sibling subtrees specialize Element, plus an Asset mixin:

  • Node — graph vertices (equipment, areas, sensors, grid topology points). Adds members and tz.
  • Edge — relationships between two nodes (lines, transformers, interconnectors). Adds from_element, to_element, directed. Endpoints are Reference[T] objects holding the target's UUID.
  • Collection — logical groupings (Portfolio, Site, ...). Adds members and tz. Not a graph vertex — isinstance(portfolio, Node) is False.
  • Asset — cross-cutting mixin marking physical equipment. Adds commissioning_date. Mixed into Node via NodeAsset and into Edge via EdgeAsset; never used as a leaf type.

System-structural classes (containers, areas, networks, bases, utilities) live flat at edm.X. Technology-specific equipment lives under sub-namespaces — edm.solar.PVSystem, edm.wind.WindTurbine, edm.grid.Line, etc.

Namespace Data Classes
🧱 edm (core) Element, Node, Edge
🏷️ edm (bases) Asset, NodeAsset, GridNode, Sensor
☀️ edm.solar FixedMount, SingleAxisTrackerMount, PVArray, PVSystem, SolarPowerArea
🌬️ edm.wind WindTurbine, WindFarm, WindPowerArea
🔋 edm.battery Battery
💦 edm.hydro Reservoir, HydroTurbine, HydroPowerPlant
♻️ edm.heatpump HeatPump
🏠 edm.building Building, House
🌡️ edm.weather TemperatureSensor, WindSpeedSensor, RadiationSensor, RainSensor, HumiditySensor
⚡ edm.grid nodes: JunctionPoint, Meter, DeliveryPoint, Transformer
edges: Line, Link, Pipe, Interconnection, EdgeAsset
other: Carrier, SubNetwork, Network
🗺️ edm (area) Area, BiddingZone, Country, ControlArea, WeatherCell, SynchronousArea
📦 edm (containers) Collection, Portfolio, Site, MultiSite, Region, EnergyCommunity, VirtualPowerPlant
📈 edm (constructors) electricity_supply, electricity_demand, electricity_supply_area, electricity_demand_area, spot_price, cross_border_flow, grid_frequency, temperature, gas_supply, gas_demand, heating_demand

Explore the data model visually here.
Read the full documentation here.

Purpose and Philosphy

The aim of EnergyDataModel is to provide the energy data and modelling community with a Python-based open-source tool to enable improvement of software engineering aspects like code quality, maintainability, modularity, reusability and interoperability. We believe that bringing more rigorous software engineering practices to the energy data community has the potential to radically improve productivity, collaboration and usefulness of software tools, utimately leading to better energy decisions.

Our philosophy is aligned on usefulness and practicality over maximizing execution performance or some kind of esoteric theoretical rigor. A well-know quote by Abelson & Sussman comes to mind:

"Programs [software] are meant to be read by humans and only incidentally for computers to execute"

Making code explicit, readable and intuitive counts.

If you are interested in joining our mission to build open-source tools that improve productiveness and workflow of energy modellers worldwide - then join our Slack!

Basic usage

Create an energy system made up of two sites with co-located solar, wind and batteries and save as a JSON-file.

import energydatamodel as edm
from shapely.geometry import Point

pvsystem_1 = edm.solar.PVSystem(name="PV-1", capacity=2400, surface_azimuth=180, surface_tilt=25)
windturbine_1 = edm.wind.WindTurbine(name="WT-1", capacity=3200, hub_height=120, rotor_diameter=100)
battery_1 = edm.battery.Battery(name="B-1", storage_capacity=1000, min_soc=150, max_charge=500, max_discharge=500)

site_1 = edm.Site(name="Site-1",
                  geometry=Point(64, 46),  # (lon, lat)
                  members=[pvsystem_1, windturbine_1, battery_1])

pvsystem_2 = edm.solar.PVSystem(name="PV-2", capacity=2400, surface_azimuth=180, surface_tilt=25)
windturbine_2 = edm.wind.WindTurbine(name="WT-2", capacity=3200, hub_height=120, rotor_diameter=100)
battery_2 = edm.battery.Battery(name="B-2", storage_capacity=1000, min_soc=150, max_charge=500, max_discharge=500)

site_2 = edm.Site(name="Site-2",
                  geometry=Point(58, 51),
                  members=[pvsystem_2, windturbine_2, battery_2])

portfolio = edm.Portfolio(name="My Portfolio", members=[site_1, site_2])

portfolio.to_json()

Modeling a synchronous area with grid frequency

import energydatamodel as edm

nsa = edm.SynchronousArea(
    name="NSA",
    nominal_frequency=50.0,
    members=[
        edm.BiddingZone(name="SE-SE1"),
        edm.BiddingZone(name="SE-SE2"),
        edm.BiddingZone(name="NO1"),
        edm.BiddingZone(name="FI"),
    ],
    timeseries=[edm.grid_frequency()],
)

For more examples on usage and applications of EnergyDataModel see the documentation examples page here.

Extending EnergyDataModel

EDM is built to be extended. To add your own asset type, just inherit from the closest base — usually edm.NodeAsset for physical equipment, edm.grid.EdgeAsset for things connecting two nodes, or edm.Element for anything else. Use @dataclass(repr=False, kw_only=True) to match the rest of the model.

from dataclasses import dataclass
import energydatamodel as edm

@dataclass(repr=False, kw_only=True)
class Electrolyzer(edm.NodeAsset):
    capacity_kw: float | None = None
    efficiency: float | None = None

Your class is automatically registered for JSON round-trips — no decorator, no extra setup:

e = Electrolyzer(name="EL-1", capacity_kw=5000, efficiency=0.65)
site = edm.Site(name="H2 Site", members=[e])

raw = site.to_json()
restored = edm.Element.from_json(raw)
assert isinstance(restored.members[0], Electrolyzer)

The only requirement is that the module defining your class is imported before from_json is called. EDM looks up types by name in a process-wide registry, so the class must be defined for the loader to find it. In practice this means a single import my_assets (or from my_assets import Electrolyzer) at the top of any script that loads saved models — the same pattern used by every Python registry library. If the loader can't find a type, it raises ValueError: Unknown Element type 'Electrolyzer'.

For one-off custom fields that don't justify a new class, every Element carries an extra: dict[str, JSONScalar] you can populate freely:

pv = edm.solar.PVSystem(name="PV-1", capacity=2400, extra={"owner": "Acme", "asset_id_legacy": 17})

Values in extra are restricted to JSON-native scalars (str, int, float, bool, None) plus nested dict / list of the same. EDM types, enums, and shapely geometries are rejected — for typed values, define a typed subclass instead. This restriction is validated at to_json time and surfaces a clear TypeError when violated.

Cross-tree references

Edges and other cross-cutting links use Reference[T] to point at another Element by its UUID. References are valid the moment they're constructed and resolve lazily against an Index:

se4 = edm.BiddingZone(name="SE4")
dk2 = edm.BiddingZone(name="DK2")
icx = edm.grid.Interconnection(
    name="SE4-DK2",
    from_element=edm.Reference(se4),  # captures se4.id
    to_element=edm.Reference(dk2),
    capacity_forward=1700,
    capacity_backward=1300,
)
portfolio = edm.Portfolio(name="Nordic", members=[se4, dk2, icx])

# Resolve a Reference against the tree root (or pre-built Index).
icx.from_element.resolve(portfolio)
icx.from_element.get().name  # "SE4"

A bare Element passed as from_element / to_element on an Edge is auto-wrapped in a Reference at construction. JSON round-trip emits refs as {"__ref__": "<uuid>"} — single-pass deserialize, no two-pass walk.

Installation

We recommend installing using a virtual environment like venv, poetry or uv.

Install the stable release:

pip install energydatamodel

PVSystem.to_pvlib() requires the optional solar extra:

pip install energydatamodel[solar]

Install the latest release:

pip install git+https://github.com/rebase-energy/energydatamodel.git

Install in editable mode for development:

git clone https://github.com/rebase-energy/energydatamodel.git
cd EnergyDataModel
pip install -e .[dev] 

Ways to Contribute

We welcome contributions from anyone interested in this project! Here are some ways to contribute to EnergyDataModel:

  • Add a new energy system assets and concepts;
  • Propose updates to existing energy assets and concepts;
  • Create a converter to new data format; or
  • Create a converter to another energy data model.

If you are interested in contributing, then feel free to join our Slack Community so that we can discuss it. 👋

Contributors

This project uses allcontributors.org to recognize all contributors, including those that don't push code.

Sebastian Haglund
Sebastian Haglund

💻
Nelson
Nelson

🤔

Licence

This project uses the MIT Licence.

Acknowledgement

The authors of this project would like to thank the Swedish Energy Agency for their financial support under the E2B2 program (project number P2022-00903)

Metadata

Release files for energydatamodel 0.12.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for energydatamodel 0.12.0
File Size Uploaded
energydatamodel-0.12.0.tar.gz 43.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for energydatamodel 0.12.0
File Interpreter ABI Platform
energydatamodel-0.12.0-py3-none-any.whl Python 3 none any Details

Total release size: 77.7 kB

Release files / energydatamodel-0.12.0.tar.gz

Download URL energydatamodel-0.12.0.tar.gz
Size 43.1 kB
Tags Source
SHA-256 checksum
How to use checksums
fd887dc96abbc6a7b0a25cd6b70d2d050ce6bb05c5e262d342f83c8cc610ecc6
BLAKE2b-256 checksum
How to use checksums
652587e6f125e8266c73586ffb65c329c7cbece219d689433b4b62049a399297
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Release files / energydatamodel-0.12.0-py3-none-any.whl

Download URL energydatamodel-0.12.0-py3-none-any.whl
Size 34.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0de97933fc0641fe6fca23f85a2d5ecf08c3375c4b0c25c160617f1ca3a0002a
BLAKE2b-256 checksum
How to use checksums
57a45e643993d9128faa501ba91b6e0dcb8cc9e8d6f43da97a4292657c84c0fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.12.0 This release

2 release files

0.11.0

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

1 release file

0.0.2

1 release file

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page