///|
/// A case groups alerts and tracks analyst workflow state.
pub(all) struct Case {
id : String
subject_id : String
alerts : Array[Alert]
status : AlertStatus
notes : Array[String]
}
///|
pub fn Case::open(id : String, subject_id : String) -> Case {
{ id, subject_id, alerts: [], status: Open, notes: [] }
}
///|
pub fn Case::add_alert(self : Case, alert : Alert) -> Case {
let alerts : Array[Alert] = []
for old in self.alerts {
alerts.push(old)
}
alerts.push(alert)
{ ..self, alerts, }
}
///|
pub fn Case::note(self : Case, message : String) -> Case {
let notes : Array[String] = []
for old in self.notes {
notes.push(old)
}
notes.push(message)
{ ..self, notes, }
}
///|
pub fn Case::transition(self : Case, status : AlertStatus) -> Case {
{ ..self, status, }
}
///|
pub fn Case::risk(self : Case) -> RiskScore {
let scores = aggregate_scores(self.alerts)
match highest_score(scores) {
Some(score) => score
None => RiskScore::empty(self.subject_id)
}
}
///|
pub fn group_cases(alerts : Array[Alert]) -> Array[Case] {
let cases : Array[Case] = []
for alert in alerts {
let mut found = false
let mut index = 0
for case in cases {
if case.subject_id == alert.transaction_id {
cases[index] = case.add_alert(alert)
found = true
}
index += 1
}
if !found {
cases.push(
Case::open("case-" + alert.transaction_id, alert.transaction_id).add_alert(
alert,
),
)
}
}
cases
}
///|
pub fn actionable_cases(cases : Array[Case]) -> Array[Case] {
let result : Array[Case] = []
for case in cases {
let mut actionable = false
for alert in case.alerts {
if alert.is_actionable() {
actionable = true
}
}
if actionable {
result.push(case)
}
}
result
}