#![allow(dead_code, unused_imports)]
pub mod elaboration;
pub mod fuzzy_overlap;
pub mod lang;
pub mod legacy;
pub mod orphan;
pub mod pane;
pub mod types;
pub use legacy::build_health_payload;
pub use types::{
CritSeverity, CritiqueEvent, FuzzWindows, FuzzyOverlapFinding, OrphanFinding,
Scope, Significance, Staleness, Suspicion,
};
use crate::timeline::Calendar;
pub fn fuzz_windows(calendar: &Calendar) -> FuzzWindows {
let day = calendar.ticks_per("day").unwrap_or(1).max(1);
FuzzWindows {
year: calendar.ticks_per("year").unwrap_or(day * 360),
season: calendar.ticks_per("season").unwrap_or(day * 90),
month: calendar.ticks_per("month").unwrap_or(day * 30),
week: calendar.ticks_per("week").unwrap_or(day * 7),
day,
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CritiqueReport {
pub orphans: Vec<OrphanFinding>,
pub overlaps: Vec<FuzzyOverlapFinding>,
}
impl CritiqueReport {
pub fn is_empty(&self) -> bool {
self.orphans.is_empty() && self.overlaps.is_empty()
}
pub fn total(&self) -> usize {
self.orphans.len() + self.overlaps.len()
}
}
pub fn run(
events: &[CritiqueEvent],
fuzz: &FuzzWindows,
min_significance: Significance,
min_suspicion: Suspicion,
cluster_min_size: usize,
staleness_threshold_days: i64,
) -> CritiqueReport {
let ctx = orphan::ScopeContext::from_events(events, staleness_threshold_days);
CritiqueReport {
orphans: orphan::detect(events, &ctx, min_significance),
overlaps: fuzzy_overlap::detect(events, fuzz, min_suspicion, cluster_min_size),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::timeline::Precision;
use uuid::Uuid;
fn fuzz() -> FuzzWindows {
FuzzWindows { year: 360, season: 90, month: 30, week: 7, day: 1 }
}
fn orphan_ev(title: &str) -> CritiqueEvent {
CritiqueEvent {
id: Uuid::new_v4(),
title: title.into(),
start_ticks: 0,
end_ticks: None,
precision: Precision::Day,
track: "main".into(),
is_orphan: true,
linked_paragraph_count: 0,
characters: vec![],
places: vec![],
age_days: Some(100),
}
}
#[test]
fn run_collects_both_kinds() {
let mara = Uuid::new_v4();
let mut overlap_a = orphan_ev("Training at Velmaril");
overlap_a.is_orphan = false;
overlap_a.precision = Precision::Season;
overlap_a.characters = vec![mara];
overlap_a.linked_paragraph_count = 1;
let mut overlap_b = overlap_a.clone();
overlap_b.id = Uuid::new_v4();
overlap_b.title = "Journey from Velmaril".into();
overlap_b.start_ticks = 30;
let events = vec![orphan_ev("The Lost Coronation Rite"), overlap_a, overlap_b];
let report = run(
&events,
&fuzz(),
Significance::Low,
Suspicion::Moderate,
DEFAULT_CLUSTER_MIN_SIZE,
60,
);
assert_eq!(report.orphans.len(), 1);
assert_eq!(report.overlaps.len(), 1);
assert!(!report.is_empty());
assert_eq!(report.total(), 2);
}
}
pub use fuzzy_overlap::DEFAULT_CLUSTER_MIN_SIZE;
pub use orphan::DEFAULT_STALENESS_DAYS;