Welcome to EnergyDB

EnergyDB is an open-source library for persisting full energy portfolios — assets, grid topology, and 3-dimensional time series — in one connected database backed by PostgreSQL and ClickHouse.

What is EnergyDB?

EnergyDB is a database for energy portfolios. It stores three things together in one connected system:

  • 🌳 An asset hierarchy — your fleet modeled as a tree (Portfolio → Site → WindTurbine, Battery, …) of arbitrary depth.

  • 🔗 Grid topology — typed edges (lines, transformers, pipes, interconnections) connecting any two assets, including across portfolios.

  • ⏱️ 3-dimensional time series — actuals and versioned forecasts attached to any node or edge, queryable as-of any point in time.

Structure lives in PostgreSQL, values live in ClickHouse, and stable UUID identity lets Python objects round-trip to the database without losing any structural state.

It extends TimeDB with persistent storage for EnergyDataModel hierarchies.

Why EnergyDB?

Most time-series systems are agnostic about what their series represent — they treat data as opaque (series_id, timestamp, value) triples. EnergyDB knows it is a portfolio, and links every series back to the asset or grid edge it describes.

  • 🔁 Round-trip persistence: every Element keeps its UUID7 from in-memory object to row primary key — renames, moves, and property edits become silent UPDATEs, never delete-then-insert.

  • 📋 Diffable structural changes: dry_run=True previews every insert, rename, move, and delete as a TreeDiff before you apply — no surprise mutations.

  • ⏱️ Time-of-knowledge queries: forecast revisions, corrections, and as-of backtests, powered by TimeDB.

  • 🧭 Lazy fluent navigation: client.get_node("Portfolio", "Site", "T01").read(...) resolves to one indexed SQL query, regardless of subtree size.

  • ⚖️ Unit conversion at the boundary: declare canonical units once; pint rescales every read and write automatically.

  • 🏢 Opt-in multi-tenancy: client.namespace("acme") binds a client view to one tenant — shared pool, namespace-stamped writes, and row filtering once the host application enables PostgreSQL RLS.

Quick Start

pip install energydb
from datetime import UTC, datetime

import energydb as edb
import pandas as pd

client = edb.Client()  # reads TIMEDB_PG_DSN / TIMEDB_CH_URL from env
client.create()        # PostgreSQL schema + ClickHouse series_values table

# 1. Declare a turbine with the series it will hold (metadata only).
t01 = edb.wind.WindTurbine(
    name="T01", lat=55.01, lon=3.02, capacity=3.5, hub_height=80,
    timeseries=[
        edb.TimeSeries(name="power", unit="MW",
                       data_type=edb.DataType.ACTUAL),
    ],
)

# 2. Wrap it in a site and a portfolio.
site = edb.Site(name="Offshore-1", lat=55.0, lon=3.0, members=[t01])
portfolio = edb.Portfolio(name="my-portfolio", members=[site])

# 3. Persist structure — create-only. Edit existing nodes via scope mutators.
client.register_tree(portfolio)

# 4. Write a day of hourly values for the turbine's power series.
start = datetime(2026, 1, 1, tzinfo=UTC)
df = pd.DataFrame({
    "valid_time": pd.date_range(start, periods=24, freq="1h", tz="UTC"),
    "value": [2.5 + 0.05 * i for i in range(24)],
})
client.get_node("my-portfolio", "Offshore-1", "T01").write(
    df, name="power", data_type="actual",
)

# 5. Read back — single asset.
client.get_node("my-portfolio", "Offshore-1", "T01").read(
    name="power", data_type="actual",
)

# Or across the whole portfolio in one fluent call.
client.get_node("my-portfolio").read(name="power", data_type="actual")

Release Notes

For version-by-version changes and migration notes, see:

Documentation

Indices and tables