pub mod record;
pub mod snapshot;
pub mod store;
pub use record::{
Disposition, DispositionError, DispositionScope, RawDisposition, Verdict, parse_dispositions,
};
pub use snapshot::{DispositionSnapshot, RuleBucketsSnapshot, SNAPSHOT_VERSION};
pub use store::{
DEFAULT_BUCKET, DEFAULT_MAX_SEEN_IDS, DEFAULT_MIN_SAMPLE, DEFAULT_WINDOW, DispositionConfig,
DispositionStore, IngestOutcome, Numerator, RuleSummary, VerdictCounts,
};
use serde_json::json;
pub fn triage_feed(store: &DispositionStore) -> serde_json::Value {
let rules: Vec<serde_json::Value> = store
.summaries()
.into_iter()
.map(|s| {
let mut entry = json!({
"rule_id": s.rule_id,
"true_positives": s.true_positives,
"false_positives": s.false_positives,
});
if let Some(ratio) = s.fp_ratio {
entry["fp_ratio"] = json!(ratio);
}
entry
})
.collect();
json!({ "rules": rules })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn triage_feed_matches_scorecard_shape() {
let mut store = DispositionStore::new(DispositionConfig {
min_sample: 1,
..Default::default()
});
let raw: RawDisposition =
serde_json::from_str(r#"{"rule_id": "r1", "verdict": "false_positive"}"#).unwrap();
let d = Disposition::from_raw(raw, 100).unwrap();
store.apply(&d, 100);
let feed = triage_feed(&store);
let rules = feed["rules"].as_array().unwrap();
assert_eq!(rules.len(), 1);
assert_eq!(rules[0]["rule_id"], "r1");
assert_eq!(rules[0]["false_positives"], 1);
assert_eq!(rules[0]["true_positives"], 0);
assert_eq!(rules[0]["fp_ratio"], 1.0);
}
}