use chrono::{DateTime, Duration, Utc};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Confidence {
Definite,
Likely,
Uncertain,
Stale,
}
impl Confidence {
pub fn annotation(&self) -> &'static str {
match self {
Confidence::Definite => " [CONFIRMED]",
Confidence::Likely => "",
Confidence::Uncertain => " [UNVERIFIED]",
Confidence::Stale => " [STALE]",
}
}
pub fn weight(&self) -> f64 {
match self {
Confidence::Definite => 1.0,
Confidence::Likely => 0.8,
Confidence::Uncertain => 0.4,
Confidence::Stale => 0.1,
}
}
}
impl std::fmt::Display for Confidence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Confidence::Definite => write!(f, "definite"),
Confidence::Likely => write!(f, "likely"),
Confidence::Uncertain => write!(f, "uncertain"),
Confidence::Stale => write!(f, "stale"),
}
}
}
fn ttl_days_for_source(source_type: &str) -> i64 {
match source_type {
"policy" => 365,
"system" => 180,
"external" => 90,
"user" | "peer" => 90,
_ => 90,
}
}
fn ttl_days_for_constraint(constraint_type: &str) -> i64 {
match constraint_type {
"policy" => 365,
"deadline" => 30,
"budget" => 90,
"capacity" => 30,
_ => 90,
}
}
pub fn infer_confidence(
authority: &str,
source_type: &str,
scope: &str,
is_constraint: bool,
created_at: DateTime<Utc>,
now: Option<DateTime<Utc>>,
) -> Confidence {
let now = now.unwrap_or_else(Utc::now);
let ttl = if is_constraint {
ttl_days_for_constraint(source_type)
} else {
ttl_days_for_source(source_type)
};
if now - created_at > Duration::days(ttl) {
return Confidence::Stale;
}
if scope == "hypothetical" || scope == "draft" {
return Confidence::Uncertain;
}
if is_constraint && (authority == "policy" || authority == "executive") {
return Confidence::Definite;
}
if source_type == "policy" || source_type == "system" {
return Confidence::Definite;
}
if authority == "policy" || authority == "executive" || authority == "manager" {
return Confidence::Likely;
}
if authority == "unverified" || authority == "peer" {
return Confidence::Uncertain;
}
Confidence::Likely
}
#[cfg(test)]
mod tests {
use super::*;
fn now() -> DateTime<Utc> {
Utc::now()
}
#[test]
fn policy_source_is_definite() {
let c = infer_confidence("policy", "policy", "global", false, now(), None);
assert_eq!(c, Confidence::Definite);
}
#[test]
fn system_source_is_definite() {
let c = infer_confidence("system", "system", "global", false, now(), None);
assert_eq!(c, Confidence::Definite);
}
#[test]
fn executive_constraint_is_definite() {
let c = infer_confidence("executive", "user", "global", true, now(), None);
assert_eq!(c, Confidence::Definite);
}
#[test]
fn manager_is_likely() {
let c = infer_confidence("manager", "user", "global", false, now(), None);
assert_eq!(c, Confidence::Likely);
}
#[test]
fn peer_is_uncertain() {
let c = infer_confidence("peer", "user", "global", false, now(), None);
assert_eq!(c, Confidence::Uncertain);
}
#[test]
fn hypothetical_is_uncertain() {
let c = infer_confidence("executive", "policy", "hypothetical", false, now(), None);
assert_eq!(c, Confidence::Uncertain);
}
#[test]
fn expired_user_fact_is_stale() {
let old = Utc::now() - Duration::days(100); let c = infer_confidence("user", "user", "global", false, old, Some(now()));
assert_eq!(c, Confidence::Stale);
}
#[test]
fn fresh_user_fact_not_stale() {
let recent = Utc::now() - Duration::days(30);
let c = infer_confidence("user", "user", "global", false, recent, Some(now()));
assert_ne!(c, Confidence::Stale);
}
#[test]
fn policy_has_long_ttl() {
let old = Utc::now() - Duration::days(300); let c = infer_confidence("policy", "policy", "global", false, old, Some(now()));
assert_eq!(c, Confidence::Definite); }
#[test]
fn annotations() {
assert_eq!(Confidence::Definite.annotation(), " [CONFIRMED]");
assert_eq!(Confidence::Likely.annotation(), "");
assert_eq!(Confidence::Stale.annotation(), " [STALE]");
}
}