""" 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" # Some criteria met UNKNOWN = "unknown" # Needs review @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 # government, foundation, corporate, EU, etc. 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 # How well it matches capabilities 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 # 0.0 to 1.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 # proposal, nomination_letter, cv, budget, etc. title: str content: str version: str = "1.0" status: str = "draft" # draft, review, final 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}") # Placeholder - would integrate with funding databases 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}") # Placeholder response 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}") # Placeholder response 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}") # Placeholder response 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}") # Placeholder response 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") # Placeholder response 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", }, }