european-parliament-api
SKILL.md
European Parliament API Skill
Purpose
This skill provides integration guidance for the European Parliament's open data APIs, enabling the CIA platform to perform cross-parliament analysis between the Swedish Riksdag and the EU Parliament. It covers MEP data retrieval, plenary voting records, committee information, and legislative document tracking.
When to Use This Skill
Apply this skill when:
- ✅ Integrating European Parliament data into the CIA platform
- ✅ Building cross-parliament comparison features (Riksdag vs EU)
- ✅ Retrieving MEP profiles and voting records
- ✅ Tracking EU legislative procedures relevant to Sweden
- ✅ Analyzing Swedish MEP participation in EU committees
- ✅ Building data import pipelines for EU Parliament data
Do NOT use for:
- ❌ Swedish Riksdag API integration (use existing riksdagen modules)
- ❌ World Bank or election authority APIs (use existing modules)
- ❌ Generic REST API design patterns
European Parliament Data Sources
EU Parliament Open Data Ecosystem
│
├─ European Parliament Open Data Portal
│ ├─ URL: https://data.europarl.europa.eu/
│ ├─ Format: RDF/SPARQL, JSON-LD, CSV
│ └─ License: CC BY 4.0
│
├─ Plenary Data (Votes, Debates)
│ ├─ Roll-call votes by session
│ ├─ Debate transcripts
│ └─ Attendance records
│
├─ MEP Data
│ ├─ Current and former MEPs
│ ├─ Political group membership
│ ├─ Committee assignments
│ └─ Contact information
│
├─ Legislative Data
│ ├─ Legislative procedures (COD, CNS, APP)
│ ├─ Committee reports and opinions
│ ├─ Amendments and resolutions
│ └─ Interinstitutional negotiations
│
└─ Parliamentary Questions
├─ Written questions
├─ Oral questions
└─ Priority questions
API Integration Patterns
REST API Client
@Service
public class EuropeanParliamentApiClient {
private static final String BASE_URL = "https://data.europarl.europa.eu/api/v2";
private static final Duration TIMEOUT = Duration.ofSeconds(30);
private static final int MAX_RETRIES = 3;
private final RestTemplate restTemplate;
public EuropeanParliamentApiClient(RestTemplateBuilder builder) {
this.restTemplate = builder
.setConnectTimeout(TIMEOUT)
.setReadTimeout(TIMEOUT)
.defaultHeader("Accept", "application/json")
.build();
}
/**
* Retrieve current MEPs with retry logic.
*/
public List<MepData> getCurrentMeps() {
String url = BASE_URL + "/meps?filter=current&format=json";
return executeWithRetry(() -> {
ResponseEntity<MepListResponse> response =
restTemplate.getForEntity(url, MepListResponse.class);
return response.getBody().getMeps();
});
}
/**
* Retrieve voting records for a specific plenary session.
*/
public List<VoteRecord> getPlenaryVotes(String sessionId) {
String url = BASE_URL + "/plenary-sessions/" + sessionId + "/votes";
return executeWithRetry(() -> {
ResponseEntity<VoteListResponse> response =
restTemplate.getForEntity(url, VoteListResponse.class);
return response.getBody().getVotes();
});
}
private <T> T executeWithRetry(Supplier<T> operation) {
int attempts = 0;
while (attempts < MAX_RETRIES) {
try {
return operation.get();
} catch (RestClientException e) {
attempts++;
if (attempts >= MAX_RETRIES) throw e;
try {
// Use TimeUnit for clarity; consider ScheduledExecutorService for non-blocking retries
TimeUnit.SECONDS.sleep((long) Math.pow(2, attempts));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
}
throw new RuntimeException("Max retries exceeded");
}
}
Data Model for EU Parliament
@Entity
@Table(name = "eu_mep_data")
public class MepData {
@Id
@Column(name = "mep_id")
private String mepId;
@Column(name = "full_name")
private String fullName;
@Column(name = "country")
private String country; // "SE" for Swedish MEPs
@Column(name = "political_group")
private String politicalGroup;
@Column(name = "national_party")
private String nationalParty;
@Column(name = "active")
private boolean active;
@Column(name = "start_date")
private LocalDate startDate;
@Column(name = "end_date")
private LocalDate endDate;
}
@Entity
@Table(name = "eu_vote_record")
public class EuVoteRecord {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "session_id")
private String sessionId;
@Column(name = "vote_date")
private LocalDate voteDate;
@Column(name = "subject")
private String subject;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "mep_id")
private MepData mep;
@Enumerated(EnumType.STRING)
@Column(name = "vote_position")
private VotePosition position; // FOR, AGAINST, ABSTAIN, ABSENT
}
Swedish MEP Analysis
Cross-Parliament Comparison
Swedish MEP Analysis Dimensions
│
├─ Voting Alignment
│ ├─ Swedish MEP votes vs national party line
│ ├─ Swedish MEP votes vs EU political group
│ └─ Deviation analysis (party loyalty metric)
│
├─ Committee Participation
│ ├─ Swedish MEP committee assignments
│ ├─ Report authorship frequency
│ ├─ Amendment submission rate
│ └─ Attendance in committee meetings
│
├─ Legislative Influence
│ ├─ Rapporteur appointments
│ ├─ Shadow rapporteur roles
│ ├─ Negotiation mandates
│ └─ Successful amendments ratio
│
└─ Cross-Parliament Correlation
├─ EU legislation impact on Swedish law
├─ Swedish Riksdag debates on EU matters
├─ Transposition compliance tracking
└─ Swedish positions in Council vs MEP votes
Query Pattern for Swedish MEPs
@Repository
public interface MepRepository extends JpaRepository<MepData, String> {
// Find all Swedish MEPs (current)
@Query("SELECT m FROM MepData m WHERE m.country = 'SE' AND m.active = true")
List<MepData> findActiveSwedishMeps();
// Find Swedish MEPs by national party
@Query("SELECT m FROM MepData m WHERE m.country = 'SE' " +
"AND m.nationalParty = :party AND m.active = true")
List<MepData> findSwedishMepsByParty(@Param("party") String party);
// Voting record for a specific MEP
@Query("SELECT v FROM EuVoteRecord v WHERE v.mep.mepId = :mepId " +
"AND v.voteDate BETWEEN :startDate AND :endDate " +
"ORDER BY v.voteDate DESC")
List<EuVoteRecord> findVotesByMepAndPeriod(
@Param("mepId") String mepId,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
}
Data Import Pipeline
Scheduled Import Service
@Service
public class EuParliamentImportService {
private static final Logger LOG =
LoggerFactory.getLogger(EuParliamentImportService.class);
@Autowired
private EuropeanParliamentApiClient apiClient;
@Autowired
private MepRepository mepRepository;
/**
* Import MEP data daily at 02:00 UTC.
* EU Parliament data updates are typically published overnight.
*/
@Scheduled(cron = "0 0 2 * * *")
@Transactional
public void importMepData() {
LOG.info("Starting EU Parliament MEP data import");
try {
List<MepData> meps = apiClient.getCurrentMeps();
int imported = 0;
for (MepData mep : meps) {
mepRepository.save(mep);
imported++;
}
LOG.info("Imported {} MEP records", imported);
} catch (Exception e) {
LOG.error("MEP import failed", e);
// Alert via CloudWatch alarm
}
}
}
Error Handling and Resilience
// Circuit breaker for EU Parliament API
@Service
public class ResilientEuParliamentClient {
private final AtomicInteger failureCount = new AtomicInteger(0);
private static final int FAILURE_THRESHOLD = 5;
private volatile Instant circuitOpenedAt;
public <T> Optional<T> callWithCircuitBreaker(Supplier<T> apiCall) {
if (isCircuitOpen()) {
LOG.warn("Circuit breaker OPEN for EU Parliament API");
return Optional.empty();
}
try {
T result = apiCall.get();
failureCount.set(0); // Reset on success
return Optional.of(result);
} catch (Exception e) {
if (failureCount.incrementAndGet() >= FAILURE_THRESHOLD) {
circuitOpenedAt = Instant.now();
LOG.error("Circuit breaker OPENED after {} failures", FAILURE_THRESHOLD);
}
return Optional.empty();
}
}
private boolean isCircuitOpen() {
if (circuitOpenedAt == null) return false;
// Auto-reset after 5 minutes
return Duration.between(circuitOpenedAt, Instant.now()).toMinutes() < 5;
}
}
Data Caching Strategy
| Data Type | Cache Duration | Reason |
|---|---|---|
| MEP profiles | 24 hours | Changes rarely, daily import |
| Plenary votes | 1 week | Immutable after session ends |
| Committee data | 12 hours | Updates with meeting schedules |
| Legislative docs | 6 hours | Active procedures change frequently |
| Session calendar | 1 month | Published well in advance |
GDPR Considerations
EU Parliament Data and GDPR:
├─ MEP public data: Legitimate interest (public figure, Art. 6(1)(f))
├─ Voting records: Public document (Art. 6(1)(e))
├─ Contact details: Only official/public information
├─ Personal data: Never collect private MEP data
└─ Data retention: Archive per parliamentary term
Integration with Existing CIA Modules
Module Integration Points
│
├─ model.external.riksdagen → Cross-reference Swedish party data
├─ service.external.riksdagen → Compare Riksdag votes with EU votes
├─ service.data.api → Shared data access patterns
├─ web-widgets → EU Parliament views and dashboards
└─ citizen-intelligence-agency → Configuration and routing
References
Weekly Installs
4
Repository
hack23/ciaGitHub Stars
213
First Seen
12 days ago
Security Audits
Installed on
opencode4
gemini-cli4
claude-code4
github-copilot4
codex4
amp4