Adapters¤
An adapter converts one detector's output frame into an
EventLog. ts-shape has one generic adapter
(ts_shape.eventlog.adapters.adapt), configured per detector method by a
LabelRule in REGISTRY. For a new detector method you normally add a
LabelRule (Adding a new detector method);
a custom adapter is needed only for output the generic adapter cannot express.
Shapes¤
The rule's shape decides where the event time comes from:
| Shape | Required columns | ocel:timestamp |
ts_shape:start_timestamp, ts_shape:duration_s |
|---|---|---|---|
point |
systime |
systime |
NaT, NaN |
interval |
start, end |
end |
start, end - start in seconds |
summary |
start, end |
end |
start, end - start in seconds |
static |
none | to_event_log(..., timestamp=...), the same for all rows; default the Unix epoch (1970-01-01T00:00Z) |
NaT, NaN |
A frame without the required columns raises an error naming the detector.
Naive timestamps are read as UTC. Interval and summary rows whose end is
before their start are dropped with a DataQualityWarning.
The implementation is adapt in src/ts_shape/eventlog/adapters.py.
LabelRule fields¤
| Field | Default | Effect |
|---|---|---|
template |
required | the ocel:activity; {column} placeholders are filled per row |
pack |
required | stored as ts_shape:pack and used as prefix of the detector's own columns |
shape |
"point" |
see above |
produces_objects |
("asset",) |
object types created automatically from standard columns (source_uuid becomes an asset); () for none |
severity_field |
None |
numeric column bucketed into ts_shape:severity (a boolean column maps True to warn, False to info); without it a severity column with info / warn / critical is passed through |
value_field |
None |
column used as ts_shape:value; without it the first of value, value_double, value_integer |
drop_fields |
() |
output columns not copied into the event log |
standard_attrs |
{} |
mapping to the standard attribute keys |
What to_event_log does¤
- Looks up
(class, method)fromdetector="Class.method"inREGISTRY(deprecated names are resolved to the current ones). An unknown name raisesKeyError. - Uses a custom adapter if one is registered for that method.
- Otherwise runs the generic adapter:
- takes the timestamps according to the shape;
- renders the activity from the template (a missing value renders as
unknown, with aDataQualityWarning); - builds a content-based
ocel:eid: a UUIDv5 of the detector, the row'ssource_uuid, its timestamps, the activity and every other column of the row. The same input always gives the same ids, independent of row order, and the same detector on two machines does not collide; - fills
ts_shape:severity,ts_shape:valueand the standard attributes; - copies every other output column as
<pack>:<column>and drops such columns when they are completely empty; - creates the automatic objects and those passed with
objects=.
- Validates the result: required columns and dtypes, unique
ocel:eid, and every relation pointing to an existing event and object.
For MachineStateEvents.detect_run_idle (template
production.machine_state.{state}, shape interval) an output row with
state="run", source_uuid="asset-A", start and end becomes an event
with activity production.machine_state.run, timestamp end, start
timestamp start, an asset object asset-A, and the attributes
production:uuid, production:is_delta and production:duration_seconds.
Custom adapters¤
Register a custom adapter with @register_adapter("Class", "method") when
the generic one does not fit: several events per output row, no usable time
columns, objects that depend on the data, or state carried across rows. The
adapter receives the detector frame and returns an EventLog; it is validated
like any other.
import pandas as pd
from ts_shape.eventlog import EventLog, register_adapter, to_event_log
from ts_shape.eventlog import schema as S
from ts_shape.eventlog.taxonomy import REGISTRY, LabelRule
# A real detector registers its rule in src/ts_shape/eventlog/taxonomy.py.
REGISTRY[("MyDetector", "alarm_windows")] = LabelRule(
template="production.custom.{kind}", pack="production", shape="point",
)
@register_adapter("MyDetector", "alarm_windows")
def raise_and_clear(detector_frame, *, rule, detector, objects, qualifiers):
"""Two events per row: one when the alarm is raised, one when it clears."""
events, relations = [], []
for i, row in detector_frame.iterrows():
for kind in ("raised", "cleared"):
eid = f"e-{detector}-{i}-{kind}"
events.append({
S.OCEL_EID: eid,
S.OCEL_ACTIVITY: f"production.custom.{kind}",
S.OCEL_TIMESTAMP: pd.Timestamp(row[f"{kind}_at"], tz="UTC"),
S.TS_DETECTOR: detector,
S.TS_PACK: rule.pack,
})
relations.append({
S.OCEL_EID: eid, S.OCEL_OID: row["asset_id"],
S.OCEL_TYPE: "asset", S.OCEL_QUALIFIER: "produced_on",
})
return EventLog(
events=pd.concat([S.empty_events(), pd.DataFrame(events)], ignore_index=True),
objects=pd.DataFrame({
S.OCEL_OID: detector_frame["asset_id"].astype("string").unique(),
S.OCEL_TYPE: "asset",
}),
relations=pd.concat([S.empty_relations(), pd.DataFrame(relations)], ignore_index=True),
)
frame = pd.DataFrame({"asset_id": ["press-01"], "raised_at": ["2025-01-01 08:00"], "cleared_at": ["2025-01-01 08:05"]})
log = to_event_log(frame, detector="MyDetector.alarm_windows")
The returned log must have at least ocel:eid (unique), ocel:activity,
ocel:timestamp, ts_shape:detector and ts_shape:pack in events, and
every relation must point to an existing event and object.
clear_overrides() removes registered adapters again.