mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
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)
416 lines
13 KiB
Python
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()
|