# -*- coding: utf-8 -*- """ 美国售价模型 V2 (shopfad) 通过输入多包裹体积和采购价格,快速计算售价 """ import math from utils.gtools import MySQLconnect import pandas as pd class USLogisticsPrice: """美国物流价格查询""" _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._init_data() return cls._instance def _init_data(self): """从数据库加载价格数据""" conn = MySQLconnect('logistics') with conn as c: self.sell_usps = pd.read_sql("SELECT * FROM sell_usps", c.con) self.sell_uandf = pd.read_sql("SELECT * FROM sell_uandf", c.con) self.sell_fedex = pd.read_sql("SELECT * FROM sell_fedex", c.con) def get_express_fee(self, packages, profit_rate=0.359): """ 计算快递费用 (尾程) - 支持多包裹 Args: packages: 包裹列表, 每个元素为 dict: {'length_cm', 'width_cm', 'height_cm', 'weight_g'} profit_rate: 利润系数 Returns: float: 快递费用 (USD) """ # 先遍历所有包裹确定物流类型(取最严格的) head_type = 0 index = 0 for pkg in packages: l, w, h, wt = pkg['length_cm'], pkg['width_cm'], pkg['height_cm'], pkg['weight_g'] sizes = sorted([l, w, h], reverse=True) fst, sed, trd = sizes[0], sizes[1], sizes[2] vol_6000 = l * w * h / 6000 if wt > 420: head_type = max(head_type, 1) if fst > 50 or sed > 40 or trd > 30 or wt > 2718 or vol_6000 > 3624: head_type = max(head_type, 2) if fst > 264 or wt > 67000 or vol_6000 > 95000 or (fst + sed * 2 + trd * 2) > 391: head_type = max(head_type, 3) index = 1 if fst > 310: head_type = max(head_type, 4) index = 1 # 逐包裹计算快递费 sku_express_fee = 0 sku_total_cubic_feet = 0 ltl_over_size = 0 ltl_over_weightfee = 0 for pkg in packages: l, w, h, wt = pkg['length_cm'], pkg['width_cm'], pkg['height_cm'], pkg['weight_g'] sizes = sorted([l, w, h], reverse=True) fst, sed, trd = sizes[0], sizes[1], sizes[2] volume = l * w * h vol_8500 = volume / 8500 vol_6000 = volume / 6000 lbs_weight = math.ceil(max(vol_8500 / 0.453, wt / 453)) oz_weight = math.ceil(wt / 28) express_base_fee = 0 oversize_charge = 0 ahs_weight = 0 ahs_dimension = 0 if head_type == 0: try: row = self.sell_usps[self.sell_usps['oz'] == oz_weight] if len(row) > 0: express_base_fee = row['最终费用_v2'].iloc[0] / profit_rate else: head_type = 1 except: head_type = 1 if head_type == 1: try: row = self.sell_uandf[self.sell_uandf['lbs'] == lbs_weight] if len(row) > 0: express_base_fee = row['加权价格_v2'].iloc[0] / profit_rate else: head_type = 2 except: head_type = 2 if head_type == 2: try: row = self.sell_fedex[self.sell_fedex['lbs'] == lbs_weight] if len(row) > 0: express_base_fee = row['售价尾端价格_v3'].iloc[0] else: express_base_fee = 99999 head_type = 3 if fst > 238 or (fst + sed * 2 + trd * 2) > 315: oversize_charge = 109.4 elif wt > 21000: ahs_weight = 6.1 except: head_type = 3 sku_express_fee += express_base_fee + oversize_charge + ahs_weight + ahs_dimension # 卡派计算 cubic_feet = math.ceil(volume / 1000000 * 35.3147) sku_total_cubic_feet += cubic_feet if fst > 250: ltl_over_size = max(ltl_over_size, round(130 / profit_rate - 118, 1)) if 111000 <= wt < 130000: ltl_over_weightfee = max(ltl_over_weightfee, round(80 / profit_rate - 78, 1)) if 130000 <= wt < 157000: ltl_over_weightfee = max(ltl_over_weightfee, round(130 / profit_rate - 118, 1)) # 卡派基础费用 if sku_total_cubic_feet < 25: ltl_base_fee1 = round(198 / profit_rate / 2, 2) elif sku_total_cubic_feet < 35: ltl_base_fee1 = round(231 / profit_rate / 2, 2) else: ltl_base_fee1 = round(max(231, 6.6 * sku_total_cubic_feet) / profit_rate / 2, 2) ltl_base_fee = ltl_base_fee1 * 1.025 ltl_fee = ltl_base_fee + ltl_over_size + ltl_over_weightfee if index == 0: return min(ltl_fee, sku_express_fee) else: return ltl_fee class USSellPrice: """ 美国售价计算器 V2 (shopfad) 使用示例: calculator = USSellPrice() # 单包裹 result = calculator.calculate( packages=[{'length_cm': 30, 'width_cm': 20, 'height_cm': 10, 'weight_g': 500}], purchase_price_cny=100, preset='shopfad' ) # 多包裹 result = calculator.calculate( packages=[ {'length_cm': 30, 'width_cm': 20, 'height_cm': 10, 'weight_g': 500}, {'length_cm': 40, 'width_cm': 30, 'height_cm': 15, 'weight_g': 1000}, ], purchase_price_cny=100, preset='shopfad' ) """ PRESETS = { 'shopfad': { 'ocean_first_cny': 0.5, 'ocean_first_usd': 0.7, 'air_first_usd': 0.65, 'air_cny_type': 0.093, 'air_first_fix': 22.7, 'exchange_rate': 6.5, 'profit_rate': 0.38, 'air_rate': 0.7, 'tax_rate': 0.1, 'shipping_type': 1, 'fuel_rate': 0, 'extra_rate': 0 }, 'shopfad2': { 'ocean_first_cny': 1, 'ocean_first_usd': 1, 'air_first_usd': 0.65, 'air_cny_type': 0.093, 'air_first_fix': 22.7, 'exchange_rate': 6.5, 'profit_rate': 0.38, 'air_rate': 0.7, 'tax_rate': 0.145, 'shipping_type': 1, 'fuel_rate': 0, 'extra_rate': 0 }, 'shopfad3': { 'ocean_first_cny': 0.67, 'ocean_first_usd': 0.7, 'air_first_usd': 0.65, 'air_cny_type': 0.093, 'air_first_fix': 22.7, 'exchange_rate': 6.5, 'profit_rate': 0.3, 'air_rate': 0.7, 'tax_rate': 0.1, 'shipping_type': 1, 'fuel_rate': 0, 'extra_rate': 0 } } def __init__(self): self.logistics = USLogisticsPrice() def calculate(self, packages, purchase_price_cny, preset='shopfad', config=None): """ 计算售价 Args: packages: 包裹列表, 每个元素为 dict: {'length_cm', 'width_cm', 'height_cm', 'weight_g'} purchase_price_cny: 采购价(CNY) preset: 预设配置名称 config: 自定义配置 (优先使用) Returns: dict: 包含详细费用信息 """ cfg = config or self.PRESETS.get(preset, self.PRESETS['shopfad']) # 汇总体积重 total_volume_weight = sum( p['length_cm'] * p['width_cm'] * p['height_cm'] / 6000 for p in packages ) # 尾程快递费 fuel_rate = cfg.get('fuel_rate', 0) extra_rate = cfg.get('extra_rate', 0) express_fee = self.logistics.get_express_fee(packages, cfg['profit_rate']) express_fee = express_fee * (1 + fuel_rate) * (1 + extra_rate) purchase_price_usd = purchase_price_cny / cfg['exchange_rate'] tax_amount = purchase_price_cny * cfg['tax_rate'] / cfg['exchange_rate'] if cfg['shipping_type'] == 0: # 空运 sell_price = 0 first_cny = 0 first_usd = 0 for pkg in packages: volume = pkg['length_cm'] * pkg['width_cm'] * pkg['height_cm'] volume_weight = volume / 6000 density = pkg['weight_g'] / (volume / 1000) if volume > 0 else 0 type_weight = min(337, max(37, density)) air_first_cny = cfg['air_cny_type'] * type_weight + cfg['air_first_fix'] air_first_cny_total = air_first_cny * volume_weight air_first_usd_total = cfg['air_first_usd'] * volume_weight sell_price += (purchase_price_usd / cfg['profit_rate'] + ((air_first_cny_total / cfg['exchange_rate'] + air_first_usd_total + tax_amount) / cfg['profit_rate'] + express_fee) * cfg['air_rate']) first_cny += air_first_cny_total first_usd += air_first_usd_total shipping_type = '空运' else: # 海运 ocean_first_cny_total = cfg['ocean_first_cny'] * total_volume_weight ocean_first_usd_total = cfg['ocean_first_usd'] * total_volume_weight sell_price = ((ocean_first_cny_total + purchase_price_cny) / cfg['exchange_rate'] + ocean_first_usd_total + tax_amount) / cfg['profit_rate'] + express_fee first_cny = ocean_first_cny_total first_usd = ocean_first_usd_total shipping_type = '海运' return { 'sell_price_usd': round(sell_price, 2), 'sell_price_cny': round(sell_price * cfg['exchange_rate'], 2), 'purchase_price_cny': purchase_price_cny, 'express_fee': round(express_fee, 2), 'first_cny': round(first_cny, 2), 'first_usd': round(first_usd, 2), 'tax_amount': round(tax_amount, 2), 'shipping_type': shipping_type, 'preset': preset } if __name__ == '__main__': calc = USSellPrice() print("=== 单包裹测试 ===") result = calc.calculate( packages=[{'length_cm': 30, 'width_cm': 20, 'height_cm': 10, 'weight_g': 500}], purchase_price_cny=100, preset='shopfad' ) print(f" 30x20x10cm 500g => ${result['sell_price_usd']}") print("\n=== 多包裹测试 ===") result = calc.calculate( packages=[ {'length_cm': 30, 'width_cm': 20, 'height_cm': 10, 'weight_g': 500}, {'length_cm': 40, 'width_cm': 30, 'height_cm': 15, 'weight_g': 1000}, ], purchase_price_cny=200, preset='shopfad' ) print(f" 两件包裹 => ${result['sell_price_usd']}") print(f" 快递费: ${result['express_fee']}") print(f" 头程CNY: {result['first_cny']}, 头程USD: ${result['first_usd']}")