修复数量增减及前后票连续追问

This commit is contained in:
2026-11-06 13:35:02 +08:00
parent bcfccc7013
commit 5fa6471929
14 changed files with 696 additions and 38 deletions
+214
View File
@@ -0,0 +1,214 @@
"""Relative quantity and condition edits keep the previously confirmed quote basis."""
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from logistics_chat import create_app,db
from logistics_chat.quote_data import import_snapshot
from test_quote_timing import timing_snapshot
from test_quotes import frames
class QuoteChangeTests(unittest.TestCase):
def setUp(self):
folder=tempfile.TemporaryDirectory();self.addCleanup(folder.cleanup)
root=Path(folder.name)
self.app=create_app({'TESTING':True,'DATABASE':root/'test.db','UPLOAD_DIR':root/'uploads',
'BACKUP_DIR':root/'backups','LOG_DIR':root/'logs','DEEPSEEK_API_KEY':'','RATE_LIMIT_PER_MIN':1000})
self.client=self.app.test_client()
self.client.post('/api/visitor/session',json={'key':'5'*64})
self.cid=self.client.post('/api/new_chat').json['id']
with self.app.app_context():
c=db.connect();import_snapshot(c,timing_snapshot(),activate=True);c.commit();c.close()
def ask(self,question):
with patch('logistics_chat.ai_client.post_completion') as model:
events=frames(self.client.post('/stream_chat',json={'msg':question,'conv_id':self.cid}))
model.assert_not_called()
self.assertEqual(events[-1],{'d':1},events)
self.assertFalse(any(event.get('e') for event in events),events)
return events[0]['t']
def test_weight_increments_are_added_and_absolute_changes_replace_the_total(self):
self.ask('美国1000KG义乌美东普船海派报价')
self.assertIn('1100KG = 6600元',self.ask('再加100KG,其他不变'))
self.assertIn('900KG = 5400元',self.ask('再减掉200KG'))
self.assertIn('900KG = 5400元',self.ask('那一共多少钱'))
self.assertIn('800KG = 4800元',self.ask('减到800KG'))
self.assertIn('900KG = 5400元',self.ask('增加0.1吨,总重900KG'))
def test_invalid_or_unitless_changes_stay_pending_until_a_new_total_is_confirmed(self):
self.ask('美国30KG义乌美西普船海派报价')
self.assertIn('调整后的总重需要大于0',self.ask('减去40KG'))
text=self.ask('换成美东')
self.assertIn('请提供调整后的总重',text)
self.assertNotIn('KG =',text)
self.assertIn('20KG = 120元',self.ask('总重20KG'))
self.assertIn('请提供调整后的总重',self.ask('再加100'))
self.assertNotIn('KG =',self.ask('具体多少'))
self.assertIn('50KG = 300元',self.ask('新总重50KG'))
def test_relative_quantity_without_a_confirmed_starting_value_requests_a_total(self):
self.assertIn('原来的总重',self.ask('再加100KG'))
self.assertIn('调整后的总重',self.ask('美国普船海派'))
self.assertIn('200KG = 800元',self.ask('总重200KG,义乌美西'))
def test_uniform_cartons_add_and_remove_without_losing_the_previous_cartons(self):
self.ask('美国10箱,每箱20KG,义乌美西普船海派报价')
self.assertIn('240KG = 960元',self.ask('再加2箱,每箱20KG'))
text=self.ask('减少1箱')
self.assertIn('220KG = 880元',text)
self.assertIn('共11箱',text)
self.assertIn('220KG = 880元',self.ask('那一共呢'))
def test_added_carton_dimensions_apply_to_the_added_cartons_only(self):
self.ask('美国1箱,每箱20KG,30x30x30cm,义乌美西普船海派报价')
text=self.ask('再加1箱,每箱10KG,60x50x40cm')
self.assertIn('40KG = 160元',text)
self.assertNotIn('50KG =',text)
def test_each_carton_minimum_remains_in_effect_after_addition(self):
self.ask('美国2箱,每箱5KG,义乌美西普船海派报价')
self.assertIn('36KG = 144元',self.ask('再加1箱,每箱5KG'))
def test_declared_new_totals_are_validated_after_a_carton_change(self):
self.ask('美国10箱每箱20KG,义乌美西普船海派报价')
self.assertIn('240KG = 960元',self.ask('再加2箱每箱20KG,总重240KG'))
self.assertIn('增减箱数推算的总重',self.ask('再加1箱每箱20KG,总重300KG'))
self.assertNotIn('KG =',self.ask('换成华南'))
self.assertIn('300KG',self.ask('总重300KG,共13箱'))
def test_heterogeneous_carton_removal_requires_the_updated_breakdown(self):
self.ask('美国1箱10KG,1箱30KG,义乌美西普船海派报价')
self.assertIn('不同箱规',self.ask('减少1箱'))
self.assertNotIn('KG =',self.ask('具体多少钱'))
self.assertIn('30KG = 120元',self.ask('现在1箱30KG'))
def test_rejected_services_remain_excluded_until_explicitly_selected_again(self):
self.ask('美国1000KG义乌美东海运最便宜报价')
text=self.ask('不走普船了,改成美森正班')
self.assertIn('1000KG = 10000元',text)
self.assertNotIn('普船海派',text)
text=self.ask('不要美森,走普船')
self.assertIn('1000KG = 6000元',text)
self.assertNotIn('美森',text)
self.ask('不走普船')
text=self.ask('改成华南')
self.assertNotIn('普船海派',text)
self.assertNotIn('美森',text)
self.assertIn('合德海派',text)
self.assertIn('普船海派',self.ask('那还是普船吧'))
self.assertIn('美森正班海派',self.ask('全部渠道报价'))
def test_tax_corrections_and_pending_conflicts_are_not_reversed(self):
self.ask('美国1000KG义乌美东海派报价')
self.assertIn('1000KG = 6000元',self.ask('不要不包税,我要包税'))
self.assertIn('没有匹配',self.ask('不是包税,是不包税'))
self.assertIn('1000KG = 6000元',self.ask('那还是包税吧'))
self.assertIn('1000KG = 6000元',self.ask('不是包税吗?'))
self.assertIn('明确选择包税',self.ask('不要包税,也不要不包税'))
self.assertNotIn('KG =',self.ask('换成华南'))
self.assertIn('1000KG = 6000元',self.ask('要包税'))
def test_new_batch_with_rejections_resets_old_shipping_conditions(self):
self.ask('美国1000KG义乌美东普船海派报价')
text=self.ask('新的一批美国30KG海派报价,不走普船')
self.assertIn('美国 30KG',text)
self.assertIn('华南',text)
self.assertIn('美西',text)
self.assertNotIn('普船海派',text)
self.assertNotIn('1000KG',text)
def test_calendar_questions_preserve_route_and_request_the_missing_dates(self):
self.ask('美国1000KG义乌美东普船海派报价')
for question in ('什么时候能到','大概哪天能收到'):
text=self.ask(question)
self.assertIn('25-30天左右提取',text)
self.assertIn('预计开船日期',text)
text=self.ask('快一点呢')
self.assertIn('美森正班海派',text)
self.assertNotIn('普船海派',text)
text=self.ask('那比慢的贵多少')
self.assertIn('运费差额为4000元',text)
def test_ambiguous_plan_reference_keeps_context_for_a_named_selection(self):
self.ask('美国1000KG海运最快和最慢报价')
self.assertIn('1000KG',self.ask('这个多少钱'))
self.assertIn('请指定方案名称',self.ask('第二个方案呢'))
self.assertIn('1000KG = 4000元',self.ask('便宜的那个'))
self.assertIn('1000KG = 4000元',self.ask('一共呢'))
def test_tied_speed_costs_produce_a_range_without_mixing_tax_or_units(self):
from logistics_chat.quote_comparison import speed_difference_lines
from logistics_chat.quote_data import load_book
from logistics_chat.quote_intent import parse_intent
base={'origin':'义乌','zone':'美东','kind':'parcel','unit':'kg','tax':'included'}
rates=[{**base,'service':name,'price':price,'eta':eta} for name,price,eta in (
('快线A','8','12-15天'),('快线B','10','12-15天'),
('慢线A','5','30-35天'),('慢线B','6','30-35天'))]
rates.append({**base,'service':'自税线','tax':'excluded','price':'999','eta':'20天'})
rates.append({**base,'service':'按方线','unit':'cbm','price':'999','eta':'20天'})
with self.app.app_context():
book=load_book()
intent=parse_intent('美国1000KG义乌美东海派报价',set())
lines=speed_difference_lines(intent,book,rates,None)
self.assertTrue(any(' · 包税 · ' in line and '2000至5000元' in line for line in lines),lines)
self.assertTrue(any('不包税' in line and '差额为0元' in line for line in lines),lines)
self.assertEqual(len(lines),2)
self.assertEqual(speed_difference_lines(intent,book,[{**rates[0],'eta':'未提供时效'}],None),[])
def test_polite_relative_edits_and_unrecognized_adjustments_do_not_replace_the_total(self):
self.ask('美国1000KG义乌美东普船海派报价')
self.assertIn('1100KG = 6600元',self.ask('那我再加一百公斤'))
self.assertIn('900KG = 5400元',self.ask('请帮我减少200KG'))
self.assertIn('调整后的总重',self.ask('这批货后面增加100KG'))
self.assertNotIn('KG =',self.ask('具体多少钱'))
def test_difference_uses_the_displayed_two_decimal_costs(self):
from logistics_chat.quote_comparison import speed_difference_lines
from logistics_chat.quote_data import load_book
from logistics_chat.quote_intent import parse_intent
base={'origin':'义乌','zone':'美东','kind':'parcel','unit':'kg','tax':'included'}
rates=[{**base,'service':'快线','price':'6.4','eta':'12天'},
{**base,'service':'慢线','price':'4.3','eta':'30天'}]
with self.app.app_context():
intent=parse_intent('美国12.555KG义乌美东海派报价',set())
text='\n'.join(speed_difference_lines(intent,load_book(),rates,None))
self.assertIn('差额为26.36元',text)
def test_difference_shows_both_speeds_for_each_origin_used_in_the_calculation(self):
with self.app.app_context():
c=db.connect()
c.execute("UPDATE quote_rates SET eta='船开后16天左右' WHERE service='美森正班海派' AND origin='华南'")
c.commit();c.close()
text=self.ask('美国1000KG美东海派最快最慢差价')
self.assertIn('美森正班海派 · 义乌',text)
self.assertIn('美森正班海派 · 华南',text)
self.assertEqual(text.count('运费差额为4000元'),2)
def test_volume_increments_keep_the_confirmed_total_and_units(self):
self.ask('美国2CBM义乌美东普船海派报价')
text=self.ask('再加1方')
self.assertIn('总体积为3CBM',text)
self.assertIn('3000元',text)
text=self.ask('总体积减少0.5CBM')
self.assertIn('总体积为2.5CBM',text)
self.assertIn('2500元',text)
def test_carton_changes_with_an_aggregate_volume_wait_for_the_updated_volume(self):
self.ask('美国2箱每箱20KG,1CBM,义乌美东普船海派报价')
self.assertIn('调整后的总体积',self.ask('再加1箱每箱20KG'))
text=self.ask('总重60KG')
self.assertIn('调整后的总体积',text)
self.assertNotIn('KG =',text)
text=self.ask('总体积1.5CBM')
self.assertIn('1500元',text)
def test_unrecognized_volume_increment_is_clarified_without_using_it_as_a_new_total(self):
self.ask('美国2CBM义乌美东普船海派报价')
text=self.ask('这批货后面增加1方')
self.assertIn('调整后的总体积',text)
self.assertNotIn('元/KG',text)
self.assertIn('3000元',self.ask('总体积3CBM'))