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

100 lines
5.8 KiB
Python

"""Resolve same-conversation quote edits and return local facts without model I/O."""
from decimal import Decimal, InvalidOperation
import re
import sqlite3
from .db import connect
from .quote_data import load_book, postcode_fee
from .quote_engine import US, QUOTE_WORDS, parse_intent, is_followup, variants, render_rates
def intent_with_context(message, conv_id, book):
from copy import deepcopy
codes = {row['code'] for row in book['warehouses']}
codes.update(value.upper() for rate in book['rates'] for value in rate['destinations'])
intent = parse_intent(message,codes)
def destination_reset(value):
if value.get('warehouse') or value.get('unknown_warehouses'):return {'postal','zone','zones'}
if value.get('postal'):return {'warehouse','warehouses','unknown_warehouses','zone','zones'}
if value.get('zone'):return {'warehouse','warehouses','unknown_warehouses','postal'}
return set()
excluded=destination_reset(intent)
selected_destination=bool(excluded)
if selected_destination and not intent.get('unknown_warehouses'):excluded.add('unknown_warehouses')
if intent['transport'] or intent['kinds'] or intent['services']:excluded.update(('air_only','air_requested'))
if intent['air_only'] or intent['tax']=='excluded':excluded.add('kinds')
if intent.get('weight') is not None and not intent.get('packages'):excluded.add('packages')
explicit_dims=bool(intent['dims'])
if conv_id and is_followup(message,intent):
connection=connect()
try:
recent=connection.execute("SELECT content FROM messages WHERE conv_id=? AND role='user' ORDER BY id DESC LIMIT 10",(conv_id,)).fetchall()
finally:connection.close()
for (previous,) in recent:
if re.search(r'理赔|赔偿|破损|丢失|退件|投诉|查单|轨迹|签收',previous):break
earlier=parse_intent(previous,codes)
if earlier['country']=='other':break
if not (QUOTE_WORDS.search(previous) or US.search(previous) or is_followup(previous,earlier) or
any(earlier.get(key) for key in ('weight','dims','cbm','warehouse','postal'))):break
if not selected_destination and destination_reset(earlier):
excluded.update(destination_reset(earlier));selected_destination=True
if intent['dims'] is None and earlier['dims']:
intent['dims']=earlier['dims'];intent['dim_unit']=earlier['dim_unit']
for key,value in earlier.items():
if key in ('mode','text','errors','dim_unit','dims'):continue
if intent.get(key) is None and key not in excluded:intent[key]=deepcopy(value)
if not is_followup(previous,earlier):break
if explicit_dims and intent.get('packages'):
for piece in intent['packages']:
piece['dims']=intent['dims'];piece['dim_unit']=intent['dim_unit']
if (intent['warehouse'] or intent.get('unknown_warehouses')) and intent['country'] is None:intent['country']='US'
return intent
def answer_quote(message, conv_id):
if re.search(r'理赔|赔偿|破损|丢失|退件|投诉|查单|轨迹|签收', message):
return None
# Avoid a new read for ordinary chat; short followups need their local context.
if not (US.search(message) or QUOTE_WORDS.search(message) or conv_id or
re.search(r'(?<![A-Za-z0-9])[A-Za-z]{2,4}[0-9](?![A-Za-z0-9])', message)):
return None
try:
book = load_book()
if not book:
return None
intent = intent_with_context(message, conv_id, book)
if intent['country'] != 'US':
return None
if intent.get('errors'):
return {'t':'\n'.join(intent['errors'])}
if intent.get('unknown_warehouses'):
return {'t':'未找到目的仓码 '+ '、'.join(intent['unknown_warehouses'])+',请核对仓码,或提供5位美国邮编继续查询。'}
if not (QUOTE_WORDS.search(message) or is_followup(message,intent) or
any(intent.get(key) for key in ('weight','dims','cbm','warehouse','postal'))):
return None
for key in ('weight','cbm'):
if intent[key] is not None and not (Decimal(0) < Decimal(intent[key]) <= Decimal(1000000)):
return {'t':'请提供大于 0 的有效重量或体积。'}
if intent['boxes'] is not None and not 0 < intent['boxes'] <= 10000:
return {'t':'请提供有效的货物箱数。'}
dimensions=[intent['dims']] if intent['dims'] else []
dimensions += [piece['dims'] for piece in intent.get('packages') or [] if piece.get('dims')]
if any(not 0 < value <= 100000 for dims in dimensions for value in dims):
return {'t':'请提供大于 0 的有效长宽高。'}
from .quote_answers import answer_business
business=answer_business(intent,book)
if business is not None:return {'t':business}
if intent['air_only']:
return {'t':'当前美国报价资料包含美森、EXX、合德及普船的海卡、海铁和海派。发我目的仓码或邮编、重量,可以列出这些线路的价格;空运费率当前资料没有列出。'}
destinations = variants(intent,book)
fees = []
for destination in destinations:
postal = destination['postal']
if postal and (postal[:3] in ('006','007','008','009','966','967','968','969','995','996','997','998','999') or postal=='89124'):
return {'t':f'当前海派资料不接收邮编 {postal} 的地址,请提供其他目的仓码或联系人工确认可用线路。'}
fees.append(postcode_fee(book['id'],postal) if postal else None)
return {'t':render_rates(intent,book,destinations,fees)}
except (sqlite3.Error, ValueError, KeyError, TypeError, InvalidOperation):
return {'t':'报价数据暂时无法读取,请稍后重试。'}