Skip to content

objects

objects ¤

Object detection — turn identifier-bearing timeseries signals into OCEL 2.0 objects, relations, and time-varying attributes.

The event packs answer "what happened?". This module answers "to which things?" — the batches, serials, coils, recipes, tools, materials, drums, bundles, families, customer part-numbers (and any other object out there) that events refer to.

It is deliberately one generic, declarative layer, not another family of detector classes: you describe which signals carry which object type with an :class:ObjectSpec (or discover them from metadata), and a run-length kernel (the same value-change segmentation that powers batch/traceability detection) extracts every object's presence interval. New object types are added by data / config, never by code.

Three steps, all feeding the OCEL 2.0 tables on :class:~ts_shape.eventlog.model.EventLog:

  • :func:object_intervals — segment id signals into (oid, type, start, end) presence intervals (an id is present until the next id on the same signal).
  • :func:detect_objects — build the objects / o2o / object_changes tables. part_of is asserted only along a declared hierarchy (it cannot be inferred from time alone); other overlaps are reported as co_occurs.
  • :func:attach_objects — link an existing event log's events to the detected objects by temporal containment, so the output of every event detector gains rich object references with no per-detector changes.

When you already hold the two lists — an event list and an object list (intervals) — the relationship derivation is automated and needs no raw signals:

  • :func:link_events_to_objects — all event-to-object (E2O) relations, by temporal containment and/or direct key_columns matching.
  • :func:derive_o2o — all object-to-object (O2O) relations (declared part_of hierarchy or symmetric co_occurs).
  • :func:relate — assemble both, plus objects and object_changes, into one validated :class:EventLog ready to ingest into an OCEL 2.0 database.

Presence semantics (shared by every function here)¤

  • An id is present from its first sample until the next value on the same signal: the next id, or an empty / missing reading ("", whitespace, NaN / None / <NA>), which means "no object". A gap in the data does not end an object; only a different value does.
  • Presence is half-open [start, end): at a hand-over instant the incoming object owns the timestamp, never both. The last presence on a signal (and, in a user-supplied table, any presence no other object of its signal / type takes over from) is closed [start, end], its end being the last observation.
  • The same id appearing again later is the same object (one row in objects) with several presence intervals.
  • All timestamps are UTC (OCEL 2.0); tz-aware input is converted, naive input is taken as UTC.

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 (uuid value) whose value column is the object id.

object_type str

OCEL object type, e.g. "batch", "coil", "serial", "recipe". Auto-registered via :func:~ts_shape.eventlog.schema.register_object_type if unknown.

value_column str

Column carrying the id ("value_string" or "value_integer"). Empty strings and missing values mean "no object present".

min_duration str | None

Optional minimum presence (e.g. "1min", measured start to next value); shorter blips are dropped and the presence around them closes over the gap. The last id on a signal is open-ended and never dropped.

id_template str

How to render the object id from the raw value. Supports {value} and {type} — default "{value}"; use "{type}:{value}" to namespace ids across types.

attributes Mapping[str, str]

{attribute_name: signal_uuid} — the value in effect at each presence start (last non-missing sample at or before it) plus every change of that signal during the presence, emitted as object_changes.

object_intervals ¤

object_intervals(
    dataframe: DataFrame,
    specs: Sequence[ObjectSpec],
    *,
    uuid_column: str = "uuid",
    time_column: str = "systime"
) -> pd.DataFrame

Extract one presence interval per object presence from id signals.

Returns a tidy frame with columns oid, type, start, end, duration_seconds, uuid (the id signal), source_uuid (of the id signal, when the frame has that column) plus one column per captured attribute (its value at the presence start). Times are UTC.

Each id signal is run-length encoded: an id is present from its first sample until the next value on the same signal (next id, or an empty / missing reading). A signal whose last reading is still an id keeps that object present until the last timestamp of the frame. An id that re-appears later yields another row with the same oid. An empty frame or no matching signal gives an empty frame with the full column set.

detect_objects ¤

detect_objects(
    dataframe: 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: each child presence is part of the parent presence it overlaps longest — 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.

Parameters:

Name Type Description Default
dataframe DataFrame

Long-format frame with the id signals.

required
specs Sequence[ObjectSpec]

Which signal carries which object type.

required
uuid_column str

Signal identifier column.

'uuid'
time_column str

Timestamp column.

'systime'
hierarchy Mapping[str, str] | None

Child type -> parent type for part_of relations.

None
infer_o2o bool

Derive object-to-object relations.

True
validate bool

Check the result against the canonical schema.

True

Returns:

Name Type Description
An EventLog

class:EventLog with objects, o2o and object_changes

EventLog

and no events.

Raises:

Type Description
ColumnNotFoundError

When dataframe lacks the uuid, time or a spec's value column.

attach_objects ¤

attach_objects(
    event_log: EventLog,
    dataframe: 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 dataframe 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.

Parameters:

Name Type Description Default
event_log EventLog

The events to link.

required
dataframe DataFrame

Long-format frame with the id signals.

required
specs Sequence[ObjectSpec]

Which signal carries which object type.

required
qualifiers Mapping[str, str] | None

Relation qualifier per object type.

None
uuid_column str

Signal identifier column.

'uuid'
time_column str

Timestamp column.

'systime'
hierarchy Mapping[str, str] | None

Child type -> parent type for part_of relations.

None
infer_o2o bool

Derive object-to-object relations.

True
validate bool

Check the result against the canonical schema.

True

Returns:

Type Description
EventLog

event_log merged with the detected objects and the new relations.

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:

  • objects from the distinct (oid, type) pairs,
  • relations (E2O) via :func:link_events_to_objects (temporal containment, plus optional key_columns matching),
  • o2o via :func:derive_o2o (declared part_of hierarchy or symmetric co_occurs),
  • object_changes (lifecycle + captured attributes).

A missing start / end (NaT) means the presence is unbounded on that side (e.g. an object that is still active). Every column other than oid / type / start / end / duration_seconds / uuid / source_uuid is an attribute, recorded at the presence start.

Unlike :func:attach_objects, no raw signals or :class:ObjectSpec are needed -- you bring the two lists, ts-shape derives the relationships.

Parameters:

Name Type Description Default
event_log EventLog

The events to link.

required
intervals DataFrame

Object table with oid, type and optionally start / end and attribute columns.

required
hierarchy Mapping[str, str] | None

Child type -> parent type for part_of relations.

None
qualifiers Mapping[str, str] | None

Relation qualifier per object type.

None
key_columns Sequence[str] | None

Event columns that name an object id directly.

None
infer_o2o bool

Derive object-to-object relations.

True
validate bool

Check the result against the canonical schema.

True

Returns:

Type Description
EventLog

The validated five-table :class:EventLog.

Raises:

Type Description
ColumnNotFoundError

When intervals lacks oid or type.

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 from each child presence to the parent presence it overlaps longest; without one, overlapping objects of different types get the symmetric co_occurs qualifier (one row per pair, the type that sorts first in ocel:oid).

Parameters:

Name Type Description Default
intervals DataFrame

Object table with oid, type, start, end.

required
hierarchy Mapping[str, str] | None

Child type -> parent type.

None

Returns:

Type Description
DataFrame

The OCEL 2.0 o2o table, de-duplicated.

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).

Parameters:

Name Type Description Default
metadata DataFrame

Metadata table, one row per signal.

required
type_field str

Field naming the object type of a signal; rows without one are skipped.

'object_type'
value_column_field str

Field naming the signal's id column (default value_string); must be one of value_string, value_integer or value_double.

'object_value_column'
uuid_column str

Column (or index name) holding the signal uuid.

'uuid'

Returns:

Name Type Description
One list[ObjectSpec]

class:ObjectSpec per tagged signal; empty when the table has

list[ObjectSpec]

no uuid or type field.

Raises:

Type Description
ValueError

When a value-column entry is not an id column.

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 on (eid, oid, type):

  • temporal containment (contain, default on): link an event to every object whose presence contains the event timestamp. Presence is half-open [start, end) when another presence of the same signal (uuid column) or, without one, of the same type starts at end — so an event at a hand-over instant belongs to the incoming object only — and closed [start, end] otherwise (the last presence). NaT bounds are open. Requires start / end columns.
  • key match (opt-in via key_columns): link an event to the object whose oid equals the value in one of the event's key_columns -- for events that already name their object (e.g. a batch attribute).

Parameters:

Name Type Description Default
events DataFrame

OCEL events table with ocel:eid (and ocel:timestamp for temporal linking).

required
intervals DataFrame

Object table with oid, type and, for temporal linking, start / end.

required
qualifiers Mapping[str, str] | None

Maps an object type to the relation qualifier to stamp (e.g. {"batch": "processed_in"}); unmapped types get <NA>.

None
key_columns Sequence[str] | None

Event columns whose value is an object id. Integral floats (an integer id column with gaps) match "1", not "1.0".

None
contain bool

Link by temporal containment.

True

Returns:

Type Description
DataFrame

The OCEL 2.0 relations table.

Raises:

Type Description
ColumnNotFoundError

When events lacks ocel:eid (or ocel:timestamp with contain) or intervals lacks oid / type.