Files
baiyue-logistics-chat/logistics_chat/quote_answers.py
T

99 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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
text=intent['text'];mode=intent.get('mode')
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=[]
metal=material_fee(book)
if metal is not None and re.search(r'铁制|钢制|铝制|铁铝|不锈钢',text):
lines.append(f'当前资料铁铝制品附加费为{metal:g}元/KG。')
if intent.get('weight'):lines.append(f'先按已提供重量{number(intent["weight"])}KG计算该项:{number(intent["weight"])}×{metal:g}={number(Decimal(intent["weight"])*metal)}元;计费重确定后继续核算。')
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