use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CadenceType {
PerfectAuthentic,
Plagal,
Deceptive,
Half,
Phrygian,
}
impl CadenceType {
pub fn description(&self) -> &str {
match self {
Self::PerfectAuthentic => "Full resolution — task definitively complete",
Self::Plagal => "Gentle resolution — task complete with soft landing",
Self::Deceptive => "False resolution — task appears done but isn't",
Self::Half => "Suspension — task paused, awaiting continuation",
Self::Phrygian => "Dramatic pause — task interrupted with tension",
}
}
pub fn is_resolved(&self) -> bool {
matches!(self, Self::PerfectAuthentic | Self::Plagal)
}
pub fn resolution_strength(&self) -> f64 {
match self {
Self::PerfectAuthentic => 1.0,
Self::Plagal => 0.85,
Self::Deceptive => 0.3,
Self::Half => 0.1,
Self::Phrygian => 0.05,
}
}
}
#[derive(Debug, Clone)]
pub struct TaskProgress {
task_id: String,
progress: f64, cadence: Option<CadenceType>,
}
impl TaskProgress {
pub fn new(task_id: impl Into<String>) -> Self {
Self {
task_id: task_id.into(),
progress: 0.0,
cadence: None,
}
}
pub fn with_progress(task_id: impl Into<String>, progress: f64) -> Self {
Self {
task_id: task_id.into(),
progress: progress.clamp(0.0, 1.0),
cadence: None,
}
}
pub fn detect_cadence(&mut self) -> CadenceType {
let cadence = if self.progress >= 0.98 {
CadenceType::PerfectAuthentic
} else if self.progress >= 0.90 {
CadenceType::Plagal
} else if self.progress >= 0.70 {
if self.progress >= 0.80 {
CadenceType::Deceptive
} else {
CadenceType::Half
}
} else if self.progress >= 0.40 {
CadenceType::Half
} else {
CadenceType::Phrygian
};
self.cadence = Some(cadence);
cadence
}
pub fn set_progress(&mut self, p: f64) {
self.progress = p.clamp(0.0, 1.0);
}
pub fn progress(&self) -> f64 {
self.progress
}
pub fn cadence(&self) -> Option<CadenceType> {
self.cadence
}
pub fn task_id(&self) -> &str {
&self.task_id
}
pub fn is_complete(&self) -> bool {
self.cadence.map_or(false, |c| c.is_resolved())
}
}
#[derive(Debug, Clone)]
pub struct CompletionSignal {
pub cadence: CadenceType,
pub agents: Vec<String>,
pub overall_completion: f64,
pub step: u64,
}
impl CompletionSignal {
pub fn detect_from_group(tasks: &[TaskProgress], step: u64) -> Option<Self> {
if tasks.is_empty() {
return None;
}
let total: f64 = tasks.iter().map(|t| t.progress()).sum();
let avg = total / tasks.len() as f64;
let cadence = if avg >= 0.98 {
CadenceType::PerfectAuthentic
} else if avg >= 0.90 {
CadenceType::Plagal
} else if avg >= 0.75 {
CadenceType::Deceptive
} else if avg >= 0.40 {
CadenceType::Half
} else {
CadenceType::Phrygian
};
if matches!(cadence, CadenceType::Phrygian) && avg < 0.1 {
return None;
}
let agents: Vec<String> = tasks.iter().map(|t| t.task_id().to_string()).collect();
Some(Self {
cadence,
agents,
overall_completion: avg,
step,
})
}
pub fn is_final(&self) -> bool {
self.cadence.is_resolved()
}
}
#[derive(Debug, Clone)]
pub struct DeceptiveResolution {
regressions: Vec<RegressionEvent>,
regression_threshold: f64,
}
#[derive(Debug, Clone)]
struct RegressionEvent {
task_id: String,
apparent_progress: f64,
actual_progress: f64,
step: u64,
}
impl DeceptiveResolution {
pub fn new(regression_threshold: f64) -> Self {
Self {
regressions: Vec::new(),
regression_threshold,
}
}
pub fn track(&mut self, task_id: impl Into<String>, previous: f64, current: f64, step: u64) -> bool {
let regression = previous - current;
if regression > self.regression_threshold {
self.regressions.push(RegressionEvent {
task_id: task_id.into(),
apparent_progress: previous,
actual_progress: current,
step,
});
true
} else {
false
}
}
pub fn regression_count(&self) -> usize {
self.regressions.len()
}
pub fn deceptive_tasks(&self) -> Vec<&str> {
self.regressions.iter().map(|r| r.task_id.as_str()).collect()
}
pub fn is_deceptive(&self, task_id: &str) -> bool {
self.regressions.iter().any(|r| r.task_id == task_id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChordFunction {
Tonic, Supertonic, Mediant, Subdominant, Dominant, Submediant, Leading, }
impl ChordFunction {
pub fn stability(&self) -> f64 {
match self {
Self::Tonic => 1.0,
Self::Submediant => 0.7,
Self::Subdominant => 0.5,
Self::Mediant => 0.4,
Self::Supertonic => 0.3,
Self::Dominant => 0.15,
Self::Leading => 0.05,
}
}
pub fn tension(&self) -> f64 {
1.0 - self.stability()
}
}
#[derive(Debug, Clone)]
pub struct ProgressionTracker {
progression: Vec<ChordFunction>,
position: usize,
}
impl ProgressionTracker {
pub fn new() -> Self {
Self {
progression: Vec::new(),
position: 0,
}
}
pub fn standard() -> Self {
Self {
progression: vec![
ChordFunction::Tonic, ChordFunction::Supertonic, ChordFunction::Subdominant, ChordFunction::Dominant, ChordFunction::Tonic, ],
position: 0,
}
}
pub fn deceptive() -> Self {
Self {
progression: vec![
ChordFunction::Tonic,
ChordFunction::Subdominant,
ChordFunction::Dominant,
ChordFunction::Submediant, ChordFunction::Subdominant, ChordFunction::Dominant,
ChordFunction::Tonic, ],
position: 0,
}
}
pub fn advance(&mut self) -> Option<ChordFunction> {
if self.position < self.progression.len() {
let chord = self.progression[self.position];
self.position += 1;
Some(chord)
} else {
None
}
}
pub fn current(&self) -> Option<&ChordFunction> {
self.progression.get(self.position.saturating_sub(1))
}
pub fn detect_cadence(&self) -> Option<CadenceType> {
if self.position < 2 {
return None;
}
let prev = self.progression[self.position - 2];
let curr = self.progression[self.position - 1];
Some(match (prev, curr) {
(ChordFunction::Dominant, ChordFunction::Tonic) => CadenceType::PerfectAuthentic,
(ChordFunction::Subdominant, ChordFunction::Tonic) => CadenceType::Plagal,
(ChordFunction::Dominant, ChordFunction::Submediant) => CadenceType::Deceptive,
(ChordFunction::Supertonic, ChordFunction::Dominant) => CadenceType::Phrygian,
(_, ChordFunction::Dominant) => CadenceType::Half,
_ => CadenceType::Half, })
}
pub fn tension(&self) -> f64 {
self.current().map(|c| c.tension()).unwrap_or(0.0)
}
pub fn progress_ratio(&self) -> f64 {
if self.progression.is_empty() {
0.0
} else {
self.position as f64 / self.progression.len() as f64
}
}
pub fn is_complete(&self) -> bool {
self.position >= self.progression.len()
}
pub fn progression(&self) -> &[ChordFunction] {
&self.progression
}
}
#[derive(Debug, Clone)]
pub struct CadenceCoordinator {
tasks: HashMap<String, TaskProgress>,
tracker: ProgressionTracker,
}
impl CadenceCoordinator {
pub fn new(tracker: ProgressionTracker) -> Self {
Self {
tasks: HashMap::new(),
tracker,
}
}
pub fn add_task(&mut self, task: TaskProgress) {
self.tasks.insert(task.task_id().to_string(), task);
}
pub fn update_task(&mut self, task_id: &str, progress: f64) {
if let Some(task) = self.tasks.get_mut(task_id) {
task.set_progress(progress);
}
}
pub fn check_cadence(&mut self, step: u64) -> Option<CompletionSignal> {
let tasks: Vec<TaskProgress> = self.tasks.values().cloned().collect();
CompletionSignal::detect_from_group(&tasks, step)
}
pub fn task_count(&self) -> usize {
self.tasks.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cadence_type_properties() {
assert!(CadenceType::PerfectAuthentic.is_resolved());
assert!(CadenceType::Plagal.is_resolved());
assert!(!CadenceType::Deceptive.is_resolved());
assert!(!CadenceType::Half.is_resolved());
assert!(!CadenceType::Phrygian.is_resolved());
assert!(CadenceType::PerfectAuthentic.resolution_strength() > CadenceType::Plagal.resolution_strength());
assert!(CadenceType::Plagal.resolution_strength() > CadenceType::Deceptive.resolution_strength());
}
#[test]
fn test_cadence_descriptions() {
for ct in [
CadenceType::PerfectAuthentic,
CadenceType::Plagal,
CadenceType::Deceptive,
CadenceType::Half,
CadenceType::Phrygian,
] {
assert!(!ct.description().is_empty());
}
}
#[test]
fn test_task_progress_detect_cadence() {
let mut tp = TaskProgress::with_progress("t1", 0.99);
assert_eq!(tp.detect_cadence(), CadenceType::PerfectAuthentic);
let mut tp = TaskProgress::with_progress("t2", 0.92);
assert_eq!(tp.detect_cadence(), CadenceType::Plagal);
let mut tp = TaskProgress::with_progress("t3", 0.83);
assert_eq!(tp.detect_cadence(), CadenceType::Deceptive);
let mut tp = TaskProgress::with_progress("t4", 0.50);
assert_eq!(tp.detect_cadence(), CadenceType::Half);
let mut tp = TaskProgress::with_progress("t5", 0.10);
assert_eq!(tp.detect_cadence(), CadenceType::Phrygian);
}
#[test]
fn test_task_progress_completion() {
let mut tp = TaskProgress::new("t1");
assert!(!tp.is_complete());
tp.set_progress(1.0);
tp.detect_cadence();
assert!(tp.is_complete());
}
#[test]
fn test_task_progress_clamp() {
let tp = TaskProgress::with_progress("t1", 1.5);
assert!((tp.progress() - 1.0).abs() < 1e-9);
let tp = TaskProgress::with_progress("t2", -0.5);
assert!((tp.progress()).abs() < 1e-9);
}
#[test]
fn test_completion_signal_from_group() {
let tasks = vec![
TaskProgress::with_progress("a", 0.99),
TaskProgress::with_progress("b", 0.98),
TaskProgress::with_progress("c", 1.0),
];
let signal = CompletionSignal::detect_from_group(&tasks, 1).unwrap();
assert!(signal.is_final());
assert_eq!(signal.agents.len(), 3);
assert!(signal.overall_completion > 0.95);
}
#[test]
fn test_completion_signal_deceptive() {
let tasks = vec![
TaskProgress::with_progress("a", 0.80),
TaskProgress::with_progress("b", 0.75),
];
let signal = CompletionSignal::detect_from_group(&tasks, 1).unwrap();
assert_eq!(signal.cadence, CadenceType::Deceptive);
}
#[test]
fn test_completion_signal_empty() {
let tasks: Vec<TaskProgress> = vec![];
assert!(CompletionSignal::detect_from_group(&tasks, 1).is_none());
}
#[test]
fn test_deceptive_resolution_detection() {
let mut dr = DeceptiveResolution::new(0.05);
assert!(!dr.track("t1", 0.5, 0.6, 1)); assert!(dr.track("t1", 0.6, 0.3, 2)); assert_eq!(dr.regression_count(), 1);
assert!(dr.is_deceptive("t1"));
assert!(!dr.is_deceptive("t2"));
}
#[test]
fn test_deceptive_resolution_small_regression() {
let mut dr = DeceptiveResolution::new(0.2);
assert!(!dr.track("t1", 0.8, 0.7, 1)); assert_eq!(dr.regression_count(), 0);
}
#[test]
fn test_deceptive_tasks_list() {
let mut dr = DeceptiveResolution::new(0.05);
dr.track("t1", 0.9, 0.5, 1);
dr.track("t2", 0.8, 0.4, 2);
let tasks = dr.deceptive_tasks();
assert_eq!(tasks.len(), 2);
}
#[test]
fn test_chord_function_stability() {
assert!((ChordFunction::Tonic.stability() - 1.0).abs() < 1e-9);
assert!((ChordFunction::Leading.stability() - 0.05).abs() < 1e-9);
assert!((ChordFunction::Dominant.tension() - 0.85).abs() < 1e-9);
}
#[test]
fn test_progression_tracker_standard() {
let mut tracker = ProgressionTracker::standard();
assert_eq!(tracker.progression().len(), 5);
let c1 = tracker.advance();
assert_eq!(c1, Some(ChordFunction::Tonic));
assert!(!tracker.is_complete());
tracker.advance(); tracker.advance(); tracker.advance(); let c5 = tracker.advance(); assert_eq!(c5, Some(ChordFunction::Tonic));
assert!(tracker.is_complete());
assert!(tracker.advance().is_none());
}
#[test]
fn test_progression_tracker_cadence_detection() {
let mut tracker = ProgressionTracker::standard();
tracker.advance(); tracker.advance(); tracker.advance(); tracker.advance(); assert_eq!(tracker.detect_cadence(), Some(CadenceType::Half)); tracker.advance(); assert_eq!(tracker.detect_cadence(), Some(CadenceType::PerfectAuthentic));
}
#[test]
fn test_progression_tracker_deceptive() {
let mut tracker = ProgressionTracker::deceptive();
tracker.advance(); tracker.advance(); tracker.advance(); tracker.advance(); assert_eq!(tracker.detect_cadence(), Some(CadenceType::Deceptive));
}
#[test]
fn test_progression_tracker_tension() {
let mut tracker = ProgressionTracker::standard();
tracker.advance(); assert!((tracker.tension() - 0.0).abs() < 1e-9);
tracker.advance(); assert!(tracker.tension() > 0.0);
}
#[test]
fn test_progression_tracker_progress_ratio() {
let mut tracker = ProgressionTracker::new();
assert!((tracker.progress_ratio()).abs() < 1e-9);
tracker.progression.push(ChordFunction::Tonic);
tracker.progression.push(ChordFunction::Dominant);
tracker.advance();
assert!((tracker.progress_ratio() - 0.5).abs() < 1e-9);
}
#[test]
fn test_cadence_coordinator() {
let tracker = ProgressionTracker::standard();
let mut coord = CadenceCoordinator::new(tracker);
coord.add_task(TaskProgress::with_progress("a", 0.0));
coord.add_task(TaskProgress::with_progress("b", 0.0));
assert_eq!(coord.task_count(), 2);
coord.update_task("a", 1.0);
coord.update_task("b", 1.0);
let signal = coord.check_cadence(1).unwrap();
assert!(signal.is_final());
}
}