1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use crate::state::{get_or_init_state, StoredObject};
use serde_json::json;
/// Priority 3 — DECLARE conformance checking.
///
/// Checks every trace in an event log against a stored DECLARE model.
/// Currently supports the "Response(A,B)" template (if A occurs, B must
/// follow it). Returns per-constraint and overall fitness metrics.
use wasm_bindgen::prelude::*;
/// Check an EventLog against a DECLARE model.
///
/// `declare_handle` — handle returned by `discover_declare` stored via
/// `store_declare_from_json`, or the raw result stored as a handle.
///
/// Returns a JSON string:
/// ```json
/// {
/// "total_traces": 100,
/// "avg_fitness": 0.92,
/// "constraints": [
/// {"template":"Response","activities":["A","B"],
/// "violations": 8, "fitness": 0.92}
/// ]
/// }
/// ```
#[wasm_bindgen]
pub fn check_declare_conformance(
log_handle: &str,
declare_handle: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
// Clone constraints out so we don't hold two locks
let constraints =
get_or_init_state().with_declare_model(declare_handle, |m| Ok(m.constraints.clone()))?;
let result_json = get_or_init_state().with_event_log(log_handle, |log| {
let total = log.traces.len();
// violations[i] = # traces violating constraint i
let mut violations: Vec<usize> = vec![0; constraints.len()];
for trace in &log.traces {
let acts: Vec<&str> = trace
.events
.iter()
.filter_map(|e| e.attributes.get(activity_key).and_then(|v| v.as_string()))
.collect();
for (ci, constraint) in constraints.iter().enumerate() {
let violated = match constraint.template.as_str() {
"Response" if constraint.activities.len() == 2 => {
let a = constraint.activities[0].as_str();
let b = constraint.activities[1].as_str();
// For each occurrence of A in the trace, B must follow
let mut violates = false;
for (i, &act) in acts.iter().enumerate() {
if act == a {
// Check if B appears anywhere after position i
if !acts[i + 1..].contains(&b) {
violates = true;
break;
}
}
}
violates
}
"Existence" if constraint.activities.len() == 1 => {
let a = constraint.activities[0].as_str();
!acts.contains(&a)
}
"Absence" if constraint.activities.len() == 1 => {
let a = constraint.activities[0].as_str();
acts.contains(&a)
}
"Init" if constraint.activities.len() == 1 => {
let a = constraint.activities[0].as_str();
acts.first().map_or(true, |&x| x != a)
}
"Precedence" if constraint.activities.len() == 2 => {
let a = constraint.activities[0].as_str();
let b = constraint.activities[1].as_str();
// B can only occur if A occurred before it
let mut a_seen = false;
let mut violates = false;
for &act in &acts {
if act == a {
a_seen = true;
}
if act == b && !a_seen {
violates = true;
break;
}
}
violates
}
_ => false, // Unknown template: no violation
};
if violated {
violations[ci] += 1;
}
}
}
let constraint_results: Vec<serde_json::Value> = constraints
.iter()
.zip(violations.iter())
.map(|(c, &v)| {
let fitness = if total == 0 {
1.0
} else {
1.0 - v as f64 / total as f64
};
json!({
"template": c.template,
"activities": c.activities,
"support": c.support,
"violations": v,
"fitness": fitness,
})
})
.collect();
let avg_fitness = if constraint_results.is_empty() {
1.0_f64
} else {
constraint_results
.iter()
.map(|r| r["fitness"].as_f64().unwrap_or(1.0))
.sum::<f64>()
/ constraint_results.len() as f64
};
serde_json::to_string(&json!({
"total_traces": total,
"avg_fitness": avg_fitness,
"constraints": constraint_results,
}))
.map_err(|e| crate::error::js_val(&e.to_string()))
})?;
Ok(crate::error::js_val(&result_json))
}
/// Store a DECLARE model from its JSON representation and return a handle.
///
/// ```javascript
/// const declareJson = JSON.stringify(pm.discover_declare(logHandle, 'concept:name'));
/// const declareHandle = pm.store_declare_from_json(declareJson);
/// const result = pm.check_declare_conformance(logHandle, declareHandle, 'concept:name');
/// ```
#[wasm_bindgen]
pub fn store_declare_from_json(declare_json: &str) -> Result<JsValue, JsValue> {
let model: crate::models::DeclareModel = serde_json::from_str(declare_json)
.map_err(|e| crate::error::js_val(&format!("Invalid DECLARE JSON: {}", e)))?;
let handle = get_or_init_state().store_object(StoredObject::DeclareModel(model))?;
Ok(crate::error::js_val(&handle))
}