dhisana 0.0.1.dev301__tar.gz → 0.0.1.dev302__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.
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/PKG-INFO +1 -1
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/setup.py +1 -1
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/apollo_tools.py +81 -2
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/check_linkedin_url_validity.py +4 -1
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/enrich_lead_information.py +8 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_search_tools.py +2 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/PKG-INFO +1 -1
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/README.md +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/pyproject.toml +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/setup.cfg +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/__init__.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/cli/__init__.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/cli/cli.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/cli/datasets.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/cli/models.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/cli/predictions.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/schemas/__init__.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/schemas/common.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/schemas/sales.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/ui/__init__.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/ui/components.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/__init__.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/add_mapping.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/agent_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/assistant_tool_tag.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/built_with_api_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/cache_output_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/cache_output_tools_local.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/check_email_validity_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/check_for_intent_signal.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/clay_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/clean_properties.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/company_utils.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/compose_salesnav_query.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/compose_search_query.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/composite_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/dataframe_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/domain_parser.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/email_body_utils.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/email_parse_helpers.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/email_provider.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/fetch_openai_config.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/field_validators.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/g2_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_content.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_custom_message.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_email.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_email_response.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_flow.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/google_custom_search.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/google_oauth_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/google_workspace_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/hubspot_clearbit.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/instantly_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/linkedin_crawler.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/lusha_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/mailgun_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/mailreach_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/microsoft365_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openai_helpers.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/profile.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/proxy_curl_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/python_function_to_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/research_lead.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/search_router.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/search_router_jobs.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/sendgrid_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serarch_router_local_business.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_google_search.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serperdev_local_business.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serperdev_search.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/smtp_email_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/test_connect.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/trasform_json.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/web_download_parse_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/workflow_code_model.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/zoominfo_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/workflow/__init__.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/workflow/agent.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/workflow/flow.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/workflow/task.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/workflow/test.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/SOURCES.txt +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/dependency_links.txt +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/entry_points.txt +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/requires.txt +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/top_level.txt +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_agent_tools.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_apollo_company_search.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_apollo_lead_search.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_connectivity.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_email_body_utils.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_generate_email.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_google_document.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_hubspot_call_logs.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_linkedin_serper.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_mailreach.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_mcp_connectivity.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_normalize_graph_datetime.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_proxycurl_get_company_search_id.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_proxycurl_job_count.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_reply_thread_fallback.py +0 -0
- {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_structured_output_with_mcp.py +0 -0
|
@@ -11,12 +11,15 @@ from dhisana.utils.assistant_tool_tag import assistant_tool
|
|
|
11
11
|
from urllib.parse import urlparse, parse_qs
|
|
12
12
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
13
13
|
|
|
14
|
+
from dhisana.utils.cache_output_tools import cache_output, retrieve_output
|
|
14
15
|
from dhisana.utils.clean_properties import cleanup_properties
|
|
15
16
|
from dhisana.utils.company_utils import normalize_company_name
|
|
16
17
|
|
|
17
18
|
logging.basicConfig(level=logging.INFO)
|
|
18
19
|
logger = logging.getLogger(__name__)
|
|
19
20
|
|
|
21
|
+
APOLLO_CACHE_TTL = 14 * 24 * 60 * 60 # 14 days in seconds
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
def get_apollo_access_token(tool_config: Optional[List[Dict]] = None) -> Tuple[str, bool]:
|
|
22
25
|
"""
|
|
@@ -109,6 +112,7 @@ async def enrich_person_info_from_apollo(
|
|
|
109
112
|
phone: Optional[str] = None,
|
|
110
113
|
fetch_valid_phone_number: Optional[bool] = False,
|
|
111
114
|
tool_config: Optional[List[Dict]] = None,
|
|
115
|
+
organization_id: Optional[str] = None,
|
|
112
116
|
) -> Dict[str, Any]:
|
|
113
117
|
"""
|
|
114
118
|
Fetch a person's details from Apollo using LinkedIn URL, email, or phone number.
|
|
@@ -118,6 +122,7 @@ async def enrich_person_info_from_apollo(
|
|
|
118
122
|
- **email** (*str*, optional): Email address of the person.
|
|
119
123
|
- **phone** (*str*, optional): Phone number of the person.
|
|
120
124
|
- **fetch_valid_phone_number** (*bool*, optional): If True, include phone numbers in the API response. Defaults to False.
|
|
125
|
+
- **organization_id** (*str*, optional): Organization ID used as prefix for cache keys to scope caching per tenant.
|
|
121
126
|
|
|
122
127
|
Returns:
|
|
123
128
|
- **dict**: JSON response containing person information.
|
|
@@ -130,6 +135,21 @@ async def enrich_person_info_from_apollo(
|
|
|
130
135
|
logger.warning("No linkedin_url, email, or phone provided. At least one is required.")
|
|
131
136
|
return {'error': "At least one of linkedin_url, email, or phone must be provided"}
|
|
132
137
|
|
|
138
|
+
# Check cache if organization_id is available
|
|
139
|
+
cache_key_identifier = linkedin_url or email or phone
|
|
140
|
+
cache_tool_name = "enrich_person_phone_from_apollo" if fetch_valid_phone_number else "enrich_person_info_from_apollo"
|
|
141
|
+
if organization_id and cache_key_identifier:
|
|
142
|
+
cache_key = f"{organization_id}:{cache_key_identifier}"
|
|
143
|
+
try:
|
|
144
|
+
cached_response = retrieve_output(cache_tool_name, cache_key)
|
|
145
|
+
if cached_response is not None and cached_response.get('error') is None:
|
|
146
|
+
logger.info(f"Cache hit for Apollo person enrichment, org_id={organization_id}, identifier={cache_key_identifier}")
|
|
147
|
+
return cached_response
|
|
148
|
+
else:
|
|
149
|
+
logger.info(f"Cache miss for Apollo person enrichment, org_id={organization_id}, identifier={cache_key_identifier}")
|
|
150
|
+
except Exception:
|
|
151
|
+
logger.warning(f"Failed to retrieve cached Apollo person data for org_id={organization_id}, proceeding with API call.")
|
|
152
|
+
|
|
133
153
|
headers = {"Content-Type": "application/json"}
|
|
134
154
|
if is_oauth:
|
|
135
155
|
headers["Authorization"] = f"Bearer {token}"
|
|
@@ -160,6 +180,13 @@ async def enrich_person_info_from_apollo(
|
|
|
160
180
|
logger.debug(f"Received response status: {response.status}")
|
|
161
181
|
if response.status == 200:
|
|
162
182
|
result = await response.json()
|
|
183
|
+
# Cache on success if organization_id is available
|
|
184
|
+
if organization_id and cache_key_identifier:
|
|
185
|
+
cache_key = f"{organization_id}:{cache_key_identifier}"
|
|
186
|
+
try:
|
|
187
|
+
cache_output(cache_tool_name, cache_key, result, ttl=APOLLO_CACHE_TTL)
|
|
188
|
+
except Exception:
|
|
189
|
+
logger.warning("Failed to cache Apollo person data.")
|
|
163
190
|
logger.info("Successfully retrieved person info from Apollo.")
|
|
164
191
|
return result
|
|
165
192
|
elif response.status == 429:
|
|
@@ -272,12 +299,14 @@ async def lookup_person_in_apollo_by_name(
|
|
|
272
299
|
async def enrich_organization_info_from_apollo(
|
|
273
300
|
organization_domain: Optional[str] = None,
|
|
274
301
|
tool_config: Optional[List[Dict]] = None,
|
|
302
|
+
organization_id: Optional[str] = None,
|
|
275
303
|
) -> Dict[str, Any]:
|
|
276
304
|
"""
|
|
277
305
|
Fetch an organization's details from Apollo using the organization domain.
|
|
278
306
|
|
|
279
307
|
Parameters:
|
|
280
308
|
- **organization_domain** (*str*, optional): Domain of the organization.
|
|
309
|
+
- **organization_id** (*str*, optional): Organization ID used as prefix for cache keys to scope caching per tenant.
|
|
281
310
|
|
|
282
311
|
Returns:
|
|
283
312
|
- **dict**: JSON response containing organization information.
|
|
@@ -290,6 +319,19 @@ async def enrich_organization_info_from_apollo(
|
|
|
290
319
|
logger.warning("No organization domain provided.")
|
|
291
320
|
return {'error': "organization domain must be provided"}
|
|
292
321
|
|
|
322
|
+
# Check cache if organization_id is available
|
|
323
|
+
if organization_id and organization_domain:
|
|
324
|
+
cache_key = f"{organization_id}:{organization_domain}"
|
|
325
|
+
try:
|
|
326
|
+
cached_response = retrieve_output("enrich_organization_info_from_apollo", cache_key)
|
|
327
|
+
if cached_response is not None and cached_response.get('error') is None:
|
|
328
|
+
logger.info(f"Cache hit for Apollo org enrichment, org_id={organization_id}, domain={organization_domain}")
|
|
329
|
+
return cached_response
|
|
330
|
+
else:
|
|
331
|
+
logger.info(f"Cache miss for Apollo org enrichment, org_id={organization_id}, domain={organization_domain}")
|
|
332
|
+
except Exception:
|
|
333
|
+
logger.warning(f"Failed to retrieve cached Apollo org data for org_id={organization_id}, proceeding with API call.")
|
|
334
|
+
|
|
293
335
|
headers = {
|
|
294
336
|
"Content-Type": "application/json",
|
|
295
337
|
"Cache-Control": "no-cache",
|
|
@@ -309,6 +351,13 @@ async def enrich_organization_info_from_apollo(
|
|
|
309
351
|
logger.debug(f"Received response status: {response.status}")
|
|
310
352
|
if response.status == 200:
|
|
311
353
|
result = await response.json()
|
|
354
|
+
# Cache on success if organization_id is available
|
|
355
|
+
if organization_id and organization_domain:
|
|
356
|
+
cache_key = f"{organization_id}:{organization_domain}"
|
|
357
|
+
try:
|
|
358
|
+
cache_output("enrich_organization_info_from_apollo", cache_key, result, ttl=APOLLO_CACHE_TTL)
|
|
359
|
+
except Exception:
|
|
360
|
+
logger.warning("Failed to cache Apollo org data.")
|
|
312
361
|
logger.info("Successfully retrieved organization info from Apollo.")
|
|
313
362
|
return result
|
|
314
363
|
elif response.status == 429:
|
|
@@ -1117,6 +1166,10 @@ async def enrich_user_info_with_apollo(
|
|
|
1117
1166
|
email = input_user_properties.get("email", "")
|
|
1118
1167
|
user_data_from_apollo = None
|
|
1119
1168
|
|
|
1169
|
+
# Extract organization_id for tenant-scoped caching
|
|
1170
|
+
org_id_raw = input_user_properties.get("organization_id", "")
|
|
1171
|
+
organization_id = str(org_id_raw) if org_id_raw else None
|
|
1172
|
+
|
|
1120
1173
|
logger.debug(f"Properties => LinkedIn URL: {linkedin_url}, Email: {email}")
|
|
1121
1174
|
|
|
1122
1175
|
# If LinkedIn url or email is present, attempt direct enrichment
|
|
@@ -1125,7 +1178,8 @@ async def enrich_user_info_with_apollo(
|
|
|
1125
1178
|
user_data_from_apollo = await enrich_person_info_from_apollo(
|
|
1126
1179
|
linkedin_url=linkedin_url,
|
|
1127
1180
|
email=email,
|
|
1128
|
-
tool_config=tool_config
|
|
1181
|
+
tool_config=tool_config,
|
|
1182
|
+
organization_id=organization_id,
|
|
1129
1183
|
)
|
|
1130
1184
|
except Exception:
|
|
1131
1185
|
logger.exception("Exception occurred while enriching person info from Apollo by LinkedIn or email.")
|
|
@@ -1185,7 +1239,8 @@ async def enrich_user_info_with_apollo(
|
|
|
1185
1239
|
try:
|
|
1186
1240
|
user_data_from_apollo = await enrich_person_info_from_apollo(
|
|
1187
1241
|
linkedin_url=linkedin_url,
|
|
1188
|
-
tool_config=tool_config
|
|
1242
|
+
tool_config=tool_config,
|
|
1243
|
+
organization_id=organization_id,
|
|
1189
1244
|
)
|
|
1190
1245
|
except Exception:
|
|
1191
1246
|
logger.exception("Exception occurred during second stage Apollo enrichment.")
|
|
@@ -1894,6 +1949,7 @@ async def search_organization_by_linkedin_or_domain(
|
|
|
1894
1949
|
linkedin_url: Optional[str] = None,
|
|
1895
1950
|
domain: Optional[str] = None,
|
|
1896
1951
|
tool_config: Optional[List[Dict]] = None,
|
|
1952
|
+
organization_id: Optional[str] = None,
|
|
1897
1953
|
) -> Dict[str, Any]:
|
|
1898
1954
|
"""
|
|
1899
1955
|
Search for an organization in Apollo using LinkedIn URL or domain and return
|
|
@@ -1933,6 +1989,21 @@ async def search_organization_by_linkedin_or_domain(
|
|
|
1933
1989
|
logger.warning("No linkedin_url or domain provided. At least one is required.")
|
|
1934
1990
|
return {'error': "At least one of linkedin_url or domain must be provided"}
|
|
1935
1991
|
|
|
1992
|
+
# --- Cache lookup ---
|
|
1993
|
+
cache_tool_name = "search_organization_by_linkedin_or_domain"
|
|
1994
|
+
cache_identifier = (linkedin_url or domain or "").strip().rstrip("/").lower()
|
|
1995
|
+
cache_key = f"{organization_id}:{cache_identifier}" if organization_id else None
|
|
1996
|
+
if cache_key:
|
|
1997
|
+
try:
|
|
1998
|
+
cached = retrieve_output(cache_tool_name, cache_key)
|
|
1999
|
+
if cached is not None and cached.get('error') is None:
|
|
2000
|
+
logger.info("Cache hit for search_organization_by_linkedin_or_domain org_id=%s identifier=%s", organization_id, cache_identifier)
|
|
2001
|
+
return cached
|
|
2002
|
+
else:
|
|
2003
|
+
logger.info("Cache miss for search_organization_by_linkedin_or_domain org_id=%s identifier=%s", organization_id, cache_identifier)
|
|
2004
|
+
except Exception as e:
|
|
2005
|
+
logger.warning("Cache retrieval error for search_organization_by_linkedin_or_domain: %s", e)
|
|
2006
|
+
|
|
1936
2007
|
token, is_oauth = get_apollo_access_token(tool_config)
|
|
1937
2008
|
|
|
1938
2009
|
headers = {
|
|
@@ -2126,6 +2197,14 @@ async def search_organization_by_linkedin_or_domain(
|
|
|
2126
2197
|
f"(domain: {standardized_org.get('domain')}, "
|
|
2127
2198
|
f"linkedin: {standardized_org.get('organization_linkedin_url')}, "
|
|
2128
2199
|
f"confidence: {match_confidence})")
|
|
2200
|
+
|
|
2201
|
+
# --- Cache the result ---
|
|
2202
|
+
if cache_key:
|
|
2203
|
+
try:
|
|
2204
|
+
cache_output(cache_tool_name, cache_key, standardized_org, APOLLO_CACHE_TTL)
|
|
2205
|
+
except Exception as e:
|
|
2206
|
+
logger.warning("Cache write error for search_organization_by_linkedin_or_domain: %s", e)
|
|
2207
|
+
|
|
2129
2208
|
return standardized_org
|
|
2130
2209
|
|
|
2131
2210
|
elif response.status == 429:
|
{dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/check_linkedin_url_validity.py
RENAMED
|
@@ -68,9 +68,12 @@ async def validate_linkedin_url_with_apollo(
|
|
|
68
68
|
linkedin_url = lead_properties.get("user_linkedin_url", "")
|
|
69
69
|
match_result = LeadLinkedInMatch()
|
|
70
70
|
|
|
71
|
+
org_id_raw = lead_properties.get("organization_id", "")
|
|
72
|
+
apollo_org_id = str(org_id_raw) if org_id_raw else None
|
|
71
73
|
linkedin_data = await enrich_person_info_from_apollo(
|
|
72
74
|
linkedin_url=linkedin_url,
|
|
73
|
-
tool_config=tool_config
|
|
75
|
+
tool_config=tool_config,
|
|
76
|
+
organization_id=apollo_org_id,
|
|
74
77
|
)
|
|
75
78
|
# If no data is returned from Apollo, return defaults
|
|
76
79
|
if not linkedin_data:
|
|
@@ -361,9 +361,12 @@ async def enrich_lead_information(
|
|
|
361
361
|
and not cloned_properties.get("user_linkedin_url")
|
|
362
362
|
):
|
|
363
363
|
try:
|
|
364
|
+
org_id_raw = cloned_properties.get("organization_id", "")
|
|
365
|
+
apollo_org_id = str(org_id_raw) if org_id_raw else None
|
|
364
366
|
apollo_result = await enrich_person_info_from_apollo(
|
|
365
367
|
email=cloned_properties["email"],
|
|
366
368
|
tool_config=tool_config,
|
|
369
|
+
organization_id=apollo_org_id,
|
|
367
370
|
)
|
|
368
371
|
if apollo_result and not apollo_result.get("error"):
|
|
369
372
|
person_data = apollo_result.get("person", {})
|
|
@@ -583,9 +586,12 @@ async def enrich_user_info(
|
|
|
583
586
|
# 1a) If email is present, first try Apollo lookup by email for more robust matching
|
|
584
587
|
if email:
|
|
585
588
|
logger.debug("Attempting Apollo lookup by email: %s", email)
|
|
589
|
+
org_id_raw = input_properties.get("organization_id", "")
|
|
590
|
+
apollo_org_id = str(org_id_raw) if org_id_raw else None
|
|
586
591
|
apollo_result = await enrich_person_info_from_apollo(
|
|
587
592
|
email=email,
|
|
588
593
|
tool_config=tool_config,
|
|
594
|
+
organization_id=apollo_org_id,
|
|
589
595
|
)
|
|
590
596
|
if apollo_result and not apollo_result.get("error"):
|
|
591
597
|
person_data = apollo_result.get("person", {})
|
|
@@ -899,6 +905,7 @@ async def enrich_organization_info_from_company_url(
|
|
|
899
905
|
extra: Optional[bool] = None,
|
|
900
906
|
use_cache: Optional[str] = "if-present",
|
|
901
907
|
fallback_to_cache: Optional[str] = "on-error",
|
|
908
|
+
organization_id: Optional[str] = None,
|
|
902
909
|
) -> Dict[str, Any]:
|
|
903
910
|
"""
|
|
904
911
|
Given an organization LinkedIn URL, attempt to enrich its data (e.g. name, website)
|
|
@@ -917,6 +924,7 @@ async def enrich_organization_info_from_company_url(
|
|
|
917
924
|
apollo_result = await search_organization_by_linkedin_or_domain(
|
|
918
925
|
linkedin_url=organization_linkedin_url,
|
|
919
926
|
tool_config=tool_config,
|
|
927
|
+
organization_id=organization_id,
|
|
920
928
|
)
|
|
921
929
|
if apollo_result and not apollo_result.get("error"):
|
|
922
930
|
logger.debug(f"Apollo returned company data: {apollo_result.get('organization_name')}")
|
|
@@ -828,6 +828,7 @@ async def get_resolved_linkedin_links(url: str) -> List[str]:
|
|
|
828
828
|
async def get_company_website_from_linkedin_url(
|
|
829
829
|
linkedin_url: str,
|
|
830
830
|
tool_config: Optional[List[Dict]] = None,
|
|
831
|
+
organization_id: Optional[str] = None,
|
|
831
832
|
) -> str:
|
|
832
833
|
"""
|
|
833
834
|
Attempt to extract a company's website from its LinkedIn URL.
|
|
@@ -859,6 +860,7 @@ async def get_company_website_from_linkedin_url(
|
|
|
859
860
|
org_info = await search_organization_by_linkedin_or_domain(
|
|
860
861
|
linkedin_url=linkedin_url,
|
|
861
862
|
tool_config=tool_config,
|
|
863
|
+
organization_id=organization_id,
|
|
862
864
|
)
|
|
863
865
|
# Prefer 'website', then build one from 'domain'
|
|
864
866
|
website = (org_info.get("website") or "").strip()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/check_email_validity_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/compose_three_step_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/extract_email_content_for_llm.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_linkedin_connect_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openai_assistant_and_file_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_tool/openapi_tool.py
RENAMED
|
File without changes
|
{dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/parse_linkedin_messages_txt.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serarch_router_local_business.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_local_business_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|