use crate::models::NGramPredictor;
use crate::state::{get_or_init_state, StoredObject};
use serde_json::json;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn build_ngram_predictor(
log_handle: &str,
activity_key: &str,
n: usize,
) -> Result<JsValue, JsValue> {
let n = n.max(2); let predictor = get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut counts: std::collections::HashMap<
Vec<String>,
std::collections::HashMap<String, usize>,
> = std::collections::HashMap::new();
for trace in &log.traces {
let acts: Vec<String> = trace
.events
.iter()
.filter_map(|e| {
e.attributes
.get(activity_key)
.and_then(|v| v.as_string())
.map(str::to_owned)
})
.collect();
if acts.len() < 2 {
continue;
}
for i in 0..acts.len() - 1 {
let context_len = (n - 1).min(i + 1);
let prefix: Vec<String> = acts[i + 1 - context_len..=i].to_vec();
let next = acts[i + 1].clone();
*counts.entry(prefix).or_default().entry(next).or_insert(0) += 1;
}
}
Ok(NGramPredictor { n, counts })
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})?;
let handle = get_or_init_state().store_object(StoredObject::NGramPredictor(predictor))?;
Ok(crate::error::js_val(&handle))
}
#[wasm_bindgen]
pub fn predict_next_activity(
predictor_handle: &str,
prefix_json: &str,
) -> Result<JsValue, JsValue> {
let prefix: Vec<String> = serde_json::from_str(prefix_json)
.map_err(|e| crate::error::js_val(&format!("Invalid prefix JSON: {}", e)))?;
let result_json = get_or_init_state().with_object(predictor_handle, |obj| match obj {
Some(StoredObject::NGramPredictor(predictor)) => {
let predictions = predictor.predict(&prefix);
let arr: Vec<serde_json::Value> = predictions
.iter()
.map(|(act, prob)| json!({"activity": act, "probability": prob}))
.collect();
serde_json::to_string(&arr).map_err(|e| crate::error::js_val(&e.to_string()))
}
Some(_) => Err(crate::error::js_val("Handle is not an NGramPredictor")),
None => Err(crate::error::js_val("NGramPredictor handle not found")),
})?;
Ok(crate::error::js_val(&result_json))
}
#[wasm_bindgen]
pub fn score_trace_likelihood(
predictor_handle: &str,
activities_json: &str,
) -> Result<JsValue, JsValue> {
let acts: Vec<String> = serde_json::from_str(activities_json)
.map_err(|e| crate::error::js_val(&format!("Invalid activities JSON: {}", e)))?;
get_or_init_state().with_object(predictor_handle, |obj| match obj {
Some(StoredObject::NGramPredictor(predictor)) => {
if acts.len() < 2 {
return Ok(JsValue::from_f64(0.0));
}
let mut log_prob = 0.0_f64;
for i in 0..acts.len() - 1 {
let context_len = (predictor.n - 1).min(i + 1);
let prefix = acts[i + 1 - context_len..=i].to_vec();
let preds = predictor.predict(&prefix);
let prob = preds
.iter()
.find(|(a, _)| a == &acts[i + 1])
.map_or(1e-10, |(_, p)| *p);
log_prob += prob.ln();
}
Ok(JsValue::from_f64(log_prob))
}
Some(_) => Err(crate::error::js_val("Handle is not an NGramPredictor")),
None => Err(crate::error::js_val("NGramPredictor handle not found")),
})
}