dhisana 0.0.1.dev249__tar.gz → 0.0.1.dev251__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.dev249 → dhisana-0.0.1.dev251}/PKG-INFO +1 -1
  2. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/setup.py +1 -1
  3. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/apollo_tools.py +121 -52
  4. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/cache_output_tools.py +22 -2
  5. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/proxy_curl_tools.py +12 -9
  6. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana.egg-info/PKG-INFO +1 -1
  7. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_apollo_company_search.py +41 -19
  8. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/README.md +0 -0
  9. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/pyproject.toml +0 -0
  10. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/setup.cfg +0 -0
  11. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/__init__.py +0 -0
  12. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/cli/__init__.py +0 -0
  13. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/cli/cli.py +0 -0
  14. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/cli/datasets.py +0 -0
  15. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/cli/models.py +0 -0
  16. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/cli/predictions.py +0 -0
  17. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/schemas/__init__.py +0 -0
  18. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/schemas/common.py +0 -0
  19. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/schemas/sales.py +0 -0
  20. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/ui/__init__.py +0 -0
  21. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/ui/components.py +0 -0
  22. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/__init__.py +0 -0
  23. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/add_mapping.py +0 -0
  24. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/agent_tools.py +0 -0
  25. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/assistant_tool_tag.py +0 -0
  26. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/built_with_api_tools.py +0 -0
  27. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/cache_output_tools_local.py +0 -0
  28. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/check_email_validity_tools.py +0 -0
  29. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/check_for_intent_signal.py +0 -0
  30. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
  31. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/clay_tools.py +0 -0
  32. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/clean_properties.py +0 -0
  33. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/company_utils.py +0 -0
  34. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/compose_salesnav_query.py +0 -0
  35. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/compose_search_query.py +0 -0
  36. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
  37. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/composite_tools.py +0 -0
  38. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/dataframe_tools.py +0 -0
  39. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/domain_parser.py +0 -0
  40. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/email_body_utils.py +0 -0
  41. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/email_parse_helpers.py +0 -0
  42. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/email_provider.py +0 -0
  43. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/enrich_lead_information.py +0 -0
  44. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
  45. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/fetch_openai_config.py +0 -0
  46. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/field_validators.py +0 -0
  47. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/g2_tools.py +0 -0
  48. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/generate_content.py +0 -0
  49. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/generate_custom_message.py +0 -0
  50. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/generate_email.py +0 -0
  51. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/generate_email_response.py +0 -0
  52. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/generate_flow.py +0 -0
  53. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
  54. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
  55. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
  56. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
  57. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/google_custom_search.py +0 -0
  58. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/google_oauth_tools.py +0 -0
  59. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/google_workspace_tools.py +0 -0
  60. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/hubspot_clearbit.py +0 -0
  61. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
  62. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/instantly_tools.py +0 -0
  63. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/linkedin_crawler.py +0 -0
  64. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/lusha_tools.py +0 -0
  65. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/mailgun_tools.py +0 -0
  66. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/mailreach_tools.py +0 -0
  67. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/microsoft365_tools.py +0 -0
  68. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
  69. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/openai_helpers.py +0 -0
  70. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
  71. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
  72. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
  73. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
  74. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
  75. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
  76. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/profile.py +0 -0
  77. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
  78. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/python_function_to_tools.py +0 -0
  79. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/research_lead.py +0 -0
  80. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
  81. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
  82. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/search_router.py +0 -0
  83. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/search_router_jobs.py +0 -0
  84. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/sendgrid_tools.py +0 -0
  85. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/serarch_router_local_business.py +0 -0
  86. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
  87. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
  88. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/serpapi_google_search.py +0 -0
  89. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
  90. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/serpapi_search_tools.py +0 -0
  91. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
  92. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/serperdev_local_business.py +0 -0
  93. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/serperdev_search.py +0 -0
  94. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/smtp_email_tools.py +0 -0
  95. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/test_connect.py +0 -0
  96. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/trasform_json.py +0 -0
  97. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/web_download_parse_tools.py +0 -0
  98. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/workflow_code_model.py +0 -0
  99. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/utils/zoominfo_tools.py +0 -0
  100. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/workflow/__init__.py +0 -0
  101. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/workflow/agent.py +0 -0
  102. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/workflow/flow.py +0 -0
  103. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/workflow/task.py +0 -0
  104. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana/workflow/test.py +0 -0
  105. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana.egg-info/SOURCES.txt +0 -0
  106. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana.egg-info/dependency_links.txt +0 -0
  107. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana.egg-info/entry_points.txt +0 -0
  108. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana.egg-info/requires.txt +0 -0
  109. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/src/dhisana.egg-info/top_level.txt +0 -0
  110. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_agent_tools.py +0 -0
  111. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_apollo_lead_search.py +0 -0
  112. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_connectivity.py +0 -0
  113. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_email_body_utils.py +0 -0
  114. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_google_document.py +0 -0
  115. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_hubspot_call_logs.py +0 -0
  116. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_linkedin_serper.py +0 -0
  117. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_mailreach.py +0 -0
  118. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_mcp_connectivity.py +0 -0
  119. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_proxycurl_get_company_search_id.py +0 -0
  120. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/tests/test_proxycurl_job_count.py +0 -0
  121. {dhisana-0.0.1.dev249 → dhisana-0.0.1.dev251}/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.dev249
3
+ Version: 0.0.1.dev251
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-dev249',
5
+ version='0.0.1-dev251',
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]
1293
1305
 
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))
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
1324
+
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
@@ -2,6 +2,7 @@ import os
2
2
  import hashlib
3
3
  import json
4
4
  import logging
5
+ from datetime import datetime, timezone
5
6
 
6
7
  from azure.storage.blob import BlobServiceClient
7
8
  from azure.core.exceptions import ResourceNotFoundError, AzureError
@@ -49,10 +50,11 @@ def cache_output(tool_name: str, key: str, value, ttl: int = None) -> bool:
49
50
  # Construct the blob name using a virtual folder for the tool name
50
51
  blob_name = f"{tool_name}/{key_hash}.json"
51
52
 
52
- # Prepare the cache data
53
+ # Prepare the cache data with timestamp for TTL expiration checking
53
54
  cache_data = {
54
55
  "value": value,
55
- "ttl": ttl
56
+ "ttl": ttl,
57
+ "cached_at": datetime.now(timezone.utc).isoformat()
56
58
  }
57
59
  data = json.dumps(cache_data)
58
60
 
@@ -88,6 +90,24 @@ def retrieve_output(tool_name: str, key: str):
88
90
  download_stream = blob_client.download_blob()
89
91
  content = download_stream.readall() # content is in bytes
90
92
  cache_data = json.loads(content.decode("utf-8"))
93
+
94
+ # Check if TTL has expired
95
+ ttl = cache_data.get("ttl")
96
+ cached_at = cache_data.get("cached_at")
97
+
98
+ if ttl is not None and cached_at is not None:
99
+ try:
100
+ cached_time = datetime.fromisoformat(cached_at)
101
+ now = datetime.now(timezone.utc)
102
+ elapsed_seconds = (now - cached_time).total_seconds()
103
+ if elapsed_seconds > ttl:
104
+ logger.info(f"Cache expired for blob '{blob_name}' (elapsed: {elapsed_seconds}s, ttl: {ttl}s)")
105
+ return None
106
+ except (ValueError, TypeError) as e:
107
+ logger.warning(f"Error parsing cached_at timestamp: {e}")
108
+ # If we can't parse the timestamp, treat as expired for safety
109
+ return None
110
+
91
111
  return cache_data.get("value")
92
112
  except ResourceNotFoundError:
93
113
  # Blob does not exist
@@ -16,6 +16,9 @@ from urllib.parse import urlparse, urlunparse
16
16
  logging.basicConfig(level=logging.INFO)
17
17
  logger = logging.getLogger(__name__)
18
18
 
19
+ # Cache TTL for Proxycurl responses: 14 days in seconds
20
+ PROXYCURL_CACHE_TTL = 14 * 24 * 60 * 60 # 1,209,600 seconds
21
+
19
22
 
20
23
  def get_proxycurl_access_token(tool_config: Optional[List[Dict]] = None) -> str:
21
24
  """
@@ -117,7 +120,7 @@ async def enrich_person_info_from_proxycurl(
117
120
  if response.status == 200:
118
121
  result = await response.json()
119
122
  if linkedin_url:
120
- cache_output("enrich_person_info_from_proxycurl", linkedin_url, result)
123
+ cache_output("enrich_person_info_from_proxycurl", linkedin_url, result, ttl=PROXYCURL_CACHE_TTL)
121
124
  logger.info("Successfully retrieved person info from Proxycurl.")
122
125
  return result
123
126
  elif response.status == 404:
@@ -195,14 +198,14 @@ async def lookup_person_in_proxy_curl_by_name(
195
198
  logger.debug(f"Received response status: {response.status}")
196
199
  if response.status == 200:
197
200
  result = await response.json()
198
- cache_output("lookup_person_in_proxycurl_by_name", key, result)
201
+ cache_output("lookup_person_in_proxycurl_by_name", key, result, ttl=PROXYCURL_CACHE_TTL)
199
202
  logger.info("Successfully retrieved person search info from Proxycurl.")
200
203
  return result
201
204
  elif response.status == 404:
202
205
  msg = "Person not found"
203
206
  logger.warning(msg)
204
207
  if key:
205
- cache_output("lookup_person_in_proxycurl_by_name", key, {'error': msg})
208
+ cache_output("lookup_person_in_proxycurl_by_name", key, {'error': msg}, ttl=PROXYCURL_CACHE_TTL)
206
209
  return {'error': msg}
207
210
  elif response.status == 429:
208
211
  msg = "Rate limit exceeded"
@@ -415,7 +418,7 @@ async def enrich_organization_info_from_proxycurl(
415
418
  if response.status == 200:
416
419
  result = await response.json()
417
420
  transformed_result = transform_company_data(result)
418
- cache_output("enrich_organization_info_from_proxycurl", cache_key, transformed_result)
421
+ cache_output("enrich_organization_info_from_proxycurl", cache_key, transformed_result, ttl=PROXYCURL_CACHE_TTL)
419
422
  logger.info("Successfully retrieved and transformed organization info from Proxycurl by LinkedIn URL.")
420
423
  return transformed_result
421
424
  elif response.status == 429:
@@ -429,7 +432,7 @@ async def enrich_organization_info_from_proxycurl(
429
432
  f"Proxycurl organization profile not found for LinkedIn URL {standardized_url}: {error_text}"
430
433
  )
431
434
  cache_output(
432
- "enrich_organization_info_from_proxycurl", cache_key, {}
435
+ "enrich_organization_info_from_proxycurl", cache_key, {}, ttl=PROXYCURL_CACHE_TTL
433
436
  )
434
437
  return {}
435
438
  else:
@@ -483,7 +486,7 @@ async def enrich_organization_info_from_proxycurl(
483
486
  if profile_response.status == 200:
484
487
  result = await profile_response.json()
485
488
  transformed_result = transform_company_data(result)
486
- cache_output("enrich_organization_info_from_proxycurl", domain_cache_key, transformed_result)
489
+ cache_output("enrich_organization_info_from_proxycurl", domain_cache_key, transformed_result, ttl=PROXYCURL_CACHE_TTL)
487
490
  logger.info("Successfully retrieved and transformed organization info from Proxycurl by domain.")
488
491
  return transformed_result
489
492
  elif profile_response.status == 429:
@@ -509,7 +512,7 @@ async def enrich_organization_info_from_proxycurl(
509
512
  elif response.status == 404:
510
513
  msg = "Item not found"
511
514
  logger.warning(msg)
512
- cache_output("enrich_organization_info_from_proxycurl", domain_cache_key, {})
515
+ cache_output("enrich_organization_info_from_proxycurl", domain_cache_key, {}, ttl=PROXYCURL_CACHE_TTL)
513
516
  return {}
514
517
  else:
515
518
  error_text = await response.text()
@@ -572,7 +575,7 @@ async def enrich_job_info_from_proxycurl(
572
575
  logger.debug(f"Received response status: {response.status}")
573
576
  if response.status == 200:
574
577
  result = await response.json()
575
- cache_output("enrich_job_info_from_proxycurl", job_url, result)
578
+ cache_output("enrich_job_info_from_proxycurl", job_url, result, ttl=PROXYCURL_CACHE_TTL)
576
579
  logger.info("Successfully retrieved job info from Proxycurl.")
577
580
  return result
578
581
  elif response.status == 429:
@@ -583,7 +586,7 @@ async def enrich_job_info_from_proxycurl(
583
586
  elif response.status == 404:
584
587
  msg = "Job not found"
585
588
  logger.warning(msg)
586
- cache_output("enrich_job_info_from_proxycurl", job_url, {'error': msg})
589
+ cache_output("enrich_job_info_from_proxycurl", job_url, {'error': msg}, ttl=PROXYCURL_CACHE_TTL)
587
590
  return {'error': msg}
588
591
  else:
589
592
  error_text = await response.text()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev249
3
+ Version: 0.0.1.dev251
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
@@ -84,31 +84,37 @@ class TestApolloCompanySearch(unittest.IsolatedAsyncioTestCase):
84
84
  company_data = self.mock_apollo_response["organizations"][0]
85
85
  result = fill_in_company_properties(company_data)
86
86
 
87
- self.assertEqual(result["organization_name"], "Example Corp")
88
- self.assertEqual(result["primary_domain"], "example.com")
89
- self.assertEqual(result["website_url"], "https://example.com")
90
- self.assertEqual(result["organization_linkedin_url"], "https://linkedin.com/company/example-corp")
91
- self.assertEqual(result["organization_city"], "San Francisco")
92
- self.assertEqual(result["organization_state"], "California")
93
- self.assertEqual(result["organization_country"], "United States")
94
- self.assertEqual(result["organization_location"], "San Francisco, California, United States")
95
- self.assertEqual(result["employee_count"], 150)
96
- 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)
97
99
  self.assertEqual(result["industry"], "Technology")
98
- self.assertEqual(result["keywords"], "saas, software")
100
+ self.assertEqual(result["keywords"], ["saas", "software"])
99
101
  self.assertEqual(result["description"], "A technology company")
100
102
  self.assertEqual(result["founded_year"], 2010)
101
- self.assertEqual(result["funding_stage"], "Series B")
102
- self.assertEqual(result["total_funding"], 5000000)
103
- self.assertEqual(result["technology_stack"], "React, Python, AWS")
104
- self.assertEqual(result["apollo_organization_id"], "123456")
105
103
  self.assertEqual(result["phone"], "+1-555-123-4567")
106
- self.assertEqual(result["facebook_url"], "https://facebook.com/example-corp")
107
- self.assertEqual(result["twitter_url"], "https://twitter.com/example_corp")
108
104
 
109
105
  # Check that additional_properties contains the raw data
110
106
  self.assertIn("additional_properties", result)
111
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"])
112
118
 
113
119
  @patch('src.dhisana.utils.apollo_tools.fetch_apollo_data')
114
120
  @patch('src.dhisana.utils.apollo_tools.get_apollo_access_token')
@@ -141,8 +147,8 @@ class TestApolloCompanySearch(unittest.IsolatedAsyncioTestCase):
141
147
 
142
148
  # Check results
143
149
  self.assertEqual(len(result["results"]), 2)
144
- self.assertEqual(result["results"][0]["organization_name"], "Example Corp")
145
- 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")
146
152
 
147
153
  # Verify API call was made correctly
148
154
  mock_fetch_data.assert_called_once()
@@ -207,6 +213,22 @@ class TestApolloCompanySearch(unittest.IsolatedAsyncioTestCase):
207
213
  self.assertEqual(query.sort_by_field, "employee_count")
208
214
  self.assertFalse(query.sort_ascending)
209
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
+
210
232
  @patch('src.dhisana.utils.apollo_tools.fetch_apollo_data')
211
233
  @patch('src.dhisana.utils.apollo_tools.get_apollo_access_token')
212
234
  async def test_search_companies_with_apollo_basic(self, mock_get_token, mock_fetch_data):
File without changes
File without changes