turash/scripts/eu_funding_api.py
Damir Mukimov 000eab4740
Major repository reorganization and missing backend endpoints implementation
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools

Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
  * GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
  * GET /api/v1/users/me/organizations - User organizations
  * POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue

API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules

Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
2025-11-25 06:01:16 +01:00

416 lines
13 KiB
Python

#!/usr/bin/env python3
"""
EU Funding & Tenders Portal API Client
This script provides a Python client for accessing the EU Funding & Tenders Portal APIs.
It supports all major services including Grants & Tenders, Topic Details, Grant Updates,
FAQs, Organization data, Partner Search, and Project Results.
Requirements:
- requests
- json
Install dependencies:
pip install requests
Usage:
python eu_funding_api.py
Author: Generated for City Resource Graph project
Date: November 2025
"""
import requests
import json
import sys
from typing import Dict, List, Optional, Any
from datetime import datetime
class EUFundingAPI:
"""EU Funding & Tenders Portal API Client"""
BASE_URL = "https://api.tech.ec.europa.eu/search-api/prod/rest"
SEARCH_API_KEY = "SEDIA"
FAQ_API_KEY = "SEDIA_FAQ"
PERSON_API_KEY = "SEDIA_PERSON"
def __init__(self, timeout: int = 30):
"""Initialize the API client"""
self.session = requests.Session()
self.session.headers.update(
{
"User-Agent": "EU-Funding-API-Client/1.0",
"Accept": "application/json",
"Content-Type": "application/json",
}
)
self.timeout = timeout
def _make_request(
self, endpoint: str, method: str = "GET", data: Optional[Dict] = None
) -> Dict:
"""Make HTTP request to API"""
try:
if method == "POST" and data:
response = self.session.post(endpoint, json=data, timeout=self.timeout)
else:
response = self.session.get(endpoint, timeout=self.timeout)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"API request failed: {e}")
return {}
def _extract_metadata_field(
self, result: Dict, field_name: str, default: Any = "N/A"
) -> Any:
"""
Extract field from metadata array
Args:
result: Result dictionary from API
field_name: Name of the field to extract
default: Default value if field not found
Returns:
Extracted field value or default
"""
metadata = result.get("metadata", {})
field_data = metadata.get(field_name, [])
if isinstance(field_data, list) and len(field_data) > 0:
return field_data[0] # Return first element of array
elif isinstance(field_data, str):
return field_data # Sometimes it's already a string
else:
return default
def _extract_result_data(self, result: Dict) -> Dict:
"""
Extract common fields from a result
Args:
result: Raw result from API
Returns:
Cleaned result data
"""
return {
"title": self._extract_metadata_field(result, "title"),
"identifier": self._extract_metadata_field(result, "identifier"),
"callIdentifier": self._extract_metadata_field(result, "callIdentifier"),
"status": self._extract_metadata_field(result, "status"),
"deadline": self._extract_metadata_field(result, "deadlineDate"),
"description": self._extract_metadata_field(result, "description"),
"type": self._extract_metadata_field(result, "type"),
"frameworkProgramme": self._extract_metadata_field(
result, "frameworkProgramme"
),
"raw_data": result, # Keep original data for debugging
}
def search_grants_tenders(self, query: Optional[Dict] = None) -> Dict:
"""
Search for grants and tenders
Args:
query: Optional search query in Elasticsearch format
Returns:
Search results with processed data
"""
endpoint = f"{self.BASE_URL}/search?apiKey={self.SEARCH_API_KEY}&text=***"
if query is None:
# Default query to get all grants and tenders
query = {
"bool": {
"must": [
{"terms": {"type": ["0", "1", "2", "8"]}}, # All types
{
"terms": {
"status": [
"31094501",
"31094502",
"31094503",
] # All statuses
}
},
]
}
}
raw_results = self._make_request(endpoint, method="POST", data=query)
# Process results to extract metadata fields
if "results" in raw_results:
processed_results = []
for result in raw_results["results"]:
processed_results.append(self._extract_result_data(result))
raw_results["processed_results"] = processed_results
return raw_results
def get_topic_details(self, topic_identifier: str) -> Dict:
"""
Get detailed information about a specific topic
Args:
topic_identifier: Topic identifier (e.g., "HORIZON-EIC-2026-ACCELERATOR-01")
Returns:
Topic details with processed data
"""
query = {"bool": {"must": [{"term": {"identifier": topic_identifier}}]}}
endpoint = f"{self.BASE_URL}/search?apiKey={self.SEARCH_API_KEY}&text=***"
raw_results = self._make_request(endpoint, method="POST", data=query)
# Process results to extract metadata fields
if "results" in raw_results and raw_results["results"]:
processed_results = []
for result in raw_results["results"]:
processed_results.append(self._extract_result_data(result))
raw_results["processed_results"] = processed_results
return raw_results
def search_grant_updates(self, framework_programme: Optional[str] = None) -> Dict:
"""
Search for grant updates
Args:
framework_programme: Optional framework programme code
Returns:
Grant updates
"""
query = {"bool": {"must": [{"terms": {"type": ["6"]}}]}} # Grant updates
if framework_programme:
query["bool"]["must"].append(
{"terms": {"frameworkProgramme": [framework_programme]}}
)
endpoint = f"{self.BASE_URL}/search?apiKey={self.SEARCH_API_KEY}&text=***"
return self._make_request(endpoint, method="POST", data=query)
def search_faqs(self, programme: Optional[str] = None) -> Dict:
"""
Search FAQs
Args:
programme: Optional programme code
Returns:
FAQ results
"""
query = {
"bool": {
"must": [
{"terms": {"type": ["0", "1"]}}, # FAQ types
{"terms": {"status": ["0", "1"]}}, # All statuses
]
}
}
if programme:
query["bool"]["must"].append({"term": {"programme": programme}})
endpoint = f"{self.BASE_URL}/search?apiKey={self.FAQ_API_KEY}&text=***"
return self._make_request(endpoint, method="POST", data=query)
def get_organization_data(self, pic_code: str) -> Dict:
"""
Get organization public data
Args:
pic_code: 9-digit PIC code of the organization
Returns:
Organization data
"""
endpoint = f"{self.BASE_URL}/document/{pic_code}?apiKey={self.PERSON_API_KEY}"
return self._make_request(endpoint)
def search_partners(self, topic: str) -> Dict:
"""
Search for partners in a specific topic
Args:
topic: Topic identifier
Returns:
Partner search results
"""
query = {
"bool": {
"must": [
{"terms": {"topics": [topic]}},
{"terms": {"type": ["ORGANISATION", "PERSON"]}},
]
}
}
endpoint = f"{self.BASE_URL}/search?apiKey={self.SEARCH_API_KEY}&text=***"
return self._make_request(endpoint, method="POST", data=query)
def search_projects(
self, programme_id: Optional[str] = None, mission_group: Optional[str] = None
) -> Dict:
"""
Search for EU funded projects
Args:
programme_id: Programme identifier
mission_group: Mission group identifier
Returns:
Project results
"""
query = {"bool": {"must": []}}
if programme_id:
query["bool"]["must"].append({"terms": {"programId": [programme_id]}})
if mission_group:
query["bool"]["must"].append({"terms": {"missionGroup": [mission_group]}})
endpoint = f"{self.BASE_URL}/search?apiKey={self.SEARCH_API_KEY}&text=***"
return self._make_request(endpoint, method="POST", data=query)
def get_facet_data(self, facet_query: Dict) -> Dict:
"""
Get facet/reference data descriptions
Args:
facet_query: Facet query
Returns:
Facet data
"""
endpoint = f"{self.BASE_URL}/facet?apiKey={self.SEARCH_API_KEY}&text=***"
return self._make_request(endpoint, method="POST", data=facet_query)
def main():
"""Main function demonstrating API usage"""
print("EU Funding & Tenders Portal API Client")
print("=" * 50)
api = EUFundingAPI()
# Example 1: Search for EIC Accelerator opportunities
print("\n1. Searching for EIC Accelerator opportunities...")
eic_query = {
"bool": {
"must": [
{"terms": {"type": ["1", "2", "8"]}}, # Grants
{
"terms": {
"status": ["31094501", "31094502", "31094503"] # All statuses
}
},
{"term": {"callIdentifier": "HORIZON-EIC-2026-ACCELERATOR-01"}},
]
}
}
results = api.search_grants_tenders(eic_query)
if results:
processed_results = results.get("processed_results", [])
print(f"Found {len(processed_results)} EIC Accelerator opportunities")
for result in processed_results[:3]: # Show first 3
title = result.get("title", "N/A")
print(f"- {title}")
else:
print("No results found")
# Example 2: Get topic details for EIC Accelerator
print("\n2. Getting EIC Accelerator topic details...")
topic_details = api.get_topic_details("HORIZON-EIC-2026-ACCELERATOR-01")
if topic_details:
print("Topic details retrieved successfully")
processed_results = topic_details.get("processed_results", [])
if processed_results:
topic = processed_results[0]
print(f"Title: {topic.get('title', 'N/A')}")
print(f"Status: {topic.get('status', 'N/A')}")
print(f"Deadline: {topic.get('deadline', 'N/A')}")
else:
print("Failed to get topic details")
# Example 3: Search for grant updates
print("\n3. Searching for recent grant updates...")
updates = api.search_grant_updates("43108390") # Horizon Europe
if updates:
print(f"Found {len(updates.get('results', []))} grant updates")
else:
print("No grant updates found")
# Example 4: Search FAQs
print("\n4. Searching for FAQs...")
faqs = api.search_faqs()
if faqs:
print(f"Found {len(faqs.get('results', []))} FAQs")
else:
print("No FAQs found")
print("\nAPI client demonstration completed!")
def create_eic_accelerator_monitor():
"""
Create a monitoring script for EIC Accelerator opportunities
This can be run periodically to check for new opportunities
"""
api = EUFundingAPI()
print("EIC Accelerator Monitor")
print("=" * 30)
# Query for EIC Accelerator opportunities
query = {
"bool": {
"must": [
{"terms": {"type": ["1", "2", "8"]}},
{"terms": {"status": ["31094501", "31094502"]}}, # Open and forthcoming
{"term": {"frameworkProgramme": "43108390"}}, # Horizon Europe
{
"query_string": {
"query": "EIC Accelerator",
"default_field": "title",
}
},
]
}
}
results = api.search_grants_tenders(query)
if results and "processed_results" in results:
opportunities = results["processed_results"]
print(f"Found {len(opportunities)} EIC Accelerator opportunities:")
for opp in opportunities:
title = opp.get("title", "N/A")
identifier = opp.get("identifier", "N/A")
status = opp.get("status", "N/A")
deadline = opp.get("deadline", "N/A")
print(f"\n📋 {title}")
print(f" ID: {identifier}")
print(f" Status: {status}")
print(f" Deadline: {deadline}")
else:
print("No EIC Accelerator opportunities found")
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "monitor":
create_eic_accelerator_monitor()
else:
main()