use uuid::Uuid;
use crate::timeline::Precision;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Scope {
Track,
View,
Book,
Project,
}
impl Scope {
pub fn label(self) -> &'static str {
match self {
Scope::Track => "track",
Scope::View => "view",
Scope::Book => "book",
Scope::Project => "project",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CritiqueEvent {
pub id: Uuid,
pub title: String,
pub start_ticks: i64,
pub end_ticks: Option<i64>,
pub precision: Precision,
pub track: String,
pub is_orphan: bool,
pub linked_paragraph_count: usize,
pub characters: Vec<Uuid>,
pub places: Vec<Uuid>,
pub age_days: Option<i64>,
}
impl CritiqueEvent {
pub fn span(&self) -> (i64, i64) {
(self.start_ticks, self.end_ticks.unwrap_or(self.start_ticks))
}
pub fn overlaps(&self, other: &CritiqueEvent) -> bool {
let (a0, a1) = self.span();
let (b0, b1) = other.span();
a0 <= b1 && b0 <= a1
}
pub fn reference_count(&self) -> usize {
self.linked_paragraph_count + self.characters.len() + self.places.len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CritSeverity {
Info,
Warning,
Contradiction,
}
impl CritSeverity {
pub fn label(self) -> &'static str {
match self {
CritSeverity::Info => "Inquiry",
CritSeverity::Warning => "Warning",
CritSeverity::Contradiction => "Contradiction",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Significance {
Low,
Moderate,
High,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Staleness {
Recent,
Old,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OrphanFinding {
pub event_id: Uuid,
pub title: String,
pub track: String,
pub start_ticks: i64,
pub precision: Precision,
pub significance: Significance,
pub staleness: Staleness,
pub age_days: Option<i64>,
pub severity: CritSeverity,
pub reasons: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Suspicion {
Low,
Moderate,
High,
Cluster,
}
impl Suspicion {
pub fn severity(self) -> CritSeverity {
match self {
Suspicion::Low => CritSeverity::Info,
Suspicion::Moderate => CritSeverity::Info,
Suspicion::High => CritSeverity::Warning,
Suspicion::Cluster => CritSeverity::Warning,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FuzzyOverlapFinding {
pub event_ids: Vec<Uuid>,
pub titles: Vec<String>,
pub track: String,
pub overlap_window: (i64, i64),
pub suspicion: Suspicion,
pub is_cluster: bool,
pub same_track: bool,
pub precision: Precision,
pub total_events: usize,
pub shared_characters: Vec<Uuid>,
pub shared_places: Vec<Uuid>,
pub severity: CritSeverity,
pub reasons: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FuzzWindows {
pub year: i64,
pub season: i64,
pub month: i64,
pub week: i64,
pub day: i64,
}
impl FuzzWindows {
pub fn radius(self, p: Precision) -> i64 {
match p {
Precision::Year => self.year,
Precision::Season => self.season,
Precision::Month => self.month,
Precision::Week => self.week,
Precision::Day | Precision::Hour | Precision::Tick => 0,
}
}
pub fn is_fuzzy(p: Precision) -> bool {
matches!(p, Precision::Year | Precision::Season | Precision::Month | Precision::Week)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn overlaps_is_inclusive() {
let a = CritiqueEvent {
id: Uuid::nil(),
title: "a".into(),
start_ticks: 10,
end_ticks: Some(20),
precision: Precision::Day,
track: "main".into(),
is_orphan: false,
linked_paragraph_count: 0,
characters: vec![],
places: vec![],
age_days: None,
};
let b = CritiqueEvent { start_ticks: 20, end_ticks: None, ..a.clone() };
let c = CritiqueEvent { start_ticks: 21, end_ticks: None, ..a.clone() };
assert!(a.overlaps(&b));
assert!(!a.overlaps(&c));
}
#[test]
fn fuzz_radius_by_precision() {
let w = FuzzWindows { year: 360, season: 90, month: 30, week: 7, day: 1 };
assert_eq!(w.radius(Precision::Season), 90);
assert_eq!(w.radius(Precision::Month), 30);
assert_eq!(w.radius(Precision::Day), 0);
assert!(FuzzWindows::is_fuzzy(Precision::Season));
assert!(!FuzzWindows::is_fuzzy(Precision::Day));
}
}