fusesell 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fusesell might be problematic. Click here for more details.
- fusesell-1.2.0.dist-info/METADATA +872 -0
- fusesell-1.2.0.dist-info/RECORD +31 -0
- fusesell-1.2.0.dist-info/WHEEL +5 -0
- fusesell-1.2.0.dist-info/entry_points.txt +2 -0
- fusesell-1.2.0.dist-info/licenses/LICENSE +21 -0
- fusesell-1.2.0.dist-info/top_level.txt +2 -0
- fusesell.py +15 -0
- fusesell_local/__init__.py +37 -0
- fusesell_local/api.py +341 -0
- fusesell_local/cli.py +1450 -0
- fusesell_local/config/__init__.py +11 -0
- fusesell_local/config/prompts.py +245 -0
- fusesell_local/config/settings.py +277 -0
- fusesell_local/pipeline.py +932 -0
- fusesell_local/stages/__init__.py +19 -0
- fusesell_local/stages/base_stage.py +602 -0
- fusesell_local/stages/data_acquisition.py +1820 -0
- fusesell_local/stages/data_preparation.py +1231 -0
- fusesell_local/stages/follow_up.py +1590 -0
- fusesell_local/stages/initial_outreach.py +2337 -0
- fusesell_local/stages/lead_scoring.py +1452 -0
- fusesell_local/tests/test_api.py +65 -0
- fusesell_local/tests/test_cli.py +37 -0
- fusesell_local/utils/__init__.py +15 -0
- fusesell_local/utils/birthday_email_manager.py +467 -0
- fusesell_local/utils/data_manager.py +4050 -0
- fusesell_local/utils/event_scheduler.py +618 -0
- fusesell_local/utils/llm_client.py +283 -0
- fusesell_local/utils/logger.py +203 -0
- fusesell_local/utils/timezone_detector.py +914 -0
- fusesell_local/utils/validators.py +416 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prompt Manager for FuseSell Local
|
|
3
|
+
Handles LLM prompt templates and variable substitution
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
import re
|
|
8
|
+
import logging
|
|
9
|
+
from .settings import ConfigManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PromptManager:
|
|
13
|
+
"""
|
|
14
|
+
Manages LLM prompts with template substitution and customization.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config_manager: ConfigManager):
|
|
18
|
+
"""
|
|
19
|
+
Initialize prompt manager.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
config_manager: Configuration manager instance
|
|
23
|
+
"""
|
|
24
|
+
self.config_manager = config_manager
|
|
25
|
+
self.logger = logging.getLogger("fusesell.prompts")
|
|
26
|
+
|
|
27
|
+
def get_prompt(
|
|
28
|
+
self,
|
|
29
|
+
stage: str,
|
|
30
|
+
prompt_key: str,
|
|
31
|
+
variables: Optional[Dict[str, Any]] = None,
|
|
32
|
+
team_id: Optional[str] = None,
|
|
33
|
+
language: str = "english"
|
|
34
|
+
) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Get formatted prompt for a specific stage and key.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
stage: Pipeline stage name
|
|
40
|
+
prompt_key: Specific prompt identifier
|
|
41
|
+
variables: Variables for template substitution
|
|
42
|
+
team_id: Optional team ID for customization
|
|
43
|
+
language: Language for prompts
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Formatted prompt string
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
# Get prompts configuration
|
|
50
|
+
prompts = self.config_manager.get_prompts(team_id, language)
|
|
51
|
+
|
|
52
|
+
# Get stage-specific prompts
|
|
53
|
+
stage_prompts = prompts.get(stage, {})
|
|
54
|
+
if not stage_prompts:
|
|
55
|
+
self.logger.warning(f"No prompts found for stage: {stage}")
|
|
56
|
+
return ""
|
|
57
|
+
|
|
58
|
+
# Get specific prompt template
|
|
59
|
+
prompt_template = stage_prompts.get(prompt_key, "")
|
|
60
|
+
if not prompt_template:
|
|
61
|
+
self.logger.warning(f"No prompt found for {stage}.{prompt_key}")
|
|
62
|
+
return ""
|
|
63
|
+
|
|
64
|
+
# Apply variable substitution
|
|
65
|
+
if variables:
|
|
66
|
+
prompt_template = self._substitute_variables(prompt_template, variables)
|
|
67
|
+
|
|
68
|
+
return prompt_template
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
self.logger.error(f"Failed to get prompt {stage}.{prompt_key}: {str(e)}")
|
|
72
|
+
return ""
|
|
73
|
+
|
|
74
|
+
def get_data_acquisition_prompts(self, variables: Dict[str, Any], **kwargs) -> Dict[str, str]:
|
|
75
|
+
"""Get all prompts for data acquisition stage."""
|
|
76
|
+
return {
|
|
77
|
+
'website_extraction': self.get_prompt('data_acquisition', 'website_extraction', variables, **kwargs),
|
|
78
|
+
'business_card_ocr': self.get_prompt('data_acquisition', 'business_card_ocr', variables, **kwargs),
|
|
79
|
+
'social_media_analysis': self.get_prompt('data_acquisition', 'social_media_analysis', variables, **kwargs),
|
|
80
|
+
'data_consolidation': self.get_prompt('data_acquisition', 'data_consolidation', variables, **kwargs)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def get_data_preparation_prompts(self, variables: Dict[str, Any], **kwargs) -> Dict[str, str]:
|
|
84
|
+
"""Get all prompts for data preparation stage."""
|
|
85
|
+
return {
|
|
86
|
+
'extract_company_info': self.get_prompt('data_preparation', 'extract_company_info', variables, **kwargs),
|
|
87
|
+
'identify_pain_points': self.get_prompt('data_preparation', 'identify_pain_points', variables, **kwargs),
|
|
88
|
+
'financial_analysis': self.get_prompt('data_preparation', 'financial_analysis', variables, **kwargs),
|
|
89
|
+
'development_plans': self.get_prompt('data_preparation', 'development_plans', variables, **kwargs),
|
|
90
|
+
'technology_analysis': self.get_prompt('data_preparation', 'technology_analysis', variables, **kwargs)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def get_lead_scoring_prompts(self, variables: Dict[str, Any], **kwargs) -> Dict[str, str]:
|
|
94
|
+
"""Get all prompts for lead scoring stage."""
|
|
95
|
+
return {
|
|
96
|
+
'evaluate_fit': self.get_prompt('lead_scoring', 'evaluate_fit', variables, **kwargs),
|
|
97
|
+
'score_calculation': self.get_prompt('lead_scoring', 'score_calculation', variables, **kwargs),
|
|
98
|
+
'competitive_analysis': self.get_prompt('lead_scoring', 'competitive_analysis', variables, **kwargs),
|
|
99
|
+
'recommendation': self.get_prompt('lead_scoring', 'recommendation', variables, **kwargs)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
def get_initial_outreach_prompts(self, variables: Dict[str, Any], **kwargs) -> Dict[str, str]:
|
|
103
|
+
"""Get all prompts for initial outreach stage."""
|
|
104
|
+
return {
|
|
105
|
+
'email_generation': self.get_prompt('initial_outreach', 'email_generation', variables, **kwargs),
|
|
106
|
+
'subject_line': self.get_prompt('initial_outreach', 'subject_line', variables, **kwargs),
|
|
107
|
+
'tone_adjustment': self.get_prompt('initial_outreach', 'tone_adjustment', variables, **kwargs),
|
|
108
|
+
'personalization': self.get_prompt('initial_outreach', 'personalization', variables, **kwargs)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def get_follow_up_prompts(self, variables: Dict[str, Any], **kwargs) -> Dict[str, str]:
|
|
112
|
+
"""Get all prompts for follow-up stage."""
|
|
113
|
+
return {
|
|
114
|
+
'analyze_interaction': self.get_prompt('follow_up', 'analyze_interaction', variables, **kwargs),
|
|
115
|
+
'generate_followup': self.get_prompt('follow_up', 'generate_followup', variables, **kwargs),
|
|
116
|
+
'timing_analysis': self.get_prompt('follow_up', 'timing_analysis', variables, **kwargs),
|
|
117
|
+
'sequence_planning': self.get_prompt('follow_up', 'sequence_planning', variables, **kwargs)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def _substitute_variables(self, template: str, variables: Dict[str, Any]) -> str:
|
|
121
|
+
"""
|
|
122
|
+
Substitute variables in prompt template.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
template: Prompt template with placeholders
|
|
126
|
+
variables: Variables to substitute
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Template with variables substituted
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
# Handle nested dictionary access (e.g., {customer.name})
|
|
133
|
+
def replace_nested(match):
|
|
134
|
+
var_path = match.group(1)
|
|
135
|
+
value = self._get_nested_value(variables, var_path)
|
|
136
|
+
return str(value) if value is not None else f"{{{var_path}}}"
|
|
137
|
+
|
|
138
|
+
# Replace {variable} and {nested.variable} patterns
|
|
139
|
+
result = re.sub(r'\{([^}]+)\}', replace_nested, template)
|
|
140
|
+
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
self.logger.warning(f"Variable substitution failed: {str(e)}")
|
|
145
|
+
return template
|
|
146
|
+
|
|
147
|
+
def _get_nested_value(self, data: Dict[str, Any], path: str) -> Any:
|
|
148
|
+
"""
|
|
149
|
+
Get value from nested dictionary using dot notation.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
data: Dictionary to search
|
|
153
|
+
path: Dot-separated path (e.g., 'customer.name')
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Value at path or None if not found
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
keys = path.split('.')
|
|
160
|
+
value = data
|
|
161
|
+
|
|
162
|
+
for key in keys:
|
|
163
|
+
if isinstance(value, dict) and key in value:
|
|
164
|
+
value = value[key]
|
|
165
|
+
else:
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
return value
|
|
169
|
+
|
|
170
|
+
except Exception:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
def validate_prompt_variables(self, template: str, variables: Dict[str, Any]) -> Dict[str, Any]:
|
|
174
|
+
"""
|
|
175
|
+
Validate that all required variables are provided for a template.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
template: Prompt template
|
|
179
|
+
variables: Available variables
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Dictionary with validation results
|
|
183
|
+
"""
|
|
184
|
+
# Find all variable placeholders
|
|
185
|
+
placeholders = re.findall(r'\{([^}]+)\}', template)
|
|
186
|
+
|
|
187
|
+
missing_vars = []
|
|
188
|
+
available_vars = []
|
|
189
|
+
|
|
190
|
+
for placeholder in placeholders:
|
|
191
|
+
value = self._get_nested_value(variables, placeholder)
|
|
192
|
+
if value is None:
|
|
193
|
+
missing_vars.append(placeholder)
|
|
194
|
+
else:
|
|
195
|
+
available_vars.append(placeholder)
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
'valid': len(missing_vars) == 0,
|
|
199
|
+
'missing_variables': missing_vars,
|
|
200
|
+
'available_variables': available_vars,
|
|
201
|
+
'total_placeholders': len(placeholders)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
def create_variable_context(self,
|
|
205
|
+
customer_data: Optional[Dict[str, Any]] = None,
|
|
206
|
+
lead_scores: Optional[Dict[str, Any]] = None,
|
|
207
|
+
org_info: Optional[Dict[str, Any]] = None,
|
|
208
|
+
team_info: Optional[Dict[str, Any]] = None,
|
|
209
|
+
**additional_vars) -> Dict[str, Any]:
|
|
210
|
+
"""
|
|
211
|
+
Create a comprehensive variable context for prompt substitution.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
customer_data: Customer information
|
|
215
|
+
lead_scores: Lead scoring results
|
|
216
|
+
org_info: Organization information
|
|
217
|
+
team_info: Team information
|
|
218
|
+
**additional_vars: Additional variables
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Complete variable context
|
|
222
|
+
"""
|
|
223
|
+
context = {}
|
|
224
|
+
|
|
225
|
+
if customer_data:
|
|
226
|
+
context['customer'] = customer_data
|
|
227
|
+
context['company'] = customer_data.get('companyInfo', {})
|
|
228
|
+
context['contact'] = customer_data.get('primaryContact', {})
|
|
229
|
+
context['pain_points'] = customer_data.get('painPoints', [])
|
|
230
|
+
|
|
231
|
+
if lead_scores:
|
|
232
|
+
context['lead_scores'] = lead_scores
|
|
233
|
+
context['top_score'] = max(lead_scores.get('scores', []), key=lambda x: x.get('score', 0), default={})
|
|
234
|
+
|
|
235
|
+
if org_info:
|
|
236
|
+
context['org'] = org_info
|
|
237
|
+
context['seller'] = org_info
|
|
238
|
+
|
|
239
|
+
if team_info:
|
|
240
|
+
context['team'] = team_info
|
|
241
|
+
|
|
242
|
+
# Add additional variables
|
|
243
|
+
context.update(additional_vars)
|
|
244
|
+
|
|
245
|
+
return context
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration Manager for FuseSell Local
|
|
3
|
+
Handles team-specific settings, prompts, and business rules
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConfigManager:
|
|
13
|
+
"""
|
|
14
|
+
Manages configuration settings for FuseSell Local.
|
|
15
|
+
Handles team-specific prompts, scoring criteria, and business rules.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, data_dir: str = "./fusesell_data"):
|
|
19
|
+
"""
|
|
20
|
+
Initialize configuration manager.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
data_dir: Directory containing configuration files
|
|
24
|
+
"""
|
|
25
|
+
self.data_dir = Path(data_dir)
|
|
26
|
+
self.config_dir = self.data_dir / "config"
|
|
27
|
+
self.logger = logging.getLogger("fusesell.config")
|
|
28
|
+
|
|
29
|
+
# Ensure config directory exists
|
|
30
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
|
|
32
|
+
# Cache for loaded configurations
|
|
33
|
+
self._cache = {}
|
|
34
|
+
|
|
35
|
+
def get_prompts(self, team_id: Optional[str] = None, language: str = "english") -> Dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
Get LLM prompts for stages, with team and language customization.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
team_id: Optional team ID for team-specific prompts
|
|
41
|
+
language: Language for prompts
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Dictionary of prompts organized by stage
|
|
45
|
+
"""
|
|
46
|
+
cache_key = f"prompts_{team_id}_{language}"
|
|
47
|
+
|
|
48
|
+
if cache_key in self._cache:
|
|
49
|
+
return self._cache[cache_key]
|
|
50
|
+
|
|
51
|
+
# Load base prompts
|
|
52
|
+
base_prompts = self._load_json_config("prompts.json", {})
|
|
53
|
+
|
|
54
|
+
# Apply team customizations if available
|
|
55
|
+
if team_id:
|
|
56
|
+
team_prompts = self._load_team_prompts(team_id, language)
|
|
57
|
+
base_prompts = self._merge_configs(base_prompts, team_prompts)
|
|
58
|
+
|
|
59
|
+
# Apply language customizations
|
|
60
|
+
if language != "english":
|
|
61
|
+
lang_prompts = self._load_language_prompts(language)
|
|
62
|
+
base_prompts = self._merge_configs(base_prompts, lang_prompts)
|
|
63
|
+
|
|
64
|
+
self._cache[cache_key] = base_prompts
|
|
65
|
+
return base_prompts
|
|
66
|
+
|
|
67
|
+
def get_scoring_criteria(self, team_id: Optional[str] = None) -> Dict[str, Any]:
|
|
68
|
+
"""
|
|
69
|
+
Get lead scoring criteria with team customization.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
team_id: Optional team ID for team-specific criteria
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Dictionary of scoring criteria and weights
|
|
76
|
+
"""
|
|
77
|
+
cache_key = f"scoring_{team_id}"
|
|
78
|
+
|
|
79
|
+
if cache_key in self._cache:
|
|
80
|
+
return self._cache[cache_key]
|
|
81
|
+
|
|
82
|
+
# Load base scoring criteria
|
|
83
|
+
base_criteria = self._load_json_config("scoring_criteria.json", {})
|
|
84
|
+
|
|
85
|
+
# Apply team customizations if available
|
|
86
|
+
if team_id:
|
|
87
|
+
team_criteria = self._load_team_scoring(team_id)
|
|
88
|
+
base_criteria = self._merge_configs(base_criteria, team_criteria)
|
|
89
|
+
|
|
90
|
+
self._cache[cache_key] = base_criteria
|
|
91
|
+
return base_criteria
|
|
92
|
+
|
|
93
|
+
def get_email_templates(self, team_id: Optional[str] = None, language: str = "english") -> Dict[str, Any]:
|
|
94
|
+
"""
|
|
95
|
+
Get email templates with team and language customization.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
team_id: Optional team ID for team-specific templates
|
|
99
|
+
language: Language for templates
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dictionary of email templates
|
|
103
|
+
"""
|
|
104
|
+
cache_key = f"templates_{team_id}_{language}"
|
|
105
|
+
|
|
106
|
+
if cache_key in self._cache:
|
|
107
|
+
return self._cache[cache_key]
|
|
108
|
+
|
|
109
|
+
# Load base templates
|
|
110
|
+
base_templates = self._load_json_config("email_templates.json", {})
|
|
111
|
+
|
|
112
|
+
# Apply team customizations if available
|
|
113
|
+
if team_id:
|
|
114
|
+
team_templates = self._load_team_templates(team_id, language)
|
|
115
|
+
base_templates = self._merge_configs(base_templates, team_templates)
|
|
116
|
+
|
|
117
|
+
# Apply language customizations
|
|
118
|
+
if language != "english":
|
|
119
|
+
lang_templates = self._load_language_templates(language)
|
|
120
|
+
base_templates = self._merge_configs(base_templates, lang_templates)
|
|
121
|
+
|
|
122
|
+
self._cache[cache_key] = base_templates
|
|
123
|
+
return base_templates
|
|
124
|
+
|
|
125
|
+
def get_business_rules(self, team_id: Optional[str] = None) -> Dict[str, Any]:
|
|
126
|
+
"""
|
|
127
|
+
Get business rules and pipeline behavior settings.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
team_id: Optional team ID for team-specific rules
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dictionary of business rules
|
|
134
|
+
"""
|
|
135
|
+
cache_key = f"rules_{team_id}"
|
|
136
|
+
|
|
137
|
+
if cache_key in self._cache:
|
|
138
|
+
return self._cache[cache_key]
|
|
139
|
+
|
|
140
|
+
# Default business rules based on original system
|
|
141
|
+
default_rules = {
|
|
142
|
+
"pipeline": {
|
|
143
|
+
"stop_on_website_fail": True,
|
|
144
|
+
"wait_after_draft_generation": True,
|
|
145
|
+
"max_operations": 10,
|
|
146
|
+
"max_simultaneous_operations": 1,
|
|
147
|
+
"sequential_execution_only": True
|
|
148
|
+
},
|
|
149
|
+
"data_acquisition": {
|
|
150
|
+
"min_execution_time_seconds": 100,
|
|
151
|
+
"required_sources": ["website"],
|
|
152
|
+
"optional_sources": ["business_card", "linkedin", "facebook"]
|
|
153
|
+
},
|
|
154
|
+
"initial_outreach": {
|
|
155
|
+
"actions": ["draft_write", "draft_rewrite", "send", "close"],
|
|
156
|
+
"default_action": "draft_write",
|
|
157
|
+
"require_human_approval": True,
|
|
158
|
+
"one_action_per_trigger": True
|
|
159
|
+
},
|
|
160
|
+
"follow_up": {
|
|
161
|
+
"actions": ["draft_write", "draft_rewrite", "send"],
|
|
162
|
+
"require_explicit_trigger": True,
|
|
163
|
+
"analyze_previous_interactions": True
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# Apply team customizations if available
|
|
168
|
+
if team_id:
|
|
169
|
+
team_rules = self._load_team_rules(team_id)
|
|
170
|
+
default_rules = self._merge_configs(default_rules, team_rules)
|
|
171
|
+
|
|
172
|
+
self._cache[cache_key] = default_rules
|
|
173
|
+
return default_rules
|
|
174
|
+
|
|
175
|
+
def _load_json_config(self, filename: str, default: Dict[str, Any]) -> Dict[str, Any]:
|
|
176
|
+
"""Load JSON configuration file with fallback to default."""
|
|
177
|
+
try:
|
|
178
|
+
config_file = self.config_dir / filename
|
|
179
|
+
if config_file.exists():
|
|
180
|
+
with open(config_file, 'r', encoding='utf-8') as f:
|
|
181
|
+
return json.load(f)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
self.logger.warning(f"Failed to load {filename}: {str(e)}")
|
|
184
|
+
|
|
185
|
+
return default.copy()
|
|
186
|
+
|
|
187
|
+
def _load_team_prompts(self, team_id: str, language: str) -> Dict[str, Any]:
|
|
188
|
+
"""Load team-specific prompts."""
|
|
189
|
+
team_file = self.config_dir / f"team_{team_id}_prompts_{language}.json"
|
|
190
|
+
return self._load_json_config(team_file.name, {})
|
|
191
|
+
|
|
192
|
+
def _load_team_scoring(self, team_id: str) -> Dict[str, Any]:
|
|
193
|
+
"""Load team-specific scoring criteria."""
|
|
194
|
+
team_file = self.config_dir / f"team_{team_id}_scoring.json"
|
|
195
|
+
return self._load_json_config(team_file.name, {})
|
|
196
|
+
|
|
197
|
+
def _load_team_templates(self, team_id: str, language: str) -> Dict[str, Any]:
|
|
198
|
+
"""Load team-specific email templates."""
|
|
199
|
+
team_file = self.config_dir / f"team_{team_id}_templates_{language}.json"
|
|
200
|
+
return self._load_json_config(team_file.name, {})
|
|
201
|
+
|
|
202
|
+
def _load_team_rules(self, team_id: str) -> Dict[str, Any]:
|
|
203
|
+
"""Load team-specific business rules."""
|
|
204
|
+
team_file = self.config_dir / f"team_{team_id}_rules.json"
|
|
205
|
+
return self._load_json_config(team_file.name, {})
|
|
206
|
+
|
|
207
|
+
def _load_language_prompts(self, language: str) -> Dict[str, Any]:
|
|
208
|
+
"""Load language-specific prompts."""
|
|
209
|
+
lang_file = self.config_dir / f"prompts_{language}.json"
|
|
210
|
+
return self._load_json_config(lang_file.name, {})
|
|
211
|
+
|
|
212
|
+
def _load_language_templates(self, language: str) -> Dict[str, Any]:
|
|
213
|
+
"""Load language-specific email templates."""
|
|
214
|
+
lang_file = self.config_dir / f"templates_{language}.json"
|
|
215
|
+
return self._load_json_config(lang_file.name, {})
|
|
216
|
+
|
|
217
|
+
def _merge_configs(self, base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
|
|
218
|
+
"""
|
|
219
|
+
Recursively merge configuration dictionaries.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
base: Base configuration
|
|
223
|
+
override: Override configuration
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Merged configuration
|
|
227
|
+
"""
|
|
228
|
+
result = base.copy()
|
|
229
|
+
|
|
230
|
+
for key, value in override.items():
|
|
231
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
232
|
+
result[key] = self._merge_configs(result[key], value)
|
|
233
|
+
else:
|
|
234
|
+
result[key] = value
|
|
235
|
+
|
|
236
|
+
return result
|
|
237
|
+
|
|
238
|
+
def save_team_config(self, team_id: str, config_type: str, config_data: Dict[str, Any], language: str = "english") -> None:
|
|
239
|
+
"""
|
|
240
|
+
Save team-specific configuration.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
team_id: Team identifier
|
|
244
|
+
config_type: Type of config (prompts, scoring, templates, rules)
|
|
245
|
+
config_data: Configuration data to save
|
|
246
|
+
language: Language for prompts/templates
|
|
247
|
+
"""
|
|
248
|
+
try:
|
|
249
|
+
if config_type in ["prompts", "templates"]:
|
|
250
|
+
filename = f"team_{team_id}_{config_type}_{language}.json"
|
|
251
|
+
else:
|
|
252
|
+
filename = f"team_{team_id}_{config_type}.json"
|
|
253
|
+
|
|
254
|
+
config_file = self.config_dir / filename
|
|
255
|
+
|
|
256
|
+
with open(config_file, 'w', encoding='utf-8') as f:
|
|
257
|
+
json.dump(config_data, f, indent=2, ensure_ascii=False)
|
|
258
|
+
|
|
259
|
+
# Clear cache for this team
|
|
260
|
+
self._clear_team_cache(team_id)
|
|
261
|
+
|
|
262
|
+
self.logger.info(f"Saved team configuration: {filename}")
|
|
263
|
+
|
|
264
|
+
except Exception as e:
|
|
265
|
+
self.logger.error(f"Failed to save team config {config_type} for {team_id}: {str(e)}")
|
|
266
|
+
raise
|
|
267
|
+
|
|
268
|
+
def _clear_team_cache(self, team_id: str) -> None:
|
|
269
|
+
"""Clear cached configurations for a specific team."""
|
|
270
|
+
keys_to_remove = [key for key in self._cache.keys() if team_id in key]
|
|
271
|
+
for key in keys_to_remove:
|
|
272
|
+
del self._cache[key]
|
|
273
|
+
|
|
274
|
+
def clear_cache(self) -> None:
|
|
275
|
+
"""Clear all cached configurations."""
|
|
276
|
+
self._cache.clear()
|
|
277
|
+
self.logger.debug("Configuration cache cleared")
|