Update euroswarm_parliament.py

pull/1030/head
CI-DEV 3 weeks ago committed by GitHub
parent c31ee1df38
commit d35fb71d57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -17,6 +17,8 @@ import random
import xml.etree.ElementTree as ET
import time
import hashlib
import requests
import re
from typing import Dict, List, Optional, Union, Any, Set
from dataclasses import dataclass, field
from enum import Enum
@ -138,6 +140,7 @@ class ParliamentaryMember:
voting_weight: Weight of the MEP's vote (default: 1.0)
agent: The AI agent representing this MEP (lazy loaded)
is_loaded: Whether the agent has been instantiated
wikipedia_info: Wikipedia-scraped personality information (optional)
"""
full_name: str
@ -151,6 +154,7 @@ class ParliamentaryMember:
voting_weight: float = 1.0
agent: Optional[Agent] = None
is_loaded: bool = False
wikipedia_info: Optional[Any] = None # Wikipedia personality information
@dataclass
@ -385,7 +389,158 @@ class EuroSwarmParliament:
def _load_mep_data(self) -> Dict[str, ParliamentaryMember]:
"""
Load MEP data from EU.xml file and create parliamentary members with lazy loading.
Load MEP data from official EU Parliament website and create parliamentary members with lazy loading.
Fetches real-time data from https://www.europarl.europa.eu/meps/en/full-list/xml
and scrapes Wikipedia information for each MEP.
Returns:
Dict[str, ParliamentaryMember]: Dictionary of MEPs
"""
meps = {}
try:
# Fetch XML data from official EU Parliament website
import requests
import re
eu_xml_url = "https://www.europarl.europa.eu/meps/en/full-list/xml"
logger.info(f"Fetching MEP data from: {eu_xml_url}")
# Fetch the XML content
response = requests.get(eu_xml_url, timeout=30)
response.raise_for_status()
content = response.text
logger.info(f"Successfully fetched {len(content)} characters of MEP data")
# Parse the XML content to extract MEP information
# The XML is properly formatted, so we can use ElementTree
try:
root = ET.fromstring(content)
mep_matches = []
for mep_element in root.findall('mep'):
full_name = mep_element.find('fullName').text.strip()
country = mep_element.find('country').text.strip()
political_group = mep_element.find('politicalGroup').text.strip()
mep_id = mep_element.find('id').text.strip()
national_party = mep_element.find('nationalPoliticalGroup').text.strip()
mep_matches.append((full_name, country, political_group, mep_id, national_party))
logger.info(f"Successfully parsed {len(mep_matches)} MEP entries from XML")
except ET.ParseError as xml_error:
logger.warning(f"XML parsing failed: {xml_error}")
# Fallback to regex parsing for malformed XML
mep_pattern = r'<fullName>(.*?)</fullName>\s*<country>(.*?)</country>\s*<politicalGroup>(.*?)</politicalGroup>\s*<id>(.*?)</id>\s*<nationalPoliticalGroup>(.*?)</nationalPoliticalGroup>'
mep_matches = re.findall(mep_pattern, content, re.DOTALL)
logger.info(f"Fallback regex parsing found {len(mep_matches)} MEP entries")
# Initialize Wikipedia scraper if available
wikipedia_scraper = None
if WIKIPEDIA_PERSONALITY_AVAILABLE:
try:
wikipedia_scraper = WikipediaPersonalityScraper()
logger.info("Wikipedia personality scraper initialized")
except Exception as e:
logger.warning(f"Failed to initialize Wikipedia scraper: {e}")
# Process each MEP
for i, mep_data in enumerate(mep_matches):
if len(mep_data) >= 5: # full_name, country, political_group, mep_id, national_party
full_name = mep_data[0].strip()
country = mep_data[1].strip()
political_group = mep_data[2].strip()
mep_id = mep_data[3].strip()
national_party = mep_data[4].strip()
# Clean up political group name
political_group = self._clean_political_group_name(political_group)
# Scrape Wikipedia information if scraper is available
wikipedia_info = None
if wikipedia_scraper:
try:
# Create MEP data dictionary for the scraper
mep_data = {
'full_name': full_name,
'country': country,
'political_group': political_group,
'national_party': national_party,
'mep_id': mep_id
}
# Create personality profile
personality_profile = wikipedia_scraper.create_personality_profile(mep_data)
# Convert to dictionary format for storage
wikipedia_info = {
'personality_summary': personality_profile.summary,
'political_views': personality_profile.political_views,
'policy_focus': personality_profile.policy_focus,
'achievements': personality_profile.achievements,
'professional_background': personality_profile.professional_background,
'political_career': personality_profile.political_career,
'education': personality_profile.education,
'wikipedia_url': personality_profile.wikipedia_url
}
if self.verbose:
logger.info(f"Scraped Wikipedia info for {full_name}")
except Exception as e:
if self.verbose:
logger.debug(f"Failed to scrape Wikipedia for {full_name}: {e}")
# Create parliamentary member (without agent for lazy loading)
mep = ParliamentaryMember(
full_name=full_name,
country=country,
political_group=political_group,
national_party=national_party,
mep_id=mep_id,
expertise_areas=self._generate_expertise_areas(political_group, country),
committees=self._assign_committees(political_group),
agent=None, # Will be created on demand
is_loaded=False,
wikipedia_info=wikipedia_info # Add Wikipedia information
)
meps[full_name] = mep
# Limit processing for performance (can be adjusted)
if len(meps) >= 705: # Standard EU Parliament size
break
# Set parliament size to actual number of MEPs loaded
if self.parliament_size is None:
self.parliament_size = len(meps)
logger.info(f"Successfully loaded {len(meps)} MEP profiles from official EU data (lazy loading enabled)")
if wikipedia_scraper:
logger.info(f"Wikipedia scraping completed for {len([m for m in meps.values() if m.wikipedia_info])} MEPs")
except Exception as e:
logger.error(f"Error loading MEP data from official website: {e}")
logger.info("Falling back to local EU.xml file...")
# Fallback to local file
try:
meps = self._load_mep_data_from_local_file()
except Exception as local_error:
logger.error(f"Error loading local MEP data: {local_error}")
# Create fallback MEPs if both methods fail
meps = self._create_fallback_meps()
if self.parliament_size is None:
self.parliament_size = len(meps)
return meps
def _load_mep_data_from_local_file(self) -> Dict[str, ParliamentaryMember]:
"""
Fallback method to load MEP data from local EU.xml file.
Returns:
Dict[str, ParliamentaryMember]: Dictionary of MEPs
@ -432,21 +587,98 @@ class EuroSwarmParliament:
meps[full_name] = mep
# Set parliament size to actual number of MEPs loaded if not specified
if self.parliament_size is None:
self.parliament_size = len(meps)
logger.info(f"Loaded {len(meps)} MEP profiles from EU data (lazy loading enabled)")
logger.info(f"Loaded {len(meps)} MEP profiles from local EU.xml file (lazy loading enabled)")
except Exception as e:
logger.error(f"Error loading MEP data: {e}")
# Create fallback MEPs if file loading fails
meps = self._create_fallback_meps()
if self.parliament_size is None:
self.parliament_size = len(meps)
logger.error(f"Error loading local MEP data: {e}")
raise
return meps
def _clean_political_group_name(self, political_group: str) -> str:
"""
Clean and standardize political group names.
Args:
political_group: Raw political group name
Returns:
str: Cleaned political group name
"""
# Map common variations to standard names
group_mapping = {
'EPP': 'Group of the European People\'s Party (Christian Democrats)',
'S&D': 'Group of the Progressive Alliance of Socialists and Democrats in the European Parliament',
'Renew': 'Renew Europe Group',
'Greens/EFA': 'Group of the Greens/European Free Alliance',
'ECR': 'European Conservatives and Reformists Group',
'ID': 'Identity and Democracy Group',
'GUE/NGL': 'The Left group in the European Parliament - GUE/NGL',
'Non-attached': 'Non-attached Members'
}
# Check for exact matches first
for key, value in group_mapping.items():
if political_group.strip() == key:
return value
# Check for partial matches
political_group_lower = political_group.lower()
for key, value in group_mapping.items():
if key.lower() in political_group_lower:
return value
# Return original if no match found
return political_group.strip()
def _generate_national_party(self, country: str, political_group: str) -> str:
"""
Generate a realistic national party name based on country and political group.
Args:
country: Country of the MEP
political_group: Political group affiliation
Returns:
str: Generated national party name
"""
# Map of countries to common parties for each political group
party_mapping = {
'Germany': {
'Group of the European People\'s Party (Christian Democrats)': 'Christlich Demokratische Union Deutschlands',
'Group of the Progressive Alliance of Socialists and Democrats in the European Parliament': 'Sozialdemokratische Partei Deutschlands',
'Renew Europe Group': 'Freie Demokratische Partei',
'Group of the Greens/European Free Alliance': 'Bündnis 90/Die Grünen',
'European Conservatives and Reformists Group': 'Alternative für Deutschland',
'Identity and Democracy Group': 'Alternative für Deutschland',
'The Left group in the European Parliament - GUE/NGL': 'Die Linke'
},
'France': {
'Group of the European People\'s Party (Christian Democrats)': 'Les Républicains',
'Group of the Progressive Alliance of Socialists and Democrats in the European Parliament': 'Parti Socialiste',
'Renew Europe Group': 'Renaissance',
'Group of the Greens/European Free Alliance': 'Europe Écologie Les Verts',
'European Conservatives and Reformists Group': 'Rassemblement National',
'Identity and Democracy Group': 'Rassemblement National',
'The Left group in the European Parliament - GUE/NGL': 'La France Insoumise'
},
'Italy': {
'Group of the European People\'s Party (Christian Democrats)': 'Forza Italia',
'Group of the Progressive Alliance of Socialists and Democrats in the European Parliament': 'Partito Democratico',
'Renew Europe Group': 'Italia Viva',
'Group of the Greens/European Free Alliance': 'Federazione dei Verdi',
'European Conservatives and Reformists Group': 'Fratelli d\'Italia',
'Identity and Democracy Group': 'Lega',
'The Left group in the European Parliament - GUE/NGL': 'Movimento 5 Stelle'
}
}
# Return mapped party or generate a generic one
if country in party_mapping and political_group in party_mapping[country]:
return party_mapping[country][political_group]
else:
return f"{country} National Party"
def _load_mep_agent(self, mep_name: str) -> Optional[Agent]:
"""
Lazy load a single MEP agent on demand.
@ -684,9 +916,6 @@ class EuroSwarmParliament:
str: System prompt for the MEP agent
"""
# Get Wikipedia personality profile if available
personality_profile = self.get_mep_personality_profile(mep.full_name)
# Base prompt structure
prompt = f"""You are {mep.full_name}, a Member of the European Parliament (MEP) representing {mep.country}.
@ -700,16 +929,16 @@ POLITICAL BACKGROUND:
"""
# Add Wikipedia personality data if available
if personality_profile and self.enable_wikipedia_personalities:
if mep.wikipedia_info and self.enable_wikipedia_personalities:
prompt += f"""
REAL PERSONALITY PROFILE (Based on Wikipedia data):
{self.personality_scraper.get_personality_summary(personality_profile)}
{mep.wikipedia_info.get('personality_summary', 'Based on parliamentary service and political alignment')}
POLITICAL VIEWS AND POSITIONS:
- Key Political Views: {personality_profile.political_views if personality_profile.political_views else 'Based on party alignment'}
- Policy Focus Areas: {personality_profile.policy_focus if personality_profile.policy_focus else ', '.join(mep.expertise_areas)}
- Notable Achievements: {personality_profile.achievements if personality_profile.achievements else 'Parliamentary service'}
- Professional Background: {personality_profile.professional_background if personality_profile.professional_background else 'Political career'}
- Key Political Views: {mep.wikipedia_info.get('political_views', 'Based on party alignment')}
- Policy Focus Areas: {mep.wikipedia_info.get('policy_focus', ', '.join(mep.expertise_areas))}
- Notable Achievements: {mep.wikipedia_info.get('achievements', 'Parliamentary service')}
- Professional Background: {mep.wikipedia_info.get('professional_background', 'Political career')}
"""
else:
@ -753,11 +982,11 @@ COMMUNICATION STYLE:
When responding to parliamentary matters, consider:
1. How does this affect {mep.country} and your constituents?
2. What is the position of {mep.political_group} on this issue?
3. What are the implications for your areas of expertise?
4. How can you contribute constructively to the discussion?
5. What is the best outcome for European citizens?
3. How does this align with your areas of expertise?
4. What are the broader European implications?
5. How can you best represent your constituents' interests?
Remember: You are a democratically elected representative working for the benefit of European citizens while representing {mep.country}'s interests within the European Union framework.
Remember: You are a real MEP with specific political views, expertise, and responsibilities. Act accordingly in all parliamentary interactions.
"""
return prompt
@ -2595,4 +2824,4 @@ Remember: You are a democratically elected representative working for the benefi
analysis["opposition"] = weighted_opposition
analysis["uncertainty"] = weighted_uncertainty
return analysis
return analysis

Loading…
Cancel
Save