Skip to content

Instantly share code, notes, and snippets.

@menjaraz
Last active June 18, 2026 19:45
Show Gist options
  • Select an option

  • Save menjaraz/8de178b58ed184e84b1db54f477dc45d to your computer and use it in GitHub Desktop.

Select an option

Save menjaraz/8de178b58ed184e84b1db54f477dc45d to your computer and use it in GitHub Desktop.
EPCIS Scenario

Seed Data

The seed mechanism loads a realistic pharmaceutical supply chain scenario into a running EPCIS 2.0 app instance. It is designed for local development and manual exploration — not for test suites (tests create their own data).

Quick start

make docker-up   # start the full stack (DB + app)
make seed        # load seed data (safe to run multiple times)

To return to the seeded state after fiddling:

make db-reset    # truncate all tables + clear graph, then re-seed

Scenario overview

A pharmaceutical manufacturer produces bottles of medication from a bulk raw material lot, packages them into cases and a pallet, ships to a distribution centre, which then unpacks and forwards to a pharmacy. A second smaller batch from the same lot is later recalled before delivery.

BULK LOT (L001)
    │
    ├─[TransformationEvent: manufacturing]─► Bottle B001
    │                                        Bottle B002
    │                                        Bottle B003
    │                                        Bottle B004
    │
    │   ┌────────────────────────────────────────────────────────┐
    │   │                  PACKAGING                             │
    │   │  B001 + B002 ──[pack]──► Case C001 ──┐                │
    │   │  B003 + B004 ──[pack]──► Case C002 ──┴──[load]──► Pallet P001
    │   └────────────────────────────────────────────────────────┘
    │                                              │
    │                                  [ship: plant → DC]
    │                                              │
    │                                       DC receives P001
    │                                              │
    │                               ┌─────────────┴──────────────┐
    │                               │  DC UNPACKING               │
    │                               │  P001 ──[unpack]──► C001, C002
    │                               │  C001 ──[unpack]──► B001, B002
    │                               │  C002 ──[unpack]──► B003, B004
    │                               └────────────────────────────┘
    │                                              │
    │                                   [ship: DC → pharmacy]
    │                                              │
    │                                    Pharmacy receives B001–B004
    │
    └─[TransformationEvent: mfg recall]──► Bottle B005
                                              │
                                         [load]──► Pallet P002
                                              │
                                         [dispatch]  ← VOIDED (recall)

Locations

Ten locations are seeded across two facility hierarchies.

Manufacturing plant

URI Name Parent
urn:epc:id:sgln:0614141.plant00.0 Manufacturing Plant
urn:epc:id:sgln:0614141.plant00.line1 Production Line 1 Plant
urn:epc:id:sgln:0614141.plant00.qlab0 Quality Lab Plant
urn:epc:id:sgln:0614141.plant00.fgsto Finished Goods Store Plant
urn:epc:id:sgln:0614141.plant00.ship0 Outbound Dock Plant

Distribution centre

URI Name Parent
urn:epc:id:sgln:0614141.dc0001.0 Distribution Centre
urn:epc:id:sgln:0614141.dc0001.recv0 DC Inbound Dock DC
urn:epc:id:sgln:0614141.dc0001.stor0 DC Storage DC
urn:epc:id:sgln:0614141.dc0001.ship0 DC Outbound Dock DC

Pharmacy

URI Name Parent
urn:epc:id:sgln:0614141.rx0001.0 City Pharmacy

The WD (within-descendant) filter flattens these hierarchies. A query with WD_bizLocation=urn:epc:id:sgln:0614141.plant00.0 matches all five plant locations.


EPCs

Items

EPC Description
urn:epc:id:sgtin:0614141.100001.L001 Bulk raw material lot
urn:epc:id:sgtin:0614141.200001.B001 Manufactured bottle 1
urn:epc:id:sgtin:0614141.200001.B002 Manufactured bottle 2
urn:epc:id:sgtin:0614141.200001.B003 Manufactured bottle 3
urn:epc:id:sgtin:0614141.200001.B004 Manufactured bottle 4
urn:epc:id:sgtin:0614141.200001.B005 Recall batch bottle

Packaging units

EPC Description Contents
urn:epc:id:sgtin:0614141.300001.C001 Case 1 B001, B002
urn:epc:id:sgtin:0614141.300001.C002 Case 2 B003, B004
urn:epc:id:sscc:0614141.0000000001 Pallet 1 C001, C002
urn:epc:id:sscc:0614141.0000000002 Pallet 2 (recall) B005

Event timeline

All 17 events have fixed UUIDs (urn:uuid:00000000-0000-0000-0000-00000000000N) to ensure idempotency.

Phase 1 — Manufacturing

# UUID suffix Type bizStep Location Key EPCs
1 …0001 ObjectEvent (ADD) receiving Production Line 1 bulk lot L001
2 …0002 TransformationEvent manufacturing Production Line 1 L001 → B001–B004
3 …0003 ObjectEvent (OBSERVE) inspecting Quality Lab B001–B004

Event 1 carries two bizTransactionList entries: a purchase order (btt:po) and a bill of lading (btt:bol).

Phase 2 — Packaging

# UUID suffix Type bizStep Location Key EPCs
4 …0004 AggregationEvent (ADD) packing Finished Goods Store B001+B002 → C001
5 …0005 AggregationEvent (ADD) packing Finished Goods Store B003+B004 → C002
6 …0006 AggregationEvent (ADD) loading Finished Goods Store C001+C002 → P001

Phase 3 — Plant → DC

# UUID suffix Type bizStep Location Key EPCs
7 …0007 TransactionEvent (OBSERVE) shipping Outbound Dock P001
8 …0008 ObjectEvent (OBSERVE) receiving DC Inbound Dock P001

Event 7 carries three bizTransactionList entries (PO, BOL, invoice) and sourceList / destinationList with owning-party GLNs.

Phase 4 — DC unpacking

# UUID suffix Type bizStep Location Key EPCs
9 …0009 AggregationEvent (DELETE) unpacking DC Storage P001 → C001, C002
10 …0010 AggregationEvent (DELETE) unpacking DC Storage C001 → B001, B002
11 …0011 AggregationEvent (DELETE) unpacking DC Storage C002 → B003, B004

DELETE-action aggregation events represent disassembly. They generate CONTAINS edge removals in the AGE graph.

Phase 5 — DC → Pharmacy

# UUID suffix Type bizStep Location Key EPCs
12 …0012 TransactionEvent (OBSERVE) shipping DC Outbound Dock B001–B004
13 …0013 ObjectEvent (ADD) receiving Pharmacy B001–B004

Phase 6 — Recall sub-chain

# UUID suffix Type bizStep Location Key EPCs
14 …0014 TransformationEvent manufacturing Production Line 1 L001 → B005
15 …0015 AggregationEvent (ADD) loading Outbound Dock B005 → P002
16 …0016 ObjectEvent (OBSERVE) shipping Outbound Dock P002
17 …0017 VoidedEvent voids event 16

Event 16 is captured then immediately voided by event 17. It appears in query results with a voidedEvent field referencing event 17.


Traceability queries

The seed data exercises every traceability path the API supports.

Backward trace — where did a bottle come from?

GET /trace/urn:epc:id:sgtin:0614141.200001.B001

Returns the full event history for B001, walking backwards through: received at pharmacy ← shipped from DC ← (was in case C001) ← packed ← QC'd ← manufactured ← bulk lot received.

Forward trace — where did the bulk lot go?

GET /trace/urn:epc:id:sgtin:0614141.100001.L001

Shows both transformation events (batch 1 producing B001–B004, recall batch producing B005) — demonstrating that a single input EPC can fan out to multiple outputs.

Aggregation hierarchy

GET /hierarchy/urn:epc:id:sscc:0614141.0000000001

Returns the tree: Pallet P001 → [Case C001 → [B001, B002], Case C002 → [B003, B004]].

GET /hierarchy/urn:epc:id:sgtin:0614141.300001.C001

Returns: Case C001 → [B001, B002].

Location hierarchy

GET /masterdata/urn:epcglobal:epcis:vtype:BusinessLocation

Returns all 10 seeded locations. Each entry includes the parentUri where applicable.

GET /masterdata/urn:epcglobal:epcis:vtype:BusinessLocation?EQ_ATTR_country=US

Matches the plant, DC, and pharmacy (all tagged "country": "US").


Useful filter queries

By facility (exact)

GET /events?EQ_bizLocation=urn:epc:id:sgln:0614141.plant00.0

Returns only events whose bizLocation is the plant root (events 7 and 8 in the scenario). Does not include events at sub-locations.

By facility (within-descendant)

GET /events?WD_bizLocation=urn:epc:id:sgln:0614141.plant00.0

Returns all 10 plant-side events (phases 1, 2, and the recall sub-chain), including those at production line, QC lab, finished goods store, and outbound dock.

GET /events?WD_bizLocation=urn:epc:id:sgln:0614141.dc0001.0

Returns all 5 DC-side events (phases 3 and 4: receipt, unpacking).

By bizStep

GET /events?EQ_bizStep=urn:epcglobal:cbv:bizstep:shipping

Returns events 7, 12, and 16 (the three shipping events).

GET /events?EQ_bizStep=urn:epcglobal:cbv:bizstep:packing&EQ_bizStep=urn:epcglobal:cbv:bizstep:loading

Returns all 4 aggregation events that pack/load items (phases 2 and recall).

By EPC

GET /events?EQ_epcList=urn:epc:id:sscc:0614141.0000000001

Returns all events mentioning pallet P001 directly (events 6 [load], 7 [ship], 8 [recv at DC], 9 [unpack]).

GET /events?EQ_parentID=urn:epc:id:sscc:0614141.0000000001

Returns the two aggregation events where P001 is the parent (events 6 and 9).

By business transaction

GET /events?EQ_bizTransaction_po=urn:example:po:PO-2026-002

Returns event 7 (plant→DC shipment carrying that PO).

GET /events?EQ_bizTransaction_inv=urn:example:inv:INV-2026-043

Returns event 12 (DC→pharmacy shipment).

By event type

GET /events?eventType=TransformationEvent

Returns events 2 and 14 — the two manufacturing transformations from the same bulk lot.

By action

GET /events?action=DELETE

Returns events 9, 10, and 11 — the three DC unpacking events.

Voided event

GET /events/urn:uuid:00000000-0000-0000-0000-000000000016

Returns event 16 (the recall dispatch) with a voidedEvent block referencing event 17.


Idempotency

Every event uses a fixed eventID UUID. The capture handlers return 409 Conflict when a UUID already exists; make seed treats 409 as a no-op and prints:

  ~ ObjectEvent  bulk lot received    (already exists)

Masterdata upserts (POST /masterdata/locations) use ON CONFLICT DO UPDATE and are always safe to re-run.

First run (empty DB):

+ urn:epc:id:sgln:0614141.plant00.0
+ urn:epc:id:sgln:0614141.plant00.line1
...
+ ObjectEvent      bulk lot received
+ TransformationEvent  bulk → 4 bottles
...

Subsequent run (seeded DB):

~ urn:epc:id:sgln:0614141.plant00.0  (updated)
...
~ ObjectEvent      bulk lot received   (already exists)
...

Reset workflow

make db-reset wipes the database and graph, then calls make seed:

Truncating relational tables and clearing graph...
  → TRUNCATE epcis_raw_events, capture_jobs, masterdata_locations, subscriptions
  → MATCH (n) DETACH DELETE n   (AGE graph)
Re-seeding...
App healthy — seeding http://localhost:3000
...

The truncation runs through scripts/truncate.sql via docker-compose exec -T db psql. It issues a single TRUNCATE … CASCADE for the relational tables followed by the AGE Cypher command in the same psql session (required because LOAD 'age' and SET search_path must apply before the Cypher call).

Never use docker-compose down -v to reset — that destroys the volume and requires running migrations again. Use make db-reset instead.


Implementation notes

The seed binary lives in src/bin/seed.rs and is built as a separate Cargo binary. It uses the same reqwest client that ships as a library dependency and requires no extra crate features.

It connects to http://localhost:3000 by default. To target a different address, edit the API constant at the top of the file or add an env-var override.

The binary does not bypass the API — all data flows through the capture and masterdata endpoints, so every graph edge (ENCOUNTERED, CONTAINS) is created by the same code paths that handle production traffic. This means the seed data is representative: trace, hierarchy, and WD filter queries all work exactly as they would in production.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment