init
eventlog ¤
ts-shape canonical event-log package.
A lightweight, pm4py-compatible (XES + OCEL 2.0) representation of detector output. ts-shape itself adds no process-mining dependencies — the columns match the specs verbatim so users can hand the resulting DataFrames to pm4py / Disco / Celonis / OCEL viewers directly.
Typical use::
from ts_shape.eventlog import (
to_event_log, concat, to_event_log_xes, to_event_log_ocel,
)
from ts_shape.events.production.machine_state import MachineStateEvents
from ts_shape.events.quality.outlier_detection import OutlierDetectionEvents
state_log = to_event_log(
MachineStateEvents(df, run_state_uuid="m").detect_run_idle(),
detector="MachineStateEvents.detect_run_idle",
)
outlier_log = to_event_log(
OutlierDetectionEvents(df, value_column="value_double").detect_outliers_zscore(),
detector="OutlierDetectionEvents.detect_outliers_zscore",
)
log = concat(state_log, outlier_log)
xes_df = to_event_log_xes(log, case_object_type="asset")
tables = to_event_log_ocel(log) # OCEL2Tables(events, objects, relations, ...)
LambdaDetector ¤
LambdaDetector(spec: RuleSpec)
Runnable form of a :class:RuleSpec.
evaluate ¤
evaluate(df: DataFrame) -> pd.DataFrame
Apply the rule to df and return a legacy-shaped DataFrame.
RuleSpec
dataclass
¤
RuleSpec(
id: str,
class_name: str,
method_name: str,
pack: str,
shape: str,
archetype: str,
template: str,
trigger: TriggerSpec,
produces_objects: tuple[str, ...] = ("asset",),
severity_field: str | None = None,
value_field: str | None = None,
standard_attrs: Mapping[str, object] = dict(),
)
A complete lambda-rule definition.
Required fields mirror the built-in REGISTRY contract — class_name
is synthesized (must start with Lambda) so the rule slots into the
same (class, method) -> LabelRule table as the 290 built-ins.
TriggerSpec
dataclass
¤
TriggerSpec(
expression: str,
min_duration_s: float | None = None,
group_by: tuple[str, ...] = (),
)
When does the rule fire?
expression is evaluated against the input DataFrame by the
AST-restricted compiler in :mod:.expression. It must produce a
boolean Series aligned with the input rows.
min_duration_s and group_by are only meaningful for
shape="interval" rules: consecutive True rows are coalesced
(per group) and the resulting interval is dropped if shorter than
min_duration_s seconds.
UnsafeExpression ¤
Bases: ValueError
Raised when a trigger expression contains a disallowed AST node.
EventLog
dataclass
¤
EventLog(
events: DataFrame = schema.empty_events(),
objects: DataFrame = schema.empty_objects(),
relations: DataFrame = schema.empty_relations(),
o2o: DataFrame = schema.empty_o2o(),
object_changes: DataFrame = schema.empty_object_changes(),
)
Canonical ts-shape event log in OCEL 2.0 shape.
Five relational tables, mirroring the OCEL 2.0 standard:
events— one row per detected event (+ event attributes),objects— the objects events refer to (ocel:oid/ocel:type),relations— event-to-object (E2O) relations with qualifiers,o2o— object-to-object relations with qualifiers,object_changes— time-varying object attribute values.
objects / relations are empty when the source detector has no
natural object association (e.g. a global cross-signal correlation
statistic); o2o / object_changes are empty unless supplied. Use
:attr:has_objects to check before calling :func:to_event_log_xes.
ObjectSpec
dataclass
¤
ObjectSpec(
uuid: str,
object_type: str,
value_column: str = "value_string",
min_duration: str | None = None,
id_template: str = "{value}",
attributes: Mapping[str, str] = dict(),
)
Declares that one signal carries the identity of one object type.
Attributes:
| Name | Type | Description |
|---|---|---|
uuid |
str
|
The signal ( |
object_type |
str
|
OCEL object type, e.g. |
value_column |
str
|
Column carrying the id ( |
min_duration |
str | None
|
Optional minimum presence (e.g. |
id_template |
str
|
How to render the object id from the raw value. Supports
|
attributes |
Mapping[str, str]
|
|
OCEL2Tables
dataclass
¤
OCEL2Tables(
events: DataFrame,
objects: DataFrame,
relations: DataFrame,
o2o: DataFrame,
object_changes: DataFrame,
)
The five OCEL 2.0 relational tables produced from an :class:EventLog.
register_adapter ¤
register_adapter(
class_name: str, method_name: str
) -> Callable[[AdapterFn], AdapterFn]
Register a custom adapter for a specific (class, method).
The function will be called with (legacy_df, *, rule, detector,
objects, qualifiers).
align_columns ¤
align_columns(*logs: EventLog) -> list[EventLog]
Reindex every log's events table to the union of their columns.
Each returned :class:EventLog exposes an identical, identically-ordered
set of event columns — the canonical core first (in emit order), then the
remaining attribute columns sorted for determinism. Columns absent from a
given log are added and filled with NA, so the frames can be appended or
stacked directly without a column mismatch. objects and relations
already have fixed schemas and are returned unchanged.
Returns a list parallel to the inputs. An empty call returns [].
to_event_log_xes ¤
to_event_log_xes(
eventlog: EventLog,
*,
case_object_type: str = "asset",
lifecycle: str = "single"
) -> pd.DataFrame
Flatten the event log into an XES-style DataFrame.
A trace is built per object of case_object_type — each event linked
to that object becomes one (or two) rows in the trace.
lifecycle="single" — interval events become one row with
lifecycle:transition="complete". time:timestamp is the
interval-end; start_timestamp is exposed verbatim.
lifecycle="two_row" — interval events expand into a start row
(using ts_shape:start_timestamp) and a complete row, paired by
concept:instance.
compile_expression ¤
compile_expression(
expression: str,
) -> Callable[[pd.DataFrame], pd.Series]
Compile a trigger expression to a vectorized boolean mask function.
The returned callable accepts a :class:pandas.DataFrame and returns
a :class:pandas.Series of booleans (NaN → False) aligned with the
frame's rows.
load_dicts ¤
load_dicts(
entries: Iterable[Mapping[str, Any]],
) -> list[LambdaDetector]
Compile + register an iterable of dict rules.
load_yaml ¤
load_yaml(path: str | Path) -> list[LambdaDetector]
Load and register every rule under a YAML file's rules: key.
YAML is imported lazily so :mod:ts_shape.eventlog does not gain a
hard runtime dependency on PyYAML for users who only use built-in
detectors. Install pyyaml to enable this loader.
register_lambda_rule ¤
register_lambda_rule(spec: RuleSpec) -> LambdaDetector
Register spec in REGISTRY + ARCHETYPE_BY_METHOD; return runnable detector.
Raises :class:ValueError if the spec is malformed, the
standard_attrs mapping uses unknown keys, the archetype's
required keys are missing, or a rule with the same
(class_name, method_name) is already registered.
run_backtest ¤
run_backtest(
detector: LambdaDetector,
df: DataFrame,
*,
objects: Mapping[str, object] | None = None,
qualifiers: Mapping[str, str] | None = None
) -> BacktestResult
Run detector over df and summarize hit counts.
unregister_lambda_rule ¤
unregister_lambda_rule(
class_name: str, method_name: str
) -> None
Remove a rule previously installed by :func:register_lambda_rule.
Used by tests to keep the global REGISTRY clean between cases. No-op if the rule was not registered.
to_event_log ¤
to_event_log(
df: DataFrame,
*,
detector: str,
objects: Mapping[str, object] | None = None,
qualifiers: Mapping[str, str] | None = None,
o2o: TableInput = None,
object_changes: TableInput = None,
validate: bool = True
) -> EventLog
Normalize a legacy detector DataFrame into the canonical event log.
detector is "ClassName.method_name" — the same key used in the
taxonomy registry and the value written to ts_shape:detector.
objects binds OCEL object types to either:
- a string column name in
df(e.g.{"asset": "source_uuid"}), - a callable taking a row dict and returning an oid,
- a
pd.Seriesaligned withdfrows, - a scalar broadcast to every row.
Caller-supplied bindings are always honored. Types listed in the
adapter's LabelRule.produces_objects are also auto-extracted from
standard legacy columns (e.g. source_uuid -> asset) when no explicit
binding is given.
o2o and object_changes optionally attach the OCEL 2.0
object-to-object relations and time-varying object attributes. Each
accepts a DataFrame (or an iterable of dict rows) with the canonical
columns — see :func:ts_shape.eventlog.schema.empty_o2o /
:func:~ts_shape.eventlog.schema.empty_object_changes.
attach_objects ¤
attach_objects(
event_log: EventLog,
df: DataFrame,
specs: Sequence[ObjectSpec],
*,
qualifiers: Mapping[str, str] | None = None,
uuid_column: str = "uuid",
time_column: str = "systime",
hierarchy: Mapping[str, str] | None = None,
infer_o2o: bool = True,
validate: bool = True
) -> EventLog
Link an existing event log to detected objects by temporal containment.
For every event, any object whose presence interval contains the event
timestamp is linked with an event-to-object (E2O) relation. The detected
objects, o2o and object_changes are merged in. This enriches the
output of any event detector — no per-detector changes required. See
:func:detect_objects for hierarchy (declared part_of edges).
This is a convenience over :func:relate: it first segments df into an
object-interval table via :func:object_intervals, then relates the events
to it. When you already hold an interval/object table, call :func:relate
directly and skip the raw signals.
derive_o2o ¤
derive_o2o(
intervals: DataFrame,
*,
hierarchy: Mapping[str, str] | None = None
) -> pd.DataFrame
Derive object-to-object (O2O) relations from an interval table.
Public entry point over the overlap kernel: a declared hierarchy
(child type -> parent type) yields part_of along overlapping presence
intervals; without one, overlapping objects of different types get the
symmetric co_occurs qualifier. Returns the OCEL 2.0 o2o table.
detect_objects ¤
detect_objects(
df: DataFrame,
specs: Sequence[ObjectSpec],
*,
uuid_column: str = "uuid",
time_column: str = "systime",
hierarchy: Mapping[str, str] | None = None,
infer_o2o: bool = True,
validate: bool = True
) -> EventLog
Detect object instances and build their OCEL 2.0 tables.
Produces an :class:EventLog with no events — only objects, o2o
(object-to-object relations), and object_changes (presence lifecycle +
captured attributes). Compose it with event-detector logs via
:func:~ts_shape.eventlog.concat.concat.
hierarchy maps a child object type to its parent type
(e.g. {"serial": "batch", "batch": "work_order"}). A compositional
part_of relation is asserted only along these declared edges, when a
child's presence overlaps a parent's — because part_of cannot be inferred
from time alone (a long-running tool temporally contains every batch
without owning it). Without a declared hierarchy, overlapping objects of
different types get the honest, symmetric co_occurs qualifier instead.
Set infer_o2o=False to skip object-to-object relations entirely.
link_events_to_objects ¤
link_events_to_objects(
events: DataFrame,
intervals: DataFrame,
*,
qualifiers: Mapping[str, str] | None = None,
key_columns: Sequence[str] | None = None,
contain: bool = True
) -> pd.DataFrame
Derive all event-to-object (E2O) relations from two lists.
Takes an OCEL events table and an object/interval table (oid,
type and, for temporal linking, start / end) and returns the
OCEL 2.0 relations table -- with no access to the raw signals. Two
complementary strategies are unioned and de-duplicated:
- temporal containment (
contain, default on): link an event to every object whose presence interval[start, end]contains the event timestamp. Requiresstart/endcolumns. - key match (opt-in via
key_columns): link an event to the object whoseoidequals the value in one of the event'skey_columns-- for events that already name their object (e.g. abatchattribute).
qualifiers maps an object type to the relation qualifier to stamp
(e.g. {"batch": "processed_in"}); unmapped types get <NA>.
object_intervals ¤
object_intervals(
df: DataFrame,
specs: Sequence[ObjectSpec],
*,
uuid_column: str = "uuid",
time_column: str = "systime"
) -> pd.DataFrame
Extract one presence interval per object instance from id signals.
Returns a tidy frame with columns oid, type, start, end plus
one column per captured attribute. Each id signal is run-length encoded into
contiguous constant-value segments.
object_specs_from_metadata ¤
object_specs_from_metadata(
metadata: DataFrame,
*,
type_field: str = "object_type",
value_column_field: str = "object_value_column",
uuid_column: str = "uuid"
) -> list[ObjectSpec]
Build :class:ObjectSpec list from per-signal metadata tags.
Scans a metadata table (e.g. MetadataJsonLoader.to_df()) for a column
naming the object type per signal — so new object types are onboarded by
tagging metadata, not by writing code. The uuid may be the index or a
column; the type/value-column fields are matched directly or with a
config_ / config. prefix (mkdocs metadata flattening).
relate ¤
relate(
event_log: EventLog,
intervals: DataFrame,
*,
hierarchy: Mapping[str, str] | None = None,
qualifiers: Mapping[str, str] | None = None,
key_columns: Sequence[str] | None = None,
infer_o2o: bool = True,
validate: bool = True
) -> EventLog
Assemble a full OCEL 2.0 log from an event list and an object list.
This is the automated relationship step: given an existing event_log
(with events) and an intervals table describing objects -- columns
oid, type and, for temporal linking, start / end (plus any
attribute columns) -- it derives all relations and returns a validated
:class:EventLog whose five tables are ready to ingest into an OCEL 2.0
database:
objectsfrom the distinct(oid, type)pairs,relations(E2O) via :func:link_events_to_objects(temporal containment, plus optionalkey_columnsmatching),o2ovia :func:derive_o2o(declaredpart_ofhierarchyor symmetricco_occurs),object_changes(lifecycle + captured attributes).
Unlike :func:attach_objects, no raw signals or :class:ObjectSpec are
needed -- you bring the two lists, ts-shape derives the relationships.
to_event_log_ocel ¤
to_event_log_ocel(eventlog: EventLog) -> OCEL2Tables
Return the OCEL 2.0 tables as an :class:OCEL2Tables bundle.
The canonical schema already uses OCEL 2.0 column names, so this is a
pass-through with defensive copy() calls to prevent caller mutation.
register_object_type ¤
register_object_type(name: str) -> None
Register a custom OCEL object type so adapters can emit it.
validate ¤
validate(eventlog: EventLog) -> None
Raise ValueError if the event log violates the canonical schema.