File size: 5,450 Bytes
c75a06a
 
 
 
4d61f2c
 
c75a06a
 
 
 
 
4d61f2c
c75a06a
4d61f2c
c75a06a
 
 
 
 
 
 
 
 
4d61f2c
c75a06a
 
 
 
 
 
 
 
 
 
 
4d61f2c
c75a06a
 
 
 
 
 
 
4d61f2c
c75a06a
 
 
 
 
4d61f2c
c75a06a
 
 
 
 
4d61f2c
c75a06a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d61f2c
c75a06a
 
 
 
 
 
 
 
4d61f2c
c75a06a
 
 
 
 
 
 
 
 
4d61f2c
c75a06a
 
 
 
4d61f2c
c75a06a
4d61f2c
 
 
 
c75a06a
4d61f2c
c75a06a
4d61f2c
 
 
 
 
 
 
 
 
 
 
 
 
 
c75a06a
4d61f2c
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
import csv
import json
import logging
from typing import Any, Dict, List
# Import lead_scoring_agent for lead scoring integration
from lead_scoring_agent import score_lead

class ResearchAgent:
    """
    Collects, augments, audits, and outputs lead and signal data for outreach,
    with robust error/memory handling and dynamic variable mapping.
    Integrates lead_scoring_agent to score leads as first logic step in pipeline.
    """
    
    def __init__(self, db: Any = None, schema: Dict[str, Any] = None):
        self.db = db
        self.schema = schema or {}
        self.memory = []
        self.errors = []
        self.profiles = []
        self.refined = []
        self.mcp_updates = []
        logging.basicConfig(level=logging.INFO)
    
    def map_raw_data(self, raw_lead: Dict[str, Any], signals: Dict[str, Any], dynamic_vars: Dict[str, Any]) -> Dict[str, Any]:
        mapped = {}
        try:
            for field, rule in (self.schema.get('lead', {})).items():
                mapped[field] = raw_lead.get(field, rule.get('default'))
            mapped.update(signals)
            mapped.update(dynamic_vars)
        except Exception as e:
            self.errors.append(f"Mapping error: {e}")
            logging.error(f"Mapping error: {e}")
        return mapped
    
    def handle_loop_errors(self, data_list: List[Dict[str, Any]]):
        for idx, entry in enumerate(data_list):
            try:
                assert all(entry.values()), f"Missing value in {entry}"
            except Exception as e:
                self.errors.append(f"Loop error at {idx}: {e}")
                logging.warning(f"Loop error at {idx}: {e}")
    
    def memory_management(self, current: Dict[str, Any]):
        self.memory.append(current)
        if len(self.memory) > 50:
            dropped = self.memory.pop(0)
            logging.info(f"Dropped old memory: {dropped}")
    
    def refine_profile(self, profile: Dict[str, Any], messaging_signals: Dict[str, Any]) -> Dict[str, Any]:
        """Output the most outreach-ready version of the profile."""
        refined = {**profile, **messaging_signals}
        self.refined.append(refined)
        return refined
    
    def handoff(self, profiles: List[Dict[str, Any]], target: str = 'csv', path: str = 'handoff.csv'):
        """Wire/export to csv/mcp/db."""
        try:
            if target == 'csv':
                with open(path, 'w', newline='') as f:
                    writer = csv.DictWriter(f, fieldnames=profiles[0].keys())
                    writer.writeheader()
                    writer.writerows(profiles)
            elif target == 'mcp' and self.db:
                for p in profiles:
                    self.db.update_profile(p)
            elif target == 'db' and self.db:
                self.db.bulk_insert(profiles)
            self.mcp_updates.extend(profiles)
            logging.info(f"Profiles handed off to {target}")
        except Exception as e:
            self.errors.append(f"Handoff error: {e}")
            logging.error(f"Handoff error: {e}")
    
    def audit(self, profiles: List[Dict[str, Any]]):
        """Audit field completeness, error/correction, and handoff readiness."""
        required_fields = self.schema.get('required', set())
        for idx, profile in enumerate(profiles):
            missing = [f for f in required_fields if not profile.get(f)]
            if missing:
                self.errors.append(f"Profile {idx} missing: {missing}")
                logging.warning(f"Profile {idx} missing: {missing}")
    
    def export_refined_for_outreach(self, out_path: str = 'refined_for_outreach.csv'):
        try:
            with open(out_path, 'w', newline='') as f:
                writer = csv.DictWriter(f, fieldnames=self.refined[0].keys())
                writer.writeheader()
                writer.writerows(self.refined)
        except Exception as e:
            self.errors.append(f"Export error: {e}")
            logging.error(f"Export error: {e}")
    
    def get_messaging_recommendations(self, profile: Dict[str, Any]) -> Dict[str, Any]:
        """Stub: Replace with your ML/model recommendation logic."""
        recs = {"recommendation": f"Personalize outreach for {profile.get('name', 'Unknown')}"}
        return recs
    
    def run_pipeline(self, leads, signals_list, dynamic_vars_list):
        """
        Run the research pipeline with lead scoring integration.
        Calls score_lead() as first logic step after mapping.
        """
        for raw_lead, signals, dynamic_vars in zip(leads, signals_list, dynamic_vars_list):
            # Map raw data
            mapped = self.map_raw_data(raw_lead, signals, dynamic_vars)
            
            # FIRST LOGIC STEP: Score the lead using lead_scoring_agent
            scored_profile = score_lead(mapped)
            logging.info(f"Lead scored: {scored_profile.get('name', 'Unknown')} - Score: {scored_profile.get('lead_score', 'N/A')}")
            
            # Continue with memory management and profiling
            self.memory_management(scored_profile)
            self.profiles.append(scored_profile)
            
            # Get messaging recommendations and refine
            messaging = self.get_messaging_recommendations(scored_profile)
            refined = self.refine_profile(scored_profile, messaging)
        
        # Audit and handoff
        self.audit(self.profiles)
        self.handoff(self.refined)