Skip to main content

ebi_objects/conversions/
to_event_log_csv.rs

1use crate::{
2    AttributeKey, CompressedEventLog, CompressedEventLogXes, EventLog, EventLogTraceAttributes,
3    EventLogXes,
4    ebi_objects::{
5        compressed_event_log_trace_attributes::CompressedEventLogTraceAttributes,
6        event_log_csv::{self, DEFAULT_QUOTE_CHARACTER, DEFAULT_SEPARATOR, EventLogCsv},
7    },
8};
9use anyhow::{Error, anyhow};
10use intmap::IntMap;
11use process_mining::core::event_data::case_centric::{AttributeValue, XESEditableAttribute};
12
13impl TryFrom<EventLog> for EventLogCsv {
14    type Error = Error;
15
16    fn try_from(value: EventLog) -> Result<Self, Self::Error> {
17        let EventLog {
18            activity_key,
19            traces,
20        } = value;
21
22        //create the attribute key
23        let mut attribute_key = AttributeKey::new();
24        let trace_id_attribute = attribute_key.process_attribute_column(0, "0");
25        let activity_attribute = attribute_key.process_attribute_column(1, "a");
26
27        //transform the traces
28        let mut data = Vec::with_capacity(traces.len());
29        for (trace_id, trace) in traces.into_iter().enumerate() {
30            if trace.len() == 0 {
31                return Err(anyhow!(
32                    "cannot convert this event log to csv; a csv file does not support empty traces"
33                ));
34            }
35
36            let mut new_events = Vec::with_capacity(trace.len());
37            for activity in trace {
38                let mut map = IntMap::with_capacity(1);
39                map.insert(trace_id_attribute, trace_id.to_string());
40                map.insert(
41                    activity_attribute,
42                    activity_key.get_activity_label(&activity).to_string(),
43                );
44                new_events.push(map);
45            }
46            data.push((trace_id.to_string(), new_events));
47        }
48
49        Ok(EventLogCsv {
50            activity_attribute,
51            activity_key,
52            attribute_key,
53            traces: data,
54            separator: event_log_csv::DEFAULT_SEPARATOR.as_bytes()[0],
55            quote_character: event_log_csv::DEFAULT_QUOTE_CHARACTER.as_bytes()[0],
56        })
57    }
58}
59
60macro_rules! via_log {
61    ($t:ident) => {
62        impl TryFrom<$t> for EventLogCsv {
63            type Error = Error;
64
65            fn try_from(value: $t) -> Result<Self, Self::Error> {
66                let log: EventLog = value.into();
67                log.try_into()
68            }
69        }
70    };
71}
72
73via_log!(CompressedEventLog);
74via_log!(CompressedEventLogTraceAttributes);
75via_log!(EventLogTraceAttributes);
76
77impl TryFrom<EventLogXes> for EventLogCsv {
78    type Error = Error;
79
80    fn try_from(value: EventLogXes) -> Result<Self, Self::Error> {
81        let EventLogXes {
82            activity_key,
83            classifier,
84            rust4pm_log,
85        } = value;
86
87        //create the attribute key
88        let mut attribute_key = AttributeKey::new();
89
90        //transform the traces and events
91        let mut traces = Vec::with_capacity(rust4pm_log.traces.len());
92        for old_trace in rust4pm_log.traces {
93            if old_trace.events.len() == 0 {
94                return Err(anyhow!(
95                    "cannot convert this XES event log to csv; a csv file does not support empty traces"
96                ));
97            }
98
99            //get trace id
100            let trace_id= attribute_to_string(old_trace.attributes.get_by_key("concept:name").ok_or_else(|| anyhow!("trace does not have a concept:name attribute, thus cannot be translated to CSV"))?.value.clone()).ok_or_else(|| anyhow!("trace has an invalid concept:name attribute, i.e. it cannot be transformed to a string"))?;
101
102            //transform events
103            let mut events = Vec::with_capacity(old_trace.events.len());
104            for old_event in old_trace.events {
105                let mut event = IntMap::new();
106                for old_attribute in old_event.attributes {
107                    let attribute = attribute_key
108                        .process_attribute_value(&old_attribute.key, &old_attribute.value);
109                    if let Some(attribute_value) = attribute_to_string(old_attribute.value) {
110                        event.insert(attribute, attribute_value);
111                    }
112                }
113
114                events.push(event);
115            }
116
117            traces.push((trace_id, events));
118        }
119
120        let activity_attribute = attribute_key
121            .label_to_attribute(
122                classifier
123                    .keys
124                    .iter()
125                    .next()
126                    .ok_or_else(|| anyhow!("no event classifier attribute defined"))?,
127            )
128            .ok_or_else(|| anyhow!("could not find event attribute"))?;
129
130        Ok(EventLogCsv {
131            activity_attribute,
132            activity_key,
133            attribute_key,
134            traces,
135            separator: DEFAULT_SEPARATOR.as_bytes()[0],
136            quote_character: DEFAULT_QUOTE_CHARACTER.as_bytes()[0],
137        })
138    }
139}
140
141impl TryFrom<CompressedEventLogXes> for EventLogCsv {
142    type Error = Error;
143
144    fn try_from(value: CompressedEventLogXes) -> Result<Self, Self::Error> {
145        let xes: EventLogXes = value.into();
146        xes.try_into()
147    }
148}
149
150fn attribute_to_string(value: AttributeValue) -> Option<String> {
151    match value {
152        AttributeValue::String(s) => Some(s.clone()),
153        AttributeValue::Date(d) => Some(d.to_rfc3339()),
154        AttributeValue::Int(i) => Some(i.to_string()),
155        AttributeValue::Float(f) => Some(f.to_string()),
156        AttributeValue::Boolean(b) => Some(b.to_string()),
157        AttributeValue::ID(id) => Some(id.to_string()),
158        AttributeValue::List(_) => None,
159        AttributeValue::Container(_) => None,
160        AttributeValue::None() => None,
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use std::fs;
167
168    use crate::{EventLog, EventLogXes, NumberOfTraces, ebi_objects::event_log_csv::EventLogCsv};
169
170    #[test]
171    fn csv_conversion() {
172        let fin = fs::read_to_string("testfiles/a-b.xes").unwrap();
173        let log: EventLog = fin.parse::<EventLog>().unwrap();
174        assert_eq!(log.number_of_traces(), 2);
175
176        let csv: EventLogCsv = log.try_into().unwrap();
177        assert_eq!(csv.number_of_traces(), 2);
178    }
179
180    #[test]
181    fn csv_xes_conversion() {
182        let fin = fs::read_to_string("testfiles/a-b_multiple_separators.csv").unwrap();
183        let csv: EventLogCsv = fin.parse::<EventLogCsv>().unwrap();
184        assert_eq!(csv.number_of_traces(), 1);
185        assert_eq!(csv.number_of_events(), 2);
186
187        let xes: EventLogXes = csv.try_into().unwrap();
188        assert_eq!(xes.number_of_traces(), 1);
189        assert_eq!(xes.number_of_events(), 2);
190    }
191}