ebi_objects/conversions/
to_event_log_csv.rs1use 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 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 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 let mut attribute_key = AttributeKey::new();
89
90 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 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 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}