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
File without changes
File without changes
@@ -0,0 +1,43 @@
1
+ import httpx
2
+
3
+ from bb_integrations_lib.provider.api.cargas.model import CreateWholesaleTicketRequest, CreateWholesaleFeeLineRequest, \
4
+ CreateWholesaleLineRequest
5
+
6
+
7
+ class CargasClient(httpx.AsyncClient):
8
+ def __init__(self,
9
+ api_key: str,
10
+ base_url: str = " https://cargase.aeroenergy.com/cargasenergy/API"):
11
+ super().__init__(base_url=base_url)
12
+ self.api_key = api_key
13
+
14
+ @property
15
+ def custom_headers(self) -> dict:
16
+ return {
17
+ 'APIKey': self.api_key,
18
+ 'Content-Type': 'application/json'
19
+ }
20
+
21
+ async def create_wholesale_ticket(self, data: CreateWholesaleTicketRequest) -> dict:
22
+ url = '/CreateWholesaleTicket'
23
+ res = await self.post(
24
+ url=url, headers=self.custom_headers, json=data.model_dump(exclude_none=True, mode="json")
25
+ )
26
+ res.raise_for_status()
27
+ return res.json()
28
+
29
+ async def create_wholesale_line(self, data: CreateWholesaleLineRequest) -> dict:
30
+ url = '/CreateWholesaleLine'
31
+ res = await self.post(
32
+ url=url, headers=self.custom_headers, json=data.model_dump(exclude_none=True, mode="json")
33
+ )
34
+ res.raise_for_status()
35
+ return res.json()
36
+
37
+ async def create_wholesale_fee_line(self, data: CreateWholesaleFeeLineRequest) -> dict:
38
+ url = '/CreateWholesaleFeeLine'
39
+ res = await self.post(
40
+ url=url, headers=self.custom_headers, json=data.model_dump(exclude_none=True, mode="json")
41
+ )
42
+ res.raise_for_status()
43
+ return res.json()
@@ -0,0 +1,49 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class CreateWholesaleFeeLineRequest(BaseModel):
8
+ DocumentID: str
9
+ ItemID: str
10
+ Quantity: Optional[float] = None
11
+ UnitPrice: Optional[float] = None
12
+ UserName: str
13
+
14
+
15
+ class CreateWholesaleLineRequest(BaseModel):
16
+ DocumentID: int
17
+ ItemID: int
18
+ TankID: int
19
+ Quantity: float
20
+ QuantityGross: float
21
+ UnitPrice: Optional[float] = None
22
+ FreightRateID: Optional[float] = None
23
+ VendorLocationID: Optional[float] = None
24
+ FreightAmount: Optional[float] = None
25
+ SurchargeAmount: Optional[float] = None
26
+ UnitCostOverride: Optional[float] = None
27
+ CustomerPricingID: Optional[str] = None
28
+ UserName: str
29
+
30
+
31
+ class CreateWholesaleTicketRequest(BaseModel):
32
+ CustomerID: int
33
+ DeliveryDate: datetime
34
+ CustomerPONumber: Optional[str] = None
35
+ DriverID: Optional[int] = None
36
+ WholesaleTruckID: Optional[int] = None
37
+ Message: Optional[str] = None
38
+ InvoiceNotes: Optional[str] = None
39
+ CostCenterID: Optional[int] = None
40
+ SubTypeID: Optional[int] = None
41
+ SalespersonID: Optional[int] = None
42
+ AdditionalNotes: Optional[str] = None
43
+ UserName: str
44
+
45
+
46
+ class CreateWholesaleTicketRequestBundle(BaseModel):
47
+ ticket_request: CreateWholesaleTicketRequest
48
+ line_requests: list[CreateWholesaleLineRequest] = []
49
+ fee_line_requests: list[CreateWholesaleLineRequest] = []
@@ -0,0 +1,23 @@
1
+ from bb_integrations_lib.provider.api.cargas.model import CreateWholesaleTicketRequest, CreateWholesaleLineRequest, \
2
+ CreateWholesaleFeeLineRequest
3
+
4
+
5
+ class CargasClient():
6
+ def __init__(self,
7
+ api_key: str,
8
+ base_url: str = " https://cargase.aeroenergy.com/cargasenergy/API"):
9
+ self.base_url = base_url
10
+ self.api_key = api_key
11
+
12
+ @property
13
+ def custom_headers(self) -> dict:
14
+ return {
15
+ 'APIKey': self.api_key,
16
+ 'Content-Type': 'application/json'
17
+ }
18
+
19
+ async def create_wholesale_ticket(self, data: CreateWholesaleTicketRequest): ...
20
+
21
+ async def create_wholesale_line(self, data: CreateWholesaleLineRequest): ...
22
+
23
+ async def create_wholesale_fee_line(self, data: CreateWholesaleFeeLineRequest): ...
File without changes
@@ -0,0 +1,128 @@
1
+ import asyncio
2
+ from contextlib import contextmanager
3
+ from typing import Dict, List
4
+ import httpx
5
+ import pandas as pd
6
+
7
+ class DTNClient(httpx.AsyncClient):
8
+ def __init__(
9
+ self, username: str,
10
+ web_service_key: str,
11
+ api_key: str,
12
+ base_url: str,
13
+ timeout: float = 180.0,
14
+ ):
15
+ super().__init__(base_url=base_url)
16
+ self.username = username
17
+ self.web_service_key = web_service_key
18
+ self.api_key = api_key
19
+ self.base_url = base_url.rstrip("/")
20
+ self.client = httpx.AsyncClient(timeout=180)
21
+ self.timeout = httpx.Timeout(timeout, connect=60.0)
22
+
23
+ def __repr__(self):
24
+ return """DTN API Client designed for DTN Allocation Tracker"""
25
+
26
+ @property
27
+ def custom_headers(self) -> Dict[str, str]:
28
+ return {
29
+ 'username': self.username,
30
+ 'Accept': 'application/vnd.dtn.energy.v1+JSON',
31
+ 'webservicekey': self.web_service_key,
32
+ 'apikey': self.api_key,
33
+ }
34
+
35
+ async def get_allocations(self) -> List[Dict]:
36
+ url = '/allocations'
37
+ res = await self.get(url=url, headers=self.custom_headers, timeout=self.timeout)
38
+ return res.json()
39
+
40
+ async def get_data_in_group(self, url: str) -> List[Dict]:
41
+ if url.startswith(self.base_url):
42
+ url = url.removeprefix(self.base_url)
43
+ if not url.startswith('/'):
44
+ url = f'/{url}'
45
+ res = await self.get(
46
+ url=url,
47
+ headers=self.custom_headers,
48
+ timeout=self.timeout
49
+ )
50
+ return res.json()
51
+
52
+ async def get_terminal(self, href) -> Dict:
53
+ res = await self.get(url=href, headers=self.custom_headers, timeout=self.timeout)
54
+ return res.json()
55
+
56
+
57
+
58
+ @contextmanager
59
+ def init_dtn_api(username=None, web_service_key=None, api_key=None) -> DTNClient:
60
+ dtn_client = DTNClient(
61
+ username=username,
62
+ web_service_key=web_service_key,
63
+ api_key=api_key,
64
+ base_url='https://api.dtn.com/fuelsuite/allocationtracker/'
65
+ )
66
+ yield dtn_client
67
+
68
+
69
+ def parse_allocation_data(allocation_data):
70
+ ret = []
71
+ for allocation in allocation_data['data']:
72
+ supplier = allocation.get('supplier', {})
73
+ location_data = allocation.get('location', {})
74
+ customer_data = allocation.get('customer', {})
75
+ consignee_group_data = customer_data.get('consigneeGroup', {})
76
+ product_group_data = allocation.get('productAllocationList', [])
77
+ terminal_data = location_data.get('terminal', {}) or {}
78
+ location_group_data = location_data.get('terminalGroup', {}) or {}
79
+ sold_to_data = consignee_group_data.get('soldTo', {}) or {}
80
+ supplier_name = supplier.get('name')
81
+ supplier_code = supplier.get('sellerNum')
82
+ terminal_name = location_group_data.get('name') or terminal_data.get('name') or None
83
+ terminal_code = location_group_data.get('id') or terminal_data.get('id') or None
84
+ terminal_hfref = location_group_data.get('terminalListLink', {}).get('href')
85
+ consignee_name = consignee_group_data.get('name')
86
+ consignee_id = consignee_group_data.get('id')
87
+ sold_to_id = sold_to_data.get('id')
88
+ sold_to_name = sold_to_data.get('name')
89
+ for group in product_group_data:
90
+ product_group_name = group.get('name')
91
+ uom = group.get('unitOfMeasure')
92
+ allocation_remaining_monthly = group.get('allocationRemainingAmountMonthly')
93
+ lifted_amount_monthly = group.get('liftedAmountMonthly')
94
+ refresh_amount_monthly = group.get('refreshAmountMonthly')
95
+ refresh_date_monthly = group.get('refreshDateMonthly')
96
+ scaled_start_amount_monthly = group.get('scaledStartAmountMonthly')
97
+ start_amount_monthly = group.get('startAmountMonthly')
98
+ row = {
99
+ 'supplier_name': supplier_name,
100
+ 'supplier_code': supplier_code,
101
+ 'terminal_name': terminal_name,
102
+ 'terminal_code': terminal_code,
103
+ 'terminal_hfref': terminal_hfref,
104
+ 'consignee_name': consignee_name,
105
+ 'consignee_id': consignee_id,
106
+ 'sold_to_name': sold_to_name,
107
+ 'sold_to_id': sold_to_id,
108
+ 'product_group_name': product_group_name,
109
+ 'uom': uom,
110
+ 'allocation_remaining_monthly': allocation_remaining_monthly,
111
+ 'lifted_amount_monthly': lifted_amount_monthly,
112
+ 'refresh_amount_monthly': refresh_amount_monthly,
113
+ 'refresh_date_monthly': refresh_date_monthly,
114
+ 'scaled_start_amount_monthly': scaled_start_amount_monthly,
115
+ 'start_amount_monthly': start_amount_monthly,
116
+ }
117
+ ret.append(row)
118
+ return pd.DataFrame(ret)
119
+
120
+
121
+ if __name__ == "__main__":
122
+ async def get_allocation_data():
123
+ with init_dtn_api(username="", web_service_key="", api_key="") as dtn_client:
124
+ allocations = await dtn_client.get_allocations()
125
+ print(allocations)
126
+
127
+
128
+ asyncio.run(get_allocation_data())
@@ -0,0 +1,9 @@
1
+ from typing import Optional, Dict, Protocol, List
2
+
3
+
4
+ class DTNClient(Protocol):
5
+ async def get_allocations(self) -> List[Dict]:
6
+ """Get all allocation data"""
7
+
8
+ async def get_data_in_group(self, url: str) -> List[Dict]:
9
+ """Get data in group href"""
File without changes
@@ -0,0 +1,30 @@
1
+ import httpx
2
+ from httpx import Request, Response
3
+
4
+ from bb_integrations_lib.provider.api.keyvu.model import KeyVuDeliveryPlan, default_serialization_options
5
+
6
+
7
+ class KeyVuAuth(httpx.Auth):
8
+ def __init__(self, token):
9
+ self.token = token
10
+
11
+ def auth_flow(self, request: Request):
12
+ request.headers["Keyvu-Api-Key"] = self.token
13
+ yield request
14
+
15
+
16
+ class KeyVuClient:
17
+ def __init__(self, api_key: str):
18
+ self.base_url = ""
19
+ self.api_key = api_key
20
+ self.client = httpx.AsyncClient(auth=KeyVuAuth(self.api_key))
21
+
22
+ def build_url(self, tail: str) -> str:
23
+ return f"{self.base_url}/{tail}"
24
+
25
+ async def upload_deliveryplan(self, data: KeyVuDeliveryPlan, override_filename: str | None = None) -> Response:
26
+ return await self.client.post(
27
+ url=self.build_url("upload/deliveryplan"),
28
+ content=data.to_xml(**default_serialization_options),
29
+ headers={"filename": override_filename} if override_filename else {}
30
+ )
@@ -0,0 +1,149 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from typing import Optional
4
+
5
+ # from pydantic import BaseModel
6
+ from pydantic_xml import BaseXmlModel, element
7
+
8
+ default_serialization_options = {
9
+ "skip_empty": False, "exclude_none": False, "exclude_unset": True
10
+ }
11
+ """To be passed to pydantic_xml.BaseXmlModel.to_xml when sending to the KeyVu API"""
12
+
13
+
14
+ class DeliveryStatus(str, Enum):
15
+ planned = "Planned"
16
+ on_route_not_loaded = "OnRouteNotLoaded"
17
+ loading = "Loading"
18
+ on_route_loaded = "OnRouteLoaded"
19
+ unloading = "Unloading"
20
+ delivered = "Delivered"
21
+ confirmed = "Confirmed"
22
+ canceled = "Canceled"
23
+
24
+
25
+ class UnitSystem(str, Enum):
26
+ metric = "Metric"
27
+ imperial = "Imperial"
28
+
29
+
30
+ class KeyVuDeliveryPlan(BaseXmlModel, tag="Loads", nsmap={"": "http://keyvu.com/schemas/carrierloads/v6",
31
+ "xsi": "http://www.w3.org/2001/XMLSchema-instance",
32
+ "xsd": "http://www.w3.org/2001/XMLSchema"}):
33
+ start_date: datetime = element(tag="StartDate")
34
+ end_date: datetime = element(tag="EndDate")
35
+ export_date: datetime = element(tag="ExportDate")
36
+ deliveries: Optional[list["Delivery"]] = element(tag="Delivery", default=None, nillable=True)
37
+
38
+ class GeoLocation(BaseXmlModel):
39
+ longitude: Optional[float] = element(tag="Longitude", nillable=True, default=None)
40
+ latitude: Optional[float] = element(tag="Latitude", nillable=True, default=None)
41
+ heading: Optional[float] = element(tag="Heading", nillable=True, default=None)
42
+ last_updated: Optional[datetime] = element(tag="LastUpdated", nillable=True, default=None)
43
+
44
+
45
+ class Delivery(BaseXmlModel):
46
+ id: str = element(tag="Id")
47
+ carrier_name: Optional[str] = element(tag="CarrierName", default=None)
48
+ scac: Optional[str] = element(tag="Scac", default=None)
49
+ station_deliveries_array: "StationDeliveryArray" = element(tag="StationDeliveries", default=None)
50
+ trailer: Optional[str] = element(tag="Trailer", default=None)
51
+ last_updated: datetime = element(tag="LastUpdated")
52
+ geo_location: "GeoLocation" = element(tag="GeoLocation", default_factory=lambda: GeoLocation(longitude=None, latitude=None, heading=None, last_updated=None))
53
+ unit: str = element(tag="Unit")
54
+
55
+ def __init__(self, station_deliveries: list["StationDelivery"], **kwargs):
56
+ super().__init__(**kwargs)
57
+ self.station_deliveries = station_deliveries
58
+
59
+ @property
60
+ def station_deliveries(self) -> list["StationDelivery"] | None:
61
+ return self.station_deliveries_array.station_deliveries
62
+
63
+ @station_deliveries.setter
64
+ def station_deliveries(self, new_deliveries: list["StationDelivery"]) -> None:
65
+ self.station_deliveries_array = StationDeliveryArray(station_deliveries=new_deliveries)
66
+
67
+
68
+ class StationDeliveryArray(BaseXmlModel):
69
+ station_deliveries: Optional[list["StationDelivery"]] = element(tag="StationDelivery", default=None)
70
+
71
+
72
+ class StationDelivery(BaseXmlModel):
73
+ delivery_status: DeliveryStatus = element(tag="DeliveryStatus")
74
+ site_id: Optional[str] = element(tag="SiteId", default=None)
75
+ details_array: Optional["StationDeliveryDetailsArray"] = element(tag="Details", default=None)
76
+ bill_of_ladings_array: Optional["StationDeliveryBOLArray"] = element(tag="BillOfLadings", default=None)
77
+ tractor: Optional[str] = element(tag="Tractor", default=None)
78
+ delivery_date: datetime = element(tag="DeliveryDate")
79
+ carrier_delivery_status: Optional[str] = element(tag="CarrierDeliveryStatus", default=None)
80
+
81
+ def __init__(self, details: list["StationDeliveryDetails"] | None = None,
82
+ bill_of_ladings: list["StationDeliveryBOL"] | None = None, **kwargs):
83
+ super().__init__(**kwargs)
84
+ self.details = details
85
+ self.bill_of_ladings = bill_of_ladings
86
+
87
+ @property
88
+ def details(self) -> list["StationDeliveryDetails"] | None:
89
+ if not self.details_array:
90
+ return None
91
+ return self.details_array.details
92
+
93
+ @details.setter
94
+ def details(self, new_details: list["StationDeliveryDetails"]) -> None:
95
+ self.details_array = StationDeliveryDetailsArray(details=new_details)
96
+
97
+ @property
98
+ def bill_of_ladings(self) -> list["StationDeliveryBOL"] | None:
99
+ if not self.bill_of_ladings_array:
100
+ return None
101
+ return self.bill_of_ladings_array.bill_of_ladings
102
+
103
+ @bill_of_ladings.setter
104
+ def bill_of_ladings(self, new_bols: list["StationDeliveryBOL"]) -> None:
105
+ self.bill_of_ladings_array = StationDeliveryBOLArray(bill_of_ladings=new_bols)
106
+
107
+
108
+ class StationDeliveryDetailsArray(BaseXmlModel):
109
+ details: Optional[list["StationDeliveryDetails"]] = element(tag="StationDeliveryDetails", default=None)
110
+
111
+
112
+ class StationDeliveryDetails(BaseXmlModel, tag="StationDeliveryDetails", skip_empty=True):
113
+ delivered_volume: Optional[float] = element(tag="DeliveredVolume", nillable=True)
114
+ volume_unit: UnitSystem = element(tag="VolumeUnit")
115
+ tank_sequence: Optional[int] = element(tag="TankSequence", default=None)
116
+ product_id: Optional[str] = element(tag="ProductId", default=None)
117
+ product: Optional[str] = element(tag="Product", default=None)
118
+ is_retained: bool = element(tag="IsRetained")
119
+
120
+ @staticmethod
121
+ def from_v1_order_dict(details: dict) -> "StationDeliveryDetails":
122
+ # Prefer using tank sequence, but fall back to product ID based matching. This is the behavior KeyVu prefers.
123
+ tank_sequence = details.get("tank_id")
124
+ product_id = None
125
+ product = None
126
+ if not tank_sequence:
127
+ product_id = details.get("product_source_id")
128
+ product = details.get("product_name")
129
+
130
+ return StationDeliveryDetails(
131
+ delivered_volume=details.get("quantity", 0.0),
132
+ volume_unit=UnitSystem.imperial,
133
+ tank_sequence=tank_sequence,
134
+ product_id=product_id,
135
+ product=product,
136
+ is_retained=False # TODO: Support retains
137
+ )
138
+
139
+
140
+ class StationDeliveryBOLArray(BaseXmlModel):
141
+ bill_of_ladings: Optional[list["StationDeliveryBOL"]] = element(tag="StationDeliveryBillOfLadings")
142
+
143
+
144
+ class StationDeliveryBOL(BaseXmlModel):
145
+ terminal_name: Optional[str] = element(tag="TerminalName", nillable=True)
146
+ terminal_control_number: str = element(tag="TerminalControlNumber")
147
+ bill_of_lading_number: str = element(tag="BillOfLadingNumber")
148
+ supplier: str = element(tag="Supplier")
149
+ consignee: str = element(tag="Consignee", default="")
@@ -0,0 +1,28 @@
1
+ import base64
2
+ from functools import lru_cache
3
+ import httpx
4
+ from httpx import Response
5
+ from bb_integrations_lib.provider.api.macropoint.model import LocationUpdateRequest
6
+
7
+
8
+ class MacropointClient(httpx.AsyncClient):
9
+ def __init__(self, username, password):
10
+ super().__init__()
11
+ self.username = username
12
+ self.password = password
13
+ self._headers = {}
14
+
15
+ @lru_cache(maxsize=1)
16
+ async def _get_token(self):
17
+ encoded = base64.standard_b64encode(f"{self.username}:{self.password}".encode()).decode()
18
+ return f"Basic {encoded}"
19
+
20
+ async def update_location(self, req: LocationUpdateRequest) -> Response:
21
+ return await self.post(
22
+ url="https://macropoint-lite.com/api/1.0/tms/data/location",
23
+ headers={
24
+ "Content-Type": "application/xml",
25
+ "Authorization": await self._get_token(),
26
+ },
27
+ data=req.to_xml()
28
+ )
@@ -0,0 +1,40 @@
1
+ from pydantic_xml import BaseXmlModel, element
2
+
3
+
4
+ class Sender(
5
+ BaseXmlModel,
6
+ ):
7
+ load_id: str = element("LoadID")
8
+
9
+ class Requestor(
10
+ BaseXmlModel,
11
+ ):
12
+ mpid: str = element("MPID")
13
+ load_id: str = element("LoadID")
14
+
15
+ class AllowAccessFrom(
16
+ BaseXmlModel,
17
+ ):
18
+ mpid: str = element("MPID")
19
+
20
+ class Coordinates(
21
+ BaseXmlModel,
22
+ ):
23
+ latitude: float = element("Latitude")
24
+ longitude: float = element("Longitude")
25
+
26
+ class Location(
27
+ BaseXmlModel,
28
+ ):
29
+ coordinates: Coordinates = element("Coordinates")
30
+ created_date_time: str = element("CreatedDateTime")
31
+
32
+ class LocationUpdateRequest(
33
+ BaseXmlModel,
34
+ tag="TMSLocationData",
35
+ nsmap={"": 'http://macropoint-lite.com/xml/1.0'}
36
+ ):
37
+ sender: Sender = element("Sender")
38
+ requestor: Requestor = element("Requestor")
39
+ allow_access_from: AllowAccessFrom = element("AllowAccessFrom")
40
+ location: Location = element("Location")
File without changes
@@ -0,0 +1,130 @@
1
+ import asyncio
2
+
3
+ import httpx
4
+ from loguru import logger
5
+ from pydantic import ValidationError
6
+ from bb_integrations_lib.provider.api.pc_miler.model import TokenData
7
+
8
+ route_reports_eagle_standard_params = {
9
+ "ReportRoutes": [
10
+ {
11
+ "ReportingOptions": {
12
+ "UseTollData": True,
13
+ "TollDiscount": "All",
14
+ "IncludeFerryDistance": True,
15
+ "EstimatedTimeOptions": {
16
+ "ETAETD": 1,
17
+ "DateOption": 2,
18
+ "DateAndTime": {
19
+ "DayOfWeek": 4,
20
+ "TimeOfDay": "10:00 AM",
21
+ "TimeZone": 0
22
+ }
23
+ },
24
+ "UseTraffic": True
25
+ },
26
+ "ReportTypes": [
27
+ {
28
+ "__type": "StateReportType:http://pcmiler.alk.com/APIs/v1.0",
29
+ "SortByRoute": False
30
+ },
31
+ {
32
+ "__type": "GeoTunnelReportType:http://pcmiler.alk.com/APIs/v1.0",
33
+ "CiteInterval": 5.0
34
+ }
35
+ ],
36
+ "Stops": [
37
+ {
38
+ "Coords": {
39
+ "Lat": "38.36440700",
40
+ "Lon": "-82.60173800"
41
+ }
42
+ },
43
+ {
44
+ "Coords": {
45
+ "Lat": "38.08867300",
46
+ "Lon": "-81.83779700"
47
+ }
48
+ }
49
+ ],
50
+ "Options": {
51
+ "VehicleType": 0,
52
+ "RoutingType": 0,
53
+ "HighwayOnly": False,
54
+ "DistanceUnits": 0,
55
+ "TollRoads": 3,
56
+ "BordersOpen": True,
57
+ "OverrideRestrict": False,
58
+ "HazMatType": 4,
59
+ "RouteOptimization": 0,
60
+ "hubRouting": False,
61
+ "SideOfStreetAdherence": 0,
62
+ "ferryDiscourage": True,
63
+ "AFSetIDs": [-1],
64
+ "UseSites": True
65
+ }
66
+ }
67
+ ]
68
+ }
69
+
70
+
71
+ class PCMilerClient(httpx.AsyncClient):
72
+ def __init__(
73
+ self,
74
+ base_url: str,
75
+ username: str,
76
+ password: str,
77
+ api_key: str,
78
+ timeout: float = 180.0,
79
+ ):
80
+ super().__init__(base_url=base_url)
81
+ self.username = username
82
+ self.password = password
83
+ self.api_key = api_key
84
+ self.apis = PCMilerAPIWrapper(client=self)
85
+ self.timeout = httpx.Timeout(timeout, connect=60.0)
86
+
87
+ def __repr__(self):
88
+ return """PC Miler Client designed for PC Miler Web Service"""
89
+
90
+ async def get_token(self, url: str) -> TokenData:
91
+ try:
92
+ json_data = {'apiKey': self.api_key}
93
+ response = await self.post(url, json=json_data)
94
+ response.raise_for_status()
95
+ response_data = response.json()
96
+ return TokenData.model_validate(response_data)
97
+
98
+ except httpx.ReadTimeout as e:
99
+ logger.error(f"Request timed out while getting token: {e}")
100
+ raise
101
+
102
+ except httpx.HTTPStatusError as e:
103
+ status_code = e.response.status_code
104
+ logger.error(f"HTTP error {status_code} while getting token: {e.response.text}")
105
+ raise
106
+
107
+ except httpx.HTTPError as e:
108
+ logger.error(f"HTTP error occurred while getting token: {e}")
109
+ raise
110
+
111
+ except ValidationError as e:
112
+ logger.error(f"Invalid token data received: {e}")
113
+ raise
114
+
115
+ except Exception as e:
116
+ logger.error(f"Unexpected error while getting token: {e}")
117
+ raise
118
+
119
+
120
+ class PCMilerAPIWrapper:
121
+ def __init__(self, client: PCMilerClient):
122
+ from bb_integrations_lib.provider.api.pc_miler.web_services_apis import RoutingAPI
123
+ from bb_integrations_lib.provider.api.pc_miler.web_services_apis import SingleSearchAPI
124
+ from bb_integrations_lib.provider.api.pc_miler.web_services_apis import PlacesAPI
125
+
126
+ self.routing = RoutingAPI(client=client)
127
+ self.single_search = SingleSearchAPI(client=client)
128
+ self.places = PlacesAPI(client=client)
129
+
130
+
@@ -0,0 +1,6 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class TokenData(BaseModel):
5
+ token: str
6
+ expires: str