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

86 lines
4.5 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.
"""Discard explicitly rejected quote conditions before parsing their replacements."""
import re
SERVICE_TERMS = ('美森正班','美森加班','美森','EXX','合德','普船','海派','海卡','海铁')
CHANGE_VERBS = r'增加|追加|加上|加|减少|减掉|减去|减|去掉'
ADJUSTMENT_QUESTION = '请提供调整后的总重和箱数,或逐箱重量明细,我再继续核算。'
VOLUME_QUESTION = '请确认调整后的总体积,或提供更新后的逐箱尺寸,我再核算体积重。'
def condition_edits(text):
rejected = r'(?:不是|不要|并非|不从|不走|别走|不用|不选|别选)\s*(?:(?:发|寄|运|送)(?:到|往)?|去|到|走|选|用|按)?\s*'
service = r'(?:(?:洛杉矶|纽约|芝加哥|休斯顿|萨凡纳)?(?:美森正班|美森加班|美森|EXX|合德|普船)(?:海派|海卡|海铁|全卡)?|海派|海卡|海铁)'
value = (service+r'|不包税|包税|最快|最慢|最便宜|'
r'(?:[A-Za-z]{2,4}[0-9][A-Za-z0-9]{0,2})(?![A-Za-z0-9])|'
r'深圳|华南|东莞|义乌|华东|美西|美中|美东|美国|洛杉矶|纽约|铁制品|钢制品|铝制品|铁铝制品|金属制品')
excluded = []
taxes = set()
clear_choice = False
def discard(match):
nonlocal clear_choice
if re.match(r'\s*(?:的)?(?:吗|嘛|么|[??])', text[match.end():]):
return match[0]
condition = match['value']
term = next((name for name in SERVICE_TERMS if name.lower() in condition.lower()), None)
if term:
excluded.append(term)
if condition in ('包税','不包税'):
taxes.add(condition)
if condition in ('最快','最慢','最便宜'):
clear_choice = True
return ' '
cleaned = re.sub(rejected+'(?P<value>'+value+')', discard, text, flags=re.I)
tax = 'included' if taxes == {'不包税'} else 'excluded' if taxes == {'包税'} else None
return {'text':cleaned, 'excluded_services':list(dict.fromkeys(excluded)) or None,
'tax':tax, 'tax_pending':len(taxes)>1, 'clear_choice':clear_choice}
def clean_conditions(text):
return condition_edits(text)['text']
def quantity_adjustment(text):
"""Separate a relative increment from an explicitly supplied new total."""
from .quote_intent import WEIGHT,kg,normalize
text=normalize(text)
prefix = re.match(r'^(?:(?:那(?:么)?|我(?:们)?(?:想|要)?|这次|现在|请帮我|帮我|麻烦|请|总重|重量|箱数|总体积|体积)[,,\s]*)*'
r'(?:再)?('+CHANGE_VERBS+r')\s*', text)
if not prefix:
unmatched=re.search(r'(?:'+CHANGE_VERBS+r')\s*-?\d+(?:\.\d+)?\s*(KG|公斤|千克|吨|箱|件|CBM|立方米|立方|方)',text,re.I)
if unmatched:
kind='volume' if unmatched[1].lower() in ('cbm','立方米','立方','方') else 'unknown'
return {'kind':kind,'delta':None,'text':text,'uncertain':True}
return None
subtract = prefix[1] in ('减少','减掉','减去','减','去掉')
sign = -1 if subtract else 1
weight = WEIGHT.match(text, prefix.end())
boxes = re.match(r'(\d+)\s*(?:个)?(?:箱|件)', text[prefix.end():])
volume = re.match(r'(-?\d+(?:\.\d+)?)\s*(?:CBM|立方米|立方|方)',text[prefix.end():],re.I)
if weight:
remainder = text[weight.end():]
return {'kind':'weight', 'delta':str(sign*kg(*weight.groups())),
'text':remainder, 'uncertain':kg(*weight.groups())<=0 or bool(re.search(CHANGE_VERBS+r'\s*\d',remainder))}
if boxes:
total_weight=re.search(r'(?:新总重|总重量|总重|合计重量|一共重)\s*'+WEIGHT.pattern.replace(r'(?<![\d.])',''),text,re.I)
total_boxes=re.search(r'(?:一共|总共|合计|总计|共)\s*(\d+)\s*(?:箱|件)|总箱数\s*(?:是|为)?\s*(\d+)',text)
return {'kind':'boxes', 'delta':sign*int(boxes[1]), 'text':text, 'uncertain':int(boxes[1])<=0,
'declared_weight':str(kg(*total_weight.groups())) if total_weight else None,
'declared_boxes':int(total_boxes[1] or total_boxes[2]) if total_boxes else None}
if volume:
from decimal import Decimal
amount=Decimal(volume[1])
return {'kind':'volume','delta':str(sign*amount),'text':text[prefix.end()+volume.end():],
'uncertain':amount<=0}
if re.match(r'-?\d', text[prefix.end():]):
return {'kind':'unknown', 'delta':None, 'text':text, 'uncertain':True}
return None
def weight_comparison(text):
return bool(re.search(r'会不会按|是不是按|是否按|向上进位|小数公斤|计费.*(?:进位|取整)|公斤.*(?:进位|取整)',text))