"""Data models for Phase 3: Multi-persona influence and opinion dynamics""" from typing import List, Dict, Optional, Any from pydantic import BaseModel, Field from enum import Enum class OpinionPosition(str, Enum): """Opinion position on a proposal""" STRONGLY_SUPPORT = "strongly_support" SUPPORT = "support" LEAN_SUPPORT = "lean_support" NEUTRAL = "neutral" LEAN_OPPOSE = "lean_oppose" OPPOSE = "oppose" STRONGLY_OPPOSE = "strongly_oppose" @property def score(self) -> float: """Convert position to numeric score (-3 to +3)""" scores = { "strongly_oppose": -3, "oppose": -2, "lean_oppose": -1, "neutral": 0, "lean_support": 1, "support": 2, "strongly_support": 3, } return scores[self.value] @classmethod def from_score(cls, score: float) -> "OpinionPosition": """Convert numeric score to position""" if score >= 2.5: return cls.STRONGLY_SUPPORT elif score >= 1.5: return cls.SUPPORT elif score >= 0.5: return cls.LEAN_SUPPORT elif score > -0.5: return cls.NEUTRAL elif score > -1.5: return cls.LEAN_OPPOSE elif score > -2.5: return cls.OPPOSE else: return cls.STRONGLY_OPPOSE class PersonaOpinion(BaseModel): """A persona's opinion at a specific round""" persona_id: str persona_name: str round_number: int position: OpinionPosition position_score: float = Field(..., ge=-3, le=3) response_text: str key_arguments: List[str] = Field(default_factory=list) confidence: float = Field(default=0.5, ge=0, le=1) influenced_by: List[str] = Field( default_factory=list, description="Persona IDs that influenced this opinion" ) position_change: Optional[float] = Field( default=None, description="Change in position score from previous round" ) class InfluenceWeight(BaseModel): """Influence weight from one persona to another""" influencer_id: str influenced_id: str weight: float = Field(..., ge=0, le=1, description="Influence strength 0-1") factors: Dict[str, float] = Field( default_factory=dict, description="Breakdown of influence factors" ) class Config: json_schema_extra = { "example": { "influencer_id": "sarah_chen", "influenced_id": "david_kim", "weight": 0.65, "factors": { "shared_values": 0.3, "expertise_credibility": 0.8, "political_alignment": 0.4, } } } class RoundResult(BaseModel): """Results from one round of opinion dynamics""" round_number: int opinions: List[PersonaOpinion] average_position: float position_variance: float total_change: float = Field( ..., description="Sum of absolute position changes from previous round" ) convergence_metric: float = Field( ..., ge=0, le=1, description="How much opinions converged (1 = no change)" ) clusters: List[List[str]] = Field( default_factory=list, description="Groups of personas with similar positions" ) class EquilibriumState(BaseModel): """Final equilibrium state of the opinion system""" reached_equilibrium: bool total_rounds: int final_opinions: List[PersonaOpinion] # Consensus metrics consensus_strength: float = Field( ..., ge=0, le=1, description="How much agreement exists (1 = full consensus)" ) majority_position: OpinionPosition majority_percentage: float # Opinion distribution position_distribution: Dict[str, int] = Field( default_factory=dict, description="Count of personas at each position" ) # Clusters opinion_clusters: List[Dict[str, Any]] = Field( default_factory=list, description="Stable opinion clusters with members" ) # Opinion leaders opinion_leaders: List[Dict[str, Any]] = Field( default_factory=list, description="Personas with highest influence on final state" ) # Evolution metrics evolution_timeline: List[RoundResult] = Field( default_factory=list, description="Complete history of opinion evolution" ) class Config: json_schema_extra = { "example": { "reached_equilibrium": True, "total_rounds": 5, "consensus_strength": 0.72, "majority_position": "support", "majority_percentage": 66.7, "position_distribution": { "support": 4, "neutral": 1, "oppose": 1 } } } class NetworkMetrics(BaseModel): """Network analysis metrics for influence graph""" centrality_scores: Dict[str, float] = Field( default_factory=dict, description="Influence centrality for each persona" ) clustering_coefficient: float = Field( ..., ge=0, le=1, description="How clustered the influence network is" ) average_influence: float = Field( ..., ge=0, le=1, description="Average influence weight in network" ) influence_clusters: List[List[str]] = Field( default_factory=list, description="Groups of personas who influence each other" )