75 lines
5.2 KiB
Python
75 lines
5.2 KiB
Python
"""Recognize explicit, whitelisted order summary and list filters."""
|
||
|
||
import re
|
||
|
||
from .order_tracking import NODE_LABELS
|
||
|
||
|
||
NODE_PHRASES = (
|
||
('DOMESTIC_RECEIVED',r'已入国内仓|已入仓'),
|
||
('LOADING_COMPLETED',r'已完成装柜|已装柜'),
|
||
('DEPARTED_WAREHOUSE',r'已离开国内仓库|已离仓|已出库'),
|
||
('AT_ORIGIN_PORT',r'重箱已进入起运港或码头|已到起运港|已进起运港'),
|
||
('LOADED_ON_VESSEL',r'已装船'),
|
||
('DEPARTED_ORIGIN_PORT',r'船舶已离开起运港|已开船|开船'),
|
||
('IN_TRANSIT',r'海运途中|运输中|在途|海上'),
|
||
('ARRIVED_DESTINATION_PORT',r'船舶已到目的港|已到目的港|已到港|到港'),
|
||
('DISCHARGED_FROM_VESSEL',r'已卸船、未清关|卸船未清关|已卸船|卸船'),
|
||
('PICKUP_READY',r'已清关、可提柜|已清关|清关完成|可提柜'),
|
||
('PICKED_UP',r'已提柜|提柜'),
|
||
('EMPTY_RETURNED',r'已还柜|还柜'),
|
||
('NO_ACTUAL',r'没有实际轨迹|暂无实际节点|没有轨迹|无轨迹'),
|
||
('MIXED',r'多柜进度不一致|多柜进度不同'),
|
||
)
|
||
STAGE_LABELS = {code:labels[1] for code,labels in NODE_LABELS.items()}
|
||
STAGE_LABELS.update(NO_ACTUAL='暂无实际节点',MIXED='多柜进度不同')
|
||
STATUSES = (('头程订单已完成','COMPLETED'),('已完成','COMPLETED'),('履约中','PROCESSING'),
|
||
('处理中','PROCESSING'),('已取消','CANCELLED'),('审核通过','APPROVED'),
|
||
('审核驳回','REJECTED'),('已接单','ACCEPTED'),('已提交','SUBMITTED'),('草稿','DRAFT'),
|
||
('processing','PROCESSING'))
|
||
NOUN = r'订单|运单|入仓码|入仓号|\borders?\b|\bshipments?\b'
|
||
COUNT = r'多少|几(?:个|票|条|张|笔)|总数|数量|总量|统计|数一下|(?:订单|运单)数(?!量)|\bhow\s+many\b|\bcount\b|\bnumber\b|\btotal\b'
|
||
|
||
|
||
def parse_query(message,listing=False):
|
||
message=re.sub(r'^(?:(?:你好|您好)[,,!!。\s]*)+','',message.strip())
|
||
message=re.sub(r'^(?:或者|那么|那)[,,\s]*','',message)
|
||
message=re.sub(r'^(?:当前|目前|现在)[,,\s]+','',message)
|
||
has_noun=bool(re.search(NOUN,message,re.I))
|
||
condition=r'异常|\b(?:exception|abnormal)\b|'+'|'.join(pattern for _,pattern in NODE_PHRASES)+'|'+'|'.join(re.escape(phrase) for phrase,_ in STATUSES)
|
||
if not has_noun and (listing or not re.search(COUNT,message,re.I) or not re.search(condition,message,re.I)):
|
||
return None
|
||
if not listing and not (re.search(COUNT,message,re.I) or re.search(r'分布|(?:都|分别).*到哪',message)):return None
|
||
if re.search(r'公斤|千克|\bkg\b|重量|毛重|体积|件数|箱数|运费|费用|价格|多少钱',message,re.I):return None
|
||
if not listing:
|
||
parts=[part.strip() for part in re.split(r'[,,。;;??]+|以及|还有|并且|和|\band\b',message,flags=re.I) if part.strip()]
|
||
if len(parts)>1 and all(re.search(COUNT,part,re.I) for part in parts):
|
||
queries=[parse_query(part if re.search(NOUN,part,re.I) else part+'订单') for part in parts[:5]]
|
||
for question,query in zip(parts,queries):query['question']=question
|
||
return {'summaries':queries,'unsupported':len(parts)>5}
|
||
remaining=message
|
||
statuses=[];nodes=[]
|
||
# Unknown/negated logistics conditions must never silently become a total.
|
||
unsupported=bool(re.search(r'未到港|没到港|还没|未开船|未发货|未签收|已签收|已送达|曾经|曾到|到过|正常订单|无异常',remaining))
|
||
for code,pattern in NODE_PHRASES:
|
||
if re.search(pattern,remaining):
|
||
nodes.append(code);remaining=re.sub(pattern,'',remaining)
|
||
for phrase,status in STATUSES:
|
||
if re.search(re.escape(phrase),remaining,re.I):
|
||
statuses.append(status);remaining=re.sub(re.escape(phrase),'',remaining,flags=re.I)
|
||
exception=bool(re.search(r'异常|\b(?:exception|abnormal)\b',remaining,re.I))
|
||
remaining=re.sub(r'物流异常|异常|\b(?:exception|abnormal)\b','',remaining,flags=re.I)
|
||
grouped=not nodes and bool(re.search(r'轨迹|节点|阶段|到哪|在哪|\btracking (?:stages?|nodes?)\b',remaining,re.I))
|
||
remaining=re.sub(r'物流轨迹|物流节点|当前节点|轨迹|节点|阶段|到哪里|到哪|在哪里|在哪儿|在哪|各自|各个|每个|分别|分布|各|都|了|\btracking (?:stages?|nodes?)\b', '',remaining,flags=re.I)
|
||
remaining=re.sub(COUNT+'|'+NOUN,'',remaining,flags=re.I)
|
||
remaining=re.sub(r'其中|当前账号|当前账户|账号下|账户下|名下|可查询|能查到|可以查到|目前|现在|当前|一共|总共|合计|总计|'
|
||
r'请问|麻烦|帮我|想知道|想查|想看|请|查询|查一下|查下|查查|看一下|看看|看下|一下|下|列出|列表|显示|全部|所有|共有|我的|我|能|有|的|个|票|条|张|笔|呢|吗|呀|啊|共|'
|
||
r'\b(?:list|what|is|of|do|i|have|are|there|my|in|all|the|currently|at|each)\b|[\s,,。.!!??、::]','',remaining,flags=re.I)
|
||
return {'status':statuses[0] if statuses else None,'stage':nodes[0] if nodes else None,
|
||
'exception':exception,'grouped':grouped,
|
||
'unsupported':bool(unsupported or remaining or len(set(statuses))>1 or len(set(nodes))>1)}
|
||
|
||
|
||
def filters(intent):
|
||
return {key:intent.get(key) for key in ('status','stage','exception')}
|