rustkernel_procint/
types.rs

1//! Process intelligence types and data structures.
2
3use std::collections::{HashMap, HashSet};
4
5// ============================================================================
6// Event Log Types
7// ============================================================================
8
9/// A process event in a trace.
10#[derive(Debug, Clone)]
11pub struct ProcessEvent {
12    /// Event ID.
13    pub id: u64,
14    /// Case/trace ID.
15    pub case_id: String,
16    /// Activity name.
17    pub activity: String,
18    /// Timestamp.
19    pub timestamp: u64,
20    /// Resource (who performed the activity).
21    pub resource: Option<String>,
22    /// Additional attributes.
23    pub attributes: HashMap<String, String>,
24}
25
26/// A trace (sequence of events for a case).
27#[derive(Debug, Clone)]
28pub struct Trace {
29    /// Case ID.
30    pub case_id: String,
31    /// Events in order.
32    pub events: Vec<ProcessEvent>,
33    /// Trace attributes.
34    pub attributes: HashMap<String, String>,
35}
36
37impl Trace {
38    /// Create a new trace.
39    pub fn new(case_id: String) -> Self {
40        Self {
41            case_id,
42            events: Vec::new(),
43            attributes: HashMap::new(),
44        }
45    }
46
47    /// Add an event to the trace.
48    pub fn add_event(&mut self, event: ProcessEvent) {
49        self.events.push(event);
50    }
51
52    /// Get activity sequence.
53    pub fn activity_sequence(&self) -> Vec<&str> {
54        self.events.iter().map(|e| e.activity.as_str()).collect()
55    }
56
57    /// Sort events by timestamp.
58    pub fn sort_by_timestamp(&mut self) {
59        self.events.sort_by_key(|e| e.timestamp);
60    }
61}
62
63/// An event log containing multiple traces.
64#[derive(Debug, Clone)]
65pub struct EventLog {
66    /// Log name.
67    pub name: String,
68    /// Traces by case ID.
69    pub traces: HashMap<String, Trace>,
70    /// Log attributes.
71    pub attributes: HashMap<String, String>,
72}
73
74impl EventLog {
75    /// Create a new event log.
76    pub fn new(name: String) -> Self {
77        Self {
78            name,
79            traces: HashMap::new(),
80            attributes: HashMap::new(),
81        }
82    }
83
84    /// Add an event to the log.
85    pub fn add_event(&mut self, event: ProcessEvent) {
86        let trace = self
87            .traces
88            .entry(event.case_id.clone())
89            .or_insert_with(|| Trace::new(event.case_id.clone()));
90        trace.add_event(event);
91    }
92
93    /// Get all unique activities.
94    pub fn activities(&self) -> HashSet<&str> {
95        self.traces
96            .values()
97            .flat_map(|t| t.events.iter().map(|e| e.activity.as_str()))
98            .collect()
99    }
100
101    /// Get trace count.
102    pub fn trace_count(&self) -> usize {
103        self.traces.len()
104    }
105
106    /// Get event count.
107    pub fn event_count(&self) -> usize {
108        self.traces.values().map(|t| t.events.len()).sum()
109    }
110}
111
112// ============================================================================
113// Directly-Follows Graph Types
114// ============================================================================
115
116/// A directly-follows graph (DFG).
117#[derive(Debug, Clone)]
118pub struct DirectlyFollowsGraph {
119    /// Activities (nodes).
120    pub activities: Vec<String>,
121    /// Edges (from_activity, to_activity, count).
122    pub edges: Vec<DFGEdge>,
123    /// Start activities with frequency.
124    pub start_activities: HashMap<String, u64>,
125    /// End activities with frequency.
126    pub end_activities: HashMap<String, u64>,
127    /// Activity frequencies.
128    pub activity_counts: HashMap<String, u64>,
129}
130
131/// An edge in the DFG.
132#[derive(Debug, Clone)]
133pub struct DFGEdge {
134    /// Source activity.
135    pub source: String,
136    /// Target activity.
137    pub target: String,
138    /// Frequency count.
139    pub count: u64,
140    /// Average time between activities (ms).
141    pub avg_duration_ms: f64,
142}
143
144impl DirectlyFollowsGraph {
145    /// Create a new DFG.
146    pub fn new() -> Self {
147        Self {
148            activities: Vec::new(),
149            edges: Vec::new(),
150            start_activities: HashMap::new(),
151            end_activities: HashMap::new(),
152            activity_counts: HashMap::new(),
153        }
154    }
155
156    /// Get outgoing edges from an activity.
157    pub fn outgoing(&self, activity: &str) -> Vec<&DFGEdge> {
158        self.edges.iter().filter(|e| e.source == activity).collect()
159    }
160
161    /// Get incoming edges to an activity.
162    pub fn incoming(&self, activity: &str) -> Vec<&DFGEdge> {
163        self.edges.iter().filter(|e| e.target == activity).collect()
164    }
165
166    /// Get edge between two activities.
167    pub fn edge(&self, source: &str, target: &str) -> Option<&DFGEdge> {
168        self.edges
169            .iter()
170            .find(|e| e.source == source && e.target == target)
171    }
172}
173
174impl Default for DirectlyFollowsGraph {
175    fn default() -> Self {
176        Self::new()
177    }
178}
179
180/// DFG construction result with statistics.
181#[derive(Debug, Clone)]
182pub struct DFGResult {
183    /// The constructed DFG.
184    pub dfg: DirectlyFollowsGraph,
185    /// Number of traces processed.
186    pub trace_count: u64,
187    /// Number of events processed.
188    pub event_count: u64,
189    /// Number of unique activity pairs.
190    pub unique_pairs: u64,
191}
192
193// ============================================================================
194// Process Model Types
195// ============================================================================
196
197/// A Petri net place.
198#[derive(Debug, Clone)]
199pub struct Place {
200    /// Place ID.
201    pub id: String,
202    /// Place name.
203    pub name: String,
204    /// Current token count.
205    pub tokens: u32,
206}
207
208/// A Petri net transition.
209#[derive(Debug, Clone)]
210pub struct Transition {
211    /// Transition ID.
212    pub id: String,
213    /// Activity label (None for silent transitions).
214    pub label: Option<String>,
215    /// Is this transition visible?
216    pub visible: bool,
217}
218
219/// An arc in a Petri net.
220#[derive(Debug, Clone)]
221pub struct Arc {
222    /// Source ID (place or transition).
223    pub source: String,
224    /// Target ID (place or transition).
225    pub target: String,
226    /// Arc weight.
227    pub weight: u32,
228}
229
230/// A Petri net process model.
231#[derive(Debug, Clone)]
232pub struct PetriNet {
233    /// Model name.
234    pub name: String,
235    /// Places.
236    pub places: Vec<Place>,
237    /// Transitions.
238    pub transitions: Vec<Transition>,
239    /// Arcs.
240    pub arcs: Vec<Arc>,
241    /// Initial marking (place_id -> tokens).
242    pub initial_marking: HashMap<String, u32>,
243    /// Final marking (place_id -> tokens).
244    pub final_marking: HashMap<String, u32>,
245}
246
247impl PetriNet {
248    /// Create a new Petri net.
249    pub fn new(name: String) -> Self {
250        Self {
251            name,
252            places: Vec::new(),
253            transitions: Vec::new(),
254            arcs: Vec::new(),
255            initial_marking: HashMap::new(),
256            final_marking: HashMap::new(),
257        }
258    }
259
260    /// Add a place.
261    pub fn add_place(&mut self, id: String, name: String) {
262        self.places.push(Place {
263            id,
264            name,
265            tokens: 0,
266        });
267    }
268
269    /// Add a transition.
270    pub fn add_transition(&mut self, id: String, label: Option<String>) {
271        self.transitions.push(Transition {
272            id,
273            label: label.clone(),
274            visible: label.is_some(),
275        });
276    }
277
278    /// Add an arc.
279    pub fn add_arc(&mut self, source: String, target: String, weight: u32) {
280        self.arcs.push(Arc {
281            source,
282            target,
283            weight,
284        });
285    }
286
287    /// Get enabled transitions for current marking.
288    pub fn enabled_transitions(&self, marking: &HashMap<String, u32>) -> Vec<&Transition> {
289        self.transitions
290            .iter()
291            .filter(|t| {
292                // Check if all input places have enough tokens
293                self.arcs
294                    .iter()
295                    .filter(|a| a.target == t.id)
296                    .all(|a| marking.get(&a.source).copied().unwrap_or(0) >= a.weight)
297            })
298            .collect()
299    }
300}
301
302// ============================================================================
303// Conformance Types
304// ============================================================================
305
306/// Conformance checking result.
307#[derive(Debug, Clone)]
308pub struct ConformanceResult {
309    /// Trace ID.
310    pub case_id: String,
311    /// Is the trace conformant?
312    pub is_conformant: bool,
313    /// Fitness score (0-1).
314    pub fitness: f64,
315    /// Precision score (0-1).
316    pub precision: f64,
317    /// Deviations found.
318    pub deviations: Vec<Deviation>,
319    /// Alignment (if computed).
320    pub alignment: Option<Vec<AlignmentStep>>,
321}
322
323/// A deviation from the model.
324#[derive(Debug, Clone)]
325pub struct Deviation {
326    /// Event index in trace.
327    pub event_index: usize,
328    /// Activity that deviated.
329    pub activity: String,
330    /// Type of deviation.
331    pub deviation_type: DeviationType,
332    /// Description.
333    pub description: String,
334}
335
336/// Type of deviation.
337#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
338pub enum DeviationType {
339    /// Activity not allowed by model.
340    UnexpectedActivity,
341    /// Expected activity was missing.
342    MissingActivity,
343    /// Wrong order of activities.
344    WrongOrder,
345    /// Activity should not repeat.
346    UnexpectedRepetition,
347}
348
349/// A step in an alignment.
350#[derive(Debug, Clone)]
351pub struct AlignmentStep {
352    /// Log move (activity from trace).
353    pub log_move: Option<String>,
354    /// Model move (transition from model).
355    pub model_move: Option<String>,
356    /// Is this a synchronous move?
357    pub sync: bool,
358    /// Cost of this move.
359    pub cost: u32,
360}
361
362/// Aggregate conformance statistics.
363#[derive(Debug, Clone)]
364pub struct ConformanceStats {
365    /// Number of traces checked.
366    pub trace_count: u64,
367    /// Number of conformant traces.
368    pub conformant_count: u64,
369    /// Average fitness.
370    pub avg_fitness: f64,
371    /// Average precision.
372    pub avg_precision: f64,
373    /// Deviation breakdown by type.
374    pub deviation_counts: HashMap<DeviationType, u64>,
375}
376
377// ============================================================================
378// Partial Order Types
379// ============================================================================
380
381/// Partial order analysis result.
382#[derive(Debug, Clone)]
383pub struct PartialOrderResult {
384    /// Concurrent activity pairs.
385    pub concurrent_pairs: Vec<(String, String)>,
386    /// Sequential activity pairs (A before B).
387    pub sequential_pairs: Vec<(String, String)>,
388    /// Exclusive activity pairs (never in same trace).
389    pub exclusive_pairs: Vec<(String, String)>,
390    /// Parallelism score (0-1).
391    pub parallelism_score: f64,
392}
393
394// ============================================================================
395// Object-Centric Process Mining Types
396// ============================================================================
397
398/// An object in OCPM.
399#[derive(Debug, Clone)]
400pub struct OCPMObject {
401    /// Object ID.
402    pub id: String,
403    /// Object type.
404    pub object_type: String,
405    /// Object attributes.
406    pub attributes: HashMap<String, String>,
407}
408
409/// An OCPM event (can relate to multiple objects).
410#[derive(Debug, Clone)]
411pub struct OCPMEvent {
412    /// Event ID.
413    pub id: u64,
414    /// Activity name.
415    pub activity: String,
416    /// Timestamp.
417    pub timestamp: u64,
418    /// Related object IDs.
419    pub objects: Vec<String>,
420    /// Attributes.
421    pub attributes: HashMap<String, String>,
422}
423
424/// Object-centric event log.
425#[derive(Debug, Clone)]
426pub struct OCPMEventLog {
427    /// Events.
428    pub events: Vec<OCPMEvent>,
429    /// Objects.
430    pub objects: HashMap<String, OCPMObject>,
431    /// Object types.
432    pub object_types: HashSet<String>,
433}
434
435impl OCPMEventLog {
436    /// Create a new OCPM event log.
437    pub fn new() -> Self {
438        Self {
439            events: Vec::new(),
440            objects: HashMap::new(),
441            object_types: HashSet::new(),
442        }
443    }
444
445    /// Add an object.
446    pub fn add_object(&mut self, object: OCPMObject) {
447        self.object_types.insert(object.object_type.clone());
448        self.objects.insert(object.id.clone(), object);
449    }
450
451    /// Add an event.
452    pub fn add_event(&mut self, event: OCPMEvent) {
453        self.events.push(event);
454    }
455
456    /// Get events for an object.
457    pub fn events_for_object(&self, object_id: &str) -> Vec<&OCPMEvent> {
458        self.events
459            .iter()
460            .filter(|e| e.objects.contains(&object_id.to_string()))
461            .collect()
462    }
463}
464
465impl Default for OCPMEventLog {
466    fn default() -> Self {
467        Self::new()
468    }
469}
470
471/// OCPM pattern match result.
472#[derive(Debug, Clone)]
473pub struct OCPMPatternResult {
474    /// Pattern name.
475    pub pattern_name: String,
476    /// Matched object IDs.
477    pub matched_objects: Vec<String>,
478    /// Matched event IDs.
479    pub matched_events: Vec<u64>,
480    /// Pattern score.
481    pub score: f64,
482    /// Description.
483    pub description: String,
484}