NEKReport/shipping_calculator(1).py

120 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import math
import re
from typing import Dict, Optional
def calculate_shipping_cost(shipment: Dict) -> Optional[Dict]:
"""
Расчёт стоимости авиаперевозки на основе параметров груза.
Возвращает словарь с ключами:
total_usd, total_rub, chargeable_weight, actual_weight, volume_weight
или None, если не хватает данных.
"""
try:
# Проверяем наличие обязательных данных
if not all(k in shipment for k in ['total_weight_kg', 'dimensions', 'cargo_value']):
return None
# Параметры груза
actual_weight = shipment.get('total_weight_kg', 0)
if not actual_weight or actual_weight <= 0:
return None
# Расчёт объёмного веса
dimensions = shipment.get('dimensions', [])
if not dimensions:
return None
total_volume_weight = 0
for dim in dimensions:
if dim.get('length_cm') and dim.get('width_cm') and dim.get('height_cm'):
# Объёмный вес = (Д×Ш×В в метрах) × 167
length_m = dim['length_cm'] / 100
width_m = dim['width_cm'] / 100
height_m = dim['height_cm'] / 100
volume = length_m * width_m * height_m
volume_weight = volume * 167
total_volume_weight += volume_weight
# Платный вес (оплачивается по большему)
chargeable_weight = max(actual_weight, total_volume_weight)
chargeable_weight = math.ceil(chargeable_weight) # округляем вверх
# Базовый тариф в зависимости от веса (USD/кг)
if chargeable_weight < 45:
base_rate = 6.5
elif chargeable_weight < 100:
base_rate = 5.5
elif chargeable_weight < 300:
base_rate = 4.5
elif chargeable_weight < 500:
base_rate = 3.8
elif chargeable_weight < 1000:
base_rate = 3.2
else:
base_rate = 2.8
# Топливный сбор и сбор за безопасность
fuel_surcharge_rate = 0.9 # USD/кг
min_fuel_surcharge = 90 # USD минимум
# Терминальные сборы
terminal_export_rate = 0.22 # USD/кг (~20 руб/кг)
terminal_export_fixed = 25 # USD фиксированных сборов (~2300 руб)
terminal_import_rate = 0.35 # EUR/кг ≈ 0.38 USD/кг по курсу 1.1
# Таможенное оформление
customs_export = 170 # USD (~15500 руб)
customs_import = 220 # EUR ≈ 242 USD
# Надбавки за особые условия
special_rate_multiplier = 1.0
# Терморежим (+15…+25 °C)
if shipment.get('special_transport_requirements') and 'термо' in str(shipment.get('special_transport_requirements', '')).lower():
special_rate_multiplier *= 1.3 # +30%
# Страхование
cargo_value_usd = 0
cargo_value_str = shipment.get('cargo_value', '0')
# Извлекаем числовое значение из строки (например, "18 500 EUR" -> 18500)
value_match = re.search(r'[\d\s]+', cargo_value_str.replace(' ', ''))
if value_match:
cargo_value_usd = float(value_match.group().replace(' ', ''))
# Если указано в EUR, конвертируем
if 'EUR' in cargo_value_str or '' in cargo_value_str:
cargo_value_usd *= 1.1 # приблизительный курс EUR/USD
insurance_cost = cargo_value_usd * 0.01 # 1% от стоимости груза
# Расчёт компонентов
base_freight = chargeable_weight * base_rate * special_rate_multiplier
fuel_surcharge = max(chargeable_weight * fuel_surcharge_rate, min_fuel_surcharge)
terminal_export = chargeable_weight * terminal_export_rate + terminal_export_fixed
terminal_import = chargeable_weight * 0.38 # конвертация EUR->USD
customs_total = customs_export + customs_import
# Дополнительные услуги
additional_services = shipment.get('additional_services', [])
additional_cost = 0
if isinstance(additional_services, list):
if 'уведомление о прибытии' in str(additional_services).lower():
additional_cost += 25 # USD
# Итог
total_usd = (base_freight + fuel_surcharge + terminal_export +
terminal_import + customs_total + additional_cost + insurance_cost)
# Конвертация в рубли для удобства (курс USD/RUB ~90)
total_rub = total_usd * 90
return {
"total_usd": round(total_usd, 2),
"total_rub": round(total_rub, 2),
"chargeable_weight": chargeable_weight,
"actual_weight": actual_weight,
"volume_weight": round(total_volume_weight, 2)
}
except Exception as e:
# Логирование ошибки, если нужно
return None