File size: 10,989 Bytes
a9dc537
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
OutreachAgent for Patent Wake-Up Scenario

Generates valorization materials and outreach communications:
- Comprehensive valorization briefs (PDF)
- Executive summaries
- Stakeholder-specific outreach materials
"""

from typing import List
import os
from datetime import datetime
from loguru import logger
from langchain_core.prompts import ChatPromptTemplate

from ..base_agent import BaseAgent, Task
from ...llm.langchain_ollama_client import LangChainOllamaClient
from ...workflow.langgraph_state import (
    PatentAnalysis,
    MarketAnalysis,
    StakeholderMatch,
    ValorizationBrief
)


class OutreachAgent(BaseAgent):
    """
    Specialized agent for generating valorization materials.
    Creates briefs, summaries, and outreach communications.
    """

    def __init__(self, llm_client: LangChainOllamaClient, memory_agent=None):
        """
        Initialize OutreachAgent.

        Args:
            llm_client: LangChain Ollama client
            memory_agent: Optional memory agent
        """
        # Note: OutreachAgent uses LangChain directly
        self.name = "OutreachAgent"
        self.description = "Valorization brief and outreach generation"

        self.llm_client = llm_client
        self.memory_agent = memory_agent

        # Use standard model for document generation
        self.llm = llm_client.get_llm('standard')  # llama3.1:8b

        # Create generation chains
        self.brief_chain = self._create_brief_chain()
        self.summary_chain = self._create_summary_chain()

        # Ensure outputs directory exists
        os.makedirs("outputs", exist_ok=True)

        logger.info("Initialized OutreachAgent")

    def _create_brief_chain(self):
        """Create chain for valorization brief generation"""
        prompt = ChatPromptTemplate.from_messages([
            ("system", "You are an expert in technology commercialization and professional business writing."),
            ("human", """
Create a comprehensive valorization brief for this patent.

PATENT ANALYSIS:
Title: {patent_title}
TRL: {trl_level}/9
Key Innovations:
{key_innovations}
Potential Applications:
{applications}

MARKET OPPORTUNITIES:
{market_opportunities}

TOP STAKEHOLDER MATCHES:
{stakeholder_matches}

Create a professional valorization brief in markdown format with:

# Valorization Brief: [Patent Title]

## Executive Summary
[1-paragraph overview highlighting commercialization potential]

## Technology Overview
### Key Innovations
[Bullet points of key innovations]

### Technology Readiness
[TRL assessment and readiness for commercialization]

### Technical Advantages
[What makes this technology unique]

## Market Opportunity Analysis
### Target Sectors
[Top 3-5 sectors with market size data]

### Market Gaps Addressed
[Specific problems this solves]

### Competitive Positioning
[How to position vs. alternatives]

## Recommended Partners
[Top 5 stakeholders with match rationale]

## Commercialization Roadmap
### Immediate Next Steps (0-6 months)
[Specific actions]

### Medium-term Goals (6-18 months)
[Development milestones]

### Long-term Vision (18+ months)
[Market expansion]

## Key Takeaways
[3-5 bullet points with main insights]

Write professionally but accessibly. Use specific numbers and data where available.
""")
        ])

        return prompt | self.llm

    def _create_summary_chain(self):
        """Create chain for executive summary extraction"""
        prompt = ChatPromptTemplate.from_messages([
            ("system", "You extract concise executive summaries from longer documents."),
            ("human", "Extract a 2-3 sentence executive summary from this brief:\n\n{brief_content}")
        ])

        return prompt | self.llm

    async def create_valorization_brief(
        self,
        patent_analysis: PatentAnalysis,
        market_analysis: MarketAnalysis,
        matches: List[StakeholderMatch]
    ) -> ValorizationBrief:
        """
        Generate comprehensive valorization brief.

        Args:
            patent_analysis: Patent technical analysis
            market_analysis: Market opportunities
            matches: Stakeholder matches

        Returns:
            ValorizationBrief with content and PDF path
        """
        logger.info(f"📝 Creating valorization brief for: {patent_analysis.title}")

        # Format data for brief generation
        key_innovations = "\n".join([f"- {inn}" for inn in patent_analysis.key_innovations])
        applications = "\n".join([f"- {app}" for app in patent_analysis.potential_applications])

        market_opps = "\n\n".join([
            f"**{opp.sector}** ({opp.technology_fit} fit)\n"
            f"- Market Size: {f'${opp.market_size_usd/1e9:.1f}B USD' if opp.market_size_usd is not None else 'NaN'}\n"
            f"- Growth: {f'{opp.growth_rate_percent}% annually' if opp.growth_rate_percent is not None else 'NaN'}\n"
            f"- Gap: {opp.market_gap}"
            for opp in market_analysis.opportunities[:5]
        ])

        stakeholder_text = "\n\n".join([
            f"{i+1}. **{m.stakeholder_name}** ({m.stakeholder_type})\n"
            f"   - Location: {m.location}\n"
            f"   - Fit Score: {m.overall_fit_score:.2f}\n"
            f"   - Why: {m.match_rationale[:200]}..."
            for i, m in enumerate(matches[:5])
        ])

        # Generate brief content
        logger.info("Generating brief content...")
        content_response = await self.brief_chain.ainvoke({
            "patent_title": patent_analysis.title,
            "trl_level": patent_analysis.trl_level,
            "key_innovations": key_innovations,
            "applications": applications,
            "market_opportunities": market_opps,
            "stakeholder_matches": stakeholder_text
        })

        content = content_response.content

        # Extract executive summary
        logger.info("Extracting executive summary...")
        summary_response = await self.summary_chain.ainvoke({
            "brief_content": content[:2000]  # First part only
        })
        executive_summary = summary_response.content

        # Generate PDF
        pdf_path = await self._generate_pdf(
            content=content,
            patent_id=patent_analysis.patent_id,
            title=patent_analysis.title
        )

        # Build ValorizationBrief
        brief = ValorizationBrief(
            patent_id=patent_analysis.patent_id,
            content=content,
            pdf_path=pdf_path,
            executive_summary=executive_summary,
            technology_overview=self._extract_section(content, "Technology Overview"),
            market_analysis_summary=self._extract_section(content, "Market Opportunity"),
            partner_recommendations=self._extract_section(content, "Recommended Partners"),
            top_opportunities=market_analysis.top_sectors,
            recommended_partners=[m.stakeholder_name for m in matches[:5]],
            key_takeaways=self._extract_takeaways(content),
            generated_date=datetime.now().strftime("%Y-%m-%d"),
            version="1.0"
        )

        logger.success(f"✅ Valorization brief created: {pdf_path}")

        return brief

    async def _generate_pdf(self, content: str, patent_id: str, title: str) -> str:
        """
        Generate PDF from markdown content.

        Args:
            content: Markdown content
            patent_id: Patent identifier
            title: Brief title

        Returns:
            Path to generated PDF
        """
        try:
            from ...tools.langchain_tools import document_generator_tool

            # Create filename
            filename = f"valorization_brief_{patent_id}_{datetime.now().strftime('%Y%m%d')}.pdf"
            pdf_path = os.path.join("outputs", filename)

            # Generate PDF
            await document_generator_tool.ainvoke({
                "output_path": pdf_path,
                "title": f"Valorization Brief: {title}",
                "content": content,
                "author": "SPARKNET Valorization System"
            })

            return pdf_path

        except Exception as e:
            logger.error(f"PDF generation failed: {e}")
            # Fallback: save as markdown
            md_path = pdf_path.replace('.pdf', '.md')
            with open(md_path, 'w', encoding='utf-8') as f:
                f.write(content)
            logger.warning(f"Saved as markdown instead: {md_path}")
            return md_path

    def _extract_section(self, content: str, section_name: str) -> str:
        """Extract a specific section from markdown content"""
        import re

        # Find section using markdown headers
        pattern = rf'##\s+{section_name}.*?\n(.*?)(?=##|\Z)'
        match = re.search(pattern, content, re.DOTALL | re.IGNORECASE)

        if match:
            return match.group(1).strip()[:500]  # Limit length
        return "Section not found"

    def _extract_takeaways(self, content: str) -> List[str]:
        """Extract key takeaways from content"""
        import re

        # Look for Key Takeaways section
        pattern = r'##\s+Key Takeaways.*?\n(.*?)(?=##|\Z)'
        match = re.search(pattern, content, re.DOTALL | re.IGNORECASE)

        if match:
            takeaways_text = match.group(1)
            # Extract bullet points
            bullets = re.findall(r'[-*]\s+(.+)', takeaways_text)
            return bullets[:5]

        # Fallback: create generic takeaways
        return [
            "Technology demonstrates strong commercialization potential",
            "Multiple market opportunities identified",
            "Strategic partners available for collaboration"
        ]

    async def process_task(self, task: Task) -> Task:
        """
        Process task using agent interface.

        Args:
            task: Task with patent_analysis, market_analysis, and matches in metadata

        Returns:
            Task with ValorizationBrief result
        """
        task.status = "in_progress"

        try:
            patent_dict = task.metadata.get('patent_analysis')
            market_dict = task.metadata.get('market_analysis')
            matches_list = task.metadata.get('matches', [])

            if not patent_dict or not market_dict:
                raise ValueError("patent_analysis and market_analysis required")

            # Convert dicts to objects
            patent_analysis = PatentAnalysis(**patent_dict)
            market_analysis = MarketAnalysis(**market_dict)
            matches = [StakeholderMatch(**m) for m in matches_list]

            brief = await self.create_valorization_brief(
                patent_analysis,
                market_analysis,
                matches
            )

            task.result = brief.model_dump()
            task.status = "completed"

        except Exception as e:
            logger.error(f"Outreach generation failed: {e}")
            task.status = "failed"
            task.error = str(e)

        return task