use crate::models::*;
use crate::state::{get_or_init_state, StoredObject};
use crate::utilities::to_js_str;
use hashbrown::HashMap;
use itertools::Itertools;
use rustc_hash::FxHashMap;
use serde_json::json;
use std::collections::HashSet;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn analyze_variant_complexity(
eventlog_handle: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(eventlog_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let total = log.traces.len() as f64;
let variants = log
.traces
.iter()
.map(|trace| {
trace
.events
.iter()
.filter_map(|e| {
if let Some(AttributeValue::String(a)) = e.attributes.get(activity_key)
{
Some(a.clone())
} else {
None
}
})
.collect::<Vec<String>>()
})
.counts();
let entropy: f64 = variants.values().fold(0.0_f64, |acc, &count| {
let p = count as f64 / total;
p.log2().mul_add(-p, acc)
});
let mut variant_counts: Vec<usize> = variants.values().copied().collect();
variant_counts.sort_unstable_by(|a, b| b.cmp(a));
let coverage_top_10: f64 = variant_counts
.iter()
.take(10)
.map(|&v| v as f64 / total)
.sum();
let max_entropy = if variants.len() > 1 {
(variants.len() as f64).log2()
} else {
1.0
};
to_js_str(&json!({
"total_variants": variants.len(),
"entropy": entropy,
"max_entropy": max_entropy,
"normalized_entropy": if max_entropy > 0.0 { entropy / max_entropy } else { 0.0 },
"top_10_coverage": coverage_top_10,
"predominant_variant_size": variant_counts.first().copied().unwrap_or(0),
}))
}
Some(_) => Err(crate::error::js_val("Not an EventLog")),
None => Err(crate::error::js_val("EventLog not found")),
})
}
#[wasm_bindgen]
pub fn compute_activity_transition_matrix(
eventlog_handle: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(eventlog_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let activities = log.get_activities(activity_key);
let mut vocab: HashMap<String, u32> = HashMap::default();
for (idx, activity) in activities.iter().enumerate() {
vocab.insert(activity.clone(), idx as u32);
}
let mut transitions: FxHashMap<(u32, u32), usize> = FxHashMap::default();
let mut activity_total: FxHashMap<u32, usize> = FxHashMap::default();
for activity_id in vocab.values() {
activity_total.insert(*activity_id, 0);
}
for trace in &log.traces {
trace.events.windows(2).for_each(|w| {
if let (Some(AttributeValue::String(a1)), Some(AttributeValue::String(a2))) = (
w[0].attributes.get(activity_key),
w[1].attributes.get(activity_key),
) {
if let (Some(&a1_id), Some(&a2_id)) = (vocab.get(a1), vocab.get(a2)) {
*transitions.entry((a1_id, a2_id)).or_insert(0) += 1;
*activity_total.entry(a1_id).or_insert(0) += 1;
}
}
});
}
let matrix_data: Vec<_> = transitions
.iter()
.filter_map(|((from, to), count)| {
activities.get(*from as usize).and_then(|from_name| {
activities.get(*to as usize).map(|to_name| {
let prob = *count as f64
/ activity_total.get(from).copied().unwrap_or(1) as f64;
json!({
"from": from_name,
"to": to_name,
"count": count,
"probability": prob
})
})
})
})
.collect();
to_js_str(&json!({
"matrix": matrix_data,
"num_activities": activities.len(),
}))
}
Some(_) => Err(crate::error::js_val("Not an EventLog")),
None => Err(crate::error::js_val("EventLog not found")),
})
}
#[wasm_bindgen]
pub fn analyze_process_speedup(
eventlog_handle: &str,
timestamp_key: &str,
_window_size: usize,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(eventlog_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut time_gaps: Vec<f64> = Vec::new();
for trace in &log.traces {
let mut timestamps: Vec<String> = Vec::new();
for event in &trace.events {
if let Some(AttributeValue::String(ts)) = event.attributes.get(timestamp_key) {
timestamps.push(ts.clone());
}
}
for i in 0..timestamps.len().saturating_sub(1) {
let gap =
crate::parse_iso8601_duration(×tamps[i], ×tamps[i + 1]).abs();
time_gaps.push(gap);
}
}
if time_gaps.is_empty() {
return to_js_str(&json!({
"message": "No timestamps found",
"gaps": []
}));
}
time_gaps.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let mean: f64 = time_gaps.iter().sum::<f64>() / time_gaps.len() as f64;
let p25_idx = ((time_gaps.len() as f64 - 1.0) * 0.25).round() as usize;
let p75_idx = ((time_gaps.len() as f64 - 1.0) * 0.75).round() as usize;
let percentile_25 = time_gaps[p25_idx];
let percentile_75 = time_gaps[p75_idx];
to_js_str(&json!({
"avg_gap": mean,
"p25": percentile_25,
"p75": percentile_75,
"speedup_range": percentile_75 - percentile_25,
}))
}
Some(_) => Err(crate::error::js_val("Not an EventLog")),
None => Err(crate::error::js_val("EventLog not found")),
})
}
#[wasm_bindgen]
pub fn compute_trace_similarity_matrix(
eventlog_handle: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(eventlog_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut similarities = Vec::new();
let trace_sets: Vec<HashSet<&str>> = log
.traces
.iter()
.map(|trace| {
trace
.events
.iter()
.filter_map(|e| e.attributes.get(activity_key)?.as_string())
.collect()
})
.collect();
for i in 0..log.traces.len() {
for j in (i + 1)..log.traces.len() {
let common = trace_sets[i].intersection(&trace_sets[j]).count();
let union = trace_sets[i].len() + trace_sets[j].len() - common;
let similarity = common as f64 / union.max(1) as f64;
if similarity > 0.5 {
similarities.push(json!({
"trace_i": i,
"trace_j": j,
"similarity": similarity
}));
}
}
}
to_js_str(&json!({
"similar_pairs": similarities,
"total_pairs": (log.traces.len() * (log.traces.len() - 1)) / 2,
}))
}
Some(_) => Err(crate::error::js_val("Not an EventLog")),
None => Err(crate::error::js_val("EventLog not found")),
})
}
#[wasm_bindgen]
pub fn analyze_temporal_bottlenecks(
eventlog_handle: &str,
activity_key: &str,
timestamp_key: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(eventlog_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut activity_durations: HashMap<String, Vec<f64>> = HashMap::default();
for trace in &log.traces {
let activities: Vec<(String, String)> = trace
.events
.iter()
.filter_map(|e| {
if let (
Some(AttributeValue::String(act)),
Some(AttributeValue::String(ts)),
) = (
e.attributes.get(activity_key),
e.attributes.get(timestamp_key),
) {
Some((act.clone(), ts.clone()))
} else {
None
}
})
.collect();
for i in 0..activities.len().saturating_sub(1) {
let duration =
crate::parse_iso8601_duration(&activities[i].1, &activities[i + 1].1);
activity_durations
.entry(activities[i].0.clone())
.or_default()
.push(duration.abs());
}
}
let bottlenecks: Vec<_> = activity_durations
.iter()
.map(|(activity, durations)| {
let avg: f64 = durations.iter().sum::<f64>() / durations.len() as f64;
let max = durations.iter().copied().fold(f64::NEG_INFINITY, f64::max);
json!({
"activity": activity,
"avg_duration": avg,
"max_duration": max,
})
})
.collect();
to_js_str(&json!({
"bottlenecks": bottlenecks,
}))
}
Some(_) => Err(crate::error::js_val("Not an EventLog")),
None => Err(crate::error::js_val("EventLog not found")),
})
}
#[wasm_bindgen]
pub fn extract_activity_ordering(
eventlog_handle: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(eventlog_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut mandatory_predecessors: HashMap<String, HashSet<String>> = HashMap::default();
for trace in &log.traces {
let activities: Vec<&str> = trace
.events
.iter()
.filter_map(|e| {
if let Some(AttributeValue::String(a)) = e.attributes.get(activity_key) {
Some(a.as_str())
} else {
None
}
})
.collect();
for (pos, &activity) in activities.iter().enumerate() {
let predecessors: HashSet<String> =
activities.iter().take(pos).map(|&a| a.to_owned()).collect();
mandatory_predecessors
.entry(activity.to_owned())
.and_modify(|existing: &mut HashSet<String>| {
existing.retain(|p| predecessors.contains(p.as_str()));
})
.or_insert(predecessors);
}
}
let result: Vec<_> = mandatory_predecessors
.iter()
.map(|(activity, preds)| {
json!({
"activity": activity,
"mandatory_predecessors": preds.iter().cloned().collect::<Vec<_>>()
})
})
.collect();
to_js_str(&json!({
"activity_ordering": result,
}))
}
Some(_) => Err(crate::error::js_val("Not an EventLog")),
None => Err(crate::error::js_val("EventLog not found")),
})
}
#[wasm_bindgen]
pub fn final_analytics_info() -> String {
json!({
"status": "final_analytics_available",
"functions": [
{"name": "variant_complexity", "type": "entropy_analysis"},
{"name": "transition_matrix", "type": "markov_chain"},
{"name": "speedup_analysis", "type": "temporal"},
{"name": "trace_similarity", "type": "distance_metric"},
{"name": "temporal_bottlenecks", "type": "performance"},
{"name": "activity_ordering", "type": "constraints"},
]
})
.to_string()
}