1use chrono::{DateTime, NaiveDate, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::{BTreeMap, HashMap};
9use uuid::Uuid;
10
11use super::{
12 ActivityType, CaseTrace, ObjectGraph, ObjectInstance, ObjectRelationship, ObjectType,
13 OcpmEvent, ProcessVariant, RelationshipIndex, Resource,
14};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct OcpmEventLog {
19 pub metadata: EventLogMetadata,
21 pub object_types: HashMap<String, ObjectType>,
23 pub activity_types: HashMap<String, ActivityType>,
25 pub objects: ObjectGraph,
27 pub object_relationships: RelationshipIndex,
29 pub events: Vec<OcpmEvent>,
31 pub resources: HashMap<String, Resource>,
33 pub variants: HashMap<String, ProcessVariant>,
35 pub cases: HashMap<Uuid, CaseTrace>,
37 #[serde(skip)]
39 events_by_object: HashMap<Uuid, Vec<usize>>,
40 #[serde(skip)]
42 events_by_activity: HashMap<String, Vec<usize>>,
43 #[serde(skip)]
45 events_by_date: BTreeMap<NaiveDate, Vec<usize>>,
46}
47
48impl Default for OcpmEventLog {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl OcpmEventLog {
55 pub fn new() -> Self {
57 Self {
58 metadata: EventLogMetadata::default(),
59 object_types: HashMap::new(),
60 activity_types: HashMap::new(),
61 objects: ObjectGraph::new(),
62 object_relationships: RelationshipIndex::new(),
63 events: Vec::new(),
64 resources: HashMap::new(),
65 variants: HashMap::new(),
66 cases: HashMap::new(),
67 events_by_object: HashMap::new(),
68 events_by_activity: HashMap::new(),
69 events_by_date: BTreeMap::new(),
70 }
71 }
72
73 pub fn with_metadata(metadata: EventLogMetadata) -> Self {
75 Self {
76 metadata,
77 ..Self::new()
78 }
79 }
80
81 pub fn register_object_type(&mut self, object_type: ObjectType) {
83 self.object_types
84 .insert(object_type.type_id.clone(), object_type);
85 }
86
87 pub fn register_activity_type(&mut self, activity_type: ActivityType) {
89 self.activity_types
90 .insert(activity_type.activity_id.clone(), activity_type);
91 }
92
93 pub fn register_resource(&mut self, resource: Resource) {
95 self.resources
96 .insert(resource.resource_id.clone(), resource);
97 }
98
99 pub fn add_object(&mut self, object: ObjectInstance) {
101 self.objects.add_object(object);
102 self.metadata.object_count = self.objects.len();
103 }
104
105 pub fn add_relationship(&mut self, relationship: ObjectRelationship) {
107 self.object_relationships.add(relationship);
108 }
109
110 pub fn add_event(&mut self, event: OcpmEvent) {
112 let idx = self.events.len();
113 let date = event.timestamp.date_naive();
114
115 for obj_ref in &event.object_refs {
117 self.events_by_object
118 .entry(obj_ref.object_id)
119 .or_default()
120 .push(idx);
121 }
122
123 self.events_by_activity
125 .entry(event.activity_id.clone())
126 .or_default()
127 .push(idx);
128
129 self.events_by_date.entry(date).or_default().push(idx);
131
132 self.events.push(event);
133 self.metadata.event_count = self.events.len();
134 }
135
136 pub fn add_case(&mut self, case: CaseTrace) {
138 self.cases.insert(case.case_id, case);
139 self.metadata.case_count = self.cases.len();
140 }
141
142 pub fn events_for_object(&self, object_id: Uuid) -> Vec<&OcpmEvent> {
144 self.events_by_object
145 .get(&object_id)
146 .map(|indices| indices.iter().filter_map(|&i| self.events.get(i)).collect())
147 .unwrap_or_default()
148 }
149
150 pub fn events_for_activity(&self, activity_id: &str) -> Vec<&OcpmEvent> {
152 self.events_by_activity
153 .get(activity_id)
154 .map(|indices| indices.iter().filter_map(|&i| self.events.get(i)).collect())
155 .unwrap_or_default()
156 }
157
158 pub fn events_in_range(&self, start: NaiveDate, end: NaiveDate) -> Vec<&OcpmEvent> {
160 self.events_by_date
161 .range(start..=end)
162 .flat_map(|(_, indices)| indices.iter().filter_map(|&i| self.events.get(i)))
163 .collect()
164 }
165
166 pub fn compute_variants(&mut self) {
168 let mut variant_map: HashMap<Vec<String>, ProcessVariant> = HashMap::new();
169 let mut variant_counter = 0usize;
170
171 for case in self.cases.values() {
172 if case.is_completed() {
173 let key = case.activity_sequence.clone();
174
175 if !variant_map.contains_key(&key) {
177 variant_counter += 1;
178 let mut v = ProcessVariant::new(
179 &format!("V{}", variant_counter),
180 case.business_process,
181 );
182 v.activity_sequence = key.clone();
183 variant_map.insert(key.clone(), v);
184 }
185
186 let variant = variant_map.get_mut(&key).expect("variant just inserted");
187
188 if let Some(duration) = case.duration_hours() {
189 variant.add_case(case.case_id, duration);
190 }
191 }
192 }
193
194 let total_cases: u64 = variant_map.values().map(|v| v.frequency).sum();
196 for variant in variant_map.values_mut() {
197 variant.frequency_percent = if total_cases > 0 {
198 variant.frequency as f64 / total_cases as f64 * 100.0
199 } else {
200 0.0
201 };
202 }
203
204 self.variants = variant_map
205 .into_values()
206 .map(|v| (v.variant_id.clone(), v))
207 .collect();
208
209 self.metadata.variant_count = self.variants.len();
210 }
211
212 pub fn summary(&self) -> EventLogSummary {
214 EventLogSummary {
215 event_count: self.events.len(),
216 object_count: self.objects.len(),
217 relationship_count: self.object_relationships.len(),
218 case_count: self.cases.len(),
219 variant_count: self.variants.len(),
220 resource_count: self.resources.len(),
221 object_type_count: self.object_types.len(),
222 activity_type_count: self.activity_types.len(),
223 }
224 }
225
226 pub fn with_standard_types(mut self) -> Self {
228 for obj_type in ObjectType::p2p_types() {
230 self.register_object_type(obj_type);
231 }
232
233 for obj_type in ObjectType::o2c_types() {
235 self.register_object_type(obj_type);
236 }
237
238 for activity in ActivityType::p2p_activities() {
240 self.register_activity_type(activity);
241 }
242
243 for activity in ActivityType::o2c_activities() {
245 self.register_activity_type(activity);
246 }
247
248 self.register_resource(Resource::erp_system());
250 self.register_resource(Resource::workflow_system());
251
252 self
253 }
254
255 pub fn rebuild_indices(&mut self) {
257 self.events_by_object.clear();
258 self.events_by_activity.clear();
259 self.events_by_date.clear();
260
261 for (idx, event) in self.events.iter().enumerate() {
262 let date = event.timestamp.date_naive();
263
264 for obj_ref in &event.object_refs {
265 self.events_by_object
266 .entry(obj_ref.object_id)
267 .or_default()
268 .push(idx);
269 }
270
271 self.events_by_activity
272 .entry(event.activity_id.clone())
273 .or_default()
274 .push(idx);
275
276 self.events_by_date.entry(date).or_default().push(idx);
277 }
278 }
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize, Default)]
283pub struct EventLogMetadata {
284 pub log_name: String,
286 pub created_at: DateTime<Utc>,
288 pub company_codes: Vec<String>,
290 pub start_date: Option<NaiveDate>,
292 pub end_date: Option<NaiveDate>,
294 pub event_count: usize,
296 pub object_count: usize,
298 pub case_count: usize,
300 pub variant_count: usize,
302 pub generator_version: String,
304}
305
306impl EventLogMetadata {
307 pub fn new(log_name: &str) -> Self {
309 Self {
310 log_name: log_name.into(),
311 created_at: Utc::now(),
312 generator_version: env!("CARGO_PKG_VERSION").into(),
313 ..Default::default()
314 }
315 }
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct EventLogSummary {
321 pub event_count: usize,
322 pub object_count: usize,
323 pub relationship_count: usize,
324 pub case_count: usize,
325 pub variant_count: usize,
326 pub resource_count: usize,
327 pub object_type_count: usize,
328 pub activity_type_count: usize,
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_event_log_creation() {
337 let log = OcpmEventLog::new().with_standard_types();
338
339 assert!(!log.object_types.is_empty());
340 assert!(!log.activity_types.is_empty());
341 assert!(!log.resources.is_empty());
342 }
343
344 #[test]
345 fn test_event_log_summary() {
346 let log = OcpmEventLog::new();
347 let summary = log.summary();
348
349 assert_eq!(summary.event_count, 0);
350 assert_eq!(summary.object_count, 0);
351 }
352}