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 theobjects/o2o/object_changestables.part_ofis asserted only along a declaredhierarchy(it cannot be inferred from time alone); other overlaps are reported asco_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 directkey_columnsmatching. - :func:
derive_o2o— all object-to-object (O2O) relations (declaredpart_ofhierarchy or symmetricco_occurs). - :func:
relate— assemble both, plus objects andobject_changes, into one validated :class:EventLogready 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 ( |
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]
|
|
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 |
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
|
and no events. |
Raises:
| Type | Description |
|---|---|
ColumnNotFoundError
|
When |
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 |
None
|
infer_o2o
|
bool
|
Derive object-to-object relations. |
True
|
validate
|
bool
|
Check the result against the canonical schema. |
True
|
Returns:
| Type | Description |
|---|---|
EventLog
|
|
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).
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 |
required |
hierarchy
|
Mapping[str, str] | None
|
Child type -> parent type for |
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: |
Raises:
| Type | Description |
|---|---|
ColumnNotFoundError
|
When |
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 |
required |
hierarchy
|
Mapping[str, str] | None
|
Child type -> parent type. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
The OCEL 2.0 |
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
|
'object_value_column'
|
uuid_column
|
str
|
Column (or index name) holding the signal uuid. |
'uuid'
|
Returns:
| Name | Type | Description |
|---|---|---|
One |
list[ObjectSpec]
|
class: |
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 ¤
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 (uuidcolumn) or, without one, of the same type starts atend— so an event at a hand-over instant belongs to the incoming object only — and closed[start, end]otherwise (the last presence).NaTbounds are open. 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).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
events
|
DataFrame
|
OCEL events table with |
required |
intervals
|
DataFrame
|
Object table with |
required |
qualifiers
|
Mapping[str, str] | None
|
Maps an object |
None
|
key_columns
|
Sequence[str] | None
|
Event columns whose value is an object id. Integral
floats (an integer id column with gaps) match |
None
|
contain
|
bool
|
Link by temporal containment. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
The OCEL 2.0 |
Raises:
| Type | Description |
|---|---|
ColumnNotFoundError
|
When |