|
|
""" |
|
|
Award Identification Agent for SPARKNET |
|
|
|
|
|
AI-powered funding opportunity discovery and award nomination assistance. |
|
|
Part of Scenario 4: Award Identification. |
|
|
|
|
|
FEATURES: |
|
|
--------- |
|
|
1. OPPORTUNITY DISCOVERY: |
|
|
- Scan funding databases and announcements |
|
|
- Match opportunities to research capabilities |
|
|
- Track application deadlines |
|
|
|
|
|
2. NOMINATION ASSISTANCE: |
|
|
- Prepare award nomination documents |
|
|
- Review and validate submissions |
|
|
- Generate supporting materials |
|
|
|
|
|
3. APPLICATION SUPPORT: |
|
|
- Document preparation workflows |
|
|
- Compliance checking |
|
|
- Reviewer matching |
|
|
|
|
|
INTEGRATIONS (Planned): |
|
|
----------------------- |
|
|
- Horizon Europe CORDIS database |
|
|
- National funding agency APIs |
|
|
- ERC portal integration |
|
|
- Patent databases for innovation evidence |
|
|
|
|
|
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 OpportunityType(str, Enum): |
|
|
"""Type of funding opportunity.""" |
|
|
GRANT = "grant" |
|
|
AWARD = "award" |
|
|
FELLOWSHIP = "fellowship" |
|
|
PRIZE = "prize" |
|
|
INVESTMENT = "investment" |
|
|
PARTNERSHIP = "partnership" |
|
|
|
|
|
|
|
|
class OpportunityStatus(str, Enum): |
|
|
"""Opportunity tracking status.""" |
|
|
IDENTIFIED = "identified" |
|
|
EVALUATING = "evaluating" |
|
|
PREPARING = "preparing" |
|
|
SUBMITTED = "submitted" |
|
|
AWARDED = "awarded" |
|
|
REJECTED = "rejected" |
|
|
EXPIRED = "expired" |
|
|
|
|
|
|
|
|
class EligibilityStatus(str, Enum): |
|
|
"""Eligibility assessment status.""" |
|
|
ELIGIBLE = "eligible" |
|
|
INELIGIBLE = "ineligible" |
|
|
PARTIAL = "partial" |
|
|
UNKNOWN = "unknown" |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class FundingOpportunity: |
|
|
""" |
|
|
Funding opportunity data model. |
|
|
|
|
|
Represents a grant, award, or other funding opportunity |
|
|
identified by the scanning system. |
|
|
""" |
|
|
opportunity_id: str |
|
|
title: str |
|
|
description: str |
|
|
opportunity_type: OpportunityType |
|
|
funder: str |
|
|
funder_type: str |
|
|
amount_min: Optional[float] = None |
|
|
amount_max: Optional[float] = None |
|
|
currency: str = "EUR" |
|
|
deadline: Optional[date] = None |
|
|
url: Optional[str] = None |
|
|
eligibility_criteria: List[str] = field(default_factory=list) |
|
|
keywords: List[str] = field(default_factory=list) |
|
|
status: OpportunityStatus = OpportunityStatus.IDENTIFIED |
|
|
match_score: Optional[float] = None |
|
|
notes: Optional[str] = None |
|
|
metadata: Dict[str, Any] = field(default_factory=dict) |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class OpportunityMatch: |
|
|
""" |
|
|
Match between opportunity and research/technology. |
|
|
|
|
|
Represents alignment between a funding opportunity |
|
|
and institutional capabilities. |
|
|
""" |
|
|
match_id: str |
|
|
opportunity_id: str |
|
|
technology_id: Optional[str] = None |
|
|
research_area: Optional[str] = None |
|
|
match_score: float = 0.0 |
|
|
match_rationale: str = "" |
|
|
eligibility_status: EligibilityStatus = EligibilityStatus.UNKNOWN |
|
|
eligibility_notes: List[str] = field(default_factory=list) |
|
|
recommended_action: str = "" |
|
|
confidence_score: float = 0.0 |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class NominationDocument: |
|
|
""" |
|
|
Award nomination document. |
|
|
|
|
|
Contains structured content for award/grant applications. |
|
|
""" |
|
|
document_id: str |
|
|
opportunity_id: str |
|
|
document_type: str |
|
|
title: str |
|
|
content: str |
|
|
version: str = "1.0" |
|
|
status: str = "draft" |
|
|
created_at: datetime = field(default_factory=datetime.now) |
|
|
updated_at: datetime = field(default_factory=datetime.now) |
|
|
created_by: Optional[str] = None |
|
|
reviewer_comments: List[Dict[str, Any]] = field(default_factory=list) |
|
|
critic_validation: Optional[Dict[str, Any]] = None |
|
|
|
|
|
|
|
|
class AwardIdentificationAgent: |
|
|
""" |
|
|
Agent for identifying funding opportunities and assisting nominations. |
|
|
|
|
|
This agent: |
|
|
- Scans funding databases for opportunities |
|
|
- Matches opportunities to research capabilities |
|
|
- Assists with nomination document preparation |
|
|
- Tracks application deadlines and status |
|
|
|
|
|
HUMAN-IN-THE-LOOP WORKFLOW: |
|
|
--------------------------- |
|
|
Award applications require human judgment. This agent implements: |
|
|
|
|
|
1. AUTOMATED SCANNING: |
|
|
- Regular scans of funding databases |
|
|
- Keyword matching and filtering |
|
|
- Initial eligibility screening |
|
|
|
|
|
2. AI-ASSISTED MATCHING: |
|
|
- Score opportunities against capabilities |
|
|
- Generate match rationale |
|
|
- Identify gaps and risks |
|
|
|
|
|
3. HUMAN DECISION POINTS: |
|
|
- Approval to pursue opportunities |
|
|
- Review of application documents |
|
|
- Final submission authorization |
|
|
|
|
|
4. QUALITY ASSURANCE: |
|
|
- CriticAgent validation of documents |
|
|
- Compliance checking |
|
|
- Reviewer feedback integration |
|
|
""" |
|
|
|
|
|
def __init__( |
|
|
self, |
|
|
llm_client: Optional[Any] = None, |
|
|
critic_agent: Optional[Any] = None, |
|
|
database_url: Optional[str] = None, |
|
|
): |
|
|
""" |
|
|
Initialize Award Identification Agent. |
|
|
|
|
|
Args: |
|
|
llm_client: LangChain LLM client for AI analysis |
|
|
critic_agent: CriticAgent for document validation |
|
|
database_url: Database connection URL |
|
|
""" |
|
|
self.llm_client = llm_client |
|
|
self.critic_agent = critic_agent |
|
|
self.database_url = database_url |
|
|
self.name = "AwardIdentificationAgent" |
|
|
self.description = "Funding opportunity discovery and nomination assistance" |
|
|
|
|
|
logger.info(f"Initialized {self.name} (placeholder)") |
|
|
|
|
|
async def scan_opportunities( |
|
|
self, |
|
|
keywords: Optional[List[str]] = None, |
|
|
opportunity_types: Optional[List[OpportunityType]] = None, |
|
|
min_amount: Optional[float] = None, |
|
|
max_deadline_days: Optional[int] = None, |
|
|
) -> List[FundingOpportunity]: |
|
|
""" |
|
|
Scan for funding opportunities matching criteria. |
|
|
|
|
|
Args: |
|
|
keywords: Keywords to search for |
|
|
opportunity_types: Types of opportunities to find |
|
|
min_amount: Minimum funding amount |
|
|
max_deadline_days: Maximum days until deadline |
|
|
|
|
|
Returns: |
|
|
List of matching opportunities |
|
|
|
|
|
TODO: Implement actual opportunity scanning |
|
|
""" |
|
|
logger.info(f"Scanning for opportunities with keywords: {keywords}") |
|
|
|
|
|
|
|
|
return [] |
|
|
|
|
|
async def match_opportunity( |
|
|
self, |
|
|
opportunity_id: str, |
|
|
technology_ids: Optional[List[str]] = None, |
|
|
research_areas: Optional[List[str]] = None, |
|
|
) -> OpportunityMatch: |
|
|
""" |
|
|
Evaluate match between opportunity and capabilities. |
|
|
|
|
|
Args: |
|
|
opportunity_id: Opportunity to evaluate |
|
|
technology_ids: Technologies to consider |
|
|
research_areas: Research areas to consider |
|
|
|
|
|
Returns: |
|
|
Match result with score and rationale |
|
|
|
|
|
TODO: Implement actual matching logic |
|
|
""" |
|
|
logger.info(f"Matching opportunity: {opportunity_id}") |
|
|
|
|
|
|
|
|
return OpportunityMatch( |
|
|
match_id=f"match_{datetime.now().strftime('%Y%m%d_%H%M%S')}", |
|
|
opportunity_id=opportunity_id, |
|
|
match_score=0.0, |
|
|
match_rationale="Matching not yet implemented", |
|
|
eligibility_status=EligibilityStatus.UNKNOWN, |
|
|
recommended_action="Review manually", |
|
|
confidence_score=0.0, |
|
|
) |
|
|
|
|
|
async def check_eligibility( |
|
|
self, |
|
|
opportunity_id: str, |
|
|
applicant_profile: Dict[str, Any], |
|
|
) -> Dict[str, Any]: |
|
|
""" |
|
|
Check eligibility for a funding opportunity. |
|
|
|
|
|
Args: |
|
|
opportunity_id: Opportunity to check |
|
|
applicant_profile: Profile of potential applicant |
|
|
|
|
|
Returns: |
|
|
Eligibility assessment with details |
|
|
|
|
|
TODO: Implement actual eligibility checking |
|
|
""" |
|
|
logger.info(f"Checking eligibility for opportunity: {opportunity_id}") |
|
|
|
|
|
|
|
|
return { |
|
|
"opportunity_id": opportunity_id, |
|
|
"status": EligibilityStatus.UNKNOWN.value, |
|
|
"criteria_met": [], |
|
|
"criteria_not_met": [], |
|
|
"criteria_unknown": [], |
|
|
"recommendation": "Manual review required", |
|
|
"confidence": 0.0, |
|
|
} |
|
|
|
|
|
async def prepare_nomination( |
|
|
self, |
|
|
opportunity_id: str, |
|
|
document_type: str, |
|
|
context: Dict[str, Any], |
|
|
) -> NominationDocument: |
|
|
""" |
|
|
Prepare a nomination/application document. |
|
|
|
|
|
Args: |
|
|
opportunity_id: Target opportunity |
|
|
document_type: Type of document to prepare |
|
|
context: Context information for document generation |
|
|
|
|
|
Returns: |
|
|
Generated nomination document |
|
|
|
|
|
TODO: Implement actual document preparation with LLM |
|
|
""" |
|
|
logger.info(f"Preparing {document_type} for opportunity: {opportunity_id}") |
|
|
|
|
|
|
|
|
return NominationDocument( |
|
|
document_id=f"doc_{datetime.now().strftime('%Y%m%d_%H%M%S')}", |
|
|
opportunity_id=opportunity_id, |
|
|
document_type=document_type, |
|
|
title=f"{document_type.replace('_', ' ').title()} - Draft", |
|
|
content="[Document content to be generated]", |
|
|
status="draft", |
|
|
) |
|
|
|
|
|
async def validate_document( |
|
|
self, |
|
|
document: NominationDocument, |
|
|
) -> Dict[str, Any]: |
|
|
""" |
|
|
Validate nomination document using CriticAgent. |
|
|
|
|
|
Args: |
|
|
document: Document to validate |
|
|
|
|
|
Returns: |
|
|
Validation result with suggestions |
|
|
|
|
|
TODO: Implement CriticAgent integration |
|
|
""" |
|
|
logger.info(f"Validating document: {document.document_id}") |
|
|
|
|
|
|
|
|
return { |
|
|
"document_id": document.document_id, |
|
|
"valid": False, |
|
|
"overall_score": 0.0, |
|
|
"dimension_scores": {}, |
|
|
"issues": ["Validation not yet implemented"], |
|
|
"suggestions": ["Complete document implementation"], |
|
|
"human_review_required": True, |
|
|
} |
|
|
|
|
|
async def get_upcoming_deadlines( |
|
|
self, |
|
|
days_ahead: int = 30, |
|
|
) -> List[FundingOpportunity]: |
|
|
""" |
|
|
Get opportunities with upcoming deadlines. |
|
|
|
|
|
Args: |
|
|
days_ahead: Number of days to look ahead |
|
|
|
|
|
Returns: |
|
|
List of opportunities with deadlines |
|
|
|
|
|
TODO: Implement actual deadline tracking |
|
|
""" |
|
|
logger.info(f"Getting deadlines for next {days_ahead} days") |
|
|
|
|
|
|
|
|
return [] |
|
|
|
|
|
def get_vista_quality_criteria(self) -> Dict[str, Any]: |
|
|
""" |
|
|
Get VISTA quality criteria for award identification. |
|
|
|
|
|
Returns quality thresholds for opportunity matching and |
|
|
document preparation aligned with VISTA objectives. |
|
|
""" |
|
|
return { |
|
|
"opportunity_relevance": { |
|
|
"weight": 0.30, |
|
|
"threshold": 0.75, |
|
|
"description": "Opportunities must be relevant to research capabilities", |
|
|
}, |
|
|
"eligibility_accuracy": { |
|
|
"weight": 0.25, |
|
|
"threshold": 0.90, |
|
|
"description": "Eligibility assessments must be accurate", |
|
|
}, |
|
|
"document_quality": { |
|
|
"weight": 0.25, |
|
|
"threshold": 0.85, |
|
|
"description": "Nomination documents must meet quality standards", |
|
|
}, |
|
|
"deadline_tracking": { |
|
|
"weight": 0.20, |
|
|
"threshold": 0.95, |
|
|
"description": "Deadlines must be tracked accurately", |
|
|
}, |
|
|
} |
|
|
|