1use crate::types::*;
25use crate::{CreateCommitmentRequest, CreateDecisionRequest, PlanningEngine};
26use std::collections::HashMap;
27use uuid::Uuid;
28
29impl PlanningEngine {
30 pub fn calculate_singularity_parallel(&self) -> IntentionSingularity {
36 let has_active = self
38 .goal_store
39 .values()
40 .any(|g| g.status == GoalStatus::Active);
41
42 if !has_active {
43 return IntentionSingularity {
44 center: IntentionCenter {
45 urgency: 0.0,
46 confidence: 0.0,
47 momentum: 0.0,
48 },
49 unified_vision: String::new(),
50 goal_positions: HashMap::new(),
51 tension_lines: Vec::new(),
52 themes: Vec::new(),
53 golden_path: Vec::new(),
54 };
55 }
56
57 self.get_intention_singularity()
60 }
61
62 pub fn scan_blockers_parallel(&self) -> Vec<BlockerProphecy> {
67 let active_goals: Vec<&Goal> = self
68 .goal_store
69 .values()
70 .filter(|g| g.status == GoalStatus::Active || g.status == GoalStatus::Blocked)
71 .collect();
72
73 if active_goals.is_empty() {
74 return Vec::new();
75 }
76
77 let mut blocker_type_counts: HashMap<String, usize> = HashMap::new();
79 for goal in self.goal_store.values() {
80 for blocker in &goal.blockers {
81 let key = format!("{:?}", std::mem::discriminant(&blocker.blocker_type));
82 *blocker_type_counts.entry(key).or_insert(0) += 1;
83 }
84 }
85 let total_historical = blocker_type_counts.values().sum::<usize>().max(1);
86
87 let mut prophecies = Vec::with_capacity(active_goals.len() * 2);
89
90 for goal in &active_goals {
91 for blocker in self.predict_blockers(goal) {
92 let type_key = format!("{:?}", std::mem::discriminant(&blocker.blocker_type));
93 let type_frequency = *blocker_type_counts.get(&type_key).unwrap_or(&0) as f64
94 / total_historical as f64;
95 let severity_signal = blocker.severity;
96 let prediction_confidence =
97 (0.3 + type_frequency * 0.3 + severity_signal * 0.3).clamp(0.1, 0.95);
98
99 let days_until = goal
100 .deadline
101 .map(|d| {
102 let days = ((d.0 - Timestamp::now().0) as f64 / (86_400.0 * 1e9)).max(0.5);
103 (days * (1.0 - severity_signal)).max(1.0)
104 })
105 .unwrap_or(14.0 * (1.0 - severity_signal * 0.5));
106
107 let mut evidence = Vec::new();
108 if goal.progress.velocity == 0.0 {
109 evidence.push("zero progress velocity".to_string());
110 }
111 if goal.feelings.neglect > 0.5 {
112 evidence.push(format!("high neglect score ({:.2})", goal.feelings.neglect));
113 }
114
115 prophecies.push(BlockerProphecy {
116 goal_id: goal.id,
117 predicted_blocker: blocker.clone(),
118 prediction_confidence,
119 days_until_materialization: days_until,
120 evidence,
121 recommended_actions: blocker
122 .resolution
123 .clone()
124 .map(|r| vec![r])
125 .unwrap_or_default(),
126 });
127 }
128 }
129
130 prophecies.sort_by(|a, b| {
132 b.prediction_confidence
133 .partial_cmp(&a.prediction_confidence)
134 .unwrap_or(std::cmp::Ordering::Equal)
135 });
136 prophecies
137 }
138
139 pub fn progress_echoes_parallel(&self) -> Vec<ProgressEcho> {
145 let candidates: Vec<&Goal> = self
146 .goal_store
147 .values()
148 .filter(|g| {
149 g.status == GoalStatus::Active
150 && g.progress.percentage > 0.5
151 && g.physics.momentum > 0.2
152 })
153 .collect();
154
155 if candidates.is_empty() {
156 return Vec::new();
157 }
158
159 let mut echoes = Vec::with_capacity(candidates.len());
160
161 for goal in &candidates {
162 let eta_days = goal
163 .progress
164 .eta
165 .map(|eta| ((eta.0 - Timestamp::now().0) as f64 / (86_400.0 * 1e9)).max(1.0))
166 .unwrap_or(30.0);
167
168 if eta_days < 30.0 {
169 echoes.push(ProgressEcho {
170 goal_id: goal.id,
171 source_milestone: Milestone {
172 name: format!("{} completed", goal.title),
173 description: "Goal completion".to_string(),
174 },
175 echo_strength: goal.physics.momentum,
176 estimated_arrival_secs: (eta_days * 86_400.0) as u64,
177 carried_information: self.extract_echo_info(goal),
178 confidence: goal.feelings.confidence,
179 });
180 }
181 }
182
183 echoes.sort_by(|a, b| {
185 b.echo_strength
186 .partial_cmp(&a.echo_strength)
187 .unwrap_or(std::cmp::Ordering::Equal)
188 });
189 echoes
190 }
191
192 pub fn create_goals_batch(
204 &mut self,
205 requests: Vec<crate::CreateGoalRequest>,
206 ) -> crate::Result<Vec<Goal>> {
207 if requests.is_empty() {
208 return Ok(Vec::new());
209 }
210
211 for (i, req) in requests.iter().enumerate() {
214 if let Err(errors) = self.validate_create_goal(req) {
215 let msg = errors
216 .iter()
217 .map(|e| e.to_string())
218 .collect::<Vec<_>>()
219 .join("; ");
220 return Err(crate::Error::CorruptedFile(format!(
221 "batch validation failed at request[{}]: {}",
222 i, msg
223 )));
224 }
225 }
226
227 let now = Timestamp::now();
229 let mut goals = Vec::with_capacity(requests.len());
230
231 for request in requests {
232 let id = GoalId(Uuid::new_v4());
233 let urgency = self.calculate_initial_urgency(&request);
234 let gravity = self.calculate_initial_gravity(&request);
235 let inertia = self.calculate_initial_inertia(&request);
236
237 let soul = GoalSoul {
238 intention: request.intention.clone(),
239 significance: request.significance.unwrap_or_default(),
240 success_criteria: request.success_criteria.unwrap_or_default(),
241 emotional_weight: request.emotional_weight.unwrap_or(0.5),
242 values: request.values.unwrap_or_default(),
243 };
244
245 let feelings = GoalFeelings {
246 urgency,
247 neglect: 0.0,
248 confidence: 0.5,
249 alignment: 1.0,
250 vitality: 1.0,
251 last_calculated: now,
252 };
253
254 let physics = GoalPhysics {
255 momentum: 0.0,
256 gravity,
257 inertia,
258 energy: 1.0,
259 last_calculated: now,
260 };
261
262 let goal = Goal {
263 id,
264 title: request.title,
265 description: request.description,
266 soul,
267 status: GoalStatus::Draft,
268 created_at: now,
269 activated_at: None,
270 completed_at: None,
271 deadline: request.deadline,
272 parent: request.parent,
273 children: Vec::new(),
274 dependencies: request.dependencies.unwrap_or_default(),
275 dependents: Vec::new(),
276 relationships: Vec::new(),
277 priority: request.priority.unwrap_or(Priority::Medium),
278 progress: Progress::new(),
279 feelings,
280 physics,
281 blockers: Vec::new(),
282 decisions: Vec::new(),
283 commitments: Vec::new(),
284 dreams: Vec::new(),
285 tags: request.tags.unwrap_or_default(),
286 metadata: request.metadata.unwrap_or_default(),
287 provenance: GoalProvenance {
288 origin: request.origin.unwrap_or(ProvenanceOrigin::UserRequest),
289 user_request: request.user_request,
290 session_id: request.session_id,
291 creation_context: request.context.unwrap_or_default(),
292 },
293 metamorphosis: None,
294 previous_life: None,
295 };
296
297 goals.push(goal);
298 }
299
300 for goal in &goals {
302 self.goal_store.insert(goal.id, goal.clone());
303 }
304
305 let links: Vec<(GoalId, Option<GoalId>, Vec<GoalId>)> = goals
308 .iter()
309 .map(|g| (g.id, g.parent, g.dependencies.clone()))
310 .collect();
311
312 for (child_id, parent_opt, deps) in &links {
313 if let Some(parent_id) = parent_opt {
314 if let Some(parent) = self.goal_store.get_mut(parent_id) {
315 parent.children.push(*child_id);
316 }
317 }
318 for dep_id in deps {
319 if let Some(dep) = self.goal_store.get_mut(dep_id) {
320 dep.dependents.push(*child_id);
321 }
322 }
323 }
324
325 self.rebuild_indexes();
329 self.mark_dirty();
330
331 Ok(goals)
332 }
333
334 pub fn batch_activate_goals(&mut self, ids: &[GoalId]) -> crate::Result<Vec<Goal>> {
343 if ids.is_empty() {
344 return Ok(Vec::new());
345 }
346
347 for id in ids {
349 let goal = self
350 .goal_store
351 .get(id)
352 .ok_or(crate::Error::GoalNotFound(*id))?;
353 if goal.status != GoalStatus::Draft && goal.status != GoalStatus::Reborn {
354 return Err(crate::Error::InvalidTransition {
355 from: goal.status,
356 to: GoalStatus::Active,
357 });
358 }
359 }
360
361 let now = Timestamp::now();
363 let mut results = Vec::with_capacity(ids.len());
364
365 for id in ids {
366 let goal = self
367 .goal_store
368 .get_mut(id)
369 .expect("goal existence validated in phase 1");
370 let old = goal.status;
371 goal.status = GoalStatus::Active;
372 goal.activated_at = Some(now);
373 goal.feelings.vitality = 1.0;
374 self.indexes
375 .goal_status_changed(*id, old, GoalStatus::Active);
376 results.push(goal.clone());
377 }
378
379 self.mark_dirty();
380 Ok(results)
381 }
382
383 pub fn batch_progress_goals(
389 &mut self,
390 updates: Vec<(GoalId, f64, Option<String>)>,
391 ) -> crate::Result<Vec<Goal>> {
392 if updates.is_empty() {
393 return Ok(Vec::new());
394 }
395
396 for (id, _, _) in &updates {
398 if !self.goal_store.contains_key(id) {
399 return Err(crate::Error::GoalNotFound(*id));
400 }
401 }
402
403 let now = Timestamp::now();
405 let mut snapshots = Vec::with_capacity(updates.len());
406
407 for (id, percentage, note) in &updates {
408 let goal = self
409 .goal_store
410 .get_mut(id)
411 .expect("goal existence validated in phase 1");
412 let p = percentage.clamp(0.0, 1.0);
413 goal.progress.history.push(ProgressPoint {
414 timestamp: now,
415 percentage: p,
416 note: note.clone(),
417 });
418 goal.progress.percentage = p;
419 snapshots.push((*id, goal.clone()));
420 }
421
422 let recalcs: Vec<(GoalId, f64, f64, f64)> = snapshots
424 .iter()
425 .map(|(id, snap)| {
426 let v = self.calculate_velocity(&snap.progress.history);
427 let m = self.calculate_momentum_from_goal(snap);
428 let c = self.calculate_confidence_from_goal(snap);
429 (*id, v, m, c)
430 })
431 .collect();
432
433 let mut results = Vec::with_capacity(recalcs.len());
435 for (id, velocity, momentum, confidence) in recalcs {
436 let goal = self
437 .goal_store
438 .get_mut(&id)
439 .expect("goal existence validated in phase 1");
440 goal.progress.velocity = velocity;
441 if velocity > 0.0 {
442 let remaining = 1.0 - goal.progress.percentage;
443 let days_remaining = remaining / velocity;
444 goal.progress.eta = Some(Timestamp::days_from_now(days_remaining));
445 }
446 goal.physics.momentum = momentum;
447 goal.feelings.neglect = 0.0;
448 goal.feelings.confidence = confidence;
449 goal.feelings.last_calculated = now;
450
451 if goal.progress.percentage >= 1.0 {
452 for c in &mut goal.soul.success_criteria {
453 c.achieved = true;
454 c.achieved_at = Some(now);
455 }
456 }
457 results.push(goal.clone());
458 }
459
460 self.mark_dirty();
461 Ok(results)
462 }
463
464 pub fn batch_create_decisions(
473 &mut self,
474 requests: Vec<CreateDecisionRequest>,
475 ) -> crate::Result<Vec<Decision>> {
476 if requests.is_empty() {
477 return Ok(Vec::new());
478 }
479
480 for (i, req) in requests.iter().enumerate() {
482 if let Some(goals) = &req.goals {
483 for gid in goals {
484 if !self.goal_store.contains_key(gid) {
485 return Err(crate::Error::CorruptedFile(format!(
486 "batch decision[{}] references nonexistent goal {:?}",
487 i, gid
488 )));
489 }
490 }
491 }
492 }
493
494 let now = Timestamp::now();
495 let mut decisions = Vec::with_capacity(requests.len());
496
497 for request in requests {
499 let id = DecisionId(Uuid::new_v4());
500
501 let decision = Decision {
502 id,
503 question: DecisionQuestion {
504 question: request.question,
505 context: request.context.unwrap_or_default(),
506 constraints: request.constraints.unwrap_or_default(),
507 asked_at: now,
508 },
509 status: DecisionStatus::Pending,
510 crystallized_at: None,
511 chosen: None,
512 shadows: Vec::new(),
513 reasoning: DecisionReasoning::default(),
514 decider: request.decider.unwrap_or(Decider::User { name: None }),
515 affected_goals: request.goals.unwrap_or_default(),
516 caused_by: request.caused_by,
517 causes: Vec::new(),
518 reversibility: Reversibility::default(),
519 consequences: Vec::new(),
520 regret_score: 0.0,
521 regret_updated_at: None,
522 };
523 decisions.push(decision);
524 }
525
526 for decision in &decisions {
528 for goal_id in &decision.affected_goals {
529 if let Some(goal) = self.goal_store.get_mut(goal_id) {
530 goal.decisions.push(decision.id);
531 }
532 }
533
534 if let Some(parent_id) = decision.caused_by {
535 if let Some(parent) = self.decision_store.get_mut(&parent_id) {
536 parent.causes.push(decision.id);
537 }
538 }
539
540 self.decision_store.insert(decision.id, decision.clone());
541 self.indexes.add_decision(decision);
542 }
543
544 self.mark_dirty();
545 Ok(decisions)
546 }
547
548 pub fn batch_create_commitments(
557 &mut self,
558 requests: Vec<CreateCommitmentRequest>,
559 ) -> crate::Result<Vec<Commitment>> {
560 if requests.is_empty() {
561 return Ok(Vec::new());
562 }
563
564 for (i, req) in requests.iter().enumerate() {
566 if let Some(gid) = req.goal {
567 if !self.goal_store.contains_key(&gid) {
568 return Err(crate::Error::CorruptedFile(format!(
569 "batch commitment[{}] references nonexistent goal {:?}",
570 i, gid
571 )));
572 }
573 }
574 }
575
576 let now = Timestamp::now();
577 let mut commitments = Vec::with_capacity(requests.len());
578
579 for request in &requests {
581 let id = CommitmentId(Uuid::new_v4());
582 let weight = self.calculate_commitment_weight(request);
583 let inertia = self.calculate_commitment_inertia(request);
584 let breaking_cost = self.calculate_breaking_cost(request);
585
586 commitments.push(Commitment {
587 id,
588 promise: request.promise.clone(),
589 made_to: request.stakeholder.clone(),
590 made_at: now,
591 due: request.due,
592 status: CommitmentStatus::Active,
593 weight,
594 inertia,
595 breaking_cost,
596 goal: request.goal,
597 entanglements: Vec::new(),
598 fulfillment: None,
599 renegotiations: Vec::new(),
600 });
601 }
602
603 for commitment in &commitments {
605 if let Some(goal_id) = commitment.goal {
606 if let Some(goal) = self.goal_store.get_mut(&goal_id) {
607 goal.commitments.push(commitment.id);
608 }
609 }
610 self.commitment_store
611 .insert(commitment.id, commitment.clone());
612 self.indexes.add_commitment(commitment);
613 }
614
615 self.mark_dirty();
616 Ok(commitments)
617 }
618
619 pub fn batch_fulfill_commitments(
624 &mut self,
625 fulfillments: Vec<(CommitmentId, String)>,
626 ) -> crate::Result<Vec<Commitment>> {
627 if fulfillments.is_empty() {
628 return Ok(Vec::new());
629 }
630
631 for (id, _) in &fulfillments {
633 let c = self
634 .commitment_store
635 .get(id)
636 .ok_or(crate::Error::CommitmentNotFound(*id))?;
637 if c.status != CommitmentStatus::Active {
638 return Err(crate::Error::CannotFulfill(c.status));
639 }
640 }
641
642 let pre_data: Vec<(CommitmentId, String, f64, f64, Vec<CommitmentEntanglement>)> =
644 fulfillments
645 .into_iter()
646 .map(|(id, how)| {
647 let bonus = self.calculate_chain_bonus(id);
648 let c = self
649 .commitment_store
650 .get(&id)
651 .expect("commitment existence validated in phase 1");
652 let weight = c.weight;
653 let entanglements = c.entanglements.clone();
654 (id, how, bonus, weight, entanglements)
655 })
656 .collect();
657
658 let now = Timestamp::now();
660 let mut results = Vec::with_capacity(pre_data.len());
661
662 for (id, how_delivered, chain_bonus, weight, entanglements) in &pre_data {
663 let energy_released = weight * (1.0 + chain_bonus);
664
665 if let Some(c) = self.commitment_store.get_mut(id) {
666 c.status = CommitmentStatus::Fulfilled;
667 c.fulfillment = Some(CommitmentFulfillment {
668 fulfilled_at: now,
669 how_delivered: how_delivered.clone(),
670 energy_released,
671 trust_gained: c.weight * 0.5,
672 });
673 }
674
675 for entanglement in entanglements {
676 if entanglement.entanglement_type == EntanglementType::Sequential {
677 let _ = self.boost_commitment(entanglement.with, energy_released * 0.3);
678 }
679 }
680
681 self.indexes.commitment_fulfilled(*id);
682 results.push(
683 self.commitment_store
684 .get(id)
685 .ok_or(crate::Error::CommitmentNotFound(*id))?
686 .clone(),
687 );
688 }
689
690 self.mark_dirty();
691 Ok(results)
692 }
693
694 pub fn momentum_report_indexed(&self) -> MomentumReport {
704 let active_ids = self
705 .indexes
706 .goals_by_status
707 .get(&GoalStatus::Active)
708 .cloned()
709 .unwrap_or_default();
710 let blocked_ids = self
711 .indexes
712 .goals_by_status
713 .get(&GoalStatus::Blocked)
714 .cloned()
715 .unwrap_or_default();
716
717 let active_goals: Vec<&Goal> = active_ids
718 .iter()
719 .chain(blocked_ids.iter())
720 .filter_map(|id| self.goal_store.get(id))
721 .collect();
722
723 let total = active_goals.len();
724 let avg = if total > 0 {
725 active_goals.iter().map(|g| g.physics.momentum).sum::<f64>() / total as f64
726 } else {
727 0.0
728 };
729
730 let mut distribution = MomentumDistribution {
731 high: 0,
732 medium: 0,
733 low: 0,
734 zero: 0,
735 };
736
737 let mut entries: Vec<GoalMomentumEntry> = active_goals
738 .iter()
739 .map(|g| {
740 match g.physics.momentum {
741 m if m >= 0.7 => distribution.high += 1,
742 m if m >= 0.3 => distribution.medium += 1,
743 m if m > 0.0 => distribution.low += 1,
744 _ => distribution.zero += 1,
745 }
746 GoalMomentumEntry {
747 goal_id: g.id,
748 title: g.title.clone(),
749 momentum: g.physics.momentum,
750 velocity: g.progress.velocity,
751 progress: g.progress.percentage,
752 }
753 })
754 .collect();
755
756 entries.sort_by(|a, b| {
757 b.momentum
758 .partial_cmp(&a.momentum)
759 .unwrap_or(std::cmp::Ordering::Equal)
760 });
761
762 let top = entries.iter().take(5).cloned().collect();
763 let stalled: Vec<GoalMomentumEntry> = entries
764 .iter()
765 .filter(|e| e.momentum < 0.05 && e.progress < 1.0)
766 .cloned()
767 .collect();
768
769 let accelerating: Vec<GoalMomentumEntry> = entries
771 .iter()
772 .filter(|e| e.velocity > e.momentum && e.velocity > 0.0)
773 .cloned()
774 .collect();
775
776 let decelerating: Vec<GoalMomentumEntry> = entries
778 .iter()
779 .filter(|e| e.momentum > e.velocity + 0.1 && e.velocity >= 0.0)
780 .cloned()
781 .collect();
782
783 MomentumReport {
784 total_goals: total,
785 average_momentum: avg,
786 momentum_distribution: distribution,
787 top_momentum: top,
788 stalled,
789 accelerating,
790 decelerating,
791 }
792 }
793
794 pub fn gravity_field_indexed(&self) -> GravityField {
800 let active_ids = self
801 .indexes
802 .goals_by_status
803 .get(&GoalStatus::Active)
804 .cloned()
805 .unwrap_or_default();
806 let blocked_ids = self
807 .indexes
808 .goals_by_status
809 .get(&GoalStatus::Blocked)
810 .cloned()
811 .unwrap_or_default();
812
813 let relevant: Vec<&Goal> = active_ids
814 .iter()
815 .chain(blocked_ids.iter())
816 .filter_map(|id| self.goal_store.get(id))
817 .collect();
818
819 let total = relevant.len();
820
821 let (weighted_urgency, weighted_priority, weighted_momentum) = if total > 0 {
822 let u = relevant
823 .iter()
824 .map(|g| g.feelings.urgency * g.physics.gravity)
825 .sum::<f64>()
826 / total as f64;
827 let p = relevant
828 .iter()
829 .map(|g| {
830 let pv = match g.priority {
831 Priority::Critical => 1.0,
832 Priority::High => 0.8,
833 Priority::Medium => 0.5,
834 Priority::Low => 0.3,
835 Priority::Someday => 0.1,
836 };
837 pv * g.physics.gravity
838 })
839 .sum::<f64>()
840 / total as f64;
841 let m = relevant
842 .iter()
843 .map(|g| g.physics.momentum * g.physics.gravity)
844 .sum::<f64>()
845 / total as f64;
846 (u, p, m)
847 } else {
848 (0.0, 0.0, 0.0)
849 };
850
851 let mut wells: Vec<GravityWell> = relevant
852 .iter()
853 .filter(|g| g.physics.gravity > 0.3)
854 .map(|g| {
855 let pull_radius = g.physics.gravity * (1.0 + g.dependents.len() as f64 * 0.2);
856 let captured: Vec<GoalId> = g
857 .children
858 .iter()
859 .chain(g.dependents.iter())
860 .copied()
861 .collect();
862 GravityWell {
863 goal_id: g.id,
864 title: g.title.clone(),
865 gravity: g.physics.gravity,
866 pull_radius,
867 captured_goals: captured,
868 }
869 })
870 .collect();
871
872 wells.sort_by(|a, b| {
873 b.gravity
874 .partial_cmp(&a.gravity)
875 .unwrap_or(std::cmp::Ordering::Equal)
876 });
877
878 let total_pull = wells.iter().map(|w| w.gravity).sum();
879 let dominant = wells.first().map(|w| w.goal_id);
880
881 GravityField {
882 total_goals: total,
883 field_center: GravityCenter {
884 weighted_urgency,
885 weighted_priority,
886 weighted_momentum,
887 },
888 wells,
889 total_pull,
890 dominant_attractor: dominant,
891 }
892 }
893
894 pub fn at_risk_commitments_scan(&self) -> Vec<(CommitmentId, Vec<String>)> {
898 let mut results = Vec::new();
899
900 for commitment in self.commitment_store.values() {
901 if commitment.status != CommitmentStatus::Active {
902 continue;
903 }
904
905 let mut risks = Vec::new();
906 if let Some(due) = commitment.due {
907 let now = Timestamp::now();
908 let days_remaining = (due.0 - now.0) as f64 / (86_400.0 * 1e9);
909
910 if days_remaining < 0.0 {
911 risks.push("Overdue".to_string());
912 } else if days_remaining < 3.0 {
913 risks.push(format!("Due in {:.1} days", days_remaining));
914 }
915
916 if let Some(goal_id) = commitment.goal {
917 if let Some(goal) = self.goal_store.get(&goal_id) {
918 let remaining_work = 1.0 - goal.progress.percentage;
919 let days_needed = if goal.progress.velocity > 0.0 {
920 remaining_work / goal.progress.velocity
921 } else {
922 f64::INFINITY
923 };
924 if days_needed > days_remaining {
925 risks.push(format!(
926 "Needs {:.1} days but only {:.1} remain",
927 days_needed, days_remaining
928 ));
929 }
930 }
931 }
932 }
933
934 if commitment.weight > 1.5 {
935 risks.push("High-weight commitment".to_string());
936 }
937
938 if !risks.is_empty() {
939 results.push((commitment.id, risks));
940 }
941 }
942
943 results.sort_by(|a, b| b.1.len().cmp(&a.1.len()));
944 results
945 }
946
947 pub fn metamorphosis_scan(&self) -> Vec<MetamorphosisSignal> {
951 let mut signals = Vec::new();
952
953 let active_ids = self
954 .indexes
955 .goals_by_status
956 .get(&GoalStatus::Active)
957 .cloned()
958 .unwrap_or_default();
959 let blocked_ids = self
960 .indexes
961 .goals_by_status
962 .get(&GoalStatus::Blocked)
963 .cloned()
964 .unwrap_or_default();
965
966 for id in active_ids.iter().chain(blocked_ids.iter()) {
967 if let Ok(signal) = self.detect_metamorphosis(*id) {
968 if signal.should_transform {
969 signals.push(signal);
970 }
971 }
972 }
973
974 signals
975 }
976
977 pub fn progress_forecast_batch(&self, ids: &[GoalId]) -> Vec<ProgressForecast> {
982 ids.iter()
983 .filter_map(|id| self.get_progress_forecast(*id).ok())
984 .collect()
985 }
986
987 pub fn progress_forecast_all_active(&self) -> Vec<ProgressForecast> {
992 let active_ids = self
993 .indexes
994 .goals_by_status
995 .get(&GoalStatus::Active)
996 .cloned()
997 .unwrap_or_default();
998
999 active_ids
1000 .iter()
1001 .filter_map(|id| self.get_progress_forecast(*id).ok())
1002 .collect()
1003 }
1004
1005 pub fn dream_goals_batch(&mut self, ids: &[GoalId]) -> Vec<crate::Result<Dream>> {
1010 if ids.is_empty() {
1011 return Vec::new();
1012 }
1013
1014 let valid_ids: Vec<GoalId> = ids
1016 .iter()
1017 .filter(|id| self.goal_store.contains_key(id))
1018 .copied()
1019 .collect();
1020
1021 valid_ids.iter().map(|id| self.dream_goal(*id)).collect()
1024 }
1025
1026 pub fn federation_health_scan(&self) -> Vec<FederationHealthEntry> {
1030 let now = Timestamp::now();
1031 self.federation_store
1032 .values()
1033 .map(|fed| {
1034 let sync_age_hours = (now.0 - fed.last_sync.0) as f64 / (3_600.0 * 1e9);
1035 let mut issues = Vec::new();
1036
1037 if fed.members.is_empty() {
1038 issues.push("No members".to_string());
1039 }
1040 if sync_age_hours > 24.0 {
1041 issues.push(format!("Last sync {:.0}h ago", sync_age_hours));
1042 }
1043 if !self.goal_store.contains_key(&fed.goal_id) {
1044 issues.push("Goal no longer exists".to_string());
1045 }
1046
1047 FederationHealthEntry {
1048 federation_id: fed.id,
1049 goal_id: fed.goal_id,
1050 member_count: fed.members.len(),
1051 sync_age_hours,
1052 issues,
1053 }
1054 })
1055 .collect()
1056 }
1057
1058 pub fn goal_health_scan(&self) -> GoalHealthReport {
1063 let active_ids = self
1064 .indexes
1065 .goals_by_status
1066 .get(&GoalStatus::Active)
1067 .cloned()
1068 .unwrap_or_default();
1069 let blocked_ids = self
1070 .indexes
1071 .goals_by_status
1072 .get(&GoalStatus::Blocked)
1073 .cloned()
1074 .unwrap_or_default();
1075
1076 let now = Timestamp::now();
1077 let mut stalled = Vec::new();
1078 let mut neglected = Vec::new();
1079 let mut deadline_risk = Vec::new();
1080 let mut blocked = Vec::new();
1081 let mut thriving = Vec::new();
1082
1083 for id in active_ids.iter().chain(blocked_ids.iter()) {
1084 let Some(goal) = self.goal_store.get(id) else {
1085 continue;
1086 };
1087
1088 if goal.status == GoalStatus::Blocked {
1089 blocked.push(*id);
1090 }
1091
1092 if goal.physics.momentum == 0.0 && goal.progress.percentage < 1.0 {
1093 stalled.push(*id);
1094 }
1095
1096 if goal.feelings.neglect > 0.5 {
1097 neglected.push(*id);
1098 }
1099
1100 if let Some(deadline) = goal.deadline {
1101 let days_left = (deadline.0 - now.0) as f64 / (86_400.0 * 1e9);
1102 if days_left < 7.0 && goal.progress.percentage < 0.8 {
1103 deadline_risk.push(*id);
1104 }
1105 }
1106
1107 if goal.physics.momentum > 0.7 && goal.progress.velocity > 0.0 {
1108 thriving.push(*id);
1109 }
1110 }
1111
1112 GoalHealthReport {
1113 total_active: active_ids.len(),
1114 total_blocked: blocked_ids.len(),
1115 stalled,
1116 neglected,
1117 deadline_risk,
1118 blocked,
1119 thriving,
1120 }
1121 }
1122
1123 pub fn start_consensus(
1132 &mut self,
1133 decision_id: DecisionId,
1134 participants: Vec<ConsensusParticipant>,
1135 ) -> crate::Result<DecisionConsensus> {
1136 let decision = self
1137 .decision_store
1138 .get(&decision_id)
1139 .ok_or(crate::Error::DecisionNotFound(decision_id))?;
1140
1141 if decision.status != DecisionStatus::Pending
1142 && decision.status != DecisionStatus::Deliberating
1143 {
1144 return Err(crate::Error::Validation(format!(
1145 "cannot start consensus on decision in state {:?}",
1146 decision.status
1147 )));
1148 }
1149
1150 if participants.is_empty() {
1151 return Err(crate::Error::CorruptedFile(
1152 "consensus requires at least one participant".to_string(),
1153 ));
1154 }
1155
1156 let now = Timestamp::now();
1157 let consensus = DecisionConsensus {
1158 decision_id,
1159 stakeholders: participants,
1160 deliberation: Vec::new(),
1161 synthesis: None,
1162 votes: HashMap::new(),
1163 alignment_score: 0.0,
1164 status: ConsensusStatus::Open,
1165 started_at: now,
1166 crystallized_at: None,
1167 };
1168
1169 if let Some(d) = self.decision_store.get_mut(&decision_id) {
1171 d.status = DecisionStatus::Deliberating;
1172 }
1173
1174 self.consensus_store.insert(decision_id, consensus.clone());
1175 self.mark_dirty();
1176 Ok(consensus)
1177 }
1178
1179 pub fn add_deliberation_round(
1184 &mut self,
1185 decision_id: DecisionId,
1186 statements: Vec<ConsensusStatement>,
1187 common_ground: Vec<CommonGround>,
1188 ) -> crate::Result<DecisionConsensus> {
1189 let consensus = self
1190 .consensus_store
1191 .get_mut(&decision_id)
1192 .ok_or(crate::Error::DecisionNotFound(decision_id))?;
1193
1194 if consensus.status == ConsensusStatus::Crystallized
1195 || consensus.status == ConsensusStatus::Deadlocked
1196 {
1197 return Err(crate::Error::CorruptedFile(
1198 "consensus already finalized".to_string(),
1199 ));
1200 }
1201
1202 let round_number = consensus.deliberation.len() + 1;
1203
1204 let cg_strength: f64 = common_ground.iter().map(|cg| cg.strength).sum::<f64>()
1206 / common_ground.len().max(1) as f64;
1207 let concession_bonus: f64 = statements
1208 .iter()
1209 .map(|s| if s.concessions.is_empty() { 0.0 } else { 0.1 })
1210 .sum::<f64>()
1211 / statements.len().max(1) as f64;
1212 let alignment_delta = (cg_strength * 0.6 + concession_bonus * 0.4).clamp(0.0, 0.3);
1213
1214 let round = DeliberationRound {
1215 round_number,
1216 statements,
1217 alignment_delta,
1218 emerged_common_ground: common_ground,
1219 recorded_at: Timestamp::now(),
1220 };
1221
1222 consensus.deliberation.push(round);
1223 consensus.alignment_score = (consensus.alignment_score + alignment_delta).clamp(0.0, 1.0);
1224 consensus.status = ConsensusStatus::Deliberating;
1225 let result = consensus.clone();
1226 self.mark_dirty();
1227 Ok(result)
1228 }
1229
1230 pub fn synthesize_consensus(
1232 &mut self,
1233 decision_id: DecisionId,
1234 proposal: String,
1235 incorporates_from: Vec<StakeholderId>,
1236 addresses_concerns: Vec<String>,
1237 ) -> crate::Result<DecisionConsensus> {
1238 let consensus = self
1239 .consensus_store
1240 .get_mut(&decision_id)
1241 .ok_or(crate::Error::DecisionNotFound(decision_id))?;
1242
1243 if consensus.status == ConsensusStatus::Crystallized {
1244 return Err(crate::Error::CorruptedFile(
1245 "consensus already crystallized".to_string(),
1246 ));
1247 }
1248
1249 let total = consensus.stakeholders.len().max(1) as f64;
1251 let incorporated = incorporates_from.len() as f64;
1252 let base_confidence = (incorporated / total).clamp(0.0, 1.0);
1253
1254 let synthesis = Synthesis {
1255 proposal,
1256 incorporates_from,
1257 addresses_concerns,
1258 confidence: base_confidence * 0.7 + consensus.alignment_score * 0.3,
1259 proposed_at: Timestamp::now(),
1260 };
1261
1262 consensus.synthesis = Some(synthesis);
1263 consensus.status = ConsensusStatus::Synthesizing;
1264 let result = consensus.clone();
1265 self.mark_dirty();
1266 Ok(result)
1267 }
1268
1269 pub fn record_consensus_vote(
1271 &mut self,
1272 decision_id: DecisionId,
1273 stakeholder_id: StakeholderId,
1274 vote: String,
1275 ) -> crate::Result<DecisionConsensus> {
1276 let consensus = self
1277 .consensus_store
1278 .get_mut(&decision_id)
1279 .ok_or(crate::Error::DecisionNotFound(decision_id))?;
1280
1281 if consensus.status == ConsensusStatus::Crystallized {
1282 return Err(crate::Error::CorruptedFile(
1283 "consensus already crystallized".to_string(),
1284 ));
1285 }
1286
1287 consensus.votes.insert(stakeholder_id, vote);
1288
1289 if consensus.status != ConsensusStatus::Voting {
1291 consensus.status = ConsensusStatus::Voting;
1292 }
1293
1294 let total_stakeholders = consensus.stakeholders.len().max(1);
1296 let voted_count = consensus.votes.len();
1297 if voted_count > 0 {
1298 let mut option_counts: HashMap<&String, usize> = HashMap::new();
1299 for v in consensus.votes.values() {
1300 *option_counts.entry(v).or_insert(0) += 1;
1301 }
1302 let max_agreement = *option_counts.values().max().unwrap_or(&0);
1303 let agreement_ratio = max_agreement as f64 / total_stakeholders as f64;
1304 consensus.alignment_score =
1305 (consensus.alignment_score * 0.5 + agreement_ratio * 0.5).clamp(0.0, 1.0);
1306 }
1307
1308 let result = consensus.clone();
1309 self.mark_dirty();
1310 Ok(result)
1311 }
1312
1313 pub fn get_consensus_status(
1315 &self,
1316 decision_id: DecisionId,
1317 ) -> crate::Result<&DecisionConsensus> {
1318 self.consensus_store
1319 .get(&decision_id)
1320 .ok_or(crate::Error::DecisionNotFound(decision_id))
1321 }
1322
1323 pub fn crystallize_consensus(
1328 &mut self,
1329 decision_id: DecisionId,
1330 chosen_path: PathId,
1331 force: bool,
1332 ) -> crate::Result<DecisionConsensus> {
1333 let alignment = self
1334 .consensus_store
1335 .get(&decision_id)
1336 .ok_or(crate::Error::DecisionNotFound(decision_id))?
1337 .alignment_score;
1338
1339 if !force && alignment < 0.5 {
1340 if let Some(c) = self.consensus_store.get_mut(&decision_id) {
1341 c.status = ConsensusStatus::Deadlocked;
1342 }
1343 self.mark_dirty();
1344 return Ok(self
1345 .consensus_store
1346 .get(&decision_id)
1347 .expect("consensus set in deadlock branch")
1348 .clone());
1349 }
1350
1351 let rationale = self
1353 .consensus_store
1354 .get(&decision_id)
1355 .and_then(|c| c.synthesis.as_ref())
1356 .map(|s| s.proposal.clone())
1357 .unwrap_or_else(|| "Consensus crystallization".to_string());
1358
1359 self.crystallize(
1360 decision_id,
1361 chosen_path,
1362 DecisionReasoning {
1363 rationale,
1364 confidence: alignment,
1365 ..Default::default()
1366 },
1367 )?;
1368
1369 if let Some(c) = self.consensus_store.get_mut(&decision_id) {
1371 c.status = ConsensusStatus::Crystallized;
1372 c.crystallized_at = Some(Timestamp::now());
1373 }
1374 self.mark_dirty();
1375 Ok(self
1376 .consensus_store
1377 .get(&decision_id)
1378 .expect("consensus set after crystallization")
1379 .clone())
1380 }
1381}
1382
1383#[cfg(test)]
1384mod tests {
1385 use super::*;
1386 use crate::CreateGoalRequest;
1387
1388 fn make_request(title: &str) -> CreateGoalRequest {
1389 CreateGoalRequest {
1390 title: title.to_string(),
1391 description: format!("Description for {}", title),
1392 intention: format!("Intention for {}", title),
1393 ..Default::default()
1394 }
1395 }
1396
1397 #[test]
1398 fn test_batch_create_empty() {
1399 let mut engine = PlanningEngine::in_memory();
1400 let result = engine.create_goals_batch(vec![]);
1401 assert!(result.is_ok());
1402 assert_eq!(result.unwrap().len(), 0);
1403 assert_eq!(engine.goal_count(), 0);
1404 }
1405
1406 #[test]
1407 fn test_batch_create_single() {
1408 let mut engine = PlanningEngine::in_memory();
1409 let result = engine.create_goals_batch(vec![make_request("Alpha")]);
1410 assert!(result.is_ok());
1411 let goals = result.unwrap();
1412 assert_eq!(goals.len(), 1);
1413 assert_eq!(goals[0].title, "Alpha");
1414 assert_eq!(engine.goal_count(), 1);
1415 }
1416
1417 #[test]
1418 fn test_batch_create_multiple() {
1419 let mut engine = PlanningEngine::in_memory();
1420 let requests = vec![
1421 make_request("Goal A"),
1422 make_request("Goal B"),
1423 make_request("Goal C"),
1424 make_request("Goal D"),
1425 make_request("Goal E"),
1426 ];
1427
1428 let result = engine.create_goals_batch(requests);
1429 assert!(result.is_ok());
1430 let goals = result.unwrap();
1431 assert_eq!(goals.len(), 5);
1432 assert_eq!(engine.goal_count(), 5);
1433
1434 for g in &goals {
1436 assert_eq!(g.status, GoalStatus::Draft);
1437 }
1438
1439 assert_eq!(engine.indexes.root_goals.len(), 5);
1441 }
1442
1443 #[test]
1444 fn test_batch_create_fail_fast_validation() {
1445 let mut engine = PlanningEngine::in_memory();
1446 let requests = vec![
1447 make_request("Valid Goal"),
1448 CreateGoalRequest {
1449 title: "".to_string(), description: "desc".to_string(),
1451 intention: "intent".to_string(),
1452 ..Default::default()
1453 },
1454 make_request("Another Valid Goal"),
1455 ];
1456
1457 let result = engine.create_goals_batch(requests);
1458 assert!(result.is_err());
1459 assert_eq!(engine.goal_count(), 0);
1461 }
1462
1463 #[test]
1464 fn test_batch_create_with_parent() {
1465 let mut engine = PlanningEngine::in_memory();
1466
1467 let parent = engine.create_goal(make_request("Parent")).unwrap();
1469
1470 let mut child_req = make_request("Child");
1472 child_req.parent = Some(parent.id);
1473 let result = engine.create_goals_batch(vec![child_req]);
1474 assert!(result.is_ok());
1475
1476 let updated_parent = engine.goal_store.get(&parent.id).unwrap();
1478 assert_eq!(updated_parent.children.len(), 1);
1479 }
1480
1481 #[test]
1482 fn test_batch_indexes_rebuilt_once() {
1483 let mut engine = PlanningEngine::in_memory();
1484 let requests: Vec<CreateGoalRequest> = (0..10)
1485 .map(|i| make_request(&format!("Batch Goal {}", i)))
1486 .collect();
1487
1488 let result = engine.create_goals_batch(requests);
1489 assert!(result.is_ok());
1490 assert_eq!(engine.goal_count(), 10);
1491
1492 assert_eq!(engine.indexes.root_goals.len(), 10);
1494 let draft_count = engine
1496 .indexes
1497 .goals_by_status
1498 .get(&GoalStatus::Draft)
1499 .map(|v| v.len())
1500 .unwrap_or(0);
1501 assert_eq!(draft_count, 10);
1502 }
1503
1504 #[test]
1505 fn test_singularity_parallel_empty() {
1506 let engine = PlanningEngine::in_memory();
1507 let singularity = engine.calculate_singularity_parallel();
1508 assert!(singularity.goal_positions.is_empty());
1509 assert_eq!(singularity.center.confidence, 0.0);
1510 }
1511
1512 #[test]
1513 fn test_blockers_parallel_empty() {
1514 let engine = PlanningEngine::in_memory();
1515 let prophecies = engine.scan_blockers_parallel();
1516 assert!(prophecies.is_empty());
1517 }
1518
1519 #[test]
1520 fn test_echoes_parallel_empty() {
1521 let engine = PlanningEngine::in_memory();
1522 let echoes = engine.progress_echoes_parallel();
1523 assert!(echoes.is_empty());
1524 }
1525
1526 #[test]
1527 fn test_singularity_parallel_with_goals() {
1528 let mut engine = PlanningEngine::in_memory();
1529 let goal = engine.create_goal(make_request("Active Goal")).unwrap();
1530 engine.activate_goal(goal.id).unwrap();
1531
1532 let singularity = engine.calculate_singularity_parallel();
1533 assert!(!singularity.goal_positions.is_empty());
1535 }
1536
1537 #[test]
1538 fn test_blockers_parallel_with_active_goal() {
1539 let mut engine = PlanningEngine::in_memory();
1540
1541 let dep = engine.create_goal(make_request("Dependency")).unwrap();
1543 let mut req = make_request("Main Goal");
1544 req.dependencies = Some(vec![dep.id]);
1545 let goal = engine.create_goal(req).unwrap();
1546 engine.activate_goal(goal.id).unwrap();
1547
1548 let prophecies = engine.scan_blockers_parallel();
1549 assert!(!prophecies.is_empty());
1551 }
1552
1553 #[test]
1554 fn test_blockers_parallel_sorted_by_confidence() {
1555 let mut engine = PlanningEngine::in_memory();
1556
1557 let dep1 = engine.create_goal(make_request("Dep 1")).unwrap();
1559 let dep2 = engine.create_goal(make_request("Dep 2")).unwrap();
1560
1561 let mut req = make_request("Goal with deps");
1562 req.dependencies = Some(vec![dep1.id, dep2.id]);
1563 let goal = engine.create_goal(req).unwrap();
1564 engine.activate_goal(goal.id).unwrap();
1565
1566 let prophecies = engine.scan_blockers_parallel();
1567 for w in prophecies.windows(2) {
1569 assert!(w[0].prediction_confidence >= w[1].prediction_confidence);
1570 }
1571 }
1572
1573 #[test]
1574 fn test_batch_create_preserves_priorities() {
1575 let mut engine = PlanningEngine::in_memory();
1576 let mut req_high = make_request("High Priority");
1577 req_high.priority = Some(Priority::High);
1578 let mut req_low = make_request("Low Priority");
1579 req_low.priority = Some(Priority::Low);
1580
1581 let goals = engine.create_goals_batch(vec![req_high, req_low]).unwrap();
1582 assert_eq!(goals[0].priority, Priority::High);
1583 assert_eq!(goals[1].priority, Priority::Low);
1584
1585 assert!(engine
1587 .indexes
1588 .goals_by_priority
1589 .get(&Priority::High)
1590 .unwrap()
1591 .contains(&goals[0].id));
1592 assert!(engine
1593 .indexes
1594 .goals_by_priority
1595 .get(&Priority::Low)
1596 .unwrap()
1597 .contains(&goals[1].id));
1598 }
1599
1600 #[test]
1605 fn test_batch_activate_empty() {
1606 let mut engine = PlanningEngine::in_memory();
1607 let result = engine.batch_activate_goals(&[]);
1608 assert!(result.is_ok());
1609 assert!(result.unwrap().is_empty());
1610 }
1611
1612 #[test]
1613 fn test_batch_activate_valid() {
1614 let mut engine = PlanningEngine::in_memory();
1615 let g1 = engine.create_goal(make_request("Goal 1")).unwrap();
1616 let g2 = engine.create_goal(make_request("Goal 2")).unwrap();
1617
1618 let result = engine.batch_activate_goals(&[g1.id, g2.id]);
1619 assert!(result.is_ok());
1620 let goals = result.unwrap();
1621 assert_eq!(goals.len(), 2);
1622 for g in &goals {
1623 assert_eq!(g.status, GoalStatus::Active);
1624 }
1625 }
1626
1627 #[test]
1628 fn test_batch_activate_fail_fast_on_invalid_state() {
1629 let mut engine = PlanningEngine::in_memory();
1630 let g1 = engine.create_goal(make_request("Goal 1")).unwrap();
1631 let g2 = engine.create_goal(make_request("Goal 2")).unwrap();
1632 engine.activate_goal(g2.id).unwrap();
1634
1635 let result = engine.batch_activate_goals(&[g1.id, g2.id]);
1636 assert!(result.is_err());
1637 let g1_check = engine.goal_store.get(&g1.id).unwrap();
1639 assert_eq!(g1_check.status, GoalStatus::Draft);
1640 }
1641
1642 #[test]
1647 fn test_batch_progress_empty() {
1648 let mut engine = PlanningEngine::in_memory();
1649 let result = engine.batch_progress_goals(vec![]);
1650 assert!(result.is_ok());
1651 assert!(result.unwrap().is_empty());
1652 }
1653
1654 #[test]
1655 fn test_batch_progress_valid() {
1656 let mut engine = PlanningEngine::in_memory();
1657 let g1 = engine.create_goal(make_request("Goal 1")).unwrap();
1658 engine.activate_goal(g1.id).unwrap();
1659 let g2 = engine.create_goal(make_request("Goal 2")).unwrap();
1660 engine.activate_goal(g2.id).unwrap();
1661
1662 let updates = vec![
1663 (g1.id, 0.5, Some("halfway".to_string())),
1664 (g2.id, 0.3, None),
1665 ];
1666 let result = engine.batch_progress_goals(updates);
1667 assert!(result.is_ok());
1668 let goals = result.unwrap();
1669 assert_eq!(goals.len(), 2);
1670 assert!((goals[0].progress.percentage - 0.5).abs() < 0.01);
1671 assert!((goals[1].progress.percentage - 0.3).abs() < 0.01);
1672 }
1673
1674 #[test]
1675 fn test_batch_progress_completion() {
1676 let mut engine = PlanningEngine::in_memory();
1677 let g = engine.create_goal(make_request("Finish Me")).unwrap();
1678 engine.activate_goal(g.id).unwrap();
1679
1680 let result = engine.batch_progress_goals(vec![(g.id, 1.0, Some("done".to_string()))]);
1681 assert!(result.is_ok());
1682 let goals = result.unwrap();
1683 assert!((goals[0].progress.percentage - 1.0).abs() < 0.01);
1685 }
1686
1687 #[test]
1692 fn test_batch_create_decisions_empty() {
1693 let mut engine = PlanningEngine::in_memory();
1694 let result = engine.batch_create_decisions(vec![]);
1695 assert!(result.is_ok());
1696 assert!(result.unwrap().is_empty());
1697 }
1698
1699 #[test]
1700 fn test_batch_create_decisions_valid() {
1701 let mut engine = PlanningEngine::in_memory();
1702 let requests = vec![
1703 CreateDecisionRequest {
1704 question: "What framework?".to_string(),
1705 ..Default::default()
1706 },
1707 CreateDecisionRequest {
1708 question: "What database?".to_string(),
1709 context: Some("Need ACID compliance".to_string()),
1710 ..Default::default()
1711 },
1712 ];
1713 let result = engine.batch_create_decisions(requests);
1714 assert!(result.is_ok());
1715 let decisions = result.unwrap();
1716 assert_eq!(decisions.len(), 2);
1717 assert_eq!(decisions[0].question.question, "What framework?");
1718 assert_eq!(decisions[1].question.question, "What database?");
1719 }
1720
1721 #[test]
1722 fn test_batch_create_decisions_with_goal_link() {
1723 let mut engine = PlanningEngine::in_memory();
1724 let goal = engine.create_goal(make_request("Main Goal")).unwrap();
1725 let requests = vec![CreateDecisionRequest {
1726 question: "How to implement?".to_string(),
1727 goals: Some(vec![goal.id]),
1728 ..Default::default()
1729 }];
1730 let result = engine.batch_create_decisions(requests);
1731 assert!(result.is_ok());
1732 let decisions = result.unwrap();
1733 assert_eq!(decisions.len(), 1);
1734 }
1735
1736 #[test]
1741 fn test_batch_create_commitments_empty() {
1742 let mut engine = PlanningEngine::in_memory();
1743 let result = engine.batch_create_commitments(vec![]);
1744 assert!(result.is_ok());
1745 assert!(result.unwrap().is_empty());
1746 }
1747
1748 #[test]
1749 fn test_batch_create_commitments_valid() {
1750 let mut engine = PlanningEngine::in_memory();
1751 let requests = vec![
1752 CreateCommitmentRequest {
1753 promise: Promise {
1754 description: "Deliver MVP".to_string(),
1755 deliverables: vec!["working app".to_string()],
1756 conditions: vec![],
1757 },
1758 stakeholder: Stakeholder {
1759 name: "Alice".to_string(),
1760 ..Default::default()
1761 },
1762 ..Default::default()
1763 },
1764 CreateCommitmentRequest {
1765 promise: Promise {
1766 description: "Write docs".to_string(),
1767 deliverables: vec!["README".to_string()],
1768 conditions: vec![],
1769 },
1770 stakeholder: Stakeholder {
1771 name: "Bob".to_string(),
1772 ..Default::default()
1773 },
1774 ..Default::default()
1775 },
1776 ];
1777 let result = engine.batch_create_commitments(requests);
1778 assert!(result.is_ok());
1779 let commitments = result.unwrap();
1780 assert_eq!(commitments.len(), 2);
1781 assert_eq!(commitments[0].promise.description, "Deliver MVP");
1782 assert_eq!(commitments[1].promise.description, "Write docs");
1783 }
1784
1785 #[test]
1790 fn test_batch_fulfill_commitments_empty() {
1791 let mut engine = PlanningEngine::in_memory();
1792 let result = engine.batch_fulfill_commitments(vec![]);
1793 assert!(result.is_ok());
1794 assert!(result.unwrap().is_empty());
1795 }
1796
1797 #[test]
1798 fn test_batch_fulfill_commitments_valid() {
1799 let mut engine = PlanningEngine::in_memory();
1800 let c = engine
1801 .create_commitment(CreateCommitmentRequest {
1802 promise: Promise {
1803 description: "Ship it".to_string(),
1804 deliverables: vec!["release".to_string()],
1805 conditions: vec![],
1806 },
1807 stakeholder: Stakeholder {
1808 name: "Team".to_string(),
1809 ..Default::default()
1810 },
1811 ..Default::default()
1812 })
1813 .unwrap();
1814
1815 let result = engine.batch_fulfill_commitments(vec![(c.id, "Shipped v1.0".to_string())]);
1816 assert!(result.is_ok());
1817 let commitments = result.unwrap();
1818 assert_eq!(commitments.len(), 1);
1819 assert_eq!(commitments[0].status, CommitmentStatus::Fulfilled);
1820 }
1821
1822 #[test]
1827 fn test_momentum_report_indexed_empty() {
1828 let engine = PlanningEngine::in_memory();
1829 let report = engine.momentum_report_indexed();
1830 assert_eq!(report.total_goals, 0);
1831 assert!((report.average_momentum - 0.0).abs() < 0.01);
1832 assert!(report.top_momentum.is_empty());
1833 }
1834
1835 #[test]
1836 fn test_momentum_report_indexed_with_active_goals() {
1837 let mut engine = PlanningEngine::in_memory();
1838 let g = engine.create_goal(make_request("Active Goal")).unwrap();
1839 engine.activate_goal(g.id).unwrap();
1840 engine
1841 .progress_goal(g.id, 0.3, Some("started".to_string()))
1842 .unwrap();
1843
1844 let report = engine.momentum_report_indexed();
1845 assert!(report.total_goals >= 1);
1846 assert!(!report.top_momentum.is_empty());
1847 let entry = &report.top_momentum[0];
1849 assert_eq!(entry.goal_id, g.id);
1850 assert!(!entry.title.is_empty());
1851 }
1852
1853 #[test]
1858 fn test_gravity_field_indexed_empty() {
1859 let engine = PlanningEngine::in_memory();
1860 let field = engine.gravity_field_indexed();
1861 assert_eq!(field.total_goals, 0);
1862 assert!(field.wells.is_empty());
1863 }
1864
1865 #[test]
1866 fn test_gravity_field_indexed_with_active_goals() {
1867 let mut engine = PlanningEngine::in_memory();
1868 let mut req = make_request("Important Goal");
1869 req.priority = Some(Priority::Critical);
1870 let g = engine.create_goal(req).unwrap();
1871 engine.activate_goal(g.id).unwrap();
1872
1873 let field = engine.gravity_field_indexed();
1874 assert!(field.total_goals >= 1);
1875 assert!(!field.wells.is_empty());
1876 assert!(field.wells[0].gravity > 0.0);
1878 }
1879
1880 #[test]
1885 fn test_at_risk_commitments_scan_empty() {
1886 let engine = PlanningEngine::in_memory();
1887 let results = engine.at_risk_commitments_scan();
1888 assert!(results.is_empty());
1889 }
1890
1891 #[test]
1892 fn test_at_risk_commitments_scan_with_commitment() {
1893 let mut engine = PlanningEngine::in_memory();
1894 let c = engine
1896 .create_commitment(CreateCommitmentRequest {
1897 promise: Promise {
1898 description: "Overdue task".to_string(),
1899 deliverables: vec!["something".to_string()],
1900 conditions: vec![],
1901 },
1902 stakeholder: Stakeholder {
1903 name: "Boss".to_string(),
1904 ..Default::default()
1905 },
1906 due: Some(Timestamp(0)), ..Default::default()
1908 })
1909 .unwrap();
1910
1911 let results = engine.at_risk_commitments_scan();
1912 let found = results.iter().any(|(id, _)| *id == c.id);
1914 assert!(found, "Overdue commitment should be flagged as at-risk");
1915 }
1916
1917 #[test]
1922 fn test_metamorphosis_scan_empty() {
1923 let engine = PlanningEngine::in_memory();
1924 let signals = engine.metamorphosis_scan();
1925 assert!(signals.is_empty());
1926 }
1927
1928 #[test]
1933 fn test_progress_forecast_batch_empty() {
1934 let engine = PlanningEngine::in_memory();
1935 let forecasts = engine.progress_forecast_batch(&[]);
1936 assert!(forecasts.is_empty());
1937 }
1938
1939 #[test]
1940 fn test_progress_forecast_batch_with_active_goals() {
1941 let mut engine = PlanningEngine::in_memory();
1942 let g = engine.create_goal(make_request("Forecast Me")).unwrap();
1943 engine.activate_goal(g.id).unwrap();
1944 engine
1945 .progress_goal(g.id, 0.4, Some("progressing".to_string()))
1946 .unwrap();
1947
1948 let forecasts = engine.progress_forecast_batch(&[g.id]);
1949 assert_eq!(forecasts.len(), 1);
1950 assert_eq!(forecasts[0].goal_id, g.id);
1951 }
1952
1953 #[test]
1954 fn test_progress_forecast_all_active_empty() {
1955 let engine = PlanningEngine::in_memory();
1956 let forecasts = engine.progress_forecast_all_active();
1957 assert!(forecasts.is_empty());
1958 }
1959
1960 #[test]
1961 fn test_progress_forecast_all_active_with_goals() {
1962 let mut engine = PlanningEngine::in_memory();
1963 let g1 = engine.create_goal(make_request("Active 1")).unwrap();
1964 engine.activate_goal(g1.id).unwrap();
1965 let g2 = engine.create_goal(make_request("Active 2")).unwrap();
1966 engine.activate_goal(g2.id).unwrap();
1967 let _draft = engine.create_goal(make_request("Draft")).unwrap();
1969
1970 let forecasts = engine.progress_forecast_all_active();
1971 assert_eq!(forecasts.len(), 2);
1972 }
1973
1974 #[test]
1979 fn test_dream_goals_batch_empty() {
1980 let mut engine = PlanningEngine::in_memory();
1981 let results = engine.dream_goals_batch(&[]);
1982 assert!(results.is_empty());
1983 }
1984
1985 #[test]
1986 fn test_dream_goals_batch_valid() {
1987 let mut engine = PlanningEngine::in_memory();
1988 let g = engine.create_goal(make_request("Dream Goal")).unwrap();
1989 engine.activate_goal(g.id).unwrap();
1990
1991 let results = engine.dream_goals_batch(&[g.id]);
1992 assert_eq!(results.len(), 1);
1993 assert!(results[0].is_ok());
1994 }
1995
1996 #[test]
2001 fn test_federation_health_scan_empty() {
2002 let engine = PlanningEngine::in_memory();
2003 let results = engine.federation_health_scan();
2004 assert!(results.is_empty());
2005 }
2006
2007 #[test]
2012 fn test_goal_health_scan_empty() {
2013 let engine = PlanningEngine::in_memory();
2014 let report = engine.goal_health_scan();
2015 assert_eq!(report.total_active, 0);
2016 assert_eq!(report.total_blocked, 0);
2017 assert!(report.stalled.is_empty());
2018 assert!(report.thriving.is_empty());
2019 }
2020
2021 #[test]
2022 fn test_goal_health_scan_with_active_goals() {
2023 let mut engine = PlanningEngine::in_memory();
2024 let g1 = engine.create_goal(make_request("Active Goal")).unwrap();
2026 engine.activate_goal(g1.id).unwrap();
2027 engine
2028 .progress_goal(g1.id, 0.5, Some("going well".to_string()))
2029 .unwrap();
2030
2031 let g2 = engine.create_goal(make_request("Stalled Goal")).unwrap();
2033 engine.activate_goal(g2.id).unwrap();
2034
2035 let report = engine.goal_health_scan();
2036 assert!(report.total_active >= 2);
2037 assert!(report.stalled.contains(&g2.id));
2039 }
2040
2041 fn make_decision(engine: &mut PlanningEngine, q: &str) -> Decision {
2046 engine
2047 .create_decision(CreateDecisionRequest {
2048 question: q.to_string(),
2049 ..Default::default()
2050 })
2051 .unwrap()
2052 }
2053
2054 fn make_participants() -> Vec<ConsensusParticipant> {
2055 vec![
2056 ConsensusParticipant {
2057 id: StakeholderId(uuid::Uuid::new_v4()),
2058 role: "Engineering".to_string(),
2059 initial_position: "Use Rust".to_string(),
2060 concerns: vec!["learning curve".to_string()],
2061 requirements: vec!["performance".to_string()],
2062 flexibility: 0.6,
2063 },
2064 ConsensusParticipant {
2065 id: StakeholderId(uuid::Uuid::new_v4()),
2066 role: "Product".to_string(),
2067 initial_position: "Use Go".to_string(),
2068 concerns: vec!["time to market".to_string()],
2069 requirements: vec!["quick iteration".to_string()],
2070 flexibility: 0.7,
2071 },
2072 ]
2073 }
2074
2075 #[test]
2076 fn test_start_consensus() {
2077 let mut engine = PlanningEngine::in_memory();
2078 let d = make_decision(&mut engine, "Which language?");
2079 let participants = make_participants();
2080 let result = engine.start_consensus(d.id, participants);
2081 assert!(result.is_ok());
2082 let consensus = result.unwrap();
2083 assert_eq!(consensus.decision_id, d.id);
2084 assert_eq!(consensus.stakeholders.len(), 2);
2085 assert_eq!(consensus.status, ConsensusStatus::Open);
2086 assert!((consensus.alignment_score - 0.0).abs() < f64::EPSILON);
2087 let decision = engine.decision_store.get(&d.id).unwrap();
2089 assert_eq!(decision.status, DecisionStatus::Deliberating);
2090 }
2091
2092 #[test]
2093 fn test_start_consensus_already_crystallized() {
2094 let mut engine = PlanningEngine::in_memory();
2095 let d = make_decision(&mut engine, "Already decided");
2096 let path = DecisionPath {
2097 name: "Option A".to_string(),
2098 description: "The only option".to_string(),
2099 ..Default::default()
2100 };
2101 engine.add_option(d.id, path.clone()).unwrap();
2102 engine
2103 .crystallize(d.id, path.id, DecisionReasoning::default())
2104 .unwrap();
2105 let result = engine.start_consensus(d.id, make_participants());
2106 assert!(result.is_err());
2107 }
2108
2109 #[test]
2110 fn test_start_consensus_empty_participants() {
2111 let mut engine = PlanningEngine::in_memory();
2112 let d = make_decision(&mut engine, "No one to ask");
2113 let result = engine.start_consensus(d.id, vec![]);
2114 assert!(result.is_err());
2115 }
2116
2117 #[test]
2118 fn test_add_deliberation_round() {
2119 let mut engine = PlanningEngine::in_memory();
2120 let d = make_decision(&mut engine, "Architecture choice");
2121 let participants = make_participants();
2122 let p0 = participants[0].id;
2123 let p1 = participants[1].id;
2124 engine.start_consensus(d.id, participants).unwrap();
2125
2126 let statements = vec![
2127 ConsensusStatement {
2128 stakeholder_id: p0,
2129 position: "Rust for safety".to_string(),
2130 supporting_arguments: vec!["memory safety".to_string()],
2131 concessions: vec!["willing to consider Go for tooling".to_string()],
2132 },
2133 ConsensusStatement {
2134 stakeholder_id: p1,
2135 position: "Go for speed".to_string(),
2136 supporting_arguments: vec!["faster dev cycle".to_string()],
2137 concessions: vec![],
2138 },
2139 ];
2140 let common_ground = vec![CommonGround {
2141 description: "Both want reliability".to_string(),
2142 agreed_by: vec![p0, p1],
2143 strength: 0.8,
2144 }];
2145
2146 let result = engine.add_deliberation_round(d.id, statements, common_ground);
2147 assert!(result.is_ok());
2148 let consensus = result.unwrap();
2149 assert_eq!(consensus.deliberation.len(), 1);
2150 assert_eq!(consensus.status, ConsensusStatus::Deliberating);
2151 assert!(consensus.alignment_score > 0.0);
2152 }
2153
2154 #[test]
2155 fn test_synthesize_consensus() {
2156 let mut engine = PlanningEngine::in_memory();
2157 let d = make_decision(&mut engine, "Tech choice");
2158 let participants = make_participants();
2159 let p0 = participants[0].id;
2160 let p1 = participants[1].id;
2161 engine.start_consensus(d.id, participants).unwrap();
2162
2163 engine
2165 .add_deliberation_round(
2166 d.id,
2167 vec![ConsensusStatement {
2168 stakeholder_id: p0,
2169 position: "Propose hybrid".to_string(),
2170 supporting_arguments: vec![],
2171 concessions: vec!["open to alternatives".to_string()],
2172 }],
2173 vec![CommonGround {
2174 description: "Performance matters".to_string(),
2175 agreed_by: vec![p0, p1],
2176 strength: 0.7,
2177 }],
2178 )
2179 .unwrap();
2180
2181 let result = engine.synthesize_consensus(
2182 d.id,
2183 "Use Rust for core, Go for tooling".to_string(),
2184 vec![p0, p1],
2185 vec!["performance".to_string(), "dev speed".to_string()],
2186 );
2187 assert!(result.is_ok());
2188 let consensus = result.unwrap();
2189 assert_eq!(consensus.status, ConsensusStatus::Synthesizing);
2190 assert!(consensus.synthesis.is_some());
2191 let syn = consensus.synthesis.unwrap();
2192 assert_eq!(syn.incorporates_from.len(), 2);
2193 assert!(syn.confidence > 0.0);
2194 }
2195
2196 #[test]
2197 fn test_record_consensus_vote() {
2198 let mut engine = PlanningEngine::in_memory();
2199 let d = make_decision(&mut engine, "Vote on approach");
2200 let participants = make_participants();
2201 let p0 = participants[0].id;
2202 let p1 = participants[1].id;
2203 engine.start_consensus(d.id, participants).unwrap();
2204
2205 let result = engine.record_consensus_vote(d.id, p0, "approve".to_string());
2206 assert!(result.is_ok());
2207 let consensus = result.unwrap();
2208 assert_eq!(consensus.status, ConsensusStatus::Voting);
2209 assert_eq!(consensus.votes.len(), 1);
2210
2211 let result2 = engine.record_consensus_vote(d.id, p1, "approve".to_string());
2213 assert!(result2.is_ok());
2214 let consensus2 = result2.unwrap();
2215 assert_eq!(consensus2.votes.len(), 2);
2216 assert!(consensus2.alignment_score > 0.3);
2218 }
2219
2220 #[test]
2221 fn test_get_consensus_status() {
2222 let mut engine = PlanningEngine::in_memory();
2223 let d = make_decision(&mut engine, "Status query test");
2224 engine.start_consensus(d.id, make_participants()).unwrap();
2225
2226 let result = engine.get_consensus_status(d.id);
2227 assert!(result.is_ok());
2228 assert_eq!(result.unwrap().stakeholders.len(), 2);
2229 }
2230
2231 #[test]
2232 fn test_get_consensus_status_not_found() {
2233 let engine = PlanningEngine::in_memory();
2234 let result = engine.get_consensus_status(DecisionId(uuid::Uuid::new_v4()));
2235 assert!(result.is_err());
2236 }
2237
2238 #[test]
2239 fn test_crystallize_consensus_sufficient_alignment() {
2240 let mut engine = PlanningEngine::in_memory();
2241 let d = make_decision(&mut engine, "Crystallize me");
2242 let participants = make_participants();
2243 let p0 = participants[0].id;
2244 let p1 = participants[1].id;
2245 engine.start_consensus(d.id, participants).unwrap();
2246
2247 let path = DecisionPath {
2249 name: "Hybrid approach".to_string(),
2250 description: "Best of both".to_string(),
2251 ..Default::default()
2252 };
2253 let path_id = path.id;
2254 engine.add_option(d.id, path).unwrap();
2255
2256 engine
2258 .record_consensus_vote(d.id, p0, "approve".to_string())
2259 .unwrap();
2260 engine
2261 .record_consensus_vote(d.id, p1, "approve".to_string())
2262 .unwrap();
2263
2264 let result = engine.crystallize_consensus(d.id, path_id, false);
2265 assert!(result.is_ok());
2266 let consensus = result.unwrap();
2267 assert_eq!(consensus.status, ConsensusStatus::Crystallized);
2268 assert!(consensus.crystallized_at.is_some());
2269 let decision = engine.decision_store.get(&d.id).unwrap();
2271 assert_eq!(decision.status, DecisionStatus::Crystallized);
2272 }
2273
2274 #[test]
2275 fn test_crystallize_consensus_low_alignment_deadlocks() {
2276 let mut engine = PlanningEngine::in_memory();
2277 let d = make_decision(&mut engine, "Deadlock me");
2278 engine.start_consensus(d.id, make_participants()).unwrap();
2279
2280 let path = DecisionPath {
2281 name: "Option A".to_string(),
2282 description: "Untested".to_string(),
2283 ..Default::default()
2284 };
2285 let path_id = path.id;
2286 engine.add_option(d.id, path).unwrap();
2287
2288 let result = engine.crystallize_consensus(d.id, path_id, false);
2290 assert!(result.is_ok());
2291 let consensus = result.unwrap();
2292 assert_eq!(consensus.status, ConsensusStatus::Deadlocked);
2293 }
2294
2295 #[test]
2296 fn test_crystallize_consensus_force() {
2297 let mut engine = PlanningEngine::in_memory();
2298 let d = make_decision(&mut engine, "Force it");
2299 engine.start_consensus(d.id, make_participants()).unwrap();
2300
2301 let path = DecisionPath {
2302 name: "Emergency choice".to_string(),
2303 description: "No time to deliberate".to_string(),
2304 ..Default::default()
2305 };
2306 let path_id = path.id;
2307 engine.add_option(d.id, path).unwrap();
2308
2309 let result = engine.crystallize_consensus(d.id, path_id, true);
2311 assert!(result.is_ok());
2312 let consensus = result.unwrap();
2313 assert_eq!(consensus.status, ConsensusStatus::Crystallized);
2314 }
2315
2316 #[test]
2317 fn test_consensus_full_lifecycle() {
2318 let mut engine = PlanningEngine::in_memory();
2319 let d = make_decision(&mut engine, "Full lifecycle test");
2320 let participants = make_participants();
2321 let p0 = participants[0].id;
2322 let p1 = participants[1].id;
2323
2324 let c = engine.start_consensus(d.id, participants).unwrap();
2326 assert_eq!(c.status, ConsensusStatus::Open);
2327
2328 let c = engine
2330 .add_deliberation_round(
2331 d.id,
2332 vec![
2333 ConsensusStatement {
2334 stakeholder_id: p0,
2335 position: "Prefer A".to_string(),
2336 supporting_arguments: vec!["fast".to_string()],
2337 concessions: vec!["B is fine too".to_string()],
2338 },
2339 ConsensusStatement {
2340 stakeholder_id: p1,
2341 position: "Prefer B".to_string(),
2342 supporting_arguments: vec!["safe".to_string()],
2343 concessions: vec!["A has merit".to_string()],
2344 },
2345 ],
2346 vec![CommonGround {
2347 description: "Both want quality".to_string(),
2348 agreed_by: vec![p0, p1],
2349 strength: 0.9,
2350 }],
2351 )
2352 .unwrap();
2353 assert_eq!(c.status, ConsensusStatus::Deliberating);
2354
2355 let c = engine
2357 .synthesize_consensus(
2358 d.id,
2359 "Combine A's speed with B's safety".to_string(),
2360 vec![p0, p1],
2361 vec!["quality".to_string()],
2362 )
2363 .unwrap();
2364 assert_eq!(c.status, ConsensusStatus::Synthesizing);
2365
2366 engine
2368 .record_consensus_vote(d.id, p0, "approve".to_string())
2369 .unwrap();
2370 let c = engine
2371 .record_consensus_vote(d.id, p1, "approve".to_string())
2372 .unwrap();
2373 assert_eq!(c.status, ConsensusStatus::Voting);
2374
2375 let path = DecisionPath {
2377 name: "Combined approach".to_string(),
2378 description: "A+B synthesis".to_string(),
2379 ..Default::default()
2380 };
2381 let path_id = path.id;
2382 engine.add_option(d.id, path).unwrap();
2383
2384 let c = engine.crystallize_consensus(d.id, path_id, false).unwrap();
2385 assert_eq!(c.status, ConsensusStatus::Crystallized);
2386 assert!(c.crystallized_at.is_some());
2387 }
2388}