{
"cells": [
{
"cell_type": "markdown",
"id": "e54b7f66a06b",
"metadata": {},
"source": "# EnergyDB — Quickstart\n\nDeclare your energy portfolio — sites, turbines, arrays, batteries — as plain Python objects. One call persists the whole tree; one fluent line reads it back across every asset."
},
{
"cell_type": "markdown",
"id": "6bd0b3f4b536",
"metadata": {},
"source": "## 1. Setup\n\n`Client` reads connection settings from `TIMEDB_PG_DSN` (Postgres for hierarchy + series catalog) and `TIMEDB_CH_URL` (ClickHouse for time-series values)."
},
{
"cell_type": "code",
"execution_count": 1,
"id": "05e4db51372d",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:51.064899Z",
"iopub.status.busy": "2026-05-18T08:28:51.063108Z",
"iopub.status.idle": "2026-05-18T08:28:51.071057Z",
"shell.execute_reply": "2026-05-18T08:28:51.070311Z"
}
},
"outputs": [],
"source": [
"try:\n",
" import urllib.request\n",
"\n",
" import google.colab # noqa: F401\n",
"\n",
" urllib.request.urlretrieve(\n",
" \"https://raw.githubusercontent.com/rebase-energy/energydb/main/examples/colab_setup.py\", \"/tmp/colab_setup.py\"\n",
" )\n",
" exec(open(\"/tmp/colab_setup.py\").read())\n",
"except ImportError:\n",
" pass"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "5fd223a88e59",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:51.073285Z",
"iopub.status.busy": "2026-05-18T08:28:51.072994Z",
"iopub.status.idle": "2026-05-18T08:28:53.057609Z",
"shell.execute_reply": "2026-05-18T08:28:53.056353Z"
}
},
"outputs": [],
"source": [
"from datetime import UTC, datetime\n",
"\n",
"import energydb as edb\n",
"import pandas as pd\n",
"\n",
"client = edb.Client()\n",
"client.delete() # clean slate\n",
"client.create()"
]
},
{
"cell_type": "markdown",
"id": "53581bd14c0f",
"metadata": {},
"source": "## 2. Declare your portfolio\n\nA `Portfolio` is just a Python object — sites, turbines, arrays, batteries — with metadata-only `TimeSeries` declarations naming the series each node will hold. Build it top-down; the tree printout below is your structural deliverable."
},
{
"cell_type": "code",
"execution_count": 3,
"id": "bf2dee4a16af",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:53.061378Z",
"iopub.status.busy": "2026-05-18T08:28:53.060876Z",
"iopub.status.idle": "2026-05-18T08:28:53.072209Z",
"shell.execute_reply": "2026-05-18T08:28:53.071136Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Portfolio('my-portfolio')\n",
"├── Site('Offshore-1')\n",
"│ ├── WindTurbine('T01')\n",
"│ └── WindTurbine('T02')\n",
"└── Site('Rooftop-1')\n",
" ├── PVSystem('PV01')\n",
" │ └── PVArray('Array-1')\n",
" └── Battery('B01')\n"
]
}
],
"source": [
"# Step 1 — declare the series this turbine will hold (metadata only; data lands later via write).\n",
"power_actual = edb.TimeSeries(name=\"power\", unit=\"MW\", data_type=edb.DataType.ACTUAL)\n",
"wind_speed = edb.TimeSeries(name=\"wind_speed\", unit=\"m/s\", data_type=edb.DataType.ACTUAL)\n",
"power_forecast = edb.TimeSeries(\n",
" name=\"power\",\n",
" unit=\"MW\",\n",
" data_type=edb.DataType.FORECAST,\n",
" timeseries_type=edb.TimeSeriesType.OVERLAPPING,\n",
")\n",
"\n",
"# Step 2 — wrap them on a WindTurbine (and a sibling with a single declaration inline).\n",
"t01 = edb.wind.WindTurbine(\n",
" name=\"T01\",\n",
" lat=55.01,\n",
" lon=3.02,\n",
" capacity=3.5,\n",
" hub_height=80,\n",
" timeseries=[power_actual, wind_speed, power_forecast],\n",
")\n",
"t02 = edb.wind.WindTurbine(\n",
" name=\"T02\",\n",
" lat=55.01,\n",
" lon=3.04,\n",
" capacity=3.5,\n",
" hub_height=80,\n",
" timeseries=[edb.TimeSeries(name=\"power\", unit=\"MW\", data_type=edb.DataType.ACTUAL)],\n",
")\n",
"\n",
"# Step 3 — group the turbines under a Site that carries its own series too.\n",
"offshore_1 = edb.Site(\n",
" name=\"Offshore-1\",\n",
" lat=55.0,\n",
" lon=3.0,\n",
" timeseries=[edb.TimeSeries(name=\"demand\", unit=\"MW\", data_type=edb.DataType.ACTUAL)],\n",
" members=[t01, t02],\n",
")\n",
"\n",
"# Step 4 — same bottom-up pattern for the second site: array → system, plus a battery, then site.\n",
"pv_array = edb.solar.PVArray(\n",
" name=\"Array-1\",\n",
" capacity=10,\n",
" surface_tilt=25,\n",
" surface_azimuth=180,\n",
" timeseries=[edb.TimeSeries(name=\"power\", unit=\"MW\", data_type=edb.DataType.ACTUAL)],\n",
")\n",
"pv_system = edb.solar.PVSystem(name=\"PV01\", members=[pv_array])\n",
"battery = edb.battery.Battery(name=\"B01\", storage_capacity=1000, max_charge=500)\n",
"rooftop_1 = edb.Site(name=\"Rooftop-1\", lat=52.0, lon=4.5, members=[pv_system, battery])\n",
"\n",
"# Step 5 — assemble the portfolio.\n",
"portfolio = edb.Portfolio(name=\"my-portfolio\", members=[offshore_1, rooftop_1])\n",
"print(portfolio.to_tree())"
]
},
{
"cell_type": "markdown",
"id": "c8f0d0db9d3e",
"metadata": {},
"source": "## 3. Persist and write data\n\n`register_tree` creates every node, edge, and series declaration in one PG transaction — the UUIDs you set in Python become the row primary keys. Then `write` streams in the actual values, one short call per series."
},
{
"cell_type": "code",
"execution_count": 4,
"id": "2e453ffcee6f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:53.077526Z",
"iopub.status.busy": "2026-05-18T08:28:53.077308Z",
"iopub.status.idle": "2026-05-18T08:28:53.360951Z",
"shell.execute_reply": "2026-05-18T08:28:53.360373Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"58297317239439162"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"client.register_tree(portfolio)\n",
"\n",
"# A day of synthetic hourly values, varied by base level per series.\n",
"start = datetime(2026, 1, 1, tzinfo=UTC)\n",
"end = datetime(2026, 1, 2, tzinfo=UTC)\n",
"hours = pd.date_range(start, end, freq=\"1h\", inclusive=\"left\", tz=\"UTC\")\n",
"\n",
"\n",
"def synthetic_dataframe(base):\n",
" return pd.DataFrame({\"valid_time\": hours, \"value\": [base + 0.05 * i for i in range(len(hours))]})\n",
"\n",
"\n",
"offshore = client.get_node(\"my-portfolio\", \"Offshore-1\")\n",
"offshore.write(synthetic_dataframe(12.0), name=\"demand\", data_type=\"actual\")\n",
"offshore.get_node(\"T01\").write(synthetic_dataframe(2.5), name=\"power\", data_type=\"actual\")\n",
"offshore.get_node(\"T01\").write(synthetic_dataframe(7.5), name=\"wind_speed\", data_type=\"actual\")\n",
"offshore.get_node(\"T02\").write(synthetic_dataframe(2.7), name=\"power\", data_type=\"actual\")\n",
"\n",
"rooftop = client.get_node(\"my-portfolio\", \"Rooftop-1\", \"PV01\", \"Array-1\")\n",
"rooftop.write(synthetic_dataframe(5.0), name=\"power\", data_type=\"actual\")"
]
},
{
"cell_type": "markdown",
"id": "97d230cb869c",
"metadata": {},
"source": "## 4. Read across the portfolio\n\nThe pay-off. One fluent scope, many routings — full subtree, single-asset drill-down, series-name filter, node-type filter. Each line below is one indexed query."
},
{
"cell_type": "code",
"execution_count": 5,
"id": "d562d4551e2f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:53.363285Z",
"iopub.status.busy": "2026-05-18T08:28:53.363125Z",
"iopub.status.idle": "2026-05-18T08:28:53.498349Z",
"shell.execute_reply": "2026-05-18T08:28:53.496914Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"
shape: (72, 5)| valid_time | value | path | data_type | name |
|---|
| datetime[μs, UTC] | f64 | str | str | str |
| 2026-01-01 00:00:00 UTC | 2.5 | "my-portfolio/Offshore-1/T01" | "actual" | "power" |
| 2026-01-01 01:00:00 UTC | 2.55 | "my-portfolio/Offshore-1/T01" | "actual" | "power" |
| 2026-01-01 02:00:00 UTC | 2.6 | "my-portfolio/Offshore-1/T01" | "actual" | "power" |
| 2026-01-01 03:00:00 UTC | 2.65 | "my-portfolio/Offshore-1/T01" | "actual" | "power" |
| 2026-01-01 04:00:00 UTC | 2.7 | "my-portfolio/Offshore-1/T01" | "actual" | "power" |
| … | … | … | … | … |
| 2026-01-01 19:00:00 UTC | 5.95 | "my-portfolio/Rooftop-1/PV01/Ar… | "actual" | "power" |
| 2026-01-01 20:00:00 UTC | 6.0 | "my-portfolio/Rooftop-1/PV01/Ar… | "actual" | "power" |
| 2026-01-01 21:00:00 UTC | 6.05 | "my-portfolio/Rooftop-1/PV01/Ar… | "actual" | "power" |
| 2026-01-01 22:00:00 UTC | 6.1 | "my-portfolio/Rooftop-1/PV01/Ar… | "actual" | "power" |
| 2026-01-01 23:00:00 UTC | 6.15 | "my-portfolio/Rooftop-1/PV01/Ar… | "actual" | "power" |
"
],
"text/plain": [
"shape: (72, 5)\n",
"┌─────────────────────────┬───────┬─────────────────────────────────┬───────────┬───────┐\n",
"│ valid_time ┆ value ┆ path ┆ data_type ┆ name │\n",
"│ --- ┆ --- ┆ --- ┆ --- ┆ --- │\n",
"│ datetime[μs, UTC] ┆ f64 ┆ str ┆ str ┆ str │\n",
"╞═════════════════════════╪═══════╪═════════════════════════════════╪═══════════╪═══════╡\n",
"│ 2026-01-01 00:00:00 UTC ┆ 2.5 ┆ my-portfolio/Offshore-1/T01 ┆ actual ┆ power │\n",
"│ 2026-01-01 01:00:00 UTC ┆ 2.55 ┆ my-portfolio/Offshore-1/T01 ┆ actual ┆ power │\n",
"│ 2026-01-01 02:00:00 UTC ┆ 2.6 ┆ my-portfolio/Offshore-1/T01 ┆ actual ┆ power │\n",
"│ 2026-01-01 03:00:00 UTC ┆ 2.65 ┆ my-portfolio/Offshore-1/T01 ┆ actual ┆ power │\n",
"│ 2026-01-01 04:00:00 UTC ┆ 2.7 ┆ my-portfolio/Offshore-1/T01 ┆ actual ┆ power │\n",
"│ … ┆ … ┆ … ┆ … ┆ … │\n",
"│ 2026-01-01 19:00:00 UTC ┆ 5.95 ┆ my-portfolio/Rooftop-1/PV01/Ar… ┆ actual ┆ power │\n",
"│ 2026-01-01 20:00:00 UTC ┆ 6.0 ┆ my-portfolio/Rooftop-1/PV01/Ar… ┆ actual ┆ power │\n",
"│ 2026-01-01 21:00:00 UTC ┆ 6.05 ┆ my-portfolio/Rooftop-1/PV01/Ar… ┆ actual ┆ power │\n",
"│ 2026-01-01 22:00:00 UTC ┆ 6.1 ┆ my-portfolio/Rooftop-1/PV01/Ar… ┆ actual ┆ power │\n",
"│ 2026-01-01 23:00:00 UTC ┆ 6.15 ┆ my-portfolio/Rooftop-1/PV01/Ar… ┆ actual ┆ power │\n",
"└─────────────────────────┴───────┴─────────────────────────────────┴───────────┴───────┘"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"portfolio = client.get_node(\"my-portfolio\")\n",
"portfolio.read(data_type=\"actual\", start_valid=start) # everything — every series on every node\n",
"df = portfolio.read(data_type=\"actual\", name=\"power\", start_valid=start) # only 'power' values\n",
"portfolio.where(type=\"WindTurbine\").read(data_type=\"actual\", name=\"power\") # turbines only\n",
"portfolio.get_node(\"Offshore-1\", \"T01\").read(data_type=\"actual\", name=\"wind_speed\") # one asset, one series\n",
"df"
]
},
{
"cell_type": "markdown",
"id": "2ef5ad382526",
"metadata": {},
"source": [
"## 5. Reconstruct the tree\n",
"\n",
"Pull the whole portfolio back as an EDM tree — same UUIDs, ready for inspection or in-memory edits."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "ad211bb04d10",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:53.501258Z",
"iopub.status.busy": "2026-05-18T08:28:53.500987Z",
"iopub.status.idle": "2026-05-18T08:28:53.511182Z",
"shell.execute_reply": "2026-05-18T08:28:53.510376Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Portfolio('my-portfolio')\n",
"├── Site('Offshore-1')\n",
"│ ├── WindTurbine('T01')\n",
"│ └── WindTurbine('T02')\n",
"└── Site('Rooftop-1')\n",
" ├── PVSystem('PV01')\n",
" │ └── PVArray('Array-1')\n",
" └── Battery('B01')\n"
]
}
],
"source": [
"tree = client.get_tree(\"my-portfolio\", include_series=True)\n",
"print(tree)"
]
},
{
"cell_type": "markdown",
"id": "efbc64d75cd7",
"metadata": {},
"source": [
"## 6. Modify existing nodes and edges\n",
"\n",
"`register_tree` is create-only — to edit what's already persisted, use scope mutators (`rename`, `update`, `delete`, `move_to`, `add`) addressed by path or uuid. The node keeps its uuid, so attached series stay attached. Every mutator accepts `dry_run=True` to return a `TreeDiff` preview without committing. `client.transaction()` batches several mutations into one atomic commit; `.commit()` is required, exiting without it raises."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "5211112b55cd",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:53.514048Z",
"iopub.status.busy": "2026-05-18T08:28:53.513054Z",
"iopub.status.idle": "2026-05-18T08:28:53.541698Z",
"shell.execute_reply": "2026-05-18T08:28:53.540970Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Renamed: Offshore-Renamed (uuid=019e3a33-c9cd-7bfa-a262-1ba2f3cafbd5)\n"
]
}
],
"source": [
"client.get_node(\"my-portfolio\", \"Offshore-1\").rename(\"Offshore-Renamed\")\n",
"client.get_node(\"my-portfolio\", \"Offshore-Renamed\", \"T01\").update({\"capacity\": 4.0})\n",
"client.get_node(\"my-portfolio\", \"Offshore-Renamed\", \"T02\").delete()\n",
"\n",
"# Add a new turbine under Offshore-Renamed; .add() returns a scope at the new node.\n",
"client.get_node(\"my-portfolio\", \"Offshore-Renamed\").add(edb.wind.WindTurbine(name=\"T03\", capacity=4.5))\n",
"\n",
"renamed = client.get_node(\"my-portfolio\", \"Offshore-Renamed\").get()\n",
"print(f\"Renamed: {renamed.name} (uuid={renamed.id})\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "8dcc3c03",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:53.543828Z",
"iopub.status.busy": "2026-05-18T08:28:53.543560Z",
"iopub.status.idle": "2026-05-18T08:28:53.552938Z",
"shell.execute_reply": "2026-05-18T08:28:53.552184Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"~ WindTurbine 'T01' [hub_height: 80 → 95]\n",
"hub_height still: 80\n"
]
}
],
"source": [
"diff = client.get_node(\"my-portfolio\", \"Offshore-Renamed\", \"T01\").update({\"hub_height\": 95}, dry_run=True)\n",
"diff.render()\n",
"\n",
"# DB is unchanged after a dry_run.\n",
"print(\"hub_height still:\", client.get_node(\"my-portfolio\", \"Offshore-Renamed\", \"T01\").get().hub_height)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "c1021287",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:53.554319Z",
"iopub.status.busy": "2026-05-18T08:28:53.554142Z",
"iopub.status.idle": "2026-05-18T08:28:53.570586Z",
"shell.execute_reply": "2026-05-18T08:28:53.569894Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"~ WindTurbine 'T01' [hub_height: 80 → 95]\n",
"~ WindTurbine 'T02' [rename 'T03' → 'T02']\n",
"~ Battery 'B01' [moved (parent 019e3a33-c9d1-7c29-ad0e-cc9d87e6152e → 019e3a33-c9cd-7bfa-a262-1ba2f3cafbd5)]\n"
]
}
],
"source": [
"with client.transaction() as txn:\n",
" txn.get_node(\"my-portfolio\", \"Offshore-Renamed\", \"T01\").update({\"hub_height\": 95})\n",
" txn.get_node(\"my-portfolio\", \"Offshore-Renamed\", \"T03\").rename(\"T02\")\n",
" txn.get_node(\"my-portfolio\", \"Rooftop-1\", \"B01\").move_to((\"my-portfolio\", \"Offshore-Renamed\"))\n",
" txn.preview().render()\n",
" txn.commit()"
]
},
{
"cell_type": "markdown",
"id": "cf462d3535b6",
"metadata": {},
"source": [
"## 7. Bulk manifest I/O\n",
"\n",
"A single manifest DataFrame fans out a read across many series at once — routing column is `path`, `node_uuid`, or `edge_uuid`, detected automatically."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "40558aac4ddd",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:53.571951Z",
"iopub.status.busy": "2026-05-18T08:28:53.571795Z",
"iopub.status.idle": "2026-05-18T08:28:53.598281Z",
"shell.execute_reply": "2026-05-18T08:28:53.597433Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
shape: (48, 5)| valid_time | value | path | data_type | name |
|---|
| datetime[μs, UTC] | f64 | str | str | str |
| 2026-01-01 00:00:00 UTC | 2.5 | "my-portfolio/Offshore-Renamed/… | "actual" | "power" |
| 2026-01-01 01:00:00 UTC | 2.55 | "my-portfolio/Offshore-Renamed/… | "actual" | "power" |
| 2026-01-01 02:00:00 UTC | 2.6 | "my-portfolio/Offshore-Renamed/… | "actual" | "power" |
| 2026-01-01 03:00:00 UTC | 2.65 | "my-portfolio/Offshore-Renamed/… | "actual" | "power" |
| 2026-01-01 04:00:00 UTC | 2.7 | "my-portfolio/Offshore-Renamed/… | "actual" | "power" |
| … | … | … | … | … |
| 2026-01-01 19:00:00 UTC | 5.95 | "my-portfolio/Rooftop-1/PV01/Ar… | "actual" | "power" |
| 2026-01-01 20:00:00 UTC | 6.0 | "my-portfolio/Rooftop-1/PV01/Ar… | "actual" | "power" |
| 2026-01-01 21:00:00 UTC | 6.05 | "my-portfolio/Rooftop-1/PV01/Ar… | "actual" | "power" |
| 2026-01-01 22:00:00 UTC | 6.1 | "my-portfolio/Rooftop-1/PV01/Ar… | "actual" | "power" |
| 2026-01-01 23:00:00 UTC | 6.15 | "my-portfolio/Rooftop-1/PV01/Ar… | "actual" | "power" |
"
],
"text/plain": [
"shape: (48, 5)\n",
"┌─────────────────────────┬───────┬─────────────────────────────────┬───────────┬───────┐\n",
"│ valid_time ┆ value ┆ path ┆ data_type ┆ name │\n",
"│ --- ┆ --- ┆ --- ┆ --- ┆ --- │\n",
"│ datetime[μs, UTC] ┆ f64 ┆ str ┆ str ┆ str │\n",
"╞═════════════════════════╪═══════╪═════════════════════════════════╪═══════════╪═══════╡\n",
"│ 2026-01-01 00:00:00 UTC ┆ 2.5 ┆ my-portfolio/Offshore-Renamed/… ┆ actual ┆ power │\n",
"│ 2026-01-01 01:00:00 UTC ┆ 2.55 ┆ my-portfolio/Offshore-Renamed/… ┆ actual ┆ power │\n",
"│ 2026-01-01 02:00:00 UTC ┆ 2.6 ┆ my-portfolio/Offshore-Renamed/… ┆ actual ┆ power │\n",
"│ 2026-01-01 03:00:00 UTC ┆ 2.65 ┆ my-portfolio/Offshore-Renamed/… ┆ actual ┆ power │\n",
"│ 2026-01-01 04:00:00 UTC ┆ 2.7 ┆ my-portfolio/Offshore-Renamed/… ┆ actual ┆ power │\n",
"│ … ┆ … ┆ … ┆ … ┆ … │\n",
"│ 2026-01-01 19:00:00 UTC ┆ 5.95 ┆ my-portfolio/Rooftop-1/PV01/Ar… ┆ actual ┆ power │\n",
"│ 2026-01-01 20:00:00 UTC ┆ 6.0 ┆ my-portfolio/Rooftop-1/PV01/Ar… ┆ actual ┆ power │\n",
"│ 2026-01-01 21:00:00 UTC ┆ 6.05 ┆ my-portfolio/Rooftop-1/PV01/Ar… ┆ actual ┆ power │\n",
"│ 2026-01-01 22:00:00 UTC ┆ 6.1 ┆ my-portfolio/Rooftop-1/PV01/Ar… ┆ actual ┆ power │\n",
"│ 2026-01-01 23:00:00 UTC ┆ 6.15 ┆ my-portfolio/Rooftop-1/PV01/Ar… ┆ actual ┆ power │\n",
"└─────────────────────────┴───────┴─────────────────────────────────┴───────────┴───────┘"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"manifest = pd.DataFrame(\n",
" [\n",
" {\"path\": \"my-portfolio/Offshore-Renamed/T01\", \"data_type\": \"actual\", \"name\": \"power\"},\n",
" {\"path\": \"my-portfolio/Rooftop-1/PV01/Array-1\", \"data_type\": \"actual\", \"name\": \"power\"},\n",
" ]\n",
")\n",
"\n",
"df = client.read(manifest, start_valid=start)\n",
"df"
]
},
{
"cell_type": "markdown",
"id": "18489e0af9eb",
"metadata": {},
"source": [
"## 8. Edges\n",
"\n",
"Lines, links, and pipes link two nodes. Pass the endpoint nodes directly — the edge captures their `.id`. Edges hold their own time series the same way nodes do (`register_series` → `write` → `read`)."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "8d62098cdcb8",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:53.599760Z",
"iopub.status.busy": "2026-05-18T08:28:53.599608Z",
"iopub.status.idle": "2026-05-18T08:28:53.618920Z",
"shell.execute_reply": "2026-05-18T08:28:53.618303Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Line: Cable-1 (uuid=019e3a33-cbe2-7189-8e27-5df23845b82f)\n"
]
}
],
"source": [
"bus_a = edb.grid.JunctionPoint(name=\"BusA\")\n",
"bus_b = edb.grid.JunctionPoint(name=\"BusB\")\n",
"line = edb.grid.Line(name=\"Cable-1\", capacity=500, from_element=bus_a, to_element=bus_b)\n",
"\n",
"offshore = client.get_node(\"my-portfolio\", \"Offshore-Renamed\")\n",
"offshore.add(bus_a)\n",
"offshore.add(bus_b)\n",
"client.create_edge(line)\n",
"\n",
"found = client.get_edge(\n",
" from_path=(\"my-portfolio\", \"Offshore-Renamed\", \"BusA\"),\n",
" to_path=(\"my-portfolio\", \"Offshore-Renamed\", \"BusB\"),\n",
" type=\"Line\",\n",
").get()\n",
"print(f\"{type(found).__name__}: {found.name} (uuid={found.id})\")"
]
},
{
"cell_type": "markdown",
"id": "9822a2743d0d",
"metadata": {},
"source": [
"## 9. Cleanup"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "6c14574f8f1a",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-18T08:28:53.620528Z",
"iopub.status.busy": "2026-05-18T08:28:53.620369Z",
"iopub.status.idle": "2026-05-18T08:28:53.644008Z",
"shell.execute_reply": "2026-05-18T08:28:53.643023Z"
}
},
"outputs": [],
"source": [
"client.delete()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "backend (3.13.12)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}