dhisana 0.0.1.dev116__py3-none-any.whl → 0.0.1.dev236__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.
Files changed (69) hide show
  1. dhisana/schemas/common.py +10 -1
  2. dhisana/schemas/sales.py +203 -22
  3. dhisana/utils/add_mapping.py +0 -2
  4. dhisana/utils/apollo_tools.py +739 -119
  5. dhisana/utils/built_with_api_tools.py +4 -2
  6. dhisana/utils/check_email_validity_tools.py +35 -18
  7. dhisana/utils/check_for_intent_signal.py +1 -2
  8. dhisana/utils/check_linkedin_url_validity.py +34 -8
  9. dhisana/utils/clay_tools.py +3 -2
  10. dhisana/utils/clean_properties.py +1 -4
  11. dhisana/utils/compose_salesnav_query.py +0 -1
  12. dhisana/utils/compose_search_query.py +7 -3
  13. dhisana/utils/composite_tools.py +0 -1
  14. dhisana/utils/dataframe_tools.py +2 -2
  15. dhisana/utils/email_body_utils.py +72 -0
  16. dhisana/utils/email_provider.py +174 -35
  17. dhisana/utils/enrich_lead_information.py +183 -53
  18. dhisana/utils/fetch_openai_config.py +129 -0
  19. dhisana/utils/field_validators.py +1 -1
  20. dhisana/utils/g2_tools.py +0 -1
  21. dhisana/utils/generate_content.py +0 -1
  22. dhisana/utils/generate_email.py +68 -23
  23. dhisana/utils/generate_email_response.py +294 -46
  24. dhisana/utils/generate_flow.py +0 -1
  25. dhisana/utils/generate_linkedin_connect_message.py +9 -2
  26. dhisana/utils/generate_linkedin_response_message.py +137 -66
  27. dhisana/utils/generate_structured_output_internal.py +317 -164
  28. dhisana/utils/google_custom_search.py +150 -44
  29. dhisana/utils/google_oauth_tools.py +721 -0
  30. dhisana/utils/google_workspace_tools.py +278 -54
  31. dhisana/utils/hubspot_clearbit.py +3 -1
  32. dhisana/utils/hubspot_crm_tools.py +718 -272
  33. dhisana/utils/instantly_tools.py +3 -1
  34. dhisana/utils/lusha_tools.py +10 -7
  35. dhisana/utils/mailgun_tools.py +150 -0
  36. dhisana/utils/microsoft365_tools.py +447 -0
  37. dhisana/utils/openai_assistant_and_file_utils.py +121 -177
  38. dhisana/utils/openai_helpers.py +8 -6
  39. dhisana/utils/parse_linkedin_messages_txt.py +1 -3
  40. dhisana/utils/profile.py +37 -0
  41. dhisana/utils/proxy_curl_tools.py +377 -76
  42. dhisana/utils/proxycurl_search_leads.py +426 -0
  43. dhisana/utils/research_lead.py +3 -3
  44. dhisana/utils/sales_navigator_crawler.py +1 -6
  45. dhisana/utils/salesforce_crm_tools.py +323 -50
  46. dhisana/utils/search_router.py +131 -0
  47. dhisana/utils/search_router_jobs.py +51 -0
  48. dhisana/utils/sendgrid_tools.py +126 -91
  49. dhisana/utils/serarch_router_local_business.py +75 -0
  50. dhisana/utils/serpapi_additional_tools.py +290 -0
  51. dhisana/utils/serpapi_google_jobs.py +117 -0
  52. dhisana/utils/serpapi_google_search.py +188 -0
  53. dhisana/utils/serpapi_local_business_search.py +129 -0
  54. dhisana/utils/serpapi_search_tools.py +360 -432
  55. dhisana/utils/serperdev_google_jobs.py +125 -0
  56. dhisana/utils/serperdev_local_business.py +154 -0
  57. dhisana/utils/serperdev_search.py +233 -0
  58. dhisana/utils/smtp_email_tools.py +178 -18
  59. dhisana/utils/test_connect.py +1603 -130
  60. dhisana/utils/trasform_json.py +3 -3
  61. dhisana/utils/web_download_parse_tools.py +0 -1
  62. dhisana/utils/zoominfo_tools.py +2 -3
  63. dhisana/workflow/test.py +1 -1
  64. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/METADATA +1 -1
  65. dhisana-0.0.1.dev236.dist-info/RECORD +100 -0
  66. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/WHEEL +1 -1
  67. dhisana-0.0.1.dev116.dist-info/RECORD +0 -83
  68. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/entry_points.txt +0 -0
  69. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/top_level.txt +0 -0
dhisana/schemas/common.py CHANGED
@@ -364,6 +364,12 @@ class Integration(IntegrationBase):
364
364
  Integration.model_rebuild()
365
365
  IntegrationUpdate.model_rebuild()
366
366
 
367
+ class BodyFormat(str, Enum):
368
+ AUTO = "auto"
369
+ HTML = "html"
370
+ TEXT = "text"
371
+
372
+
367
373
  class SendEmailContext(BaseModel):
368
374
  recipient: str
369
375
  subject: str
@@ -371,6 +377,7 @@ class SendEmailContext(BaseModel):
371
377
  sender_name: str
372
378
  sender_email: str
373
379
  labels: Optional[List[str]]
380
+ body_format: BodyFormat = BodyFormat.AUTO
374
381
 
375
382
  class QueryEmailContext(BaseModel):
376
383
  start_time: str
@@ -385,5 +392,7 @@ class ReplyEmailContext(BaseModel):
385
392
  reply_body: str
386
393
  sender_email: str
387
394
  sender_name: str
395
+ fallback_recipient: Optional[str] = None
388
396
  mark_as_read: str = "True"
389
- add_labels: Optional[List[str]] = None
397
+ add_labels: Optional[List[str]] = None
398
+ reply_body_format: BodyFormat = BodyFormat.AUTO
dhisana/schemas/sales.py CHANGED
@@ -1,24 +1,31 @@
1
+ import json
2
+
1
3
  from uuid import UUID
2
- from pydantic import BaseModel, Field
4
+ from pydantic import BaseModel, Field, field_validator
3
5
  from typing import List, Optional, Dict, Any
4
6
  from enum import Enum
5
7
  from typing import Optional, List, Dict, Literal
6
8
 
7
- from dhisana.schemas.common import User
8
9
 
9
10
  # -----------------------------
10
11
  # Lead-List-Specific Schemas
11
12
  # -----------------------------
12
13
 
13
14
  class Lead(BaseModel):
14
- id: Optional[str] = None
15
+ id: Optional[UUID] = None
15
16
  full_name: Optional[str] = None
16
17
  first_name: Optional[str] = None
17
18
  last_name: Optional[str] = None
18
19
  email: Optional[str] = None
19
20
  user_linkedin_url: Optional[str] = None
20
21
  user_linkedin_salesnav_url: Optional[str] = None
22
+ organization_linkedin_url: Optional[str] = None
23
+ organization_linkedin_salesnav_url: Optional[str] = None
24
+ linkedin_follower_count: Optional[int] = None
21
25
  primary_domain_of_organization: Optional[str] = None
26
+ twitter_handle: Optional[str] = None
27
+ twitch_handle: Optional[str] = None
28
+ github_handle: Optional[str] = None
22
29
  job_title: Optional[str] = None
23
30
  phone: Optional[str] = None
24
31
  headline: Optional[str] = None
@@ -26,25 +33,69 @@ class Lead(BaseModel):
26
33
  organization_name: Optional[str] = None
27
34
  organization_website: Optional[str] = None
28
35
  summary_about_lead: Optional[str] = None
36
+
37
+ qualification_score: Optional[float] = None
38
+ qualification_reason: Optional[str] = None
39
+ revenue: Optional[str] = None
40
+ company_size: Optional[str] = None
41
+ industry: Optional[str] = None
42
+
43
+ keywords: Optional[Any] = None
44
+ tags: List[str] = []
45
+ notes: List[str] = []
46
+ additional_properties: Optional[Dict[str, Any]] = {}
29
47
  workflow_stage: Optional[str] = None
30
- assigned_to: Optional[str] = None
31
- engaged: Optional[bool] = None
48
+
49
+ engaged: bool = False
32
50
  last_contact: Optional[int] = None
33
- additional_properties: Optional[Dict[str, str]] = None
34
51
  research_summary: Optional[str] = None
35
52
  task_ids: Optional[List[str]] = None
36
- email_validation_status: Optional[
37
- Literal["not_started", "in_progress", "valid", "invalid"]
38
- ] = None
39
- linkedin_validation_status: Optional[
40
- Literal["not_started", "in_progress", "valid", "invalid"]
41
- ] = None
42
- research_status: Optional[
43
- Literal["not_started", "in_progress", "done", "failed"]
44
- ] = None
45
- enchrichment_status: Optional[
46
- Literal["not_started", "in_progress", "done", "failed"]
47
- ] = None
53
+ email_validation_status: Optional[str] = None
54
+ linkedin_validation_status: Optional[str] = None
55
+ research_status: Optional[str] = None
56
+ enchrichment_status: Optional[str] = None
57
+
58
+
59
+ @field_validator("linkedin_follower_count", mode="before")
60
+ @classmethod
61
+ def parse_linkedin_follower_count(cls, v):
62
+ if v is None or v == "":
63
+ return None
64
+ if isinstance(v, str):
65
+ v = v.strip()
66
+ if v == "":
67
+ return None
68
+ try:
69
+ return int(v)
70
+ except ValueError:
71
+ raise ValueError("linkedin_follower_count must be an integer")
72
+ return v
73
+
74
+ @field_validator("notes", mode="before")
75
+ @classmethod
76
+ def ensure_notes_list(cls, v):
77
+ """Coerce notes to a list of strings.
78
+ Handles legacy cases where the DB may contain a scalar or JSON string.
79
+ """
80
+ if v is None:
81
+ return []
82
+ if isinstance(v, list):
83
+ # Ensure all elements are strings
84
+ return [str(item) if not isinstance(item, str) else item for item in v]
85
+ if isinstance(v, str):
86
+ # Try to parse JSON array; if not, wrap as single-note list
87
+ try:
88
+ parsed = json.loads(v)
89
+ if isinstance(parsed, list):
90
+ return [str(item) if not isinstance(item, str) else item for item in parsed]
91
+ except Exception:
92
+ pass
93
+ return [v]
94
+ # Fallback: wrap any other scalar/object as a single string entry
95
+ try:
96
+ return [json.dumps(v)]
97
+ except Exception:
98
+ return [str(v)]
48
99
 
49
100
 
50
101
  class LeadList(BaseModel):
@@ -240,9 +291,14 @@ class MessageGenerationInstructions(BaseModel):
240
291
  Holds the user-supplied instructions for generating the message:
241
292
  - instructions_to_generate_message: Plain text or template instructions from the user.
242
293
  - prompt_engineering_guidance: (Optional) Extra guidelines for structuring the prompt.
294
+ - allow_html: Whether HTML output is allowed.
295
+ - html_template: Optional HTML scaffolding or guidance.
243
296
  """
244
297
  instructions_to_generate_message: Optional[str] = None
245
298
  prompt_engineering_guidance: Optional[PromptEngineeringGuidance] = None
299
+ use_cache: Optional[bool] = True
300
+ allow_html: Optional[bool] = False
301
+ html_template: Optional[str] = None
246
302
 
247
303
  class CampaignContext(BaseModel):
248
304
  """
@@ -304,6 +360,7 @@ class MessageItem(BaseModel):
304
360
  ...,
305
361
  description="Body of the message in plain text"
306
362
  )
363
+ html_body: Optional[str] = None
307
364
 
308
365
  class MessageResponse(BaseModel):
309
366
  """
@@ -439,14 +496,14 @@ class HubSpotLeadInformation(BaseModel):
439
496
  organization_name: str = Field("", description="Current Company where lead works")
440
497
  organization_website: str = Field("", description="Current Company website of the lead")
441
498
  organization_linkedin_url : str = Field("", description="Company LinkedIn URL")
442
- additional_properties: Optional[Dict[str, str]] = None
499
+ additional_properties: Optional[Dict[str, Any]] = None
443
500
 
444
501
  class HubSpotCompanyinformation(BaseModel):
445
502
  primary_domain_of_organization: str = Field("", description="Primary domain of the organization")
446
503
  organization_name: str = Field("", description="Current Company where lead works")
447
504
  organization_website: str = Field("", description="Current Company website of the lead")
448
505
  organization_linkedin_url : str = Field("", description="Company LinkedIn URL")
449
- additional_properties: Optional[Dict[str, str]] = None
506
+ additional_properties: Optional[Dict[str, Any]] = None
450
507
 
451
508
 
452
509
  # --------------------------------------------------------------------
@@ -463,6 +520,7 @@ HUBSPOT_TO_LEAD_MAPPING = {
463
520
  "address": "lead_location", # You can choose "city", "state", etc. if you prefer
464
521
  "city": "lead_location",
465
522
  "domain": "primary_domain_of_organization",
523
+ "hs_linkedin_url": "user_linkedin_url",
466
524
  }
467
525
 
468
526
  class SmartListStatus(str, Enum):
@@ -483,6 +541,11 @@ class SmartListSourceType(str, Enum):
483
541
  GOOGLE_SHEETS = "GOOGLE_SHEETS"
484
542
  CUSTOM_WEBSITE = "CUSTOM_WEBSITE"
485
543
  GITHUB = "GITHUB"
544
+ ICP_SEARCH = "ICP_SEARCH"
545
+ LOCAL_BUSINESS = "LOCAL_BUSINESS"
546
+ GOOGLE_JOBS = "GOOGLE_JOBS"
547
+ WEBHOOK = "WEBHOOK"
548
+ GOOGLE_CUSTOM_SITE_SEARCH = "GOOGLE_CUSTOM_SITE_SEARCH"
486
549
 
487
550
  class SourceConfiguration(BaseModel):
488
551
  """
@@ -578,13 +641,18 @@ class SmartListLead(BaseModel):
578
641
  organization_name: Optional[str] = None
579
642
  organization_website: Optional[str] = None
580
643
  summary_about_lead: Optional[str] = None
581
- keywords: Optional[List[str]] = None
644
+ keywords: Optional[Any] = None
582
645
  additional_properties: Optional[Dict[str, Any]] = None
583
646
  research_summary: Optional[str] = None
584
647
 
585
648
  qualification_score: Optional[float] = None
586
649
  qualification_reason: Optional[str] = None
587
650
  source: Optional[str] = None
651
+
652
+ email_validation_status: Optional[str] = None
653
+ linkedin_validation_status: Optional[str] = None
654
+ research_status: Optional[str] = None
655
+ enchrichment_status: Optional[str] = None
588
656
 
589
657
  agent_instance_id: Optional[UUID] = None
590
658
  organization_id: Optional[UUID] = None
@@ -596,7 +664,6 @@ class SmartListLead(BaseModel):
596
664
  revenue: Optional[str] = None
597
665
  company_size: Optional[str] = None
598
666
  industry: Optional[str] = None
599
-
600
667
  class Config:
601
668
  from_attributes = True
602
669
 
@@ -761,3 +828,117 @@ class LeadsQueryFilters(BaseModel):
761
828
  description="Ranges for organization number of employees."
762
829
  )
763
830
 
831
+
832
+ class CompanyQueryFilters(BaseModel):
833
+ """
834
+ Defines the filter parameters for querying companies/organizations in the Apollo database.
835
+ All fields are optional and default to None if not specified by user.
836
+ """
837
+
838
+ # Core company search parameters
839
+ organization_locations: Optional[List[str]] = Field(
840
+ default=None,
841
+ description="List of organization headquarters locations (city, state, country)."
842
+ )
843
+
844
+ organization_num_employees_ranges: Optional[List[str]] = Field(
845
+ default=None,
846
+ description="Employee count ranges, e.g. ['1,10', '11,50', '51,200']. Use specific ranges."
847
+ )
848
+
849
+ min_employees: Optional[int] = Field(
850
+ default=None,
851
+ description="Minimum number of employees (>=1). Internally converted to a numeric range."
852
+ )
853
+
854
+ max_employees: Optional[int] = Field(
855
+ default=None,
856
+ description="Maximum number of employees (<=100000). Internally converted to a numeric range."
857
+ )
858
+
859
+ organization_industries: Optional[List[str]] = Field(
860
+ default=None,
861
+ description="List of organization industries."
862
+ )
863
+
864
+ organization_industry_tag_ids: Optional[List[str]] = Field(
865
+ default=None,
866
+ description="List of industry tag IDs, e.g. ['5567cd4773696439b10b0000']."
867
+ )
868
+
869
+ # Revenue filters
870
+ revenue_range_min: Optional[int] = Field(
871
+ default=None,
872
+ description="Minimum company revenue in USD."
873
+ )
874
+
875
+ revenue_range_max: Optional[int] = Field(
876
+ default=None,
877
+ description="Maximum company revenue in USD."
878
+ )
879
+
880
+ # Funding and growth
881
+ organization_latest_funding_stage_cd: Optional[List[str]] = Field(
882
+ default=None,
883
+ description="List of funding stage codes, e.g. ['2', '3', '10']."
884
+ )
885
+
886
+ # Technology and keywords
887
+ currently_using_any_of_technology_uids: Optional[List[str]] = Field(
888
+ default=None,
889
+ description="Technology UIDs used by the organization, e.g. ['google_font_api']."
890
+ )
891
+
892
+ q_keywords: Optional[str] = Field(
893
+ default=None,
894
+ description="Keywords to search for in company descriptions, names, etc."
895
+ )
896
+
897
+ q_organization_domains: Optional[List[str]] = Field(
898
+ default=None,
899
+ description="Specific company domains to search for, e.g. ['microsoft.com', 'google.com']."
900
+ )
901
+
902
+ # Company-specific filters
903
+ organization_ids: Optional[List[str]] = Field(
904
+ default=None,
905
+ description="Specific Apollo organization IDs to include."
906
+ )
907
+
908
+ not_organization_ids: Optional[List[str]] = Field(
909
+ default=None,
910
+ description="Apollo organization IDs to exclude from results."
911
+ )
912
+
913
+ # Search lists
914
+ q_organization_search_list_id: Optional[str] = Field(
915
+ default=None,
916
+ description="Include only organizations in a specific search list."
917
+ )
918
+
919
+ q_not_organization_search_list_id: Optional[str] = Field(
920
+ default=None,
921
+ description="Exclude organizations in a specific search list."
922
+ )
923
+
924
+ # Sorting
925
+ sort_by_field: Optional[str] = Field(
926
+ default=None,
927
+ description="Sort field, e.g. 'name', 'employee_count', 'last_updated', etc."
928
+ )
929
+
930
+ sort_ascending: Optional[bool] = Field(
931
+ default=None,
932
+ description="Sort ascending (True) or descending (False)."
933
+ )
934
+
935
+ # Additional filters that might be useful
936
+ organization_founded_year_min: Optional[int] = Field(
937
+ default=None,
938
+ description="Minimum founding year for the organization."
939
+ )
940
+
941
+ organization_founded_year_max: Optional[int] = Field(
942
+ default=None,
943
+ description="Maximum founding year for the organization."
944
+ )
@@ -1,9 +1,7 @@
1
1
  import hashlib
2
- from urllib.parse import urlparse
3
2
  from typing import List, Optional
4
3
  import logging
5
4
  from typing import Optional, Dict, Any
6
- from pydantic import ValidationError
7
5
  from dhisana.schemas.sales import MessageItem
8
6
  from dhisana.utils.cache_output_tools import (
9
7
  retrieve_output,