bb-integrations-library 3.0.11__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 (217) hide show
  1. bb_integrations_lib/__init__.py +0 -0
  2. bb_integrations_lib/converters/__init__.py +0 -0
  3. bb_integrations_lib/gravitate/__init__.py +0 -0
  4. bb_integrations_lib/gravitate/base_api.py +20 -0
  5. bb_integrations_lib/gravitate/model.py +29 -0
  6. bb_integrations_lib/gravitate/pe_api.py +122 -0
  7. bb_integrations_lib/gravitate/rita_api.py +552 -0
  8. bb_integrations_lib/gravitate/sd_api.py +572 -0
  9. bb_integrations_lib/gravitate/testing/TTE/sd/models.py +1398 -0
  10. bb_integrations_lib/gravitate/testing/TTE/sd/tests/test_models.py +2987 -0
  11. bb_integrations_lib/gravitate/testing/__init__.py +0 -0
  12. bb_integrations_lib/gravitate/testing/builder.py +55 -0
  13. bb_integrations_lib/gravitate/testing/openapi.py +70 -0
  14. bb_integrations_lib/gravitate/testing/util.py +274 -0
  15. bb_integrations_lib/mappers/__init__.py +0 -0
  16. bb_integrations_lib/mappers/prices/__init__.py +0 -0
  17. bb_integrations_lib/mappers/prices/model.py +106 -0
  18. bb_integrations_lib/mappers/prices/price_mapper.py +127 -0
  19. bb_integrations_lib/mappers/prices/protocol.py +20 -0
  20. bb_integrations_lib/mappers/prices/util.py +61 -0
  21. bb_integrations_lib/mappers/rita_mapper.py +523 -0
  22. bb_integrations_lib/models/__init__.py +0 -0
  23. bb_integrations_lib/models/dtn_supplier_invoice.py +487 -0
  24. bb_integrations_lib/models/enums.py +28 -0
  25. bb_integrations_lib/models/pipeline_structs.py +76 -0
  26. bb_integrations_lib/models/probe/probe_event.py +20 -0
  27. bb_integrations_lib/models/probe/request_data.py +431 -0
  28. bb_integrations_lib/models/probe/resume_token.py +7 -0
  29. bb_integrations_lib/models/rita/audit.py +113 -0
  30. bb_integrations_lib/models/rita/auth.py +30 -0
  31. bb_integrations_lib/models/rita/bucket.py +17 -0
  32. bb_integrations_lib/models/rita/config.py +188 -0
  33. bb_integrations_lib/models/rita/constants.py +19 -0
  34. bb_integrations_lib/models/rita/crossroads_entities.py +293 -0
  35. bb_integrations_lib/models/rita/crossroads_mapping.py +428 -0
  36. bb_integrations_lib/models/rita/crossroads_monitoring.py +78 -0
  37. bb_integrations_lib/models/rita/crossroads_network.py +41 -0
  38. bb_integrations_lib/models/rita/crossroads_rules.py +80 -0
  39. bb_integrations_lib/models/rita/email.py +39 -0
  40. bb_integrations_lib/models/rita/issue.py +63 -0
  41. bb_integrations_lib/models/rita/mapping.py +227 -0
  42. bb_integrations_lib/models/rita/probe.py +58 -0
  43. bb_integrations_lib/models/rita/reference_data.py +110 -0
  44. bb_integrations_lib/models/rita/source_system.py +9 -0
  45. bb_integrations_lib/models/rita/workers.py +76 -0
  46. bb_integrations_lib/models/sd/bols_and_drops.py +241 -0
  47. bb_integrations_lib/models/sd/get_order.py +301 -0
  48. bb_integrations_lib/models/sd/orders.py +18 -0
  49. bb_integrations_lib/models/sd_api.py +115 -0
  50. bb_integrations_lib/pipelines/__init__.py +0 -0
  51. bb_integrations_lib/pipelines/parsers/__init__.py +0 -0
  52. bb_integrations_lib/pipelines/parsers/distribution_report/__init__.py +0 -0
  53. bb_integrations_lib/pipelines/parsers/distribution_report/order_by_site_product_parser.py +50 -0
  54. bb_integrations_lib/pipelines/parsers/distribution_report/tank_configs_parser.py +47 -0
  55. bb_integrations_lib/pipelines/parsers/dtn/__init__.py +0 -0
  56. bb_integrations_lib/pipelines/parsers/dtn/dtn_price_parser.py +102 -0
  57. bb_integrations_lib/pipelines/parsers/dtn/model.py +79 -0
  58. bb_integrations_lib/pipelines/parsers/price_engine/__init__.py +0 -0
  59. bb_integrations_lib/pipelines/parsers/price_engine/parse_accessorials_prices_parser.py +67 -0
  60. bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/__init__.py +0 -0
  61. bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/price_merge_parser.py +111 -0
  62. bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/price_sync_parser.py +107 -0
  63. bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/shared.py +81 -0
  64. bb_integrations_lib/pipelines/parsers/tank_reading_parser.py +155 -0
  65. bb_integrations_lib/pipelines/parsers/tank_sales_parser.py +144 -0
  66. bb_integrations_lib/pipelines/shared/__init__.py +0 -0
  67. bb_integrations_lib/pipelines/shared/allocation_matching.py +227 -0
  68. bb_integrations_lib/pipelines/shared/bol_allocation.py +2793 -0
  69. bb_integrations_lib/pipelines/steps/__init__.py +0 -0
  70. bb_integrations_lib/pipelines/steps/create_accessorials_step.py +80 -0
  71. bb_integrations_lib/pipelines/steps/distribution_report/__init__.py +0 -0
  72. bb_integrations_lib/pipelines/steps/distribution_report/distribution_report_datafram_to_raw_data.py +33 -0
  73. bb_integrations_lib/pipelines/steps/distribution_report/get_model_history_step.py +50 -0
  74. bb_integrations_lib/pipelines/steps/distribution_report/get_order_by_site_product_step.py +62 -0
  75. bb_integrations_lib/pipelines/steps/distribution_report/get_tank_configs_step.py +40 -0
  76. bb_integrations_lib/pipelines/steps/distribution_report/join_distribution_order_dos_step.py +85 -0
  77. bb_integrations_lib/pipelines/steps/distribution_report/upload_distribution_report_datafram_to_big_query.py +47 -0
  78. bb_integrations_lib/pipelines/steps/echo_step.py +14 -0
  79. bb_integrations_lib/pipelines/steps/export_dataframe_to_rawdata_step.py +28 -0
  80. bb_integrations_lib/pipelines/steps/exporting/__init__.py +0 -0
  81. bb_integrations_lib/pipelines/steps/exporting/bbd_export_payroll_file_step.py +107 -0
  82. bb_integrations_lib/pipelines/steps/exporting/bbd_export_readings_step.py +236 -0
  83. bb_integrations_lib/pipelines/steps/exporting/cargas_wholesale_bundle_upload_step.py +33 -0
  84. bb_integrations_lib/pipelines/steps/exporting/dataframe_flat_file_export.py +29 -0
  85. bb_integrations_lib/pipelines/steps/exporting/gcs_bucket_export_file_step.py +34 -0
  86. bb_integrations_lib/pipelines/steps/exporting/keyvu_export_step.py +356 -0
  87. bb_integrations_lib/pipelines/steps/exporting/pe_price_export_step.py +238 -0
  88. bb_integrations_lib/pipelines/steps/exporting/platform_science_order_sync_step.py +500 -0
  89. bb_integrations_lib/pipelines/steps/exporting/save_rawdata_to_disk.py +15 -0
  90. bb_integrations_lib/pipelines/steps/exporting/sftp_export_file_step.py +60 -0
  91. bb_integrations_lib/pipelines/steps/exporting/sftp_export_many_files_step.py +23 -0
  92. bb_integrations_lib/pipelines/steps/exporting/update_exported_orders_table_step.py +64 -0
  93. bb_integrations_lib/pipelines/steps/filter_step.py +22 -0
  94. bb_integrations_lib/pipelines/steps/get_latest_sync_date.py +34 -0
  95. bb_integrations_lib/pipelines/steps/importing/bbd_import_payroll_step.py +30 -0
  96. bb_integrations_lib/pipelines/steps/importing/get_order_numbers_to_export_step.py +138 -0
  97. bb_integrations_lib/pipelines/steps/importing/load_file_to_dataframe_step.py +46 -0
  98. bb_integrations_lib/pipelines/steps/importing/load_imap_attachment_step.py +172 -0
  99. bb_integrations_lib/pipelines/steps/importing/pe_bulk_sync_price_structure_step.py +68 -0
  100. bb_integrations_lib/pipelines/steps/importing/pe_price_merge_step.py +86 -0
  101. bb_integrations_lib/pipelines/steps/importing/sftp_file_config_step.py +124 -0
  102. bb_integrations_lib/pipelines/steps/importing/test_exact_file_match.py +57 -0
  103. bb_integrations_lib/pipelines/steps/null_step.py +15 -0
  104. bb_integrations_lib/pipelines/steps/pe_integration_job_step.py +32 -0
  105. bb_integrations_lib/pipelines/steps/processing/__init__.py +0 -0
  106. bb_integrations_lib/pipelines/steps/processing/archive_gcs_step.py +76 -0
  107. bb_integrations_lib/pipelines/steps/processing/archive_sftp_step.py +48 -0
  108. bb_integrations_lib/pipelines/steps/processing/bbd_format_tank_readings_step.py +492 -0
  109. bb_integrations_lib/pipelines/steps/processing/bbd_upload_prices_step.py +54 -0
  110. bb_integrations_lib/pipelines/steps/processing/bbd_upload_tank_sales_step.py +124 -0
  111. bb_integrations_lib/pipelines/steps/processing/bbd_upload_tankreading_step.py +80 -0
  112. bb_integrations_lib/pipelines/steps/processing/convert_bbd_order_to_cargas_step.py +226 -0
  113. bb_integrations_lib/pipelines/steps/processing/delete_sftp_step.py +33 -0
  114. bb_integrations_lib/pipelines/steps/processing/dtn/__init__.py +2 -0
  115. bb_integrations_lib/pipelines/steps/processing/dtn/convert_dtn_invoice_to_sd_model.py +145 -0
  116. bb_integrations_lib/pipelines/steps/processing/dtn/parse_dtn_invoice_step.py +38 -0
  117. bb_integrations_lib/pipelines/steps/processing/file_config_parser_step.py +720 -0
  118. bb_integrations_lib/pipelines/steps/processing/file_config_parser_step_v2.py +418 -0
  119. bb_integrations_lib/pipelines/steps/processing/get_sd_price_price_request.py +105 -0
  120. bb_integrations_lib/pipelines/steps/processing/keyvu_upload_deliveryplan_step.py +39 -0
  121. bb_integrations_lib/pipelines/steps/processing/mark_orders_exported_in_bbd_step.py +185 -0
  122. bb_integrations_lib/pipelines/steps/processing/pe_price_rows_processing_step.py +174 -0
  123. bb_integrations_lib/pipelines/steps/processing/send_process_report_step.py +47 -0
  124. bb_integrations_lib/pipelines/steps/processing/sftp_renamer_step.py +61 -0
  125. bb_integrations_lib/pipelines/steps/processing/tank_reading_touchup_steps.py +75 -0
  126. bb_integrations_lib/pipelines/steps/processing/upload_supplier_invoice_step.py +16 -0
  127. bb_integrations_lib/pipelines/steps/send_attached_in_rita_email_step.py +44 -0
  128. bb_integrations_lib/pipelines/steps/send_rita_email_step.py +34 -0
  129. bb_integrations_lib/pipelines/steps/sleep_step.py +24 -0
  130. bb_integrations_lib/pipelines/wrappers/__init__.py +0 -0
  131. bb_integrations_lib/pipelines/wrappers/accessorials_transformation.py +104 -0
  132. bb_integrations_lib/pipelines/wrappers/distribution_report.py +191 -0
  133. bb_integrations_lib/pipelines/wrappers/export_tank_readings.py +237 -0
  134. bb_integrations_lib/pipelines/wrappers/import_tank_readings.py +192 -0
  135. bb_integrations_lib/pipelines/wrappers/wrapper.py +81 -0
  136. bb_integrations_lib/protocols/__init__.py +0 -0
  137. bb_integrations_lib/protocols/flat_file.py +210 -0
  138. bb_integrations_lib/protocols/gravitate_client.py +104 -0
  139. bb_integrations_lib/protocols/pipelines.py +697 -0
  140. bb_integrations_lib/provider/__init__.py +0 -0
  141. bb_integrations_lib/provider/api/__init__.py +0 -0
  142. bb_integrations_lib/provider/api/cargas/__init__.py +0 -0
  143. bb_integrations_lib/provider/api/cargas/client.py +43 -0
  144. bb_integrations_lib/provider/api/cargas/model.py +49 -0
  145. bb_integrations_lib/provider/api/cargas/protocol.py +23 -0
  146. bb_integrations_lib/provider/api/dtn/__init__.py +0 -0
  147. bb_integrations_lib/provider/api/dtn/client.py +128 -0
  148. bb_integrations_lib/provider/api/dtn/protocol.py +9 -0
  149. bb_integrations_lib/provider/api/keyvu/__init__.py +0 -0
  150. bb_integrations_lib/provider/api/keyvu/client.py +30 -0
  151. bb_integrations_lib/provider/api/keyvu/model.py +149 -0
  152. bb_integrations_lib/provider/api/macropoint/__init__.py +0 -0
  153. bb_integrations_lib/provider/api/macropoint/client.py +28 -0
  154. bb_integrations_lib/provider/api/macropoint/model.py +40 -0
  155. bb_integrations_lib/provider/api/pc_miler/__init__.py +0 -0
  156. bb_integrations_lib/provider/api/pc_miler/client.py +130 -0
  157. bb_integrations_lib/provider/api/pc_miler/model.py +6 -0
  158. bb_integrations_lib/provider/api/pc_miler/web_services_apis.py +131 -0
  159. bb_integrations_lib/provider/api/platform_science/__init__.py +0 -0
  160. bb_integrations_lib/provider/api/platform_science/client.py +147 -0
  161. bb_integrations_lib/provider/api/platform_science/model.py +82 -0
  162. bb_integrations_lib/provider/api/quicktrip/__init__.py +0 -0
  163. bb_integrations_lib/provider/api/quicktrip/client.py +52 -0
  164. bb_integrations_lib/provider/api/telapoint/__init__.py +0 -0
  165. bb_integrations_lib/provider/api/telapoint/client.py +68 -0
  166. bb_integrations_lib/provider/api/telapoint/model.py +178 -0
  167. bb_integrations_lib/provider/api/warren_rogers/__init__.py +0 -0
  168. bb_integrations_lib/provider/api/warren_rogers/client.py +207 -0
  169. bb_integrations_lib/provider/aws/__init__.py +0 -0
  170. bb_integrations_lib/provider/aws/s3/__init__.py +0 -0
  171. bb_integrations_lib/provider/aws/s3/client.py +126 -0
  172. bb_integrations_lib/provider/ftp/__init__.py +0 -0
  173. bb_integrations_lib/provider/ftp/client.py +140 -0
  174. bb_integrations_lib/provider/ftp/interface.py +273 -0
  175. bb_integrations_lib/provider/ftp/model.py +76 -0
  176. bb_integrations_lib/provider/imap/__init__.py +0 -0
  177. bb_integrations_lib/provider/imap/client.py +228 -0
  178. bb_integrations_lib/provider/imap/model.py +3 -0
  179. bb_integrations_lib/provider/sqlserver/__init__.py +0 -0
  180. bb_integrations_lib/provider/sqlserver/client.py +106 -0
  181. bb_integrations_lib/secrets/__init__.py +4 -0
  182. bb_integrations_lib/secrets/adapters.py +98 -0
  183. bb_integrations_lib/secrets/credential_models.py +222 -0
  184. bb_integrations_lib/secrets/factory.py +85 -0
  185. bb_integrations_lib/secrets/providers.py +160 -0
  186. bb_integrations_lib/shared/__init__.py +0 -0
  187. bb_integrations_lib/shared/exceptions.py +25 -0
  188. bb_integrations_lib/shared/model.py +1039 -0
  189. bb_integrations_lib/shared/shared_enums.py +510 -0
  190. bb_integrations_lib/storage/README.md +236 -0
  191. bb_integrations_lib/storage/__init__.py +0 -0
  192. bb_integrations_lib/storage/aws/__init__.py +0 -0
  193. bb_integrations_lib/storage/aws/s3.py +8 -0
  194. bb_integrations_lib/storage/defaults.py +72 -0
  195. bb_integrations_lib/storage/gcs/__init__.py +0 -0
  196. bb_integrations_lib/storage/gcs/client.py +8 -0
  197. bb_integrations_lib/storage/gcsmanager/__init__.py +0 -0
  198. bb_integrations_lib/storage/gcsmanager/client.py +8 -0
  199. bb_integrations_lib/storage/setup.py +29 -0
  200. bb_integrations_lib/util/__init__.py +0 -0
  201. bb_integrations_lib/util/cache/__init__.py +0 -0
  202. bb_integrations_lib/util/cache/custom_ttl_cache.py +75 -0
  203. bb_integrations_lib/util/cache/protocol.py +9 -0
  204. bb_integrations_lib/util/config/__init__.py +0 -0
  205. bb_integrations_lib/util/config/manager.py +391 -0
  206. bb_integrations_lib/util/config/model.py +41 -0
  207. bb_integrations_lib/util/exception_logger/__init__.py +0 -0
  208. bb_integrations_lib/util/exception_logger/exception_logger.py +146 -0
  209. bb_integrations_lib/util/exception_logger/test.py +114 -0
  210. bb_integrations_lib/util/utils.py +364 -0
  211. bb_integrations_lib/workers/__init__.py +0 -0
  212. bb_integrations_lib/workers/groups.py +13 -0
  213. bb_integrations_lib/workers/rpc_worker.py +50 -0
  214. bb_integrations_lib/workers/topics.py +20 -0
  215. bb_integrations_library-3.0.11.dist-info/METADATA +59 -0
  216. bb_integrations_library-3.0.11.dist-info/RECORD +217 -0
  217. bb_integrations_library-3.0.11.dist-info/WHEEL +4 -0
@@ -0,0 +1,104 @@
1
+ from bb_integrations_lib.mappers.prices.model import PricePublisher
2
+ from bb_integrations_lib.models.pipeline_structs import PipelineProcessReportConfig
3
+ from bb_integrations_lib.pipelines.parsers.price_engine.parse_accessorials_prices_parser import AccessorialPricesParser
4
+ from bb_integrations_lib.pipelines.steps.create_accessorials_step import BBDUploadAccessorialsStep
5
+ from bb_integrations_lib.pipelines.wrappers.wrapper import PipelineWrapper
6
+ from bb_integrations_lib.protocols.pipelines import JobPipeline
7
+ from bb_integrations_lib.pipelines.steps.exporting.pe_price_export_step import PEPriceExportStep
8
+ from bb_integrations_lib.util.config.manager import GlobalConfigManager
9
+ from loguru import logger
10
+ from pydantic import BaseModel
11
+
12
+
13
+
14
+ class AccessorialPipelineConfig(BaseModel):
15
+ price_publishers: list[PricePublisher]
16
+ accessorial_date_timezone: str = "America/New_York"
17
+ price_instrument_ids: list[int]
18
+ source_system: str = "LCFS"
19
+ hours_back: int = 24
20
+
21
+
22
+
23
+ class AccesorialsPriceTransformationPipeline(JobPipeline):
24
+ def __init__(self,
25
+ config: AccessorialPipelineConfig,
26
+ config_id: str,
27
+ tenant_name: str = "Loves",
28
+ mode: str = "development",
29
+ ):
30
+ self.mode = mode
31
+ self.tenant_name = tenant_name
32
+ self.config_manager = GlobalConfigManager()
33
+ self.secret_data = self.config_manager.get_environment(environment_name=self.tenant_name)
34
+ self.config = config
35
+ self.config_id = config_id
36
+ self.process_report_config = PipelineProcessReportConfig(
37
+ config_id=self.config_id,
38
+ trigger=f"{self.tenant_name} Custom Accessorials Price Integration",
39
+ rita_url=self.secret_data.prod.rita.base_url,
40
+ rita_client_id=self.secret_data.prod.rita.client_id,
41
+ rita_client_secret=self.secret_data.prod.rita.client_secret,
42
+ rita_tenant=self.tenant_name,
43
+ )
44
+ steps = [
45
+ {
46
+ "id": "1",
47
+ "parent_id": None,
48
+ "step": PEPriceExportStep({
49
+ "tenant_name": self.tenant_name,
50
+ "price_publishers": self.config.price_publishers,
51
+ "mode": self.mode,
52
+ "config_id": self.config_id,
53
+ "hours_back": self.config.hours_back,
54
+ "additional_endpoint_arguments": {
55
+ "IsActiveFilterType": "ActiveOnly",
56
+ "PriceInstrumentIds": self.config.price_instrument_ids,
57
+ "IncludeSourceData": False,
58
+ "IncludeFormulaResultData": False
59
+ },
60
+ "parser": AccessorialPricesParser,
61
+ "parser_kwargs": {
62
+ "source_system":self.config.source_system,
63
+ "timezone": self.config.accessorial_date_timezone,
64
+ }
65
+ })
66
+ },
67
+ {
68
+ "id": "2",
69
+ "parent_id": "1",
70
+ "step": BBDUploadAccessorialsStep({
71
+ "tenant_name": self.tenant_name,
72
+ "mode": self.mode,
73
+ })
74
+ }
75
+
76
+ ]
77
+ super().__init__(steps, None, catch_step_errors=True, process_report_config=self.process_report_config)
78
+
79
+
80
+ class CreateAccessorialReportPipeline(PipelineWrapper):
81
+ def __init__(self,
82
+ tenant_name: str,
83
+ bucket_name: str,
84
+ mode: str = "test",
85
+ config_class=AccessorialPipelineConfig,
86
+ ):
87
+ super().__init__(
88
+ tenant_name=tenant_name,
89
+ bucket_name=bucket_name,
90
+ config_class=config_class,
91
+ mode=mode
92
+ )
93
+
94
+ async def create(self,
95
+ config_name: str,
96
+ ) -> AccesorialsPriceTransformationPipeline:
97
+ config, config_id, name = await self.load_config(config_name)
98
+ logger.info(
99
+ f"Loaded config for tenant '{self.tenant_name}' with name '{name}': {config.model_dump()}"
100
+ )
101
+ return AccesorialsPriceTransformationPipeline(config_id=config_id,
102
+ tenant_name=self.tenant_name,
103
+ config=config,
104
+ mode=self.mode)
@@ -0,0 +1,191 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+ from pydantic import BaseModel
4
+ from bb_integrations_lib.models.pipeline_structs import PipelineProcessReportConfig
5
+ from bb_integrations_lib.pipelines.parsers.distribution_report.order_by_site_product_parser import \
6
+ OrderBySiteProductParser
7
+ from bb_integrations_lib.pipelines.parsers.distribution_report.tank_configs_parser import TankConfigsParser
8
+ from bb_integrations_lib.pipelines.steps.distribution_report.distribution_report_datafram_to_raw_data import \
9
+ DistributionReportDfToRawData
10
+ from bb_integrations_lib.pipelines.steps.distribution_report.get_model_history_step import GetModelHistoryStep
11
+ from bb_integrations_lib.pipelines.steps.distribution_report.get_order_by_site_product_step import \
12
+ GetOrderBySiteProductStep
13
+ from bb_integrations_lib.pipelines.steps.distribution_report.get_tank_configs_step import GetTankConfigsStep
14
+ from bb_integrations_lib.pipelines.steps.distribution_report.join_distribution_order_dos_step import \
15
+ JoinDistributionOrderDosStep
16
+ from bb_integrations_lib.pipelines.steps.distribution_report.upload_distribution_report_datafram_to_big_query import \
17
+ UploadDistributionReportToBigQuery
18
+ from bb_integrations_lib.pipelines.steps.exporting.sftp_export_file_step import SFTPExportFileStep
19
+ from bb_integrations_lib.pipelines.steps.send_attached_in_rita_email_step import SendAttachedInRitaEmailStep
20
+ from bb_integrations_lib.pipelines.wrappers.wrapper import PipelineWrapper
21
+ from bb_integrations_lib.protocols.pipelines import JobPipeline
22
+ from bb_integrations_lib.shared.model import DistributionReportConfig
23
+ from bb_integrations_lib.util.config.model import GlobalConfig
24
+ from loguru import logger
25
+
26
+
27
+ class DistributionReportPipeline(JobPipeline):
28
+ def __init__(self,
29
+ tenant_name: str,
30
+ pipeline_config_id: str,
31
+ config_name: str,
32
+ secret_data: GlobalConfig,
33
+ config: DistributionReportConfig,
34
+ mode: str = "test",
35
+ ftp_creds: Optional[str] = None,
36
+ sd_basic_auth: bool = False,
37
+ ):
38
+ self.mode = mode
39
+ self.tenant_name = tenant_name
40
+ self.config = config
41
+ self.config_id = pipeline_config_id
42
+ self.config_name = config_name
43
+ self.secret_data = secret_data
44
+ self.ftp_creds = ftp_creds
45
+ self.process_report_config = PipelineProcessReportConfig(
46
+ config_id=self.config_id,
47
+ trigger=self.config_name,
48
+ )
49
+ steps = [
50
+ {
51
+ "id": "get_model_history",
52
+ "parent_id": None,
53
+ "step": GetModelHistoryStep({
54
+ "tenant_name": self.tenant_name,
55
+ "mode": self.mode,
56
+ "n_hours_back": self.config.n_hours_back,
57
+ "include_model_mode": self.config.include_model_mode,
58
+ "state": self.config.order_state,
59
+ })
60
+ },
61
+ {
62
+ "id": "get_tank_config",
63
+ "parent_id": "get_model_history",
64
+ "step": GetTankConfigsStep({
65
+ "tenant_name": self.tenant_name,
66
+ "mode": self.mode,
67
+ "parser": TankConfigsParser
68
+ }),
69
+ },
70
+ {
71
+ "id": "get_orders",
72
+ "parent_id": "get_model_history",
73
+ "step":
74
+ GetOrderBySiteProductStep({
75
+ "tenant_name": self.tenant_name,
76
+ "mode": self.mode,
77
+ "parser": OrderBySiteProductParser,
78
+ "include_model_mode": self.config.include_model_mode,
79
+ "sd_basic_auth": sd_basic_auth,
80
+ })
81
+ },
82
+ {
83
+ "id": "join_df",
84
+ "parent_id": "get_orders",
85
+ "alt_input": "get_model_history",
86
+ "step":
87
+ JoinDistributionOrderDosStep({
88
+ "tenant_name": self.tenant_name,
89
+ })
90
+ },
91
+ {
92
+ "id": "upload_to_gbq",
93
+ "parent_id": "join_df",
94
+ "step":
95
+ UploadDistributionReportToBigQuery({
96
+ "google_project_id": self.config.google_project_id,
97
+ "gbq_table_summary": self.config.gbq_table_summary,
98
+ "gbq_table_details": self.config.gbq_table_details,
99
+ })
100
+
101
+ }
102
+ ]
103
+ if self.config.ftp_directory is not None or self.config.email_addresses is not None:
104
+ steps.append({
105
+ "id": "df_to_raw_data",
106
+ "parent_id": "join_df",
107
+ "step":
108
+ DistributionReportDfToRawData({
109
+ "tenant_name": self.tenant_name,
110
+ "file_base_name": self.config.file_base_name,
111
+ "file_name_date_format": self.config.file_name_date_format,
112
+ })
113
+
114
+ })
115
+ if self.config.ftp_directory is not None:
116
+ steps.append({
117
+ "id": "upload_to_ftp",
118
+ "parent_id": "df_to_raw_data",
119
+ "step": SFTPExportFileStep({
120
+ "ftp_creds": self.ftp_creds,
121
+ "sftp_destination_dir": self.config.ftp_directory,
122
+ })
123
+ })
124
+ if self.config.email_addresses is not None:
125
+ steps.append({
126
+ "id": "send_email",
127
+ "parent_id": "df_to_raw_data",
128
+ "step": SendAttachedInRitaEmailStep({
129
+ "base_url": "https://rita.gravitate.energy",
130
+ "client_id": self.secret_data.prod.rita.client_id,
131
+ "client_secret": self.secret_data.prod.rita.client_secret,
132
+ "rita_tenant": self.tenant_name,
133
+ "to": self.config.email_addresses,
134
+ "html_content": "Distribution Report",
135
+ "subject": f"Gravitate Distribution Report - {datetime.now().isoformat()}",
136
+ "use_extension": False,
137
+ })
138
+ })
139
+ super().__init__(steps, initial_input=None, catch_step_errors=True,
140
+ process_report_config=self.process_report_config)
141
+
142
+
143
+ class CreateDistributionReportPipeline(PipelineWrapper):
144
+ def __init__(self,
145
+ tenant_name: str,
146
+ bucket_name: str,
147
+ run_mode: str,
148
+ config_class=DistributionReportConfig,
149
+ ftp_creds: Optional[str] = None,
150
+ sd_basic_auth: bool = False,
151
+ ):
152
+ self.tenant_name = tenant_name
153
+ self.bucket_name = bucket_name
154
+ self.mode = run_mode
155
+ self.ftp_creds = ftp_creds
156
+ self.sd_basic_auth = sd_basic_auth
157
+ super().__init__(
158
+ tenant_name=tenant_name,
159
+ bucket_name=bucket_name,
160
+ config_class=config_class,
161
+ mode=run_mode
162
+ )
163
+
164
+ async def create(self, config_name: str) -> DistributionReportPipeline:
165
+ config, config_id, name = await self.load_config(config_name)
166
+ logger.info(
167
+ f"Loaded config for tenant '{self.tenant_name}' with name '{name}': {config.model_dump()}"
168
+ )
169
+ return DistributionReportPipeline(
170
+ tenant_name=self.tenant_name,
171
+ config=config,
172
+ pipeline_config_id=config_id,
173
+ secret_data=self.secret_data,
174
+ config_name=name,
175
+ mode=self.mode,
176
+ ftp_creds=self.ftp_creds,
177
+ sd_basic_auth=self.sd_basic_auth
178
+ )
179
+
180
+ async def run(self, base: str = "Distribution Report"):
181
+ config_name = f"{self.tenant_name} - {base}"
182
+ try:
183
+ logger.info(f"Starting distribution report pipeline for tenant '{self.tenant_name}' with config '{config_name}'")
184
+ pipeline = await self.create(config_name)
185
+ await pipeline.execute()
186
+ logger.info(f"Completed pipeline for client: {self.tenant_name}")
187
+ return f"Success: {self.tenant_name}"
188
+ except Exception as e:
189
+ logger.error(
190
+ f"Failed to run import pipeline for client '{self.tenant_name}' with config '{config_name}': {e}")
191
+ return f"Error: {self.tenant_name} - {str(e)}"
@@ -0,0 +1,237 @@
1
+ import asyncio
2
+ import csv
3
+ from datetime import datetime, UTC
4
+ from typing import Optional, List, Dict, Union
5
+ from loguru import logger
6
+
7
+ from bb_integrations_lib.gravitate.rita_api import GravitateRitaAPI
8
+ from bb_integrations_lib.models.pipeline_structs import PipelineProcessReportConfig
9
+ from bb_integrations_lib.pipelines.steps.export_dataframe_to_rawdata_step import ExportDataFrameToRawDataStep
10
+ from bb_integrations_lib.pipelines.steps.exporting.bbd_export_readings_step import BBDExportReadingsStep
11
+ from bb_integrations_lib.pipelines.steps.exporting.sftp_export_file_step import SFTPExportFileStep
12
+ from bb_integrations_lib.pipelines.steps.processing.bbd_format_tank_readings_step import ParseTankReadingsStep
13
+ from bb_integrations_lib.pipelines.steps.processing.tank_reading_touchup_steps import TRTouchUpStep
14
+ from bb_integrations_lib.pipelines.steps.send_attached_in_rita_email_step import SendAttachedInRitaEmailStep
15
+ from bb_integrations_lib.pipelines.wrappers.wrapper import PipelineWrapper
16
+ from bb_integrations_lib.protocols.pipelines import JobPipeline, Step
17
+ from bb_integrations_lib.shared.model import ExportReadingsConfig, FileFormat
18
+ from bb_integrations_lib.util.config.manager import GlobalConfigManager
19
+ from bb_integrations_lib.util.config.model import GlobalConfig
20
+
21
+
22
+ class BBDReadingExportPipeline(JobPipeline):
23
+ def __init__(
24
+ self,
25
+ rita_tenant_name: str,
26
+ config: ExportReadingsConfig,
27
+ pipeline_config_id: str,
28
+ secret_data: GlobalConfig,
29
+ config_name: str,
30
+ ftp_creds: str,
31
+ mode: str,
32
+ touchup_step: TRTouchUpStep,
33
+ file_date: Optional[datetime] = None,
34
+ use_polars: bool = False,
35
+ ):
36
+ self.mode = mode
37
+ self.tenant_name = rita_tenant_name
38
+ self.config_id = pipeline_config_id
39
+ self.config = config
40
+ self.secret_data = secret_data
41
+ self.config_name = config_name
42
+ self.ftp_creds = ftp_creds
43
+ self.touchup_step = touchup_step
44
+ self.file_date = file_date or datetime.now(UTC)
45
+ self.file_name = f"{self.config.file_base_name}_{self.file_date.strftime(config.file_name_date_format)}.csv"
46
+ self.use_polars = use_polars
47
+
48
+ self.process_report_config = PipelineProcessReportConfig(
49
+ config_id=self.config_id,
50
+ trigger=self.config_name,
51
+ )
52
+ steps = [
53
+ {
54
+ "id": "1",
55
+ "parent_id": None,
56
+ "step": BBDExportReadingsStep({
57
+ "tenant_name": self.tenant_name,
58
+ "readings_query": self.config.reading_query.model_dump(),
59
+ "hours_back": self.config.hours_back,
60
+ "window_mode": self.config.window_mode,
61
+ "timezone": self.config.reading_reported_timezone
62
+ })
63
+ },
64
+ {
65
+ "id": "2",
66
+ "parent_id": "1",
67
+ "step": ParseTankReadingsStep({
68
+ "format": self.config.file_format,
69
+ "timezone": self.config.reading_reported_timezone,
70
+ "include_water_level": self.config.include_water_level,
71
+ "disconnected_column": self.config.disconnected_column,
72
+ "disconnected_only": self.config.disconnected_only,
73
+ "disconnected_hours_threshold": self.config.disconnected_hours_threshold,
74
+ })
75
+ },
76
+ ]
77
+ if self.touchup_step is not None:
78
+ steps.append({
79
+ "id": "touchup",
80
+ "parent_id": "2",
81
+ "step": self.touchup_step,
82
+ })
83
+ final_build_step = "touchup"
84
+ else:
85
+ final_build_step = "2"
86
+ pd_export_function = "write_csv" if self.use_polars else "to_csv"
87
+ if self.use_polars:
88
+ pd_kwargs = {"include_header": True}
89
+ if self.config.file_format == FileFormat.reduced:
90
+ pd_kwargs["quote_style"] = "never"
91
+ else:
92
+ pd_kwargs = {"header": True, "index": False}
93
+ if self.config.file_format == FileFormat.reduced:
94
+ pd_kwargs["quoting"] = csv.QUOTE_NONE
95
+ pd_kwargs["escapechar"] = "\\"
96
+
97
+ steps.append({
98
+ "id": "3",
99
+ "parent_id": final_build_step,
100
+ "step": ExportDataFrameToRawDataStep({
101
+ "pandas_export_function": pd_export_function,
102
+ "pandas_export_kwargs": pd_kwargs,
103
+ "file_name": self.file_name
104
+ })
105
+ })
106
+
107
+ if self.config.ftp_directory is not None:
108
+ steps.append({
109
+ "id": "4",
110
+ "parent_id": "3",
111
+ "step": SFTPExportFileStep({
112
+ "ftp_creds": self.ftp_creds,
113
+ "sftp_destination_dir": self.config.ftp_directory,
114
+ })
115
+ })
116
+ if self.config.email_addresses is not None:
117
+ steps.append({
118
+ "id": "send_email",
119
+ "parent_id": "3",
120
+ "step": SendAttachedInRitaEmailStep({
121
+ "base_url": "https://rita.gravitate.energy",
122
+ "client_id": self.secret_data.prod.rita.client_id,
123
+ "client_secret": self.secret_data.prod.rita.client_secret,
124
+ "rita_tenant": self.tenant_name,
125
+ "to": self.config.email_addresses,
126
+ "html_content": "Tank Reading Export",
127
+ "subject": f"Gravitate Tank Reading Export - {datetime.now().isoformat()}",
128
+ "timeout": 30.0,
129
+ "use_extension": True
130
+ })
131
+ })
132
+ super().__init__(steps, None, catch_step_errors=True, process_report_config=self.process_report_config)
133
+
134
+
135
+ class CreateExportReadingPipeline(PipelineWrapper):
136
+ def __init__(self,
137
+ tenant_name: str,
138
+ bucket_name: str,
139
+ config_class=ExportReadingsConfig,
140
+ ftp_creds: Optional[str] = None,
141
+ mode: str = "production",
142
+ touchup_step: Optional[TRTouchUpStep] = None,
143
+ file_date_override: Optional[datetime] = None,
144
+ use_polars: bool = False,
145
+ ) -> None:
146
+ self.mode = mode
147
+ self.ftp_creds = ftp_creds
148
+ self.touchup_step = touchup_step
149
+ self.file_date_override = file_date_override
150
+ self.use_polars = use_polars
151
+ super().__init__(
152
+ tenant_name=tenant_name,
153
+ bucket_name=bucket_name,
154
+ config_class=config_class
155
+ )
156
+
157
+ async def create(self, config_name: str, client_ftp_creds: Optional[str] = None) -> BBDReadingExportPipeline:
158
+ """Create a pipeline instance for a specific config name."""
159
+ config, config_id, name = await self.load_config(config_name)
160
+ logger.info(
161
+ f"Loaded config for tenant '{self.tenant_name}' with name '{name}': {config.model_dump()}"
162
+ )
163
+
164
+ # Determine FTP credentials to use (priority: client-specific > instance-level > config)
165
+ ftp_creds = client_ftp_creds or self.ftp_creds or config.ftp_credentials
166
+
167
+ if client_ftp_creds:
168
+ logger.info(f"Using client-specific FTP credentials for config '{config_name}'")
169
+ elif self.ftp_creds:
170
+ logger.info(f"Using instance-level FTP credentials for config '{config_name}'")
171
+ else:
172
+ logger.info(f"Using config-level FTP credentials for config '{config_name}'")
173
+
174
+ return BBDReadingExportPipeline(
175
+ rita_tenant_name=self.tenant_name,
176
+ config=config,
177
+ pipeline_config_id=config_id,
178
+ secret_data=self.secret_data,
179
+ config_name=name,
180
+ ftp_creds=ftp_creds,
181
+ mode=self.mode,
182
+ touchup_step=self.touchup_step,
183
+ file_date=self.file_date_override,
184
+ use_polars=self.use_polars,
185
+ )
186
+
187
+ async def run_for_client(self, client_name: str, base: str = "ATG Readings Export",
188
+ client_ftp_creds: Optional[str] = None):
189
+ """Run pipeline for a specific client with optional custom FTP credentials."""
190
+ config_name = f"{self.tenant_name} - {client_name} {base}"
191
+ try:
192
+ logger.info(f"Starting export pipeline for client '{client_name}' with config '{config_name}'")
193
+ pipeline = await self.create(config_name, client_ftp_creds)
194
+ await pipeline.execute()
195
+ logger.info(f"Completed pipeline for client: {client_name}")
196
+ return f"Success: {client_name}"
197
+ except Exception as e:
198
+ logger.error(f"Failed to run export pipeline for client '{client_name}' with config '{config_name}': {e}")
199
+ return f"Error: {client_name} - {str(e)}"
200
+
201
+ async def run_pipeline_with_delay(self, client_name: str, delay: int, base: str = "ATG Readings Export",
202
+ client_ftp_creds: Optional[str] = None):
203
+ """Run pipeline for a client with a delay."""
204
+ if delay > 0:
205
+ logger.info(f"Waiting {delay} seconds before starting pipeline for client: {client_name}")
206
+ await asyncio.sleep(delay)
207
+ return await self.run_for_client(client_name, base, client_ftp_creds)
208
+
209
+ async def run_for_many_clients(self, clients: Union[List[str], Dict[str, str]],
210
+ base: str = "ATG Readings Export"):
211
+ """
212
+ Run pipelines for multiple clients with staggered delays.
213
+
214
+ Args:
215
+ clients: Either a list of client names or a dict mapping client names to FTP credentials
216
+ base: Base name for config lookup
217
+ """
218
+ # Handle both list and dict formats
219
+ if isinstance(clients, dict):
220
+ client_names = list(clients.keys())
221
+ tasks = [
222
+ self.run_pipeline_with_delay(client, i * 3, base, clients.get(client))
223
+ for i, client in enumerate(client_names)
224
+ ]
225
+ else:
226
+ client_names = clients
227
+ tasks = [
228
+ self.run_pipeline_with_delay(client, i * 3, base)
229
+ for i, client in enumerate(client_names)
230
+ ]
231
+
232
+ logger.info(f"Starting {len(tasks)} pipeline tasks for clients: {client_names}")
233
+ results = await asyncio.gather(*tasks, return_exceptions=True)
234
+ logger.info("Results:")
235
+ for client, result in zip(client_names, results):
236
+ logger.info(f"{client}: {result}")
237
+ return results