File size: 11,957 Bytes
76c3b0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
"""
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",
            },
        }