"""Fast/slow comparisons preserve shipping conditions and comparable rate groups.""" 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 logistics_chat.quote_engine import select_timed_rates from test_quotes import fixture_snapshot, frames def timing_snapshot(): book=fixture_snapshot() rates=[] for origin in ('义乌','华南'): for zone,offset in (('美西',0),('美中',1),('美东',2)): for service,price,eta in (('美森正班海派',8,'船开后15天左右'), ('合德海派',6,'船开后20-22天左右'), ('普船海派',4,'船开后25-30天左右')): rates.append({'service':service,'kind':'parcel','origin':origin,'zone':zone, 'unit':'kg','tax':'included','min_weight':'12','price':str(price+offset), 'eta':eta,'destinations':[],'source_sheet':'合成时效','source_cell':'A1'}) book['rates']=rates return book class QuoteTimingTests(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':'2'*64}) with self.app.app_context(): connection=db.connect() import_snapshot(connection,timing_snapshot(),activate=True) connection.commit();connection.close() def ask(self,question,cid=None): with patch('logistics_chat.ai_client.post_completion') as model: events=frames(self.client.post('/stream_chat',json={'msg':question,'conv_id':cid})) model.assert_not_called() self.assertEqual(events[-1],{'d':1},events) return events[0]['t'] def test_fastest_and_slowest_show_each_region_origin_and_the_correct_amount(self): text=self.ask('我想发一批货,一吨发发到美国走海运,最快的和最慢的价格分别是多少?') fast,slow=text.split('最慢参考方案') self.assertIn('最快参考方案',fast) self.assertIn('美西 8元/KG,1000KG = 8000元',fast) self.assertIn('美东 10元/KG,1000KG = 10000元',fast) self.assertIn('美森正班海派 · 华南',fast) self.assertNotIn('普船海派',fast) self.assertIn('美西 4元/KG,1000KG = 4000元',slow) self.assertIn('美东 6元/KG,1000KG = 6000元',slow) self.assertIn('船开后25-30天左右提取',slow) self.assertNotIn('合德海派',text) def test_short_followups_keep_weight_origin_zone_and_selected_comparison(self): cid=self.client.post('/api/new_chat').json['id'] self.ask('1000KG到美国走海运,最快和最慢价格分别多少',cid) for question in ('慢的呢?','义乌交仓','美东呢','请给我具体报价'): text=self.ask(question,cid) self.assertIn('美国 1000KG',text) self.assertIn('最慢参考方案',text) self.assertNotIn('最快参考方案',text) self.assertNotIn('华南',text) self.assertIn('1000KG = 6000元',text) text=self.ask('50KG呢',cid) self.assertIn('50KG = 300元',text) self.assertNotIn('1000KG',text) text=self.ask('全部渠道报价',cid) self.assertIn('美森正班海派',text) self.assertIn('合德海派',text) self.assertNotIn('最慢参考方案',text) text=self.ask('新的一批100KG发美国海运报价',cid) self.assertIn('美国 100KG',text) self.assertIn('华南',text) self.assertNotIn('最慢参考方案',text) def test_numeric_timing_groups_keep_destinations_tax_and_service_kinds_separate(self): base={'zone':'美西','kind':'parcel','unit':'kg','tax':'included'} rates=[{**base,'eta':'10-35天','service':'wide'}, {**base,'eta':'15-25天','service':'narrow'}, {**base,'zone':'美东','eta':'40-45天','service':'east'}, {**base,'tax':'excluded','eta':'20天','service':'excluded'}, {**base,'kind':'truck','eta':'50天','service':'truck'}, {**base,'eta':'未提供时效','service':'unknown'}] self.assertEqual({r['service'] for r in select_timed_rates(rates)}, {'wide','east','excluded','truck'}) self.assertEqual({r['service'] for r in select_timed_rates(rates,slowest=True)}, {'wide','east','excluded','truck'}) self.assertEqual(select_timed_rates([{**base,'eta':'若干天'}]),[]) def test_english_comparison_keeps_numbers_and_renders_both_speeds(self): text=self.ask('1000kg to USA by sea, fastest and slowest prices please') self.assertIn('Fastest reference options',text) self.assertIn('Slowest reference options',text) self.assertIn('1000KG = 4000 CNY',text) def test_different_regional_times_are_rendered_on_their_own_price_lines(self): with self.app.app_context(): connection=db.connect() connection.execute("UPDATE quote_rates SET eta='船开后18天左右' WHERE service='美森正班海派' AND zone='美东'") connection.commit();connection.close() text=self.ask('美国1000KG海运最快价格,义乌') self.assertIn('美东 10元/KG,1000KG = 10000元(船开后18天左右提取)',text) self.assertIn('美西 8元/KG,1000KG = 8000元',text) def test_unknown_times_do_not_become_a_fastest_or_slowest_promise(self): with self.app.app_context(): connection=db.connect() connection.execute("UPDATE quote_rates SET eta='若干天'") connection.commit();connection.close() text=self.ask('美国1000KG海运最快和最慢价格') self.assertIn('最快参考方案暂时无法确定',text) self.assertIn('最慢参考方案暂时无法确定',text) self.assertNotIn('1000KG =',text) def test_selected_route_survives_total_calculation_time_and_tax_questions(self): cid=self.client.post('/api/new_chat').json['id'] self.ask('美国1000KG义乌美东海运,便宜的报价',cid) for question in ('那一共多少钱','怎么算出来的'): text=self.ask(question,cid) self.assertIn('普船海派',text) self.assertIn('1000KG = 6000元',text) self.assertNotIn('美森正班海派',text) text=self.ask('多快能到',cid) self.assertIn('普船海派',text) self.assertIn('船开后25-30天左右提取',text) self.assertNotIn('美森正班海派',text) self.assertIn('标注包税',self.ask('含税吗',cid)) text=self.ask('具体多少',cid) self.assertIn('1000KG = 6000元',text) self.assertNotIn('美森正班海派',text) text=self.ask('全部渠道报价',cid) self.assertIn('美森正班海派',text) self.assertIn('普船海派',text) def test_polite_acknowledgements_keep_quote_conditions_and_selection(self): cid=self.client.post('/api/new_chat').json['id'] self.ask('美国30KG义乌美西最快海派报价',cid) for question in ('谢谢','好的,谢谢您','OK'): self.ask(question,cid) text=self.ask('改成50KG呢',cid) self.assertIn('美森正班海派 · 义乌',text) self.assertIn('50KG = 400元',text) self.assertNotIn('普船海派',text) text=self.ask('所有箱都是普通货',cid) self.assertIn('最快参考方案',text) self.assertNotIn('普船海派',text) def test_missing_weight_unit_stays_pending_until_an_explicit_unit_is_supplied(self): cid=self.client.post('/api/new_chat').json['id'] self.ask('美国1000KG义乌美西海运最快报价',cid) for question in ('重量改成800,其他不变','换成华南','谢谢','美东呢'): text=self.ask(question,cid) if question!='谢谢': self.assertIn('单位',text) self.assertNotIn('1000KG =',text) text=self.ask('单位是KG',cid) self.assertIn('美森正班海派 · 华南',text) self.assertIn('800KG = 8000元',text) self.assertNotIn('1000KG',text) self.assertIn('800KG = 8000元',self.ask('具体多少',cid)) def test_quote_resume_is_explicit_and_survives_later_field_edits(self): cid=self.client.post('/api/new_chat').json['id'] self.ask('美国30KG义乌美西最快报价',cid) self.ask('我有多少个订单',cid) text=self.ask('50KG呢',cid) self.assertNotIn('美森正班海派',text) text=self.ask('回到刚才的报价',cid) self.assertIn('30KG = 240元',text) self.assertNotIn('50KG',text) text=self.ask('换成华南',cid) self.assertIn('美森正班海派 · 华南',text) self.assertIn('30KG = 240元',text) def test_resume_does_not_recover_us_conditions_across_a_new_country_quote(self): cid=self.client.post('/api/new_chat').json['id'] self.ask('美国30KG义乌海运报价',cid) with self.app.app_context(): db.save_message(cid,'user','新的一票100KG发德国海运报价') db.save_message(cid,'assistant','请核对对应目的地的报价。') self.ask('我有多少个订单',cid) text=self.ask('回到刚才的报价',cid) self.assertIn('目的国家',text) self.assertNotIn('美国 30KG',text) self.assertNotIn('元/KG',text) def test_new_quote_with_a_topic_switch_becomes_the_next_followup_root(self): cid=self.client.post('/api/new_chat').json['id'] self.ask('美国30KG义乌美西最快报价',cid) self.ask('换个话题,美国50KG华南美东海运报价',cid) text=self.ask('最慢的呢',cid) self.assertIn('普船海派 · 华南',text) self.assertIn('50KG = 300元',text) self.assertNotIn('30KG',text) self.ask('另外问美国海运报价',cid) text=self.ask('20KG呢',cid) self.assertIn('美国 20KG',text) self.assertIn('义乌',text) self.assertNotIn('50KG',text) def test_explicit_selection_and_business_question_are_retained_independently(self): cid=self.client.post('/api/new_chat').json['id'] self.ask('美国1000KG义乌美东海运报价',cid) self.assertIn('标注包税',self.ask('最快的含税吗',cid)) text=self.ask('那一共多少钱',cid) self.assertIn('1000KG = 10000元',text) self.assertNotIn('普船海派',text) text=self.ask('最快多久能提取',cid) self.assertIn('15天左右提取',text) self.assertNotIn('普船海派',text) def test_missing_weight_units_before_a_destination_do_not_reuse_the_old_weight(self): cid=self.client.post('/api/new_chat').json['id'] self.ask('美国1000KG义乌美西最快海运报价',cid) self.assertIn('单位',self.ask('重量改成800美东,其他不变',cid)) text=self.ask('KG',cid) self.assertIn('800KG = 8000元',text) self.assertNotIn('1000KG',text) def test_all_channels_clears_the_named_service_and_keeps_origin_destination_weight(self): cid=self.client.post('/api/new_chat').json['id'] self.ask('美国1000KG义乌美东普船海派最便宜报价',cid) text=self.ask('全部渠道报价',cid) self.assertIn('美森正班海派',text) self.assertIn('合德海派',text) self.assertIn('普船海派',text) self.assertIn('1000KG = 10000元',text) self.assertNotIn('华南',text) self.assertNotIn('美西 ',text)