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.
Files changed (121) hide show
  1. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/PKG-INFO +1 -1
  2. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/setup.py +1 -1
  3. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/apollo_tools.py +152 -65
  4. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/PKG-INFO +1 -1
  5. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_apollo_company_search.py +70 -26
  6. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/README.md +0 -0
  7. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/pyproject.toml +0 -0
  8. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/setup.cfg +0 -0
  9. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/__init__.py +0 -0
  10. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/cli/__init__.py +0 -0
  11. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/cli/cli.py +0 -0
  12. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/cli/datasets.py +0 -0
  13. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/cli/models.py +0 -0
  14. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/cli/predictions.py +0 -0
  15. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/schemas/__init__.py +0 -0
  16. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/schemas/common.py +0 -0
  17. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/schemas/sales.py +0 -0
  18. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/ui/__init__.py +0 -0
  19. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/ui/components.py +0 -0
  20. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/__init__.py +0 -0
  21. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/add_mapping.py +0 -0
  22. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/agent_tools.py +0 -0
  23. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/assistant_tool_tag.py +0 -0
  24. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/built_with_api_tools.py +0 -0
  25. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/cache_output_tools.py +0 -0
  26. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/cache_output_tools_local.py +0 -0
  27. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/check_email_validity_tools.py +0 -0
  28. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/check_for_intent_signal.py +0 -0
  29. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
  30. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/clay_tools.py +0 -0
  31. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/clean_properties.py +0 -0
  32. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/company_utils.py +0 -0
  33. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/compose_salesnav_query.py +0 -0
  34. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/compose_search_query.py +0 -0
  35. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
  36. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/composite_tools.py +0 -0
  37. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/dataframe_tools.py +0 -0
  38. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/domain_parser.py +0 -0
  39. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/email_body_utils.py +0 -0
  40. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/email_parse_helpers.py +0 -0
  41. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/email_provider.py +0 -0
  42. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/enrich_lead_information.py +0 -0
  43. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
  44. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/fetch_openai_config.py +0 -0
  45. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/field_validators.py +0 -0
  46. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/g2_tools.py +0 -0
  47. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_content.py +0 -0
  48. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_custom_message.py +0 -0
  49. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_email.py +0 -0
  50. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_email_response.py +0 -0
  51. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_flow.py +0 -0
  52. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
  53. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
  54. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
  55. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
  56. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/google_custom_search.py +0 -0
  57. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/google_oauth_tools.py +0 -0
  58. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/google_workspace_tools.py +0 -0
  59. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/hubspot_clearbit.py +0 -0
  60. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
  61. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/instantly_tools.py +0 -0
  62. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/linkedin_crawler.py +0 -0
  63. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/lusha_tools.py +0 -0
  64. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/mailgun_tools.py +0 -0
  65. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/mailreach_tools.py +0 -0
  66. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/microsoft365_tools.py +0 -0
  67. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
  68. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openai_helpers.py +0 -0
  69. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
  70. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
  71. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
  72. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
  73. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
  74. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
  75. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/profile.py +0 -0
  76. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/proxy_curl_tools.py +0 -0
  77. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
  78. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/python_function_to_tools.py +0 -0
  79. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/research_lead.py +0 -0
  80. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
  81. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
  82. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/search_router.py +0 -0
  83. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/search_router_jobs.py +0 -0
  84. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/sendgrid_tools.py +0 -0
  85. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serarch_router_local_business.py +0 -0
  86. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
  87. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
  88. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serpapi_google_search.py +0 -0
  89. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
  90. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serpapi_search_tools.py +0 -0
  91. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
  92. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serperdev_local_business.py +0 -0
  93. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/serperdev_search.py +0 -0
  94. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/smtp_email_tools.py +0 -0
  95. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/test_connect.py +0 -0
  96. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/trasform_json.py +0 -0
  97. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/web_download_parse_tools.py +0 -0
  98. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/workflow_code_model.py +0 -0
  99. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/utils/zoominfo_tools.py +0 -0
  100. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/workflow/__init__.py +0 -0
  101. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/workflow/agent.py +0 -0
  102. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/workflow/flow.py +0 -0
  103. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/workflow/task.py +0 -0
  104. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana/workflow/test.py +0 -0
  105. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/SOURCES.txt +0 -0
  106. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/dependency_links.txt +0 -0
  107. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/entry_points.txt +0 -0
  108. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/requires.txt +0 -0
  109. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/src/dhisana.egg-info/top_level.txt +0 -0
  110. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_agent_tools.py +0 -0
  111. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_apollo_lead_search.py +0 -0
  112. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_connectivity.py +0 -0
  113. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_email_body_utils.py +0 -0
  114. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_google_document.py +0 -0
  115. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_hubspot_call_logs.py +0 -0
  116. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_linkedin_serper.py +0 -0
  117. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_mailreach.py +0 -0
  118. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_mcp_connectivity.py +0 -0
  119. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_proxycurl_get_company_search_id.py +0 -0
  120. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_proxycurl_job_count.py +0 -0
  121. {dhisana-0.0.1.dev248 → dhisana-0.0.1.dev250}/tests/test_structured_output_with_mcp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev248
3
+ Version: 0.0.1.dev250
4
4
  Summary: A Python SDK for Dhisana AI Platform
5
5
  Home-page: https://github.com/dhisana-ai/dhisana-python-sdk
6
6
  Author: Admin
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='dhisana',
5
- version='0.0.1-dev248',
5
+ version='0.0.1-dev250',
6
6
  description='A Python SDK for Dhisana AI Platform',
7
7
  author='Admin',
8
8
  author_email='contact@dhisana.ai',
@@ -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 with standardized company properties
1290
+ Dictionary matching the SmartList `Account` schema shape.
1291
1291
  """
1292
- company_properties = {}
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
- # Basic company information
1295
- company_properties["organization_name"] = company_data.get("name", "")
1296
- company_properties["primary_domain"] = company_data.get("primary_domain", "")
1297
- company_properties["website_url"] = company_data.get("website_url", "")
1298
- company_properties["organization_linkedin_url"] = company_data.get("linkedin_url", "")
1299
-
1300
- # Location information
1301
- company_properties["organization_city"] = company_data.get("city", "")
1302
- company_properties["organization_state"] = company_data.get("state", "")
1303
- company_properties["organization_country"] = company_data.get("country", "")
1304
-
1305
- # Create a combined location string
1306
- location_parts = [
1307
- company_data.get("city", ""),
1308
- company_data.get("state", ""),
1309
- company_data.get("country", "")
1310
- ]
1311
- company_properties["organization_location"] = ", ".join([part for part in location_parts if part])
1312
-
1313
- # Company size and financial info
1314
- company_properties["employee_count"] = company_data.get("estimated_num_employees", 0)
1315
- company_properties["annual_revenue"] = company_data.get("annual_revenue", 0)
1316
-
1317
- # Industry and business info
1318
- company_properties["industry"] = company_data.get("industry", "")
1319
- company_properties["keywords"] = ", ".join(company_data.get("keywords", []))
1320
- company_properties["description"] = company_data.get("description", "")
1321
-
1322
- # Funding and growth
1323
- company_properties["founded_year"] = company_data.get("founded_year", "")
1324
- company_properties["funding_stage"] = company_data.get("latest_funding_stage", "")
1325
- company_properties["total_funding"] = company_data.get("total_funding", 0)
1326
-
1327
- # Technology stack
1328
- tech_stack = company_data.get("technology_names", [])
1329
- if tech_stack:
1330
- company_properties["technology_stack"] = ", ".join(tech_stack)
1331
-
1332
- # Apollo-specific IDs
1333
- company_properties["apollo_organization_id"] = company_data.get("id", "")
1334
-
1335
- # Additional metadata
1336
- company_properties["phone"] = company_data.get("phone", "")
1337
- company_properties["facebook_url"] = company_data.get("facebook_url", "")
1338
- company_properties["twitter_url"] = company_data.get("twitter_url", "")
1339
-
1340
- # Store raw data for reference
1341
- company_properties["additional_properties"] = {
1342
- "apollo_organization_data": json.dumps(cleanup_properties(company_data))
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 company_properties
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
- # Split comma-separated keywords into an array for company search
1510
- if isinstance(query.q_keywords, str):
1511
- keyword_tags = [tag.strip() for tag in query.q_keywords.split(",") if tag.strip()]
1512
- else:
1513
- keyword_tags = query.q_keywords
1514
-
1515
- if query.q_organization_keyword_tags:
1516
- dynamic_payload["q_organization_keyword_tags"] = keyword_tags
1517
-
1518
- if query.q_not_organization_keyword_tags:
1519
- dynamic_payload["q_not_organization_keyword_tags"] = query.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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev248
3
+ Version: 0.0.1.dev250
4
4
  Summary: A Python SDK for Dhisana AI Platform
5
5
  Home-page: https://github.com/dhisana-ai/dhisana-python-sdk
6
6
  Author: Admin
@@ -1,8 +1,6 @@
1
- import asyncio
2
- import json
3
- import pytest
4
1
  import unittest
5
- from unittest.mock import patch, MagicMock, AsyncMock
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.TestCase):
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["organization_name"], "Example Corp")
90
- self.assertEqual(result["primary_domain"], "example.com")
91
- self.assertEqual(result["website_url"], "https://example.com")
92
- self.assertEqual(result["organization_linkedin_url"], "https://linkedin.com/company/example-corp")
93
- self.assertEqual(result["organization_city"], "San Francisco")
94
- self.assertEqual(result["organization_state"], "California")
95
- self.assertEqual(result["organization_country"], "United States")
96
- self.assertEqual(result["organization_location"], "San Francisco, California, United States")
97
- self.assertEqual(result["employee_count"], 150)
98
- self.assertEqual(result["annual_revenue"], 10000000)
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]["organization_name"], "Example Corp")
147
- self.assertEqual(result["results"][1]["organization_name"], "Test Inc")
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
- call_args = mock_fetch_data.call_args
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