use crate::store::Store;
use crate::tick::{source_ref_key, Tick};
use serde_json::{json, Value};
use std::io::Write;
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
pub fn append(
store: &Store,
op: &str,
tick: Option<&Tick>,
verdict: Option<&str>,
masked_stale: Option<&str>,
) {
let now = OffsetDateTime::now_utc();
let ts = now.format(&Rfc3339).unwrap_or_default();
let mut e = json!({ "ts": ts, "op": op });
if let Some(o) = e.as_object_mut() {
if let Some(t) = tick {
o.insert("tick_id".into(), Value::String(t.id.clone()));
if let Some(sr) = &t.source_ref {
o.insert("source_ref".into(), Value::String(source_ref_key(sr)));
}
if let Some(age) = age_bucket(&t.held_since, now.unix_timestamp()) {
o.insert("age".into(), Value::String(age.into()));
}
}
if let Some(v) = verdict {
o.insert("verdict".into(), Value::String(v.into()));
}
if let Some(m) = masked_stale {
o.insert("masked_stale".into(), Value::String(m.into()));
}
}
let dir = store.root.join("results");
if std::fs::create_dir_all(&dir).is_err() {
return;
}
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(dir.join("events.jsonl"))
{
let _ = writeln!(f, "{}", serde_json::to_string(&e).unwrap_or_default());
}
}
fn age_bucket(held_since: &str, now_unix: i64) -> Option<&'static str> {
let then = OffsetDateTime::parse(held_since, &Rfc3339).ok()?;
let days = (now_unix - then.unix_timestamp()) / 86_400;
Some(if days < 1 {
"fresh"
} else if days < 7 {
"days"
} else if days < 30 {
"weeks"
} else if days < 365 {
"months"
} else {
"year+"
})
}
#[cfg(test)]
mod tests {
use super::age_bucket;
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
const NOW: i64 = 1_750_000_000;
fn held(secs_ago: i64) -> String {
OffsetDateTime::from_unix_timestamp(NOW - secs_ago)
.unwrap()
.format(&Rfc3339)
.unwrap()
}
#[test]
fn age_bucket_should_label_each_threshold() {
let h = 3_600;
let d = 86_400;
assert_eq!(age_bucket(&held(0), NOW), Some("fresh"));
assert_eq!(age_bucket(&held(23 * h), NOW), Some("fresh"));
assert_eq!(age_bucket(&held(25 * h), NOW), Some("days"));
assert_eq!(age_bucket(&held(6 * d), NOW), Some("days"));
assert_eq!(age_bucket(&held(8 * d), NOW), Some("weeks"));
assert_eq!(age_bucket(&held(29 * d), NOW), Some("weeks"));
assert_eq!(age_bucket(&held(31 * d), NOW), Some("months"));
assert_eq!(age_bucket(&held(364 * d), NOW), Some("months"));
assert_eq!(age_bucket(&held(366 * d), NOW), Some("year+"));
}
#[test]
fn age_bucket_should_be_none_when_held_since_is_unparseable() {
assert_eq!(age_bucket("not a timestamp", NOW), None);
assert_eq!(age_bucket("", NOW), None);
}
}