The script below builds a SlimLinkedOCEL from five CSVs: one for events, one per object type (here order and item), and one each for E2O and O2O relationships. It reads them with the standard library csv module to keep the example dependency-free. Any source that yields row-dicts works the same way.
locel_add_event and locel_add_object accept an id argument, so the CSV string IDs (e1, o3, i7) go in directly. The relationship loops resolve them back to integer refs via locel_get_ev_by_id and locel_get_ob_by_id, so there's no manual id-to-ref map. The only ordering requirement is that events and objects are added before the relationships that reference them.
Input tables
Download the example CSVs or copy them from the previews. Drop them next to the script before running.
events.csvDownloadevent_id,event_type,timestamp,tip e1,place order,2026-03-02T10:00:00Z,2.50 e2,place order,2026-03-02T10:15:00Z,5.00 e3,place order,2026-03-02T10:30:00Z,3.25 e4,place order,2026-03-02T10:45:00Z,0.00 e5,place order,2026-03-02T11:00:00Z,7.50orders.csvDownloadorder_id o1 o2 o3 o4 o5items.csvDownloaditem_id,price i1,4.30 i2,8.60 i3,4.30 i4,12.90 i5,8.60 i6,4.30 i7,12.90 i8,8.60 i9,4.30 i10,4.30 i11,8.60 i12,12.90e2o.csvDownloadevent_id,object_id,qualifier e1,o1,of e1,i1,with e1,i2,with e2,o2,of e2,i3,with e2,i4,with e2,i5,with e3,o3,of e3,i6,with e3,i7,with e4,o4,of e4,i8,with e4,i9,with e5,o5,of e5,i10,with e5,i11,with e5,i12,witho2o.csvDownloadsource_object_id,target_object_id,qualifier i1,o1,is in i2,o1,is in i3,o2,is in i4,o2,is in i5,o2,is in i6,o3,is in i7,o3,is in i8,o4,is in i9,o4,is in i10,o5,is in i11,o5,is in i12,o5,is in
Script
"""
Build a SlimLinkedOCEL from CSV tables.
Inputs (in the same directory as this script):
events.csv one row per event
orders.csv, items.csv one row per object, one CSV per object type
e2o.csv, o2o.csv relationships
Run: python example.py
Install: pip install r4pm
"""
import csv
from pathlib import Path
import r4pm
import r4pm.bindings as b
HERE = Path(__file__).parent
def read_csv(name):
with (HERE / name).open(newline="") as f:
return list(csv.DictReader(f))
# --- Declare types ---
locel = b.locel_new()
b.locel_add_event_type(locel, "place order", [{"name": "tip", "type": "float"}])
b.locel_add_object_type(locel, "order")
b.locel_add_object_type(locel, "item", [{"name": "price", "type": "float"}])
# --- Add events and objects ---
# Pass the CSV string ID as the OCEL `id`. The relationship loops below resolve
# it back to an integer ref via `locel_get_ev_by_id` / `locel_get_ob_by_id`,
# so no manual id-to-ref dict is needed. Order matters: every event/object
# must be added before any relationship that references it.
for row in read_csv("events.csv"):
# Attribute values are positional, in the order declared above.
b.locel_add_event(
locel,
row["event_type"],
row["timestamp"],
row["event_id"],
[float(row["tip"])],
)
for row in read_csv("orders.csv"):
b.locel_add_object(locel, "order", row["order_id"])
for row in read_csv("items.csv"):
# Object attributes are time-indexed; UNIX epoch stands in for "constant".
history = [[("1970-01-01T00:00:00Z", float(row["price"]))]]
b.locel_add_object(locel, "item", row["item_id"], history)
# --- Add relationships ---
for row in read_csv("e2o.csv"):
b.locel_add_e2o(
locel,
b.locel_get_ev_by_id(locel, row["event_id"]),
b.locel_get_ob_by_id(locel, row["object_id"]),
row["qualifier"],
)
for row in read_csv("o2o.csv"):
b.locel_add_o2o(
locel,
b.locel_get_ob_by_id(locel, row["source_object_id"]),
b.locel_get_ob_by_id(locel, row["target_object_id"]),
row["qualifier"],
)
# --- Export ---
# Extension picks the OCEL 2.0 format: .xml / .sqlite / .json
r4pm.export_item(locel, "export.sqlite")
print("Exported.")
r4pm.export_item picks the OCEL 2.0 format from the file extension: .xml, .sqlite, or .json.