"""Normalize customer wording into bounded quote fields without model inference.""" from decimal import Decimal import re from .pricing import parse_shipping_intent, detect_dim_unit, shipping_region US = re.compile(r'美国|美西|美中|美东|洛杉矶|纽约|芝加哥|休斯顿|萨凡纳|' r'(?1:parsed['dims']=None elif assigned: weight=str(sum(Decimal(a['weight']) for a in assigned)) if set(a['warehouse'] for a in assigned)!=set(warehouses):errors.append('请分别提供每个目的仓的重量,避免遗漏分票。') total=re.search(r'(?:总重|合计重量)\s*'+WEIGHT.pattern,quantity_text,re.I) if total and kg(*total.groups())!=Decimal(weight):errors.append('总重与各目的仓分配重量不一致,请确认。') elif len(weights)>1 and request_mode(text)!='rounding': errors.append('请明确总重,或分别提供每箱重量,避免把不同重量混在一起。') if assigned and packages and Decimal(weight)!=sum(Decimal(a['weight']) for a in assigned): errors.append('分仓重量与分箱重量不一致,请分别确认每仓的箱数和重量。') if any(kg(*w.groups())<=0 for w in weights):errors.append('重量必须大于 0,请确认有效重量。') labeled=re.search(r'(?:邮编|zip(?:\s*code)?|post\s*code)\s*[::为是]?\s*([0-9-]+)',text,re.I) postal=None if labeled: if re.fullmatch(r'[0-9]{5}(?:-[0-9]{4})?',labeled.group(1)):postal=labeled.group(1)[:5] else:errors.append('美国邮编请提供5位数字,可附加4位扩展码;当前邮编格式不正确。') else: remaining=WEIGHT.sub(' ',text) remaining=re.sub(r'\d+(?:\.\d+)?\s*[*xX×]\s*\d+(?:\.\d+)?\s*[*xX×]\s*\d+(?:\.\d+)?',' ',remaining) match=re.search(r'(?:^|美国\s*|[,,\s])([0-9]{5})(?:-[0-9]{4})?(?=$|[,,\s])',remaining) if match:postal=match.group(1) cbm=re.search(r'(-?\d+(?:\.\d+)?)\s*(?:cbm|立方米|立方|方)',text,re.I) origin='华南' if re.search(r'华南|深圳|东莞|south china|shenzhen',text,re.I) else ('义乌' if re.search(r'华东|义乌|yiwu',text,re.I) else None) if re.search(r'华南|深圳|东莞',text) and re.search(r'华东|义乌',text):origin=None services=[n for n in ('美森正班','美森加班','EXX','合德','普船') if n.lower() in text.lower()] if '美森' in text and not any(n.startswith('美森') for n in services):services+=['美森正班','美森加班'] kinds=[] if re.search(r'海派|快递|UPS|FEDEX',text,re.I):kinds.append('parcel') if re.search(r'海卡|海铁|全卡|卡派|卡车|铁路',text):kinds.append('truck') zones=[z for z in ('美西','美中','美东','东1区','东2区','中1区','中2区','西1区','西2区') if z in text] if not zones and '洛杉矶' in text:zones=['美西'] if not zones and re.search(r'纽约|萨凡纳',text):zones=['美东'] air=bool(re.search(r'空运|空派|\bair\b',text,re.I)) sea=bool(re.search(r'海运|海派|海卡|海铁|\bsea\b',text,re.I)) english=not re.search(r'[\u4e00-\u9fff]',message) and bool(re.search(r'[A-Za-z]{3,}\s+[A-Za-z]',message)) mode=request_mode(text) return {'country':country,'weight':weight,'dims':parsed['dims'],'dim_unit':detect_dim_unit(text), 'cbm':cbm.group(1) if cbm else None,'boxes':count,'packages':packages or None,'allocations':assigned,'origin':origin, 'services':services or None,'kinds':kinds or None,'transport':parsed['channel'], 'air_only':True if air and not sea else None,'air_requested':True if air else None, 'warehouse':warehouses[0] if warehouses else None,'warehouses':warehouses or None, 'unknown_warehouses':unknown or None,'postal':postal,'zone':zones[0] if zones else None,'zones':zones or None, 'tax':'excluded' if re.search(r'不包税|自税|不含税|tax excluded',text,re.I) else ('included' if re.search(r'包税|tax included',text,re.I) else None), 'errors':errors,'mode':mode,'selection':request_selection(text), 'pending_weight':pending_weight[1] if pending_weight else None, 'weight_unit':weight_unit[1] if weight_unit else None, 'clear_selection':bool(re.search(r'(?:全部|所有|各个?)(?:的)?(?:渠道|线路|方案)|(?:全部|所有)(?:报价|价格|运价)',text)), 'english':True if english else (False if re.search(r'[\u4e00-\u9fff]',message) else None), 'material':materials['material'],'metal_scope':materials['metal_scope'], 'metal_chargeable_weight':materials['metal_chargeable_weight'], 'metal_weight_update':materials['metal_weight_update'], 'address_type':('non_fba' if re.search(r'私人|住宅|商业地址|非亚马逊',text) else ('fba' if warehouses else None)), 'preserve_total':True if re.search(r'总重不变|重量不变',text) else None,'text':text} def is_followup(message,intent): if resumes_quote(message):return True if intent.get('weight_unit'):return True if re.search(r'和刚才一样|其他不变|其它不变|每箱不变',message):return True if TOPIC_BOUNDARY.search(message):return False text=message.strip().strip(',,。.!!?? ') if intent.get('errors') and re.search(r'邮编|重量|箱|kg',message,re.I):return True if re.match(r'^(?:那(?:么)?|换成|改成|改为|如果|还是|同样|目的仓改|仓码改|只要|货物是|这个价格|这个费用)',text):return True if intent.get('warehouse') or intent.get('unknown_warehouses'): return not (intent['country'] and intent['weight']) if intent.get('mode')!='quote':return not (intent['country'] and intent['weight']) if re.fullmatch(r'具体(?:多少|多少钱)(?:呢)?',text):return True if re.fullmatch(r'(?:请|请问|帮我|给我|请给我|麻烦)?(?:再|继续)?(?:提供|显示|列出|看一下|看看|查询)?' r'(?:具体|详细|全部渠道|所有渠道|各渠道)?(?:报价|价格|运费|费用)(?:呢|是多少|多少|怎么样|如何|多少钱)?',text):return True if re.fullmatch(r'(?:邮编|zip|postcode)?\s*\d{5}(?:-\d{4})?(?:呢)?',text,re.I):return True if re.fullmatch(r'(?:义乌|华东|深圳|华南|美国|美西|美中|美东|东[12]区|西[12]区|中[12]区|不包税|包税)(?:交仓|仓)?(?:呢)?',text):return True return bool(len(text)<=60 and not (intent['country'] and intent['weight']) and any(intent.get(k) is not None for k in ('services','kinds','transport','weight','dims','cbm','tax','origin','zone','material','metal_scope','metal_chargeable_weight')))