1use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum CadenceType {
11 PerfectAuthentic,
13 Plagal,
15 Deceptive,
17 Half,
19 Phrygian,
21}
22
23impl CadenceType {
24 pub fn description(&self) -> &str {
26 match self {
27 Self::PerfectAuthentic => "Full resolution — task definitively complete",
28 Self::Plagal => "Gentle resolution — task complete with soft landing",
29 Self::Deceptive => "False resolution — task appears done but isn't",
30 Self::Half => "Suspension — task paused, awaiting continuation",
31 Self::Phrygian => "Dramatic pause — task interrupted with tension",
32 }
33 }
34
35 pub fn is_resolved(&self) -> bool {
37 matches!(self, Self::PerfectAuthentic | Self::Plagal)
38 }
39
40 pub fn resolution_strength(&self) -> f64 {
42 match self {
43 Self::PerfectAuthentic => 1.0,
44 Self::Plagal => 0.85,
45 Self::Deceptive => 0.3,
46 Self::Half => 0.1,
47 Self::Phrygian => 0.05,
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
54pub struct TaskProgress {
55 task_id: String,
56 progress: f64, cadence: Option<CadenceType>,
58}
59
60impl TaskProgress {
61 pub fn new(task_id: impl Into<String>) -> Self {
62 Self {
63 task_id: task_id.into(),
64 progress: 0.0,
65 cadence: None,
66 }
67 }
68
69 pub fn with_progress(task_id: impl Into<String>, progress: f64) -> Self {
70 Self {
71 task_id: task_id.into(),
72 progress: progress.clamp(0.0, 1.0),
73 cadence: None,
74 }
75 }
76
77 pub fn detect_cadence(&mut self) -> CadenceType {
79 let cadence = if self.progress >= 0.98 {
80 CadenceType::PerfectAuthentic
81 } else if self.progress >= 0.90 {
82 CadenceType::Plagal
83 } else if self.progress >= 0.70 {
84 if self.progress >= 0.80 {
86 CadenceType::Deceptive
87 } else {
88 CadenceType::Half
89 }
90 } else if self.progress >= 0.40 {
91 CadenceType::Half
92 } else {
93 CadenceType::Phrygian
94 };
95 self.cadence = Some(cadence);
96 cadence
97 }
98
99 pub fn set_progress(&mut self, p: f64) {
100 self.progress = p.clamp(0.0, 1.0);
101 }
102
103 pub fn progress(&self) -> f64 {
104 self.progress
105 }
106
107 pub fn cadence(&self) -> Option<CadenceType> {
108 self.cadence
109 }
110
111 pub fn task_id(&self) -> &str {
112 &self.task_id
113 }
114
115 pub fn is_complete(&self) -> bool {
116 self.cadence.map_or(false, |c| c.is_resolved())
117 }
118}
119
120#[derive(Debug, Clone)]
122pub struct CompletionSignal {
123 pub cadence: CadenceType,
125 pub agents: Vec<String>,
127 pub overall_completion: f64,
129 pub step: u64,
131}
132
133impl CompletionSignal {
134 pub fn detect_from_group(tasks: &[TaskProgress], step: u64) -> Option<Self> {
136 if tasks.is_empty() {
137 return None;
138 }
139
140 let total: f64 = tasks.iter().map(|t| t.progress()).sum();
141 let avg = total / tasks.len() as f64;
142
143 let cadence = if avg >= 0.98 {
145 CadenceType::PerfectAuthentic
146 } else if avg >= 0.90 {
147 CadenceType::Plagal
148 } else if avg >= 0.75 {
149 CadenceType::Deceptive
150 } else if avg >= 0.40 {
151 CadenceType::Half
152 } else {
153 CadenceType::Phrygian
154 };
155
156 if matches!(cadence, CadenceType::Phrygian) && avg < 0.1 {
158 return None;
159 }
160
161 let agents: Vec<String> = tasks.iter().map(|t| t.task_id().to_string()).collect();
162
163 Some(Self {
164 cadence,
165 agents,
166 overall_completion: avg,
167 step,
168 })
169 }
170
171 pub fn is_final(&self) -> bool {
173 self.cadence.is_resolved()
174 }
175}
176
177#[derive(Debug, Clone)]
179pub struct DeceptiveResolution {
180 regressions: Vec<RegressionEvent>,
182 regression_threshold: f64,
184}
185
186#[derive(Debug, Clone)]
187struct RegressionEvent {
188 task_id: String,
189 apparent_progress: f64,
190 actual_progress: f64,
191 step: u64,
192}
193
194impl DeceptiveResolution {
195 pub fn new(regression_threshold: f64) -> Self {
196 Self {
197 regressions: Vec::new(),
198 regression_threshold,
199 }
200 }
201
202 pub fn track(&mut self, task_id: impl Into<String>, previous: f64, current: f64, step: u64) -> bool {
204 let regression = previous - current;
205 if regression > self.regression_threshold {
206 self.regressions.push(RegressionEvent {
207 task_id: task_id.into(),
208 apparent_progress: previous,
209 actual_progress: current,
210 step,
211 });
212 true
213 } else {
214 false
215 }
216 }
217
218 pub fn regression_count(&self) -> usize {
220 self.regressions.len()
221 }
222
223 pub fn deceptive_tasks(&self) -> Vec<&str> {
225 self.regressions.iter().map(|r| r.task_id.as_str()).collect()
226 }
227
228 pub fn is_deceptive(&self, task_id: &str) -> bool {
230 self.regressions.iter().any(|r| r.task_id == task_id)
231 }
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
236pub enum ChordFunction {
237 Tonic, Supertonic, Mediant, Subdominant,Dominant, Submediant, Leading, }
245
246impl ChordFunction {
247 pub fn stability(&self) -> f64 {
249 match self {
250 Self::Tonic => 1.0,
251 Self::Submediant => 0.7,
252 Self::Subdominant => 0.5,
253 Self::Mediant => 0.4,
254 Self::Supertonic => 0.3,
255 Self::Dominant => 0.15,
256 Self::Leading => 0.05,
257 }
258 }
259
260 pub fn tension(&self) -> f64 {
262 1.0 - self.stability()
263 }
264}
265
266#[derive(Debug, Clone)]
268pub struct ProgressionTracker {
269 progression: Vec<ChordFunction>,
271 position: usize,
273}
274
275impl ProgressionTracker {
276 pub fn new() -> Self {
277 Self {
278 progression: Vec::new(),
279 position: 0,
280 }
281 }
282
283 pub fn standard() -> Self {
285 Self {
286 progression: vec![
287 ChordFunction::Tonic, ChordFunction::Supertonic, ChordFunction::Subdominant, ChordFunction::Dominant, ChordFunction::Tonic, ],
293 position: 0,
294 }
295 }
296
297 pub fn deceptive() -> Self {
299 Self {
300 progression: vec![
301 ChordFunction::Tonic,
302 ChordFunction::Subdominant,
303 ChordFunction::Dominant,
304 ChordFunction::Submediant, ChordFunction::Subdominant, ChordFunction::Dominant,
307 ChordFunction::Tonic, ],
309 position: 0,
310 }
311 }
312
313 pub fn advance(&mut self) -> Option<ChordFunction> {
315 if self.position < self.progression.len() {
316 let chord = self.progression[self.position];
317 self.position += 1;
318 Some(chord)
319 } else {
320 None
321 }
322 }
323
324 pub fn current(&self) -> Option<&ChordFunction> {
326 self.progression.get(self.position.saturating_sub(1))
327 }
328
329 pub fn detect_cadence(&self) -> Option<CadenceType> {
331 if self.position < 2 {
332 return None;
333 }
334 let prev = self.progression[self.position - 2];
335 let curr = self.progression[self.position - 1];
336 Some(match (prev, curr) {
337 (ChordFunction::Dominant, ChordFunction::Tonic) => CadenceType::PerfectAuthentic,
338 (ChordFunction::Subdominant, ChordFunction::Tonic) => CadenceType::Plagal,
339 (ChordFunction::Dominant, ChordFunction::Submediant) => CadenceType::Deceptive,
340 (ChordFunction::Supertonic, ChordFunction::Dominant) => CadenceType::Phrygian,
341 (_, ChordFunction::Dominant) => CadenceType::Half,
342 _ => CadenceType::Half, })
344 }
345
346 pub fn tension(&self) -> f64 {
348 self.current().map(|c| c.tension()).unwrap_or(0.0)
349 }
350
351 pub fn progress_ratio(&self) -> f64 {
353 if self.progression.is_empty() {
354 0.0
355 } else {
356 self.position as f64 / self.progression.len() as f64
357 }
358 }
359
360 pub fn is_complete(&self) -> bool {
361 self.position >= self.progression.len()
362 }
363
364 pub fn progression(&self) -> &[ChordFunction] {
365 &self.progression
366 }
367}
368
369#[derive(Debug, Clone)]
371pub struct CadenceCoordinator {
372 tasks: HashMap<String, TaskProgress>,
373 tracker: ProgressionTracker,
374}
375
376impl CadenceCoordinator {
377 pub fn new(tracker: ProgressionTracker) -> Self {
378 Self {
379 tasks: HashMap::new(),
380 tracker,
381 }
382 }
383
384 pub fn add_task(&mut self, task: TaskProgress) {
385 self.tasks.insert(task.task_id().to_string(), task);
386 }
387
388 pub fn update_task(&mut self, task_id: &str, progress: f64) {
389 if let Some(task) = self.tasks.get_mut(task_id) {
390 task.set_progress(progress);
391 }
392 }
393
394 pub fn check_cadence(&mut self, step: u64) -> Option<CompletionSignal> {
396 let tasks: Vec<TaskProgress> = self.tasks.values().cloned().collect();
397 CompletionSignal::detect_from_group(&tasks, step)
398 }
399
400 pub fn task_count(&self) -> usize {
401 self.tasks.len()
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408
409 #[test]
410 fn test_cadence_type_properties() {
411 assert!(CadenceType::PerfectAuthentic.is_resolved());
412 assert!(CadenceType::Plagal.is_resolved());
413 assert!(!CadenceType::Deceptive.is_resolved());
414 assert!(!CadenceType::Half.is_resolved());
415 assert!(!CadenceType::Phrygian.is_resolved());
416
417 assert!(CadenceType::PerfectAuthentic.resolution_strength() > CadenceType::Plagal.resolution_strength());
418 assert!(CadenceType::Plagal.resolution_strength() > CadenceType::Deceptive.resolution_strength());
419 }
420
421 #[test]
422 fn test_cadence_descriptions() {
423 for ct in [
424 CadenceType::PerfectAuthentic,
425 CadenceType::Plagal,
426 CadenceType::Deceptive,
427 CadenceType::Half,
428 CadenceType::Phrygian,
429 ] {
430 assert!(!ct.description().is_empty());
431 }
432 }
433
434 #[test]
435 fn test_task_progress_detect_cadence() {
436 let mut tp = TaskProgress::with_progress("t1", 0.99);
437 assert_eq!(tp.detect_cadence(), CadenceType::PerfectAuthentic);
438
439 let mut tp = TaskProgress::with_progress("t2", 0.92);
440 assert_eq!(tp.detect_cadence(), CadenceType::Plagal);
441
442 let mut tp = TaskProgress::with_progress("t3", 0.83);
443 assert_eq!(tp.detect_cadence(), CadenceType::Deceptive);
444
445 let mut tp = TaskProgress::with_progress("t4", 0.50);
446 assert_eq!(tp.detect_cadence(), CadenceType::Half);
447
448 let mut tp = TaskProgress::with_progress("t5", 0.10);
449 assert_eq!(tp.detect_cadence(), CadenceType::Phrygian);
450 }
451
452 #[test]
453 fn test_task_progress_completion() {
454 let mut tp = TaskProgress::new("t1");
455 assert!(!tp.is_complete());
456 tp.set_progress(1.0);
457 tp.detect_cadence();
458 assert!(tp.is_complete());
459 }
460
461 #[test]
462 fn test_task_progress_clamp() {
463 let tp = TaskProgress::with_progress("t1", 1.5);
464 assert!((tp.progress() - 1.0).abs() < 1e-9);
465 let tp = TaskProgress::with_progress("t2", -0.5);
466 assert!((tp.progress()).abs() < 1e-9);
467 }
468
469 #[test]
470 fn test_completion_signal_from_group() {
471 let tasks = vec![
472 TaskProgress::with_progress("a", 0.99),
473 TaskProgress::with_progress("b", 0.98),
474 TaskProgress::with_progress("c", 1.0),
475 ];
476 let signal = CompletionSignal::detect_from_group(&tasks, 1).unwrap();
477 assert!(signal.is_final());
478 assert_eq!(signal.agents.len(), 3);
479 assert!(signal.overall_completion > 0.95);
480 }
481
482 #[test]
483 fn test_completion_signal_deceptive() {
484 let tasks = vec![
485 TaskProgress::with_progress("a", 0.80),
486 TaskProgress::with_progress("b", 0.75),
487 ];
488 let signal = CompletionSignal::detect_from_group(&tasks, 1).unwrap();
489 assert_eq!(signal.cadence, CadenceType::Deceptive);
490 }
491
492 #[test]
493 fn test_completion_signal_empty() {
494 let tasks: Vec<TaskProgress> = vec![];
495 assert!(CompletionSignal::detect_from_group(&tasks, 1).is_none());
496 }
497
498 #[test]
499 fn test_deceptive_resolution_detection() {
500 let mut dr = DeceptiveResolution::new(0.05);
501 assert!(!dr.track("t1", 0.5, 0.6, 1)); assert!(dr.track("t1", 0.6, 0.3, 2)); assert_eq!(dr.regression_count(), 1);
504 assert!(dr.is_deceptive("t1"));
505 assert!(!dr.is_deceptive("t2"));
506 }
507
508 #[test]
509 fn test_deceptive_resolution_small_regression() {
510 let mut dr = DeceptiveResolution::new(0.2);
511 assert!(!dr.track("t1", 0.8, 0.7, 1)); assert_eq!(dr.regression_count(), 0);
513 }
514
515 #[test]
516 fn test_deceptive_tasks_list() {
517 let mut dr = DeceptiveResolution::new(0.05);
518 dr.track("t1", 0.9, 0.5, 1);
519 dr.track("t2", 0.8, 0.4, 2);
520 let tasks = dr.deceptive_tasks();
521 assert_eq!(tasks.len(), 2);
522 }
523
524 #[test]
525 fn test_chord_function_stability() {
526 assert!((ChordFunction::Tonic.stability() - 1.0).abs() < 1e-9);
527 assert!((ChordFunction::Leading.stability() - 0.05).abs() < 1e-9);
528 assert!((ChordFunction::Dominant.tension() - 0.85).abs() < 1e-9);
529 }
530
531 #[test]
532 fn test_progression_tracker_standard() {
533 let mut tracker = ProgressionTracker::standard();
534 assert_eq!(tracker.progression().len(), 5);
535
536 let c1 = tracker.advance();
537 assert_eq!(c1, Some(ChordFunction::Tonic));
538 assert!(!tracker.is_complete());
539
540 tracker.advance(); tracker.advance(); tracker.advance(); let c5 = tracker.advance(); assert_eq!(c5, Some(ChordFunction::Tonic));
545 assert!(tracker.is_complete());
546 assert!(tracker.advance().is_none());
547 }
548
549 #[test]
550 fn test_progression_tracker_cadence_detection() {
551 let mut tracker = ProgressionTracker::standard();
552 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));
559 }
560
561 #[test]
562 fn test_progression_tracker_deceptive() {
563 let mut tracker = ProgressionTracker::deceptive();
564 tracker.advance(); tracker.advance(); tracker.advance(); tracker.advance(); assert_eq!(tracker.detect_cadence(), Some(CadenceType::Deceptive));
569 }
570
571 #[test]
572 fn test_progression_tracker_tension() {
573 let mut tracker = ProgressionTracker::standard();
574 tracker.advance(); assert!((tracker.tension() - 0.0).abs() < 1e-9);
576 tracker.advance(); assert!(tracker.tension() > 0.0);
578 }
579
580 #[test]
581 fn test_progression_tracker_progress_ratio() {
582 let mut tracker = ProgressionTracker::new();
583 assert!((tracker.progress_ratio()).abs() < 1e-9);
584 tracker.progression.push(ChordFunction::Tonic);
585 tracker.progression.push(ChordFunction::Dominant);
586 tracker.advance();
587 assert!((tracker.progress_ratio() - 0.5).abs() < 1e-9);
588 }
589
590 #[test]
591 fn test_cadence_coordinator() {
592 let tracker = ProgressionTracker::standard();
593 let mut coord = CadenceCoordinator::new(tracker);
594 coord.add_task(TaskProgress::with_progress("a", 0.0));
595 coord.add_task(TaskProgress::with_progress("b", 0.0));
596 assert_eq!(coord.task_count(), 2);
597
598 coord.update_task("a", 1.0);
599 coord.update_task("b", 1.0);
600 let signal = coord.check_cadence(1).unwrap();
601 assert!(signal.is_final());
602 }
603}