use anyhow::Context;
use wasm4pm_compat::event_log::EventLog as CompatEventLog;
use wasm4pm_compat::process_tree::ProcessTree;
pub fn discover_inductive(log: &CompatEventLog, activity_key: &str) -> anyhow::Result<ProcessTree> {
discover_inductive_with_fallback(log, activity_key).map(|(tree, _)| tree)
}
pub fn discover_inductive_with_fallback(
log: &CompatEventLog,
activity_key: &str,
) -> anyhow::Result<(ProcessTree, bool)> {
let native_log: wasm4pm::models::EventLog = log.clone().into();
let admitted = wasm4pm_compat::admission::Admission::<_, ()>::new(native_log).into_evidence();
let typed_tree = wasm4pm::more_discovery::InductiveMiner::discover(&admitted, activity_key)
.map_err(|e| anyhow::anyhow!(e))
.context("Inductive Miner discovery failed")?;
let flower_fallback = flower_fallback_detected(&typed_tree.value);
Ok((typed_tree.value, flower_fallback))
}
fn flower_fallback_detected(tree: &ProcessTree) -> bool {
use wasm4pm_compat::process_tree::ProcessTreeNode;
tree.nodes.iter().any(|n| match n {
ProcessTreeNode::Operator { operator, children } => {
operator == &wasm4pm_compat::process_tree::ProcessTreeOperator::Loop
&& children.len() == 2
&& matches!(
tree.nodes.get(children[1].0),
Some(ProcessTreeNode::Activity(label)) if label.is_empty()
)
}
_ => false,
})
}
#[cfg(test)]
mod tests {
use super::*;
use wasm4pm_compat::event_log::{Event, Trace};
fn build_small_log() -> CompatEventLog {
let trace1 = Trace::new(
"case-1".to_string(),
vec![
Event::with_activity("a"),
Event::with_activity("b"),
Event::with_activity("d"),
],
);
let trace2 = Trace::new(
"case-2".to_string(),
vec![
Event::with_activity("a"),
Event::with_activity("c"),
Event::with_activity("d"),
],
);
CompatEventLog {
attributes: Vec::new(),
traces: vec![trace1, trace2],
extensions: None,
classifiers: None,
global_trace_attrs: None,
global_event_attrs: None,
}
}
#[test]
fn discover_inductive_runs_on_small_log_and_returns_nonempty_tree() {
let log = build_small_log();
let tree = discover_inductive(&log, "concept:name")
.expect("inductive miner should discover a process tree without panicking");
assert!(
!tree.nodes.is_empty(),
"expected a non-empty process tree, got {tree:?}"
);
assert!(
tree.root.is_some(),
"expected the discovered tree to have a root node"
);
}
}