fusesell 1.2.7__tar.gz → 1.2.9__tar.gz

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.

Files changed (43) hide show
  1. {fusesell-1.2.7 → fusesell-1.2.9}/CHANGELOG.md +251 -231
  2. {fusesell-1.2.7/fusesell.egg-info → fusesell-1.2.9}/PKG-INFO +1 -1
  3. {fusesell-1.2.7 → fusesell-1.2.9/fusesell.egg-info}/PKG-INFO +1 -1
  4. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/__init__.py +1 -1
  5. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/stages/initial_outreach.py +176 -29
  6. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/utils/event_scheduler.py +42 -14
  7. {fusesell-1.2.7 → fusesell-1.2.9}/pyproject.toml +1 -1
  8. {fusesell-1.2.7 → fusesell-1.2.9}/LICENSE +0 -0
  9. {fusesell-1.2.7 → fusesell-1.2.9}/MANIFEST.in +0 -0
  10. {fusesell-1.2.7 → fusesell-1.2.9}/README.md +0 -0
  11. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell.egg-info/SOURCES.txt +0 -0
  12. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell.egg-info/dependency_links.txt +0 -0
  13. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell.egg-info/entry_points.txt +0 -0
  14. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell.egg-info/requires.txt +0 -0
  15. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell.egg-info/top_level.txt +0 -0
  16. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell.py +0 -0
  17. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/api.py +0 -0
  18. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/cli.py +0 -0
  19. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/config/__init__.py +0 -0
  20. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/config/prompts.py +0 -0
  21. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/config/settings.py +0 -0
  22. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/pipeline.py +0 -0
  23. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/stages/__init__.py +0 -0
  24. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/stages/base_stage.py +0 -0
  25. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/stages/data_acquisition.py +0 -0
  26. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/stages/data_preparation.py +0 -0
  27. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/stages/follow_up.py +0 -0
  28. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/stages/lead_scoring.py +0 -0
  29. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/tests/conftest.py +0 -0
  30. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/tests/test_api.py +0 -0
  31. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/tests/test_cli.py +0 -0
  32. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/tests/test_data_manager_products.py +0 -0
  33. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/tests/test_data_manager_sales_process.py +0 -0
  34. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/tests/test_data_manager_teams.py +0 -0
  35. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/utils/__init__.py +0 -0
  36. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/utils/birthday_email_manager.py +0 -0
  37. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/utils/data_manager.py +0 -0
  38. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/utils/llm_client.py +0 -0
  39. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/utils/logger.py +0 -0
  40. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/utils/timezone_detector.py +0 -0
  41. {fusesell-1.2.7 → fusesell-1.2.9}/fusesell_local/utils/validators.py +0 -0
  42. {fusesell-1.2.7 → fusesell-1.2.9}/requirements.txt +0 -0
  43. {fusesell-1.2.7 → fusesell-1.2.9}/setup.cfg +0 -0
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to FuseSell Local will be documented in this file.
4
4
 
5
+ # [1.2.9] - 2025-10-24
6
+
7
+ ### Changed
8
+ - Primary sales rep metadata from gs_team_rep now flows into draft prompts, reminders, and signatures so outreach reflects the configured sender.
9
+ - Reminder scheduling stores the Unix timestamp (cron_ts) alongside the ISO string for easier downstream filtering.
10
+ - Greeting sanitizer standardises the first paragraph and removes duplicate salutations while keeping HTML formatting intact.
11
+
12
+ ### Fixed
13
+ - Removed [Your ...] placeholder leftovers inside LLM responses and ensured drafts remain valid HTML even when the model mixes plain text and bullet lists.
14
+ - Reminder creation no longer fails when the data acquisition stage supplies the email address, and follow-up reminders inherit the same customer metadata.
15
+
16
+ # [1.2.8] - 2025-10-24
17
+
18
+ ### Changed
19
+ - Initial outreach resolves the primary sales rep from `gs_team_rep` and injects their identity into prompts, reminders, and draft metadata so outreach reflects real team settings.
20
+
21
+ ### Fixed
22
+ - Sanitizes generated email bodies to replace or remove `[Your …]` placeholders, ensuring signatures contain actual values even when optional rep fields are missing.
23
+ - Reminder scheduling now preserves merged contact emails so follow-up records always carry `customer_email` for downstream automations.
24
+
5
25
  # [1.2.7] - 2025-10-24
6
26
 
7
27
  ### Changed
@@ -48,7 +68,7 @@ All notable changes to FuseSell Local will be documented in this file.
48
68
 
49
69
  ### Fixed
50
70
  - Default team settings seeding now targets the `gs_team_*` columns, preventing initialization failures on fresh databases.
51
-
71
+
52
72
  # [1.2.1] - 2025-10-21
53
73
 
54
74
  ### Changed
@@ -62,234 +82,234 @@ All notable changes to FuseSell Local will be documented in this file.
62
82
  ### Added
63
83
  - Packaged CLI access via `FuseSellCLI` export to support embedded runtimes and automated tests.
64
84
  - pytest coverage (`fusesell_local/tests/test_cli.py`) ensuring the CLI dry-run path remains stable.
65
-
66
- ### Changed
67
- - Distribution renamed to `fusesell` to align with upcoming PyPI publication; console entry point now resolves to `fusesell_local.cli:main`.
68
- - CLI implementation moved into `fusesell_local/cli.py`, with top-level `fusesell.py` delegating for backward compatibility.
69
- - Documentation refreshed to instruct `pip install fusesell` and demonstrate programmatic CLI reuse.
70
- - Published version `1.2.0` to PyPI under the `fusesell` distribution name.
71
-
72
- ### Fixed
73
- - Ensured package metadata includes the CLI module so installations via pip expose the `fusesell` console script.
74
-
75
- ## [1.1.0] - 2025-10-20
76
-
77
- ### Added
78
- - Library-first API (`fusesell_local.api`) exposing `build_config`, `execute_pipeline`, and supporting helpers for embedding FuseSell in external runtimes.
79
- - Public exports in `fusesell_local.__init__` so consumers can import `FuseSellPipeline` and the new helpers directly.
80
- - Programmatic configuration validation error (`ConfigValidationError`) for clearer failures in host applications.
81
-
82
- ### Changed
83
- - CLI now delegates configuration/build/validation/logging to the shared library utilities, ensuring consistent behaviour across CLI and embedded usage.
84
- - Pipeline context now forwards scheduling preferences (timezone, send_immediately, business hour fields) from configuration to stages.
85
-
86
- ### Fixed
87
- - Continuation validation correctly requires `selected_draft_id` when performing `draft_rewrite` or `send` actions.
88
-
89
- ## [1.0.0] - 2025-01-07
90
-
91
- ### Added - Core Infrastructure Complete
92
-
93
- #### CLI Interface
94
- - Complete command-line interface with 25+ configuration options
95
- - Comprehensive argument validation and error handling
96
- - Multiple output formats (JSON, YAML, text)
97
- - Dry-run mode for safe testing
98
- - Process continuation support for resuming executions
99
-
100
- #### Pipeline Engine
101
- - Full pipeline orchestration with stage control
102
- - Business logic validation extracted from original YAML workflows
103
- - Sequential stage execution with data flow management
104
- - Human-in-the-loop controls and approval points
105
- - Comprehensive error handling and recovery mechanisms
106
- - Execution tracking and detailed logging
107
-
108
- #### Data Management
109
- - SQLite database with complete schema for all data entities
110
- - CRUD operations for executions, customers, lead scores, and email drafts
111
- - Data export/import functionality for backup and migration
112
- - Local data storage ensuring complete data ownership
113
-
114
- #### Configuration System
115
- - Team-specific configuration management
116
- - Customizable LLM prompts for all stages
117
- - Configurable scoring criteria and email templates
118
- - JSON-based configuration files with validation
119
-
120
- #### LLM Integration
121
- - OpenAI GPT-4o-mini client with error handling
122
- - Structured response parsing and validation
123
- - Token usage tracking and optimization
124
- - Response caching and retry mechanisms
125
-
126
- #### Documentation
127
- - Comprehensive README with installation and usage instructions
128
- - Technical documentation covering architecture and APIs
129
- - Business logic documentation extracted from original system
130
- - Troubleshooting guide and development workflow
131
-
132
- ### Technical Details
133
-
134
- #### Project Structure
135
- ```
136
- fusesell-local/
137
- fusesell.py # Main CLI entry point
138
- requirements.txt # Python dependencies
139
- setup.py # Package installation
140
- README.md # User documentation
141
- TECHNICAL.md # Technical documentation
142
- CHANGELOG.md # This file
143
- business_logic.md # Business logic documentation
144
- fusesell_local/ # Main package
145
- pipeline.py # Pipeline orchestrator
146
- stages/ # Pipeline stages (base implementation)
147
- utils/ # Utilities (data, LLM, validation, logging)
148
- config/ # Configuration management
149
- fusesell_data/ # Local data storage
150
- config/ # Configuration files
151
- drafts/ # Generated email drafts
152
- logs/ # Execution logs
153
- ```
154
-
155
- #### Key Features Implemented
156
- - **Local Execution**: Complete data ownership with no external dependencies except LLM API
157
- - **Business Logic Preservation**: All orchestration intelligence from original YAML workflows
158
- - **Flexible Data Sources**: Support for websites, business cards, social media, and manual input
159
- - **Stage Control**: Skip stages, stop after specific stages, save intermediate results
160
- - **Process Continuation**: Resume executions from any point with specific actions
161
- - **Comprehensive Validation**: Input validation, configuration validation, and error handling
162
- - **Extensible Architecture**: Modular design for easy customization and extension
163
-
164
- #### Database Schema
165
- - `executions`: Execution tracking and metadata
166
- - `stage_results`: Individual stage outputs and status
167
- - `customers`: Customer profiles and contact information
168
- - `lead_scores`: Scoring results and recommendations
169
- - `email_drafts`: Generated email content and variations
170
-
171
- #### Configuration Files
172
- - `prompts.json`: LLM prompts for all stages
173
- - `scoring_criteria.json`: Lead scoring rules and weights
174
- - `email_templates.json`: Email template variations
175
- - `team_settings.json`: Team-specific configurations
176
-
177
- ### Next Phase - Stage Implementations
178
-
179
- The core infrastructure is complete and ready for individual stage implementations:
180
-
181
- #### Planned Stage Development
182
- 1. **Data Acquisition**: Website scraping, business card OCR, social media extraction
183
- 2. **Data Preparation**: AI-powered data structuring and pain point identification
184
- 3. **Lead Scoring**: Product-customer fit evaluation with detailed breakdowns
185
- 4. **Initial Outreach**: Personalized email generation with multiple approaches
186
- 5. **Follow-up**: Context-aware follow-up sequences and timing optimization
187
-
188
- #### Development Status
189
- - Core infrastructure (CLI, pipeline, database, configuration)
190
- - Business logic validation and orchestration rules
191
- - Process continuation and human-in-the-loop controls
192
- - Comprehensive documentation and user guides
193
- - Individual stage implementations (next development phase)
194
-
195
- ### Migration from Server-Based System
196
-
197
- This release represents a complete conversion of the server-based FuseSell system to a local implementation:
198
-
199
- #### Preserved Features
200
- - All business logic and orchestration intelligence
201
- - Team-specific prompts and configuration
202
- - Human approval workflows and controls
203
- - Comprehensive logging and execution tracking
204
- - Multi-stage pipeline with flexible control
205
-
206
- #### Enhanced Features
207
- - Complete local data ownership and privacy
208
- - Command-line interface with extensive options
209
- - Process continuation and recovery capabilities
210
- - Flexible data source handling
211
- - Enhanced error handling and validation
212
-
213
- #### Architectural Improvements
214
- - Modular, extensible design
215
- - Comprehensive input validation
216
- - Local SQLite database for performance
217
- - Configuration-driven customization
218
- - Detailed technical documentation
219
-
220
- ### Installation and Usage
221
-
222
- #### Requirements
223
- - Python 3.8+
224
- - OpenAI API key
225
- - 50MB disk space for installation
226
- - Additional space for data storage (varies by usage)
227
-
228
- #### Quick Start
229
- ```bash
230
- # Install dependencies
231
- pip install -r requirements.txt
232
-
233
- # Run basic execution
234
- python fusesell.py --openai-api-key YOUR_API_KEY \
235
- --org-id your_org \
236
- --org-name "Your Company" \
237
- --customer-website "https://example.com"
238
- ```
239
-
240
- #### Advanced Usage
241
- ```bash
242
- # Full pipeline with custom settings
243
- python fusesell.py --openai-api-key sk-xxx \
244
- --org-id rta \
245
- --org-name "RTA Corp" \
246
- --customer-website "https://example.com" \
247
- --customer-name "Acme Inc" \
248
- --contact-name "John Doe" \
249
- --team-id sales_team_1 \
250
- --language english \
251
- --output-format json \
252
- --data-dir ./custom_data
253
- ```
254
-
255
- ### Support and Development
256
-
257
- For technical support, feature requests, or contributions:
258
- - Review the technical documentation in `TECHNICAL.md`
259
- - Check the troubleshooting guide for common issues
260
- - Refer to the business logic documentation for workflow details
261
- - Contact the development team for advanced customization needs
262
-
263
- ---
264
-
265
- ## Future Releases
266
-
267
- ### [1.1.0] - Planned
268
- - Complete data acquisition stage implementation
269
- - Website scraping with content extraction
270
- - Business card OCR processing
271
- - Social media profile data extraction
272
-
273
- ### [1.2.0] - Planned
274
- - Complete data preparation stage implementation
275
- - AI-powered customer profiling
276
- - Pain point identification and analysis
277
- - Financial and technology stack analysis
278
-
279
- ### [1.3.0] - Planned
280
- - Complete lead scoring stage implementation
281
- - Product-customer fit evaluation
282
- - Detailed scoring breakdowns and recommendations
283
- - Multi-product scoring capabilities
284
-
285
- ### [1.4.0] - Planned
286
- - Complete initial outreach stage implementation
287
- - Personalized email generation
288
- - Multiple draft variations and approaches
289
- - Human review workflow integration
290
-
291
- ### [1.5.0] - Planned
292
- - Complete follow-up stage implementation
293
- - Context-aware follow-up sequences
294
- - Interaction history analysis
85
+
86
+ ### Changed
87
+ - Distribution renamed to `fusesell` to align with upcoming PyPI publication; console entry point now resolves to `fusesell_local.cli:main`.
88
+ - CLI implementation moved into `fusesell_local/cli.py`, with top-level `fusesell.py` delegating for backward compatibility.
89
+ - Documentation refreshed to instruct `pip install fusesell` and demonstrate programmatic CLI reuse.
90
+ - Published version `1.2.0` to PyPI under the `fusesell` distribution name.
91
+
92
+ ### Fixed
93
+ - Ensured package metadata includes the CLI module so installations via pip expose the `fusesell` console script.
94
+
95
+ ## [1.1.0] - 2025-10-20
96
+
97
+ ### Added
98
+ - Library-first API (`fusesell_local.api`) exposing `build_config`, `execute_pipeline`, and supporting helpers for embedding FuseSell in external runtimes.
99
+ - Public exports in `fusesell_local.__init__` so consumers can import `FuseSellPipeline` and the new helpers directly.
100
+ - Programmatic configuration validation error (`ConfigValidationError`) for clearer failures in host applications.
101
+
102
+ ### Changed
103
+ - CLI now delegates configuration/build/validation/logging to the shared library utilities, ensuring consistent behaviour across CLI and embedded usage.
104
+ - Pipeline context now forwards scheduling preferences (timezone, send_immediately, business hour fields) from configuration to stages.
105
+
106
+ ### Fixed
107
+ - Continuation validation correctly requires `selected_draft_id` when performing `draft_rewrite` or `send` actions.
108
+
109
+ ## [1.0.0] - 2025-01-07
110
+
111
+ ### Added - Core Infrastructure Complete
112
+
113
+ #### CLI Interface
114
+ - Complete command-line interface with 25+ configuration options
115
+ - Comprehensive argument validation and error handling
116
+ - Multiple output formats (JSON, YAML, text)
117
+ - Dry-run mode for safe testing
118
+ - Process continuation support for resuming executions
119
+
120
+ #### Pipeline Engine
121
+ - Full pipeline orchestration with stage control
122
+ - Business logic validation extracted from original YAML workflows
123
+ - Sequential stage execution with data flow management
124
+ - Human-in-the-loop controls and approval points
125
+ - Comprehensive error handling and recovery mechanisms
126
+ - Execution tracking and detailed logging
127
+
128
+ #### Data Management
129
+ - SQLite database with complete schema for all data entities
130
+ - CRUD operations for executions, customers, lead scores, and email drafts
131
+ - Data export/import functionality for backup and migration
132
+ - Local data storage ensuring complete data ownership
133
+
134
+ #### Configuration System
135
+ - Team-specific configuration management
136
+ - Customizable LLM prompts for all stages
137
+ - Configurable scoring criteria and email templates
138
+ - JSON-based configuration files with validation
139
+
140
+ #### LLM Integration
141
+ - OpenAI GPT-4o-mini client with error handling
142
+ - Structured response parsing and validation
143
+ - Token usage tracking and optimization
144
+ - Response caching and retry mechanisms
145
+
146
+ #### Documentation
147
+ - Comprehensive README with installation and usage instructions
148
+ - Technical documentation covering architecture and APIs
149
+ - Business logic documentation extracted from original system
150
+ - Troubleshooting guide and development workflow
151
+
152
+ ### Technical Details
153
+
154
+ #### Project Structure
155
+ ```
156
+ fusesell-local/
157
+ fusesell.py # Main CLI entry point
158
+ requirements.txt # Python dependencies
159
+ setup.py # Package installation
160
+ README.md # User documentation
161
+ TECHNICAL.md # Technical documentation
162
+ CHANGELOG.md # This file
163
+ business_logic.md # Business logic documentation
164
+ fusesell_local/ # Main package
165
+ pipeline.py # Pipeline orchestrator
166
+ stages/ # Pipeline stages (base implementation)
167
+ utils/ # Utilities (data, LLM, validation, logging)
168
+ config/ # Configuration management
169
+ fusesell_data/ # Local data storage
170
+ config/ # Configuration files
171
+ drafts/ # Generated email drafts
172
+ logs/ # Execution logs
173
+ ```
174
+
175
+ #### Key Features Implemented
176
+ - **Local Execution**: Complete data ownership with no external dependencies except LLM API
177
+ - **Business Logic Preservation**: All orchestration intelligence from original YAML workflows
178
+ - **Flexible Data Sources**: Support for websites, business cards, social media, and manual input
179
+ - **Stage Control**: Skip stages, stop after specific stages, save intermediate results
180
+ - **Process Continuation**: Resume executions from any point with specific actions
181
+ - **Comprehensive Validation**: Input validation, configuration validation, and error handling
182
+ - **Extensible Architecture**: Modular design for easy customization and extension
183
+
184
+ #### Database Schema
185
+ - `executions`: Execution tracking and metadata
186
+ - `stage_results`: Individual stage outputs and status
187
+ - `customers`: Customer profiles and contact information
188
+ - `lead_scores`: Scoring results and recommendations
189
+ - `email_drafts`: Generated email content and variations
190
+
191
+ #### Configuration Files
192
+ - `prompts.json`: LLM prompts for all stages
193
+ - `scoring_criteria.json`: Lead scoring rules and weights
194
+ - `email_templates.json`: Email template variations
195
+ - `team_settings.json`: Team-specific configurations
196
+
197
+ ### Next Phase - Stage Implementations
198
+
199
+ The core infrastructure is complete and ready for individual stage implementations:
200
+
201
+ #### Planned Stage Development
202
+ 1. **Data Acquisition**: Website scraping, business card OCR, social media extraction
203
+ 2. **Data Preparation**: AI-powered data structuring and pain point identification
204
+ 3. **Lead Scoring**: Product-customer fit evaluation with detailed breakdowns
205
+ 4. **Initial Outreach**: Personalized email generation with multiple approaches
206
+ 5. **Follow-up**: Context-aware follow-up sequences and timing optimization
207
+
208
+ #### Development Status
209
+ - Core infrastructure (CLI, pipeline, database, configuration)
210
+ - Business logic validation and orchestration rules
211
+ - Process continuation and human-in-the-loop controls
212
+ - Comprehensive documentation and user guides
213
+ - Individual stage implementations (next development phase)
214
+
215
+ ### Migration from Server-Based System
216
+
217
+ This release represents a complete conversion of the server-based FuseSell system to a local implementation:
218
+
219
+ #### Preserved Features
220
+ - All business logic and orchestration intelligence
221
+ - Team-specific prompts and configuration
222
+ - Human approval workflows and controls
223
+ - Comprehensive logging and execution tracking
224
+ - Multi-stage pipeline with flexible control
225
+
226
+ #### Enhanced Features
227
+ - Complete local data ownership and privacy
228
+ - Command-line interface with extensive options
229
+ - Process continuation and recovery capabilities
230
+ - Flexible data source handling
231
+ - Enhanced error handling and validation
232
+
233
+ #### Architectural Improvements
234
+ - Modular, extensible design
235
+ - Comprehensive input validation
236
+ - Local SQLite database for performance
237
+ - Configuration-driven customization
238
+ - Detailed technical documentation
239
+
240
+ ### Installation and Usage
241
+
242
+ #### Requirements
243
+ - Python 3.8+
244
+ - OpenAI API key
245
+ - 50MB disk space for installation
246
+ - Additional space for data storage (varies by usage)
247
+
248
+ #### Quick Start
249
+ ```bash
250
+ # Install dependencies
251
+ pip install -r requirements.txt
252
+
253
+ # Run basic execution
254
+ python fusesell.py --openai-api-key YOUR_API_KEY \
255
+ --org-id your_org \
256
+ --org-name "Your Company" \
257
+ --customer-website "https://example.com"
258
+ ```
259
+
260
+ #### Advanced Usage
261
+ ```bash
262
+ # Full pipeline with custom settings
263
+ python fusesell.py --openai-api-key sk-xxx \
264
+ --org-id rta \
265
+ --org-name "RTA Corp" \
266
+ --customer-website "https://example.com" \
267
+ --customer-name "Acme Inc" \
268
+ --contact-name "John Doe" \
269
+ --team-id sales_team_1 \
270
+ --language english \
271
+ --output-format json \
272
+ --data-dir ./custom_data
273
+ ```
274
+
275
+ ### Support and Development
276
+
277
+ For technical support, feature requests, or contributions:
278
+ - Review the technical documentation in `TECHNICAL.md`
279
+ - Check the troubleshooting guide for common issues
280
+ - Refer to the business logic documentation for workflow details
281
+ - Contact the development team for advanced customization needs
282
+
283
+ ---
284
+
285
+ ## Future Releases
286
+
287
+ ### [1.1.0] - Planned
288
+ - Complete data acquisition stage implementation
289
+ - Website scraping with content extraction
290
+ - Business card OCR processing
291
+ - Social media profile data extraction
292
+
293
+ ### [1.2.0] - Planned
294
+ - Complete data preparation stage implementation
295
+ - AI-powered customer profiling
296
+ - Pain point identification and analysis
297
+ - Financial and technology stack analysis
298
+
299
+ ### [1.3.0] - Planned
300
+ - Complete lead scoring stage implementation
301
+ - Product-customer fit evaluation
302
+ - Detailed scoring breakdowns and recommendations
303
+ - Multi-product scoring capabilities
304
+
305
+ ### [1.4.0] - Planned
306
+ - Complete initial outreach stage implementation
307
+ - Personalized email generation
308
+ - Multiple draft variations and approaches
309
+ - Human review workflow integration
310
+
311
+ ### [1.5.0] - Planned
312
+ - Complete follow-up stage implementation
313
+ - Context-aware follow-up sequences
314
+ - Interaction history analysis
295
315
  - Automated timing optimization
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fusesell
3
- Version: 1.2.7
3
+ Version: 1.2.9
4
4
  Summary: Local implementation of FuseSell AI sales automation pipeline
5
5
  Author-email: FuseSell Team <team@fusesell.ai>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fusesell
3
- Version: 1.2.7
3
+ Version: 1.2.9
4
4
  Summary: Local implementation of FuseSell AI sales automation pipeline
5
5
  Author-email: FuseSell Team <team@fusesell.ai>
6
6
  License-Expression: MIT
@@ -32,6 +32,6 @@ __all__ = [
32
32
  "validate_config",
33
33
  ]
34
34
 
35
- __version__ = "1.2.7"
35
+ __version__ = "1.2.9"
36
36
  __author__ = "FuseSell Team"
37
37
  __description__ = "Local implementation of FuseSell AI sales automation pipeline"
@@ -12,13 +12,17 @@ from datetime import datetime
12
12
  from .base_stage import BaseStage
13
13
 
14
14
 
15
- class InitialOutreachStage(BaseStage):
16
- """
17
- Initial Outreach stage with full server executor schema compliance.
18
- Supports: draft_write, draft_rewrite, send, close actions.
19
- """
20
-
21
- def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
15
+ class InitialOutreachStage(BaseStage):
16
+ """
17
+ Initial Outreach stage with full server executor schema compliance.
18
+ Supports: draft_write, draft_rewrite, send, close actions.
19
+ """
20
+
21
+ def __init__(self, *args, **kwargs):
22
+ super().__init__(*args, **kwargs)
23
+ self._active_rep_profile: Dict[str, Any] = {}
24
+
25
+ def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
22
26
  """
23
27
  Execute initial outreach stage with action-based routing (matching server executor).
24
28
 
@@ -99,9 +103,21 @@ class InitialOutreachStage(BaseStage):
99
103
  if not recommended_product:
100
104
  raise ValueError("No product recommendation available for email generation")
101
105
 
102
- # Generate multiple email drafts
103
- email_drafts = self._generate_email_drafts(customer_data, recommended_product, scoring_data, context)
104
-
106
+ rep_profile = self._resolve_primary_sales_rep(context)
107
+ self._active_rep_profile = rep_profile or {}
108
+
109
+ try:
110
+ # Generate multiple email drafts
111
+ email_drafts = self._generate_email_drafts(
112
+ customer_data,
113
+ recommended_product,
114
+ scoring_data,
115
+ context,
116
+ rep_profile=self._active_rep_profile
117
+ )
118
+ finally:
119
+ self._active_rep_profile = {}
120
+
105
121
  # Save drafts to local files and database
106
122
  saved_drafts = self._save_email_drafts(context, email_drafts)
107
123
 
@@ -624,16 +640,31 @@ class InitialOutreachStage(BaseStage):
624
640
  self.logger.error(f"Failed to get auto interaction config for team {team_id}: {str(e)}")
625
641
  return default_config
626
642
 
627
- def _generate_email_drafts(self, customer_data: Dict[str, Any], recommended_product: Dict[str, Any], scoring_data: Dict[str, Any], context: Dict[str, Any]) -> List[Dict[str, Any]]:
628
- """Generate multiple personalized email drafts using LLM."""
629
- if self.is_dry_run():
630
- return self._get_mock_email_drafts(customer_data, recommended_product, context)
631
-
632
- try:
633
- input_data = context.get('input_data', {})
634
- company_info = customer_data.get('companyInfo', {})
635
- contact_info = customer_data.get('primaryContact', {})
636
- pain_points = customer_data.get('painPoints', [])
643
+ def _generate_email_drafts(self, customer_data: Dict[str, Any], recommended_product: Dict[str, Any], scoring_data: Dict[str, Any], context: Dict[str, Any], rep_profile: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
644
+ """Generate multiple personalized email drafts using LLM."""
645
+ if self.is_dry_run():
646
+ return self._get_mock_email_drafts(customer_data, recommended_product, context)
647
+
648
+ try:
649
+ input_data = context.get('input_data', {})
650
+ rep_profile = rep_profile or {}
651
+ if rep_profile:
652
+ primary_name = rep_profile.get('name')
653
+ if primary_name:
654
+ input_data['staff_name'] = primary_name
655
+ self.config['staff_name'] = primary_name
656
+ if rep_profile.get('email'):
657
+ input_data.setdefault('staff_email', rep_profile.get('email'))
658
+ if rep_profile.get('phone') or rep_profile.get('primary_phone'):
659
+ input_data.setdefault('staff_phone', rep_profile.get('phone') or rep_profile.get('primary_phone'))
660
+ if rep_profile.get('position'):
661
+ input_data.setdefault('staff_title', rep_profile.get('position'))
662
+ if rep_profile.get('website'):
663
+ input_data.setdefault('staff_website', rep_profile.get('website'))
664
+
665
+ company_info = customer_data.get('companyInfo', {})
666
+ contact_info = customer_data.get('primaryContact', {})
667
+ pain_points = customer_data.get('painPoints', [])
637
668
 
638
669
  prompt_drafts = self._generate_email_drafts_from_prompt(
639
670
  customer_data,
@@ -873,6 +904,7 @@ class InitialOutreachStage(BaseStage):
873
904
  first_name = name_parts[0]
874
905
  else:
875
906
  first_name = contact_name or ''
907
+ context.setdefault('customer_first_name', first_name or contact_name or '')
876
908
 
877
909
  action = input_data.get('action', 'draft_write')
878
910
  action_labels = {
@@ -1000,12 +1032,123 @@ class InitialOutreachStage(BaseStage):
1000
1032
  f'Start with "Hi {en_name}," or "Hello {en_name}," and do not use the surname or placeholders.'
1001
1033
  )
1002
1034
  return 'If the recipient name is unknown, use a neutral greeting like "Hi there," without placeholders.'
1003
-
1004
- def _extract_first_name(self, full_name: str) -> str:
1005
- if not full_name:
1006
- return ''
1007
- parts = full_name.strip().split()
1008
- return parts[-1] if parts else full_name
1035
+
1036
+ def _resolve_primary_sales_rep(self, context: Dict[str, Any]) -> Dict[str, Any]:
1037
+ team_id = context.get('input_data', {}).get('team_id') or self.config.get('team_id')
1038
+ if not team_id:
1039
+ return {}
1040
+ reps = self.get_team_setting('gs_team_rep', team_id, [])
1041
+ if not isinstance(reps, list):
1042
+ return {}
1043
+ for rep in reps:
1044
+ if rep and rep.get('is_primary'):
1045
+ return rep
1046
+ return reps[0] if reps else {}
1047
+
1048
+ def _sanitize_email_body(self, html: str, staff_name: str, rep_profile: Dict[str, Any], customer_first_name: str) -> str:
1049
+ if not html:
1050
+ return ''
1051
+
1052
+ replacements = {
1053
+ '[Your Name]': rep_profile.get('name') or staff_name,
1054
+ '[Your Email]': rep_profile.get('email'),
1055
+ '[Your Phone Number]': rep_profile.get('phone') or rep_profile.get('primary_phone'),
1056
+ '[Your Phone]': rep_profile.get('phone') or rep_profile.get('primary_phone'),
1057
+ '[Your Title]': rep_profile.get('position'),
1058
+ '[Your LinkedIn Profile]': rep_profile.get('linkedin') or rep_profile.get('linkedin_profile'),
1059
+ '[Your LinkedIn Profile URL]': rep_profile.get('linkedin') or rep_profile.get('linkedin_profile'),
1060
+ '[Your Website]': rep_profile.get('website'),
1061
+ }
1062
+
1063
+ for placeholder, value in replacements.items():
1064
+ if value:
1065
+ html = html.replace(placeholder, str(value))
1066
+ else:
1067
+ html = html.replace(placeholder, '')
1068
+
1069
+ html = re.sub(r'\[Your[^<\]]+\]?', '', html, flags=re.IGNORECASE)
1070
+
1071
+ if '<p' not in html.lower():
1072
+ lines = [line.strip() for line in html.splitlines() if line.strip()]
1073
+ if lines:
1074
+ html = ''.join(f'<p>{line}</p>' for line in lines)
1075
+
1076
+ html = self._deduplicate_greeting(html, customer_first_name or '')
1077
+ html = re.sub(r'(<p>\s*</p>)+', '', html, flags=re.IGNORECASE)
1078
+ return html
1079
+
1080
+ def _deduplicate_greeting(self, html: str, customer_first_name: str) -> str:
1081
+ paragraphs = re.findall(r'(<p.*?>.*?</p>)', html, flags=re.IGNORECASE | re.DOTALL)
1082
+ if not paragraphs:
1083
+ return html
1084
+
1085
+ greeting_seen = False
1086
+ cleaned: List[str] = []
1087
+ for para in paragraphs:
1088
+ text = self._strip_html_tags(para).strip()
1089
+ normalized_para = para
1090
+ if self._looks_like_greeting(text):
1091
+ normalized_para = self._standardize_greeting_paragraph(para, customer_first_name)
1092
+ if greeting_seen:
1093
+ continue
1094
+ greeting_seen = True
1095
+ cleaned.append(normalized_para)
1096
+
1097
+ remainder = re.sub(r'(<p.*?>.*?</p>)', '__PARA__', html, flags=re.IGNORECASE | re.DOTALL)
1098
+ rebuilt = ''
1099
+ idx = 0
1100
+ for segment in remainder.split('__PARA__'):
1101
+ rebuilt += segment
1102
+ if idx < len(cleaned):
1103
+ rebuilt += cleaned[idx]
1104
+ idx += 1
1105
+ if idx < len(cleaned):
1106
+ rebuilt += ''.join(cleaned[idx:])
1107
+ return rebuilt
1108
+
1109
+ def _looks_like_greeting(self, text: str) -> bool:
1110
+ lowered = text.lower().replace('\xa0', ' ').strip()
1111
+ return lowered.startswith(('hi ', 'hello ', 'dear '))
1112
+
1113
+ def _standardize_greeting_paragraph(self, paragraph_html: str, customer_first_name: str) -> str:
1114
+ text = self._strip_html_tags(paragraph_html).strip()
1115
+ lowered = text.lower()
1116
+ first_word = next((candidate.title() for candidate in ('dear', 'hello', 'hi') if lowered.startswith(candidate)), 'Hi')
1117
+
1118
+ if customer_first_name:
1119
+ greeting = f"{first_word} {customer_first_name},"
1120
+ else:
1121
+ greeting = f"{first_word} there,"
1122
+
1123
+ remainder = ''
1124
+ match = re.match(r' *(hi|hello|dear)\b[^,]*,(.*)', text, flags=re.IGNORECASE | re.DOTALL)
1125
+ if match:
1126
+ remainder = match.group(2).lstrip()
1127
+ elif lowered.startswith(('hi', 'hello', 'dear')):
1128
+ parts = text.split(',', 1)
1129
+ if len(parts) > 1:
1130
+ remainder = parts[1].lstrip()
1131
+ else:
1132
+ remainder = text[len(text.split(' ', 1)[0]):].lstrip()
1133
+
1134
+ if remainder:
1135
+ sanitized_text = f"{greeting} {remainder}".strip()
1136
+ else:
1137
+ sanitized_text = greeting
1138
+
1139
+ return re.sub(
1140
+ r'(<p.*?>).*?(</p>)',
1141
+ lambda m: f"{m.group(1)}{sanitized_text}{m.group(2)}",
1142
+ paragraph_html,
1143
+ count=1,
1144
+ flags=re.IGNORECASE | re.DOTALL
1145
+ )
1146
+
1147
+ def _extract_first_name(self, full_name: str) -> str:
1148
+ if not full_name:
1149
+ return ''
1150
+ parts = full_name.strip().split()
1151
+ return parts[-1] if parts else full_name
1009
1152
 
1010
1153
  def _strip_code_fences(self, text: str) -> str:
1011
1154
  if not text:
@@ -1102,9 +1245,13 @@ class InitialOutreachStage(BaseStage):
1102
1245
  tags = [tags]
1103
1246
  tags = [str(tag).strip() for tag in tags if str(tag).strip()]
1104
1247
 
1105
- call_to_action = self._extract_call_to_action(email_body)
1106
- personalization_score = self._calculate_personalization_score(email_body, customer_data)
1107
- message_type = entry.get('message_type') or 'Email'
1248
+ call_to_action = self._extract_call_to_action(email_body)
1249
+ personalization_score = self._calculate_personalization_score(email_body, customer_data)
1250
+ message_type = entry.get('message_type') or 'Email'
1251
+ rep_profile = getattr(self, '_active_rep_profile', {}) or {}
1252
+ staff_name = context.get('input_data', {}).get('staff_name') or self.config.get('staff_name', 'Sales Team')
1253
+ first_name = context.get('customer_first_name') or context.get('input_data', {}).get('customer_name') or ''
1254
+ email_body = self._sanitize_email_body(email_body, staff_name, rep_profile, first_name)
1108
1255
 
1109
1256
  metadata = {
1110
1257
  'customer_company': customer_data.get('companyInfo', {}).get('name', 'Unknown'),
@@ -84,6 +84,7 @@ class EventScheduler:
84
84
  status TEXT NOT NULL,
85
85
  task TEXT NOT NULL,
86
86
  cron TEXT NOT NULL,
87
+ cron_ts INTEGER,
87
88
  room_id TEXT,
88
89
  tags TEXT,
89
90
  customextra TEXT,
@@ -115,6 +116,11 @@ class EventScheduler:
115
116
  CREATE INDEX IF NOT EXISTS idx_reminder_task_cron
116
117
  ON reminder_task(cron)
117
118
  """)
119
+
120
+ cursor.execute("PRAGMA table_info(reminder_task)")
121
+ columns = {row[1] for row in cursor.fetchall()}
122
+ if 'cron_ts' not in columns:
123
+ cursor.execute("ALTER TABLE reminder_task ADD COLUMN cron_ts INTEGER")
118
124
 
119
125
  conn.commit()
120
126
  conn.close()
@@ -191,6 +197,19 @@ class EventScheduler:
191
197
  except ValueError:
192
198
  return value_str
193
199
 
200
+ def _to_unix_timestamp(self, value: Union[str, datetime, None]) -> Optional[int]:
201
+ """
202
+ Convert a datetime-like value to a Unix timestamp (seconds).
203
+ """
204
+ iso_value = self._format_datetime(value)
205
+ try:
206
+ parsed = datetime.fromisoformat(iso_value)
207
+ except ValueError:
208
+ return None
209
+ if parsed.tzinfo is None:
210
+ parsed = parsed.replace(tzinfo=timezone.utc)
211
+ return int(parsed.timestamp())
212
+
194
213
  def _build_reminder_payload(
195
214
  self,
196
215
  base_context: Dict[str, Any],
@@ -296,11 +315,13 @@ class EventScheduler:
296
315
 
297
316
  cron_value = self._format_datetime(cron_value or send_time)
298
317
  scheduled_time_str = self._format_datetime(scheduled_time_value or send_time)
318
+ cron_ts = self._to_unix_timestamp(cron_value)
299
319
 
300
320
  return {
301
321
  'status': status,
302
322
  'task': task_label,
303
323
  'cron': cron_value,
324
+ 'cron_ts': cron_ts,
304
325
  'room_id': room_id,
305
326
  'tags': tags,
306
327
  'customextra': customextra,
@@ -346,15 +367,20 @@ class EventScheduler:
346
367
  conn = sqlite3.connect(self.main_db_path)
347
368
  cursor = conn.cursor()
348
369
 
370
+ cron_ts = payload.get('cron_ts')
371
+ if cron_ts is None:
372
+ cron_ts = self._to_unix_timestamp(payload.get('cron'))
373
+
349
374
  cursor.execute("""
350
375
  INSERT INTO reminder_task
351
- (id, status, task, cron, room_id, tags, customextra, org_id, customer_id, task_id, import_uuid, scheduled_time)
352
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
376
+ (id, status, task, cron, cron_ts, room_id, tags, customextra, org_id, customer_id, task_id, import_uuid, scheduled_time)
377
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
353
378
  """, (
354
379
  reminder_id,
355
380
  payload.get('status', 'published'),
356
381
  payload.get('task') or 'FuseSell Reminder',
357
382
  self._format_datetime(payload.get('cron')),
383
+ cron_ts,
358
384
  payload.get('room_id'),
359
385
  tags_str,
360
386
  customextra_str,
@@ -455,6 +481,7 @@ class EventScheduler:
455
481
  draft_id=draft_id,
456
482
  customer_timezone=customer_timezone
457
483
  )
484
+ reminder_payload.setdefault('cron_ts', self._to_unix_timestamp(reminder_payload.get('cron')))
458
485
  reminder_task_id = self._insert_reminder_task(reminder_payload)
459
486
 
460
487
  # Log the scheduling
@@ -585,6 +612,7 @@ class EventScheduler:
585
612
  draft_id=original_draft_id,
586
613
  customer_timezone=event_data['customer_timezone']
587
614
  )
615
+ reminder_payload.setdefault('cron_ts', self._to_unix_timestamp(reminder_payload.get('cron')))
588
616
  reminder_task_id = self._insert_reminder_task(reminder_payload)
589
617
 
590
618
  self.logger.info(f"Scheduled follow-up event {followup_event_id} for {follow_up_time}")
@@ -834,18 +862,18 @@ class EventScheduler:
834
862
 
835
863
  # Convert to list of dictionaries
836
864
  events = []
837
- for row in rows:
838
- event = dict(zip(columns, row))
839
- # Parse event_data JSON
840
- if event['event_data']:
841
- try:
842
- event['event_data'] = json.loads(event['event_data'])
843
- except json.JSONDecodeError:
844
- pass
845
- events.append(event)
846
-
847
- return events
848
-
865
+ for row in rows:
866
+ event = dict(zip(columns, row))
867
+ # Parse event_data JSON
868
+ if event['event_data']:
869
+ try:
870
+ event['event_data'] = json.loads(event['event_data'])
871
+ except json.JSONDecodeError:
872
+ pass
873
+ events.append(event)
874
+
875
+ return events
876
+
849
877
  except Exception as e:
850
878
  self.logger.error(f"Failed to get scheduled events: {str(e)}")
851
879
  return []
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fusesell"
7
- version = "1.2.7"
7
+ version = "1.2.9"
8
8
  description = "Local implementation of FuseSell AI sales automation pipeline"
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes