陆运补问把货物品名放进选项清单,并按销售原文回显已填项。

空着的运输分类不再被菜单第一项认成已填。空运报价改为货物信息加价格方案,锁舱成功回执补起运地、目的地、航司和航班日期。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-14 17:39:54 +08:00
co-authored by Cursor
parent cc8529f569
commit 9ffba70741
21 changed files with 1129 additions and 130 deletions
+46 -25
View File
@@ -20,7 +20,10 @@ from agent.schema.field_validate import (
)
from agent.llm.extract_text import parse_labeled_facts
from agent.schema.land_options import (
drop_blank_paste_options,
drop_land_prompt_echo,
harvest_land_options,
land_cargo_name,
option_prompt,
scrub_land_placeholder_ports,
validate_land_options,
@@ -220,15 +223,26 @@ def _segment_required_check(seg: Segment) -> tuple[dict[str, Any], dict[str, str
)
)
option_missing = [str(x) for x in (opt.get("missing") or []) if str(x).strip()]
cargo_missing = [str(x) for x in (check.get("missing") or []) if str(x).strip()]
missing = option_missing + [k for k in cargo_missing if k not in option_missing]
cargo_missing = [
str(x)
for x in (check.get("missing") or [])
if str(x).strip() and str(x) not in option_missing and str(x) != "品名"
]
# 货物品名没填时仍算这张选项清单没完成,不进始发站待填。
cargo_name = land_cargo_name(facts)
missing = list(option_missing) + cargo_missing
if not cargo_name:
missing.append("品名")
merged = dict(check.get("facts") or facts)
merged.update({k: facts[k] for k in ("运输类型", "线路类别", "运输分类") if facts.get(k)})
for key, val in facts.items():
if str(key).startswith("_land_show_") and str(val).strip():
merged[str(key)] = str(val).strip()
check["facts"] = merged
check["missing"] = missing
check["ok"] = not missing
check["need_land_subtype"] = False
check["need_land_options"] = bool(option_missing)
check["need_land_options"] = bool(option_missing) or not cargo_name
return check, merged
check = validate_required_fields(facts=dict(seg.facts), business_line=seg.mode)
facts = dict(seg.facts)
@@ -342,7 +356,10 @@ def _overlay_segments(
continue
blob = (neu.source or "").strip()
if target.mode == "LAND":
overlay = harvest_land_options(blob, dict(neu.facts))
overlay = drop_blank_paste_options(
harvest_land_options(blob, dict(neu.facts)),
blob,
)
elif target.mode == "SEA":
overlay = harvest_sea_class(blob, dict(neu.facts))
else:
@@ -412,16 +429,23 @@ def group_brief(
return "\n".join(lines)
def segments_to_payload(segments: list[Segment]) -> list[dict[str, Any]]:
"""书签和工单里保存的段。只保留字符串,避免书签序列化掉类型。"""
return [
{
"index": seg.index,
"mode": seg.mode,
"facts": {str(k): str(v) for k, v in (seg.facts or {}).items() if str(v).strip()},
}
for seg in segments
]
def segments_to_payload(
segments: list[Segment],
*,
keep_prompt_echo: bool = True,
) -> list[dict[str, Any]]:
"""
书签和工单里保存的段。只保留字符串,避免书签序列化掉类型。
建单时 keep_prompt_echo=False,回显用的内部键不进工单。
"""
rows: list[dict[str, Any]] = []
for seg in segments:
facts = {str(k): str(v) for k, v in (seg.facts or {}).items() if str(v).strip()}
if not keep_prompt_echo:
facts = drop_land_prompt_echo(facts)
rows.append({"index": seg.index, "mode": seg.mode, "facts": facts})
return rows
def segments_from_payload(raw: Any) -> list[Segment]:
@@ -462,16 +486,13 @@ def _segment_clarify(seg: Segment, check: dict[str, Any]) -> str:
if check.get("ok") and not check.get("need_land_subtype"):
return ""
missing = [str(x) for x in (check.get("missing") or []) if str(x).strip()]
if check.get("need_land_subtype"):
return (
copy.CLARIFY_PASTE_HINT
+ "\n1.运输类型(必填):\n(如:国内运输拼车)"
)
if seg.mode == "LAND" and (
check.get("need_land_subtype") or check.get("need_land_options")
):
# 和单聊同一张清单:类型、线路、分类、货物品名。已填的留在复制框上面。
return option_prompt(current=seg.facts)
if seg.mode == "LAND":
if check.get("need_land_options"):
# 和单聊一样先列出线路类别、运输分类,选完再问询价字段。
return option_prompt(current=seg.facts)
cargo_missing = [k for k in missing if k not in _LAND_OPTION_KEYS]
cargo_missing = [k for k in missing if k not in _LAND_OPTION_KEYS and k != "品名"]
raw = copy.ask_land_fields(
facts=seg.facts,
missing_keys=cargo_missing,
@@ -594,7 +615,7 @@ def _facts_from_body(body: str, mode: str) -> dict[str, str]:
if val and not str(facts.get(key) or "").strip():
facts[key] = val
if mode == "LAND":
facts = harvest_land_options(body, facts)
facts = drop_blank_paste_options(harvest_land_options(body, facts), body)
if mode == "SEA":
facts = harvest_sea_class(body, facts)
return facts
@@ -624,7 +645,7 @@ def _split_by_segment_mark(text: str) -> dict[int, dict[str, str]]:
end = marks[i + 1].start() if i + 1 < len(marks) else len(raw)
body = raw[mark.end() : end]
facts = parse_labeled_facts(body)
facts = harvest_land_options(body, facts)
facts = drop_blank_paste_options(harvest_land_options(body, facts), body)
facts = harvest_sea_class(body, facts)
facts = harvest_oral_measures(body, facts)
if facts: