use crate::algorithms::discover_alpha_plus_plus;
use crate::error::{codes, wasm_err};
use crate::models::OCEL;
use crate::state::{get_or_init_state, StoredObject};
use crate::utilities::to_js;
use serde_json::json;
#[cfg(feature = "ocel")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "ocel")]
#[wasm_bindgen]
pub fn discover_oc_petri_net(ocel_handle: &str, algorithm: &str) -> Result<JsValue, JsValue> {
let ocel = get_or_init_state().with_object(ocel_handle, |obj| match obj {
Some(StoredObject::OCEL(ocel)) => Ok(ocel.clone()),
Some(_) => Err(wasm_err(codes::INVALID_INPUT, "Object is not an OCEL")),
None => Err(wasm_err(
codes::INVALID_HANDLE,
format!("OCEL '{}' not found", ocel_handle),
)),
})?;
let mut result = serde_json::Map::new();
for obj_type in &ocel.object_types {
let flattened_log = flatten_ocel_to_eventlog_for_type(&ocel, obj_type)?;
let temp_handle = get_or_init_state()
.store_object(StoredObject::EventLog(flattened_log))
.map_err(|_e| crate::error::js_val("Failed to store flattened EventLog"))?;
let net_json_value = match algorithm {
"alpha++" | "alpha-plus-plus" => {
discover_alpha_plus_plus(&temp_handle, "concept:name", 0.5)?
}
"heuristic" => {
discover_alpha_plus_plus(&temp_handle, "concept:name", 0.5)?
}
_ => {
return Err(crate::error::js_val(&format!(
"Unknown algorithm: {}",
algorithm
)))
}
};
let net_json = serde_wasm_bindgen::from_value::<serde_json::Value>(net_json_value)
.map_err(|e| crate::error::js_val(&format!("Failed to parse Petri Net: {}", e)))?;
let mut annotated_net = net_json.clone();
if let Some(obj) = annotated_net.as_object_mut() {
if let Some(places) = obj.get_mut("places") {
if let Some(places_arr) = places.as_array_mut() {
for place in places_arr {
if let Some(place_obj) = place.as_object_mut() {
place_obj.insert("object_type".to_string(), json!(obj_type));
}
}
}
}
}
result.insert(obj_type.clone(), annotated_net);
}
to_js(&result)
}
pub fn flatten_ocel_to_eventlog_for_type(
ocel: &OCEL,
object_type: &str,
) -> Result<crate::models::EventLog, JsValue> {
use crate::models::{AttributeValue, Event, EventLog, Trace};
use std::collections::HashMap;
let target_objects: Vec<_> = ocel
.objects
.iter()
.filter(|o| o.object_type == object_type)
.collect();
if target_objects.is_empty() {
return Err(crate::error::js_val(&format!(
"No objects found of type '{}'",
object_type
)));
}
let mut event_log = EventLog::new();
for obj in target_objects {
let mut events_for_obj: Vec<&crate::models::OCELEvent> = ocel
.events
.iter()
.filter(|e| e.all_object_ids().any(|oid| oid == obj.id))
.collect();
events_for_obj.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
let mut trace = Trace {
attributes: {
let mut attrs = HashMap::new();
attrs.insert(
"concept:name".to_string(),
AttributeValue::String(obj.id.clone()),
);
attrs.insert(
"object_id".to_string(),
AttributeValue::String(obj.id.clone()),
);
attrs.insert(
"object_type".to_string(),
AttributeValue::String(object_type.to_string()),
);
attrs.extend(obj.attributes.clone());
attrs
},
events: Vec::new(),
};
for ocel_event in events_for_obj {
let mut event_attrs = HashMap::new();
event_attrs.insert(
"concept:name".to_string(),
AttributeValue::String(ocel_event.event_type.clone()),
);
event_attrs.insert(
"time:timestamp".to_string(),
AttributeValue::String(ocel_event.timestamp.clone()),
);
event_attrs.extend(ocel_event.attributes.clone());
trace.events.push(Event {
attributes: event_attrs,
});
}
event_log.traces.push(trace);
}
Ok(event_log)
}
#[wasm_bindgen]
pub fn oc_petri_net_info() -> JsValue {
let info = json!({
"module": "oc_petri_net",
"description": "Object-Centric Petri Net discovery from OCEL",
"algorithms": ["alpha++", "heuristic"],
"functions": [
{
"name": "discover_oc_petri_net",
"description": "Discover per-type Petri Nets from OCEL",
"params": ["ocel_handle", "algorithm"],
"returns": "JSON {object_type: {places, transitions, ...}}"
},
{
"name": "oc_petri_net_info",
"description": "Get information about this module",
"params": [],
"returns": "JSON info"
}
]
});
to_js(&info).unwrap_or(JsValue::NULL)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{OCELEvent, OCELObject, OCEL};
fn create_test_ocel() -> OCEL {
OCEL {
event_types: vec!["A".to_string(), "B".to_string()],
object_types: vec!["Order".to_string()],
events: vec![
OCELEvent {
id: "e1".to_string(),
event_type: "A".to_string(),
timestamp: "2024-01-01T10:00:00Z".to_string(),
attributes: std::collections::HashMap::new(),
object_ids: vec!["order1".to_string()],
object_refs: vec![],
},
OCELEvent {
id: "e2".to_string(),
event_type: "B".to_string(),
timestamp: "2024-01-01T11:00:00Z".to_string(),
attributes: std::collections::HashMap::new(),
object_ids: vec!["order1".to_string()],
object_refs: vec![],
},
],
objects: vec![OCELObject {
id: "order1".to_string(),
object_type: "Order".to_string(),
attributes: std::collections::HashMap::new(),
changes: vec![],
embedded_relations: vec![],
}],
object_relations: vec![],
}
}
#[test]
#[ignore = "discover_oc_petri_net uses JsValue which panics in test environment"]
fn test_oc_petri_net_discovery() {
let ocel = create_test_ocel();
let handle = get_or_init_state()
.store_object(StoredObject::OCEL(ocel))
.expect("Failed to store OCEL");
let result = discover_oc_petri_net(&handle, "alpha++");
assert!(result.is_ok(), "Petri net discovery should succeed");
}
#[test]
#[ignore = "discover_oc_petri_net uses JsValue which panics in test environment"]
fn test_oc_petri_net_invalid_handle() {
let result = discover_oc_petri_net("invalid", "alpha++");
assert!(result.is_err(), "Should fail on invalid handle");
}
#[test]
#[ignore = "serde_wasm_bindgen requires WASM context"]
fn test_oc_petri_net_returns_json() {
let ocel = create_test_ocel();
let handle = get_or_init_state()
.store_object(StoredObject::OCEL(ocel))
.expect("Failed to store OCEL");
let result = discover_oc_petri_net(&handle, "alpha++").expect("Discovery failed");
let json = serde_wasm_bindgen::from_value::<serde_json::Value>(result)
.expect("Should return valid JSON");
assert!(json.is_object());
}
#[test]
#[ignore = "discover_oc_petri_net uses JsValue which panics in test environment"]
fn test_oc_petri_net_empty_ocel() {
let ocel = OCEL {
event_types: vec![],
object_types: vec![],
events: vec![],
objects: vec![],
object_relations: vec![],
};
let handle = get_or_init_state()
.store_object(StoredObject::OCEL(ocel))
.expect("Failed to store OCEL");
let result = discover_oc_petri_net(&handle, "alpha++");
assert!(result.is_ok());
}
#[test]
#[ignore = "discover_oc_petri_net uses JsValue which panics in test environment"]
fn test_oc_petri_net_heuristic_algorithm() {
let ocel = create_test_ocel();
let handle = get_or_init_state()
.store_object(StoredObject::OCEL(ocel))
.expect("Failed to store OCEL");
let result = discover_oc_petri_net(&handle, "heuristic");
assert!(result.is_ok(), "Heuristic discovery should succeed");
}
}