#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Event {
activity: String,
timestamp_ns: Option<i64>,
resource: Option<String>,
lifecycle: Option<String>,
}
impl Event {
pub fn new(activity: impl Into<String>) -> Self {
Event {
activity: activity.into(),
timestamp_ns: None,
resource: None,
lifecycle: None,
}
}
pub fn at_ns(mut self, ts: i64) -> Self {
self.timestamp_ns = Some(ts);
self
}
pub fn by(mut self, resource: impl Into<String>) -> Self {
self.resource = Some(resource.into());
self
}
pub fn with_lifecycle(mut self, transition: impl Into<String>) -> Self {
self.lifecycle = Some(transition.into());
self
}
pub fn activity(&self) -> &str {
&self.activity
}
#[must_use]
pub fn timestamp_ns(&self) -> Option<i64> {
self.timestamp_ns
}
#[must_use]
pub fn resource(&self) -> Option<&str> {
self.resource.as_deref()
}
#[must_use]
pub fn lifecycle(&self) -> Option<&str> {
self.lifecycle.as_deref()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Trace {
case_id: String,
events: Vec<Event>,
}
impl Trace {
pub fn new(case_id: impl Into<String>, events: impl IntoIterator<Item = Event>) -> Self {
Trace {
case_id: case_id.into(),
events: events.into_iter().collect(),
}
}
pub fn from_events(events: impl IntoIterator<Item = Event>) -> Self {
Trace::new("_", events)
}
pub fn case_id(&self) -> &str {
&self.case_id
}
pub fn events(&self) -> &[Event] {
&self.events
}
pub fn len(&self) -> usize {
self.events.len()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
#[must_use = "check the shape-check result"]
pub fn validate(&self) -> Result<(), EventLogRefusal> {
if self.case_id.is_empty() {
return Err(EventLogRefusal::MissingCaseId);
}
if self.events.is_empty() {
return Err(EventLogRefusal::EmptyTrace);
}
let mut last_ts: Option<i64> = None;
for ev in &self.events {
if ev.activity().is_empty() {
return Err(EventLogRefusal::MissingActivity);
}
if let Some(ts) = ev.timestamp_ns() {
if let Some(prev) = last_ts {
if ts < prev {
return Err(EventLogRefusal::NonMonotonicTrace);
}
}
last_ts = Some(ts);
}
}
Ok(())
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct EventLog {
traces: Vec<Trace>,
}
impl EventLog {
pub fn from_traces(traces: impl IntoIterator<Item = Trace>) -> Self {
EventLog {
traces: traces.into_iter().collect(),
}
}
pub fn traces(&self) -> &[Trace] {
&self.traces
}
pub fn trace_count(&self) -> usize {
self.traces.len()
}
pub fn event_count(&self) -> usize {
self.traces.iter().map(Trace::len).sum()
}
#[must_use = "check the shape-check result"]
pub fn validate(&self) -> Result<(), EventLogRefusal> {
for t in &self.traces {
t.validate()?;
}
Ok(())
}
}
impl<'a> IntoIterator for &'a EventLog {
type Item = &'a Trace;
type IntoIter = core::slice::Iter<'a, Trace>;
fn into_iter(self) -> Self::IntoIter {
self.traces.iter()
}
}
impl IntoIterator for EventLog {
type Item = Trace;
type IntoIter = std::vec::IntoIter<Trace>;
fn into_iter(self) -> Self::IntoIter {
self.traces.into_iter()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct EventStream {
buffered: Vec<Event>,
}
impl EventStream {
pub fn new() -> Self {
EventStream::default()
}
pub fn push(&mut self, event: Event) {
self.buffered.push(event);
}
pub fn buffered(&self) -> &[Event] {
&self.buffered
}
pub fn len(&self) -> usize {
self.buffered.len()
}
pub fn is_empty(&self) -> bool {
self.buffered.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EventLogClassifier {
pub name: String,
pub keys: Vec<String>,
}
impl EventLogClassifier {
pub fn new(name: impl Into<String>, keys: impl IntoIterator<Item = impl Into<String>>) -> Self {
EventLogClassifier {
name: name.into(),
keys: keys.into_iter().map(Into::into).collect(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum EventLogRefusal {
MissingCaseId,
MissingActivity,
MissingTimestamp,
EmptyTrace,
NonMonotonicTrace,
DuplicateEvent,
InvalidLifecycle,
}
impl core::fmt::Display for EventLogRefusal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let law = match self {
EventLogRefusal::MissingCaseId => "MissingCaseId",
EventLogRefusal::MissingActivity => "MissingActivity",
EventLogRefusal::MissingTimestamp => "MissingTimestamp",
EventLogRefusal::EmptyTrace => "EmptyTrace",
EventLogRefusal::NonMonotonicTrace => "NonMonotonicTrace",
EventLogRefusal::DuplicateEvent => "DuplicateEvent",
EventLogRefusal::InvalidLifecycle => "InvalidLifecycle",
};
write!(f, "event-log refused by law: {law}")
}
}
impl From<crate::ocel::OcelEvent> for Event {
fn from(ocel_event: crate::ocel::OcelEvent) -> Self {
let mut ev = Event::new(ocel_event.activity().to_owned());
if let Some(ts) = ocel_event.timestamp_ns() {
ev = ev.at_ns(ts);
}
ev
}
}
impl From<crate::xes::XesEvent> for Event {
fn from(xes_event: crate::xes::XesEvent) -> Self {
let activity = xes_event.concept_name().unwrap_or("").to_owned();
let mut ev = Event::new(activity);
if let Some(ts_str) = xes_event.attribute("time:timestamp") {
if let Ok(ts) = ts_str.parse::<i64>() {
ev = ev.at_ns(ts);
}
}
if let Some(res) = xes_event.attribute("org:resource") {
ev = ev.by(res.to_owned());
}
if let Some(lc) = xes_event.attribute("lifecycle:transition") {
ev = ev.with_lifecycle(lc.to_owned());
}
ev
}
}
pub type EventLogExtension = crate::xes::XesExtension;