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