use std::collections::HashSet;
#[derive(Clone, Debug, PartialEq)]
pub enum OcelAttributeValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
TimestampNs(i64),
List(Vec<OcelAttributeValue>),
Map(Vec<(String, OcelAttributeValue)>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct OcelAttribute {
pub key: String,
pub value: OcelAttributeValue,
}
impl OcelAttribute {
pub fn string(key: impl Into<String>, value: impl Into<String>) -> Self {
OcelAttribute {
key: key.into(),
value: OcelAttributeValue::String(value.into()),
}
}
pub fn integer(key: impl Into<String>, value: i64) -> Self {
OcelAttribute {
key: key.into(),
value: OcelAttributeValue::Integer(value),
}
}
pub fn float(key: impl Into<String>, value: f64) -> Self {
OcelAttribute {
key: key.into(),
value: OcelAttributeValue::Float(value),
}
}
pub fn boolean(key: impl Into<String>, value: bool) -> Self {
OcelAttribute {
key: key.into(),
value: OcelAttributeValue::Boolean(value),
}
}
pub fn timestamp_ns(key: impl Into<String>, value: i64) -> Self {
OcelAttribute {
key: key.into(),
value: OcelAttributeValue::TimestampNs(value),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OcelObject {
id: String,
object_type: String,
attributes: Vec<OcelAttribute>,
}
impl OcelObject {
pub fn new(id: impl Into<String>, object_type: impl Into<String>) -> Self {
OcelObject {
id: id.into(),
object_type: object_type.into(),
attributes: Vec::new(),
}
}
pub fn with_attribute(mut self, attr: OcelAttribute) -> Self {
self.attributes.push(attr);
self
}
pub fn id(&self) -> &str {
&self.id
}
pub fn object_type(&self) -> &str {
&self.object_type
}
pub fn attributes(&self) -> &[OcelAttribute] {
&self.attributes
}
}
#[deprecated(
since = "26.6.5",
note = "use `OcelObject` — the unambiguous name for the OCEL object shape"
)]
pub type Object = OcelObject;
#[derive(Clone, Debug, PartialEq)]
pub struct OcelEvent {
id: String,
activity: String,
timestamp_ns: Option<i64>,
attributes: Vec<OcelAttribute>,
}
impl OcelEvent {
pub fn new(id: impl Into<String>, activity: impl Into<String>) -> Self {
OcelEvent {
id: id.into(),
activity: activity.into(),
timestamp_ns: None,
attributes: Vec::new(),
}
}
pub fn with_attribute(mut self, attr: OcelAttribute) -> Self {
self.attributes.push(attr);
self
}
pub fn attributes(&self) -> &[OcelAttribute] {
&self.attributes
}
pub fn at_ns(mut self, ts: i64) -> Self {
self.timestamp_ns = Some(ts);
self
}
pub fn id(&self) -> &str {
&self.id
}
pub fn activity(&self) -> &str {
&self.activity
}
#[must_use]
pub fn timestamp_ns(&self) -> Option<i64> {
self.timestamp_ns
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EventObjectLink {
event_id: String,
object_id: String,
qualifier: Option<String>,
}
impl EventObjectLink {
pub fn new(event_id: impl Into<String>, object_id: impl Into<String>) -> Self {
EventObjectLink {
event_id: event_id.into(),
object_id: object_id.into(),
qualifier: None,
}
}
pub fn qualified(mut self, qualifier: impl Into<String>) -> Self {
self.qualifier = Some(qualifier.into());
self
}
pub fn event_id(&self) -> &str {
&self.event_id
}
pub fn object_id(&self) -> &str {
&self.object_id
}
#[must_use]
pub fn qualifier(&self) -> Option<&str> {
self.qualifier.as_deref()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ObjectObjectLink {
source_id: String,
target_id: String,
qualifier: Option<String>,
}
impl ObjectObjectLink {
pub fn new(source_id: impl Into<String>, target_id: impl Into<String>) -> Self {
ObjectObjectLink {
source_id: source_id.into(),
target_id: target_id.into(),
qualifier: None,
}
}
pub fn qualified(mut self, qualifier: impl Into<String>) -> Self {
self.qualifier = Some(qualifier.into());
self
}
pub fn source_id(&self) -> &str {
&self.source_id
}
pub fn target_id(&self) -> &str {
&self.target_id
}
#[must_use]
pub fn qualifier(&self) -> Option<&str> {
self.qualifier.as_deref()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ObjectChange {
object_id: String,
attribute: String,
value: String,
timestamp_ns: Option<i64>,
}
impl ObjectChange {
pub fn new(
object_id: impl Into<String>,
attribute: impl Into<String>,
value: impl Into<String>,
) -> Self {
ObjectChange {
object_id: object_id.into(),
attribute: attribute.into(),
value: value.into(),
timestamp_ns: None,
}
}
pub fn at_ns(mut self, ts: i64) -> Self {
self.timestamp_ns = Some(ts);
self
}
pub fn object_id(&self) -> &str {
&self.object_id
}
pub fn attribute(&self) -> &str {
&self.attribute
}
pub fn value(&self) -> &str {
&self.value
}
#[must_use]
pub fn timestamp_ns(&self) -> Option<i64> {
self.timestamp_ns
}
}
#[doc(alias = "object-centric event log")]
#[doc(alias = "OCEL")]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct OcelLog {
objects: Vec<OcelObject>,
events: Vec<OcelEvent>,
e2o: Vec<EventObjectLink>,
o2o: Vec<ObjectObjectLink>,
changes: Vec<ObjectChange>,
}
impl OcelLog {
pub fn new(
objects: impl IntoIterator<Item = OcelObject>,
events: impl IntoIterator<Item = OcelEvent>,
e2o: impl IntoIterator<Item = EventObjectLink>,
o2o: impl IntoIterator<Item = ObjectObjectLink>,
changes: impl IntoIterator<Item = ObjectChange>,
) -> Self {
OcelLog {
objects: objects.into_iter().collect(),
events: events.into_iter().collect(),
e2o: e2o.into_iter().collect(),
o2o: o2o.into_iter().collect(),
changes: changes.into_iter().collect(),
}
}
pub fn objects(&self) -> &[OcelObject] {
&self.objects
}
pub fn events(&self) -> &[OcelEvent] {
&self.events
}
pub fn event_object_links(&self) -> &[EventObjectLink] {
&self.e2o
}
pub fn object_object_links(&self) -> &[ObjectObjectLink] {
&self.o2o
}
pub fn object_changes(&self) -> &[ObjectChange] {
&self.changes
}
#[must_use = "check the shape-check result"]
pub fn validate(&self) -> Result<(), OcelRefusal> {
if self.objects.is_empty() {
return Err(OcelRefusal::MissingObject);
}
if self.events.is_empty() {
return Err(OcelRefusal::MissingEvent);
}
let mut object_ids: HashSet<&str> = HashSet::new();
for o in &self.objects {
if o.object_type().is_empty() {
return Err(OcelRefusal::MissingObjectType);
}
if !object_ids.insert(o.id()) {
return Err(OcelRefusal::DuplicateObjectId);
}
}
let mut event_ids: HashSet<&str> = HashSet::new();
for e in &self.events {
if !event_ids.insert(e.id()) {
return Err(OcelRefusal::DuplicateEventId);
}
}
if self.e2o.is_empty() {
return Err(OcelRefusal::EmptyEventObjectLinks);
}
for l in &self.e2o {
if !event_ids.contains(l.event_id()) || !object_ids.contains(l.object_id()) {
return Err(OcelRefusal::DanglingEventObjectLink);
}
}
for l in &self.o2o {
if !object_ids.contains(l.source_id()) || !object_ids.contains(l.target_id()) {
return Err(OcelRefusal::DanglingObjectObjectLink);
}
}
for c in &self.changes {
if c.attribute().is_empty() || !object_ids.contains(c.object_id()) {
return Err(OcelRefusal::InvalidObjectChange);
}
}
Ok(())
}
}
impl<'a> IntoIterator for &'a OcelLog {
type Item = &'a OcelEvent;
type IntoIter = core::slice::Iter<'a, OcelEvent>;
fn into_iter(self) -> Self::IntoIter {
self.events.iter()
}
}
impl IntoIterator for OcelLog {
type Item = OcelEvent;
type IntoIter = std::vec::IntoIter<OcelEvent>;
fn into_iter(self) -> Self::IntoIter {
self.events.into_iter()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum OcelRefusal {
MissingObject,
MissingEvent,
EmptyEventObjectLinks,
DanglingEventObjectLink,
DanglingObjectObjectLink,
DuplicateObjectId,
DuplicateEventId,
FlatteningLoss,
MissingObjectType,
InvalidObjectChange,
}
impl core::fmt::Display for OcelAttributeValue {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
OcelAttributeValue::String(s) => f.write_str(s),
OcelAttributeValue::Integer(n) => write!(f, "{n}"),
OcelAttributeValue::Float(v) => write!(f, "{v}"),
OcelAttributeValue::Boolean(b) => write!(f, "{b}"),
OcelAttributeValue::TimestampNs(n) => write!(f, "@{n}ns"),
OcelAttributeValue::List(items) => {
f.write_str("[")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{item}")?;
}
f.write_str("]")
}
OcelAttributeValue::Map(pairs) => {
f.write_str("{")?;
for (i, (k, v)) in pairs.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{k}: {v}")?;
}
f.write_str("}")
}
}
}
}
impl From<String> for OcelAttributeValue {
fn from(s: String) -> Self {
OcelAttributeValue::String(s)
}
}
impl From<&str> for OcelAttributeValue {
fn from(s: &str) -> Self {
OcelAttributeValue::String(s.to_owned())
}
}
impl From<i64> for OcelAttributeValue {
fn from(n: i64) -> Self {
OcelAttributeValue::Integer(n)
}
}
impl From<f64> for OcelAttributeValue {
fn from(v: f64) -> Self {
OcelAttributeValue::Float(v)
}
}
impl From<bool> for OcelAttributeValue {
fn from(b: bool) -> Self {
OcelAttributeValue::Boolean(b)
}
}
impl core::fmt::Display for OcelRefusal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let law = match self {
OcelRefusal::MissingObject => "MissingObject",
OcelRefusal::MissingEvent => "MissingEvent",
OcelRefusal::EmptyEventObjectLinks => "EmptyEventObjectLinks",
OcelRefusal::DanglingEventObjectLink => "DanglingEventObjectLink",
OcelRefusal::DanglingObjectObjectLink => "DanglingObjectObjectLink",
OcelRefusal::DuplicateObjectId => "DuplicateObjectId",
OcelRefusal::DuplicateEventId => "DuplicateEventId",
OcelRefusal::FlatteningLoss => "FlatteningLoss",
OcelRefusal::MissingObjectType => "MissingObjectType",
OcelRefusal::InvalidObjectChange => "InvalidObjectChange",
};
write!(f, "OCEL refused by law: {law}")
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct OcelDims {
pub object_types: Vec<String>,
pub activities: Vec<String>,
}
impl OcelDims {
#[must_use]
pub fn from_log(log: &OcelLog) -> Self {
use std::collections::BTreeSet;
let object_types: BTreeSet<String> = log
.objects()
.iter()
.map(|o| o.object_type().to_owned())
.collect();
let activities: BTreeSet<String> = log
.events()
.iter()
.map(|e| e.activity().to_owned())
.collect();
OcelDims {
object_types: object_types.into_iter().collect(),
activities: activities.into_iter().collect(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.object_types.is_empty() && self.activities.is_empty()
}
}
pub trait ObjectTypeTag: core::fmt::Debug + Clone + PartialEq {
const TYPE_NAME: &'static str;
}
#[derive(Clone, Debug, PartialEq)]
pub struct TypedObject<OT: ObjectTypeTag> {
inner: OcelObject,
_tag: core::marker::PhantomData<OT>,
}
impl<OT: ObjectTypeTag> TypedObject<OT> {
pub fn wrap(inner: OcelObject) -> Self {
TypedObject {
inner,
_tag: core::marker::PhantomData,
}
}
pub fn new(id: impl Into<String>) -> Self {
TypedObject {
inner: OcelObject::new(id, OT::TYPE_NAME),
_tag: core::marker::PhantomData,
}
}
pub fn inner(&self) -> &OcelObject {
&self.inner
}
pub fn into_inner(self) -> OcelObject {
self.inner
}
}
pub trait EventTypeTag: core::fmt::Debug + Clone + PartialEq {
const ACTIVITY_NAME: &'static str;
}
#[derive(Clone, Debug, PartialEq)]
pub struct TypedEvent<ET: EventTypeTag> {
inner: OcelEvent,
_tag: core::marker::PhantomData<ET>,
}
impl<ET: EventTypeTag> TypedEvent<ET> {
pub fn wrap(inner: OcelEvent) -> Self {
TypedEvent {
inner,
_tag: core::marker::PhantomData,
}
}
pub fn new(id: impl Into<String>) -> Self {
TypedEvent {
inner: OcelEvent::new(id, ET::ACTIVITY_NAME),
_tag: core::marker::PhantomData,
}
}
pub fn inner(&self) -> &OcelEvent {
&self.inner
}
pub fn into_inner(self) -> OcelEvent {
self.inner
}
}
pub trait AttributeTypeTag: core::fmt::Debug + Clone + PartialEq {
const ATTR_NAME: &'static str;
}
#[derive(Clone, Debug, PartialEq)]
pub struct TypedAttribute<AT: AttributeTypeTag> {
inner: OcelAttribute,
_tag: core::marker::PhantomData<AT>,
}
impl<AT: AttributeTypeTag> TypedAttribute<AT> {
pub fn wrap(inner: OcelAttribute) -> Self {
TypedAttribute {
inner,
_tag: core::marker::PhantomData,
}
}
pub fn inner(&self) -> &OcelAttribute {
&self.inner
}
pub fn into_inner(self) -> OcelAttribute {
self.inner
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TypedObjectChange {
object_id: String,
attribute: String,
value: OcelAttributeValue,
timestamp_ns: Option<i64>,
}
impl TypedObjectChange {
pub fn new(
object_id: impl Into<String>,
attribute: impl Into<String>,
value: OcelAttributeValue,
) -> Self {
TypedObjectChange {
object_id: object_id.into(),
attribute: attribute.into(),
value,
timestamp_ns: None,
}
}
pub fn at_ns(mut self, ts: i64) -> Self {
self.timestamp_ns = Some(ts);
self
}
pub fn object_id(&self) -> &str {
&self.object_id
}
pub fn attribute(&self) -> &str {
&self.attribute
}
pub fn value(&self) -> &OcelAttributeValue {
&self.value
}
#[must_use]
pub fn timestamp_ns(&self) -> Option<i64> {
self.timestamp_ns
}
}