Skip to main content

Pharma — batch manufacturing & genealogy

The problem. A finished drug lot fails a quality check. Before anything ships, the manufacturer must reconstruct its genealogy: which intermediate lots, raw material lots, and equipment produced it, and whether any deviation in the process record explains the failure. The process data has to be exact — a pH or temperature stored with floating-point drift isn't acceptable in a regulated batch record — and the lineage has to be walkable in seconds, not days.

This scenario combines exact-value time-series with upstream graph genealogy.

Set up demo data

New workspace? Run this once (Python) to create the batch genealogy — a final lot derived from an intermediate, which came from two raw-material lots, produced on a bioreactor — so the upstream walk in step 2 returns a real lineage. Safe to re-run.

import datahub_sdk

client = datahub_sdk.DataHubClient.from_env()

client.resources.create(
[datahub_sdk.Resource(external_id=x, name=x, labels=[lbl]) for x, lbl in
[("lot_22f_final", "Lot"), ("lot_int_88", "Lot"), ("lot_raw_acme_41", "Lot"),
("lot_raw_acme_42", "Lot"), ("equipment_bioreactor_3", "Equipment")]],
[datahub_sdk.RelForm.by_external_ids("lot_22f_final", "lot_int_88", "derived_from"),
datahub_sdk.RelForm.by_external_ids("lot_int_88", "lot_raw_acme_41", "derived_from"),
datahub_sdk.RelForm.by_external_ids("lot_int_88", "lot_raw_acme_42", "derived_from"),
datahub_sdk.RelForm.by_external_ids("lot_22f_final", "equipment_bioreactor_3", "produced_on")])

1. Record process data exactly

A bioreactor's pH, temperature and dissolved oxygen are the batch record. Use the NUMERIC value type so values store as exact decimals, not floats.

var ph = Timeseries.of("batch_22f_ph")
.name("Batch 22F — pH")
.setValueType("numeric");
ph.setUnit("ph");
client.timeseries().create(ph);

client.timeseries().ingest(Map.of(
"batch_22f_ph", List.of(Datapoint.of(Instant.now(), "7.0421")))); // exact
Exact decimals

NUMERIC stores values without floating-point rounding — see the Time-series reference for the value types. For a regulated batch record, prefer it over the float types.

2. Walk the genealogy upstream

The failed lot derived_from intermediate lots, which derived_from raw material lots; each step produced_on a piece of equipment. Walk up from the failed lot and the returned sub-graph is its complete genealogy — every input and every machine that touched it.

ResourceNetwork lineage = client.resources().fetchRelated("lot_22f_final", 10);

lineage.nodes().forEach(n -> {
if (n.getExternalId().startsWith("lot_raw_"))
System.out.println("raw material: " + n.getExternalId());
if (n.getExternalId().startsWith("equipment_"))
System.out.println("equipment: " + n.getExternalId());
});

If a raw material lot turns up in the genealogy of other failed batches too, you've found a shared root cause — the same intersection trick as alarm correlation.

3. Capture deviations as events

Every out-of-band reading in the batch record is a process_deviation event tied to the lot, so the review team reconstructs an exact, ordered deviation timeline. See Turn readings into events.

See the result

Walking up from the failed lot returns its complete genealogy:

raw material: lot_raw_acme_41
raw material: lot_raw_acme_42
equipment: equipment_bioreactor_3

See also