Skip to main content

Energy — electrical grid telemetry

The problem. A distribution operator runs a region of substations, each stepping power down through transformers onto feeders that serve neighbourhoods. Operators need second-by-second load, voltage and frequency; planners need hourly and daily roll-ups to spot trends and size upgrades; and any feeder pushed past its rating must raise an overload alarm.

This scenario centres on aggregation — turning a firehose of telemetry into the roll-ups a SCADA dashboard and a planning report actually consume.

Set up demo data​

New workspace? Run this once (Python) to create a feeder's load series with a day of hourly readings including an overload peak — so the roll-up and overload event below have data. The region/substation graph is created by step 1. Safe to re-run.

import intellistream_datahub_sdk, numpy as np, pandas as pd

client = intellistream_datahub_sdk.DataHubClient.from_env()

client.timeseries.create([intellistream_datahub_sdk.TimeSeries(external_id="feeder_f12_load_mw", name="Feeder F12 load", unit="mw")])
idx = pd.date_range(end=pd.Timestamp.now(tz="UTC"), periods=24, freq="1h")
load = 8 + 4 * np.sin(np.arange(24) / 24 * 2 * np.pi) + np.random.normal(0, 0.5, 24)
load[18] = 14.8 # an evening overload past the 12 MW rating
client.timeseries.insert_from_lists(timestamps=idx, values=load, ts="feeder_f12_load_mw")

1. Model the network​

A region contains substations, a substation contains transformers, a transformer feeds feeders. The same graph pattern as any asset hierarchy.

ResourceForm region = new ResourceForm();
region.setExternalId("grid_region_east");
region.setName("Eastern region");
region.setLabels(List.of("Region"));

ResourceForm substation = new ResourceForm();
substation.setExternalId("substation_oslo_1");
substation.setName("Oslo substation 1");
substation.setLabels(List.of("Substation"));

RelForm contains = new RelForm();
contains.setName("contains");
contains.setFromExternalId("grid_region_east");
contains.setToExternalId("substation_oslo_1");

client.resources().create(List.of(region, substation), List.of(contains));

Per-feeder series follow the same naming: feeder_f12_load_mw, feeder_f12_voltage_kv, substation_oslo_1_frequency_hz.

2. Roll telemetry up for the dashboard​

Raw load is far too dense to chart directly. Ask for hourly averages and peaks and the platform returns one value per bucket. The full mechanics are in Query & aggregate.

var filter = new RetrieveFilter();
filter.setExternalId("feeder_f12_load_mw");
filter.setStart(ZonedDateTime.now().minusDays(1));
filter.setEnd(ZonedDateTime.now());
filter.setAggregates(List.of("avg", "max"));
filter.setGranularity("1h");

var request = new DataRetriever<RetrieveFilter>();
request.setItems(List.of(filter));

client.timeseries().retrieve(request).getItems().get(0).getDatapoints()
.forEach(p -> chart(p.getTimestamp(), p.getValue())); // hourly load

3. Overload alarm​

When a feeder's hourly peak exceeds its rating, record a feeder_overload event so the planning team sees a ranked list of strained circuits.

EventModel event = new EventModel();
event.setExternalId("feeder_overload_f12_" + System.currentTimeMillis());
event.setType("feeder_overload");
event.setStatus("open");
event.setMetadata(Map.of("feeder", "feeder_f12", "peak_mw", "14.8", "rating_mw", "12.0"));
event.setEventTime(ZonedDateTime.now());
client.events().create(List.of(event));

See the result​

Chart the feeder's hourly peak load — one bar pokes above the 12 MW rating line, which is the overload the event flags:

import matplotlib.pyplot as plt

rf = intellistream_datahub_sdk.RetrieveFilter(ts="feeder_f12_load_mw",
start=pd.Timestamp.now(tz="UTC") - pd.Timedelta(days=1), end=pd.Timestamp.now(tz="UTC"),
aggregates=["max"], granularity="1h")
peaks = [dp.max for dp in client.timeseries.retrieve_datapoints(rf)[0].get_datapoints()]
plt.bar(range(len(peaks)), peaks); plt.axhline(12, color="r", ls="--")
plt.title("Feeder F12 hourly peak load (MW)"); plt.show()

See also​