aiecs 1.1.0__py3-none-any.whl → 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aiecs might be problematic. Click here for more details.

Files changed (81) hide show
  1. aiecs/__init__.py +1 -1
  2. aiecs/aiecs_client.py +1 -1
  3. aiecs/config/config.py +38 -0
  4. aiecs/domain/__init__.py +95 -0
  5. aiecs/domain/community/__init__.py +159 -0
  6. aiecs/domain/community/agent_adapter.py +516 -0
  7. aiecs/domain/community/analytics.py +465 -0
  8. aiecs/domain/community/collaborative_workflow.py +99 -7
  9. aiecs/domain/community/communication_hub.py +649 -0
  10. aiecs/domain/community/community_builder.py +322 -0
  11. aiecs/domain/community/community_integration.py +365 -12
  12. aiecs/domain/community/community_manager.py +481 -5
  13. aiecs/domain/community/decision_engine.py +459 -13
  14. aiecs/domain/community/exceptions.py +238 -0
  15. aiecs/domain/community/models/__init__.py +36 -0
  16. aiecs/domain/community/resource_manager.py +1 -1
  17. aiecs/domain/community/shared_context_manager.py +621 -0
  18. aiecs/domain/context/context_engine.py +37 -33
  19. aiecs/infrastructure/monitoring/__init__.py +22 -0
  20. aiecs/infrastructure/monitoring/global_metrics_manager.py +207 -0
  21. aiecs/infrastructure/persistence/file_storage.py +41 -28
  22. aiecs/llm/__init__.py +44 -7
  23. aiecs/llm/callbacks/__init__.py +12 -0
  24. aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +1 -1
  25. aiecs/llm/client_factory.py +23 -6
  26. aiecs/llm/clients/__init__.py +35 -0
  27. aiecs/llm/{base_client.py → clients/base_client.py} +73 -1
  28. aiecs/llm/{googleai_client.py → clients/googleai_client.py} +19 -15
  29. aiecs/llm/{openai_client.py → clients/openai_client.py} +9 -14
  30. aiecs/llm/{vertex_client.py → clients/vertex_client.py} +15 -15
  31. aiecs/llm/{xai_client.py → clients/xai_client.py} +36 -50
  32. aiecs/llm/config/__init__.py +54 -0
  33. aiecs/llm/config/config_loader.py +275 -0
  34. aiecs/llm/config/config_validator.py +237 -0
  35. aiecs/llm/config/model_config.py +132 -0
  36. aiecs/llm/utils/__init__.py +11 -0
  37. aiecs/llm/utils/validate_config.py +91 -0
  38. aiecs/main.py +32 -2
  39. aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
  40. aiecs/scripts/aid/__init__.py +15 -0
  41. aiecs/scripts/aid/version_manager.py +224 -0
  42. aiecs/scripts/dependance_check/download_nlp_data.py +1 -0
  43. aiecs/tools/__init__.py +23 -23
  44. aiecs/tools/docs/__init__.py +5 -2
  45. aiecs/tools/docs/ai_document_orchestrator.py +39 -26
  46. aiecs/tools/docs/ai_document_writer_orchestrator.py +61 -38
  47. aiecs/tools/docs/content_insertion_tool.py +48 -28
  48. aiecs/tools/docs/document_creator_tool.py +47 -29
  49. aiecs/tools/docs/document_layout_tool.py +35 -20
  50. aiecs/tools/docs/document_parser_tool.py +56 -36
  51. aiecs/tools/docs/document_writer_tool.py +115 -62
  52. aiecs/tools/schema_generator.py +56 -56
  53. aiecs/tools/statistics/__init__.py +82 -0
  54. aiecs/tools/statistics/ai_data_analysis_orchestrator.py +581 -0
  55. aiecs/tools/statistics/ai_insight_generator_tool.py +473 -0
  56. aiecs/tools/statistics/ai_report_orchestrator_tool.py +629 -0
  57. aiecs/tools/statistics/data_loader_tool.py +518 -0
  58. aiecs/tools/statistics/data_profiler_tool.py +599 -0
  59. aiecs/tools/statistics/data_transformer_tool.py +531 -0
  60. aiecs/tools/statistics/data_visualizer_tool.py +460 -0
  61. aiecs/tools/statistics/model_trainer_tool.py +470 -0
  62. aiecs/tools/statistics/statistical_analyzer_tool.py +426 -0
  63. aiecs/tools/task_tools/chart_tool.py +2 -1
  64. aiecs/tools/task_tools/image_tool.py +43 -43
  65. aiecs/tools/task_tools/office_tool.py +39 -36
  66. aiecs/tools/task_tools/pandas_tool.py +37 -33
  67. aiecs/tools/task_tools/report_tool.py +67 -56
  68. aiecs/tools/task_tools/research_tool.py +32 -31
  69. aiecs/tools/task_tools/scraper_tool.py +53 -46
  70. aiecs/tools/task_tools/search_tool.py +1123 -0
  71. aiecs/tools/task_tools/stats_tool.py +20 -15
  72. aiecs/tools/tool_executor/__init__.py +2 -2
  73. aiecs/tools/tool_executor/tool_executor.py +3 -3
  74. {aiecs-1.1.0.dist-info → aiecs-1.2.1.dist-info}/METADATA +5 -1
  75. aiecs-1.2.1.dist-info/RECORD +144 -0
  76. {aiecs-1.1.0.dist-info → aiecs-1.2.1.dist-info}/entry_points.txt +1 -0
  77. aiecs/tools/task_tools/search_api.py +0 -7
  78. aiecs-1.1.0.dist-info/RECORD +0 -114
  79. {aiecs-1.1.0.dist-info → aiecs-1.2.1.dist-info}/WHEEL +0 -0
  80. {aiecs-1.1.0.dist-info → aiecs-1.2.1.dist-info}/licenses/LICENSE +0 -0
  81. {aiecs-1.1.0.dist-info → aiecs-1.2.1.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,7 @@ from .models.community_models import (
15
15
  CommunityDecision, CommunityMember, AgentCommunity,
16
16
  DecisionStatus, GovernanceType
17
17
  )
18
- from ..core.exceptions.task_exceptions import TaskValidationError
18
+ from .exceptions import CommunityValidationError as TaskValidationError
19
19
 
20
20
  logger = logging.getLogger(__name__)
21
21
 
@@ -334,21 +334,467 @@ class DecisionEngine:
334
334
  return await resolver_func(decision_id, community_id)
335
335
 
336
336
  async def _mediation_resolution(self, decision_id: str, community_id: str) -> Dict[str, Any]:
337
- """Mediation-based conflict resolution."""
338
- # TODO: Implement mediation logic
339
- return {"strategy": "mediation", "status": "pending", "mediator": None}
337
+ """
338
+ Mediation-based conflict resolution.
339
+
340
+ Process:
341
+ 1. Select neutral mediator (high reputation, not involved in voting)
342
+ 2. Facilitate structured discussion between opposing sides
343
+ 3. Identify core concerns and interests
344
+ 4. Propose compromise solutions
345
+ 5. Build consensus on mediated outcome
346
+ """
347
+ if not self.community_manager:
348
+ return {"strategy": "mediation", "status": "failed", "reason": "Community manager not available"}
349
+
350
+ decision = self.community_manager.decisions.get(decision_id)
351
+ community = self.community_manager.communities.get(community_id)
352
+
353
+ if not decision or not community:
354
+ return {"strategy": "mediation", "status": "failed", "reason": "Decision or community not found"}
355
+
356
+ # Step 1: Select mediator
357
+ mediator_id = await self._select_mediator(decision, community)
358
+ if not mediator_id:
359
+ return {"strategy": "mediation", "status": "failed", "reason": "No suitable mediator found"}
360
+
361
+ # Step 2: Identify opposing sides
362
+ for_side = decision.votes_for
363
+ against_side = decision.votes_against
364
+
365
+ # Step 3: Analyze core concerns (simulate by examining vote distribution)
366
+ concerns = {
367
+ "support_concerns": self._extract_concerns(for_side, community),
368
+ "opposition_concerns": self._extract_concerns(against_side, community)
369
+ }
370
+
371
+ # Step 4: Propose compromise
372
+ compromise_proposal = {
373
+ "original_proposal": decision.title,
374
+ "modifications": [
375
+ "Address key concerns from opposition",
376
+ "Maintain core value from supporters",
377
+ "Add safeguards or conditions",
378
+ "Phased implementation approach"
379
+ ],
380
+ "mediator_recommendation": "Modified proposal with balanced approach"
381
+ }
382
+
383
+ # Step 5: Set up for re-vote
384
+ result = {
385
+ "strategy": "mediation",
386
+ "status": "mediation_completed",
387
+ "mediator_id": mediator_id,
388
+ "mediator": self.community_manager.members.get(mediator_id).agent_id if mediator_id else None,
389
+ "concerns_identified": concerns,
390
+ "compromise_proposal": compromise_proposal,
391
+ "next_steps": "Re-vote on mediated proposal",
392
+ "recommended_threshold": "simple_majority"
393
+ }
394
+
395
+ logger.info(f"Mediation completed for decision {decision_id} by mediator {mediator_id}")
396
+ return result
397
+
398
+ async def _select_mediator(self, decision: CommunityDecision, community: AgentCommunity) -> Optional[str]:
399
+ """Select a neutral mediator for conflict resolution."""
400
+ # Find members who didn't vote or abstained, with high reputation
401
+ candidates = []
402
+
403
+ all_voters = set(decision.votes_for + decision.votes_against)
404
+
405
+ for member_id in community.members:
406
+ if member_id not in all_voters:
407
+ member = self.community_manager.members.get(member_id)
408
+ if member and member.reputation > 0.5: # High reputation threshold
409
+ candidates.append((member_id, member.reputation))
410
+
411
+ # Also consider abstentions with very high reputation
412
+ for member_id in decision.abstentions:
413
+ member = self.community_manager.members.get(member_id)
414
+ if member and member.reputation > 0.7:
415
+ candidates.append((member_id, member.reputation))
416
+
417
+ if not candidates:
418
+ return None
419
+
420
+ # Select highest reputation member
421
+ candidates.sort(key=lambda x: x[1], reverse=True)
422
+ return candidates[0][0]
423
+
424
+ def _extract_concerns(self, voter_ids: List[str], community: AgentCommunity) -> List[str]:
425
+ """Extract concerns from voter groups based on their roles and specializations."""
426
+ concerns = []
427
+ role_distribution = {}
428
+
429
+ for voter_id in voter_ids:
430
+ member = self.community_manager.members.get(voter_id) if self.community_manager else None
431
+ if member:
432
+ role = member.community_role.value
433
+ role_distribution[role] = role_distribution.get(role, 0) + 1
434
+
435
+ # Generate concerns based on roles
436
+ if "leader" in role_distribution:
437
+ concerns.append("Strategic alignment and governance impact")
438
+ if "specialist" in role_distribution:
439
+ concerns.append("Technical feasibility and implementation details")
440
+ if "contributor" in role_distribution:
441
+ concerns.append("Practical implications and workload impact")
442
+
443
+ return concerns if concerns else ["General concerns about proposal"]
340
444
 
341
445
  async def _arbitration_resolution(self, decision_id: str, community_id: str) -> Dict[str, Any]:
342
- """Arbitration-based conflict resolution."""
343
- # TODO: Implement arbitration logic
344
- return {"strategy": "arbitration", "status": "pending", "arbitrator": None}
446
+ """
447
+ Arbitration-based conflict resolution.
448
+
449
+ Process:
450
+ 1. Select authoritative arbitrator (leader or senior coordinator)
451
+ 2. Review all arguments and evidence
452
+ 3. Make binding decision
453
+ 4. Provide detailed rationale
454
+ """
455
+ if not self.community_manager:
456
+ return {"strategy": "arbitration", "status": "failed", "reason": "Community manager not available"}
457
+
458
+ decision = self.community_manager.decisions.get(decision_id)
459
+ community = self.community_manager.communities.get(community_id)
460
+
461
+ if not decision or not community:
462
+ return {"strategy": "arbitration", "status": "failed", "reason": "Decision or community not found"}
463
+
464
+ # Step 1: Select arbitrator (prefer leader, then coordinator with highest reputation)
465
+ arbitrator_id = await self._select_arbitrator(community)
466
+ if not arbitrator_id:
467
+ return {"strategy": "arbitration", "status": "failed", "reason": "No suitable arbitrator found"}
468
+
469
+ # Step 2: Analyze decision context
470
+ votes_for = len(decision.votes_for)
471
+ votes_against = len(decision.votes_against)
472
+ total_votes = votes_for + votes_against
473
+
474
+ # Step 3: Make arbitration decision (simulate based on vote distribution and priority)
475
+ # In practice, this would involve the actual arbitrator's judgment
476
+ support_ratio = votes_for / total_votes if total_votes > 0 else 0
477
+
478
+ # Arbitrator considers: vote distribution, decision priority, community impact
479
+ arbitration_decision = "approved" if support_ratio >= 0.4 else "rejected" # Lower threshold with rationale
480
+
481
+ # Step 4: Provide rationale
482
+ rationale = self._generate_arbitration_rationale(
483
+ arbitration_decision, support_ratio, decision, community
484
+ )
485
+
486
+ result = {
487
+ "strategy": "arbitration",
488
+ "status": "arbitration_completed",
489
+ "arbitrator_id": arbitrator_id,
490
+ "arbitrator": self.community_manager.members.get(arbitrator_id).agent_id if arbitrator_id else None,
491
+ "binding_decision": arbitration_decision,
492
+ "rationale": rationale,
493
+ "votes_for": votes_for,
494
+ "votes_against": votes_against,
495
+ "support_ratio": support_ratio,
496
+ "is_binding": True,
497
+ "appeal_allowed": False
498
+ }
499
+
500
+ # Update decision status based on arbitration
501
+ if arbitration_decision == "approved":
502
+ decision.status = DecisionStatus.APPROVED
503
+ else:
504
+ decision.status = DecisionStatus.REJECTED
505
+
506
+ logger.info(f"Arbitration completed for decision {decision_id}: {arbitration_decision}")
507
+ return result
508
+
509
+ async def _select_arbitrator(self, community: AgentCommunity) -> Optional[str]:
510
+ """Select an authoritative arbitrator."""
511
+ # Prefer leaders first
512
+ if community.leaders:
513
+ # Select leader with highest reputation
514
+ best_leader = None
515
+ best_reputation = -1
516
+
517
+ for leader_id in community.leaders:
518
+ member = self.community_manager.members.get(leader_id)
519
+ if member and member.reputation > best_reputation:
520
+ best_reputation = member.reputation
521
+ best_leader = leader_id
522
+
523
+ if best_leader:
524
+ return best_leader
525
+
526
+ # Fall back to coordinators
527
+ if community.coordinators:
528
+ best_coordinator = None
529
+ best_reputation = -1
530
+
531
+ for coordinator_id in community.coordinators:
532
+ member = self.community_manager.members.get(coordinator_id)
533
+ if member and member.reputation > best_reputation:
534
+ best_reputation = member.reputation
535
+ best_coordinator = coordinator_id
536
+
537
+ if best_coordinator:
538
+ return best_coordinator
539
+
540
+ return None
541
+
542
+ def _generate_arbitration_rationale(
543
+ self,
544
+ decision: str,
545
+ support_ratio: float,
546
+ proposal: CommunityDecision,
547
+ community: AgentCommunity
548
+ ) -> str:
549
+ """Generate detailed rationale for arbitration decision."""
550
+ rationale_parts = []
551
+
552
+ rationale_parts.append(f"After careful review of the proposal '{proposal.title}', ")
553
+ rationale_parts.append(f"with {support_ratio:.1%} support from voting members, ")
554
+
555
+ if decision == "approved":
556
+ rationale_parts.append("this arbitration approves the proposal. ")
557
+ rationale_parts.append("The decision aligns with community interests and demonstrates sufficient support. ")
558
+ if support_ratio < 0.5:
559
+ rationale_parts.append("While not achieving majority, the strategic importance warrants approval. ")
560
+ else:
561
+ rationale_parts.append("this arbitration rejects the proposal. ")
562
+ rationale_parts.append("The concerns raised outweigh the benefits, and insufficient consensus exists. ")
563
+
564
+ rationale_parts.append(f"Priority level: {proposal.priority}. ")
565
+ rationale_parts.append("This decision is binding and final.")
566
+
567
+ return "".join(rationale_parts)
345
568
 
346
569
  async def _compromise_resolution(self, decision_id: str, community_id: str) -> Dict[str, Any]:
347
- """Compromise-based conflict resolution."""
348
- # TODO: Implement compromise logic
349
- return {"strategy": "compromise", "status": "pending", "compromise_proposal": None}
570
+ """
571
+ Compromise-based conflict resolution.
572
+
573
+ Process:
574
+ 1. Analyze opposing positions
575
+ 2. Identify negotiable vs non-negotiable elements
576
+ 3. Generate compromise alternatives
577
+ 4. Create hybrid solution
578
+ 5. Test acceptance with stakeholders
579
+ """
580
+ if not self.community_manager:
581
+ return {"strategy": "compromise", "status": "failed", "reason": "Community manager not available"}
582
+
583
+ decision = self.community_manager.decisions.get(decision_id)
584
+ community = self.community_manager.communities.get(community_id)
585
+
586
+ if not decision or not community:
587
+ return {"strategy": "compromise", "status": "failed", "reason": "Decision or community not found"}
588
+
589
+ votes_for = len(decision.votes_for)
590
+ votes_against = len(decision.votes_against)
591
+ total_votes = votes_for + votes_against
592
+
593
+ if total_votes == 0:
594
+ return {"strategy": "compromise", "status": "failed", "reason": "No votes to analyze"}
595
+
596
+ # Step 1: Analyze positions
597
+ support_ratio = votes_for / total_votes
598
+ opposition_ratio = votes_against / total_votes
599
+
600
+ position_analysis = {
601
+ "support_strength": support_ratio,
602
+ "opposition_strength": opposition_ratio,
603
+ "balance": "balanced" if 0.4 <= support_ratio <= 0.6 else "polarized"
604
+ }
605
+
606
+ # Step 2: Identify elements
607
+ elements = {
608
+ "core_proposal": decision.title,
609
+ "negotiable_elements": [
610
+ "Implementation timeline",
611
+ "Resource allocation",
612
+ "Scope limitations",
613
+ "Review checkpoints"
614
+ ],
615
+ "non_negotiable_elements": [
616
+ "Core objectives",
617
+ "Safety requirements",
618
+ "Community values alignment"
619
+ ]
620
+ }
621
+
622
+ # Step 3 & 4: Generate compromise alternatives
623
+ compromise_options = []
624
+
625
+ # Option 1: Phased approach
626
+ compromise_options.append({
627
+ "option": "phased_implementation",
628
+ "description": "Implement in phases with review points",
629
+ "modifications": [
630
+ "Start with pilot/trial phase",
631
+ "Review after initial phase",
632
+ "Full rollout conditional on pilot success"
633
+ ],
634
+ "acceptance_probability": 0.75
635
+ })
636
+
637
+ # Option 2: Conditional approval
638
+ compromise_options.append({
639
+ "option": "conditional_approval",
640
+ "description": "Approve with conditions addressing concerns",
641
+ "modifications": [
642
+ "Add oversight committee",
643
+ "Include opposition representatives",
644
+ "Establish success metrics and review schedule"
645
+ ],
646
+ "acceptance_probability": 0.70
647
+ })
648
+
649
+ # Option 3: Scaled-down version
650
+ compromise_options.append({
651
+ "option": "scaled_down",
652
+ "description": "Reduced scope addressing primary concerns",
653
+ "modifications": [
654
+ "Limit scope to less contentious areas",
655
+ "Reduce resource commitment",
656
+ "Extend timeline for gradual adoption"
657
+ ],
658
+ "acceptance_probability": 0.65
659
+ })
660
+
661
+ # Select best compromise based on vote distribution
662
+ recommended_option = compromise_options[0] if support_ratio > 0.45 else compromise_options[2]
663
+
664
+ result = {
665
+ "strategy": "compromise",
666
+ "status": "compromise_proposed",
667
+ "position_analysis": position_analysis,
668
+ "elements": elements,
669
+ "compromise_options": compromise_options,
670
+ "recommended_option": recommended_option,
671
+ "next_steps": "Review compromise options and vote on preferred alternative",
672
+ "requires_revote": True,
673
+ "expected_consensus": recommended_option["acceptance_probability"]
674
+ }
675
+
676
+ logger.info(f"Compromise resolution generated for decision {decision_id}")
677
+ return result
350
678
 
351
679
  async def _escalation_resolution(self, decision_id: str, community_id: str) -> Dict[str, Any]:
352
- """Escalation-based conflict resolution."""
353
- # TODO: Implement escalation logic
354
- return {"strategy": "escalation", "status": "pending", "escalation_level": 1}
680
+ """
681
+ Escalation-based conflict resolution.
682
+
683
+ Process:
684
+ 1. Determine current escalation level
685
+ 2. Escalate to higher authority/broader group
686
+ 3. Apply progressively stronger resolution mechanisms
687
+ 4. Track escalation path and outcomes
688
+
689
+ Escalation Levels:
690
+ - Level 1: Community-wide discussion and re-vote
691
+ - Level 2: Coordinator council review
692
+ - Level 3: Leader decision
693
+ - Level 4: External arbitration or parent community
694
+ """
695
+ if not self.community_manager:
696
+ return {"strategy": "escalation", "status": "failed", "reason": "Community manager not available"}
697
+
698
+ decision = self.community_manager.decisions.get(decision_id)
699
+ community = self.community_manager.communities.get(community_id)
700
+
701
+ if not decision or not community:
702
+ return {"strategy": "escalation", "status": "failed", "reason": "Decision or community not found"}
703
+
704
+ # Determine current escalation level from metadata
705
+ current_level = decision.metadata.get("escalation_level", 0)
706
+ next_level = current_level + 1
707
+
708
+ # Define escalation path
709
+ escalation_path = {
710
+ 1: {
711
+ "level": 1,
712
+ "name": "Community Discussion",
713
+ "authority": "All active members",
714
+ "process": "Open discussion with extended voting period",
715
+ "threshold": "supermajority (67%)",
716
+ "timeline": "7 days"
717
+ },
718
+ 2: {
719
+ "level": 2,
720
+ "name": "Coordinator Council",
721
+ "authority": "Community coordinators",
722
+ "process": "Coordinator review and recommendation",
723
+ "threshold": "coordinator consensus",
724
+ "timeline": "3 days"
725
+ },
726
+ 3: {
727
+ "level": 3,
728
+ "name": "Leadership Decision",
729
+ "authority": "Community leaders",
730
+ "process": "Leadership panel makes binding decision",
731
+ "threshold": "leader majority or single leader decision",
732
+ "timeline": "1 day"
733
+ },
734
+ 4: {
735
+ "level": 4,
736
+ "name": "External Review",
737
+ "authority": "External arbitrator or parent community",
738
+ "process": "Independent third-party review",
739
+ "threshold": "external arbitrator decision",
740
+ "timeline": "As needed"
741
+ }
742
+ }
743
+
744
+ if next_level > 4:
745
+ return {
746
+ "strategy": "escalation",
747
+ "status": "max_escalation_reached",
748
+ "message": "Maximum escalation level reached. Decision must be resolved or abandoned.",
749
+ "recommendation": "Consider abandoning or significantly revising the proposal"
750
+ }
751
+
752
+ current_escalation = escalation_path[next_level]
753
+
754
+ # Determine escalation authority
755
+ authority_members = []
756
+ if next_level == 1:
757
+ authority_members = community.members
758
+ elif next_level == 2:
759
+ authority_members = community.coordinators
760
+ elif next_level == 3:
761
+ authority_members = community.leaders
762
+ elif next_level == 4:
763
+ authority_members = [] # External
764
+
765
+ # Update decision metadata
766
+ decision.metadata["escalation_level"] = next_level
767
+ decision.metadata["escalation_timestamp"] = datetime.utcnow().isoformat()
768
+ decision.metadata["escalation_history"] = decision.metadata.get("escalation_history", [])
769
+ decision.metadata["escalation_history"].append({
770
+ "from_level": current_level,
771
+ "to_level": next_level,
772
+ "timestamp": datetime.utcnow().isoformat(),
773
+ "reason": "Unresolved conflict"
774
+ })
775
+
776
+ result = {
777
+ "strategy": "escalation",
778
+ "status": "escalated",
779
+ "previous_level": current_level,
780
+ "current_level": next_level,
781
+ "escalation_details": current_escalation,
782
+ "authority_members": authority_members,
783
+ "authority_count": len(authority_members),
784
+ "escalation_history": decision.metadata["escalation_history"],
785
+ "next_steps": f"Proceed with {current_escalation['name']} process",
786
+ "required_action": current_escalation["process"],
787
+ "decision_threshold": current_escalation["threshold"],
788
+ "timeline": current_escalation["timeline"]
789
+ }
790
+
791
+ # Reset voting for re-evaluation at new level
792
+ if next_level < 4:
793
+ decision.votes_for = []
794
+ decision.votes_against = []
795
+ decision.abstentions = []
796
+ decision.status = DecisionStatus.PROPOSED
797
+ decision.voting_ends_at = datetime.utcnow() + timedelta(days=int(current_escalation["timeline"].split()[0]))
798
+
799
+ logger.info(f"Decision {decision_id} escalated to level {next_level}: {current_escalation['name']}")
800
+ return result