1use super::{ActivityId, TraceId};
6use rkyv::{Archive, Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Archive, Serialize, Deserialize)]
10#[repr(u8)]
11pub enum ConformanceStatus {
12 #[default]
14 Conformant = 0,
15 WrongSequence = 1,
17 MissingActivity = 2,
19 ExtraActivity = 3,
21 Deviation = 4,
23 TimingViolation = 5,
25}
26
27impl ConformanceStatus {
28 pub fn name(&self) -> &'static str {
30 match self {
31 ConformanceStatus::Conformant => "Conformant",
32 ConformanceStatus::WrongSequence => "Wrong Sequence",
33 ConformanceStatus::MissingActivity => "Missing Activity",
34 ConformanceStatus::ExtraActivity => "Extra Activity",
35 ConformanceStatus::Deviation => "Deviation",
36 ConformanceStatus::TimingViolation => "Timing Violation",
37 }
38 }
39}
40
41#[derive(
43 Debug,
44 Clone,
45 Copy,
46 PartialEq,
47 Eq,
48 PartialOrd,
49 Ord,
50 Hash,
51 Default,
52 Archive,
53 Serialize,
54 Deserialize,
55)]
56#[repr(u8)]
57pub enum ComplianceLevel {
58 #[default]
60 FullyCompliant = 0,
61 MostlyCompliant = 1,
63 PartiallyCompliant = 2,
65 NonCompliant = 3,
67}
68
69impl ComplianceLevel {
70 pub fn color(&self) -> [u8; 3] {
72 match self {
73 ComplianceLevel::FullyCompliant => [40, 167, 69], ComplianceLevel::MostlyCompliant => [255, 193, 7], ComplianceLevel::PartiallyCompliant => [255, 152, 0], ComplianceLevel::NonCompliant => [220, 53, 69], }
78 }
79
80 pub fn from_fitness(fitness: f32) -> Self {
82 if fitness >= 0.95 {
83 ComplianceLevel::FullyCompliant
84 } else if fitness >= 0.80 {
85 ComplianceLevel::MostlyCompliant
86 } else if fitness >= 0.50 {
87 ComplianceLevel::PartiallyCompliant
88 } else {
89 ComplianceLevel::NonCompliant
90 }
91 }
92
93 pub fn name(&self) -> &'static str {
95 match self {
96 ComplianceLevel::FullyCompliant => "Fully Compliant",
97 ComplianceLevel::MostlyCompliant => "Mostly Compliant",
98 ComplianceLevel::PartiallyCompliant => "Partially Compliant",
99 ComplianceLevel::NonCompliant => "Non-Compliant",
100 }
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Archive, Serialize, Deserialize)]
106#[repr(u8)]
107pub enum AlignmentType {
108 #[default]
110 Synchronous = 0,
111 LogMove = 1,
113 ModelMove = 2,
115}
116
117impl AlignmentType {
118 pub fn cost(&self) -> u32 {
120 match self {
121 AlignmentType::Synchronous => 0,
122 AlignmentType::LogMove => 1,
123 AlignmentType::ModelMove => 1,
124 }
125 }
126}
127
128#[derive(Debug, Clone, Copy, Default, Archive, Serialize, Deserialize)]
130#[repr(C)]
131pub struct AlignmentMove {
132 pub move_type: u8,
134 pub _padding: [u8; 3],
136 pub log_activity: u32,
138 pub model_activity: u32,
140 pub cost: u32,
142}
143
144impl AlignmentMove {
145 pub fn synchronous(activity: ActivityId) -> Self {
147 Self {
148 move_type: AlignmentType::Synchronous as u8,
149 log_activity: activity,
150 model_activity: activity,
151 cost: 0,
152 _padding: [0; 3],
153 }
154 }
155
156 pub fn log_move(activity: ActivityId) -> Self {
158 Self {
159 move_type: AlignmentType::LogMove as u8,
160 log_activity: activity,
161 model_activity: u32::MAX,
162 cost: 1,
163 _padding: [0; 3],
164 }
165 }
166
167 pub fn model_move(activity: ActivityId) -> Self {
169 Self {
170 move_type: AlignmentType::ModelMove as u8,
171 log_activity: u32::MAX,
172 model_activity: activity,
173 cost: 1,
174 _padding: [0; 3],
175 }
176 }
177
178 pub fn get_type(&self) -> AlignmentType {
180 match self.move_type {
181 0 => AlignmentType::Synchronous,
182 1 => AlignmentType::LogMove,
183 2 => AlignmentType::ModelMove,
184 _ => AlignmentType::Synchronous,
185 }
186 }
187}
188
189#[derive(Debug, Clone, Copy, Default, Archive, Serialize, Deserialize)]
191#[repr(C, align(64))]
192pub struct ConformanceResult {
193 pub trace_id: u64,
195 pub model_id: u32,
197 pub status: u8,
199 pub compliance_level: u8,
201 pub _padding1: [u8; 2],
203 pub fitness: f32,
205 pub precision: f32,
207 pub generalization: f32,
209 pub simplicity: f32,
211 pub missing_count: u16,
213 pub extra_count: u16,
215 pub alignment_cost: u32,
217 pub alignment_length: u32,
219 pub _reserved: [u8; 16],
221}
222
223const _: () = assert!(std::mem::size_of::<ConformanceResult>() == 64);
225
226impl ConformanceResult {
227 pub fn conformant(trace_id: TraceId, model_id: u32) -> Self {
229 Self {
230 trace_id,
231 model_id,
232 status: ConformanceStatus::Conformant as u8,
233 compliance_level: ComplianceLevel::FullyCompliant as u8,
234 fitness: 1.0,
235 precision: 1.0,
236 generalization: 0.8,
237 simplicity: 1.0,
238 ..Default::default()
239 }
240 }
241
242 pub fn with_deviations(
244 trace_id: TraceId,
245 model_id: u32,
246 missing: u16,
247 extra: u16,
248 alignment_cost: u32,
249 ) -> Self {
250 let total = missing + extra;
251 let fitness = if total > 0 {
252 1.0 - (alignment_cost as f32 / (alignment_cost + 10) as f32)
253 } else {
254 1.0
255 };
256
257 let status = if missing > 0 {
258 ConformanceStatus::MissingActivity
259 } else if extra > 0 {
260 ConformanceStatus::ExtraActivity
261 } else {
262 ConformanceStatus::Deviation
263 };
264
265 Self {
266 trace_id,
267 model_id,
268 status: status as u8,
269 compliance_level: ComplianceLevel::from_fitness(fitness) as u8,
270 fitness,
271 precision: 1.0 - (extra as f32 / 10.0).min(1.0),
272 generalization: 0.8,
273 simplicity: 1.0,
274 missing_count: missing,
275 extra_count: extra,
276 alignment_cost,
277 ..Default::default()
278 }
279 }
280
281 pub fn get_status(&self) -> ConformanceStatus {
283 match self.status {
284 0 => ConformanceStatus::Conformant,
285 1 => ConformanceStatus::WrongSequence,
286 2 => ConformanceStatus::MissingActivity,
287 3 => ConformanceStatus::ExtraActivity,
288 4 => ConformanceStatus::Deviation,
289 5 => ConformanceStatus::TimingViolation,
290 _ => ConformanceStatus::Conformant,
291 }
292 }
293
294 pub fn get_compliance_level(&self) -> ComplianceLevel {
296 match self.compliance_level {
297 0 => ComplianceLevel::FullyCompliant,
298 1 => ComplianceLevel::MostlyCompliant,
299 2 => ComplianceLevel::PartiallyCompliant,
300 3 => ComplianceLevel::NonCompliant,
301 _ => ComplianceLevel::FullyCompliant,
302 }
303 }
304
305 pub fn is_conformant(&self) -> bool {
307 self.status == ConformanceStatus::Conformant as u8
308 }
309
310 pub fn f_score(&self) -> f32 {
312 if self.fitness + self.precision > 0.0 {
313 2.0 * self.fitness * self.precision / (self.fitness + self.precision)
314 } else {
315 0.0
316 }
317 }
318}
319
320#[derive(Debug, Clone)]
322pub struct ProcessModel {
323 pub id: u32,
325 pub name: String,
327 pub model_type: ProcessModelType,
329 pub start_activities: Vec<ActivityId>,
331 pub end_activities: Vec<ActivityId>,
333 pub transitions: Vec<(ActivityId, ActivityId)>,
335}
336
337impl ProcessModel {
338 pub fn new(id: u32, name: impl Into<String>, model_type: ProcessModelType) -> Self {
340 Self {
341 id,
342 name: name.into(),
343 model_type,
344 start_activities: Vec::new(),
345 end_activities: Vec::new(),
346 transitions: Vec::new(),
347 }
348 }
349
350 pub fn add_transition(&mut self, source: ActivityId, target: ActivityId) {
352 self.transitions.push((source, target));
353 }
354
355 pub fn has_transition(&self, source: ActivityId, target: ActivityId) -> bool {
357 self.transitions.contains(&(source, target))
358 }
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Archive, Serialize, Deserialize)]
363#[repr(u8)]
364pub enum ProcessModelType {
365 #[default]
367 DFG = 0,
368 PetriNet = 1,
370 BPMN = 2,
372 ProcessTree = 3,
374 Declare = 4,
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
383 fn test_conformance_result_size() {
384 assert_eq!(std::mem::size_of::<ConformanceResult>(), 64);
385 }
386
387 #[test]
388 fn test_compliance_level_from_fitness() {
389 assert_eq!(
390 ComplianceLevel::from_fitness(0.98),
391 ComplianceLevel::FullyCompliant
392 );
393 assert_eq!(
394 ComplianceLevel::from_fitness(0.85),
395 ComplianceLevel::MostlyCompliant
396 );
397 assert_eq!(
398 ComplianceLevel::from_fitness(0.60),
399 ComplianceLevel::PartiallyCompliant
400 );
401 assert_eq!(
402 ComplianceLevel::from_fitness(0.30),
403 ComplianceLevel::NonCompliant
404 );
405 }
406
407 #[test]
408 fn test_alignment_moves() {
409 let sync = AlignmentMove::synchronous(1);
410 assert_eq!(sync.cost, 0);
411 assert_eq!(sync.get_type(), AlignmentType::Synchronous);
412
413 let log = AlignmentMove::log_move(2);
414 assert_eq!(log.cost, 1);
415 assert_eq!(log.get_type(), AlignmentType::LogMove);
416
417 let model = AlignmentMove::model_move(3);
418 assert_eq!(model.cost, 1);
419 assert_eq!(model.get_type(), AlignmentType::ModelMove);
420 }
421}