"""User-bound scopes and result requirements, without model/platform calls.""" from copy import deepcopy import pytest from backend.prospecting.contracts import ProspectingBrief, TARGET_QUESTION from backend.prospecting.dialogue import _ground_scope, reground_requirements REQUEST = ( "我提供杭州装修服务,请在小红书找2位本人明确表达装修或设计服务需求的候选,优先查看评论和回复。" "按人去重,整理需求原话、来源链接和主页入口;页面可见的小红书号能取得就记录,没有就说明。" "最后导出名单,只做阅读和整理,不关注、不私信。遇到平台限制就停下来告诉我,保留已有成果。" ) def ground(message, *, current=None, history=None, proposed=None): return _ground_scope(proposed or ProspectingBrief(target_candidates=99, required_result_fields=["csv", "xiaohongshu_id", "explicit_demand"]), current or ProspectingBrief(), message, history or []) def test_exact_real_request_keeps_count_and_explicit_demand_while_account_remains_optional(): actual = ground(REQUEST) assert actual.target_candidates == 2 and TARGET_QUESTION not in actual.missing() assert actual.required_result_fields == ["csv", "explicit_demand"] assert actual.region is None # Merchant location is not a new geographic filter. assert actual.max_notes is None and actual.max_comments_per_note is None @pytest.mark.parametrize("message,count", [ ("找3位正在准备装修的客户", 3), ("请寻找4名本人提出设计需求的潜在客户", 4), ("整理5个最近正在比较装修公司的真实用户", 5), ("收集6条已经明确表示要找设计服务的线索", 6), ("先找2位预算在10万元并且正在装修的客户", 2), ("找2位有需求的候选,改成3位", 3), ("先找3位客户,最后给我2位本人明确表达需求的候选", 2), ("例如找8位客户,实际先找2位有装修需求的候选", 2), ]) def test_quantity_binds_unit_and_described_person_object(message, count): assert ground(message).target_candidates == count @pytest.mark.parametrize("message", [ "预算300元,找本人有装修需求的客户", "我做iPhone15配件,找本人明确需求的客户", "目标是20人以上的公司,找有需求的客户", "我们有2位同事,请找本人表达需求的候选", "需要3位员工负责找客户,人数暂没定", "找2个装修套餐并整理有需求的客户", "找2位或3位有明确需求的候选", "找2到3位有明确需求的候选", "找2位有明确需求的候选到3位候选", "不要帮我找2位有明确需求的候选,先讨论方案", "比如找2位有明确需求的候选,实际数量还没决定", "‘找2位本人明确表达需求的候选’是引用,帮我找客户", "『找2位本人明确表达需求的候选』是示例,帮我找客户", "`找2位本人明确表达需求的候选` 是代码示例,帮我找客户", "```python\n找2位本人明确表达需求的候选\n```\n帮我找客户", ]) def test_unrelated_or_unconfirmed_quantities_cannot_be_used_as_target(message): assert ground(message).target_candidates is None @pytest.mark.parametrize("message", [ "页面可见的小红书号能取得就记录,没有就说明", "小红书号有则填,无则空", "取得小红书号就记录,获取不到则留空", "小红书号最好取得,但拿不到也不影响交付", "整理小红书号,找不到就标注未知", "账号可选,名单继续保留", "小红书号不是必须,没有也可以", ]) def test_conditional_account_recording_is_optional_and_removes_old_compiled_requirement(message): current = ProspectingBrief(target_candidates=1, required_result_fields=["xiaohongshu_id"]) result = ground(message, current=current) assert "xiaohongshu_id" not in result.required_result_fields assert result.target_candidates == 1 @pytest.mark.parametrize("message", [ "必须取得小红书号,没有小红书号的人不计入", "整理2个页面可见小红书号", "需要提供正确归属的小红书号", "有小红书号才算候选", "只有取得小红书号才计入名单", "必须有小红书号,拿不到也要继续找", "必须有小红书号,没有就说明缺失", "必须取得小红书号后再记录", ]) def test_explicit_account_requirement_is_not_weakened(message): assert "xiaohongshu_id" in ground(message).required_result_fields @pytest.mark.parametrize("message,fields", [ ("找2位本人明确表达装修或设计服务需求的候选", {"explicit_demand"}), ("找2位候选,要求本人明确表达装修或设计服务需求", {"explicit_demand"}), ("目标客户:本人明确表达装修或设计服务需求的用户", {"explicit_demand"}), ("优先找有明确需求的人", set()), ("不一定有明确需求,相关信号也保留", set()), ("告诉我用户有没有明确需求", set()), ("小红书号可选,但必须导出CSV", {"csv"}), ("CSV可选,但必须提供小红书号", {"xiaohongshu_id"}), ]) def test_requirement_subject_and_optional_mode_stay_with_their_own_field(message, fields): assert set(ground(message).required_result_fields) == fields def test_revisions_follow_conversation_order_and_assistant_text_cannot_add_requirements(): history = [{"role": "user", "text": "必须有小红书号,只接受本人明确需求"}, {"role": "assistant", "text": "必须导出CSV"}, {"role": "user", "text": "小红书号能取得就记录,没有就说明"}] result = ground("明确需求也不作为必要条件", history=history) assert result.required_result_fields == [] revised = ground("这次必须提供小红书号", history=history) assert set(revised.required_result_fields) == {"explicit_demand", "xiaohongshu_id"} def test_reground_on_continue_repairs_fields_without_overriding_confirmed_quantity_card(): current = ProspectingBrief(target_candidates=1, required_result_fields=["csv", "xiaohongshu_id"], product="杭州装修服务", user_notes=REQUEST) history = [{"role": "user", "text": REQUEST}, {"role": "assistant", "text": TARGET_QUESTION}, {"role": "user", "text": "补充当前任务的信息:\n这次先整理多少位候选?:1"}] before, saved_history = current.model_dump(), deepcopy(history) result = reground_requirements(current, history) assert result.target_candidates == 1 assert result.required_result_fields == ["csv", "explicit_demand"] assert result.product == current.product and result.user_notes == REQUEST assert current.model_dump() == before and history == saved_history assert reground_requirements(result, history) == result