dhisana 0.0.1.dev248__tar.gz → 0.0.1.dev250__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.dev248 → dhisana-0.0.1.dev250}/PKG-INFO +1 -1
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/setup.py +1 -1
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/apollo_tools.py +152 -65
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/PKG-INFO +1 -1
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_apollo_company_search.py +70 -26
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/README.md +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/pyproject.toml +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/setup.cfg +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/__init__.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/cli/__init__.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/cli/cli.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/cli/datasets.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/cli/models.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/cli/predictions.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/schemas/__init__.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/schemas/common.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/schemas/sales.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/ui/__init__.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/ui/components.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/__init__.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/add_mapping.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/agent_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/assistant_tool_tag.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/built_with_api_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/cache_output_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/cache_output_tools_local.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/check_email_validity_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/check_for_intent_signal.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/clay_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/clean_properties.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/company_utils.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/compose_salesnav_query.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/compose_search_query.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/composite_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/dataframe_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/domain_parser.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/email_body_utils.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/email_parse_helpers.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/email_provider.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/enrich_lead_information.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/fetch_openai_config.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/field_validators.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/g2_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_content.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_custom_message.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_email.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_email_response.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_flow.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/google_custom_search.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/google_oauth_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/google_workspace_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/hubspot_clearbit.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/instantly_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/linkedin_crawler.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/lusha_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/mailgun_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/mailreach_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/microsoft365_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openai_helpers.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/profile.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/proxy_curl_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/python_function_to_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/research_lead.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/search_router.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/search_router_jobs.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/sendgrid_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serarch_router_local_business.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serpapi_google_search.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serpapi_search_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serperdev_local_business.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serperdev_search.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/smtp_email_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/test_connect.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/trasform_json.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/web_download_parse_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/workflow_code_model.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/zoominfo_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/workflow/__init__.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/workflow/agent.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/workflow/flow.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/workflow/task.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/workflow/test.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/SOURCES.txt +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/dependency_links.txt +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/entry_points.txt +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/requires.txt +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/top_level.txt +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_agent_tools.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_apollo_lead_search.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_connectivity.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_email_body_utils.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_google_document.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_hubspot_call_logs.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_linkedin_serper.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_mailreach.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_mcp_connectivity.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_proxycurl_get_company_search_id.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_proxycurl_job_count.py +0 -0
- {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_structured_output_with_mcp.py +0 -0
|
@@ -1287,62 +1287,131 @@ def fill_in_company_properties(company_data: dict) -> dict:
|
|
|
1287
1287
|
company_data: Raw company data from Apollo API
|
|
1288
1288
|
|
|
1289
1289
|
Returns:
|
|
1290
|
-
Dictionary
|
|
1290
|
+
Dictionary matching the SmartList `Account` schema shape.
|
|
1291
1291
|
"""
|
|
1292
|
-
|
|
1292
|
+
def _parse_keywords(value: Any) -> List[Any]:
|
|
1293
|
+
if value is None:
|
|
1294
|
+
return []
|
|
1295
|
+
if isinstance(value, list):
|
|
1296
|
+
return value
|
|
1297
|
+
if isinstance(value, str):
|
|
1298
|
+
text = value.strip()
|
|
1299
|
+
if not text:
|
|
1300
|
+
return []
|
|
1301
|
+
if "," in text:
|
|
1302
|
+
return [part.strip() for part in text.split(",") if part.strip()]
|
|
1303
|
+
return [text]
|
|
1304
|
+
return [value]
|
|
1305
|
+
|
|
1306
|
+
def _parse_compact_number(value: Any) -> Optional[float]:
|
|
1307
|
+
if value is None:
|
|
1308
|
+
return None
|
|
1309
|
+
if isinstance(value, (int, float)):
|
|
1310
|
+
return float(value)
|
|
1311
|
+
text = str(value).strip()
|
|
1312
|
+
if not text:
|
|
1313
|
+
return None
|
|
1314
|
+
text = text.replace("$", "").replace(",", "").strip()
|
|
1315
|
+
multiplier = 1.0
|
|
1316
|
+
suffix = text[-1:].upper()
|
|
1317
|
+
if suffix in ("K", "M", "B"):
|
|
1318
|
+
multiplier = {"K": 1e3, "M": 1e6, "B": 1e9}[suffix]
|
|
1319
|
+
text = text[:-1].strip()
|
|
1320
|
+
try:
|
|
1321
|
+
return float(text) * multiplier
|
|
1322
|
+
except ValueError:
|
|
1323
|
+
return None
|
|
1293
1324
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1325
|
+
annual_revenue = (
|
|
1326
|
+
company_data.get("organization_revenue")
|
|
1327
|
+
if company_data.get("organization_revenue") is not None
|
|
1328
|
+
else company_data.get("annual_revenue")
|
|
1329
|
+
)
|
|
1330
|
+
annual_revenue = _parse_compact_number(annual_revenue)
|
|
1331
|
+
if annual_revenue is None:
|
|
1332
|
+
annual_revenue = _parse_compact_number(company_data.get("organization_revenue_printed"))
|
|
1333
|
+
|
|
1334
|
+
company_size = company_data.get("estimated_num_employees")
|
|
1335
|
+
if company_size is not None:
|
|
1336
|
+
try:
|
|
1337
|
+
company_size = int(company_size)
|
|
1338
|
+
except (TypeError, ValueError):
|
|
1339
|
+
company_size = None
|
|
1340
|
+
|
|
1341
|
+
founded_year = company_data.get("founded_year")
|
|
1342
|
+
if founded_year is not None:
|
|
1343
|
+
try:
|
|
1344
|
+
founded_year = int(founded_year)
|
|
1345
|
+
except (TypeError, ValueError):
|
|
1346
|
+
founded_year = None
|
|
1347
|
+
|
|
1348
|
+
primary_phone = company_data.get("primary_phone")
|
|
1349
|
+
primary_phone_number = None
|
|
1350
|
+
if isinstance(primary_phone, dict):
|
|
1351
|
+
primary_phone_number = primary_phone.get("number") or primary_phone.get(
|
|
1352
|
+
"sanitized_number"
|
|
1353
|
+
)
|
|
1354
|
+
|
|
1355
|
+
phone = (
|
|
1356
|
+
primary_phone_number
|
|
1357
|
+
or company_data.get("phone")
|
|
1358
|
+
or company_data.get("primary_phone_number")
|
|
1359
|
+
or company_data.get("sanitized_phone")
|
|
1360
|
+
)
|
|
1361
|
+
|
|
1362
|
+
industry = company_data.get("industry")
|
|
1363
|
+
if not industry and isinstance(company_data.get("industries"), list):
|
|
1364
|
+
industries = [str(x).strip() for x in company_data["industries"] if str(x).strip()]
|
|
1365
|
+
industry = industries[0] if industries else None
|
|
1366
|
+
|
|
1367
|
+
billing_street = (
|
|
1368
|
+
company_data.get("street_address")
|
|
1369
|
+
or company_data.get("billing_street")
|
|
1370
|
+
or company_data.get("address")
|
|
1371
|
+
or company_data.get("raw_address")
|
|
1372
|
+
)
|
|
1373
|
+
|
|
1374
|
+
account: Dict[str, Any] = {
|
|
1375
|
+
"name": company_data.get("name"),
|
|
1376
|
+
"domain": company_data.get("primary_domain"),
|
|
1377
|
+
"website": company_data.get("website_url"),
|
|
1378
|
+
"phone": phone,
|
|
1379
|
+
"fax": company_data.get("fax") or company_data.get("fax_number"),
|
|
1380
|
+
"industry": industry,
|
|
1381
|
+
"company_size": company_size,
|
|
1382
|
+
"founded_year": founded_year,
|
|
1383
|
+
"annual_revenue": annual_revenue,
|
|
1384
|
+
"type": company_data.get("type") or company_data.get("organization_type"),
|
|
1385
|
+
"ownership": company_data.get("ownership"),
|
|
1386
|
+
"organization_linkedin_url": company_data.get("linkedin_url"),
|
|
1387
|
+
"billing_street": billing_street,
|
|
1388
|
+
"billing_city": company_data.get("city"),
|
|
1389
|
+
"billing_state": company_data.get("state"),
|
|
1390
|
+
"billing_zip": company_data.get("postal_code")
|
|
1391
|
+
or company_data.get("zip")
|
|
1392
|
+
or company_data.get("zipcode"),
|
|
1393
|
+
"billing_country": company_data.get("country"),
|
|
1394
|
+
"description": company_data.get("description"),
|
|
1395
|
+
"keywords": _parse_keywords(company_data.get("keywords")),
|
|
1396
|
+
"tags": [],
|
|
1397
|
+
"notes": [],
|
|
1398
|
+
"additional_properties": {
|
|
1399
|
+
"apollo_organization_id": company_data.get("id"),
|
|
1400
|
+
"facebook_url": company_data.get("facebook_url"),
|
|
1401
|
+
"twitter_url": company_data.get("twitter_url"),
|
|
1402
|
+
"funding_stage": company_data.get("latest_funding_stage"),
|
|
1403
|
+
"total_funding": company_data.get("total_funding"),
|
|
1404
|
+
"technology_names": company_data.get("technology_names"),
|
|
1405
|
+
"primary_phone": primary_phone if isinstance(primary_phone, dict) else None,
|
|
1406
|
+
"raw_address": company_data.get("raw_address"),
|
|
1407
|
+
"organization_revenue_printed": company_data.get("organization_revenue_printed"),
|
|
1408
|
+
"apollo_organization_data": json.dumps(cleanup_properties(company_data)),
|
|
1409
|
+
},
|
|
1410
|
+
"research_summary": None,
|
|
1411
|
+
"enchrichment_status": None,
|
|
1343
1412
|
}
|
|
1344
1413
|
|
|
1345
|
-
return
|
|
1414
|
+
return account
|
|
1346
1415
|
|
|
1347
1416
|
|
|
1348
1417
|
@assistant_tool
|
|
@@ -1485,7 +1554,7 @@ async def search_companies_with_apollo_page(
|
|
|
1485
1554
|
# -----------------------------------
|
|
1486
1555
|
else:
|
|
1487
1556
|
dynamic_payload = {}
|
|
1488
|
-
|
|
1557
|
+
|
|
1489
1558
|
# Only add fields if they have values (Apollo doesn't like empty arrays)
|
|
1490
1559
|
if query.organization_locations:
|
|
1491
1560
|
dynamic_payload["organization_locations"] = query.organization_locations
|
|
@@ -1505,19 +1574,37 @@ async def search_companies_with_apollo_page(
|
|
|
1505
1574
|
dynamic_payload["organization_num_employees_ranges"] = employee_ranges
|
|
1506
1575
|
|
|
1507
1576
|
# Add optional parameters only if they have values
|
|
1577
|
+
def _normalize_string_list(value: Any) -> List[str]:
|
|
1578
|
+
if value is None:
|
|
1579
|
+
return []
|
|
1580
|
+
if isinstance(value, str):
|
|
1581
|
+
return [part.strip() for part in value.split(",") if part.strip()]
|
|
1582
|
+
if isinstance(value, list):
|
|
1583
|
+
normalized: List[str] = []
|
|
1584
|
+
for item in value:
|
|
1585
|
+
if item is None:
|
|
1586
|
+
continue
|
|
1587
|
+
text = str(item).strip()
|
|
1588
|
+
if not text:
|
|
1589
|
+
continue
|
|
1590
|
+
normalized.extend([part.strip() for part in text.split(",") if part.strip()])
|
|
1591
|
+
return normalized
|
|
1592
|
+
text = str(value).strip()
|
|
1593
|
+
return [text] if text else []
|
|
1594
|
+
|
|
1508
1595
|
if query.q_keywords:
|
|
1509
|
-
|
|
1510
|
-
if
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
if
|
|
1519
|
-
dynamic_payload["q_not_organization_keyword_tags"] =
|
|
1520
|
-
|
|
1596
|
+
keywords = _normalize_string_list(query.q_keywords)
|
|
1597
|
+
if keywords:
|
|
1598
|
+
dynamic_payload["q_keywords"] = " ".join(keywords)
|
|
1599
|
+
|
|
1600
|
+
org_keyword_tags = _normalize_string_list(query.q_organization_keyword_tags)
|
|
1601
|
+
if org_keyword_tags:
|
|
1602
|
+
dynamic_payload["q_organization_keyword_tags"] = org_keyword_tags
|
|
1603
|
+
|
|
1604
|
+
not_org_keyword_tags = _normalize_string_list(query.q_not_organization_keyword_tags)
|
|
1605
|
+
if not_org_keyword_tags:
|
|
1606
|
+
dynamic_payload["q_not_organization_keyword_tags"] = not_org_keyword_tags
|
|
1607
|
+
|
|
1521
1608
|
if query.q_organization_domains:
|
|
1522
1609
|
dynamic_payload["q_organization_domains_list"] = query.q_organization_domains
|
|
1523
1610
|
if query.revenue_range_min is not None:
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import json
|
|
3
|
-
import pytest
|
|
4
1
|
import unittest
|
|
5
|
-
from unittest.mock import patch
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
6
4
|
from src.dhisana.schemas.sales import CompanyQueryFilters
|
|
7
5
|
from src.dhisana.utils.apollo_tools import (
|
|
8
6
|
search_companies_with_apollo_page,
|
|
@@ -11,7 +9,7 @@ from src.dhisana.utils.apollo_tools import (
|
|
|
11
9
|
)
|
|
12
10
|
|
|
13
11
|
|
|
14
|
-
class TestApolloCompanySearch(unittest.
|
|
12
|
+
class TestApolloCompanySearch(unittest.IsolatedAsyncioTestCase):
|
|
15
13
|
"""Test cases for Apollo company search functionality."""
|
|
16
14
|
|
|
17
15
|
def setUp(self):
|
|
@@ -86,31 +84,37 @@ class TestApolloCompanySearch(unittest.TestCase):
|
|
|
86
84
|
company_data = self.mock_apollo_response["organizations"][0]
|
|
87
85
|
result = fill_in_company_properties(company_data)
|
|
88
86
|
|
|
89
|
-
self.assertEqual(result["
|
|
90
|
-
self.assertEqual(result["
|
|
91
|
-
self.assertEqual(result["
|
|
92
|
-
self.assertEqual(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
self.assertEqual(result["
|
|
97
|
-
self.assertEqual(result["
|
|
98
|
-
self.assertEqual(result["
|
|
87
|
+
self.assertEqual(result["name"], "Example Corp")
|
|
88
|
+
self.assertEqual(result["domain"], "example.com")
|
|
89
|
+
self.assertEqual(result["website"], "https://example.com")
|
|
90
|
+
self.assertEqual(
|
|
91
|
+
result["organization_linkedin_url"],
|
|
92
|
+
"https://linkedin.com/company/example-corp",
|
|
93
|
+
)
|
|
94
|
+
self.assertEqual(result["billing_city"], "San Francisco")
|
|
95
|
+
self.assertEqual(result["billing_state"], "California")
|
|
96
|
+
self.assertEqual(result["billing_country"], "United States")
|
|
97
|
+
self.assertEqual(result["company_size"], 150)
|
|
98
|
+
self.assertEqual(result["annual_revenue"], 10000000.0)
|
|
99
99
|
self.assertEqual(result["industry"], "Technology")
|
|
100
|
-
self.assertEqual(result["keywords"], "saas, software")
|
|
100
|
+
self.assertEqual(result["keywords"], ["saas", "software"])
|
|
101
101
|
self.assertEqual(result["description"], "A technology company")
|
|
102
102
|
self.assertEqual(result["founded_year"], 2010)
|
|
103
|
-
self.assertEqual(result["funding_stage"], "Series B")
|
|
104
|
-
self.assertEqual(result["total_funding"], 5000000)
|
|
105
|
-
self.assertEqual(result["technology_stack"], "React, Python, AWS")
|
|
106
|
-
self.assertEqual(result["apollo_organization_id"], "123456")
|
|
107
103
|
self.assertEqual(result["phone"], "+1-555-123-4567")
|
|
108
|
-
self.assertEqual(result["facebook_url"], "https://facebook.com/example-corp")
|
|
109
|
-
self.assertEqual(result["twitter_url"], "https://twitter.com/example_corp")
|
|
110
104
|
|
|
111
105
|
# Check that additional_properties contains the raw data
|
|
112
106
|
self.assertIn("additional_properties", result)
|
|
113
107
|
self.assertIn("apollo_organization_data", result["additional_properties"])
|
|
108
|
+
self.assertEqual(result["additional_properties"]["apollo_organization_id"], "123456")
|
|
109
|
+
self.assertEqual(
|
|
110
|
+
result["additional_properties"]["facebook_url"], "https://facebook.com/example-corp"
|
|
111
|
+
)
|
|
112
|
+
self.assertEqual(
|
|
113
|
+
result["additional_properties"]["twitter_url"], "https://twitter.com/example_corp"
|
|
114
|
+
)
|
|
115
|
+
self.assertEqual(result["additional_properties"]["funding_stage"], "Series B")
|
|
116
|
+
self.assertEqual(result["additional_properties"]["total_funding"], 5000000)
|
|
117
|
+
self.assertEqual(result["additional_properties"]["technology_names"], ["React", "Python", "AWS"])
|
|
114
118
|
|
|
115
119
|
@patch('src.dhisana.utils.apollo_tools.fetch_apollo_data')
|
|
116
120
|
@patch('src.dhisana.utils.apollo_tools.get_apollo_access_token')
|
|
@@ -143,18 +147,42 @@ class TestApolloCompanySearch(unittest.TestCase):
|
|
|
143
147
|
|
|
144
148
|
# Check results
|
|
145
149
|
self.assertEqual(len(result["results"]), 2)
|
|
146
|
-
self.assertEqual(result["results"][0]["
|
|
147
|
-
self.assertEqual(result["results"][1]["
|
|
150
|
+
self.assertEqual(result["results"][0]["name"], "Example Corp")
|
|
151
|
+
self.assertEqual(result["results"][1]["name"], "Test Inc")
|
|
148
152
|
|
|
149
153
|
# Verify API call was made correctly
|
|
150
154
|
mock_fetch_data.assert_called_once()
|
|
151
|
-
|
|
152
|
-
payload = call_args[1]["payload"]
|
|
155
|
+
payload = mock_fetch_data.call_args.args[3]
|
|
153
156
|
self.assertEqual(payload["page"], 1)
|
|
154
157
|
self.assertEqual(payload["per_page"], 25)
|
|
155
158
|
self.assertIn("organization_locations", payload)
|
|
156
159
|
self.assertIn("organization_num_employees_ranges", payload)
|
|
157
160
|
|
|
161
|
+
@patch('src.dhisana.utils.apollo_tools.fetch_apollo_data')
|
|
162
|
+
@patch('src.dhisana.utils.apollo_tools.get_apollo_access_token')
|
|
163
|
+
async def test_search_companies_with_apollo_page_accepts_keyword_tags_without_q_keywords(
|
|
164
|
+
self, mock_get_token, mock_fetch_data
|
|
165
|
+
):
|
|
166
|
+
mock_get_token.return_value = ("test_api_key", False)
|
|
167
|
+
mock_fetch_data.return_value = self.mock_apollo_response
|
|
168
|
+
|
|
169
|
+
query = CompanyQueryFilters(
|
|
170
|
+
q_organization_keyword_tags=["Marketing and Advertising", "Media Production"]
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
await search_companies_with_apollo_page(
|
|
174
|
+
query=query,
|
|
175
|
+
page=1,
|
|
176
|
+
per_page=25,
|
|
177
|
+
tool_config=self.tool_config,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
payload = mock_fetch_data.call_args.args[3]
|
|
181
|
+
self.assertEqual(
|
|
182
|
+
payload["q_organization_keyword_tags"],
|
|
183
|
+
["Marketing and Advertising", "Media Production"],
|
|
184
|
+
)
|
|
185
|
+
|
|
158
186
|
def test_company_query_filters_initialization(self):
|
|
159
187
|
"""Test that CompanyQueryFilters can be initialized with various parameters."""
|
|
160
188
|
query = CompanyQueryFilters(
|
|
@@ -185,6 +213,22 @@ class TestApolloCompanySearch(unittest.TestCase):
|
|
|
185
213
|
self.assertEqual(query.sort_by_field, "employee_count")
|
|
186
214
|
self.assertFalse(query.sort_ascending)
|
|
187
215
|
|
|
216
|
+
def test_fill_in_company_properties_prefers_primary_phone_and_org_revenue(self):
|
|
217
|
+
company_data = dict(self.mock_apollo_response["organizations"][0])
|
|
218
|
+
company_data["primary_phone"] = {
|
|
219
|
+
"number": "+971 4 433 2589",
|
|
220
|
+
"source": "Account",
|
|
221
|
+
"sanitized_number": "+97144332589",
|
|
222
|
+
}
|
|
223
|
+
company_data.pop("annual_revenue", None)
|
|
224
|
+
company_data["organization_revenue_printed"] = "35M"
|
|
225
|
+
company_data["organization_revenue"] = 35000000.0
|
|
226
|
+
|
|
227
|
+
result = fill_in_company_properties(company_data)
|
|
228
|
+
self.assertEqual(result["phone"], "+971 4 433 2589")
|
|
229
|
+
self.assertEqual(result["annual_revenue"], 35000000.0)
|
|
230
|
+
self.assertEqual(result["additional_properties"]["organization_revenue_printed"], "35M")
|
|
231
|
+
|
|
188
232
|
@patch('src.dhisana.utils.apollo_tools.fetch_apollo_data')
|
|
189
233
|
@patch('src.dhisana.utils.apollo_tools.get_apollo_access_token')
|
|
190
234
|
async def test_search_companies_with_apollo_basic(self, mock_get_token, mock_fetch_data):
|
|
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.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/check_email_validity_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/check_linkedin_url_validity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/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
|
|
File without changes
|
{dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/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.dev248 → dhisana-0.0.1.dev250}/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.dev248 → dhisana-0.0.1.dev250}/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.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_tool/openapi_tool.py
RENAMED
|
File without changes
|
{dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/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.dev248 → dhisana-0.0.1.dev250}/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.dev248 → dhisana-0.0.1.dev250}/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
|