use crate::models::OCEL;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum OCDeclareTemplate {
Precedence,
Response,
Succession,
Existence,
Absence,
Init,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct OCDeclareOptions {
pub noise_threshold: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OCDeclareRule {
pub template: OCDeclareTemplate,
pub activity_a: String,
pub activity_b: Option<String>,
pub object_type: String,
pub confidence: f64,
pub support: f64,
}
pub fn discover_oc_declare(ocel: &OCEL, options: OCDeclareOptions) -> Vec<OCDeclareRule> {
let mut rules = Vec::new();
let obj_to_type: HashMap<String, String> = ocel
.objects
.iter()
.map(|o| (o.id.clone(), o.object_type.clone()))
.collect();
let mut object_traces: HashMap<String, Vec<String>> = HashMap::new();
let mut event_idx_by_obj: HashMap<String, Vec<usize>> = HashMap::new();
for (idx, event) in ocel.events.iter().enumerate() {
for obj_id in event.all_object_ids() {
event_idx_by_obj
.entry(obj_id.to_string())
.or_default()
.push(idx);
}
}
for (obj_id, mut indices) in event_idx_by_obj {
indices.sort_by_key(|&idx| &ocel.events[idx].timestamp);
let activities: Vec<String> = indices
.iter()
.map(|&idx| ocel.events[idx].event_type.clone())
.collect();
object_traces.insert(obj_id, activities);
}
let activity_types: HashSet<String> =
ocel.events.iter().map(|e| e.event_type.clone()).collect();
let object_types: Vec<String> = ocel.object_types.clone();
for ot in &object_types {
let traces_of_type: Vec<&Vec<String>> = object_traces
.iter()
.filter(|(id, _)| obj_to_type.get(*id) == Some(ot))
.map(|(_, trace)| trace)
.collect();
let total_instances = traces_of_type.len();
if total_instances == 0 {
continue;
}
for act_a in &activity_types {
let mut init_count = 0;
let mut existence_count = 0;
for trace in &traces_of_type {
if trace.first() == Some(act_a) {
init_count += 1;
}
if trace.contains(act_a) {
existence_count += 1;
}
}
let init_conf = init_count as f64 / total_instances as f64;
if init_conf >= 1.0 - options.noise_threshold {
rules.push(OCDeclareRule {
template: OCDeclareTemplate::Init,
activity_a: act_a.clone(),
activity_b: None,
object_type: ot.clone(),
confidence: init_conf,
support: init_conf,
});
}
let exist_conf = existence_count as f64 / total_instances as f64;
if exist_conf >= 1.0 - options.noise_threshold {
rules.push(OCDeclareRule {
template: OCDeclareTemplate::Existence,
activity_a: act_a.clone(),
activity_b: None,
object_type: ot.clone(),
confidence: exist_conf,
support: exist_conf,
});
}
for act_b in &activity_types {
if act_a == act_b {
continue;
}
let mut precedence_satisfied = 0;
let mut response_satisfied = 0;
let mut a_count = 0;
let mut b_count = 0;
for trace in &traces_of_type {
let has_a = trace.contains(act_a);
let has_b = trace.contains(act_b);
if has_a {
a_count += 1;
}
if has_b {
b_count += 1;
}
if has_b {
let first_b = trace.iter().position(|r| r == act_b).unwrap();
if trace[..first_b].contains(act_a) {
precedence_satisfied += 1;
}
}
if has_a {
let last_a = trace.iter().rposition(|r| r == act_a).unwrap();
if trace[last_a..].contains(act_b) {
response_satisfied += 1;
}
}
}
let prec_conf = if b_count > 0 {
precedence_satisfied as f64 / b_count as f64
} else {
1.0
};
if prec_conf >= 1.0 - options.noise_threshold && b_count > 0 {
rules.push(OCDeclareRule {
template: OCDeclareTemplate::Precedence,
activity_a: act_a.clone(),
activity_b: Some(act_b.clone()),
object_type: ot.clone(),
confidence: prec_conf,
support: b_count as f64 / total_instances as f64,
});
}
let resp_conf = if a_count > 0 {
response_satisfied as f64 / a_count as f64
} else {
1.0
};
if resp_conf >= 1.0 - options.noise_threshold && a_count > 0 {
rules.push(OCDeclareRule {
template: OCDeclareTemplate::Response,
activity_a: act_a.clone(),
activity_b: Some(act_b.clone()),
object_type: ot.clone(),
confidence: resp_conf,
support: a_count as f64 / total_instances as f64,
});
}
}
}
}
rules
}