"""Bounded quote matching and Decimal arithmetic, independent of Flask/models.""" from decimal import Decimal, ROUND_HALF_UP import re from .pricing import dims_to_cm from .quote_intent import US, QUOTE_WORDS, parse_intent, is_followup def broad_zone(postal): if not postal: return None if postal.startswith(('8','9')): return '美西' if postal.startswith('46') or postal[0] in '0123': return '美东' return '美中' def variants(intent, book): if intent.get('allocations'): return [{**destination,'allocation':part} for part in intent['allocations'] for destination in variants({**intent,'allocations':None,'warehouse':part['warehouse'], 'warehouses':None,'postal':None},book)] if len(intent.get('warehouses') or [])>1: return [variant for warehouse in intent['warehouses'] for variant in variants({**intent,'warehouse':warehouse,'warehouses':None,'postal':None},book)] if not intent['warehouse'] and len(intent.get('zones') or [])>1: return [variant for zone in intent['zones'] for variant in variants({**intent,'zone':zone,'zones':None},book)] warehouse = intent['warehouse'] found = [r for r in book['warehouses'] if r['code'] == warehouse] if warehouse else [] zoned = [r for r in found if r['zone']] if zoned: unique = {} for row in zoned: postal = intent['postal'] or row['postal'][:5] key = (row['zone'], postal) unique.setdefault(key, {'label':f"{warehouse} · {row['zone']}", 'postal':postal, 'zone':broad_zone(postal), 'warehouse':warehouse}) return list(unique.values()) postal = intent['postal'] if not postal and found: match = re.search(r'(?0 else None volume_weight = ((volume or Decimal(0))*1000000/6000 if rate['kind']=='parcel' else (volume or Decimal(0))*167) charge = max(real, volume_weight,package_charge) if rate['kind'] == 'parcel': charge = max(charge, Decimal(12)*(intent['boxes'] or 1)) return charge if real or volume else None def match_rates(intent, book, variant): selected = [] kinds = intent['kinds'] or (['truck','parcel'] if variant['warehouse'] else ['parcel']) for rate in book['rates']: if rate['kind'] not in kinds or (intent['origin'] and intent['origin'] != rate['origin']): continue if intent['services'] and not any(name in rate['service'] for name in intent['services']): continue if intent['tax'] and rate['tax'] != intent['tax']: continue if rate['kind'] == 'truck': if not variant['warehouse'] or variant['warehouse'] not in [value.upper() for value in rate['destinations']]: continue elif variant['zone'] and rate['zone'] != variant['zone']: continue selected.append(rate) groups = {} for rate in selected: key = (rate['service'],rate['origin'],rate['zone'],rate['unit'],rate['tax']) charge = quantity(intent,rate) if rate['kind'] == 'parcel' and Decimal(rate['min_weight']) > (charge or Decimal(12)): continue if key not in groups or Decimal(rate['min_weight']) > Decimal(groups[key]['min_weight']): groups[key] = rate return list(groups.values()) def number(value, places=2): return format(Decimal(value).quantize(Decimal(10)**-places, rounding=ROUND_HALF_UP), 'f').rstrip('0').rstrip('.') if places else str(value) def exact_number(value): text=format(Decimal(value),'f') return text.rstrip('0').rstrip('.') if '.' in text else text def quantity_number(value,intent): if Decimal(value)==Decimal(intent.get('weight') or '0') or Decimal(value).as_tuple().exponent>=-6: return exact_number(value) return '约'+number(value,6) def select_timed_rates(rates,slowest=False): """Compare numeric day ranges within one region, service kind and billing basis.""" ranked=[] best={} for rate in rates: match=re.search(r'(?1: lines.append('以下按各目的仓分别比较;实际分票请提供每个仓的重量。') if intent.get('air_requested') and not intent.get('air_only'): lines.append('当前资料没有美国空运费率,下面列出海运方案。') for destination, fees in zip(destinations, remote_fees): part=destination.get('allocation') billing=({**intent,'weight':part['weight'],'packages':part.get('packages'),'dims':None,'cbm':None,'boxes':part.get('boxes')} if part else intent) if part and intent.get('metal_chargeable_weight') is not None: billing.update(metal_chargeable_weight=None,metal_scope='partial') rates = match_rates(billing, book, destination) if rate_selection=='cheapest' and rates: eligible=[r for r in rates if quantity(billing,r) is not None] best={} for rate in eligible: key=(destination['zone'] or rate['zone'],rate['unit'],rate['tax']) value=quantity(billing,rate)*Decimal(rate['price'])+charge_details(billing,rate,fees,book)['cny'] best[key]=min(best.get(key,value),value) rates=[r for r in eligible if quantity(billing,r)*Decimal(r['price'])+charge_details(billing,r,fees,book)['cny']==best[(destination['zone'] or r['zone'],r['unit'],r['tax'])]] or rates lines.append('按相同区域、计费单位及税费方式比较的较低价方案:') if not rates: lines.append(f"{destination['label']}:当前资料没有匹配的线路价格,提供目的仓码或邮编可继续查询。") continue if destination['label'] != '美国': lines.append(destination['label']+':') selections=[('',rates)] if rate_selection in ('fastest','slowest','speed_compare'): selections=[] if rate_selection in ('fastest','speed_compare'): selections.append(('最快参考方案',select_timed_rates(rates))) if rate_selection in ('slowest','speed_compare'): selections.append(('最慢参考方案',select_timed_rates(rates,slowest=True))) lines.append('按相同区域、运输方式、计费单位及税费方式比较参考时效。最快按天数下限、最慢按天数上限选择。') grouped = {} for selection,selected_rates in selections: if selection and not selected_rates: lines.append(selection+'暂时无法确定,匹配线路未列明可比较的天数。') for rate in selected_rates: key = (selection, rate['service'], rate['origin'], rate['unit'], rate['tax'], rate['eta']) grouped.setdefault(key, []).append(rate) last_selection=None for (selection,service, origin, unit, tax,eta_source), rows in grouped.items(): if selection and selection!=last_selection: lines.append(selection) last_selection=selection eta = re.sub(r'\s*无时效赔偿', '', eta_source).strip() if rows[0]['kind']=='parcel' and eta and '提取' not in eta:eta+='提取' elif rows[0]['kind']=='truck' and eta and '船开' not in eta:eta='开船后参考'+eta if intent.get('mode')=='time': lines.append(f'- {service} · {origin},{eta or "当前资料未列明时效"}。') continue parts = [] for rate in rows: amount = quantity(billing,rate) price = Decimal(rate['price']) rate_text = f"{rate['zone']} " if rate['zone'] and not destination['zone'] else '' rate_text += f"{number(price)}元/{'KG' if unit == 'kg' else 'CBM'}" if amount is not None: total = amount*price details=charge_details(billing,rate,fees,book) total+=details['cny'] rate_text += f",{quantity_number(amount,billing)}{'KG' if unit == 'kg' else 'CBM'} = {number(total)}元" if details['items']:rate_text+='('+ ';'.join(details['items'])+')' for note in details['notes']: if note not in lines:lines.append(note) elif intent.get('cbm') and rate['unit']=='cbm': lines.append('计费体积四舍五入后不足0.01CBM,最低计费体积需人工确认,不能按零费用出价。') parts.append(rate_text) lines.append(f"- {service} · {origin} · {'包税' if tax=='included' else '不包税'}:"+';'.join(parts) + (f'({eta})' if eta else '')) if fees: if fees['fee'] is None: lines.append('该邮编属于遥远地址,派送附加费需单独确认。') else: lines.append(f"海派已计入偏远附加费 {number(fees['fee'])}元/KG。") if not any(intent.get(key) for key in ('warehouse','postal')): lines.append('发我目的仓码或邮编,可继续匹配到仓线路。') if intent.get('mode')=='time': lines.append('以上时效按开船后到提取计算,送达时间需结合末端派送进度核对。') elif not intent['weight'] and not intent['cbm'] and not intent['dims']: lines.append('货物多少公斤、几箱?') elif not intent['boxes'] or not intent['dims']: lines.append('请补充箱数、每箱重量和尺寸,继续核算计费重及超重超尺寸费用。') if intent.get('mode')=='calculation': lines.append('计算过程:各箱实重与体积重取大;海派每箱不足12KG按12KG计费,再按整票计费重选择重量档位,乘对应单价,并单列已知附加费。') text='\n'.join(lines) return translate_quote(text) if intent.get('english') else text