|
|
""" |
|
|
Opportunity Scanner for Award Identification |
|
|
|
|
|
Scans funding databases and announcement sources for opportunities. |
|
|
|
|
|
PLANNED DATA SOURCES: |
|
|
--------------------- |
|
|
- Horizon Europe / CORDIS |
|
|
- European Research Council (ERC) |
|
|
- National funding agencies (DFG, ANR, UKRI, etc.) |
|
|
- Foundation databases |
|
|
- Corporate R&D partnerships |
|
|
- Innovation prizes and awards |
|
|
|
|
|
SCANNING STRATEGY: |
|
|
----------------- |
|
|
1. KEYWORD MATCHING: |
|
|
- Technology-specific terms |
|
|
- Research area keywords |
|
|
- Institution eligibility terms |
|
|
|
|
|
2. SEMANTIC SEARCH: |
|
|
- Vector similarity to capability descriptions |
|
|
- Cross-lingual matching for EU opportunities |
|
|
|
|
|
3. FILTERING: |
|
|
- Deadline filtering (exclude expired) |
|
|
- Amount thresholds |
|
|
- Eligibility pre-screening |
|
|
|
|
|
Author: SPARKNET Team |
|
|
Project: VISTA/Horizon EU |
|
|
Status: Placeholder - In Development |
|
|
""" |
|
|
|
|
|
from typing import Optional, Dict, Any, List |
|
|
from dataclasses import dataclass, field |
|
|
from datetime import datetime, date |
|
|
from enum import Enum |
|
|
from loguru import logger |
|
|
|
|
|
|
|
|
class DataSource(str, Enum): |
|
|
"""Funding data sources.""" |
|
|
CORDIS = "cordis" |
|
|
ERC = "erc" |
|
|
NATIONAL = "national" |
|
|
FOUNDATION = "foundation" |
|
|
CORPORATE = "corporate" |
|
|
CUSTOM = "custom" |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class ScanConfiguration: |
|
|
""" |
|
|
Configuration for opportunity scanning. |
|
|
|
|
|
Defines what and how to scan for opportunities. |
|
|
""" |
|
|
config_id: str |
|
|
name: str |
|
|
sources: List[DataSource] |
|
|
keywords: List[str] |
|
|
research_areas: List[str] |
|
|
min_amount: Optional[float] = None |
|
|
max_amount: Optional[float] = None |
|
|
currency: str = "EUR" |
|
|
exclude_expired: bool = True |
|
|
include_rolling: bool = True |
|
|
scan_frequency_hours: int = 24 |
|
|
last_scan: Optional[datetime] = None |
|
|
enabled: bool = True |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class ScanResult: |
|
|
""" |
|
|
Result of an opportunity scan. |
|
|
|
|
|
Contains discovered opportunities and scan metadata. |
|
|
""" |
|
|
scan_id: str |
|
|
config_id: str |
|
|
started_at: datetime |
|
|
completed_at: Optional[datetime] = None |
|
|
sources_scanned: List[str] = field(default_factory=list) |
|
|
opportunities_found: int = 0 |
|
|
new_opportunities: int = 0 |
|
|
updated_opportunities: int = 0 |
|
|
errors: List[str] = field(default_factory=list) |
|
|
status: str = "in_progress" |
|
|
|
|
|
|
|
|
class OpportunityScanner: |
|
|
""" |
|
|
Scans funding databases for opportunities. |
|
|
|
|
|
This component: |
|
|
- Connects to funding data sources |
|
|
- Runs periodic scans |
|
|
- Identifies new opportunities |
|
|
- Updates existing opportunity data |
|
|
|
|
|
INTEGRATION NOTES: |
|
|
----------------- |
|
|
For production deployment, integrate with: |
|
|
|
|
|
1. HORIZON EUROPE (CORDIS): |
|
|
- Use CORDIS API for call announcements |
|
|
- Parse work programme documents |
|
|
- Track topic deadlines |
|
|
|
|
|
2. NATIONAL AGENCIES: |
|
|
- DFG (Germany): RSS feeds |
|
|
- ANR (France): Open data portal |
|
|
- UKRI (UK): Gateway API |
|
|
|
|
|
3. FOUNDATIONS: |
|
|
- Scrape foundation websites |
|
|
- Monitor RSS/newsletter feeds |
|
|
- Parse PDF announcements |
|
|
|
|
|
4. CUSTOM SOURCES: |
|
|
- Support for institution-specific sources |
|
|
- Private funding networks |
|
|
- Industry partnership programs |
|
|
""" |
|
|
|
|
|
def __init__( |
|
|
self, |
|
|
database_url: Optional[str] = None, |
|
|
embedding_client: Optional[Any] = None, |
|
|
): |
|
|
""" |
|
|
Initialize Opportunity Scanner. |
|
|
|
|
|
Args: |
|
|
database_url: Database for storing opportunities |
|
|
embedding_client: Client for semantic search embeddings |
|
|
""" |
|
|
self.database_url = database_url |
|
|
self.embedding_client = embedding_client |
|
|
self.name = "OpportunityScanner" |
|
|
|
|
|
|
|
|
self.configurations: Dict[str, ScanConfiguration] = {} |
|
|
|
|
|
logger.info(f"Initialized {self.name} (placeholder)") |
|
|
|
|
|
async def register_configuration( |
|
|
self, |
|
|
config: ScanConfiguration, |
|
|
) -> None: |
|
|
""" |
|
|
Register a scan configuration. |
|
|
|
|
|
Args: |
|
|
config: Scan configuration to register |
|
|
""" |
|
|
self.configurations[config.config_id] = config |
|
|
logger.info(f"Registered scan configuration: {config.name}") |
|
|
|
|
|
async def run_scan( |
|
|
self, |
|
|
config_id: Optional[str] = None, |
|
|
) -> ScanResult: |
|
|
""" |
|
|
Run an opportunity scan. |
|
|
|
|
|
Args: |
|
|
config_id: Specific configuration to use (or all if None) |
|
|
|
|
|
Returns: |
|
|
Scan result with discovered opportunities |
|
|
|
|
|
TODO: Implement actual scanning logic |
|
|
""" |
|
|
logger.info(f"Running opportunity scan (config: {config_id or 'all'})") |
|
|
|
|
|
|
|
|
return ScanResult( |
|
|
scan_id=f"scan_{datetime.now().strftime('%Y%m%d_%H%M%S')}", |
|
|
config_id=config_id or "all", |
|
|
started_at=datetime.now(), |
|
|
completed_at=datetime.now(), |
|
|
sources_scanned=[], |
|
|
opportunities_found=0, |
|
|
new_opportunities=0, |
|
|
updated_opportunities=0, |
|
|
status="placeholder", |
|
|
) |
|
|
|
|
|
async def scan_cordis( |
|
|
self, |
|
|
keywords: List[str], |
|
|
) -> List[Dict[str, Any]]: |
|
|
""" |
|
|
Scan CORDIS for Horizon Europe opportunities. |
|
|
|
|
|
Args: |
|
|
keywords: Keywords to search for |
|
|
|
|
|
Returns: |
|
|
List of opportunities from CORDIS |
|
|
|
|
|
TODO: Implement CORDIS API integration |
|
|
""" |
|
|
logger.info(f"Scanning CORDIS with keywords: {keywords}") |
|
|
|
|
|
|
|
|
return [] |
|
|
|
|
|
async def scan_erc( |
|
|
self, |
|
|
research_areas: List[str], |
|
|
) -> List[Dict[str, Any]]: |
|
|
""" |
|
|
Scan ERC for grant opportunities. |
|
|
|
|
|
Args: |
|
|
research_areas: Research areas to match |
|
|
|
|
|
Returns: |
|
|
List of ERC opportunities |
|
|
|
|
|
TODO: Implement ERC portal integration |
|
|
""" |
|
|
logger.info(f"Scanning ERC for research areas: {research_areas}") |
|
|
|
|
|
|
|
|
return [] |
|
|
|
|
|
async def semantic_search( |
|
|
self, |
|
|
query: str, |
|
|
sources: Optional[List[DataSource]] = None, |
|
|
top_k: int = 10, |
|
|
) -> List[Dict[str, Any]]: |
|
|
""" |
|
|
Semantic search for relevant opportunities. |
|
|
|
|
|
Uses vector similarity to find opportunities |
|
|
matching natural language descriptions. |
|
|
|
|
|
Args: |
|
|
query: Natural language query |
|
|
sources: Data sources to search |
|
|
top_k: Number of results to return |
|
|
|
|
|
Returns: |
|
|
List of matching opportunities with scores |
|
|
|
|
|
TODO: Implement embedding-based search |
|
|
""" |
|
|
logger.info(f"Semantic search: {query[:50]}...") |
|
|
|
|
|
|
|
|
return [] |
|
|
|
|
|
async def get_scan_history( |
|
|
self, |
|
|
limit: int = 10, |
|
|
) -> List[ScanResult]: |
|
|
""" |
|
|
Get history of recent scans. |
|
|
|
|
|
Args: |
|
|
limit: Maximum number of results |
|
|
|
|
|
Returns: |
|
|
List of recent scan results |
|
|
|
|
|
TODO: Implement scan history retrieval |
|
|
""" |
|
|
logger.info(f"Getting scan history (limit: {limit})") |
|
|
|
|
|
|
|
|
return [] |
|
|
|