drc-vantage 1.0.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. drc_vantage-1.0.7.dist-info/METADATA +70 -0
  2. drc_vantage-1.0.7.dist-info/RECORD +108 -0
  3. drc_vantage-1.0.7.dist-info/WHEEL +4 -0
  4. vantage/__init__.py +16 -0
  5. vantage/client.py +126 -0
  6. vantage/core/logo.py +157 -0
  7. vantage/core/organization.py +51 -0
  8. vantage/core/s3.py +31 -0
  9. vantage/data_sources/data_source.py +71 -0
  10. vantage/data_sources/data_source_category.py +32 -0
  11. vantage/data_sources/sync/data_source_helpers.py +25 -0
  12. vantage/data_sources/sync/sync_data_source.py +98 -0
  13. vantage/data_sources/sync/sync_data_source_category.py +88 -0
  14. vantage/data_storage_system/data_storage_system.py +77 -0
  15. vantage/data_storage_system/sync/data_storage_system_helpers.py +51 -0
  16. vantage/data_storage_system/sync/sync_data_storage_system.py +108 -0
  17. vantage/datasets/alerts/check_alert.py +21 -0
  18. vantage/datasets/alerts/check_dataset_alerts.py +22 -0
  19. vantage/datasets/alerts/email.py +170 -0
  20. vantage/datasets/alerts/evaluate_alert.py +283 -0
  21. vantage/datasets/alerts/queries.py +128 -0
  22. vantage/datasets/alerts/run_alert_check.py +95 -0
  23. vantage/datasets/analysis/analyze_dataset.py +16 -0
  24. vantage/datasets/analysis/column_analysis.py +436 -0
  25. vantage/datasets/analysis/health_score.py +138 -0
  26. vantage/datasets/analysis/list_dataset_ids.py +14 -0
  27. vantage/datasets/analysis/metrics/anomalies.py +167 -0
  28. vantage/datasets/analysis/metrics/completion.py +103 -0
  29. vantage/datasets/analysis/metrics/row_count.py +139 -0
  30. vantage/datasets/analysis/run_analysis.py +228 -0
  31. vantage/datasets/analysis/smart_alerts.py +138 -0
  32. vantage/datasets/dataset.py +150 -0
  33. vantage/datasets/linking/assign_dataset_data_sources.py +105 -0
  34. vantage/datasets/sync/dataset_helpers.py +275 -0
  35. vantage/datasets/sync/materialized_views.py +116 -0
  36. vantage/datasets/sync/sync_dataset.py +227 -0
  37. vantage/datasets/teams/assign_dataset_to_team.py +279 -0
  38. vantage/datasets/teams/team_helpers.py +69 -0
  39. vantage/datasets/validations/checks/__init__.py +3 -0
  40. vantage/datasets/validations/checks/check_column_avg.py +74 -0
  41. vantage/datasets/validations/checks/check_column_between.py +59 -0
  42. vantage/datasets/validations/checks/check_column_count_distinct.py +68 -0
  43. vantage/datasets/validations/checks/check_column_count_not_null.py +68 -0
  44. vantage/datasets/validations/checks/check_column_count_null.py +60 -0
  45. vantage/datasets/validations/checks/check_column_max.py +64 -0
  46. vantage/datasets/validations/checks/check_column_max_length.py +58 -0
  47. vantage/datasets/validations/checks/check_column_mean.py +74 -0
  48. vantage/datasets/validations/checks/check_column_median.py +78 -0
  49. vantage/datasets/validations/checks/check_column_min.py +64 -0
  50. vantage/datasets/validations/checks/check_column_min_length.py +58 -0
  51. vantage/datasets/validations/checks/check_column_not_between.py +59 -0
  52. vantage/datasets/validations/checks/check_column_not_null.py +46 -0
  53. vantage/datasets/validations/checks/check_column_percent_not_null.py +83 -0
  54. vantage/datasets/validations/checks/check_column_percent_null.py +81 -0
  55. vantage/datasets/validations/checks/check_column_percentile.py +86 -0
  56. vantage/datasets/validations/checks/check_column_regex_match.py +58 -0
  57. vantage/datasets/validations/checks/check_column_regex_not_match.py +58 -0
  58. vantage/datasets/validations/checks/check_column_stddev.py +74 -0
  59. vantage/datasets/validations/checks/check_column_sum.py +66 -0
  60. vantage/datasets/validations/checks/check_column_unique.py +49 -0
  61. vantage/datasets/validations/checks/check_column_values_in_set.py +62 -0
  62. vantage/datasets/validations/checks/check_column_values_not_in_set.py +61 -0
  63. vantage/datasets/validations/checks/check_custom_sql.py +58 -0
  64. vantage/datasets/validations/checks/check_table_duplicate_rows.py +52 -0
  65. vantage/datasets/validations/checks/check_table_freshness.py +86 -0
  66. vantage/datasets/validations/checks/check_table_row_count.py +52 -0
  67. vantage/datasets/validations/checks/check_table_row_count_between.py +43 -0
  68. vantage/datasets/validations/checks/check_table_schema_change.py +25 -0
  69. vantage/datasets/validations/checks/run_check.py +124 -0
  70. vantage/datasets/validations/checks/sql_helpers.py +80 -0
  71. vantage/datasets/validations/get_dataset_info.py +65 -0
  72. vantage/datasets/validations/get_dataset_validations.py +105 -0
  73. vantage/datasets/validations/recommend_validations.py +143 -0
  74. vantage/datasets/validations/recommendations/dedupe.py +154 -0
  75. vantage/datasets/validations/recommendations/fetch.py +333 -0
  76. vantage/datasets/validations/recommendations/llm.py +78 -0
  77. vantage/datasets/validations/recommendations/normalize.py +75 -0
  78. vantage/datasets/validations/recommendations/prompt.py +140 -0
  79. vantage/datasets/validations/recommendations/save.py +165 -0
  80. vantage/datasets/validations/recommendations/schemas.py +67 -0
  81. vantage/datasets/validations/rule_config.py +188 -0
  82. vantage/datasets/validations/rule_config_reference.json +143 -0
  83. vantage/datasets/validations/run_all_dataset_validation_suites.py +28 -0
  84. vantage/datasets/validations/run_dataset_validations.py +13 -0
  85. vantage/datasets/validations/run_validation.py +18 -0
  86. vantage/datasets/validations/run_validation_rule.py +120 -0
  87. vantage/datasets/validations/run_validation_suite.py +190 -0
  88. vantage/datasets/validations/schemas.py +136 -0
  89. vantage/kpis/compute/compute.py +160 -0
  90. vantage/kpis/compute/compute_manual.py +399 -0
  91. vantage/kpis/compute/compute_sql.py +37 -0
  92. vantage/kpis/compute/fetch.py +35 -0
  93. vantage/kpis/compute/save.py +65 -0
  94. vantage/kpis/compute/schemas.py +0 -0
  95. vantage/smart_cockpit/ai_summary/fetch.py +105 -0
  96. vantage/smart_cockpit/ai_summary/llm.py +112 -0
  97. vantage/smart_cockpit/ai_summary/prompt.py +86 -0
  98. vantage/smart_cockpit/ai_summary/save.py +80 -0
  99. vantage/smart_cockpit/ai_summary/schemas.py +26 -0
  100. vantage/smart_cockpit/company_news/llm.py +0 -0
  101. vantage/smart_cockpit/company_news/save.py +0 -0
  102. vantage/smart_cockpit/company_news/schemas.py +0 -0
  103. vantage/smart_cockpit/configuration.py +35 -0
  104. vantage/smart_cockpit/kpi_insights/fetch.py +60 -0
  105. vantage/smart_cockpit/kpi_insights/llm.py +59 -0
  106. vantage/smart_cockpit/kpi_insights/prompt.py +117 -0
  107. vantage/smart_cockpit/kpi_insights/save.py +79 -0
  108. vantage/smart_cockpit/regenerate/generate_missing_kpi_insights.py +23 -0
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: drc-vantage
3
+ Version: 1.0.7
4
+ Summary: Python SDK for the DRC Vantage data platform
5
+ Project-URL: Homepage, https://github.com/nathan294/vantage
6
+ Project-URL: Repository, https://github.com/nathan294/vantage
7
+ Project-URL: Issues, https://github.com/nathan294/vantage/issues
8
+ Author: DRC
9
+ License-Expression: LicenseRef-Proprietary
10
+ Keywords: analytics,data,governance,postgresql,sdk,vantage
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: Other/Proprietary License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Database
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.13
20
+ Requires-Dist: boto3>=1.43.46
21
+ Requires-Dist: langchain-core>=0.3.0
22
+ Requires-Dist: langchain-openai>=0.3.0
23
+ Requires-Dist: loguru>=0.7.3
24
+ Requires-Dist: numpy>=2.5.1
25
+ Requires-Dist: pillow>=11.0.0
26
+ Requires-Dist: psycopg[binary]>=3.3.4
27
+ Requires-Dist: pydantic>=2.13.4
28
+ Requires-Dist: resend>=2.32.2
29
+ Requires-Dist: scikit-learn>=1.9.0
30
+ Requires-Dist: sqlalchemy>=2.0.51
31
+ Description-Content-Type: text/markdown
32
+
33
+ # DRC Vantage SDK
34
+
35
+ Python SDK for the [Vantage](https://github.com/nathan294/vantage) data platform: dataset sync, validation, analysis, KPI computation, and smart cockpit pipelines.
36
+
37
+ ## Requirements
38
+
39
+ - Python 3.13+
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install drc-vantage
45
+ ```
46
+
47
+ ## Quick start
48
+
49
+ ```python
50
+ from vantage import Client, Dataset
51
+
52
+ client = Client(
53
+ postgres_host="localhost",
54
+ postgres_user="postgres",
55
+ postgres_password="secret",
56
+ postgres_database="vantage",
57
+ )
58
+
59
+ dataset = Dataset(
60
+ client=client,
61
+ location={"schema": "marts", "table": "customers"},
62
+ dataset_name="Customers",
63
+ dataset_type="TABLE",
64
+ )
65
+ dataset.sync()
66
+ ```
67
+
68
+ ## License
69
+
70
+ Proprietary. All rights reserved.
@@ -0,0 +1,108 @@
1
+ vantage/__init__.py,sha256=IwtLxN9hObbtEWHAFxr41FPcKYr5DdWJhihyPOs41tk,485
2
+ vantage/client.py,sha256=3LlIfQM-oTNwYrfZt8jPMgDWlJ6lwYRi6iRJ9CTLBRw,4627
3
+ vantage/core/logo.py,sha256=Q6AKz32TNlhQpDpFmsKdTb6ZqJP2U9p2APGJmm2un1g,5255
4
+ vantage/core/organization.py,sha256=WQT_zi2PJshUZhkHlD7u49oltqy-165o79eRuEC2CyM,1976
5
+ vantage/core/s3.py,sha256=6auzMfiJ1mQwDokEjqEua7h9fMT4Aw65S-D3BRM9DNY,884
6
+ vantage/data_sources/data_source.py,sha256=vdsXpxDOE0n49Hj_erfux_OG69A5JVSRBuEVTQk4M_I,3198
7
+ vantage/data_sources/data_source_category.py,sha256=6pmOhrRLeJOcFJnpWZ_qqwKpvMi0sP_B5GyoIW650-Q,1286
8
+ vantage/data_sources/sync/data_source_helpers.py,sha256=bDAER2ueQD0e0Qfi6phH-FOfftthzHBFlf_5KmRT5mg,676
9
+ vantage/data_sources/sync/sync_data_source.py,sha256=COLBhuDPo0r2rnwlLWjJZnRZZQWGJmmKhyQv9kH9Cw0,3016
10
+ vantage/data_sources/sync/sync_data_source_category.py,sha256=PEk46tcHGwVnPt3-aj78iQptEgG539wGjOOdXsZeUqM,2572
11
+ vantage/data_storage_system/data_storage_system.py,sha256=4By8m9xgEJxHh2a226DGPR4PLKWRk2rX8CcF-Brc9gI,3296
12
+ vantage/data_storage_system/sync/data_storage_system_helpers.py,sha256=rYQPT05MtAWTHzH_I0IfYCetBeF7hkJ1NtFWFJcCmK4,1661
13
+ vantage/data_storage_system/sync/sync_data_storage_system.py,sha256=2Wbaj8MokWGhv7g-iHP_DKzrcPd3zo95PKDu0Mu5SJQ,3208
14
+ vantage/datasets/dataset.py,sha256=D7ITADuz21xyLpUsL-O_AGeZZN-tzWvuXnbz-EKFL7s,7006
15
+ vantage/datasets/alerts/check_alert.py,sha256=gdspbWip-Z6t3ez358mR82Ym90ANVySCEMvRTt5YsPA,838
16
+ vantage/datasets/alerts/check_dataset_alerts.py,sha256=avDHyqvLGxNQiAQkBg7mPuaytSM8XW7k8LXfhbg40To,985
17
+ vantage/datasets/alerts/email.py,sha256=E1YhWLVlrHoqJRjYZQUCLLweL-9Cm8rHNLD-die3irk,6905
18
+ vantage/datasets/alerts/evaluate_alert.py,sha256=00N9cpCmJ-IZ7pT4iwuCMRSGEfdzve4m2Wbif3JQ-Hg,10003
19
+ vantage/datasets/alerts/queries.py,sha256=1pXs30lBHvvCIaiv9zA3RS4a4vlwM0m7zbcke0DZn9o,4275
20
+ vantage/datasets/alerts/run_alert_check.py,sha256=uvNSNDmPQ_7YUL2FoRdjpKXjYmU3_TeH7W2eq018G14,3139
21
+ vantage/datasets/analysis/analyze_dataset.py,sha256=ftypJJDMPmGoHSlGXjAwtXzgwn4TtGjhVWiANrXXQP4,669
22
+ vantage/datasets/analysis/column_analysis.py,sha256=eII0mbn6-XMCXdAl0nPlKxq7CFH8OzpTu6uJIdwye0Y,14471
23
+ vantage/datasets/analysis/health_score.py,sha256=WUEYz6YA3yuI8A_VdT2g0R-ZSMgx2ZwdAVITphzT6gE,5156
24
+ vantage/datasets/analysis/list_dataset_ids.py,sha256=UmZq4VsFzpDUI-tKeOzPLYuuUEwToan06iqx-KJEyRM,485
25
+ vantage/datasets/analysis/run_analysis.py,sha256=IsVOISHwtebnq-utRbhWDlotnHrcQyxkhEEJ2rMK9f4,9760
26
+ vantage/datasets/analysis/smart_alerts.py,sha256=B4jfN2pfEf6FXPZ0kQ0PKhmtEwlYIYSn78GIlBuj3IQ,4465
27
+ vantage/datasets/analysis/metrics/anomalies.py,sha256=JQ0zhz2lYqeTppcCvw-34lnfErHpCL4FfmBUXC62xSU,5931
28
+ vantage/datasets/analysis/metrics/completion.py,sha256=K8TgO36-GlmQxZfp2vtE8jeYQj7uvgParxgSbG5toYU,3552
29
+ vantage/datasets/analysis/metrics/row_count.py,sha256=w4HrdE01uG4xW2cl8WT8khZ4pEM5GEerTTq7KeGOquc,4528
30
+ vantage/datasets/linking/assign_dataset_data_sources.py,sha256=Ki4tdhea12vCpdLyaGDyPfxFG_qcHvcmg2at_-_NhrY,3226
31
+ vantage/datasets/sync/dataset_helpers.py,sha256=mVTRHWpk50SQSvL2iO4flkr8-1CYGBD1dmw9i0zD2O0,9803
32
+ vantage/datasets/sync/materialized_views.py,sha256=_3vVTVqyYh6HHa3Es7bq2Lnrpjvz56wxK-cU0JHTazQ,4349
33
+ vantage/datasets/sync/sync_dataset.py,sha256=oKYAn9_BPD04n_rEOcU0Fm1k-UMF8CGqAQcP6crRS7c,8456
34
+ vantage/datasets/teams/assign_dataset_to_team.py,sha256=5YT9aUrrO-OQo5UXCKxdWTSjDkXc1WNpzmGcQXB8jbI,9004
35
+ vantage/datasets/teams/team_helpers.py,sha256=aijTYDAjtrpuIGEl_872j-7y_jfmsr3hp7d-9GyZWMY,1912
36
+ vantage/datasets/validations/get_dataset_info.py,sha256=Rf6H6Jy7Oqh1Odn4G_OUxp61uujhcav83K4MBecZWwY,2079
37
+ vantage/datasets/validations/get_dataset_validations.py,sha256=3fQzdc7Yj6bfVYZVDNJZhW6DQU9L60WteYuNyKHR3I8,3514
38
+ vantage/datasets/validations/recommend_validations.py,sha256=YnoeP5j6fls0o562nyPOGb16NxFUaFZo31p7RaEVURM,5348
39
+ vantage/datasets/validations/rule_config.py,sha256=eYnfcCIgCJxc8osHTwh54PFniAgJyw4Sk8WxbcM1Fgo,6093
40
+ vantage/datasets/validations/rule_config_reference.json,sha256=l6gnTuHpxxpOP72j4epfXct5Y-OrwzoHuTuxLmm-wwE,5212
41
+ vantage/datasets/validations/run_all_dataset_validation_suites.py,sha256=rKtnchFOI_-bZMR0CEJh7V2MPmEg8E3XYyGZfnf4Spc,1079
42
+ vantage/datasets/validations/run_dataset_validations.py,sha256=E4KWj2jimFKx_CRI2f4Y9hCQn_18Y3aNyrNfU2-_Wkk,497
43
+ vantage/datasets/validations/run_validation.py,sha256=1LCkfbuZRH-RLNJcqCm0xnIjf6ZsYqQVJD5NM2lQdC0,656
44
+ vantage/datasets/validations/run_validation_rule.py,sha256=pIUz0Sbwdz842Nk5M_fHlTHo8PmBi8MNtWaE9DmIr2Y,4337
45
+ vantage/datasets/validations/run_validation_suite.py,sha256=xiaKOUSBMENl_dFrTMgcDVaSlwQcChpF1dgXK8CgWpk,6474
46
+ vantage/datasets/validations/schemas.py,sha256=9CpqZvzHV9eA3DOJz1lK_259cMkotVDNy5SBXrQfvi4,5877
47
+ vantage/datasets/validations/checks/__init__.py,sha256=cldmnJuBUq3ws4-eC0iVJCtyuqduy5SlLkTlkCyXEsA,96
48
+ vantage/datasets/validations/checks/check_column_avg.py,sha256=BmQNXXcvdKO9cENc14yMZY4EXZ_dGtPLKWwBHTIUWBI,2252
49
+ vantage/datasets/validations/checks/check_column_between.py,sha256=WyE9yPMqAGF-swOJRX02vY-kAJe4fa7V9KAVSJG7enA,1897
50
+ vantage/datasets/validations/checks/check_column_count_distinct.py,sha256=oef1gwdj4EDww1A8e7msn-eubYs233ky7tjRy9pxAC8,2104
51
+ vantage/datasets/validations/checks/check_column_count_not_null.py,sha256=UpHYrs0AMK5WTTsbA7tdJFfLpk9-4hmq0iFDKhOkMTA,2086
52
+ vantage/datasets/validations/checks/check_column_count_null.py,sha256=J-NTXvb4QBt_67qv26zScaZ3hHR0RiW4gDyn7VonEFA,1830
53
+ vantage/datasets/validations/checks/check_column_max.py,sha256=3mExqkbXjQmxsEJK17M9LZK98xR81qzLJx-5c4_nUyI,1951
54
+ vantage/datasets/validations/checks/check_column_max_length.py,sha256=_31vYeiTzf92iv3W7ICKoLui0ec6W6JmaftkPd4gPeA,1775
55
+ vantage/datasets/validations/checks/check_column_mean.py,sha256=LKy13YqwNwu3EPNqO1dVFkFW8mJPGu2410BUuLrK6LM,2265
56
+ vantage/datasets/validations/checks/check_column_median.py,sha256=os1xWke3dHop0izqMwH3raUq8yP7KLlZsPSUrYEIEdc,2377
57
+ vantage/datasets/validations/checks/check_column_min.py,sha256=IJ_Y1HNu71b3QrOQFeUIK3eJbd2FL_VpWK_88rjCz0A,1948
58
+ vantage/datasets/validations/checks/check_column_min_length.py,sha256=GV1Ujk8fpkz1RIJZGGcsyImaUEUnel74uik7EiAFxQM,1776
59
+ vantage/datasets/validations/checks/check_column_not_between.py,sha256=pkgHMoT33FiNt8ElK31LdfU10dxlInzeqvtIhY7w8NA,1907
60
+ vantage/datasets/validations/checks/check_column_not_null.py,sha256=abQz3N-ySgl7nBK6Yw_iPQsjyfDYdI-WaRB3Dc9FS6k,1346
61
+ vantage/datasets/validations/checks/check_column_percent_not_null.py,sha256=5Q97lrnSGCCfurLMNRhRfSzWSEw9TtJVmmvNQ6W0XmI,2534
62
+ vantage/datasets/validations/checks/check_column_percent_null.py,sha256=wuirX2XytzHIso212-bbwjJqcyLe6NkLA0XAh__8ies,2460
63
+ vantage/datasets/validations/checks/check_column_percentile.py,sha256=YHXvtabK_kLzaJOBnNYoT40TsvCIZ6lN1OaRzyZSivY,2670
64
+ vantage/datasets/validations/checks/check_column_regex_match.py,sha256=ZB4WS9eSxMXsYnZmq7d8LznAqIFSht5lfXmeAbvLEXI,1732
65
+ vantage/datasets/validations/checks/check_column_regex_not_match.py,sha256=RA8idqhQvnNfB1IlauiT5EowJ4bemuG0p0ejb48Qp6A,1749
66
+ vantage/datasets/validations/checks/check_column_stddev.py,sha256=QaUi17JMhb1NbK8AEpztrkFqvUEhL10_xMxq8lBCjwg,2321
67
+ vantage/datasets/validations/checks/check_column_sum.py,sha256=EUW7INL63Tp4e_5KclYEVOEdvAbjKRvfSKvzt1oPWm0,2060
68
+ vantage/datasets/validations/checks/check_column_unique.py,sha256=SDZV8w3x9RIPTUVff239ivn8luWBcBh2RutOkJOOFIM,1434
69
+ vantage/datasets/validations/checks/check_column_values_in_set.py,sha256=rrj_3ksfPFlVM8yVueAO5oM-998pz5gI1Qt6qWrn9zU,1953
70
+ vantage/datasets/validations/checks/check_column_values_not_in_set.py,sha256=KFAh51KfbOTNm-WajjgyL53M2xzU6HYR66hup_Cf02A,1919
71
+ vantage/datasets/validations/checks/check_custom_sql.py,sha256=Tq0IWZaRNR-yKRbZzjxB6gKpoDiIdHVVuHTzI7-w1VM,1622
72
+ vantage/datasets/validations/checks/check_table_duplicate_rows.py,sha256=nq_9xWmioqCXm9SQDsw11-2711I8IsTjA8RbPdF1uAk,1678
73
+ vantage/datasets/validations/checks/check_table_freshness.py,sha256=OdWKkI0M1Sx2iRJXJ92I_l8uzm3h6BO6S0RHSfAnp5o,2708
74
+ vantage/datasets/validations/checks/check_table_row_count.py,sha256=UrxIlQ7dSVNZRs66sXzYmP_Jw9AEN2AIRvq0skHfTFs,1610
75
+ vantage/datasets/validations/checks/check_table_row_count_between.py,sha256=jnw_VSTpErPvcrqVlhirFRgNp-DLOrtz7TiYKdxlvCw,1392
76
+ vantage/datasets/validations/checks/check_table_schema_change.py,sha256=3vGbhc1buvqNRdYO6RXjdh-8JSphfULxpMafFL8dIFI,712
77
+ vantage/datasets/validations/checks/run_check.py,sha256=NRoI-c0h35eDkuIPIsDLpARsDZgsO4QiNqnqlzT1nsA,6273
78
+ vantage/datasets/validations/checks/sql_helpers.py,sha256=IDAiuVyBxfw3hfV3k5OuYcBNUmVAtI8ipRDljAUdWow,2522
79
+ vantage/datasets/validations/recommendations/dedupe.py,sha256=Hu84bJ55VcwJwPhxkANgrqKbRpjxtMY1wut4MugcQa4,4921
80
+ vantage/datasets/validations/recommendations/fetch.py,sha256=YYMZfAMHGp8jqmnIqa2L4_Wrh96gdrcUrKVZL6ryvPY,11834
81
+ vantage/datasets/validations/recommendations/llm.py,sha256=ofDx55M01DdgN5xO4860zwUkhtgx1YQ5To1hiwyemzQ,2872
82
+ vantage/datasets/validations/recommendations/normalize.py,sha256=VnmsSCsXHrxmx1eW-HRgkvrBoFvY6KlmioscuazPKDQ,2437
83
+ vantage/datasets/validations/recommendations/prompt.py,sha256=yG3f10VZ1XZv9uzOznHd3h1a9j8XTZIYiFPh-EpU0nI,5914
84
+ vantage/datasets/validations/recommendations/save.py,sha256=5-MLHvY-TKA1PvQ5oj_0y-UYybT_6tiykXlohh8q_nw,6030
85
+ vantage/datasets/validations/recommendations/schemas.py,sha256=DV4at5srdG8nTEY5My9mnd7ljdSgYS5HMI_mZ988vwc,2504
86
+ vantage/kpis/compute/compute.py,sha256=18h_lnPDZCerdez5wdD7Ng2Z4f1-4Qll_h0_M8qfBfc,6818
87
+ vantage/kpis/compute/compute_manual.py,sha256=8HOAfb_77AfnoeY55C8AyLtDkv05mGJfRItaYU1TGoY,15517
88
+ vantage/kpis/compute/compute_sql.py,sha256=nXajHqIgb3cOUMG7v3pMxd7nrs7U3H2R5Gd1C7PcDqM,1213
89
+ vantage/kpis/compute/fetch.py,sha256=Yy2oOeaVa8p_wCo2JUBo1aXo1kTspcg8xIhwC74Csdw,1176
90
+ vantage/kpis/compute/save.py,sha256=QVZNMDTteWizSmItcr0QxqGhK-y76QmSGe6Exq46gT0,2291
91
+ vantage/kpis/compute/schemas.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
+ vantage/smart_cockpit/configuration.py,sha256=Sc0xzGRhyiM2wwn78aLiIzT4tELkxprwojFhJvjs8iY,1003
93
+ vantage/smart_cockpit/ai_summary/fetch.py,sha256=ro-z5xCd5PrN33q49enstP0Bvqz0PgqhvW7ThsYuJ9A,3938
94
+ vantage/smart_cockpit/ai_summary/llm.py,sha256=RzTyd5xnOmgNDTbdsKgqGBLyu8H_S55WCNl65wsTjA4,4012
95
+ vantage/smart_cockpit/ai_summary/prompt.py,sha256=7WdoVZs7fXOAiCvc2VUxGmpg1Ml9ooZSfLqQTUb4H20,3749
96
+ vantage/smart_cockpit/ai_summary/save.py,sha256=Ofryr-GrVeiIqcRzIgeixjzF4sgg6LxikrpArhkOW2I,2653
97
+ vantage/smart_cockpit/ai_summary/schemas.py,sha256=h2KoqFFoxuUnr1fmGaTjbucoXjqIGM2DHPzBnDwqWkM,471
98
+ vantage/smart_cockpit/company_news/llm.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
+ vantage/smart_cockpit/company_news/save.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
+ vantage/smart_cockpit/company_news/schemas.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
+ vantage/smart_cockpit/kpi_insights/fetch.py,sha256=IUGr0HDinkuhOGhK5UGWv7Ge1SUxyfrBRYQd-zaZ1NU,2219
102
+ vantage/smart_cockpit/kpi_insights/llm.py,sha256=4IbZQK2N5QcvW5kBNZY0t0lRgdznQyK4nC1DzbfdMN8,1777
103
+ vantage/smart_cockpit/kpi_insights/prompt.py,sha256=0T_n9NXC3UVkoWe-8EtETenLGje7FlsLKPzDRqGOyV4,6078
104
+ vantage/smart_cockpit/kpi_insights/save.py,sha256=NLxhxcM2cCKizk2auXgEq9_0TDc2nkoWtdphEitVvxk,2624
105
+ vantage/smart_cockpit/regenerate/generate_missing_kpi_insights.py,sha256=hbSPg3mm6RuChfKQQTvkFSO9nwCTy_OqEJSjfBu4Z3Q,1001
106
+ drc_vantage-1.0.7.dist-info/METADATA,sha256=Ay1L1uQ47NsZNXkLbFygh1AubWn_j6Ca6rMAxnv1TZw,1920
107
+ drc_vantage-1.0.7.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
108
+ drc_vantage-1.0.7.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
vantage/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ """DRC Vantage SDK — platform data pipelines."""
2
+
3
+ from vantage.client import Base, Client
4
+ from vantage.data_sources.data_source import DataSource
5
+ from vantage.data_sources.data_source_category import DataSourceCategory
6
+ from vantage.data_storage_system.data_storage_system import DataStorageSystem
7
+ from vantage.datasets.dataset import Dataset
8
+
9
+ __all__ = [
10
+ "Base",
11
+ "Client",
12
+ "DataSource",
13
+ "DataSourceCategory",
14
+ "DataStorageSystem",
15
+ "Dataset",
16
+ ]
vantage/client.py ADDED
@@ -0,0 +1,126 @@
1
+ """Vantage SDK client: database access and runtime configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import contextmanager
6
+ from typing import Any, Generator
7
+ from urllib.parse import quote_plus
8
+
9
+ from loguru import logger
10
+ from sqlalchemy import create_engine, text
11
+ from sqlalchemy.engine import Engine
12
+ from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
13
+
14
+
15
+ class Base(DeclarativeBase):
16
+ pass
17
+
18
+
19
+ def _get_secret(value: Any) -> str:
20
+ if hasattr(value, "get_secret_value"):
21
+ return value.get_secret_value()
22
+ return str(value)
23
+
24
+
25
+ class Client:
26
+ """Entry point for SDK runtime configuration and database access."""
27
+
28
+ def __init__(
29
+ self,
30
+ *,
31
+ vantage_schema: str = "vantage",
32
+ generate_ai_validations: bool = False,
33
+ postgres_host: str | None = None,
34
+ postgres_port: int = 5432,
35
+ postgres_user: str | None = None,
36
+ postgres_password: Any | None = None,
37
+ postgres_database: str = "vantage",
38
+ openai_api_key: str | None = None,
39
+ resend_api_key: str | None = None,
40
+ s3_client: Any | None = None,
41
+ s3_bucket_name: str | None = None,
42
+ ) -> None:
43
+ self.vantage_schema = vantage_schema
44
+ self.generate_ai_validations = generate_ai_validations
45
+ self.postgres_host = postgres_host
46
+ self.postgres_port = postgres_port
47
+ self.postgres_user = postgres_user
48
+ self.postgres_password = postgres_password
49
+ self.postgres_database = postgres_database
50
+ self.openai_api_key = openai_api_key
51
+ self.resend_api_key = resend_api_key
52
+ self.s3_client = s3_client
53
+ self.s3_bucket_name = s3_bucket_name
54
+ self._engine: Engine | None = None
55
+ self._session_maker: sessionmaker[Session] | None = None
56
+
57
+ @property
58
+ def is_postgres_configured(self) -> bool:
59
+ return bool(self.postgres_host and self.postgres_user and self.postgres_password)
60
+
61
+ @property
62
+ def is_s3_configured(self) -> bool:
63
+ return self.s3_client is not None and self.s3_bucket_name is not None
64
+
65
+ def _build_postgres_url(self, database: str | None = None) -> str:
66
+ if not self.is_postgres_configured:
67
+ raise ValueError("PostgreSQL is not configured. Set POSTGRES_* variables in your .env file.")
68
+
69
+ db = database or self.postgres_database
70
+ password = quote_plus(_get_secret(self.postgres_password))
71
+ user = quote_plus(self.postgres_user) # type: ignore[arg-type]
72
+ return f"postgresql+psycopg://{user}:{password}@{self.postgres_host}:{self.postgres_port}/{db}"
73
+
74
+ def get_engine(self) -> Engine:
75
+ if self._engine is None:
76
+ self._engine = create_engine(self._build_postgres_url(), pool_pre_ping=True)
77
+ return self._engine
78
+
79
+ def get_session_maker(self) -> sessionmaker[Session]:
80
+ if self._session_maker is None:
81
+ self._session_maker = sessionmaker(
82
+ bind=self.get_engine(),
83
+ class_=Session,
84
+ expire_on_commit=False,
85
+ )
86
+ return self._session_maker
87
+
88
+ @contextmanager
89
+ def get_session(self) -> Generator[Session, None, None]:
90
+ session = self.get_session_maker()()
91
+ try:
92
+ yield session
93
+ session.commit()
94
+ except Exception:
95
+ session.rollback()
96
+ raise
97
+ finally:
98
+ session.close()
99
+
100
+ def ensure_database_exists(self) -> None:
101
+ if not self.is_postgres_configured:
102
+ logger.warning("Database configuration is not valid, skipping database creation check")
103
+ return
104
+
105
+ engine = create_engine(
106
+ self._build_postgres_url("postgres"),
107
+ pool_pre_ping=True,
108
+ isolation_level="AUTOCOMMIT",
109
+ )
110
+
111
+ try:
112
+ with engine.connect() as conn:
113
+ exists = conn.execute(
114
+ text("SELECT 1 FROM pg_database WHERE datname = :dbname"),
115
+ {"dbname": self.postgres_database},
116
+ ).scalar()
117
+
118
+ if exists:
119
+ logger.info(f"Database '{self.postgres_database}' already exists")
120
+ return
121
+
122
+ logger.info(f"Database '{self.postgres_database}' does not exist, creating it...")
123
+ conn.execute(text(f'CREATE DATABASE "{self.postgres_database}"'))
124
+ logger.info(f"Database '{self.postgres_database}' created successfully")
125
+ finally:
126
+ engine.dispose()
vantage/core/logo.py ADDED
@@ -0,0 +1,157 @@
1
+ """Prepare and upload data source logos to S3 (mirrors frontend logo pipeline)."""
2
+
3
+ import io
4
+ import mimetypes
5
+ from pathlib import Path
6
+ from urllib.parse import urlparse
7
+ from urllib.request import Request, urlopen
8
+ from uuid import uuid4
9
+
10
+ from loguru import logger
11
+ from PIL import Image
12
+ from vantage.client import Client
13
+ from vantage.core.s3 import upload_object
14
+
15
+ SVG_MIME = "image/svg+xml"
16
+ MAX_INPUT_BYTES = 8 * 1024 * 1024
17
+ LOGO_MAX_DIMENSION = 200
18
+ WEBP_QUALITY = 85
19
+ LOGO_FETCH_USER_AGENT = "demo-flows/0.1 (Foresyn; data source logo sync)"
20
+
21
+
22
+ def get_extension_for_logo_content_type(content_type: str) -> str | None:
23
+ normalized = content_type.split(";")[0].strip().lower()
24
+ mapping = {
25
+ "image/webp": "webp",
26
+ "image/svg+xml": "svg",
27
+ "image/png": "png",
28
+ "image/jpeg": "jpg",
29
+ "image/jpg": "jpg",
30
+ }
31
+ return mapping.get(normalized)
32
+
33
+
34
+ def prepare_data_source_logo(
35
+ image_bytes: bytes,
36
+ *,
37
+ filename: str | None = None,
38
+ content_type: str | None = None,
39
+ ) -> tuple[bytes, str]:
40
+ """
41
+ Prepare a logo for upload.
42
+
43
+ Raster images: resize/compress to WebP, max 200×200px.
44
+ SVG: kept as vector text (no raster compression), basic structure check.
45
+ """
46
+ if len(image_bytes) > MAX_INPUT_BYTES:
47
+ raise ValueError("FILE_TOO_LARGE")
48
+
49
+ guessed_type = content_type or (
50
+ mimetypes.guess_type(filename or "")[0] if filename else None
51
+ )
52
+ looks_svg = guessed_type == SVG_MIME or (
53
+ filename and filename.lower().endswith(".svg")
54
+ )
55
+
56
+ if looks_svg:
57
+ return _prepare_svg_logo(image_bytes)
58
+
59
+ try:
60
+ return _prepare_raster_logo(image_bytes)
61
+ except ValueError:
62
+ raise
63
+ except Exception as exc:
64
+ raise ValueError("COMPRESS_FAILED") from exc
65
+
66
+
67
+ def upload_data_source_logo(client: Client, image_buffer: bytes, content_type: str) -> str:
68
+ """
69
+ Upload a data source logo to S3. Uses a random object name (never the client filename).
70
+
71
+ Returns:
72
+ S3 object key to store in ``DataSource.logoUrl`` (e.g. ``logos/uuid.webp``)
73
+ """
74
+ ext = get_extension_for_logo_content_type(content_type)
75
+ if not ext:
76
+ raise ValueError(f"Unsupported logo content type: {content_type}")
77
+
78
+ normalized_type = content_type.split(";")[0].strip().lower()
79
+ object_key = f"logos/{uuid4()}.{ext}"
80
+
81
+ try:
82
+ upload_object(
83
+ client=client,
84
+ object_key=object_key,
85
+ body=image_buffer,
86
+ content_type=normalized_type or f"image/{'jpeg' if ext == 'jpg' else ext}",
87
+ )
88
+ logger.info("Uploaded data source logo to %s", object_key)
89
+ return object_key
90
+ except Exception as exc:
91
+ logger.exception("Error uploading data source logo to S3")
92
+ raise RuntimeError(
93
+ f"Failed to upload data source logo: {exc if isinstance(exc, Exception) else 'Unknown error'}"
94
+ ) from exc
95
+
96
+
97
+ def upload_data_source_logo_from_path(client: Client, path: str | Path) -> str:
98
+ """Read a local file, prepare it, upload to S3, and return the object key."""
99
+ file_path = Path(path)
100
+ if not file_path.is_file():
101
+ raise FileNotFoundError(f"Logo file not found: {file_path}")
102
+
103
+ raw = file_path.read_bytes()
104
+ prepared, content_type = prepare_data_source_logo(raw, filename=file_path.name)
105
+ return upload_data_source_logo(client, prepared, content_type)
106
+
107
+
108
+ def upload_data_source_logo_from_url(client: Client, url: str) -> str:
109
+ """Download a remote image, prepare it, upload to S3, and return the object key."""
110
+ if not client.is_s3_configured:
111
+ return url
112
+
113
+ parsed = urlparse(url)
114
+ if parsed.scheme not in ("http", "https"):
115
+ raise ValueError(f"Invalid logo URL: {url}")
116
+
117
+ request = Request(url, headers={"User-Agent": LOGO_FETCH_USER_AGENT})
118
+ with urlopen(request, timeout=30) as response:
119
+ raw = response.read()
120
+ content_type = response.headers.get("Content-Type")
121
+ filename = Path(parsed.path).name or None
122
+
123
+ prepared, content_type = prepare_data_source_logo(
124
+ raw, filename=filename, content_type=content_type
125
+ )
126
+ return upload_data_source_logo(client, prepared, content_type)
127
+
128
+
129
+ def _prepare_svg_logo(image_bytes: bytes) -> tuple[bytes, str]:
130
+ try:
131
+ text = image_bytes.decode("utf-8")
132
+ except UnicodeDecodeError as exc:
133
+ raise ValueError("INVALID_SVG") from exc
134
+
135
+ if not text.strip().lstrip().lower().startswith("<svg"):
136
+ raise ValueError("INVALID_SVG")
137
+
138
+ return text.encode("utf-8"), SVG_MIME
139
+
140
+
141
+ def _prepare_raster_logo(image_bytes: bytes) -> tuple[bytes, str]:
142
+ with Image.open(io.BytesIO(image_bytes)) as img:
143
+ img.thumbnail(
144
+ (LOGO_MAX_DIMENSION, LOGO_MAX_DIMENSION), Image.Resampling.LANCZOS
145
+ )
146
+
147
+ if img.mode in ("RGBA", "LA") or (
148
+ img.mode == "P" and "transparency" in img.info
149
+ ):
150
+ img = img.convert("RGBA")
151
+ else:
152
+ img = img.convert("RGB")
153
+
154
+ out = io.BytesIO()
155
+ img.save(out, format="WEBP", quality=WEBP_QUALITY, method=6)
156
+
157
+ return out.getvalue(), "image/webp"
@@ -0,0 +1,51 @@
1
+ """Shared helpers for resolving platform organizations."""
2
+
3
+ from typing import Optional
4
+
5
+ from sqlalchemy import text
6
+
7
+ from vantage.client import Client
8
+
9
+
10
+ def get_organization_by_slug(client: Client, organization_slug: str) -> Optional[str]:
11
+ with client.get_session() as session:
12
+ organization = session.execute(
13
+ text(f"SELECT id FROM {client.vantage_schema}.organization WHERE slug = :organization_slug"),
14
+ {"organization_slug": organization_slug},
15
+ ).fetchone()
16
+ return organization[0] if organization else None
17
+
18
+
19
+ def get_organization_count(client: Client) -> int:
20
+ with client.get_session() as session:
21
+ row = session.execute(text(f"SELECT COUNT(*) FROM {client.vantage_schema}.organization")).fetchone()
22
+ return int(row[0]) if row else 0
23
+
24
+
25
+ def get_single_organization_id_if_only_one_exists(client: Client) -> Optional[str]:
26
+ with client.get_session() as session:
27
+ rows = session.execute(text(f"SELECT id FROM {client.vantage_schema}.organization LIMIT 2")).fetchall()
28
+ if len(rows) == 1:
29
+ return rows[0][0]
30
+ return None
31
+
32
+
33
+ def resolve_organization_id(client: Client,
34
+ organization_id: Optional[str] = None,
35
+ organization_slug: Optional[str] = None,
36
+ ) -> str:
37
+ if organization_slug:
38
+ org_id = get_organization_by_slug(client, organization_slug=organization_slug)
39
+ if not org_id:
40
+ raise ValueError(f"Organization with slug '{organization_slug}' not found")
41
+ return org_id
42
+ if organization_id:
43
+ return organization_id
44
+ org_id = get_single_organization_id_if_only_one_exists(client)
45
+ if org_id:
46
+ return org_id
47
+ if get_organization_count(client) == 0:
48
+ raise ValueError("No organization found; provide organization_id or organization_slug")
49
+ raise ValueError(
50
+ "Multiple organizations found; provide organization_id or organization_slug"
51
+ )
vantage/core/s3.py ADDED
@@ -0,0 +1,31 @@
1
+ """S3-compatible object storage client (MinIO, AWS S3, etc.)."""
2
+
3
+ from typing import Any
4
+
5
+ from vantage.client import Client
6
+
7
+
8
+ def get_s3_client(client: Client) -> Any:
9
+ if not client.is_s3_configured:
10
+ raise ValueError("S3 is not configured. Pass s3_client and s3_bucket_name to Client.")
11
+ return client.s3_client
12
+
13
+
14
+ def upload_object(
15
+ client: Client,
16
+ object_key: str,
17
+ body: bytes,
18
+ content_type: str,
19
+ ) -> str:
20
+ """Upload bytes to the configured bucket. Returns the object key."""
21
+ if not client.is_s3_configured:
22
+ raise ValueError("S3 is not configured. Pass s3_client and s3_bucket_name to Client.")
23
+
24
+ client.s3_client.put_object(
25
+ Bucket=client.s3_bucket_name,
26
+ Key=object_key,
27
+ Body=body,
28
+ ContentLength=len(body),
29
+ ContentType=content_type,
30
+ )
31
+ return object_key
@@ -0,0 +1,71 @@
1
+ """Platform data source definition."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Optional, Union
6
+
7
+ from vantage.client import Client
8
+ from vantage.data_sources.data_source_category import DataSourceCategory
9
+ from vantage.data_sources.sync.data_source_helpers import get_data_source_id_by_slug
10
+ from vantage.core.logo import (
11
+ upload_data_source_logo_from_path,
12
+ upload_data_source_logo_from_url,
13
+ )
14
+ from vantage.data_sources.sync.sync_data_source import sync_data_source
15
+ from vantage.core.organization import resolve_organization_id
16
+
17
+
18
+ @dataclass
19
+ class DataSource:
20
+ """Platform data source."""
21
+
22
+ client: Client
23
+ slug: str # URL-safe unique identifier for the data source
24
+ name: str # Display name shown in the platform
25
+ category: DataSourceCategory # Category this data source belongs to (synced first)
26
+ description: Optional[str] = None # Optional summary shown in the platform UI
27
+ logo_path: Optional[Union[str, Path]] = None # Local logo file uploaded on first sync
28
+ logo_url: Optional[str] = None # Remote logo URL or existing platform logo URL
29
+ organization_id: Optional[str] = None # Platform organization UUID (falls back to category value)
30
+ organization_slug: Optional[str] = None # Organization slug (falls back to category value)
31
+ data_source_id: Optional[str] = field(default=None, init=False) # Set after sync(); platform data source UUID
32
+
33
+ def _resolve_organization_id(self) -> str:
34
+ return resolve_organization_id(
35
+ self.client,
36
+ organization_id=self.organization_id or self.category.organization_id,
37
+ organization_slug=self.organization_slug or self.category.organization_slug,
38
+ )
39
+
40
+ def _resolve_logo_url(self) -> str:
41
+ if self.logo_path and self.logo_url:
42
+ raise ValueError("Provide only one of logo_path or logo_url")
43
+ if self.logo_path:
44
+ return upload_data_source_logo_from_path(self.client, self.logo_path)
45
+ if self.logo_url:
46
+ if self.logo_url.startswith(("http://", "https://")):
47
+ return upload_data_source_logo_from_url(self.client, self.logo_url)
48
+ return self.logo_url
49
+ raise ValueError("Either logo_path or logo_url is required")
50
+
51
+ def sync(self) -> str:
52
+ """Sync this data source (and its category) to the vantage. Returns the data source ID."""
53
+ org_id = self._resolve_organization_id()
54
+ self.category.organization_id = self.category.organization_id or org_id
55
+ category_id = self.category.sync()
56
+
57
+ with self.client.get_session() as session:
58
+ existing = get_data_source_id_by_slug(self.client, session, org_id, self.slug) is not None
59
+
60
+ logo_url = "" if existing else self._resolve_logo_url()
61
+
62
+ self.data_source_id = sync_data_source(
63
+ client=self.client,
64
+ slug=self.slug,
65
+ name=self.name,
66
+ logo_url=logo_url,
67
+ category_id=category_id,
68
+ description=self.description,
69
+ organization_id=org_id,
70
+ )
71
+ return self.data_source_id