Skip to main content

datasynth_ocpm/models/
event_log.rs

1//! Event log model for OCPM.
2//!
3//! The event log is the main container for all OCPM data, including
4//! events, objects, relationships, resources, and variants.
5
6use 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/// Complete OCPM event log with all events, objects, and relationships.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct OcpmEventLog {
19    /// Log metadata
20    pub metadata: EventLogMetadata,
21    /// Object type definitions
22    pub object_types: HashMap<String, ObjectType>,
23    /// Activity type definitions
24    pub activity_types: HashMap<String, ActivityType>,
25    /// All object instances
26    pub objects: ObjectGraph,
27    /// All object relationships
28    pub object_relationships: RelationshipIndex,
29    /// All events
30    pub events: Vec<OcpmEvent>,
31    /// Resources
32    pub resources: HashMap<String, Resource>,
33    /// Process variants (computed)
34    pub variants: HashMap<String, ProcessVariant>,
35    /// Case traces
36    pub cases: HashMap<Uuid, CaseTrace>,
37    /// Event index by object ID
38    #[serde(skip)]
39    events_by_object: HashMap<Uuid, Vec<usize>>,
40    /// Event index by activity
41    #[serde(skip)]
42    events_by_activity: HashMap<String, Vec<usize>>,
43    /// Event index by date
44    #[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    /// Create a new empty event log.
56    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    /// Create with metadata.
74    pub fn with_metadata(metadata: EventLogMetadata) -> Self {
75        Self {
76            metadata,
77            ..Self::new()
78        }
79    }
80
81    /// Register an object type.
82    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    /// Register an activity type.
88    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    /// Register a resource.
94    pub fn register_resource(&mut self, resource: Resource) {
95        self.resources
96            .insert(resource.resource_id.clone(), resource);
97    }
98
99    /// Add an object.
100    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    /// Add an object relationship.
106    pub fn add_relationship(&mut self, relationship: ObjectRelationship) {
107        self.object_relationships.add(relationship);
108    }
109
110    /// Add an event and update indices.
111    pub fn add_event(&mut self, event: OcpmEvent) {
112        let idx = self.events.len();
113        let date = event.timestamp.date_naive();
114
115        // Index by object
116        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        // Index by activity
124        self.events_by_activity
125            .entry(event.activity_id.clone())
126            .or_default()
127            .push(idx);
128
129        // Index by date
130        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    /// Add a case trace.
137    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    /// Get events for an object.
143    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    /// Get events for an activity.
151    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    /// Get events for a date range.
159    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    /// Compute process variants from completed cases.
167    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                // Check if variant exists, if not create it
176                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        // Calculate frequency percentages
195        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    /// Get summary statistics.
213    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    /// Initialize with standard P2P and O2C types.
227    pub fn with_standard_types(mut self) -> Self {
228        // Register P2P object types
229        for obj_type in ObjectType::p2p_types() {
230            self.register_object_type(obj_type);
231        }
232
233        // Register O2C object types
234        for obj_type in ObjectType::o2c_types() {
235            self.register_object_type(obj_type);
236        }
237
238        // Register P2P activities
239        for activity in ActivityType::p2p_activities() {
240            self.register_activity_type(activity);
241        }
242
243        // Register O2C activities
244        for activity in ActivityType::o2c_activities() {
245            self.register_activity_type(activity);
246        }
247
248        // Register standard resources
249        self.register_resource(Resource::erp_system());
250        self.register_resource(Resource::workflow_system());
251
252        self
253    }
254
255    /// Rebuild indices (call after deserialization).
256    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/// Event log metadata.
282#[derive(Debug, Clone, Serialize, Deserialize, Default)]
283pub struct EventLogMetadata {
284    /// Log name
285    pub log_name: String,
286    /// Creation timestamp
287    pub created_at: DateTime<Utc>,
288    /// Company codes included
289    pub company_codes: Vec<String>,
290    /// Start date of data
291    pub start_date: Option<NaiveDate>,
292    /// End date of data
293    pub end_date: Option<NaiveDate>,
294    /// Total event count
295    pub event_count: usize,
296    /// Total object count
297    pub object_count: usize,
298    /// Total case count
299    pub case_count: usize,
300    /// Total variant count
301    pub variant_count: usize,
302    /// Generator version
303    pub generator_version: String,
304}
305
306impl EventLogMetadata {
307    /// Create new metadata.
308    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/// Summary statistics for an event log.
319#[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}