fusesell 1.3.0__py3-none-any.whl → 1.3.1__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fusesell
3
- Version: 1.3.0
3
+ Version: 1.3.1
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
  fusesell.py,sha256=t5PjkhWEJGINp4k517u0EX0ge7lzuHOUHHro-BE1mGk,596
2
- fusesell-1.3.0.dist-info/licenses/LICENSE,sha256=GDz1ZoC4lB0kwjERpzqc_OdA_awYVso2aBnUH-ErW_w,1070
3
- fusesell_local/__init__.py,sha256=7shpYv9KVMmtCmwRUUrJOIid2cvvUuLpZwnbNNk4TS8,967
2
+ fusesell-1.3.1.dist-info/licenses/LICENSE,sha256=GDz1ZoC4lB0kwjERpzqc_OdA_awYVso2aBnUH-ErW_w,1070
3
+ fusesell_local/__init__.py,sha256=4eRHhad_hjqLaoWSd_wZzPRBuX_V_Aqqf2_o93NeHao,967
4
4
  fusesell_local/api.py,sha256=AcPune5YJdgi7nsMeusCUqc49z5UiycsQb6n3yiV_No,10839
5
5
  fusesell_local/cli.py,sha256=MYnVxuEf5KTR4VcO3sc-VtP9NkWlSixJsYfOWST2Ds0,65859
6
6
  fusesell_local/pipeline.py,sha256=RMF_kgwNEc1ka8-CDJyzIOTSo8PGtR_zPKAgRevhlNo,39913
@@ -12,7 +12,7 @@ fusesell_local/stages/base_stage.py,sha256=ldo5xuHZto7ceEg3i_3rxAx0xPccK4n2jaxEJ
12
12
  fusesell_local/stages/data_acquisition.py,sha256=Td3mwakJRoEYbi3od4v2ZzKOHLgLSgccZVxH3ezs1_4,71081
13
13
  fusesell_local/stages/data_preparation.py,sha256=XWLg9b1w2NrMxLcrWDqB95mRmLQmVIMXpKNaBNr98TQ,52751
14
14
  fusesell_local/stages/follow_up.py,sha256=H9Xek6EoIbHrerQvGTRswXDNFH6zq71DcRxxj0zpo2g,77747
15
- fusesell_local/stages/initial_outreach.py,sha256=hvt0tpJQGDX5-k3pb2SR54IOnhQv2XyYXYtiVVECmts,131850
15
+ fusesell_local/stages/initial_outreach.py,sha256=8Ra-Nmq4njAG1iMNiUW7FQbYnX0h9p5F59OXlyGdFdU,135285
16
16
  fusesell_local/stages/lead_scoring.py,sha256=ir3l849eMGrGLf0OYUcmA1F3FwyYhAplS4niU3R2GRY,60658
17
17
  fusesell_local/tests/conftest.py,sha256=TWUtlP6cNPVOYkTPz-j9BzS_KnXdPWy8D-ObPLHvXYs,366
18
18
  fusesell_local/tests/test_api.py,sha256=763rUVb5pAuAQOovug6Ka0T9eGK8-WVOC_J08M7TETo,1827
@@ -28,8 +28,8 @@ fusesell_local/utils/llm_client.py,sha256=FVc25UlGt6hro7h5Iw7PHSXY3E3_67Xc-SUbHu
28
28
  fusesell_local/utils/logger.py,sha256=sWlV8Tjyz_Z8J4zXKOnNalh8_iD6ytfrwPZpD-wcEOs,6259
29
29
  fusesell_local/utils/timezone_detector.py,sha256=0cAE4c8ZXqCA8AvxRKm6PrFKmAmsbq3HOHR6w-mW3KQ,39997
30
30
  fusesell_local/utils/validators.py,sha256=Z1VzeoxFsnuzlIA_ZaMWoy-0Cgyqupd47kIdljlMDbs,15438
31
- fusesell-1.3.0.dist-info/METADATA,sha256=cBJe2l9s_vsCApaTmAtH_tOjdq9berqTED9CGOtSoEM,35074
32
- fusesell-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
- fusesell-1.3.0.dist-info/entry_points.txt,sha256=Vqek7tbiX7iF4rQkCRBZvT5WrB0HUduqKTsI2ZjhsXo,53
34
- fusesell-1.3.0.dist-info/top_level.txt,sha256=VP9y1K6DEq6gNq2UgLd7ChujxViF6OzeAVCK7IUBXPA,24
35
- fusesell-1.3.0.dist-info/RECORD,,
31
+ fusesell-1.3.1.dist-info/METADATA,sha256=hYcoE3sJK-2TPuxHSUv5yxaoNYu1_B5gW1bfScica5w,35074
32
+ fusesell-1.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
+ fusesell-1.3.1.dist-info/entry_points.txt,sha256=Vqek7tbiX7iF4rQkCRBZvT5WrB0HUduqKTsI2ZjhsXo,53
34
+ fusesell-1.3.1.dist-info/top_level.txt,sha256=VP9y1K6DEq6gNq2UgLd7ChujxViF6OzeAVCK7IUBXPA,24
35
+ fusesell-1.3.1.dist-info/RECORD,,
@@ -32,6 +32,6 @@ __all__ = [
32
32
  "validate_config",
33
33
  ]
34
34
 
35
- __version__ = "1.3.0"
35
+ __version__ = "1.3.1"
36
36
  __author__ = "FuseSell Team"
37
37
  __description__ = "Local implementation of FuseSell AI sales automation pipeline"
@@ -776,12 +776,13 @@ class InitialOutreachStage(BaseStage):
776
776
 
777
777
  for approach in draft_approaches:
778
778
  try:
779
- # Generate email content for this approach
780
- email_content = self._generate_single_email_draft(
781
- customer_data, recommended_product, scoring_data,
782
- approach, context
783
- )
784
-
779
+ # Generate email content for this approach
780
+ email_content = self._generate_single_email_draft(
781
+ customer_data, recommended_product, scoring_data,
782
+ approach, context
783
+ )
784
+ email_content = self._ensure_html_email(email_content, context)
785
+
785
786
  # Generate subject lines for this approach
786
787
  subject_lines = self._generate_subject_lines(
787
788
  customer_data, recommended_product, approach, context
@@ -1587,6 +1588,19 @@ Generate 4 subject lines, one per line, no numbering or bullets:"""
1587
1588
  sanitized = f"<html><body>{sanitized}</body></html>"
1588
1589
 
1589
1590
  return sanitized
1591
+
1592
+ def _ensure_html_email(self, raw_content: Any, context: Dict[str, Any]) -> str:
1593
+ """
1594
+ Normalize potentially plain-text content into HTML output.
1595
+ """
1596
+ if raw_content is None:
1597
+ return "<html><body></body></html>"
1598
+
1599
+ text = str(raw_content)
1600
+ if '<html' in text.lower():
1601
+ return text
1602
+
1603
+ return self._clean_email_content(text, context)
1590
1604
 
1591
1605
  def _extract_call_to_action(self, email_content: str) -> str:
1592
1606
  """Extract the main call-to-action from email content."""
@@ -1657,24 +1671,74 @@ Generate 4 subject lines, one per line, no numbering or bullets:"""
1657
1671
 
1658
1672
  return min(score, 100)
1659
1673
 
1660
- def _generate_template_email(self, customer_data: Dict[str, Any], recommended_product: Dict[str, Any],
1661
- approach: Dict[str, Any], context: Dict[str, Any]) -> str:
1662
- """Generate email using template as fallback."""
1663
- input_data = context.get('input_data', {})
1664
- company_info = customer_data.get('companyInfo', {})
1665
- contact_info = customer_data.get('primaryContact', {})
1666
-
1667
- return f"""Dear {contact_info.get('name', 'there')},
1668
-
1669
- I hope this email finds you well. I'm reaching out from {input_data.get('org_name', 'our company')} regarding a potential opportunity for {company_info.get('name', 'your company')}.
1670
-
1671
- Based on our research of companies in the {company_info.get('industry', 'technology')} sector, I believe {company_info.get('name', 'your company')} could benefit from our {recommended_product.get('product_name', 'solution')}.
1672
-
1673
- We've helped similar organizations achieve significant improvements in their operations. Would you be interested in a brief 15-minute call to discuss how we might be able to help {company_info.get('name', 'your company')} achieve its goals?
1674
-
1675
- Best regards,
1676
- {input_data.get('staff_name', 'Sales Team')}
1677
- {input_data.get('org_name', 'Our Company')}"""
1674
+ def _generate_template_email(self, customer_data: Dict[str, Any], recommended_product: Dict[str, Any],
1675
+ approach: Dict[str, Any], context: Dict[str, Any]) -> str:
1676
+ """Generate a deterministic HTML email when LLM generation is unavailable."""
1677
+ input_data = context.get('input_data', {}) or {}
1678
+ company_info = customer_data.get('companyInfo', {}) or {}
1679
+ contact_info = customer_data.get('primaryContact', {}) or {}
1680
+ identity = context.get('_recipient_identity') or self._resolve_recipient_identity(customer_data, context)
1681
+
1682
+ first_name = identity.get('first_name') or contact_info.get('name') or input_data.get('customer_name') or input_data.get('recipient_name') or 'there'
1683
+ first_name = self._extract_first_name(first_name) if isinstance(first_name, str) else 'there'
1684
+
1685
+ staff_name = input_data.get('staff_name') or self.config.get('staff_name', 'Sales Team')
1686
+ org_name = input_data.get('org_name') or self.config.get('org_name', 'FuseSell')
1687
+ company_name = company_info.get('name', 'your company')
1688
+ industry = company_info.get('industry', 'your industry')
1689
+ approach_name = approach.get('name', 'professional_direct')
1690
+ approach_focus = approach.get('focus', 'business value')
1691
+ approach_tone = approach.get('tone', 'professional')
1692
+
1693
+ benefits: List[str] = []
1694
+ if recommended_product:
1695
+ product_name = recommended_product.get('product_name')
1696
+ benefits = [b for b in (recommended_product.get('key_benefits') or []) if b]
1697
+ if not benefits and product_name:
1698
+ benefits = [
1699
+ f"{product_name} accelerates {company_name}'s {approach_focus} goals",
1700
+ f"Designed specifically for {industry} operators",
1701
+ "Rapid onboarding with dedicated local support"
1702
+ ]
1703
+ if not benefits:
1704
+ benefits = [
1705
+ f"Measurable improvements in {approach_focus}",
1706
+ f"Playbooks tailored for {industry} teams",
1707
+ "Guided adoption with FuseSell specialists"
1708
+ ]
1709
+
1710
+ bullet_html = ''.join(f"<li>{benefit}</li>" for benefit in benefits)
1711
+
1712
+ cta_map = {
1713
+ 'professional_direct': f"Would you have 20 minutes this week to explore how FuseSell can lighten {company_name}'s {approach_focus} workload?",
1714
+ 'consultative': f"Could we schedule a short working session to dig into your current {approach_focus} priorities?",
1715
+ 'industry_expert': f"Shall we review the latest {industry} benchmarks together and map them to your roadmap?",
1716
+ 'relationship_building': f"I'd love to hear how your team is approaching {approach_focus}; is a quick virtual coffee an option?"
1717
+ }
1718
+ cta_text = cta_map.get(approach_name, f"Would you be open to a brief call to discuss {approach_focus} priorities at {company_name}?")
1719
+
1720
+ product_sentence = ""
1721
+ if recommended_product and recommended_product.get('product_name'):
1722
+ product_sentence = f"<p>We engineered <strong>{recommended_product['product_name']}</strong> specifically for teams tackling {approach_focus} in {industry}. It's a natural fit for {company_name}'s next phase.</p>"
1723
+
1724
+ news = company_info.get('recentNews')
1725
+ intro_sentence = f"<p>I'm reaching out because leaders at {company_name} are raising the same questions we hear from other {industry} innovators: how to keep {approach_focus} moving without burning out the team.</p>"
1726
+ if news:
1727
+ intro_sentence = f"<p>I noticed the recent update about {news}. Many {industry} peers use FuseSell to capitalise on moments exactly like this.</p>"
1728
+
1729
+ html = (
1730
+ "<html><body>"
1731
+ f"<p>Hi {first_name},</p>"
1732
+ f"{intro_sentence}"
1733
+ f"<p>From our {approach_tone.lower()} conversations with {industry} operators, three ideas could help {company_name} right away:</p>"
1734
+ f"<ul>{bullet_html}</ul>"
1735
+ f"{product_sentence}"
1736
+ f"<p>{cta_text}</p>"
1737
+ f"<p>Best regards,<br>{staff_name}<br>{org_name}</p>"
1738
+ "</body></html>"
1739
+ )
1740
+
1741
+ return html
1678
1742
 
1679
1743
  def _generate_fallback_draft(self, customer_data: Dict[str, Any], recommended_product: Dict[str, Any], context: Dict[str, Any]) -> List[Dict[str, Any]]:
1680
1744
  """Generate fallback draft when LLM generation fails."""