use chrono::{DateTime, Utc};
use mempill_types::Claim;
pub(crate) fn valid_time_is_trusted(vt: &mempill_types::ValidTime, threshold: f32) -> bool {
vt.valid_time_confidence >= threshold && vt.start.is_some()
}
pub(crate) fn valid_times_non_overlapping(
a: &mempill_types::ValidTime,
b: &mempill_types::ValidTime,
) -> bool {
let a_start = a.start.unwrap();
let b_start = b.start.unwrap();
let a_end = a.end;
let b_end = b.end;
let a_ends_before_b_starts = match a_end {
Some(ae) => ae <= b_start,
None => false,
};
let b_ends_before_a_starts = match b_end {
Some(be) => be <= a_start,
None => false,
};
a_ends_before_b_starts || b_ends_before_a_starts
}
pub(crate) fn claim_is_trusted(claim: &Claim, threshold: f32) -> bool {
let vt = claim.valid_time();
vt.valid_time_confidence >= threshold && vt.start.is_some()
}
pub(crate) fn windows_non_overlapping(a: &Claim, b: &Claim) -> bool {
let a_start = a.valid_time().start.unwrap(); let b_start = b.valid_time().start.unwrap();
let a_end = a.valid_time().end;
let b_end = b.valid_time().end;
let a_ends_before_b_starts = match a_end {
Some(ae) => ae <= b_start,
None => false, };
let b_ends_before_a_starts = match b_end {
Some(be) => be <= a_start,
None => false,
};
a_ends_before_b_starts || b_ends_before_a_starts
}
pub(crate) fn is_trusted_succession(claims: &[&Claim], threshold: f32) -> bool {
if !claims.iter().all(|c| claim_is_trusted(c, threshold)) {
return false;
}
for i in 0..claims.len() {
for j in (i + 1)..claims.len() {
if !windows_non_overlapping(claims[i], claims[j]) {
return false;
}
}
}
true
}
pub(crate) fn select_by_valid_time_instant<'a>(
claims: &[&'a Claim],
instant: DateTime<Utc>,
) -> Option<&'a Claim> {
for claim in claims {
let start = claim.valid_time().start.unwrap(); let end = claim.valid_time().end;
let after_start = instant >= start;
let before_end = end.is_none_or(|e| instant < e);
if after_start && before_end {
return Some(claim);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{TimeZone, Utc};
use mempill_types::{
AgentId, Cardinality, Claim, ClaimRef, Confidence, Criticality, ExternalAnchor,
ExternalKind, Fact, ProvenanceLabel, TransactionTime, ValidTime,
};
fn dt(year: i32, month: u32, day: u32) -> DateTime<Utc> {
Utc.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap()
}
fn make_claim(start: Option<DateTime<Utc>>, end: Option<DateTime<Utc>>, confidence: f32) -> Claim {
Claim::new(
ClaimRef::new_random(),
AgentId("a".into()),
Fact { subject: "s".into(), predicate: "p".into(), value: serde_json::json!("v") },
Cardinality::Functional,
ProvenanceLabel::External(ExternalKind::UserAsserted),
ExternalAnchor { nearest_external_anchor: None, derivation_depth: 0 },
TransactionTime(dt(2026, 1, 1)),
ValidTime { start, end, valid_time_confidence: confidence },
Confidence { value_confidence: 0.9, valid_time_confidence: confidence },
Criticality::Medium,
vec![],
None,
None,
)
}
const THRESHOLD: f32 = 0.7;
#[test]
fn trusted_with_start_and_high_confidence() {
let c = make_claim(Some(dt(2024, 1, 1)), None, 0.9);
assert!(claim_is_trusted(&c, THRESHOLD));
}
#[test]
fn not_trusted_without_start() {
let c = make_claim(None, None, 0.9);
assert!(!claim_is_trusted(&c, THRESHOLD));
}
#[test]
fn not_trusted_below_confidence() {
let c = make_claim(Some(dt(2024, 1, 1)), None, 0.5);
assert!(!claim_is_trusted(&c, THRESHOLD));
}
#[test]
fn trusted_at_threshold_exactly() {
let c = make_claim(Some(dt(2024, 1, 1)), None, 0.7);
assert!(claim_is_trusted(&c, THRESHOLD));
}
#[test]
fn non_overlapping_a_ends_before_b_starts() {
let a = make_claim(Some(dt(2024, 1, 1)), Some(dt(2024, 3, 1)), 0.9);
let b = make_claim(Some(dt(2024, 3, 1)), None, 0.9);
assert!(windows_non_overlapping(&a, &b));
assert!(windows_non_overlapping(&b, &a));
}
#[test]
fn overlapping_windows() {
let a = make_claim(Some(dt(2024, 1, 1)), Some(dt(2024, 4, 1)), 0.9);
let b = make_claim(Some(dt(2024, 3, 1)), None, 0.9);
assert!(!windows_non_overlapping(&a, &b));
}
#[test]
fn both_open_ended_overlap() {
let a = make_claim(Some(dt(2024, 1, 1)), None, 0.9);
let b = make_claim(Some(dt(2024, 3, 1)), None, 0.9);
assert!(!windows_non_overlapping(&a, &b));
}
#[test]
fn adjacent_windows_exact_boundary() {
let a = make_claim(Some(dt(2024, 1, 1)), Some(dt(2024, 3, 1)), 0.9);
let b = make_claim(Some(dt(2024, 3, 1)), Some(dt(2024, 5, 1)), 0.9);
assert!(windows_non_overlapping(&a, &b));
}
#[test]
fn two_claim_trusted_succession() {
let a = make_claim(Some(dt(2024, 1, 1)), Some(dt(2024, 3, 1)), 0.9);
let b = make_claim(Some(dt(2024, 3, 1)), None, 0.9);
assert!(is_trusted_succession(&[&a, &b], THRESHOLD));
}
#[test]
fn three_claim_chain_is_succession() {
let a = make_claim(Some(dt(2020, 1, 1)), Some(dt(2022, 1, 1)), 0.9);
let b = make_claim(Some(dt(2022, 1, 1)), Some(dt(2024, 1, 1)), 0.9);
let c = make_claim(Some(dt(2024, 1, 1)), None, 0.9);
assert!(is_trusted_succession(&[&a, &b, &c], THRESHOLD));
}
#[test]
fn overlapping_is_not_succession() {
let a = make_claim(Some(dt(2024, 1, 1)), Some(dt(2024, 4, 1)), 0.9);
let b = make_claim(Some(dt(2024, 3, 1)), None, 0.9);
assert!(!is_trusted_succession(&[&a, &b], THRESHOLD));
}
#[test]
fn low_confidence_breaks_succession() {
let a = make_claim(Some(dt(2024, 1, 1)), Some(dt(2024, 3, 1)), 0.5); let b = make_claim(Some(dt(2024, 3, 1)), None, 0.9);
assert!(!is_trusted_succession(&[&a, &b], THRESHOLD));
}
#[test]
fn no_start_breaks_succession() {
let a = make_claim(None, Some(dt(2024, 3, 1)), 0.9); let b = make_claim(Some(dt(2024, 3, 1)), None, 0.9);
assert!(!is_trusted_succession(&[&a, &b], THRESHOLD));
}
#[test]
fn select_first_window_matches_past_instant() {
let a = make_claim(Some(dt(2020, 1, 1)), Some(dt(2024, 3, 1)), 0.9);
let b = make_claim(Some(dt(2024, 3, 1)), None, 0.9);
let instant = dt(2022, 6, 1); let selected = select_by_valid_time_instant(&[&a, &b], instant);
assert!(selected.is_some());
assert_eq!(selected.unwrap().claim_ref(), a.claim_ref());
}
#[test]
fn select_second_window_matches_now_like_instant() {
let a = make_claim(Some(dt(2020, 1, 1)), Some(dt(2024, 3, 1)), 0.9);
let b = make_claim(Some(dt(2024, 3, 1)), None, 0.9);
let instant = dt(2025, 6, 1); let selected = select_by_valid_time_instant(&[&a, &b], instant);
assert!(selected.is_some());
assert_eq!(selected.unwrap().claim_ref(), b.claim_ref());
}
#[test]
fn select_boundary_start_inclusive() {
let a = make_claim(Some(dt(2024, 1, 1)), Some(dt(2024, 3, 1)), 0.9);
let b = make_claim(Some(dt(2024, 3, 1)), None, 0.9);
let instant = dt(2024, 3, 1); let selected = select_by_valid_time_instant(&[&a, &b], instant);
assert!(selected.is_some());
assert_eq!(selected.unwrap().claim_ref(), b.claim_ref()); }
#[test]
fn select_gap_returns_none() {
let a = make_claim(Some(dt(2024, 1, 1)), Some(dt(2024, 3, 1)), 0.9);
let b = make_claim(Some(dt(2024, 5, 1)), None, 0.9);
let instant = dt(2024, 4, 1); let selected = select_by_valid_time_instant(&[&a, &b], instant);
assert!(selected.is_none());
}
#[test]
fn select_before_all_windows_returns_none() {
let a = make_claim(Some(dt(2024, 1, 1)), None, 0.9);
let instant = dt(2020, 1, 1); let selected = select_by_valid_time_instant(&[&a], instant);
assert!(selected.is_none());
}
}