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.
- bb_integrations_lib/__init__.py +0 -0
- bb_integrations_lib/converters/__init__.py +0 -0
- bb_integrations_lib/gravitate/__init__.py +0 -0
- bb_integrations_lib/gravitate/base_api.py +20 -0
- bb_integrations_lib/gravitate/model.py +29 -0
- bb_integrations_lib/gravitate/pe_api.py +122 -0
- bb_integrations_lib/gravitate/rita_api.py +552 -0
- bb_integrations_lib/gravitate/sd_api.py +572 -0
- bb_integrations_lib/gravitate/testing/TTE/sd/models.py +1398 -0
- bb_integrations_lib/gravitate/testing/TTE/sd/tests/test_models.py +2987 -0
- bb_integrations_lib/gravitate/testing/__init__.py +0 -0
- bb_integrations_lib/gravitate/testing/builder.py +55 -0
- bb_integrations_lib/gravitate/testing/openapi.py +70 -0
- bb_integrations_lib/gravitate/testing/util.py +274 -0
- bb_integrations_lib/mappers/__init__.py +0 -0
- bb_integrations_lib/mappers/prices/__init__.py +0 -0
- bb_integrations_lib/mappers/prices/model.py +106 -0
- bb_integrations_lib/mappers/prices/price_mapper.py +127 -0
- bb_integrations_lib/mappers/prices/protocol.py +20 -0
- bb_integrations_lib/mappers/prices/util.py +61 -0
- bb_integrations_lib/mappers/rita_mapper.py +523 -0
- bb_integrations_lib/models/__init__.py +0 -0
- bb_integrations_lib/models/dtn_supplier_invoice.py +487 -0
- bb_integrations_lib/models/enums.py +28 -0
- bb_integrations_lib/models/pipeline_structs.py +76 -0
- bb_integrations_lib/models/probe/probe_event.py +20 -0
- bb_integrations_lib/models/probe/request_data.py +431 -0
- bb_integrations_lib/models/probe/resume_token.py +7 -0
- bb_integrations_lib/models/rita/audit.py +113 -0
- bb_integrations_lib/models/rita/auth.py +30 -0
- bb_integrations_lib/models/rita/bucket.py +17 -0
- bb_integrations_lib/models/rita/config.py +188 -0
- bb_integrations_lib/models/rita/constants.py +19 -0
- bb_integrations_lib/models/rita/crossroads_entities.py +293 -0
- bb_integrations_lib/models/rita/crossroads_mapping.py +428 -0
- bb_integrations_lib/models/rita/crossroads_monitoring.py +78 -0
- bb_integrations_lib/models/rita/crossroads_network.py +41 -0
- bb_integrations_lib/models/rita/crossroads_rules.py +80 -0
- bb_integrations_lib/models/rita/email.py +39 -0
- bb_integrations_lib/models/rita/issue.py +63 -0
- bb_integrations_lib/models/rita/mapping.py +227 -0
- bb_integrations_lib/models/rita/probe.py +58 -0
- bb_integrations_lib/models/rita/reference_data.py +110 -0
- bb_integrations_lib/models/rita/source_system.py +9 -0
- bb_integrations_lib/models/rita/workers.py +76 -0
- bb_integrations_lib/models/sd/bols_and_drops.py +241 -0
- bb_integrations_lib/models/sd/get_order.py +301 -0
- bb_integrations_lib/models/sd/orders.py +18 -0
- bb_integrations_lib/models/sd_api.py +115 -0
- bb_integrations_lib/pipelines/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/distribution_report/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/distribution_report/order_by_site_product_parser.py +50 -0
- bb_integrations_lib/pipelines/parsers/distribution_report/tank_configs_parser.py +47 -0
- bb_integrations_lib/pipelines/parsers/dtn/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/dtn/dtn_price_parser.py +102 -0
- bb_integrations_lib/pipelines/parsers/dtn/model.py +79 -0
- bb_integrations_lib/pipelines/parsers/price_engine/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/price_engine/parse_accessorials_prices_parser.py +67 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/price_merge_parser.py +111 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/price_sync_parser.py +107 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/shared.py +81 -0
- bb_integrations_lib/pipelines/parsers/tank_reading_parser.py +155 -0
- bb_integrations_lib/pipelines/parsers/tank_sales_parser.py +144 -0
- bb_integrations_lib/pipelines/shared/__init__.py +0 -0
- bb_integrations_lib/pipelines/shared/allocation_matching.py +227 -0
- bb_integrations_lib/pipelines/shared/bol_allocation.py +2793 -0
- bb_integrations_lib/pipelines/steps/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/create_accessorials_step.py +80 -0
- bb_integrations_lib/pipelines/steps/distribution_report/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/distribution_report/distribution_report_datafram_to_raw_data.py +33 -0
- bb_integrations_lib/pipelines/steps/distribution_report/get_model_history_step.py +50 -0
- bb_integrations_lib/pipelines/steps/distribution_report/get_order_by_site_product_step.py +62 -0
- bb_integrations_lib/pipelines/steps/distribution_report/get_tank_configs_step.py +40 -0
- bb_integrations_lib/pipelines/steps/distribution_report/join_distribution_order_dos_step.py +85 -0
- bb_integrations_lib/pipelines/steps/distribution_report/upload_distribution_report_datafram_to_big_query.py +47 -0
- bb_integrations_lib/pipelines/steps/echo_step.py +14 -0
- bb_integrations_lib/pipelines/steps/export_dataframe_to_rawdata_step.py +28 -0
- bb_integrations_lib/pipelines/steps/exporting/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/exporting/bbd_export_payroll_file_step.py +107 -0
- bb_integrations_lib/pipelines/steps/exporting/bbd_export_readings_step.py +236 -0
- bb_integrations_lib/pipelines/steps/exporting/cargas_wholesale_bundle_upload_step.py +33 -0
- bb_integrations_lib/pipelines/steps/exporting/dataframe_flat_file_export.py +29 -0
- bb_integrations_lib/pipelines/steps/exporting/gcs_bucket_export_file_step.py +34 -0
- bb_integrations_lib/pipelines/steps/exporting/keyvu_export_step.py +356 -0
- bb_integrations_lib/pipelines/steps/exporting/pe_price_export_step.py +238 -0
- bb_integrations_lib/pipelines/steps/exporting/platform_science_order_sync_step.py +500 -0
- bb_integrations_lib/pipelines/steps/exporting/save_rawdata_to_disk.py +15 -0
- bb_integrations_lib/pipelines/steps/exporting/sftp_export_file_step.py +60 -0
- bb_integrations_lib/pipelines/steps/exporting/sftp_export_many_files_step.py +23 -0
- bb_integrations_lib/pipelines/steps/exporting/update_exported_orders_table_step.py +64 -0
- bb_integrations_lib/pipelines/steps/filter_step.py +22 -0
- bb_integrations_lib/pipelines/steps/get_latest_sync_date.py +34 -0
- bb_integrations_lib/pipelines/steps/importing/bbd_import_payroll_step.py +30 -0
- bb_integrations_lib/pipelines/steps/importing/get_order_numbers_to_export_step.py +138 -0
- bb_integrations_lib/pipelines/steps/importing/load_file_to_dataframe_step.py +46 -0
- bb_integrations_lib/pipelines/steps/importing/load_imap_attachment_step.py +172 -0
- bb_integrations_lib/pipelines/steps/importing/pe_bulk_sync_price_structure_step.py +68 -0
- bb_integrations_lib/pipelines/steps/importing/pe_price_merge_step.py +86 -0
- bb_integrations_lib/pipelines/steps/importing/sftp_file_config_step.py +124 -0
- bb_integrations_lib/pipelines/steps/importing/test_exact_file_match.py +57 -0
- bb_integrations_lib/pipelines/steps/null_step.py +15 -0
- bb_integrations_lib/pipelines/steps/pe_integration_job_step.py +32 -0
- bb_integrations_lib/pipelines/steps/processing/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/processing/archive_gcs_step.py +76 -0
- bb_integrations_lib/pipelines/steps/processing/archive_sftp_step.py +48 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_format_tank_readings_step.py +492 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_upload_prices_step.py +54 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_upload_tank_sales_step.py +124 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_upload_tankreading_step.py +80 -0
- bb_integrations_lib/pipelines/steps/processing/convert_bbd_order_to_cargas_step.py +226 -0
- bb_integrations_lib/pipelines/steps/processing/delete_sftp_step.py +33 -0
- bb_integrations_lib/pipelines/steps/processing/dtn/__init__.py +2 -0
- bb_integrations_lib/pipelines/steps/processing/dtn/convert_dtn_invoice_to_sd_model.py +145 -0
- bb_integrations_lib/pipelines/steps/processing/dtn/parse_dtn_invoice_step.py +38 -0
- bb_integrations_lib/pipelines/steps/processing/file_config_parser_step.py +720 -0
- bb_integrations_lib/pipelines/steps/processing/file_config_parser_step_v2.py +418 -0
- bb_integrations_lib/pipelines/steps/processing/get_sd_price_price_request.py +105 -0
- bb_integrations_lib/pipelines/steps/processing/keyvu_upload_deliveryplan_step.py +39 -0
- bb_integrations_lib/pipelines/steps/processing/mark_orders_exported_in_bbd_step.py +185 -0
- bb_integrations_lib/pipelines/steps/processing/pe_price_rows_processing_step.py +174 -0
- bb_integrations_lib/pipelines/steps/processing/send_process_report_step.py +47 -0
- bb_integrations_lib/pipelines/steps/processing/sftp_renamer_step.py +61 -0
- bb_integrations_lib/pipelines/steps/processing/tank_reading_touchup_steps.py +75 -0
- bb_integrations_lib/pipelines/steps/processing/upload_supplier_invoice_step.py +16 -0
- bb_integrations_lib/pipelines/steps/send_attached_in_rita_email_step.py +44 -0
- bb_integrations_lib/pipelines/steps/send_rita_email_step.py +34 -0
- bb_integrations_lib/pipelines/steps/sleep_step.py +24 -0
- bb_integrations_lib/pipelines/wrappers/__init__.py +0 -0
- bb_integrations_lib/pipelines/wrappers/accessorials_transformation.py +104 -0
- bb_integrations_lib/pipelines/wrappers/distribution_report.py +191 -0
- bb_integrations_lib/pipelines/wrappers/export_tank_readings.py +237 -0
- bb_integrations_lib/pipelines/wrappers/import_tank_readings.py +192 -0
- bb_integrations_lib/pipelines/wrappers/wrapper.py +81 -0
- bb_integrations_lib/protocols/__init__.py +0 -0
- bb_integrations_lib/protocols/flat_file.py +210 -0
- bb_integrations_lib/protocols/gravitate_client.py +104 -0
- bb_integrations_lib/protocols/pipelines.py +697 -0
- bb_integrations_lib/provider/__init__.py +0 -0
- bb_integrations_lib/provider/api/__init__.py +0 -0
- bb_integrations_lib/provider/api/cargas/__init__.py +0 -0
- bb_integrations_lib/provider/api/cargas/client.py +43 -0
- bb_integrations_lib/provider/api/cargas/model.py +49 -0
- bb_integrations_lib/provider/api/cargas/protocol.py +23 -0
- bb_integrations_lib/provider/api/dtn/__init__.py +0 -0
- bb_integrations_lib/provider/api/dtn/client.py +128 -0
- bb_integrations_lib/provider/api/dtn/protocol.py +9 -0
- bb_integrations_lib/provider/api/keyvu/__init__.py +0 -0
- bb_integrations_lib/provider/api/keyvu/client.py +30 -0
- bb_integrations_lib/provider/api/keyvu/model.py +149 -0
- bb_integrations_lib/provider/api/macropoint/__init__.py +0 -0
- bb_integrations_lib/provider/api/macropoint/client.py +28 -0
- bb_integrations_lib/provider/api/macropoint/model.py +40 -0
- bb_integrations_lib/provider/api/pc_miler/__init__.py +0 -0
- bb_integrations_lib/provider/api/pc_miler/client.py +130 -0
- bb_integrations_lib/provider/api/pc_miler/model.py +6 -0
- bb_integrations_lib/provider/api/pc_miler/web_services_apis.py +131 -0
- bb_integrations_lib/provider/api/platform_science/__init__.py +0 -0
- bb_integrations_lib/provider/api/platform_science/client.py +147 -0
- bb_integrations_lib/provider/api/platform_science/model.py +82 -0
- bb_integrations_lib/provider/api/quicktrip/__init__.py +0 -0
- bb_integrations_lib/provider/api/quicktrip/client.py +52 -0
- bb_integrations_lib/provider/api/telapoint/__init__.py +0 -0
- bb_integrations_lib/provider/api/telapoint/client.py +68 -0
- bb_integrations_lib/provider/api/telapoint/model.py +178 -0
- bb_integrations_lib/provider/api/warren_rogers/__init__.py +0 -0
- bb_integrations_lib/provider/api/warren_rogers/client.py +207 -0
- bb_integrations_lib/provider/aws/__init__.py +0 -0
- bb_integrations_lib/provider/aws/s3/__init__.py +0 -0
- bb_integrations_lib/provider/aws/s3/client.py +126 -0
- bb_integrations_lib/provider/ftp/__init__.py +0 -0
- bb_integrations_lib/provider/ftp/client.py +140 -0
- bb_integrations_lib/provider/ftp/interface.py +273 -0
- bb_integrations_lib/provider/ftp/model.py +76 -0
- bb_integrations_lib/provider/imap/__init__.py +0 -0
- bb_integrations_lib/provider/imap/client.py +228 -0
- bb_integrations_lib/provider/imap/model.py +3 -0
- bb_integrations_lib/provider/sqlserver/__init__.py +0 -0
- bb_integrations_lib/provider/sqlserver/client.py +106 -0
- bb_integrations_lib/secrets/__init__.py +4 -0
- bb_integrations_lib/secrets/adapters.py +98 -0
- bb_integrations_lib/secrets/credential_models.py +222 -0
- bb_integrations_lib/secrets/factory.py +85 -0
- bb_integrations_lib/secrets/providers.py +160 -0
- bb_integrations_lib/shared/__init__.py +0 -0
- bb_integrations_lib/shared/exceptions.py +25 -0
- bb_integrations_lib/shared/model.py +1039 -0
- bb_integrations_lib/shared/shared_enums.py +510 -0
- bb_integrations_lib/storage/README.md +236 -0
- bb_integrations_lib/storage/__init__.py +0 -0
- bb_integrations_lib/storage/aws/__init__.py +0 -0
- bb_integrations_lib/storage/aws/s3.py +8 -0
- bb_integrations_lib/storage/defaults.py +72 -0
- bb_integrations_lib/storage/gcs/__init__.py +0 -0
- bb_integrations_lib/storage/gcs/client.py +8 -0
- bb_integrations_lib/storage/gcsmanager/__init__.py +0 -0
- bb_integrations_lib/storage/gcsmanager/client.py +8 -0
- bb_integrations_lib/storage/setup.py +29 -0
- bb_integrations_lib/util/__init__.py +0 -0
- bb_integrations_lib/util/cache/__init__.py +0 -0
- bb_integrations_lib/util/cache/custom_ttl_cache.py +75 -0
- bb_integrations_lib/util/cache/protocol.py +9 -0
- bb_integrations_lib/util/config/__init__.py +0 -0
- bb_integrations_lib/util/config/manager.py +391 -0
- bb_integrations_lib/util/config/model.py +41 -0
- bb_integrations_lib/util/exception_logger/__init__.py +0 -0
- bb_integrations_lib/util/exception_logger/exception_logger.py +146 -0
- bb_integrations_lib/util/exception_logger/test.py +114 -0
- bb_integrations_lib/util/utils.py +364 -0
- bb_integrations_lib/workers/__init__.py +0 -0
- bb_integrations_lib/workers/groups.py +13 -0
- bb_integrations_lib/workers/rpc_worker.py +50 -0
- bb_integrations_lib/workers/topics.py +20 -0
- bb_integrations_library-3.0.11.dist-info/METADATA +59 -0
- bb_integrations_library-3.0.11.dist-info/RECORD +217 -0
- 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
|