- matrix-gateway: POST /internal/matrix/presence/online endpoint - usePresenceHeartbeat hook with activity tracking - Auto away after 5 min inactivity - Offline on page close/visibility change - Integrated in MatrixChatRoom component
208 lines
6.9 KiB
Python
208 lines
6.9 KiB
Python
"""
|
|
Governance Engine — Voting power calculation and proposal evaluation
|
|
Phase 8: DAO Dashboard
|
|
"""
|
|
from decimal import Decimal, getcontext
|
|
from typing import List, Optional
|
|
from models import DaoRead, ProposalRead, VoteRead, ProposalResult
|
|
|
|
# Set decimal precision
|
|
getcontext().prec = 28
|
|
|
|
class GovernanceEngine:
|
|
"""Engine for calculating voting power and evaluating proposals"""
|
|
|
|
# ========================================================================
|
|
# Voting Power Calculation
|
|
# ========================================================================
|
|
|
|
async def calculate_voting_power(
|
|
self,
|
|
actor_user_id: str,
|
|
dao: DaoRead,
|
|
base_power: Optional[Decimal] = None
|
|
) -> Decimal:
|
|
"""
|
|
Calculate voting power based on DAO's governance model
|
|
|
|
Args:
|
|
actor_user_id: User ID
|
|
dao: DAO configuration
|
|
base_power: Base voting power (e.g., token balance)
|
|
|
|
Returns:
|
|
Calculated voting weight
|
|
"""
|
|
if dao.governance_model == "simple":
|
|
return await self.calculate_voting_power_simple(actor_user_id, dao)
|
|
elif dao.governance_model == "quadratic":
|
|
return await self.calculate_voting_power_quadratic(actor_user_id, dao, base_power or Decimal('1'))
|
|
elif dao.governance_model == "delegated":
|
|
return await self.calculate_voting_power_delegated(actor_user_id, dao)
|
|
else:
|
|
# Default to simple
|
|
return Decimal('1')
|
|
|
|
async def calculate_voting_power_simple(
|
|
self,
|
|
actor_user_id: str,
|
|
dao: DaoRead
|
|
) -> Decimal:
|
|
"""
|
|
Simple voting: 1 user = 1 vote
|
|
|
|
Returns:
|
|
Always returns 1
|
|
"""
|
|
return Decimal('1')
|
|
|
|
async def calculate_voting_power_quadratic(
|
|
self,
|
|
actor_user_id: str,
|
|
dao: DaoRead,
|
|
base_power: Decimal
|
|
) -> Decimal:
|
|
"""
|
|
Quadratic voting: weight = sqrt(base_power)
|
|
Reduces influence of whales
|
|
|
|
Args:
|
|
actor_user_id: User ID
|
|
dao: DAO configuration
|
|
base_power: Base voting power (e.g., token count)
|
|
|
|
Returns:
|
|
Square root of base power
|
|
"""
|
|
if base_power <= 0:
|
|
return Decimal('0')
|
|
|
|
# Calculate square root using Decimal
|
|
return base_power.sqrt()
|
|
|
|
async def calculate_voting_power_delegated(
|
|
self,
|
|
actor_user_id: str,
|
|
dao: DaoRead
|
|
) -> Decimal:
|
|
"""
|
|
Delegated voting: includes delegated votes from other users
|
|
|
|
TODO: Implement delegation graph traversal
|
|
For MVP, return simple power
|
|
|
|
Args:
|
|
actor_user_id: User ID
|
|
dao: DAO configuration
|
|
|
|
Returns:
|
|
Voting power including delegated votes
|
|
"""
|
|
# For MVP, return simple power
|
|
# In production, would query delegation graph
|
|
return Decimal('1')
|
|
|
|
# ========================================================================
|
|
# Proposal Evaluation
|
|
# ========================================================================
|
|
|
|
async def evaluate_proposal(
|
|
self,
|
|
dao: DaoRead,
|
|
proposal: ProposalRead,
|
|
votes: List[VoteRead],
|
|
total_eligible_voters: int
|
|
) -> ProposalResult:
|
|
"""
|
|
Evaluate proposal outcome based on votes and DAO configuration
|
|
|
|
Args:
|
|
dao: DAO configuration
|
|
proposal: Proposal being evaluated
|
|
votes: List of all votes
|
|
total_eligible_voters: Total number of DAO members eligible to vote
|
|
|
|
Returns:
|
|
ProposalResult with evaluation outcome
|
|
"""
|
|
# Calculate vote counts
|
|
votes_yes = len([v for v in votes if v.vote_value == 'yes'])
|
|
votes_no = len([v for v in votes if v.vote_value == 'no'])
|
|
votes_abstain = len([v for v in votes if v.vote_value == 'abstain'])
|
|
|
|
# Calculate vote weights
|
|
total_weight_yes = sum([v.weight for v in votes if v.vote_value == 'yes'], Decimal('0'))
|
|
total_weight_no = sum([v.weight for v in votes if v.vote_value == 'no'], Decimal('0'))
|
|
total_weight_abstain = sum([v.weight for v in votes if v.vote_value == 'abstain'], Decimal('0'))
|
|
|
|
# Total participating voters
|
|
total_participating = votes_yes + votes_no + votes_abstain
|
|
|
|
# Get quorum requirement (from proposal override or DAO default)
|
|
quorum_percent = proposal.quorum_percent_override or dao.quorum_percent
|
|
quorum_required = Decimal(str(quorum_percent))
|
|
|
|
# Calculate participation rate
|
|
if total_eligible_voters > 0:
|
|
participation_rate = Decimal(str(total_participating)) / Decimal(str(total_eligible_voters)) * Decimal('100')
|
|
else:
|
|
participation_rate = Decimal('0')
|
|
|
|
# Check if quorum is reached
|
|
quorum_reached = participation_rate >= quorum_required
|
|
|
|
# Determine if proposal passed
|
|
is_passed = False
|
|
winning_option = None
|
|
|
|
if quorum_reached:
|
|
# Simple majority: yes must be greater than no
|
|
if total_weight_yes > total_weight_no:
|
|
is_passed = True
|
|
winning_option = "yes"
|
|
elif total_weight_no > total_weight_yes:
|
|
winning_option = "no"
|
|
# If equal, proposal fails (no action taken)
|
|
|
|
# Determine status
|
|
if proposal.status == "active":
|
|
# Proposal is still active, don't change status
|
|
status = "active"
|
|
elif not quorum_reached:
|
|
status = "rejected" # Failed due to quorum
|
|
elif is_passed:
|
|
status = "passed"
|
|
else:
|
|
status = "rejected"
|
|
|
|
return ProposalResult(
|
|
proposal_id=proposal.id,
|
|
status=status,
|
|
votes_yes=votes_yes,
|
|
votes_no=votes_no,
|
|
votes_abstain=votes_abstain,
|
|
total_weight_yes=total_weight_yes,
|
|
total_weight_no=total_weight_no,
|
|
total_weight_abstain=total_weight_abstain,
|
|
total_eligible_voters=total_eligible_voters,
|
|
quorum_required=quorum_required,
|
|
quorum_reached=quorum_reached,
|
|
is_passed=is_passed,
|
|
winning_option=winning_option
|
|
)
|
|
|
|
# ========================================================================
|
|
# Helper Methods
|
|
# ========================================================================
|
|
|
|
def calculate_participation_rate(
|
|
self,
|
|
total_votes: int,
|
|
total_eligible: int
|
|
) -> Decimal:
|
|
"""Calculate participation rate as percentage"""
|
|
if total_eligible == 0:
|
|
return Decimal('0')
|
|
return Decimal(str(total_votes)) / Decimal(str(total_eligible)) * Decimal('100')
|
|
|