Skip to main content

Renewable energy — wind farm generation

The problem. A wind farm's output swings with the weather minute to minute. The operations desk needs to know how much each turbine and the farm as a whole is generating against its rated capacity, spot under-performers, and feed a generation forecast to the grid operator. Raw per-second power is far too dense — what matters is the rolled-up capacity factor over useful windows.

This scenario centres on aggregation of generation data into the hourly and daily figures a forecasting model and a performance dashboard consume.

Set up demo data

New workspace? Run this once (Python) to create a turbine's power and wind series with a week of hourly readings — so the capacity-factor roll-up has data. Safe to re-run.

import datahub_sdk, numpy as np, pandas as pd

client = datahub_sdk.DataHubClient.from_env()

for s, u in [("turbine_t14_power_kw", "kw"), ("turbine_t14_wind_ms", "m_s")]:
client.timeseries.create([datahub_sdk.TimeSeries(external_id=s, name=s, unit=u)])
idx = pd.date_range(end=pd.Timestamp.now(tz="UTC"), periods=168, freq="1h")
wind = np.clip(np.random.weibull(2.0, 168) * 6.0, 0, 25)
power = np.clip(wind**3 * 2.0, 0, 3500) # rated ~3500 kW
client.timeseries.insert_from_lists(timestamps=idx, values=power, ts="turbine_t14_power_kw")
client.timeseries.insert_from_lists(timestamps=idx, values=wind, ts="turbine_t14_wind_ms")

1. Ingest per-turbine output

Each turbine reports power and wind speed continuously. Hand the readings off in bulk.

var power = Timeseries.of("turbine_t14_power_kw").name("Turbine T14 power");
power.setUnit("kw");
var wind = Timeseries.of("turbine_t14_wind_ms").name("Turbine T14 wind speed");
wind.setUnit("m_s");
client.timeseries().create(power, wind);

client.timeseries().ingest(Map.of(
"turbine_t14_power_kw", powerReadings, // List<Datapoint>
"turbine_t14_wind_ms", windReadings));

2. Roll up to a capacity factor

Capacity factor is average output over rated capacity. Ask for hourly averages, divide by the turbine's rating, and you have the curve the forecast and the dashboard need. Full mechanics in Query & aggregate.

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

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

double rated = 3500.0; // kW
client.timeseries().retrieve(request).getItems().get(0).getDatapoints()
.forEach(p -> recordCapacityFactor(
p.getTimestamp(), Double.parseDouble(p.getValue()) / rated));

3. Flag an under-performer

When a turbine's capacity factor stays well below the farm average in good wind, raise a turbine_underperformance event for the maintenance team — a sign of a pitch or gearbox problem worth a closer look.

See the result

Print the average capacity factor and chart it over the week:

import matplotlib.pyplot as plt

rf = datahub_sdk.RetrieveFilter(ts="turbine_t14_power_kw",
start=pd.Timestamp.now(tz="UTC") - pd.Timedelta(days=7), end=pd.Timestamp.now(tz="UTC"),
aggregates=["avg"], granularity="1h")
cf = [dp.average / 3500 for dp in client.timeseries.retrieve_datapoints(rf)[0].get_datapoints()]
print(f"average capacity factor: {sum(cf)/len(cf):.0%}")
plt.plot(cf); plt.title("Turbine T14 capacity factor"); plt.show()

See also