Skip to main content

Oil & gas — refinery operations

The problem. A refinery turns crude into products through tightly-coupled units — distillation columns, crackers, reformers — each running a delicate balance of temperature, pressure and flow. Drift off that balance and the unit makes off-spec product (reprocessing cost), burns excess energy (the refinery's second-biggest expense after feed), or trips entirely. Operators need the process KPIs that reveal drift and an alert when a unit heads off-spec, early enough to nudge it back.

What we solve here is keeping units on-spec and energy-efficient by turning raw process tags into the few numbers that actually run the plant.

Set up demo data

New workspace? Run this once (Python) to create the crude unit's energy series with a day of hourly readings, so the roll-up in step 1 has data. Safe to re-run.

import datahub_sdk, numpy as np, pandas as pd

client = datahub_sdk.DataHubClient.from_env()

client.timeseries.create([datahub_sdk.TimeSeries(external_id="cdu_1_energy_gj", name="CDU-1 energy", unit="gj", value_type="float")])
idx = pd.date_range(end=pd.Timestamp.now(tz="UTC"), periods=24, freq="1h")
client.timeseries.insert_from_lists(timestamps=idx, values=np.random.uniform(40, 60, 24), ts="cdu_1_energy_gj")

1. Track the process KPIs

Column temperatures, reflux and feed are series; the numbers operators steer by — energy per barrel, reflux ratio, separation quality — come from rolling these up. See Query & aggregate.

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

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

// divide hourly energy by hourly throughput for energy-per-barrel
client.timeseries().retrieve(request).getItems().get(0).getDatapoints()
.forEach(p -> recordEnergyIntensity(p.getTimestamp(), p.getValue()));

2. Catch a process upset early

When a column's top temperature drifts out of its control band, raise a process_upset event so the board operator corrects before product goes off-spec or the unit trips. See Turn readings into events.

EventModel upset = new EventModel();
upset.setExternalId("process_upset_cdu1_" + System.currentTimeMillis());
upset.setType("process_upset");
upset.setStatus("open");
upset.setMetadata(Map.of("unit", "crude_unit_1", "tag", "column_top_temp", "value_c", "168"));
upset.setEventTime(ZonedDateTime.now());
client.events().create(List.of(upset));

See the result

Chart the hourly energy you rolled up — the bar chart is the shape the board operator watches for drift:

import matplotlib.pyplot as plt

rf = datahub_sdk.RetrieveFilter(ts="cdu_1_energy_gj",
start=pd.Timestamp.now(tz="UTC") - pd.Timedelta(days=1), end=pd.Timestamp.now(tz="UTC"),
aggregates=["sum"], granularity="1h")
vals = [dp.sum for dp in client.timeseries.retrieve_datapoints(rf)[0].get_datapoints()]
plt.bar(range(len(vals)), vals); plt.title("CDU-1 hourly energy (GJ)"); plt.xlabel("hour"); plt.show()

See also