karrio-hermes 2026.1.3__py3-none-any.whl → 2026.1.5__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.
@@ -20,12 +20,11 @@ def _match_status(code: str) -> typing.Optional[str]:
20
20
 
21
21
 
22
22
  def _match_reason(code: str) -> typing.Optional[str]:
23
- """Match Hermes event code against TrackingIncidentReason enum values."""
24
- if not code:
25
- return None
26
- for reason in list(provider_units.TrackingIncidentReason):
27
- if code in reason.value:
28
- return reason.name
23
+ """Match Hermes event code against incident reasons.
24
+
25
+ Hermes uses numeric codes that map to statuses, not specific incident reasons.
26
+ Returns None as Hermes does not provide granular incident reason codes.
27
+ """
29
28
  return None
30
29
 
31
30
 
@@ -64,48 +64,198 @@ class ShippingService(lib.StrEnum):
64
64
  class ShippingOption(lib.Enum):
65
65
  """Carrier specific options."""
66
66
 
67
- # Hermes services as options
68
- hermes_tan_service = lib.OptionEnum("tanService", bool)
69
- hermes_limited_quantities = lib.OptionEnum("limitedQuantitiesService", bool, meta=dict(category="DANGEROUS_GOOD"))
70
- hermes_bulk_goods = lib.OptionEnum("bulkGoodService", bool)
71
- hermes_household_signature = lib.OptionEnum("householdSignatureService", bool, meta=dict(category="SIGNATURE"))
72
- hermes_compact_parcel = lib.OptionEnum("compactParcelService", bool)
73
- hermes_next_day = lib.OptionEnum("nextDayService", bool, meta=dict(category="DELIVERY_OPTIONS"))
74
- hermes_signature = lib.OptionEnum("signatureService", bool, meta=dict(category="SIGNATURE"))
75
- hermes_redirection_prohibited = lib.OptionEnum("redirectionProhibitedService", bool, meta=dict(category="DELIVERY_OPTIONS"))
76
- hermes_exclude_parcel_shop_auth = lib.OptionEnum("excludeParcelShopAuthorization", bool, meta=dict(category="PUDO"))
77
- hermes_late_injection = lib.OptionEnum("lateInjectionService", bool)
78
-
79
- # Cash on delivery
80
- hermes_cod_amount = lib.OptionEnum("codAmount", float, meta=dict(category="COD"))
81
- hermes_cod_currency = lib.OptionEnum("codCurrency", str, meta=dict(category="COD"))
82
-
83
- # Customer alert service
84
- hermes_notification_email = lib.OptionEnum("notificationEmail", str, meta=dict(category="NOTIFICATION"))
85
- hermes_notification_type = lib.OptionEnum("notificationType", str, meta=dict(category="NOTIFICATION")) # EMAIL, SMS, EMAIL_SMS
86
-
87
- # Stated day service
88
- hermes_stated_day = lib.OptionEnum("statedDay", str, meta=dict(category="DELIVERY_OPTIONS")) # YYYY-MM-DD format
89
-
90
- # Stated time service
91
- hermes_time_slot = lib.OptionEnum("timeSlot", str, meta=dict(category="DELIVERY_OPTIONS")) # FORENOON, NOON, AFTERNOON, EVENING
92
-
93
- # Ident service
94
- hermes_ident_id = lib.OptionEnum("identID", str, meta=dict(category="SIGNATURE"))
95
- hermes_ident_type = lib.OptionEnum("identType", str, meta=dict(category="SIGNATURE")) # GERMAN_IDENTITY_CARD, etc.
96
- hermes_ident_fsk = lib.OptionEnum("identVerifyFsk", str, meta=dict(category="SIGNATURE")) # 18
97
- hermes_ident_birthday = lib.OptionEnum("identVerifyBirthday", str, meta=dict(category="SIGNATURE")) # YYYY-MM-DD
98
-
99
- # Parcel shop delivery
100
- hermes_parcel_shop_id = lib.OptionEnum("psID", str, meta=dict(category="PUDO"))
101
- hermes_parcel_shop_selection_rule = lib.OptionEnum("psSelectionRule", str, meta=dict(category="PUDO")) # SELECT_BY_ID, SELECT_BY_RECEIVER_ADDRESS
102
- hermes_parcel_shop_customer_firstname = lib.OptionEnum("psCustomerFirstName", str, meta=dict(category="PUDO"))
103
- hermes_parcel_shop_customer_lastname = lib.OptionEnum("psCustomerLastName", str, meta=dict(category="PUDO"))
104
-
105
- # Multipart service
106
- hermes_part_number = lib.OptionEnum("partNumber", int)
107
- hermes_number_of_parts = lib.OptionEnum("numberOfParts", int)
108
- hermes_parent_shipment_order_id = lib.OptionEnum("parentShipmentOrderID", str)
67
+ # Delivery Options (Zustelloptionen tab)
68
+ hermes_next_day = lib.OptionEnum(
69
+ "nextDayService", bool,
70
+ help="Enable next-day delivery service",
71
+ meta=dict(category="DELIVERY_OPTIONS", configurable=True)
72
+ )
73
+ hermes_bulk_goods = lib.OptionEnum(
74
+ "bulkGoodService", bool,
75
+ help="Mark shipment as bulky goods (Sperrgut)",
76
+ meta=dict(category="DELIVERY_OPTIONS", configurable=True)
77
+ )
78
+ hermes_compact_parcel = lib.OptionEnum(
79
+ "compactParcelService", bool,
80
+ help="Enable compact parcel service",
81
+ meta=dict(category="DELIVERY_OPTIONS", configurable=True)
82
+ )
83
+ hermes_redirection_prohibited = lib.OptionEnum(
84
+ "redirectionProhibitedService", bool,
85
+ help="Do not allow redirection to neighbor",
86
+ meta=dict(category="DELIVERY_OPTIONS", configurable=True)
87
+ )
88
+ hermes_stated_day = lib.OptionEnum(
89
+ "statedDay", str,
90
+ help="Specific delivery date (YYYY-MM-DD format)",
91
+ meta=dict(category="DELIVERY_OPTIONS", configurable=True)
92
+ )
93
+ hermes_time_slot = lib.OptionEnum(
94
+ "timeSlot", str,
95
+ help="Delivery time slot (FORENOON, NOON, AFTERNOON, EVENING)",
96
+ meta=dict(category="DELIVERY_OPTIONS", configurable=True)
97
+ )
98
+ hermes_express = lib.OptionEnum(
99
+ "expressService", bool,
100
+ help="Enable express delivery service",
101
+ meta=dict(category="DELIVERY_OPTIONS", configurable=True)
102
+ )
103
+ hermes_after_hours_delivery = lib.OptionEnum(
104
+ "afterHoursDeliveryService", bool,
105
+ help="Enable after-hours delivery (Feierabendservice)",
106
+ meta=dict(category="DELIVERY_OPTIONS", configurable=True)
107
+ )
108
+ hermes_parcel_class = lib.OptionEnum(
109
+ "parcelClass", str,
110
+ help="Parcel size class (XS, S, M, L, XL)",
111
+ meta=dict(category="DELIVERY_OPTIONS", configurable=True)
112
+ )
113
+
114
+ # Signature Options
115
+ hermes_signature = lib.OptionEnum(
116
+ "signatureService", bool,
117
+ help="Require signature upon delivery",
118
+ meta=dict(category="SIGNATURE", configurable=True)
119
+ )
120
+ hermes_household_signature = lib.OptionEnum(
121
+ "householdSignatureService", bool,
122
+ help="Require household member signature",
123
+ meta=dict(category="SIGNATURE", configurable=True)
124
+ )
125
+ hermes_ident_id = lib.OptionEnum(
126
+ "identID", str,
127
+ help="ID number for identity verification",
128
+ meta=dict(category="SIGNATURE", configurable=True)
129
+ )
130
+ hermes_ident_type = lib.OptionEnum(
131
+ "identType", str,
132
+ help="Type of ID for verification (e.g., GERMAN_IDENTITY_CARD)",
133
+ meta=dict(category="SIGNATURE", configurable=True)
134
+ )
135
+ hermes_ident_fsk = lib.OptionEnum(
136
+ "identVerifyFsk", str,
137
+ help="Minimum age verification (e.g., 18)",
138
+ meta=dict(category="SIGNATURE", configurable=True)
139
+ )
140
+ hermes_ident_birthday = lib.OptionEnum(
141
+ "identVerifyBirthday", str,
142
+ help="Verify recipient birthday (YYYY-MM-DD)",
143
+ meta=dict(category="SIGNATURE", configurable=True)
144
+ )
145
+
146
+ # PUDO Options (Parcel Shop)
147
+ hermes_parcel_shop_id = lib.OptionEnum(
148
+ "psID", str,
149
+ help="Hermes ParcelShop ID for delivery",
150
+ meta=dict(category="PUDO", configurable=True)
151
+ )
152
+ hermes_parcel_shop_selection_rule = lib.OptionEnum(
153
+ "psSelectionRule", str,
154
+ help="ParcelShop selection rule (SELECT_BY_ID, SELECT_BY_RECEIVER_ADDRESS)",
155
+ meta=dict(category="PUDO", configurable=True)
156
+ )
157
+ hermes_parcel_shop_customer_firstname = lib.OptionEnum(
158
+ "psCustomerFirstName", str,
159
+ help="Customer first name for ParcelShop pickup",
160
+ meta=dict(category="PUDO", configurable=True)
161
+ )
162
+ hermes_parcel_shop_customer_lastname = lib.OptionEnum(
163
+ "psCustomerLastName", str,
164
+ help="Customer last name for ParcelShop pickup",
165
+ meta=dict(category="PUDO", configurable=True)
166
+ )
167
+ hermes_exclude_parcel_shop_auth = lib.OptionEnum(
168
+ "excludeParcelShopAuthorization", bool,
169
+ help="Exclude ParcelShop delivery authorization",
170
+ meta=dict(category="PUDO", configurable=True)
171
+ )
172
+
173
+ # Notification Options
174
+ hermes_notification_email = lib.OptionEnum(
175
+ "notificationEmail", str,
176
+ help="Email for delivery notifications",
177
+ meta=dict(category="NOTIFICATION", configurable=True)
178
+ )
179
+ hermes_notification_type = lib.OptionEnum(
180
+ "notificationType", str,
181
+ help="Notification type (EMAIL, SMS, EMAIL_SMS)",
182
+ meta=dict(category="NOTIFICATION", configurable=True)
183
+ )
184
+
185
+ # COD Options (Cash on Delivery)
186
+ hermes_cod_amount = lib.OptionEnum(
187
+ "codAmount", float,
188
+ help="Cash on delivery amount",
189
+ meta=dict(category="COD", configurable=True)
190
+ )
191
+ hermes_cod_currency = lib.OptionEnum(
192
+ "codCurrency", str,
193
+ help="Currency for COD amount",
194
+ meta=dict(category="COD", configurable=True)
195
+ )
196
+ hermes_cod_distribution = lib.OptionEnum(
197
+ "codDistribution", str,
198
+ help="COD distribution method (e.g., transfer, check)",
199
+ meta=dict(category="COD", configurable=True)
200
+ )
201
+
202
+ # Dangerous Goods
203
+ hermes_limited_quantities = lib.OptionEnum(
204
+ "limitedQuantitiesService", bool,
205
+ help="Mark shipment as containing limited quantity hazardous materials",
206
+ meta=dict(category="DANGEROUS_GOOD", configurable=True)
207
+ )
208
+
209
+ # Return Options
210
+ hermes_return_enabled = lib.OptionEnum(
211
+ "returnService", bool,
212
+ help="Enable return label for this shipment",
213
+ meta=dict(category="RETURN", configurable=True)
214
+ )
215
+ hermes_include_return_label = lib.OptionEnum(
216
+ "includeReturnLabel", bool,
217
+ help="Include a pre-printed return label inside the package",
218
+ meta=dict(category="RETURN", configurable=True)
219
+ )
220
+ hermes_digital_sales_return = lib.OptionEnum(
221
+ "digitalSalesReturn", bool,
222
+ help="Enable digital sales return (digitale Verkaufsretoure)",
223
+ meta=dict(category="RETURN", configurable=True)
224
+ )
225
+
226
+ # Reference/Instructions Options
227
+ hermes_customer_reference_1 = lib.OptionEnum(
228
+ "customerReference1", str,
229
+ help="Customer reference field 1 (Kundenreferenz 1)",
230
+ meta=dict(category="INSTRUCTIONS", configurable=True)
231
+ )
232
+ hermes_customer_reference_2 = lib.OptionEnum(
233
+ "customerReference2", str,
234
+ help="Customer reference field 2 (Kundenreferenz 2)",
235
+ meta=dict(category="INSTRUCTIONS", configurable=True)
236
+ )
237
+
238
+ # Internal/Multipart Options (not configurable in shipping method editor)
239
+ hermes_tan_service = lib.OptionEnum(
240
+ "tanService", bool,
241
+ meta=dict(configurable=False)
242
+ )
243
+ hermes_late_injection = lib.OptionEnum(
244
+ "lateInjectionService", bool,
245
+ meta=dict(configurable=False)
246
+ )
247
+ hermes_part_number = lib.OptionEnum(
248
+ "partNumber", int,
249
+ meta=dict(configurable=False)
250
+ )
251
+ hermes_number_of_parts = lib.OptionEnum(
252
+ "numberOfParts", int,
253
+ meta=dict(configurable=False)
254
+ )
255
+ hermes_parent_shipment_order_id = lib.OptionEnum(
256
+ "parentShipmentOrderID", str,
257
+ meta=dict(configurable=False)
258
+ )
109
259
 
110
260
  """Unified Option type mapping."""
111
261
  signature_required = hermes_signature
@@ -128,247 +278,16 @@ def shipping_options_initializer(
128
278
 
129
279
 
130
280
  class TrackingStatus(lib.Enum):
131
- """Hermes tracking status mapping.
281
+ """Hermes tracking status mapping based on Hermes event codes."""
132
282
 
133
- Maps Hermes 2x2 event codes (4-digit) to Karrio unified statuses.
134
- Based on Hermes Germany Eventcodes.csv.
135
- """
136
-
137
- pending = [
138
- "0000", # Die Sendung wurde Hermes elektronisch angekündigt
139
- "0600", # Shipment has not arrived at the depot (1st notice)
140
- "0700", # Shipment has not arrived at the depot (2nd notice)
141
- "0800", # Shipment has not arrived at the depot (7 days)
142
- "0900", # Shipment has not arrived at the depot (28 days)
143
- ]
144
- picked_up = [
145
- "1000", # Die Sendung hat das Lager des Auftraggebers verlassen
146
- "1900", # Shipment accepted after collection
147
- "1901", # Shipment arrived at branch by self-delivery
148
- "1910", # Shipment received on tour
149
- ]
150
- in_transit = [
151
- "1510", # Sendung am LC umgeschlagen (sorted at logistics center)
152
- "1610", # Shipment in international transit
153
- "1710", # Customs export created
154
- "1720", # Customs clearance completed
155
- "1810", # Handed to partner carrier (international)
156
- "1820", # Handed to partner carrier (international)
157
- "2000", # Die Sendung ist eingetroffen (arrived at depot)
158
- "2100", # Arrived without advice data
159
- "2300", # Automatic shipment received
160
- "2400", # Shipment handed in at ParcelShop
161
- ]
162
- out_for_delivery = [
163
- "3000", # Die Sendung ist auf Zustelltour gegangen
164
- "3010", # Shipment has left depot on tour
165
- "3300", # Sorted for delivery tour (automatic)
166
- ]
167
- ready_for_pickup = [
168
- "3410", # Die Sendung liegt im PaketShop zur Abholung bereit
169
- "3430", # Handed over to island carrier
170
- ]
171
- delivered = [
172
- "3500", # Die Sendung wurde zugestellt
173
- "3510", # Shipment delivered (with scanner)
174
- "3511", # Delivered in letterbox
175
- "3520", # Shipment delivered (without scanner)
176
- "3530", # Collected by recipient from ParcelShop
177
- "7500", # Return shipment arrived at client
178
- ]
179
- delivery_failed = [
180
- "3710", # Annahmeverweigerung (refused)
181
- "3715", # COD not paid
182
- "3720", # Address not found
183
- "3731", # Recipient not present (1st attempt)
184
- "3732", # Recipient not present (2nd attempt)
185
- "3733", # Recipient not present (3rd attempt)
186
- "3734", # Recipient not present (4th attempt)
187
- "3740", # Damage detected
188
- "3750", # Tour cancellation
189
- "3751", # Incorrect TAN (1st attempt)
190
- "3752", # Incorrect TAN (2nd attempt)
191
- "3753", # Incorrect TAN (3rd attempt)
192
- "3754", # Incorrect TAN (4th attempt)
193
- "3760", # Return shipment collected
194
- "3761", # Return shipment taken
195
- "3780", # Misdirected
196
- "3782", # Ident failed - photo mismatch
197
- "3783", # Ident failed - name mismatch
198
- "3784", # Ident failed - DOB mismatch
199
- "3785", # Ident failed - document mismatch
200
- "3786", # Ident failed - PIN code
201
- "3787", # Ident failed - age verification
202
- "3795", # Shipment stopped
203
- ]
204
- on_hold = [
205
- "1730", # Held by customs
206
- "1751", # Rejected by customs
207
- "4100", # Sendung wird aufbewahrt (shipment stored)
208
- "4500", # Stored (stocktaking)
209
- "4610", # ParcelShop - high volume, cannot pick up
210
- "4620", # ParcelShop - shipment not available
211
- "4630", # ParcelShop - shipment not found
212
- "4690", # Status corrected at ParcelShop
213
- ]
214
- delivery_delayed = [
215
- "4010", # Return - refused
216
- "4015", # Return - COD not paid
217
- "4020", # Return - address not found
218
- "4024", # Return - too large/heavy for ParcelShop
219
- "4025", # Shipment returned to depot
220
- "4031", # Return - N1 (1st attempt failed)
221
- "4032", # Return - N2 (2nd attempt failed)
222
- "4033", # Return - N3 (3rd attempt failed)
223
- "4034", # Return - N4 (4th attempt failed)
224
- "4035", # Return - not collected from ParcelShop
225
- "4040", # Return - damage
226
- "4050", # Return - tour cancellation
227
- "4051", # Return - TAN 1
228
- "4052", # Return - TAN 2
229
- "4053", # Return - TAN 3
230
- "4054", # Return - TAN 4
231
- "4060", # Return shipment received (pickup)
232
- "4061", # Return shipment received (take-away)
233
- "4062", # Return handed in at ParcelShop
234
- "4070", # Tour departure cancelled
235
- "4072", # Return cancelled (correction)
236
- "4080", # Misdirected
237
- "4081", # Ident failed
238
- "4082", # Ident failed - photo
239
- "4083", # Ident failed - name
240
- "4084", # Ident failed - DOB
241
- "4085", # Ident failed - document
242
- "4086", # Ident failed - PIN
243
- "4087", # Ident failed - age
244
- "4095", # Delivery stopped
245
- ]
246
- return_to_sender = [
247
- "1520", # Return shipment sorted
248
- "6080", # Rückversand (return shipment)
249
- "6081", # Return - refused
250
- "6082", # Return - address not readable
251
- "6083", # Return - address not found
252
- "6084", # Return - receiver not met
253
- "6085", # Return - damage
254
- "6086", # Return - sorting error
255
- "6087", # Return - technical issue
256
- "6088", # Redirected at receiver request
257
- "6089", # Return - returns
258
- "6090", # Forwarded to logistics center
259
- "6092", # Return - ident failed
260
- "6093", # Return - not collected from ParcelShop
261
- "6094", # Return - COD not paid
262
- "6096", # Return - too large/heavy for ParcelShop
263
- "6098", # Return - incorrect TAN
264
- "6099", # Return - delivery stopped
265
- ]
266
-
267
-
268
- class TrackingIncidentReason(lib.Enum):
269
- """Maps Hermes exception codes to normalized incident reasons.
270
-
271
- Based on Hermes Germany Eventcodes.csv.
272
- Maps carrier-specific exception/status codes to standardized
273
- incident reasons for tracking events.
274
- """
275
-
276
- # Consignee-caused issues
277
- consignee_refused = [
278
- "3710", # Annahmeverweigerung
279
- "4010", # Return - refused
280
- "6081", # Return - refused
281
- ]
282
- consignee_not_home = [
283
- "3731", # Recipient not present (1st attempt)
284
- "3732", # Recipient not present (2nd attempt)
285
- "3733", # Recipient not present (3rd attempt)
286
- "3734", # Recipient not present (4th attempt)
287
- "4031", # Return - N1
288
- "4032", # Return - N2
289
- "4033", # Return - N3
290
- "4034", # Return - N4
291
- "6084", # Return - receiver not met
292
- ]
293
- consignee_incorrect_address = [
294
- "3720", # Address not found
295
- "4020", # Return - address not found
296
- "6082", # Return - address not readable
297
- "6083", # Return - address not found
298
- ]
299
- consignee_not_available = [
300
- "4035", # Not collected from ParcelShop
301
- "6093", # Return - not collected from ParcelShop
302
- ]
303
- consignee_cod_unpaid = [
304
- "3715", # COD not paid
305
- "4015", # Return - COD not paid
306
- "6094", # Return - COD not paid
307
- ]
308
- consignee_id_failed = [
309
- "3782", # Ident failed - photo mismatch
310
- "3783", # Ident failed - name mismatch
311
- "3784", # Ident failed - DOB mismatch
312
- "3785", # Ident failed - document mismatch
313
- "3786", # Ident failed - PIN code
314
- "3787", # Ident failed - age verification
315
- "4081", # Ident failed
316
- "4082", # Ident failed - photo
317
- "4083", # Ident failed - name
318
- "4084", # Ident failed - DOB
319
- "4085", # Ident failed - document
320
- "4086", # Ident failed - PIN
321
- "4087", # Ident failed - age
322
- "6092", # Return - ident failed
323
- ]
324
- consignee_tan_invalid = [
325
- "3751", # Incorrect TAN (1st attempt)
326
- "3752", # Incorrect TAN (2nd attempt)
327
- "3753", # Incorrect TAN (3rd attempt)
328
- "3754", # Incorrect TAN (4th attempt)
329
- "4051", # Return - TAN 1
330
- "4052", # Return - TAN 2
331
- "4053", # Return - TAN 3
332
- "4054", # Return - TAN 4
333
- "6098", # Return - incorrect TAN
334
- ]
335
-
336
- # Carrier-caused issues
337
- carrier_damaged_parcel = [
338
- "3740", # Damage detected
339
- "4040", # Return - damage
340
- "6085", # Return - damage
341
- ]
342
- carrier_sorting_error = [
343
- "3780", # Misdirected
344
- "4080", # Misdirected
345
- "6086", # Return - sorting error
346
- "6087", # Return - technical issue
347
- ]
348
- carrier_not_enough_time = [
349
- "3750", # Tour cancellation
350
- "4050", # Return - tour cancellation
351
- "4070", # Tour departure cancelled
352
- ]
353
- carrier_parcel_too_large = [
354
- "4024", # Return - too large/heavy for ParcelShop
355
- "6096", # Return - too large/heavy for ParcelShop
356
- ]
357
-
358
- # Customs-related issues
359
- customs_delay = [
360
- "1730", # Held by customs
361
- ]
362
- customs_rejected = [
363
- "1751", # Rejected by customs
364
- ]
365
-
366
- # Shipment stopped
367
- shipment_stopped = [
368
- "3795", # Shipment stopped
369
- "4095", # Delivery stopped
370
- "6099", # Return - delivery stopped
371
- ]
283
+ pending = ["0000"]
284
+ in_transit = ["1000", "2000"]
285
+ out_for_delivery = ["3000"]
286
+ delivered = ["3500"]
287
+ delivery_failed = ["4000", "4500"]
288
+ ready_for_pickup = ["5000"]
289
+ on_hold = ["6000"]
290
+ delivery_delayed = ["7000"]
372
291
 
373
292
 
374
293
  class LabelType(lib.StrEnum):
@@ -418,18 +337,17 @@ def load_services_from_csv() -> list:
418
337
  # Map carrier service code to karrio service code
419
338
  karrio_service_code = ShippingService.map(service_code).name_or_key
420
339
 
340
+ row_min_weight = float(row["min_weight"]) if row.get("min_weight") else None
341
+ row_max_weight = float(row["max_weight"]) if row.get("max_weight") else None
342
+
421
343
  # Initialize service if not exists
422
344
  if karrio_service_code not in services_dict:
423
345
  services_dict[karrio_service_code] = {
424
346
  "service_name": service_name,
425
347
  "service_code": karrio_service_code,
426
348
  "currency": row.get("currency", "EUR"),
427
- "min_weight": (
428
- float(row["min_weight"]) if row.get("min_weight") else None
429
- ),
430
- "max_weight": (
431
- float(row["max_weight"]) if row.get("max_weight") else None
432
- ),
349
+ "min_weight": row_min_weight,
350
+ "max_weight": row_max_weight,
433
351
  "max_length": (
434
352
  float(row["max_length"]) if row.get("max_length") else None
435
353
  ),
@@ -447,6 +365,15 @@ def load_services_from_csv() -> list:
447
365
  ),
448
366
  "zones": [],
449
367
  }
368
+ else:
369
+ # Update service-level weight bounds to cover all zones
370
+ current = services_dict[karrio_service_code]
371
+ if row_min_weight is not None:
372
+ if current["min_weight"] is None or row_min_weight < current["min_weight"]:
373
+ current["min_weight"] = row_min_weight
374
+ if row_max_weight is not None:
375
+ if current["max_weight"] is None or row_max_weight > current["max_weight"]:
376
+ current["max_weight"] = row_max_weight
450
377
 
451
378
  # Parse country codes
452
379
  country_codes = [
@@ -457,6 +384,8 @@ def load_services_from_csv() -> list:
457
384
  zone = models.ServiceZone(
458
385
  label=row.get("zone_label", "Default Zone"),
459
386
  rate=float(row.get("rate", 0.0)),
387
+ min_weight=row_min_weight,
388
+ max_weight=row_max_weight,
460
389
  transit_days=(
461
390
  int(row["transit_days"].split("-")[0]) if row.get("transit_days") and row["transit_days"].split("-")[0].isdigit() else None
462
391
  ),
@@ -5,7 +5,6 @@ import typing
5
5
 
6
6
  @attr.s(auto_attribs=True)
7
7
  class TimeSlotType:
8
- timeSlotfrom: typing.Optional[str] = None
9
8
  to: typing.Optional[str] = None
10
9
 
11
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karrio_hermes
3
- Version: 2026.1.3
3
+ Version: 2026.1.5
4
4
  Summary: Karrio - Hermes Shipping Extension
5
5
  Author-email: karrio <hello@karrio.io>
6
6
  License-Expression: LGPL-3.0
@@ -5,8 +5,8 @@ karrio/mappers/hermes/settings.py,sha256=4BTM7ncSQqTBP6J_oJTksBDGhaFHOWoHDnpq0iO
5
5
  karrio/plugins/hermes/__init__.py,sha256=zUfOhtzuErd1H1ICL4rQt68e6BWRQf_B6_kW5UVzd9M,1020
6
6
  karrio/providers/hermes/__init__.py,sha256=wMiVnPz7EgKdbu1pGBuvalfyuHj5QwSeMijjvEP6q4w,590
7
7
  karrio/providers/hermes/error.py,sha256=YWmYC_l7_nyJCxiqM0Kgf6yV9IpJ04tkdZyyMT7QnkY,2974
8
- karrio/providers/hermes/tracking.py,sha256=F_NYX_G7hnQe04zPbeXfc_IuN69XLOjYC9v9ezHB0Kg,5958
9
- karrio/providers/hermes/units.py,sha256=voG1oHUfF2Fjzo0w7FOVqrNGmGLTCWDai1zlMng-9mQ,18148
8
+ karrio/providers/hermes/tracking.py,sha256=olKfHEVYZuwGL9e4348Ui-mAKBGr94FLF6VyxCwtZZg,5941
9
+ karrio/providers/hermes/units.py,sha256=8-_jGT_yjnBmZWcNBzbGD1AszxUIJ4v3apQjOfzW6N0,14275
10
10
  karrio/providers/hermes/utils.py,sha256=NOfS_K0Zw7_wxJGBJfkdXwZP6hs0bsaZye5uypztGc4,4163
11
11
  karrio/providers/hermes/pickup/__init__.py,sha256=E59ks-qJAsNmfQgaZ7X0tZuBetITsdRSMJUAZnqxabg,307
12
12
  karrio/providers/hermes/pickup/cancel.py,sha256=JYVGL9decPB0QOHsjrYU5cwT_4vzrpsaEjapC9F3t7A,1587
@@ -21,9 +21,9 @@ karrio/schemas/hermes/pickup_create_request.py,sha256=0V14LMl0XVLOZ_zXRzoTLUZ8nB
21
21
  karrio/schemas/hermes/pickup_create_response.py,sha256=sAgq92J0TZOUolhMcvDkp6qSRvxOqWCIZHNQWQN7KeU,397
22
22
  karrio/schemas/hermes/shipment_request.py,sha256=FMqrSlCDG4eGDkuqPDc_DDCbtV_vGRyrRBFCiNTFkmM,7415
23
23
  karrio/schemas/hermes/shipment_response.py,sha256=BI2kUemG-wTzAFpok9KEnlwjm6eK_LemBA_Cyga_e6A,3477
24
- karrio/schemas/hermes/tracking_response.py,sha256=Ye4Uo6UEnEy-A5VX4MDmyyePXZMM_meMiecJlht9qMc,2013
25
- karrio_hermes-2026.1.3.dist-info/METADATA,sha256=P2iafkpDRgz5fQhi9oQvv_GWYDj2N23CwqdgPnnqvhc,982
26
- karrio_hermes-2026.1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
27
- karrio_hermes-2026.1.3.dist-info/entry_points.txt,sha256=R8XDrYBqWXTPqaO0iTi4Buz8iQiiBiuHkJVNtZIXTkU,57
28
- karrio_hermes-2026.1.3.dist-info/top_level.txt,sha256=9Nasa6abG7pPPG8MGzlemnqw1ohIqgouzQ7HGBnOFLg,27
29
- karrio_hermes-2026.1.3.dist-info/RECORD,,
24
+ karrio/schemas/hermes/tracking_response.py,sha256=6-bqTSl-N65PHl8cs1puOCFubzIZB55RveLdpNsmTEE,1967
25
+ karrio_hermes-2026.1.5.dist-info/METADATA,sha256=Ute2gZYW9m-tFogSK6_rWzfYnplNCXt63cvk1bzjhuk,982
26
+ karrio_hermes-2026.1.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
27
+ karrio_hermes-2026.1.5.dist-info/entry_points.txt,sha256=R8XDrYBqWXTPqaO0iTi4Buz8iQiiBiuHkJVNtZIXTkU,57
28
+ karrio_hermes-2026.1.5.dist-info/top_level.txt,sha256=9Nasa6abG7pPPG8MGzlemnqw1ohIqgouzQ7HGBnOFLg,27
29
+ karrio_hermes-2026.1.5.dist-info/RECORD,,