126 lines
11 KiB
Python
126 lines
11 KiB
Python
"""Answer business questions from the active local book, without old regional FAQs."""
|
||
|
||
from decimal import Decimal, ROUND_HALF_UP
|
||
import re
|
||
|
||
from .quote_fees import document, amount, material_fee, limit_fees
|
||
|
||
|
||
def answer_business(intent,book):
|
||
from .quote_engine import number,exact_number
|
||
|
||
text=intent['text'];mode=intent.get('mode')
|
||
if mode=='weight_basis':
|
||
return '运费按计费重量计算,不是只看净重。实重按含包装的毛重,与材积重取较大值;美国海派材积重为长×宽×高(cm)÷6000,每箱不足12KG按12KG计费。海卡包税按对应公斤规则,不包税按方方案以实际体积和毛重÷363取大;请按所选方案核算。'
|
||
if mode=='rounding':
|
||
weight=(f'若{exact_number(intent["weight"])}KG已是计费重,就按{exact_number(intent["weight"])}KG计算。' if intent.get('weight') else '')
|
||
return '美国海派按原小数计费,不向上取整;每箱不足12KG仍按12KG计费。'+weight+'计费重先按各箱实重与材积重取大,再按渠道对应的重量门槛匹配档位。海卡等其他方案的进位方式需另行核对。'
|
||
if mode=='mixing':
|
||
return '请分别提供各产品的品名、材质、数量、重量,以及是否带电、带磁、液体或粉末,并说明是否放在同一箱。需先核对各品类的承运与申报要求,再确认混装方案,不能仅凭“两种产品”判断可以混装。'
|
||
if mode=='metal_scope':
|
||
from .quote_fees import material_charge
|
||
from .quote_engine import quantity
|
||
per_kg=material_fee(book)
|
||
if per_kg is None:return '铁铝附加费只按铁铝货物的计费重收取,当前未匹配到费率,请联系人工核对。'
|
||
lines=[f'铁铝附加费为{per_kg:g}元/KG,只按铁铝货物的计费重收取;一票只有部分铁铝货物时,不按整票计费重加收。']
|
||
unit='cbm' if intent.get('tax')=='excluded' and intent.get('cbm') else 'kg'
|
||
rate={'kind':'truck' if intent.get('kinds')==['truck'] else 'parcel','unit':unit,'service':''}
|
||
details=material_charge(intent,quantity(intent,rate),unit,book)
|
||
lines+=details['items']+details['notes']
|
||
if intent.get('metal_scope')!='partial' and details['items'] and not intent.get('dims'):
|
||
lines.append('以上按当前提供的重量条件估算,仍需核对各箱尺寸及材积重。')
|
||
return '\n'.join(dict.fromkeys(lines))
|
||
if mode=='currency':
|
||
return '当前海卡、海派运价中的“元/KG”和“元/CBM”按人民币(RMB/CNY)计;一件代发等资料中标注“$”“USD”“美金”的费用按美元计。不同币种分别列明,未提供结算汇率时不自动换算。'
|
||
if mode=='insurance':
|
||
raw=document(book,('保险费','投保金额'))
|
||
percent=amount(raw,r'百分之\s*(\d+(?:\.\d+)?)')
|
||
minimum=amount(raw,r'min\s*(\d+(?:\.\d+)?)\s*USD')
|
||
multiplier=amount(raw,r'货值乘以\s*(\d+(?:\.\d+)?)')
|
||
if percent is None or minimum is None or multiplier is None:
|
||
return '当前资料未明确保险的完整计费条件,请提供货值和币种,由人工确认保险费。'
|
||
lines=[f'当前资料保险费率为保额的{percent:g}%,最低{minimum:g}USD;按货值投保时,投保金额为货值×{multiplier:g}。']
|
||
money=re.search(r'(-?\d+(?:\.\d+)?)\s*(万)?\s*(美元|美金|USD|人民币|元)',text,re.I)
|
||
if money and money.group(3).lower() in ('美元','美金','usd'):
|
||
value=Decimal(money.group(1))*(10000 if money.group(2) else 1)
|
||
if not 0<value<=1000000000:return '请提供大于0的有效货值或保额及币种。'
|
||
insured=value if re.search(r'保价|保额',text) and not re.search(r'货值',text) else value*multiplier
|
||
total=max(insured*percent/100,minimum).quantize(Decimal('.01'),rounding=ROUND_HALF_UP)
|
||
lines.append(f'按保额{number(insured)}USD计算:max({number(insured)}×{percent:g}%,{minimum:g})={number(total)}USD。')
|
||
else:
|
||
lines.append('请提供货值/保额及币种;人民币货值还需确认适用结算汇率。')
|
||
return '\n'.join(lines)
|
||
if mode=='fulfillment':
|
||
tokens=[t for t in ('20GP','40GP','45HQ','散箱','散托','贴标','仓储','仓租','分拣','SKU') if t.lower() in text.lower()]
|
||
if '仓租' in tokens:tokens.append('仓储')
|
||
if '卸货' in text and not tokens:tokens=['20GP','40GP','45HQ']
|
||
rows=[]
|
||
for d in book['documents']:
|
||
if d['sheet']!='一件代发收费' or not re.search(r'\$|USD|美金',d['content'],re.I):continue
|
||
score=sum(t.lower() in d['content'].lower() for t in tokens) if tokens else 1
|
||
if score:rows.append((score,d['content']))
|
||
rows=sorted(rows,key=lambda row:(-row[0],-(';' in row[1])))
|
||
facts=list(dict.fromkeys(row[1] for row in rows))[:4]
|
||
return '美国仓一件代发收费:\n'+'\n'.join('- '+r for r in facts) if facts else '当前资料中未匹配到该一件代发收费项,请补充服务项目、箱数或体积,由人工核实。'
|
||
if mode=='customs':
|
||
if intent.get('services') and '美森加班' in intent['services'] and document(book,('不可接一般贸易',),'美森加班海卡'):
|
||
return '当前资料中美森加班海卡不可接一般贸易,请由人工确认其他渠道,不能直接按该线路加报关费办理。'
|
||
if re.search(r'FDA',text,re.I):
|
||
raw=document(book,('FDA','申报费'))
|
||
fee=amount(raw,r'申报费[::]?\s*RMB\s*(\d+(?:\.\d+)?)')
|
||
if fee is not None:return f'当前资料中需做FDA Filing等申报的产品,申报费为{fee:g}人民币/票。请补充具体品名和渠道,确认是否接收及需要的资料。'
|
||
return '当前资料未匹配到FDA申报费,请提供具体品名和渠道,由人工确认是否接收及费用。'
|
||
raw=document(book,('海外进口清关','续页'))
|
||
extra=amount(raw,r'加收USD\s*(\d+(?:\.\d+)?)')
|
||
lines=[]
|
||
if re.search(r'续页|品名',text) and extra is not None:
|
||
lines.append(f'海外进口清关:每票超过5个品名的部分,按{extra:g}美元/个收取。')
|
||
count=re.search(r'(\d+)\s*个品名',text)
|
||
if count:lines.append(f'{count.group(1)}个品名的该项费用:max({count.group(1)}−5,0)×{extra:g}={max(int(count.group(1))-5,0)*extra:g}美元。')
|
||
lines.append('国内出口报关续项与海外进口清关分别收费;国内条款的品名计数方式需按票确认。')
|
||
raw_fee=document(book,('一般贸易出口报关费',))
|
||
declaration=amount(raw_fee,r'出口报关费[::]\s*RMB\s*(\d+(?:\.\d+)?)')
|
||
if declaration is not None:lines.append(f'一般贸易出口报关费为{declaration:g}人民币/票。')
|
||
if re.search(r'单独报关',text):
|
||
minimum=document(book,('单独报关货物','300KG'))
|
||
if minimum:lines.append('单独报关货物最低按300KG计费;还需确认所选渠道是否接受一般贸易。')
|
||
return '\n'.join(lines) or '请提供具体申报项目、品名数量及渠道,当前资料无法确定该项费用。'
|
||
if mode=='acceptance':
|
||
raw=document(book,('禁止运输的货物',))
|
||
if raw:
|
||
prohibited=next((t for t in ('纯电池','液体','粉末','食品','医疗用品','仿牌') if t in text),None)
|
||
if prohibited:return f'当前海派资料将{prohibited}列为不接收/禁止运输的类别,不能按普通货直接报价。若咨询其他渠道,请先由人工核实承运条件。'
|
||
if re.search(r'带电|电池',text):return '当前海派资料中内置电池需要提供产品图片、MSDS及海运运输鉴定报告后单独确认;纯电池不接收。请补充电池类型和产品信息,确认后再匹配运价。'
|
||
return '请提供品名、材质、是否带电及所选渠道,先核对承运条件,再计算对应费用。'
|
||
if mode=='fees':
|
||
lines=[]
|
||
if re.search(r'含税|含派送|全包|包不包',text):
|
||
lines.append('报价行标注“包税”或“不包税”的方案按各自税费方式计算;海派的派送还需核对目的仓/邮编,住宅、商业地址及偏远等附加费不能直接当作已全部包含。')
|
||
metal=material_fee(book)
|
||
if metal is not None and re.search(r'铁制|钢制|铝制|铁铝|不锈钢',text):
|
||
from .quote_fees import material_charge
|
||
from .quote_engine import quantity
|
||
unit='cbm' if intent.get('tax')=='excluded' and intent.get('cbm') else 'kg'
|
||
charge=quantity(intent,{'kind':'truck' if intent.get('kinds')==['truck'] else 'parcel','unit':unit,'service':''})
|
||
details=material_charge(intent,charge,unit,book)
|
||
lines.append(f'铁铝附加费为{metal:g}元/KG,只按铁铝货物的计费重收取。')
|
||
lines+=details['items']+details['notes']
|
||
if re.search(r'超重|超尺寸|超长',text):
|
||
raw=document(book,('超重收',))
|
||
fee=amount(raw,r'超重收\s*[++]?\s*(\d+(?:\.\d+)?)')
|
||
if fee is not None:lines.append(f'海派明确的普通超重附加费为{fee:g}元/件;同箱同时超重、超尺寸时只收适用的最高一项。')
|
||
limits=limit_fees(intent,book)
|
||
lines+=limits['items']+limits['notes']
|
||
if re.search(r'住宅|私人|商业地址|非亚马逊',text):
|
||
lines.append('住宅或商业等非亚马逊地址的派送附加费在原表中需单独询价;请提供完整邮编、地址类型和箱规,不能直接当作FBA仓价格。')
|
||
if not lines or re.search(r'包含|包括|还[要需]|什么费用',text):
|
||
raw=document(book,('偏远地址加收','超偏远'))
|
||
remote=amount(raw,r'(?<!超)偏远地址加收\s*(\d+(?:\.\d+)?)元/kg')
|
||
extended=amount(raw,r'超偏远地址加收\s*(\d+(?:\.\d+)?)元/kg')
|
||
if remote is not None and extended is not None:
|
||
lines.append(f'当前美国海派资料:偏远地址加{remote:g}元/KG,超偏远地址加{extended:g}元/KG,遥远地址单询;海派每箱不足12KG按12KG计费。')
|
||
lines.append('报价中的包税/不包税是不同费率口径;材质、报关申报、偏远及超重超尺寸等按条件单列,不代表所有服务费用都已包含。')
|
||
if metal is not None:lines.append(f'铁铝制品附加费{metal:g}元/KG,只按铁铝货物计费重收取;多项费用分别列明,同箱超限费用只取最高项。')
|
||
return '\n'.join(lines)
|
||
return None
|