Skip to content

Commit

Permalink
OCEL2.0 JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
fit-alessandro-berti committed Sep 26, 2023
1 parent 634b6a5 commit f699329
Show file tree
Hide file tree
Showing 9 changed files with 593 additions and 4 deletions.
4 changes: 4 additions & 0 deletions pm4py/objects/ocel/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
OCEL_TYPED_OMAP_KEY = "ocel:typedOmap"
OCEL_VMAP_KEY = "ocel:vmap"
OCEL_OVMAP_KEY = "ocel:ovmap"
OCEL_O2O_KEY = "ocel:o2o"
OCEL_OBJCHANGES_KEY = "ocel:objectChanges"
OCEL_EVTYPES_KEY = "ocel:eventTypes"
OCEL_OBJTYPES_KEY = "ocel:objectTypes"
OCEL_GLOBAL_LOG = "ocel:global-log"
OCEL_GLOBAL_LOG_ATTRIBUTE_NAMES = "ocel:attribute-names"
OCEL_GLOBAL_LOG_OBJECT_TYPES = "ocel:object-types"
Expand Down
3 changes: 2 additions & 1 deletion pm4py/objects/ocel/exporter/jsonocel/exporter.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from enum import Enum
from typing import Optional, Dict, Any

from pm4py.objects.ocel.exporter.jsonocel.variants import classic
from pm4py.objects.ocel.exporter.jsonocel.variants import classic, ocel20
from pm4py.objects.ocel.obj import OCEL
from pm4py.util import exec_utils


class Variants(Enum):
CLASSIC = classic
OCEL20 = ocel20


def apply(ocel: OCEL, target_path: str, variant=Variants.CLASSIC, parameters: Optional[Dict[Any, Any]] = None):
Expand Down
2 changes: 1 addition & 1 deletion pm4py/objects/ocel/exporter/jsonocel/variants/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from pm4py.objects.ocel.exporter.jsonocel.variants import classic
from pm4py.objects.ocel.exporter.jsonocel.variants import classic, ocel20
117 changes: 117 additions & 0 deletions pm4py/objects/ocel/exporter/jsonocel/variants/ocel20.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import json
from enum import Enum
from typing import Optional, Dict, Any

import pandas as pd

from pm4py.objects.ocel import constants
from pm4py.objects.ocel.obj import OCEL
from pm4py.util import exec_utils, constants as pm4_constants
from pm4py.objects.ocel.util import ocel_consistency
from pm4py.objects.ocel.exporter.jsonocel.variants import classic
from pm4py.objects.ocel.util import attributes_per_type


class Parameters(Enum):
EVENT_ID = constants.PARAM_EVENT_ID
OBJECT_ID = constants.PARAM_OBJECT_ID
OBJECT_TYPE = constants.PARAM_OBJECT_TYPE
EVENT_ACTIVITY = constants.PARAM_EVENT_ACTIVITY
EVENT_TIMESTAMP = constants.PARAM_EVENT_TIMESTAMP
ENCODING = "encoding"


def apply(ocel: OCEL, target_path: str, parameters: Optional[Dict[Any, Any]] = None):
"""
Exports an object-centric event log (OCEL 2.0) in a JSONOCEL 2.0 file, using the classic JSON dump
Parameters
------------------
ocel
Object-centric event log
target_path
Destination path
parameters
Parameters of the algorithm, including:
- Parameters.EVENT_ID => the event ID column
- Parameters.OBJECT_ID => the object ID column
- Parameters.OBJECT_TYPE => the object type column
"""
if parameters is None:
parameters = {}

event_id = exec_utils.get_param_value(Parameters.EVENT_ID, parameters, ocel.event_id_column)
object_id = exec_utils.get_param_value(Parameters.OBJECT_ID, parameters, ocel.object_id_column)
object_type = exec_utils.get_param_value(Parameters.OBJECT_TYPE, parameters, ocel.object_type_column)
event_activity = exec_utils.get_param_value(Parameters.EVENT_ACTIVITY, parameters, ocel.event_activity)
event_timestamp = exec_utils.get_param_value(Parameters.EVENT_TIMESTAMP, parameters, ocel.event_timestamp)

encoding = exec_utils.get_param_value(Parameters.ENCODING, parameters, pm4_constants.DEFAULT_ENCODING)

ocel = ocel_consistency.apply(ocel, parameters=parameters)

base_object = classic.get_base_json_object(ocel, parameters=parameters)

ets, ots = attributes_per_type.get(ocel, parameters=parameters)

base_object[constants.OCEL_EVTYPES_KEY] = {}
for et in ets:
base_object[constants.OCEL_EVTYPES_KEY][et] = {}
et_atts = ets[et]
for k, v in et_atts.items():
this_type = "string"
if "date" in v or "time" in v:
this_type = "date"
elif "float" in v or "double" in v:
this_type = "float"
base_object[constants.OCEL_EVTYPES_KEY][et][k] = this_type

base_object[constants.OCEL_OBJTYPES_KEY] = {}
for ot in ots:
base_object[constants.OCEL_OBJTYPES_KEY][ot] = {}
ot_atts = ots[ot]
for k, v in ot_atts.items():
this_type = "string"
if "date" in v or "time" in v:
this_type = "date"
elif "float" in v or "double" in v:
this_type = "float"
base_object[constants.OCEL_OBJTYPES_KEY][ot][k] = this_type

base_object[constants.OCEL_OBJCHANGES_KEY] = []
if len(ocel.object_changes) > 0:
object_changes = ocel.object_changes.to_dict("records")
for i in range(len(object_changes)):
object_changes[i][event_timestamp] = object_changes[i][event_timestamp].isoformat()

base_object[constants.OCEL_OBJCHANGES_KEY] = object_changes

e2o_list = ocel.relations[[event_id, object_id, constants.DEFAULT_QUALIFIER]].to_dict("records")
eids = set()

for elem in e2o_list:
eid = elem[event_id]
oid = elem[object_id]
qualifier = elem[constants.DEFAULT_QUALIFIER]

if eid not in eids:
base_object[constants.OCEL_EVENTS_KEY][eid][constants.OCEL_TYPED_OMAP_KEY] = []
eids.add(eid)

base_object[constants.OCEL_EVENTS_KEY][eid][constants.OCEL_TYPED_OMAP_KEY].append({object_id: oid, constants.DEFAULT_QUALIFIER: qualifier})

o2o_list = ocel.o2o.to_dict("records")
oids = set()

for elem in o2o_list:
oid = elem[object_id]
oid2 = elem[object_id+"_2"]
qualifier = elem[constants.DEFAULT_QUALIFIER]

if oid not in oids:
base_object[constants.OCEL_OBJECTS_KEY][oid][constants.OCEL_O2O_KEY] = []
oids.add(oid)

base_object[constants.OCEL_OBJECTS_KEY][oid][constants.OCEL_O2O_KEY].append({object_id: oid2, constants.DEFAULT_QUALIFIER: qualifier})

json.dump(base_object, open(target_path, "w", encoding=encoding), indent=2)
21 changes: 20 additions & 1 deletion pm4py/objects/ocel/importer/jsonocel/variants/classic.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def get_base_ocel(json_obj: Any, parameters: Optional[Dict[Any, Any]] = None):
events = []
relations = []
objects = []
o2o = []
object_changes = []

event_id = exec_utils.get_param_value(Parameters.EVENT_ID, parameters, constants.DEFAULT_EVENT_ID)
event_activity = exec_utils.get_param_value(Parameters.EVENT_ACTIVITY, parameters, constants.DEFAULT_EVENT_ACTIVITY)
Expand All @@ -44,6 +46,12 @@ def get_base_ocel(json_obj: Any, parameters: Optional[Dict[Any, Any]] = None):
dct = {object_id: obj_id, object_type: obj_type}
for k, v in obj[constants.OCEL_OVMAP_KEY].items():
dct[k] = v
if constants.OCEL_O2O_KEY in obj:
this_rel_objs = obj[constants.OCEL_O2O_KEY]
for newel in this_rel_objs:
target_id = newel[object_id]
qualifier = newel[constants.DEFAULT_QUALIFIER]
o2o.append({object_id: obj_id, object_id+"_2": target_id, constants.DEFAULT_QUALIFIER: qualifier})
objects.append(dct)

for ev_id in json_obj[constants.OCEL_EVENTS_KEY]:
Expand All @@ -64,6 +72,9 @@ def get_base_ocel(json_obj: Any, parameters: Optional[Dict[Any, Any]] = None):
relations.append(this_rel[obj])
events.append(dct)

if constants.OCEL_OBJCHANGES_KEY in json_obj:
object_changes = json_obj[constants.OCEL_OBJCHANGES_KEY]

events = pd.DataFrame(events)
objects = pd.DataFrame(objects)
relations = pd.DataFrame(relations)
Expand All @@ -82,7 +93,15 @@ def get_base_ocel(json_obj: Any, parameters: Optional[Dict[Any, Any]] = None):
globals[constants.OCEL_GLOBAL_EVENT] = json_obj[constants.OCEL_GLOBAL_EVENT]
globals[constants.OCEL_GLOBAL_OBJECT] = json_obj[constants.OCEL_GLOBAL_OBJECT]

log = OCEL(events=events, objects=objects, relations=relations, globals=globals, parameters=parameters)
o2o = pd.DataFrame(o2o) if o2o else None
object_changes = pd.DataFrame(object_changes) if object_changes else None
if object_changes is not None and len(object_changes) > 0:
object_changes[event_timestamp] = pd.to_datetime(object_changes[event_timestamp])
obj_id_map = objects[[object_id, object_type]].to_dict("records")
obj_id_map = {x[object_id]: x[object_type] for x in obj_id_map}
object_changes[object_type] = object_changes[object_id].map(obj_id_map)

log = OCEL(events=events, objects=objects, relations=relations, o2o=o2o, object_changes=object_changes, globals=globals, parameters=parameters)

return log

Expand Down
2 changes: 2 additions & 0 deletions pm4py/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@ def read_ocel2(file_path: str) -> OCEL:
return read_ocel2_sqlite(file_path)
elif file_path.lower().endswith("xml") or file_path.lower().endswith("xmlocel"):
return read_ocel2_xml(file_path)
elif file_path.lower().endswith("jsonocel"):
return read_ocel_json(file_path)


def read_ocel2_sqlite(file_path: str) -> OCEL:
Expand Down
8 changes: 7 additions & 1 deletion pm4py/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,11 @@ def write_ocel_json(ocel: OCEL, file_path: str):
file_path = file_path + ".jsonocel"

from pm4py.objects.ocel.exporter.jsonocel import exporter as jsonocel_exporter
return jsonocel_exporter.apply(ocel, file_path, variant=jsonocel_exporter.Variants.CLASSIC)

is_ocel20 = ocel.is_ocel20()
variant = jsonocel_exporter.Variants.OCEL20 if is_ocel20 else jsonocel_exporter.Variants.CLASSIC

return jsonocel_exporter.apply(ocel, file_path, variant=variant)


def write_ocel_xml(ocel: OCEL, file_path: str):
Expand Down Expand Up @@ -282,6 +286,8 @@ def write_ocel2(ocel: OCEL, file_path: str):
return write_ocel2_sqlite(ocel, file_path)
elif file_path.lower().endswith("xml") or file_path.lower().endswith("xmlocel"):
return write_ocel2_xml(ocel, file_path)
elif file_path.lower().endswith("jsonocel"):
return write_ocel_json(ocel, file_path)


def write_ocel2_sqlite(ocel: OCEL, file_path: str):
Expand Down
Loading

0 comments on commit f699329

Please sign in to comment.