dhisana 0.0.1.dev243__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 (102) hide show
  1. dhisana/__init__.py +1 -0
  2. dhisana/cli/__init__.py +1 -0
  3. dhisana/cli/cli.py +20 -0
  4. dhisana/cli/datasets.py +27 -0
  5. dhisana/cli/models.py +26 -0
  6. dhisana/cli/predictions.py +20 -0
  7. dhisana/schemas/__init__.py +1 -0
  8. dhisana/schemas/common.py +399 -0
  9. dhisana/schemas/sales.py +965 -0
  10. dhisana/ui/__init__.py +1 -0
  11. dhisana/ui/components.py +472 -0
  12. dhisana/utils/__init__.py +1 -0
  13. dhisana/utils/add_mapping.py +352 -0
  14. dhisana/utils/agent_tools.py +51 -0
  15. dhisana/utils/apollo_tools.py +1597 -0
  16. dhisana/utils/assistant_tool_tag.py +4 -0
  17. dhisana/utils/built_with_api_tools.py +282 -0
  18. dhisana/utils/cache_output_tools.py +98 -0
  19. dhisana/utils/cache_output_tools_local.py +78 -0
  20. dhisana/utils/check_email_validity_tools.py +717 -0
  21. dhisana/utils/check_for_intent_signal.py +107 -0
  22. dhisana/utils/check_linkedin_url_validity.py +209 -0
  23. dhisana/utils/clay_tools.py +43 -0
  24. dhisana/utils/clean_properties.py +135 -0
  25. dhisana/utils/company_utils.py +60 -0
  26. dhisana/utils/compose_salesnav_query.py +259 -0
  27. dhisana/utils/compose_search_query.py +759 -0
  28. dhisana/utils/compose_three_step_workflow.py +234 -0
  29. dhisana/utils/composite_tools.py +137 -0
  30. dhisana/utils/dataframe_tools.py +237 -0
  31. dhisana/utils/domain_parser.py +45 -0
  32. dhisana/utils/email_body_utils.py +72 -0
  33. dhisana/utils/email_parse_helpers.py +132 -0
  34. dhisana/utils/email_provider.py +375 -0
  35. dhisana/utils/enrich_lead_information.py +933 -0
  36. dhisana/utils/extract_email_content_for_llm.py +101 -0
  37. dhisana/utils/fetch_openai_config.py +129 -0
  38. dhisana/utils/field_validators.py +426 -0
  39. dhisana/utils/g2_tools.py +104 -0
  40. dhisana/utils/generate_content.py +41 -0
  41. dhisana/utils/generate_custom_message.py +271 -0
  42. dhisana/utils/generate_email.py +278 -0
  43. dhisana/utils/generate_email_response.py +465 -0
  44. dhisana/utils/generate_flow.py +102 -0
  45. dhisana/utils/generate_leads_salesnav.py +303 -0
  46. dhisana/utils/generate_linkedin_connect_message.py +224 -0
  47. dhisana/utils/generate_linkedin_response_message.py +317 -0
  48. dhisana/utils/generate_structured_output_internal.py +462 -0
  49. dhisana/utils/google_custom_search.py +267 -0
  50. dhisana/utils/google_oauth_tools.py +727 -0
  51. dhisana/utils/google_workspace_tools.py +1294 -0
  52. dhisana/utils/hubspot_clearbit.py +96 -0
  53. dhisana/utils/hubspot_crm_tools.py +2440 -0
  54. dhisana/utils/instantly_tools.py +149 -0
  55. dhisana/utils/linkedin_crawler.py +168 -0
  56. dhisana/utils/lusha_tools.py +333 -0
  57. dhisana/utils/mailgun_tools.py +156 -0
  58. dhisana/utils/mailreach_tools.py +123 -0
  59. dhisana/utils/microsoft365_tools.py +455 -0
  60. dhisana/utils/openai_assistant_and_file_utils.py +267 -0
  61. dhisana/utils/openai_helpers.py +977 -0
  62. dhisana/utils/openapi_spec_to_tools.py +45 -0
  63. dhisana/utils/openapi_tool/__init__.py +1 -0
  64. dhisana/utils/openapi_tool/api_models.py +633 -0
  65. dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +271 -0
  66. dhisana/utils/openapi_tool/openapi_tool.py +319 -0
  67. dhisana/utils/parse_linkedin_messages_txt.py +100 -0
  68. dhisana/utils/profile.py +37 -0
  69. dhisana/utils/proxy_curl_tools.py +1226 -0
  70. dhisana/utils/proxycurl_search_leads.py +426 -0
  71. dhisana/utils/python_function_to_tools.py +83 -0
  72. dhisana/utils/research_lead.py +176 -0
  73. dhisana/utils/sales_navigator_crawler.py +1103 -0
  74. dhisana/utils/salesforce_crm_tools.py +477 -0
  75. dhisana/utils/search_router.py +131 -0
  76. dhisana/utils/search_router_jobs.py +51 -0
  77. dhisana/utils/sendgrid_tools.py +162 -0
  78. dhisana/utils/serarch_router_local_business.py +75 -0
  79. dhisana/utils/serpapi_additional_tools.py +290 -0
  80. dhisana/utils/serpapi_google_jobs.py +117 -0
  81. dhisana/utils/serpapi_google_search.py +188 -0
  82. dhisana/utils/serpapi_local_business_search.py +129 -0
  83. dhisana/utils/serpapi_search_tools.py +852 -0
  84. dhisana/utils/serperdev_google_jobs.py +125 -0
  85. dhisana/utils/serperdev_local_business.py +154 -0
  86. dhisana/utils/serperdev_search.py +233 -0
  87. dhisana/utils/smtp_email_tools.py +582 -0
  88. dhisana/utils/test_connect.py +2087 -0
  89. dhisana/utils/trasform_json.py +173 -0
  90. dhisana/utils/web_download_parse_tools.py +189 -0
  91. dhisana/utils/workflow_code_model.py +5 -0
  92. dhisana/utils/zoominfo_tools.py +357 -0
  93. dhisana/workflow/__init__.py +1 -0
  94. dhisana/workflow/agent.py +18 -0
  95. dhisana/workflow/flow.py +44 -0
  96. dhisana/workflow/task.py +43 -0
  97. dhisana/workflow/test.py +90 -0
  98. dhisana-0.0.1.dev243.dist-info/METADATA +43 -0
  99. dhisana-0.0.1.dev243.dist-info/RECORD +102 -0
  100. dhisana-0.0.1.dev243.dist-info/WHEEL +5 -0
  101. dhisana-0.0.1.dev243.dist-info/entry_points.txt +2 -0
  102. dhisana-0.0.1.dev243.dist-info/top_level.txt +1 -0
@@ -0,0 +1,965 @@
1
+ import json
2
+
3
+ from uuid import UUID
4
+ from pydantic import BaseModel, Field, field_validator
5
+ from typing import List, Optional, Dict, Any
6
+ from enum import Enum
7
+ from typing import Optional, List, Dict, Literal
8
+
9
+
10
+ # -----------------------------
11
+ # Lead-List-Specific Schemas
12
+ # -----------------------------
13
+
14
+ class Lead(BaseModel):
15
+ id: Optional[UUID] = None
16
+ full_name: Optional[str] = None
17
+ first_name: Optional[str] = None
18
+ last_name: Optional[str] = None
19
+ email: Optional[str] = None
20
+ user_linkedin_url: Optional[str] = None
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
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
29
+ job_title: Optional[str] = None
30
+ phone: Optional[str] = None
31
+ headline: Optional[str] = None
32
+ lead_location: Optional[str] = None
33
+ organization_name: Optional[str] = None
34
+ organization_website: Optional[str] = None
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]] = {}
47
+ workflow_stage: Optional[str] = None
48
+
49
+ engaged: bool = False
50
+ last_contact: Optional[int] = None
51
+ research_summary: Optional[str] = None
52
+ task_ids: Optional[List[str]] = 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)]
99
+
100
+
101
+ class LeadList(BaseModel):
102
+ id: Optional[str] = None
103
+ name: str
104
+ sources: List[str]
105
+ tags: List[str]
106
+ category: str
107
+ leads_count: int
108
+ assigned_users: List[str]
109
+ updated_at: int
110
+ status: Literal["connected", "disconnected", "coming soon"]
111
+ leads: Optional[List[Lead]] = None
112
+ public: Optional[bool] = None
113
+
114
+ # -----------------------------
115
+ # Task-Specific Schemas
116
+ # -----------------------------
117
+
118
+ class TaskStatus(str, Enum):
119
+ PENDING = "PENDING"
120
+ RUNNING = "RUNNING"
121
+ FAILED = "FAILED"
122
+ COMPLETED = "COMPLETED"
123
+
124
+ class TaskBase(BaseModel):
125
+ name: str
126
+ task_type: str
127
+ data_id: Optional[UUID] = None
128
+ data_type: Optional[str] = None
129
+
130
+ inputs: Optional[List[Dict[str, Any]]] = []
131
+ outputs: Optional[List[Dict[str, Any]]] = []
132
+
133
+ class TaskCreate(TaskBase):
134
+ pass
135
+
136
+ class TaskUpdate(BaseModel):
137
+ status: Optional[TaskStatus] = None
138
+ logs: Optional[List[Any]] = None
139
+ metrics: Optional[Dict[str, Any]] = None
140
+ outputs: Optional[List[Dict[str, Any]]] = None
141
+
142
+ class Task(TaskBase):
143
+ id: UUID
144
+ status: TaskStatus
145
+ logs: List[Any] = []
146
+ metrics: Dict[str, Any] = {}
147
+ created_at: int # store as ms since epoch
148
+ updated_at: int
149
+ completed_at: Optional[int] = None
150
+
151
+ class Config:
152
+ from_attributes = True
153
+
154
+
155
+ # -----------------------------
156
+ # Campaign-Specific Schemas
157
+ # -----------------------------
158
+
159
+ class SendRules(BaseModel):
160
+ daily_send_limit: Optional[int] = None
161
+ concurrency_limit: Optional[int] = None
162
+ time_window_start: Optional[str] = None # "HH:MM" string
163
+ time_window_end: Optional[str] = None
164
+ block_weekends: Optional[bool] = False
165
+
166
+ class Touch(BaseModel):
167
+ type: str # e.g. 'email', 'linkedin', ...
168
+ action: str # e.g. 'view_linkedin_profile', 'send_connection_request'
169
+ details: str
170
+ delay_days: int
171
+ template_id: Optional[str] = None
172
+
173
+ class PromptEngineeringGuidance(BaseModel):
174
+ tone: str
175
+ word_count: int
176
+ paragraphs: int
177
+
178
+ class LeadLog(BaseModel):
179
+ id: Optional[str] = None
180
+ message: str
181
+ channel: Optional[str] = None
182
+ timestamp: int # ms since epoch
183
+ status: Optional[str] = None
184
+
185
+ class CampaignLead(BaseModel):
186
+ id: Optional[str] = None
187
+ campaign_id: str
188
+ lead_list_id: Optional[str] = None
189
+ lead_id: Optional[str] = None
190
+ lead_name: str
191
+
192
+ status: Optional[str] = None # 'PENDING', 'WAITING_APPROVAL', 'OUTBOUND_PENDING', 'COMPLETED'
193
+ current_step: Optional[int] = 0
194
+ total_steps: Optional[int] = 0
195
+ engaged: Optional[bool] = False
196
+ last_touch: Optional[str] = None
197
+ created_at: Optional[int] = None # ms since epoch
198
+ updated_at: Optional[int] = None
199
+
200
+ logs: Optional[List[LeadLog]] = None
201
+
202
+ class CampaignCounter(BaseModel):
203
+ id: Optional[str] = None
204
+ campaign_id: str
205
+ date: str # "YYYY-MM-DD"
206
+ daily_sends: int
207
+ current_concurrency: int
208
+
209
+ class CampaignStatus(str, Enum):
210
+ DRAFT = "DRAFT"
211
+ ACTIVE = "ACTIVE"
212
+ PAUSED = "PAUSED"
213
+ COMPLETED = "COMPLETED"
214
+
215
+ class PendingEvent(BaseModel):
216
+ event_id: str
217
+ lead_id: str
218
+ touch_index: int
219
+ channel: str
220
+ action: str
221
+ subject: str
222
+ message: str
223
+ created_at: int # ms since epoch
224
+
225
+ class Campaign(BaseModel):
226
+ id: str
227
+ name: str
228
+ description: str
229
+ lead_lists: List[str]
230
+ run_mode: str
231
+ updated_at: int # ms since epoch
232
+
233
+ tags: Optional[List[str]] = None
234
+ channel: Optional[str] = None
235
+ mission_objective: Optional[str] = None
236
+ mission_progress: Optional[int] = None
237
+ ai_alerts: Optional[List[str]] = None
238
+ automatic_adjustments: Optional[List[str]] = None
239
+
240
+ product_name: Optional[str] = None
241
+ value_prop: Optional[str] = None
242
+ call_to_action: Optional[str] = None
243
+ pain_points: Optional[List[str]] = None
244
+ proof_points: Optional[List[str]] = None
245
+ prompt_engineering_guidance: Optional[PromptEngineeringGuidance] = None
246
+ prompt_templates: Optional[List[Dict[str, Any]]] = None
247
+ touches: Optional[List[Touch]] = None
248
+ send_rules: Optional[SendRules] = None
249
+
250
+ status: Optional[CampaignStatus] = None
251
+ start_date: Optional[int] = None # ms since epoch
252
+ pause_date: Optional[int] = None
253
+ end_date: Optional[int] = None
254
+
255
+ leads: Optional[List[CampaignLead]] = None
256
+ counter: Optional[CampaignCounter] = None
257
+ pending_events: Optional[List[PendingEvent]] = None
258
+
259
+ class Config:
260
+ from_attributes = True
261
+
262
+ # ---------------------------------------------------------------------
263
+ # New Classes with Comments
264
+ # ---------------------------------------------------------------------
265
+ class ChannelType(str, Enum):
266
+ """
267
+ Enumerates the different communication channels.
268
+ """
269
+ NEW_EMAIL = "new_email"
270
+ LINKEDIN_CONNECT_MESSAGE = "linkedin_connect_message"
271
+ REPLY_EMAIL = "reply_email"
272
+ LINKEDIN_USER_MESSAGE = "linkedin_user_message"
273
+ CUSTOM_MESSAGE = "custom_message"
274
+
275
+ class SenderInfo(BaseModel):
276
+ """
277
+ Holds information about the sender:
278
+ - sender_full_name: Full name of the sender.
279
+ - sender_first_name: Sender's first name.
280
+ - sender_last_name: Sender's last name.
281
+ - sender_bio: Optional biography or short description of the sender.
282
+ """
283
+ sender_full_name: Optional[str] = None
284
+ sender_first_name: Optional[str] = None
285
+ sender_last_name: Optional[str] = None
286
+ sender_email: Optional[str] = None
287
+ sender_bio: Optional[str] = None
288
+
289
+
290
+ class MessageGenerationInstructions(BaseModel):
291
+ """
292
+ Holds the user-supplied instructions for generating the message:
293
+ - instructions_to_generate_message: Plain text or template instructions from the user.
294
+ - prompt_engineering_guidance: (Optional) Extra guidelines for structuring the prompt.
295
+ - allow_html: Whether HTML output is allowed.
296
+ - html_template: Optional HTML scaffolding or guidance.
297
+ """
298
+ instructions_to_generate_message: Optional[str] = None
299
+ prompt_engineering_guidance: Optional[PromptEngineeringGuidance] = None
300
+ use_cache: Optional[bool] = True
301
+ allow_html: Optional[bool] = False
302
+ html_template: Optional[str] = None
303
+
304
+ class CampaignContext(BaseModel):
305
+ """
306
+ Represents the context of the campaign or marketing effort:
307
+ - product_name: Name of the product or service.
308
+ - value_prop: Value proposition of the product.
309
+ - call_to_action: Suggested CTA for the user to take.
310
+ - pain_points: List of known pain points for the lead or market.
311
+ - proof_points: List of proof points or social proof for the product.
312
+ - email_triage_guidelines: Guidelines for triaging or responding to emails.
313
+ - linkedin_triage_guidelines: Guidelines for triaging or responding to LinkedIn messages.
314
+ """
315
+ product_name: Optional[str] = None
316
+ value_prop: Optional[str] = None
317
+ call_to_action: Optional[str] = None
318
+ pain_points: Optional[List[str]] = None
319
+ proof_points: Optional[List[str]] = None
320
+ email_triage_guidelines: Optional[str] = None
321
+ linkedin_triage_guidelines: Optional[str] = None
322
+
323
+
324
+ class MessageItem(BaseModel):
325
+ """
326
+ Represents a single message item in a conversation.
327
+ """
328
+ message_id: str = Field(
329
+ ...,
330
+ description="Unique identifier for the message"
331
+ )
332
+ thread_id: str = Field(
333
+ ...,
334
+ description="Unique identifier for the conversation thread"
335
+ )
336
+ sender_name: str = Field(
337
+ ...,
338
+ description="Sender's display name (if available)"
339
+ )
340
+ sender_email: str = Field(
341
+ ...,
342
+ description="Sender's email address"
343
+ )
344
+ receiver_name: str = Field(
345
+ ...,
346
+ description="Comma-separated list of receiver names"
347
+ )
348
+ receiver_email: str = Field(
349
+ ...,
350
+ description="Comma-separated list of receiver emails"
351
+ )
352
+ iso_datetime: str = Field(
353
+ ...,
354
+ description="Date/time of the message in ISO 8601 format"
355
+ )
356
+ subject: str = Field(
357
+ ...,
358
+ description="Subject of the message"
359
+ )
360
+ body: str = Field(
361
+ ...,
362
+ description="Body of the message in plain text"
363
+ )
364
+ html_body: Optional[str] = None
365
+
366
+ class MessageResponse(BaseModel):
367
+ """
368
+ Model representing the structured response for a LinkedIn conversation triage.
369
+ - triage_status: "AUTOMATIC" or "REQUIRES_APPROVAL"
370
+ - triage_reason: Optional reason text if triage_status == "REQUIRES_APPROVAL"
371
+ - response_action_to_take: The recommended next action (e.g., SEND_REPLY, WAIT_TO_SEND, STOP_SENDING, etc.)
372
+ - message_item: The actual message to be sent or used for approval.
373
+ """
374
+ triage_status: str # "AUTOMATIC" or "REQUIRES_APPROVAL"
375
+ triage_reason: Optional[str]
376
+ response_action_to_take: str
377
+ message_item: MessageItem
378
+
379
+
380
+ class ConversationContext(BaseModel):
381
+ """
382
+ Contains the current conversation threads for email or LinkedIn:
383
+ - current_email_thread: Existing email thread if any.
384
+ - current_linkedin_thread: Existing LinkedIn thread if any.
385
+ """
386
+ current_email_thread: Optional[List[MessageItem]] = None
387
+ current_linkedin_thread: Optional[List[MessageItem]] = None
388
+
389
+ class ExternalDataSources(BaseModel):
390
+ """
391
+ Holds references to external or third-party data integrations:
392
+ - external_openai_vector_store_id: ID for a vector store used for context retrieval.
393
+ """
394
+ external_openai_vector_store_id: Optional[str] = None
395
+
396
+ class ContentGenerationContext(BaseModel):
397
+ """
398
+ Consolidates all relevant data needed for generating content:
399
+ 1) campaign_context: Details about the current campaign or marketing approach.
400
+ 2) lead_info: The lead's basic information.
401
+ 3) sender_info: Who is sending the message.
402
+ 4) external_known_data: Any references to external data sources (e.g., vector store IDs).
403
+ 5) current_conversation_context: Current or ongoing conversation threads (email or LinkedIn).
404
+ 6) target_channel_type: Which channel we are generating content for (email, LinkedIn, etc.).
405
+ """
406
+ campaign_context: Optional[CampaignContext] = None
407
+ lead_info: Optional[Lead] = None
408
+ sender_info: Optional[SenderInfo] = None
409
+ external_known_data: Optional[ExternalDataSources] = None
410
+ current_conversation_context: Optional[ConversationContext] = None
411
+ target_channel_type: Optional[ChannelType] = None
412
+ message_instructions: MessageGenerationInstructions = None
413
+
414
+
415
+ # -----------------------------
416
+ # TOUCHPOINT + EXECUTION
417
+ # -----------------------------
418
+ class TouchPointStatus(BaseModel):
419
+ """
420
+ Holds the dynamic state of each lead’s progress in the campaign.
421
+ """
422
+ is_connected_on_linkedin: bool
423
+ first_introduction_message_sent: bool
424
+ got_postive_response: bool
425
+ got_negative_response: bool
426
+
427
+ # Logging how many total messages or touches we’ve attempted.
428
+ messages_sent: int
429
+
430
+ # If the lead explicitly requested no further contact.
431
+ did_opt_out: bool
432
+
433
+ # Specific dispositions from the lead's response (e.g. "not_interested", "schedule_demo", etc.)
434
+ response_disposition: Optional[str] = None
435
+
436
+ # Maximum allowed attempts (touches)
437
+ max_touch_points: int
438
+
439
+ likely_to_engage_score: int
440
+
441
+ # Date/time fields
442
+ last_profile_viewed_date: Optional[str] = None
443
+ last_linkedin_message_sent_date: Optional[str] = None
444
+ last_email_sent_date: Optional[str] = None
445
+ last_email_response_received_date: Optional[str] = None
446
+ last_post_date: Optional[str] = None
447
+ last_linkedin_message_received_date: Optional[str] = None
448
+
449
+ # LinkedIn Connection fields
450
+ connection_status: Optional[str] = None # e.g., "connected", "pending"
451
+ connection_request_sent_date: Optional[str] = None
452
+ connection_degree: Optional[str] = None # "1st", "2nd", or "3rd"
453
+
454
+ # Additional fields we want to store
455
+ sdr_user_id: Optional[str] = None
456
+ user_linkedin_url: Optional[str] = None
457
+ user_linkedin_salesnav_url: Optional[str] = None
458
+ full_name: Optional[str] = None
459
+ first_name: Optional[str] = None
460
+ last_name: Optional[str] = None
461
+ location: Optional[str] = None
462
+ is_past_colleague: Optional[bool] = None
463
+ number_of_mutual_connections: Optional[int] = None
464
+ organization_name: Optional[str] = None
465
+
466
+
467
+ class LeadCampaignExecutionContext(BaseModel):
468
+ """
469
+ This is the combined object passed to run_campaign_cadence_for_lead.
470
+ """
471
+ campaign_context: Optional[CampaignContext] = None
472
+ lead_info: Optional[Lead] = None
473
+ sender_info: Optional[SenderInfo] = None
474
+ external_known_data: Optional[ExternalDataSources] = None
475
+ current_conversation_context: Optional[ConversationContext] = None
476
+ message_instructions: MessageGenerationInstructions = MessageGenerationInstructions()
477
+ touchpoint_status: Optional[TouchPointStatus] = None
478
+ current_user_id: Optional[str] = None
479
+ custom_instructions_for_cadence: Optional[str] = None
480
+
481
+
482
+
483
+ # --------------------------------------------------------------------
484
+ # 1. Define your HubSpotLeadInformation model
485
+ # --------------------------------------------------------------------
486
+ class HubSpotLeadInformation(BaseModel):
487
+ full_name: str = Field("", description="Full name of the lead")
488
+ first_name: str = Field("", description="First name of the lead")
489
+ last_name: str = Field("", description="Last name of the lead")
490
+ email: str = Field("", description="Email address of the lead")
491
+ user_linkedin_url: str = Field("", description="LinkedIn URL of the lead")
492
+ primary_domain_of_organization: str = Field("", description="Primary domain of the organization")
493
+ job_title: str = Field("", description="Job Title of the lead")
494
+ phone: str = Field("", description="Phone number of the lead")
495
+ headline: str = Field("", description="Headline of the lead")
496
+ lead_location: str = Field("", description="Location of the lead")
497
+ organization_name: str = Field("", description="Current Company where lead works")
498
+ organization_website: str = Field("", description="Current Company website of the lead")
499
+ organization_linkedin_url : str = Field("", description="Company LinkedIn URL")
500
+ additional_properties: Optional[Dict[str, Any]] = None
501
+
502
+ class HubSpotCompanyinformation(BaseModel):
503
+ primary_domain_of_organization: str = Field("", description="Primary domain of the organization")
504
+ organization_name: str = Field("", description="Current Company where lead works")
505
+ organization_website: str = Field("", description="Current Company website of the lead")
506
+ organization_linkedin_url : str = Field("", description="Company LinkedIn URL")
507
+ additional_properties: Optional[Dict[str, Any]] = None
508
+
509
+
510
+ # --------------------------------------------------------------------
511
+ # 2. Map HubSpot property names -> HubSpotLeadInformation fields
512
+ # --------------------------------------------------------------------
513
+ HUBSPOT_TO_LEAD_MAPPING = {
514
+ "firstname": "first_name",
515
+ "lastname": "last_name",
516
+ "email": "email",
517
+ "phone": "phone",
518
+ "jobtitle": "job_title", # Default HubSpot job title property
519
+ "company": "organization_name", # Map "company" -> "organization_name"
520
+ "website": "organization_website", # Map "website" -> "organization_website"
521
+ "address": "lead_location", # You can choose "city", "state", etc. if you prefer
522
+ "city": "lead_location",
523
+ "domain": "primary_domain_of_organization",
524
+ "hs_linkedin_url": "user_linkedin_url",
525
+ }
526
+
527
+ class SmartListStatus(str, Enum):
528
+ DRAFT = "DRAFT"
529
+ ACTIVE = "ACTIVE"
530
+ COMPLETED = "COMPLETED"
531
+
532
+ class SmartListInputType(str, Enum):
533
+ LEADS = "LEADS"
534
+ ACCOUNTS = "ACCOUNTS"
535
+
536
+ class SmartListSourceType(str, Enum):
537
+ SALES_NAVIGATOR = "SALES_NAVIGATOR"
538
+ GOOGLE_SEARCH = "GOOGLE_SEARCH"
539
+ HUBSPOT = "HUBSPOT"
540
+ APOLLO = "APOLLO"
541
+ CSV = "CSV"
542
+ GOOGLE_SHEETS = "GOOGLE_SHEETS"
543
+ CUSTOM_WEBSITE = "CUSTOM_WEBSITE"
544
+ GITHUB = "GITHUB"
545
+ ICP_SEARCH = "ICP_SEARCH"
546
+ LOCAL_BUSINESS = "LOCAL_BUSINESS"
547
+ GOOGLE_JOBS = "GOOGLE_JOBS"
548
+ WEBHOOK = "WEBHOOK"
549
+ GOOGLE_CUSTOM_SITE_SEARCH = "GOOGLE_CUSTOM_SITE_SEARCH"
550
+
551
+ class SourceConfiguration(BaseModel):
552
+ """
553
+ Defines configuration details for each source type.
554
+ Depending on the source_type, only certain fields
555
+ may be required or used.
556
+ """
557
+ # For Sales Navigator or Apollo or Google Search
558
+ search_query: Optional[str] = None
559
+ # For HubSpot
560
+ list_id: Optional[str] = None
561
+ list_name: Optional[str] = None
562
+ # For CSV
563
+ file_path: Optional[str] = None
564
+ # For Google Sheets
565
+ source_url: Optional[str] = None
566
+ # For Github
567
+ github_search_query: Optional[str] = None
568
+ github_max_repos: Optional[int] = None
569
+ github_max_contributors: Optional[int] = None
570
+
571
+ # Custom website inputs
572
+ custom_instructions_for_doing_pagination: Optional[str] = None
573
+ custom_instructions_for_data_extraction_from_page: Optional[str] = None
574
+ custom_instruction_to_fetch_details_page: Optional[str] = None
575
+
576
+ class SmartListSource(BaseModel):
577
+ """
578
+ A single lead source definition. Contains the
579
+ source type (e.g., 'SALES_NAVIGATOR') plus the
580
+ relevant configuration.
581
+ """
582
+ source_type: SmartListSourceType
583
+ input_type: SmartListInputType
584
+ configuration: SourceConfiguration
585
+
586
+ class SmartList(BaseModel):
587
+ id: Optional[UUID] = None
588
+ name: Optional[str] = None
589
+ description: Optional[str] = None
590
+ category: Optional[str] = None
591
+ status: SmartListStatus = SmartListStatus.DRAFT
592
+ start_date: Optional[int] = None
593
+ end_date: Optional[int] = None
594
+
595
+ sources: Optional[List[SmartListSource]] = None
596
+
597
+ qualification_instructions: Optional[str] = None
598
+ fetch_instructions: Optional[str] = None
599
+ max_items_to_search: Optional[int] = None
600
+ max_items_in_qualified_results: Optional[int] = None
601
+ enrich_information_from_online_research: bool = False
602
+ enrich_information_from_lead_website: bool = False
603
+ enrich_with_valid_email: bool = False
604
+ enrich_with_phone_number: bool = False
605
+ min_qualification_score: Optional[int] = None
606
+ number_of_leads_per_company: Optional[int] = None
607
+
608
+ agent_instance_id: Optional[UUID] = None
609
+ organization_id: Optional[UUID] = None
610
+ created_by: Optional[UUID] = None
611
+ created_at: Optional[int] = None
612
+ updated_by: Optional[UUID] = None
613
+ updated_at: Optional[int] = None
614
+ file_content: Optional[List[Any]] = None
615
+ account_qualification_instructions: Optional[str] = None
616
+ exclude_company_domains: Optional[List[Any]] = None
617
+ exclude_leads: Optional[List[Any]] = None
618
+
619
+ class Config:
620
+ from_attributes = True
621
+
622
+ class SmartListLead(BaseModel):
623
+ id: Optional[UUID] = None
624
+
625
+ smart_list_id: Optional[UUID] = None
626
+
627
+ full_name: Optional[str] = None
628
+ first_name: Optional[str] = None
629
+ last_name: Optional[str] = None
630
+ email: Optional[str] = None
631
+ user_linkedin_url: Optional[str] = None
632
+ user_linkedin_salesnav_url: Optional[str] = None
633
+ organization_linkedin_url: Optional[str] = None
634
+ organization_linkedin_salesnav_url: Optional[str] = None
635
+ primary_domain_of_organization: Optional[str] = None
636
+ twitter_handle: Optional[str] = None
637
+ github_handle: Optional[str] = None
638
+ job_title: Optional[str] = None
639
+ phone: Optional[str] = None
640
+ headline: Optional[str] = None
641
+ lead_location: Optional[str] = None
642
+ organization_name: Optional[str] = None
643
+ organization_website: Optional[str] = None
644
+ summary_about_lead: Optional[str] = None
645
+ keywords: Optional[Any] = None
646
+ additional_properties: Optional[Dict[str, Any]] = None
647
+ research_summary: Optional[str] = None
648
+
649
+ qualification_score: Optional[float] = None
650
+ qualification_reason: Optional[str] = None
651
+ source: Optional[str] = None
652
+
653
+ email_validation_status: Optional[str] = None
654
+ linkedin_validation_status: Optional[str] = None
655
+ research_status: Optional[str] = None
656
+ enchrichment_status: Optional[str] = None
657
+
658
+ agent_instance_id: Optional[UUID] = None
659
+ organization_id: Optional[UUID] = None
660
+ created_by: Optional[UUID] = None
661
+ created_at: Optional[int] = None
662
+ updated_by: Optional[UUID] = None
663
+ updated_at: Optional[int] = None
664
+
665
+ revenue: Optional[str] = None
666
+ company_size: Optional[str] = None
667
+ industry: Optional[str] = None
668
+ class Config:
669
+ from_attributes = True
670
+
671
+
672
+ class SmartListLog(BaseModel):
673
+ id: Optional[UUID] = None
674
+ message: str
675
+
676
+ smart_list_id: UUID
677
+
678
+ agent_instance_id: Optional[UUID] = None
679
+ organization_id: Optional[UUID] = None
680
+ created_by: Optional[UUID] = None
681
+ created_at: Optional[int] = None
682
+ updated_by: Optional[UUID] = None
683
+ updated_at: Optional[int] = None
684
+
685
+ class Config:
686
+ from_attributes = True
687
+
688
+ # --------------------------------------------------
689
+ # Updated LeadsQueryFilters
690
+ # --------------------------------------------------
691
+ class LeadsQueryFilters(BaseModel):
692
+ """
693
+ Defines the filter parameters for querying leads in the Apollo database.
694
+ All fields are optional and default to None if not specified by user.
695
+ """
696
+
697
+ # CHANGED: Renamed field to be more descriptive (person's current job titles)
698
+ person_current_titles: Optional[List[str]] = Field(
699
+ default=None,
700
+ description="List of job titles for the person (maps to Apollo's person_titles)."
701
+ )
702
+
703
+ # CHANGED: Renamed field to be more descriptive (person's locations)
704
+ person_locations: Optional[List[str]] = Field(
705
+ default=None,
706
+ description="List of personal locations (city, state, country). Maps to person_locations in Apollo."
707
+ )
708
+
709
+ # CHANGED: Renamed to be more descriptive
710
+ min_employees_in_organization: Optional[int] = Field(
711
+ default=None,
712
+ description="Minimum number of employees (>=1). Internally converted to a numeric range for Apollo."
713
+ )
714
+ max_employees_in_organization: Optional[int] = Field(
715
+ default=None,
716
+ description="Maximum number of employees (<=100000). Internally converted to a numeric range for Apollo."
717
+ )
718
+
719
+ filter_by_signals: Optional[List[str]] = Field(
720
+ default=None,
721
+ description="List of signals to filter by, e.g. ['RECENT_JOB_CHANGE']. Maps internally to search_signal_ids."
722
+ )
723
+ max_number_of_items_to_return: Optional[int] = Field(
724
+ default=None,
725
+ description="Max # of items (<=5000). Default=100."
726
+ )
727
+
728
+ # CHANGED: Renamed to be more descriptive
729
+ industries: Optional[List[str]] = Field(
730
+ default=None,
731
+ description="List of organization industries. Maps to organization_industries in Apollo."
732
+ )
733
+
734
+ # Potential existing fields
735
+ min_revenue_of_the_company: Optional[int] = Field(
736
+ default=None,
737
+ description="Minimum company revenue. Maps to revenueRange[min]."
738
+ )
739
+ max_revenue_of_the_company: Optional[int] = Field(
740
+ default=None,
741
+ description="Maximum company revenue. Maps to revenueRange[max]."
742
+ )
743
+ job_functions: Optional[List[str]] = Field(
744
+ default=None,
745
+ description="List of job functions (not directly used)."
746
+ )
747
+
748
+ # CHANGED: Renamed to be more descriptive
749
+ search_keywords: Optional[str] = Field(
750
+ default=None,
751
+ description="A string of keywords to filter results. Maps to q_keywords in Apollo."
752
+ )
753
+
754
+ # CHANGED: Renamed to be more descriptive
755
+ company_domains: Optional[List[str]] = Field(
756
+ default=None,
757
+ description="Domains of the person's employer (e.g., ['microsoft.com']). Maps to q_organization_domains."
758
+ )
759
+
760
+ # CHANGED: Renamed to be more descriptive
761
+ company_hq_locations: Optional[List[str]] = Field(
762
+ default=None,
763
+ description="List of HQ locations for the employer. Maps to organization_locations."
764
+ )
765
+
766
+ contact_email_status: Optional[List[str]] = Field(
767
+ default=None,
768
+ description="Email statuses, e.g. ['verified', 'unavailable']. Maps to contact_email_status."
769
+ )
770
+
771
+ # CHANGED: Renamed to be more descriptive
772
+ company_ids: Optional[List[str]] = Field(
773
+ default=None,
774
+ description="Apollo IDs for the companies (string IDs). Maps to organization_ids."
775
+ )
776
+
777
+ person_seniorities: Optional[List[str]] = Field(
778
+ default=None,
779
+ description=(
780
+ "List of job seniorities, e.g. ['manager', 'director', 'vp']. "
781
+ "Apollo supports: owner, founder, c_suite, partner, vp, head, "
782
+ "director, manager, senior, entry, intern."
783
+ )
784
+ )
785
+
786
+ # CHANGED: This replaces the old 'organization_job_titles' field
787
+ job_openings_with_titles: Optional[List[str]] = Field(
788
+ default=None,
789
+ description="List of job titles for posted positions in the organization. Maps to q_organization_job_titles."
790
+ )
791
+
792
+ # CHANGED: We no longer expose organization_num_employee_ranges to the user;
793
+ # we will build that internally from min_employees_in_organization & max_employees_in_organization.
794
+
795
+ latest_funding_stages: Optional[List[str]] = Field(
796
+ default=None,
797
+ description="List of funding stage codes, e.g. ['2', '3', '10']. Maps to organization_latest_funding_stage_cd."
798
+ )
799
+
800
+ # CHANGED: Renamed for consistency
801
+ company_industry_tag_ids: Optional[List[str]] = Field(
802
+ default=None,
803
+ description="List of industry tag IDs, e.g. ['5567cd4773696439b10b0000']. Maps to organization_industry_tag_ids."
804
+ )
805
+
806
+ q_organization_keyword_tags: Optional[List[str]] = Field(
807
+ default=None,
808
+ description="Organization Keyword tags to search by"
809
+ )
810
+
811
+ q_not_organization_keyword_tags: Optional[List[str]] = Field(
812
+ default=None,
813
+ description="Organization Keyword tags to search by"
814
+ )
815
+
816
+ q_organization_search_list_id: Optional[str] = Field(
817
+ default=None,
818
+ description="Include only organizations in a specific search list. Maps to qOrganizationSearchListId."
819
+ )
820
+ q_not_organization_search_list_id: Optional[str] = Field(
821
+ default=None,
822
+ description="Exclude organizations in a specific search list. Maps to qNotOrganizationSearchListId."
823
+ )
824
+ currently_using_any_of_technology_uids: Optional[List[str]] = Field(
825
+ default=None,
826
+ description="Technology UIDs used by the organization, e.g. ['google_font_api']."
827
+ )
828
+ sort_by_field: Optional[str] = Field(
829
+ default=None,
830
+ description="Sort field, e.g. '[none]', 'last_name', etc. Maps to sortByField."
831
+ )
832
+ sort_ascending: Optional[bool] = Field(
833
+ default=None,
834
+ description="Sort ascending or descending (maps to sortAscending)."
835
+ )
836
+
837
+ organization_num_employees_ranges: Optional[List[str]] = Field(
838
+ default=None,
839
+ description="Ranges for organization number of employees."
840
+ )
841
+
842
+
843
+ class CompanyQueryFilters(BaseModel):
844
+ """
845
+ Defines the filter parameters for querying companies/organizations in the Apollo database.
846
+ All fields are optional and default to None if not specified by user.
847
+ """
848
+
849
+ # Core company search parameters
850
+ organization_locations: Optional[List[str]] = Field(
851
+ default=None,
852
+ description="List of organization headquarters locations (city, state, country)."
853
+ )
854
+
855
+ organization_num_employees_ranges: Optional[List[str]] = Field(
856
+ default=None,
857
+ description="Employee count ranges, e.g. ['1,10', '11,50', '51,200']. Use specific ranges."
858
+ )
859
+
860
+ min_employees: Optional[int] = Field(
861
+ default=None,
862
+ description="Minimum number of employees (>=1). Internally converted to a numeric range."
863
+ )
864
+
865
+ max_employees: Optional[int] = Field(
866
+ default=None,
867
+ description="Maximum number of employees (<=100000). Internally converted to a numeric range."
868
+ )
869
+
870
+ organization_industries: Optional[List[str]] = Field(
871
+ default=None,
872
+ description="List of organization industries."
873
+ )
874
+
875
+ organization_industry_tag_ids: Optional[List[str]] = Field(
876
+ default=None,
877
+ description="List of industry tag IDs, e.g. ['5567cd4773696439b10b0000']."
878
+ )
879
+
880
+ q_organization_keyword_tags: Optional[List[str]] = Field(
881
+ default=None,
882
+ description="Organization Keyword tags to search by"
883
+ )
884
+
885
+ q_not_organization_keyword_tags: Optional[List[str]] = Field(
886
+ default=None,
887
+ description="Organization Keyword tags to search by"
888
+ )
889
+
890
+ # Revenue filters
891
+ revenue_range_min: Optional[int] = Field(
892
+ default=None,
893
+ description="Minimum company revenue in USD."
894
+ )
895
+
896
+ revenue_range_max: Optional[int] = Field(
897
+ default=None,
898
+ description="Maximum company revenue in USD."
899
+ )
900
+
901
+ # Funding and growth
902
+ organization_latest_funding_stage_cd: Optional[List[str]] = Field(
903
+ default=None,
904
+ description="List of funding stage codes, e.g. ['2', '3', '10']."
905
+ )
906
+
907
+ # Technology and keywords
908
+ currently_using_any_of_technology_uids: Optional[List[str]] = Field(
909
+ default=None,
910
+ description="Technology UIDs used by the organization, e.g. ['google_font_api']."
911
+ )
912
+
913
+ q_keywords: Optional[str] = Field(
914
+ default=None,
915
+ description="Keywords to search for in company descriptions, names, etc."
916
+ )
917
+
918
+ q_organization_domains: Optional[List[str]] = Field(
919
+ default=None,
920
+ description="Specific company domains to search for, e.g. ['microsoft.com', 'google.com']."
921
+ )
922
+
923
+ # Company-specific filters
924
+ organization_ids: Optional[List[str]] = Field(
925
+ default=None,
926
+ description="Specific Apollo organization IDs to include."
927
+ )
928
+
929
+ not_organization_ids: Optional[List[str]] = Field(
930
+ default=None,
931
+ description="Apollo organization IDs to exclude from results."
932
+ )
933
+
934
+ # Search lists
935
+ q_organization_search_list_id: Optional[str] = Field(
936
+ default=None,
937
+ description="Include only organizations in a specific search list."
938
+ )
939
+
940
+ q_not_organization_search_list_id: Optional[str] = Field(
941
+ default=None,
942
+ description="Exclude organizations in a specific search list."
943
+ )
944
+
945
+ # Sorting
946
+ sort_by_field: Optional[str] = Field(
947
+ default=None,
948
+ description="Sort field, e.g. 'name', 'employee_count', 'last_updated', etc."
949
+ )
950
+
951
+ sort_ascending: Optional[bool] = Field(
952
+ default=None,
953
+ description="Sort ascending (True) or descending (False)."
954
+ )
955
+
956
+ # Additional filters that might be useful
957
+ organization_founded_year_min: Optional[int] = Field(
958
+ default=None,
959
+ description="Minimum founding year for the organization."
960
+ )
961
+
962
+ organization_founded_year_max: Optional[int] = Field(
963
+ default=None,
964
+ description="Maximum founding year for the organization."
965
+ )