EnergyDB extends TimeDB with persistent storage for EnergyDataModel hierarchies — portfolios, sites, and assets — links them to time series with full auditability, and models grid topology via typed edges.
EnergyDB bridges two libraries:
- EnergyDataModel defines energy assets in Python (wind turbines, solar PV, batteries, etc.) organized into hierarchies (Portfolio → Site → Asset → TimeSeries), plus grid topology (JunctionPoint → Line → JunctionPoint).
- TimeDB stores time series in PostgreSQL with three-dimensional temporal tracking (valid time, knowledge time, change time).
EnergyDB adds node and edge tables to the same PostgreSQL database and links them to TimeDB's time series, enabling SQL joins across both.
Portfolio
└── Site "Offshore-1"
├── WindTurbine "T01" ← static: capacity, hub_height, ...
│ ├── TimeSeries "active_power" ← stored in TimeDB
│ └── TimeSeries "wind_speed" ← stored in TimeDB
├── WindTurbine "T02"
├── JunctionPoint "BusA"
└── JunctionPoint "BusB"
└── Line "Cable-1" (BusA → BusB) ← edge with own TimeSeries
pip install energydbRequires Python 3.9+ and a PostgreSQL database (e.g., Neon, local Postgres, or any hosted provider).
Build the entire tree in Python — structure, series schemas, and data — then persist it in one call.
import energydb as edb
import polars as pl
from datetime import datetime, timezone, timedelta
from shapely.geometry import Point
from timedb import TimeDataClient
td = TimeDataClient()
client = edb.EnergyDataClient(td)
client.create() # creates the energydb schema + tables
# Helper to build a simple hourly DataFrame
base = datetime(2025, 1, 1, tzinfo=timezone.utc)
index = [base + timedelta(hours=i) for i in range(24)]
def hourly(values):
return pl.DataFrame({"valid_time": index, "value": values})
# 1. Declare the full portfolio as an EDM tree
# TimeSeries carries data inline, TimeSeriesDescriptor is schema-only
portfolio = edb.Portfolio(name="My Portfolio", members=[
edb.Site(name="Offshore-1", geometry=Point(3.0, 55.0), members=[
edb.WindTurbine(name="T01", capacity=3.5, hub_height=80, timeseries=[
edb.TimeSeries(hourly([2.5 + 0.1*h for h in range(24)]),
name="power", unit="MW", data_type=edb.DataType.ACTUAL),
edb.TimeSeriesDescriptor(name="power", unit="MW", data_type=edb.DataType.FORECAST,
timeseries_type=edb.TimeSeriesType.OVERLAPPING),
]),
edb.WindTurbine(name="T02", capacity=3.5, hub_height=80, timeseries=[
edb.TimeSeries(hourly([2.8 + 0.1*h for h in range(24)]),
name="power", unit="MW", data_type=edb.DataType.ACTUAL),
]),
]),
edb.Site(name="Rooftop-1", geometry=Point(4.5, 52.0), members=[
edb.PVSystem(name="PV01", capacity=10, surface_tilt=25, surface_azimuth=180, timeseries=[
edb.TimeSeries(hourly([5.0 + 0.2*h for h in range(24)]),
name="power", unit="MW", data_type=edb.DataType.ACTUAL),
]),
edb.Battery(name="B01", storage_capacity=1000, max_charge=500),
]),
])
# 2. Persist it — one call
# Upserts every node, registers every series, bulk-writes all data. Idempotent.
root_id = client.write(portfolio)
# 3. Read with the fluent API
client.node("My Portfolio").node("Offshore-1").node("T01").read(
data_type="actual", name="power",
start_valid=datetime(2025, 1, 1, tzinfo=timezone.utc),
)
# Subtree read — all actuals for 'power' across the portfolio
client.node("My Portfolio").read(data_type="actual", name="power")
# Filter descendants by EDM type
client.node("My Portfolio").find(type="WindTurbine").read(data_type="actual", name="power")
# 4. Reconstruct the full EDM tree from the database
tree = client.get_tree("My Portfolio", include_series=True)Want to try EnergyDB without a local setup? Open our Quickstart in Colab.
Note: Data persists only within the active Colab session. Additional notebooks are available in the
examples/directory.
| Project | Description |
|---|---|
| TimeDB | Time series database with auditability and overlapping forecast support |
| TimeDataModel | Pythonic data model for time series |
| EnergyDataModel | Data model for energy assets (solar, wind, battery, grid, ...) |
Contributions are welcome! If you're interested in improving EnergyDB, please open an issue or pull request.
Licensed under the Apache-2.0 License.
Find a bug or have a feature request? Open an Issue.