海运补问保留贸易条款;陆运多车型价表按列回填,车型码不进起始地

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-10-29 10:53:30 +08:00
co-authored by Cursor
parent 56ba8e93b5
commit eca0a7ad93
10 changed files with 809 additions and 18 deletions
+15
View File
@@ -335,6 +335,21 @@ class OralExtractTests(unittest.TestCase):
self.assertIn("重量(KG):1.5KG", shown)
self.assertNotIn("1.5KGS", shown)
def test_broken_decimal_space_keeps_full_weight_volume(self) -> None:
"""小数点后空格不得只抽到小数段:578. 5KG/1. 03CBM → 578.5KG/1.03CBM。"""
from agent.schema.field_validate import harvest_oral_measures
text = "1个木箱/578. 5KG/1. 03CBM空运普货1箱托盘到MAA"
facts = harvest_oral_measures(text, {})
self.assertEqual(facts.get("毛重"), "578.5KG")
self.assertEqual(facts.get("体积"), "1.03CBM")
self.assertNotEqual(facts.get("毛重"), "5KG")
self.assertNotEqual(facts.get("体积"), "03CBM")
fullwidth = harvest_oral_measures("茶叶,578. 5KG,1。03CBM,托盘", {})
self.assertEqual(fullwidth.get("毛重"), "578.5KG")
self.assertEqual(fullwidth.get("体积"), "1.03CBM")
def test_dimension_text_is_volume_without_multiplying(self) -> None:
from agent.schema.field_validate import harvest_oral_measures
@@ -360,6 +360,43 @@ class LandGroupCollabTests(unittest.TestCase):
"只有这张单的销售能调价。航线请继续报价,价格未改。",
)
def test_land_multi_vehicle_rates_attach_segment(self) -> None:
"""多车型价挂到 landSegments,出单才能按车型列写入;OD 用工单事实。"""
from agent.llm.extract_text import extract_product_quote
from agent.policy.sea_group_ops import _attach_land_vehicle_rates_segment
quote = extract_product_quote(
"太原富士康--春疆\n车型:\n5T/8T:13000\n10T:14000\n40HQ/45HQ:19500\n含0点税",
business_line="LAND",
)
view = {
"facts": {
"起运港": "太原富士康",
"目的港": "越南北寧市南山工業區",
}
}
out = _attach_land_vehicle_rates_segment(quote, view)
segs = list(out.get("landSegments") or [])
self.assertEqual(len(segs), 1)
seg = segs[0]
self.assertEqual(str(seg.get("origin") or ""), "太原富士康")
self.assertEqual(str(seg.get("destination") or ""), "越南北寧市南山工業區")
rates = dict(seg.get("vehicleRates") or {})
self.assertEqual(rates.get("5T"), "13000")
self.assertEqual(rates.get("45HQ"), "19500")
self.assertEqual(str(seg.get("remark") or ""), "含0点税")
self.assertFalse(str(seg.get("salePrice") or "").strip())
self.assertEqual(list(out.get("fee_rows") or []), [])
# 收货地址归一键也能取到目的地
out2 = _attach_land_vehicle_rates_segment(
quote,
{"facts": {"提货地址": "太原富士康", "收货地址": "越南北寧市南山工業區"}},
)
self.assertEqual(
str((out2.get("landSegments") or [{}])[0].get("destination") or ""),
"越南北寧市南山工業區",
)
def test_land_bare_price_defaults_to_land_fee(self) -> None:
"""陆运「价格为1RMB/KG」无费用名时默认陆运费,禁止落成海运费(WO118)。"""
from agent.llm.extract_text import extract_product_quote
@@ -518,6 +518,41 @@ class SeaTextInquiryTests(unittest.TestCase):
self.assertNotIn("1.贸易条款(必填)", body)
self.assertNotEqual(phase, "need_mode")
def test_clarify_qty_keeps_gangdaogang_and_accepts_full_box_qty(self) -> None:
"""已有港到港时只补「装满」货量:不再追问货物数量和贸易条款。"""
inq = "东莞到新西兰利特尔顿港和奥克兰港!报一下40HQ和20GP的价格,产品是装奶粉的罐子,条款港到港"
facts = {
"起运港": "东莞",
"目的港": "利特尔顿港",
"品名": "奶粉罐子",
"整柜或拼柜": "整柜",
"箱型箱量": "40HQ、20GP",
"贸易条款": "港到港",
"运输分类": "Port to Port",
}
phase = self.flow.on_text(
sender_id="s-full-box",
text=inq,
reply=self._reply,
injected_facts=facts,
injected_mode="SEA",
)
self.assertEqual(phase, "clarify")
self.replies.clear()
phase = self.flow.on_text(
sender_id="s-full-box",
text="1.货物数量(必填):40HQ、20GP 装满,报价2个价格\n(如:20吨)",
reply=self._reply,
)
self.assertEqual(phase, "wait_confirm")
sess = self.flow.session_of("s-full-box")
self.assertEqual(sess.facts.get("贸易条款"), "港到港")
self.assertIn("装满", str(sess.facts.get("货量") or ""))
body = "\n".join(self.replies)
self.assertIn("贸易条款(必填):港到港", body)
self.assertNotIn("1.货物数量(必填):\n", body)
self.assertNotIn("贸易条款(必填):\n(如:门到门、港到港)", body)
def test_harvest_class_from_reply_then_quotes(self) -> None:
first = dict(COMPLETE_SEA)
first.pop("运输分类")
@@ -1341,6 +1376,45 @@ class SeaGroupCollabFieldTests(unittest.TestCase):
self.assertEqual(str(rows["海运费"].get("amount") or ""), "100.00")
self.assertEqual(str(rows["燃油附加费"].get("amount") or ""), "15.00")
def test_land_multi_vehicle_rates_not_hq_fee(self) -> None:
"""陆运整车多车型价:不能抽成 HQ=19500;要拆成 5T/8T/10T/40HQ/45HQ 价表。"""
from agent.llm.extract_text import extract_product_quote
quote = extract_product_quote(
"太原富士康--春疆\n车型:\n5T/8T:13000\n10T:14000\n40HQ/45HQ:19500\n 含0点税",
business_line="LAND",
transport_mode="陆运",
)
rates = dict(quote.get("vehicleRates") or {})
self.assertEqual(rates.get("5T"), "13000")
self.assertEqual(rates.get("8T"), "13000")
self.assertEqual(rates.get("10T"), "14000")
self.assertEqual(rates.get("40HQ"), "19500")
self.assertEqual(rates.get("45HQ"), "19500")
self.assertEqual(str(quote.get("remark") or ""), "含0点税")
names = {str(x.get("name") or "") for x in (quote.get("fee_rows") or [])}
self.assertNotIn("HQ", names)
self.assertNotIn("5T", names)
self.assertNotIn("40HQ", names)
self.assertIn("陆运费", names)
veh_names = {str(x.get("name") or "") for x in (quote.get("vehicle_fee_rows") or [])}
self.assertIn("5T", veh_names)
self.assertIn("40HQ", veh_names)
def test_land_multi_vehicle_40_slash_45hq(self) -> None:
"""口语 40/45HQ 19500 要拆成 40HQ+45HQ,不能只认 45HQ。"""
from agent.llm.extract_text import extract_product_quote
quote = extract_product_quote(
"车型:5T/8T:13000 10T:14000 40/45HQ 19500 含0点 国六车提货",
business_line="LAND",
)
rates = dict(quote.get("vehicleRates") or {})
self.assertEqual(rates.get("40HQ"), "19500")
self.assertEqual(rates.get("45HQ"), "19500")
self.assertEqual(rates.get("5T"), "13000")
self.assertIn("含0点", str(quote.get("remark") or ""))
def test_land_weight_band_unit_price_and_rates(self) -> None:
"""陆运集拼:2-500KG 单价 + 燃油附加费率/税率,不能把档下限 2 当成燃油附加费。"""
from agent.llm.extract_text import extract_product_quote
@@ -1379,6 +1453,24 @@ class SeaGroupCollabFieldTests(unittest.TestCase):
self.assertEqual(str(rows["空运费"].get("quantity") or ""), "100")
self.assertEqual(str(quote.get("total") or ""), "199.0")
def test_air_unit_qty_without_amount_multiplies(self) -> None:
"""WO028:只写单价+数量、不写金额时,行金额必须是单价×数量,不能落成单价。"""
from agent.llm.extract_text import extract_product_quote
quote = extract_product_quote(
"空运费单价CNY17.00 数量3500@智价通AIQ\n备注:1:250+ 吨上\nWO202610230028",
business_line="AIR",
transport_mode="空运",
)
rows = {str(x.get("name") or ""): x for x in (quote.get("fee_rows") or [])}
self.assertIn("空运费", rows)
self.assertEqual(str(rows["空运费"].get("unit_price") or ""), "17.00")
self.assertEqual(str(rows["空运费"].get("quantity") or ""), "3500")
self.assertEqual(str(rows["空运费"].get("amount") or ""), "59500")
self.assertEqual(str(rows["空运费"].get("currency") or ""), "CNY")
self.assertEqual(str(rows["空运费"].get("remark") or ""), "1:250+ 吨上")
self.assertEqual(str(quote.get("total") or ""), "59500.0")
def test_air_fee_priced_with_unit_between_name_and_unit_price(self) -> None:
"""WO003:空运费, KG, 单价…数量…金额… 中间夹单位时不能把单价当行金额。"""
from agent.llm.extract_text import extract_product_quote