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

100 lines
5.6 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 .quote_context import merge_intent
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)
earlier_intents=[]
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
earlier_intents.append(earlier)
if not is_followup(previous,earlier):break
if earlier_intents:
state=earlier_intents[-1]
for update in list(reversed(earlier_intents[:-1]))+[intent]:
state=merge_intent(update,state)
intent=state
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.
from .shipping_faq import preparation_topic
if not (US.search(message) or QUOTE_WORDS.search(message) or preparation_topic(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':
if intent['country'] is None and intent.get('mode')=='preparation':
return {'t':'请先确认目的国家和所选渠道,我再为您核对交仓、标签或发货资料要求。'}
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
preparation=None
if intent.get('mode')=='preparation':
from .shipping_faq import answer_preparation
if intent['air_only']:return {'t':'当前美国资料未列明空运的交仓和发货要求,请确认具体空运渠道后联系人工核对。'}
preparation=answer_preparation(intent,book)
if preparation_topic(message)!='schedule' or not re.search(r'\d+\s*(?:KG|公斤|千克|克|吨|方|CBM)',intent['text'],re.I):
return {'t':preparation}
intent['mode']='cheapest' if re.search(r'最便宜|最划算|最低价',message) else 'quote'
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)
reply=render_rates(intent,book,destinations,fees)
return {'t':reply+('\n'+preparation if preparation else '')}
except (sqlite3.Error, ValueError, KeyError, TypeError, InvalidOperation):
return {'t':'报价数据暂时无法读取,请稍后重试。'}