From 7d39cf503c05c279b30e333f4cd939a12f55964b Mon Sep 17 00:00:00 2001 From: Yuuuu <1569752928@qq.com> Date: Wed, 8 Apr 2026 09:11:52 +0800 Subject: [PATCH] Refine semantic routing and ingress decision flow --- AGENTS.md | 3 + CLAUDE.md | 10 +- .../api/ai_pmo_api/helpers_inbox_filters.py | 25 +- .../api/ai_pmo_api/helpers_inbox_payload.py | 2 +- backend/app/api/ai_pmo_api/shared.py | 11 +- backend/app/config/ai_prompts.py | 194 +- .../mixin_answer_mainline.py | 3 +- .../mixin_requirement_ack_core.py | 43 +- .../mixin_requirement_ack_flow.py | 2 - .../adaptive_rag_service/shared_part_02.py | 35 - .../services/ai_pmo/ai_pmo_intent/__init__.py | 16 - .../ai_pmo/ai_pmo_intent/intent_resolver.py | 101 +- .../ai_pmo/ai_pmo_intent/route_resolver.py | 22 +- .../services/ai_pmo/ai_pmo_intent/shared.py | 22 +- .../ai_pmo/ai_pmo_kernel/ai_agent_kernel.py | 72 +- .../ai_pmo_typed_executor_common.py | 4 - .../ai_pmo_typed_executor_mixins_parse.py | 6 +- .../ai_pmo_kernel_mixins_closure_data.py | 15 +- .../ai_pmo_kernel_mixins_closure_payload.py | 22 +- .../ai_pmo_kernel_mixins_followup_routing.py | 2 +- .../ai_pmo_kernel_mixins_ingress_render.py | 4 +- .../intent/ai_pmo_kernel_mixins_intent_llm.py | 14 +- .../ai_pmo_kernel_mixins_intent_resolve.py | 74 +- .../ai_pmo_kernel_mixins_intent_rules.py | 122 +- ...i_pmo_kernel_mixins_world_model_session.py | 21 +- .../ai_pmo_kernel_common_core/constants.py | 10 +- .../ai_pmo_kernel_common_core/helpers.py | 11 +- .../ai_pmo_kernel_common_core/imports.py | 3 - .../ai_pmo_kernel_ingress_flow/mixin_entry.py | 527 ++- .../mixin_project.py | 8 +- .../mixin_execute_query_project.py | 2770 ++++++++++----- .../services/ai_pmo/consultant_dispatch.py | 53 +- .../services/ai_pmo/draft_confirm_handler.py | 332 +- .../ai_pmo/semantic_context/assembler.py | 46 +- .../semantic_context/decision_schema.py | 26 + .../ai_pmo/semantic_context/packet_schema.py | 13 + .../semantic_decision_service.py | 5 +- .../workflow_semantic_planner/constants.py | 609 ---- .../workflow_semantic_planner/service.py | 1240 +------ .../mixin_receive_routing.py | 23 +- .../mixin_reply_llm_core.py | 26 +- .../mixin_reply_outbox.py | 35 +- .../integrations/feishu_outbox/service.py | 25 +- .../mixin_context_prefill_a.py | 22 +- .../mixin_context_prefill_b.py | 30 +- .../feishu_signal_pipeline/mixin_core_base.py | 23 +- .../mixin_intent_gate_core.py | 46 +- .../mixin_lookup_and_block.py | 75 +- .../mixin_process_message_default.py | 120 +- .../mixin_process_message_helpers_a.py | 9 - .../mixin_serialization.py | 6 +- .../feishu_signal_pipeline/shared_core.py | 27 - .../signal/draft_generator_service.py | 4 +- .../mixin_analysis_normalize.py | 21 +- .../mixin_capture_entry.py | 47 +- .../mixin_pipeline_cache.py | 5 +- .../signal/signal_capture_service/shared.py | 5 - .../mixin_entry_orchestrate.py | 16 - .../mixin_planning_locator_roles.py | 9 - .../mixin_planning_onboard.py | 44 +- .../signal_source_orchestration/shared.py | 10 +- .../test_adaptive_rag_mainline_contract.py | 47 +- backend/tests/test_ai_pmo_ingress_mainline.py | 3088 ++++++++++++++++- ...t_ai_pmo_signal_source_onboard_contract.py | 25 + .../tests/test_ai_pmo_task_intent_contract.py | 84 +- backend/tests/test_draft_confirm_handler.py | 537 ++- .../test_feishu_pipeline_queue_contract.py | 158 +- ...t_feishu_receive_orchestration_contract.py | 38 + .../tests/test_semantic_context_assembler.py | 98 + .../tests/test_semantic_decision_service.py | 16 + backend/tests/test_semantic_lint.py | 5 +- .../tests/test_workflow_semantic_planner.py | 347 +- scripts/semantic_lint.py | 1 - 工单全字段报表_2026-04-07.xlsx | Bin 0 -> 636009 bytes 74 files changed, 7395 insertions(+), 4175 deletions(-) create mode 100644 工单全字段报表_2026-04-07.xlsx diff --git a/AGENTS.md b/AGENTS.md index 862267ab..fd56301b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -90,6 +90,8 @@ frontend/ # React 前端 - 禁止模型兜底回复:模型失败/超时/空响应/结构化解析失败时,必须显式报错并保留真实失败原因,禁止返回固定兜底文案冒充成功结果 - 核心业务内容零兜底:宪章、信号分析、任务拆解、项目建议、范围边界、风险判断、资源计划、对齐结论等用户可见核心业务内容,禁止使用默认文案、模板句子、占位字段或 stub 数据冒充真实生成结果;AI 失败、证据不足或结构化失败时,必须显式返回失败或标记为“待确认/证据不足/生成失败” - 业务语义治理:凡是会决定 `project.create / signal_source.onboard / followup.* / query vs execute` 的中文关键词、正则、`looks_like_*`、`*_HINTS/*_KEYWORDS`,只能定义在 `backend/app/services/ai_pmo/workflow_semantic_planner/` 或 prompt/catalog 配置层;其它目录只允许做文本清洗、ID/URL/repo/locator 抽取与结构化结果解析 +- 用户可见业务动作拍板必须优先走“上下文 packet -> 模型结构化决策 -> gatekeeper”链路;`reply anchor / button click / work_item_id / tenant / permission / safety / idempotency` 这类协议级判断允许保留确定性规则,但展示反馈、草稿确认/修改/取消、query vs execute、followup 动作归类不得再由 `looks_like_*` 或关键词直接最终拍板 +- 若问题根因属于“动作路由错误 / 上下文劫持 / 独立业务流误触发”,禁止把“改 prompt、补反例、扩关键词”当作主修法;必须优先检查 ingress 分流、active topic/draft 检测、`SemanticContextPacket` 组装、`SemanticDecision` 结构化输出与 gatekeeper 放行条件。Prompt 只允许补结构、口径和禁止事项,不得替代主链裁决 ### 8.1 系统定位 @@ -225,6 +227,7 @@ frontend/ # React 前端 ## 13. 测试与验证 - 后端改动:`python -m py_compile` 通过 + 相关单测通过 +- 后端代码或数据库迁移改动:在编译/测试通过后,必须继续执行当前环境对应的热部署/重启/重载步骤,确保变更真实生效;可用方式包括 `docker compose restart backend`、`scripts/docker_stack.sh restart|dev-restart`、`systemctl restart neural-sync`、`nginx -s reload` 等。若当前环境不是部署机、权限不足或不具备热部署条件,必须明确说明未执行原因与阻塞点,禁止默认跳过 - 前端改动:`npm run build` 通过 + `tsc --noEmit` 零错误 - 新增接口:必须在 `backend/tests/` 补充对应单测 diff --git a/CLAUDE.md b/CLAUDE.md index 4704340b..5a39624a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,8 @@ - 建议:规则放 `AGENTS.md`,架构/目录说明放 `CLAUDE.md` - 当前 AI 行为的系统宪章已在 `AGENTS.md` 中升级为“事实层 / 评审层 / 生成层 / 执行层”四层分治;所有新增 AI 业务默认按该宪章接入,`CLAUDE.md` 仅保留结构与背景说明 - 新增执行约定:计划类需求一旦确认进入实现阶段,默认连续执行到“实现完成 + 验证完成 + 统一回复验收结果”;仅在明确阻塞、高风险冲突、context 限制或用户主动中断时才中途打断。 +- 新增 bugfix 执行约定:缺陷修复/回归修复允许跨多个直接相关文件端到端完成,不再按文件数硬性拆分;但必须同时满足“改动范围围绕同一根因”“不夹带无关重构”“最终附带编译/测试验证”。大功能开发或结构性重构仍优先按子任务拆分。 +- 新增发布约定:凡后端代码或数据库迁移发生改动,完成编译/测试后必须继续执行当前环境对应的热部署/重启/重载步骤,确认服务已加载新版本;若当前机器不是部署环境、缺少权限或不具备热部署条件,必须在结果中明确说明未执行原因与阻塞点,禁止默认省略。 ## 2. 项目定位 - 项目名:`PMFlow / Neural Sync` @@ -197,7 +199,7 @@ backend/ - 新增:`backend/app/utils/project_command_utils.py`(项目创建标题/描述提取与归一化辅助) - 新增:`backend/app/services/ai_pmo_intent_routing.py`(AI PMO 意图/路由常量与解析器收口,统一 `family.action` 路由映射) - 新增:`backend/app/services/ai_pmo/workflow_semantic_planner/`(统一业务语义规划层:唯一允许承载 workflow bundle 语义关键词/正则/兼容 helper 的目录) -- 新增:`backend/app/services/ai_pmo/semantic_context/`(OpenCow 风格语义上下文子模块:统一 `SemanticContextPacket/ModeCard/SemanticDecision/DecisionGateResult` 契约、`SemanticContextAssembler`、`SemanticDecisionGatekeeper` 与 `SemanticDecisionService`;当前已接入 ingress shadow 观测,并为 intent/query-scene 主链提供 direct metadata 替代语义入口) +- 新增:`backend/app/services/ai_pmo/semantic_context/`(OpenCow 风格语义上下文子模块:统一 `SemanticContextPacket/ModeCard/SemanticDecision/DecisionGateResult` 契约、`SemanticContextAssembler`、`SemanticDecisionGatekeeper` 与 `SemanticDecisionService`;当前除 ingress shadow 观测外,也开始承接 draft followup 这类用户可见动作判定,作为 intent/query-scene/followup 统一 packet 决策底座) - 新增:`backend/app/models/reply_iteration.py`(回复质量迭代模型:`QualityAsset/QualityAssetPair/JudgeRun/OptimizerCandidate/PromptRelease`) - 新增:`backend/app/models/training_pipeline.py`(训练/微调管线模型:`TrainingDataset/TrainingJob/TrainingEvaluation/TrainedModelArtifact/TrainedModelDeployment`) - 新增:`backend/app/models/mounted_ai_runtime.py`(Mounted Runtime 运行时模型:`MountedTopicWorkspace/MountedDraftWorkspace/MountedConversationRun/MountedAgentTrace/MountedSourceActivation/MountedToolCapabilityPolicy`;`MountedTopicWorkspace` 额外承载 `conversation_scope_key + plan_status`,用于同群同人 topic 隔离与 plan 状态机;`MountedDraftWorkspace` 额外承载 `active_work_item_id + latest_source_message_id + latest_reply_message_id`,用于当前 active draft 绑定与 reply anchor 锚定) @@ -583,10 +585,10 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 - `ExecuteQueryProjectMixin` 对 `message_intent=requirement_plan` 的查询必须优先走 mounted runtime 直答快路径:默认只输出自然短话术,每轮最多只保留 1 个关键问题;当 `scoring_basis/target_user/output_form/cadence/first_scope` 收集完成时,必须直接整理成需求草稿,禁止继续空转追问 - `requirement_plan` 的 Mounted Runtime 状态必须显式落库到 `MountedTopicWorkspace.plan_status`:至少区分 `idle / collecting / options_ready / plan_ready / awaiting_execute_confirm / suspended`,禁止只靠 `draft.state` 或 `metadata_json` 猜当前阶段 - 新的 OpenCow 风格语义上下文链必须先组装 `SemanticContextPacket`,再由模型返回 `SemanticDecision`,最后由 gatekeeper 决定 `plan_status`;当前 shadow 结果已进入 Mounted Runtime 观测,同时 intent/query-scene 主链已切到 direct metadata,不得再把 `semantic_bundle` 当作主链真源或绕过统一执行门禁 +- `requirement_plan` 的 A/B/C 是用户可见需求收敛骨架,可以固定结构;但 A/B/C 的业务选项、AI 推荐项、推荐理由和代价必须参照 OpenCow 的 `上下文包 -> 模型结构化决策 -> gatekeeper -> 渲染` 模式生成,禁止在代码里硬编码业务候选项或旧场景模板。用户回复 `A/B/C` 时必须优先回写上一轮模型生成的选项映射,不能再落回静态选项表。 - 正在收敛中的 `requirement_plan` 必须支持普通人碎片化回复:像“技术指标吧 / 给投顾看 / 飞书日报就行 / 每天收盘后发”这种单字段短句,即使缺少新的 `semantic_bundle`,只要同线程/同群同人存在 active plan workspace,也必须自动续接当前需求收敛,不得回落到通用 query 或高阶 RAG - `requirement_plan` 的追问节奏必须像自然聊天:上一轮只确认新增信息、下一轮只问一个缺口;初始描述中的“每日/自动”不能过早推断成已确认的更新频率,避免少问关键问题或提前出草稿 -- `requirement_plan` 必须按 profile 区分场景:`scoring` 需求继续收集 `打分依据/目标用户/输出形式/更新频率/第一版范围`,`generic` 需求收集 `输入材料/目标用户/输出形式/响应时效/第一版范围`;像“会议纪要提待办”这类普通工具需求,禁止再复用“这个分数怎么打”的旧问题模板 -- `generic requirement_plan` 的 `A/B/C` 选项只能出现在 active requirement session 的 `collecting` 轮次里,并且只允许绑定当前缺口字段(如 `target_user / output_form / cadence`);普通闲聊、query.answer、非 requirement followup 必须继续保持自由文案,禁止全局套用选项模板 +- `requirement_plan` 当前用户可见主链只保留 IT 项目收敛口径;历史 `generic/scoring` 数据只允许作为兼容读取或旧测试场景,不得再作为新会话的用户可见模板扩散。普通闲聊、query.answer、非 requirement followup 必须继续保持自由文案,禁止全局套用 A/B/C 选项骨架 - 用户显式说“聊个新需求 / 另一个需求 / 跟上一个无关”时,AI PMO 必须先清掉当前会话里继承的旧 `current_project_id`,避免后续短回复继续被旧项目上下文污染 - 裸 `A/B/C/确认/你自行推荐` 这类低信息量回复,如果没有命中当前 active requirement session 或待确认草稿,必须直接提示“当前没有进行中的收敛轮次/草稿”,禁止再回落到 `query.answer` 检索图谱并生成旧项目结论 - 飞书私聊中的“答错了/不对/调用高级模型/用更强模型/重新回答”必须被识别为 `correction_escalation` 指令:优先复用上一轮上下文主题并强制深度回答,禁止把这类话术当成新的系统配置查询 @@ -607,6 +609,8 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 - AI PMO 执行层必须统一产出 `execution_result.action_contract` 与 `action_param_source`,并写入 `operation_execution_ledger`;禁止在执行链路旁路写副作用且不入账 - AI PMO 写操作幂等以消息级锚点为准:优先使用 `source_message_id` / `feishu_message_id` 作为执行账本幂等键,标题/正文仅用于业务内容,不得参与重复执行判定 - PMFlow 业务语义治理:会决定 `project.create / signal_source.onboard / followup.* / query vs execute` 的中文关键词、正则、`looks_like_*`、`*_HINTS/*_KEYWORDS` 只能定义在 `backend/app/services/ai_pmo/workflow_semantic_planner/` 或 prompt/catalog 配置层;其他目录仅允许做文本清洗、ID/URL/repo/locator 抽取与结构化结果解析 +- 用户可见业务动作拍板必须优先走 `SemanticContextPacket -> SemanticDecision -> gatekeeper` 主链;协议级锚点(`reply_to_message_id/button/work_item_id`)、租户权限、幂等等确定性规则可以保留,但展示反馈、草稿确认/修改/取消、followup 动作归类、`query vs execute` 不能再由 `looks_like_*` 或关键词直接最终拍板 +- 若问题根因属于“动作路由错误 / 上下文劫持 / 独立业务流误触发”,主修法必须优先落在 ingress 分流、active topic/draft 检测、`SemanticContextPacket` 组包、`SemanticDecision` 结构化判断与 gatekeeper 放行条件;禁止先用“补 prompt 反例 / 扩关键词 / 调语义文案”顶上去。Prompt 只负责结构、口径、禁止事项,不替代主链裁决 - `scripts/semantic_lint.py` + `scripts/semantic_lint_baseline.txt` 为语义治理守门脚本;CI 会阻止新的散落语义进入非 allowlist 目录,baseline 仅记录已知兼容残留 - 只读问答链路的 prompt 读取优先级固定为:`prompt registry manifest 校验 -> active prompt release -> 代码 seed prompt fallback`;业务服务禁止直接绕过 `PromptReleaseService` 拼接已纳管的 read-only QA prompt - 回复质量迭代与训练管线的原始反馈入口统一复用 `feedback_center_records` / `qa_records` / `ai_call_logs`;新增 `quality_assets/training_datasets` 只能作为派生层,禁止再造平行原始数据主表 diff --git a/backend/app/api/ai_pmo_api/helpers_inbox_filters.py b/backend/app/api/ai_pmo_api/helpers_inbox_filters.py index eb5f6421..cabbe9e5 100644 --- a/backend/app/api/ai_pmo_api/helpers_inbox_filters.py +++ b/backend/app/api/ai_pmo_api/helpers_inbox_filters.py @@ -20,11 +20,18 @@ def _should_hide_work_item_from_inbox(*, row: WorkItem, ingest: IngestEvent) -> normalized = _normalize_noise_text(message_text) if not normalized: return True - if any(keyword in normalized for keyword in FRONT_NOISE_STRUCTURED_HINTS): + has_structured_context = any( + ( + _safe_int(row.project_id) is not None, + _safe_int(row.signal_id) is not None, + bool(payload.get("closure_summary")), + bool(payload.get("closure_dialogue")), + bool(payload.get("closure_modules")), + ) + ) + if has_structured_context: return False - if normalized in FRONT_NOISE_EXACT: - return True - if len(normalized) <= 8 and any(keyword in normalized for keyword in FRONT_NOISE_HINTS): + if len(normalized) <= 3: return True return False @@ -61,19 +68,15 @@ def _is_low_value_closure_payload(payload: dict[str, Any]) -> bool: has_closure = bool(summary or dialogue or modules) if not has_closure: return True - compact_summary = re.sub(r"\s+", "", summary).lower() if summary else "" - summary_is_generic = bool(compact_summary and GENERIC_SOURCE_TEXT_RE.match(compact_summary)) - weak_summary = bool(summary and any(token in summary for token in LOW_VALUE_CLOSURE_HINTS)) has_assistant_turn = any( str(item.get("role") or "").strip().lower() == "assistant" and str(item.get("text") or "").strip() for item in turns if isinstance(item, dict) ) - if weak_summary and not has_assistant_turn: + summary_len = len(re.sub(r"\s+", "", summary)) if summary else 0 + if summary_len <= 6 and not has_assistant_turn and not modules: return True - if summary_is_generic and not modules and not has_assistant_turn: - return True - if dialogue and "机器人:" not in dialogue and len(turns) <= 1 and weak_summary: + if dialogue and "机器人:" not in dialogue and len(turns) <= 1 and summary_len <= 12 and not modules: return True return False diff --git a/backend/app/api/ai_pmo_api/helpers_inbox_payload.py b/backend/app/api/ai_pmo_api/helpers_inbox_payload.py index 2e6ea77f..0008e78f 100644 --- a/backend/app/api/ai_pmo_api/helpers_inbox_payload.py +++ b/backend/app/api/ai_pmo_api/helpers_inbox_payload.py @@ -97,7 +97,7 @@ def _serialize_work_item_summary( summary_seed = closure_summary or message_text or _next_step_text(row) or row.intent or "AI PMO 工作项" summary = _compact_text(summary_seed, 180) compact_message = re.sub(r"\s+", "", message_text).lower() if message_text else "" - prefer_closure_title = bool(compact_message and GENERIC_SOURCE_TEXT_RE.match(compact_message)) + prefer_closure_title = bool(compact_message and len(compact_message) <= 8 and closure_summary) title_seed = ( (closure_summary or message_text) if prefer_closure_title diff --git a/backend/app/api/ai_pmo_api/shared.py b/backend/app/api/ai_pmo_api/shared.py index f88f2cea..3b01ac3d 100644 --- a/backend/app/api/ai_pmo_api/shared.py +++ b/backend/app/api/ai_pmo_api/shared.py @@ -17,16 +17,7 @@ from ...dependencies.tenant import get_tenant_id from ...models import DecisionRecord, IngestEvent, PlanBundle, PortfolioSnapshot, Project, ReplyReceipt, User, WorkItem from ...services.ai_pmo.ai_pmo_cadence import AIPMOCadenceService from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService -from ...services.ai_pmo.workflow_semantic_planner.constants import ( - FRONT_NOISE_HINTS, - FRONT_NOISE_STRUCTURED_HINTS, - GENERIC_SIGNAL_SOURCE_MESSAGE_RE as GENERIC_SOURCE_TEXT_RE, - INTENT_QUERY, - LOW_VALUE_CLOSURE_HINTS, - ROUTE_QUERY_ANSWER, - SMALLTALK_EXACT as FRONT_NOISE_EXACT, -) router = APIRouter(prefix="/ai-pmo", tags=["AI PMO"]) logger = get_logger(__name__) -FRONT_NOISE_INTENTS = {INTENT_QUERY, ROUTE_QUERY_ANSWER} +FRONT_NOISE_INTENTS = {"query.read_only", "query.answer"} diff --git a/backend/app/config/ai_prompts.py b/backend/app/config/ai_prompts.py index b74eb427..57ca9350 100644 --- a/backend/app/config/ai_prompts.py +++ b/backend/app/config/ai_prompts.py @@ -72,24 +72,78 @@ PMFLOW_SEMANTIC_CONTEXT_DECISION_PROMPT = """ 你的输入是系统整理好的 semantic context packet。你的职责不是执行写操作,而是: 1. 判断用户这句话是否继续当前 topic。 2. 判断当前 workflow 是否继续停留在 plan。 -3. 给出 draft patch 建议。 -4. 给出要回复给用户的内容。 -5. 明确 requested_actions 与是否需要确认。 +3. 如果 packet.ingress.candidate_kind == "draft_followup",要判断这句回复是在确认草稿、修改草稿、取消草稿,还是仅仅反馈展示/沟通方式。 +4. 如果 packet.ingress.candidate_kind == "active_context_followup",要判断这句短追问应该留在当前需求、留在当前草稿,还是升级成独立 workflow。 +5. 给出 draft patch 建议。 +6. 给出要回复给用户的内容。 +7. 明确 requested_actions 与是否需要确认。 硬约束: - 只能依据 packet 中的事实、草稿、recent turns、mode card 判断。 - 不得把推断包装成事实。 - 不得请求 mode card 明确禁止的动作。 +- 如果 packet.ingress.candidate_kind == "draft_followup": + - 只有用户明确要求改内容、补内容、删内容、缩范围、改字段时,才能输出 followup_action="revise"。 + - 如果用户是在追问当前草稿里明显缺失的模块或字段,这属于补充当前草稿内容,输出 followup_action="revise",并把 revision_note 写成要补充的模块。 + - 如果用户只是反馈展示形式、消息长短、卡片样式、沟通方式,输出 followup_action="answer_only"。 + - 如果用户明确同意草稿内容,输出 followup_action="confirm"。 + - 如果用户明确不要当前草稿,输出 followup_action="cancel"。 + - 展示反馈不能包装成 revision_note。 +- 如果 packet.ingress.candidate_kind == "active_context_followup": + - 这是“当前会话已经有 active requirement / active draft 时”的上下文优先决策,不是在做通用 intent 分类。 + - 必须先基于当前已绑定的对象来判断动作,不要把这句消息先当成全局 query 或通用新意图。 + - 对于只补充当前讨论模块的短追问,默认优先判断为 current_requirement 或 current_draft,而不是独立 workflow。 + - 如果 active_context_kind == "draft_pending_confirm": + - 用户明确表示确认当前草稿时,输出 followup_scope="current_draft" 且 followup_action="confirm"。 + - 用户明确表示重写、重出一版、按这版重来、补充缺失模块时,输出 followup_scope="current_draft" 且 followup_action="revise"。 + - 用户明确表示不要当前草稿时,输出 followup_scope="current_draft" 且 followup_action="cancel"。 + - 仅当用户真正要脱离当前草稿、发起独立动作时,才允许输出 followup_scope="independent_workflow"。 + - 只有当用户同时表达了明确动作、明确对象、明确作用域时,才能输出 followup_scope="independent_workflow"。 + - 如果当前上下文是待确认草稿,且用户追问的是草稿里缺失模块,应输出 followup_scope="current_draft",同时给出 followup_action="revise" 和 revision_note。 + - 如果当前上下文是收敛中的 requirement plan,且用户只是在追问某个模块或缺口,应输出 followup_scope="current_requirement"。 + - 如果证据不足以判断归属,输出 followup_scope="clarify",并用 assistant_reply 追问,不要擅自跳到独立 workflow。 +- followup_scope 仅在 active_context_followup 场景下使用;其它场景必须输出 "none"。 +- followup_action 在 draft_followup 场景下必须使用;在 active_context_followup 场景下仅当 followup_scope="current_draft" 时允许使用,其它情况输出 "none"。 +- 如果 packet.ingress.candidate_kind == "cold_start_route": + - 这是“当前消息还没有绑定到明确对象时”的冷启动路由决策。 + - 你的职责是基于 packet.latest_user_message、packet.topic.metadata、packet.thread、packet.ingress.thread_context 决定应该进入哪些 workflow。 + - 不要使用硬编码关键词仓库思维,不要把业务词表当成主裁决器;必须根据当前消息表达的动作、对象、作用域和上下文关系做整体判断。 + - 如果消息本质上是在咨询、分析、聊需求、收敛范围、问建议,而不是立即执行写动作,应输出 query_flow。 + - 如果消息明确表达了新建项目、创建任务、接入信号源、创建项目信号等独立动作,才输出对应 workflow。 + - 允许多 workflow,但只有当消息同时表达了多个明确动作时才这样做。 + - scene_metadata 只允许包含:message_intent、review_framework、consultant_mode_preference、force_thinking_mode、advise_subtype、topic_switch。 + - workflow_plans 里的 workflow_type 只允许:query_flow、new_project_flow、project_flow、task_flow、signal_source_flow、project_signal_flow、followup_flow。 + - workflow_action 应与 workflow_type 匹配,例如 answer/create/onboard/discover/continue/confirm/cancel/refresh/commit。 + - target_scope 只允许:global、project、thread。 + - operation_codes 必须使用系统路由键,例如 query.answer、project.create、task.create、signal_source.onboard、project_signal.create、project_signal.discover、project_signal.commit、project_signal.cancel、project_signal.refresh、followup.continue、followup.confirm。 + - cold_start_route 场景下,requested_actions 只允许请求 workflow.bundle.compose。 + - 如果证据不足,不要伪造执行意图;优先保守为 query_flow,并把不确定性写入 uncertainties。 - 输出必须是一个 JSON 对象,不要输出 Markdown。 JSON schema: { "topic_relation": "continue_current_topic | new_topic | uncertain", "mode_decision": "stay_plan | ready_confirm | request_execute | answer_only", + "followup_scope": "none | current_requirement | current_draft | independent_workflow | clarify", + "followup_action": "none | confirm | revise | cancel | answer_only", + "revision_note": "string", "draft_patch": {}, + "workflow_plans": [ + { + "workflow_type": "query_flow | new_project_flow | project_flow | task_flow | signal_source_flow | project_signal_flow | followup_flow", + "workflow_action": "string", + "target_scope": "global | project | thread", + "operation_codes": ["string"], + "requires_confirmation": false, + "confidence": 0.0, + "clarification_needed": false, + "metadata": {} + } + ], + "scene_metadata": {}, "assistant_reply": "string", "requires_confirmation": true, - "requested_actions": ["reply.compose"], + "requested_actions": ["reply.compose | workflow.bundle.compose"], "confidence": 0.0, "uncertainties": [] } @@ -98,6 +152,136 @@ semantic_context_packet: {semantic_packet} """.strip() +PMFLOW_REQUIREMENT_PLAN_ROUND_PROMPT = """ +你是 PMFlow 的需求收敛 PM。 + +你的输入是系统整理好的 requirement_plan_round_packet。你的职责是:在当前需求上下文内,为“本轮缺口字段”生成 A/B/C 三个可选收敛方向,并给出一个 AI 推荐。 + +硬约束: +- A/B/C 是固定需求收敛骨架,必须保留。 +- 不要复用旧项目模板、旧业务场景或 packet 中不存在的产品名。 +- 不要把模板占位句冒充业务判断。 +- 只能基于 packet.current_context / collected_fields / collected_field_summary / latest_user_message / latest_user_reply_summary 生成选项。 +- 这是要发给飞书的一条文本消息,必须严格遵守 packet.reply_budget。 +- 最终渲染后的整条消息必须控制在 packet.reply_budget.max_chars 以内,避免消息被截断。 +- opening / focus / 每个 option 字段 / recommendation.reason / reply_instruction 都要短句化,不要写成长段说明。 +- 不要输出“当前我们正在收敛”“project_nature 字段需判断”这类内部字段说明。 +- 不要复述 packet、current_context、latest_user_message、collected_fields、reply_budget 这类内部 JSON 键名,也不要把 snake_case 英文字段名直接写给用户看。 +- 如果 latest_user_message 只是 A / B / C 这种短回复,优先参考 packet.latest_user_reply_summary 和 packet.collected_field_summary 理解用户刚刚确认的方向。 +- 如果证据不足,要把不确定性写进 recommendation.reason 或 uncertainties,不要编造成事实。 +- 输出必须是一个 JSON 对象,不要输出 Markdown。 + +JSON schema: +{ + "opening": "string", + "focus": "string", + "options": [ + { + "key": "A", + "title": "string", + "best_for": "string", + "suggested_capability": "string", + "rationale": "string", + "tradeoff": "string" + }, + { + "key": "B", + "title": "string", + "best_for": "string", + "suggested_capability": "string", + "rationale": "string", + "tradeoff": "string" + }, + { + "key": "C", + "title": "string", + "best_for": "string", + "suggested_capability": "string", + "rationale": "string", + "tradeoff": "string" + } + ], + "recommendation": { + "key": "A | B | C", + "reason": "string" + }, + "reply_instruction": "string", + "uncertainties": [] +} + +requirement_plan_round_packet: +{round_packet} +""".strip() + +PMFLOW_REQUIREMENT_PLAN_DRAFT_PROMPT = """ +你是 PMFlow 的需求收敛 PM。 + +你的输入是系统整理好的 requirement_plan_draft_packet。你的职责是:基于当前已经收敛出的事实,生成一版贴合当前业务语境、可以直接给用户确认的需求草稿。 + +硬约束: +- 只能基于 packet.current_context / collected_fields / collected_field_summary / pending_items / known_constraints / revision_request 生成草稿。 +- 草稿必须贴合当前需求,不要复用旧项目模板、旧业务场景或 packet 中不存在的产品名。 +- 不要复述 packet、current_context、collected_fields、known_constraints 这类内部 JSON 键名,也不要把 snake_case 英文字段名直接写给用户看。 +- 不要输出“审批、审计、权限一次做满”“人员能力图谱”“工作流状态与审计日志”“确认门禁”这类与当前需求无关的旧系统固定话术。 +- 如果当前需求不是部署/发布/运维类项目,不要引入回滚、部署目标清单、环境凭证、平台运维团队等旧部署语境。 +- 不要把模板占位句冒充业务判断。 +- 如果证据不足,要把不确定性放进 pending_items,不要编造成事实。 +- 必须给出 3-5 个外部信号源候选,它们属于当前需求下面的子需求候选,不是新的独立 workflow。 +- external_signal_sources 只能写外部来源,例如开放平台、第三方 API、外部趋势源、外部商品源、外部回流源;不能写飞书消息、项目任务、责任人、数据库表、内部日志、内部工作流。 +- 输出必须是一个 JSON 对象,不要输出 Markdown。 + +JSON schema: +{ + "intro": "string", + "title": "string", + "one_liner": "string", + "goal": "string", + "project_nature": "string", + "target_user": "string", + "signal_inputs": "string", + "known_info": "string", + "tech_route": "string", + "first_scope": "string", + "core_flow": "string", + "delivery_mode": "string", + "success_signal": "string", + "risks": "string", + "pending_items": ["string"], + "closing": "string", + "summary": "string", + "positioning_one_liner": "string", + "target_customers": "string", + "external_signal_sources": [ + { + "name": "string", + "purpose": "string", + "phase": "string" + } + ], + "requirement_items": [ + { + "section": "string", + "title": "string", + "description": "string" + } + ], + "missing_info": ["string"], + "mvp_items": ["string"], + "team_setup": [ + { + "role": "string", + "notes": "string" + } + ], + "validation_metrics": { + "product": ["string"] + } +} + +requirement_plan_draft_packet: +{draft_packet} +""".strip() + def build_default_role_prompt_bundle() -> dict: """返回默认角色 Prompt 配置(每次调用返回新对象)。""" @@ -230,6 +414,8 @@ __all__ = [ "PROMPT_EXTERNAL_TREND_QA", "PROMPT_INDUSTRY_TREND_QA", "PMFLOW_SEMANTIC_CONTEXT_DECISION_PROMPT", + "PMFLOW_REQUIREMENT_PLAN_ROUND_PROMPT", + "PMFLOW_REQUIREMENT_PLAN_DRAFT_PROMPT", "PROMPT_REGISTRY", "PROMPT_REGISTRY_MAP", "get_prompt_registry", diff --git a/backend/app/services/adaptive_rag_service/mixin_answer_mainline.py b/backend/app/services/adaptive_rag_service/mixin_answer_mainline.py index ca604655..8890d9da 100644 --- a/backend/app/services/adaptive_rag_service/mixin_answer_mainline.py +++ b/backend/app/services/adaptive_rag_service/mixin_answer_mainline.py @@ -164,10 +164,11 @@ class AdaptiveRAGAnswerMainlineMixin: ) query_type = self._classify_query_type(effective_q) requested_action_mode = self._normalize_action_mode(action_mode) - effective_action_mode = self._resolve_action_mode( + effective_action_mode = await self._resolve_action_mode_async( requested_mode=requested_action_mode, question=resolved_question, effective_question=effective_q, + asked_via=asked_via, ) return { "session_id": session_id, diff --git a/backend/app/services/adaptive_rag_service/mixin_requirement_ack_core.py b/backend/app/services/adaptive_rag_service/mixin_requirement_ack_core.py index a29d8488..371d3e64 100644 --- a/backend/app/services/adaptive_rag_service/mixin_requirement_ack_core.py +++ b/backend/app/services/adaptive_rag_service/mixin_requirement_ack_core.py @@ -13,37 +13,41 @@ class AdaptiveRAGRequirementAckCoreMixin: return ACTION_MODE_AUTO @classmethod - def _looks_like_write_intent(cls, question: str) -> bool: - text = str(question or "").strip().lower() - if not text: - return False - has_verb = any(k in text for k in REQUIREMENT_WRITE_VERB_KEYWORDS) - has_object = any(k in text for k in REQUIREMENT_WRITE_OBJECT_KEYWORDS) - if not has_verb or not has_object: - return False - if any(prefix in text for prefix in WRITE_EXPRESSIONS): - return True - if "新增" in text or "增加" in text or "记录" in text or "入库" in text: - return True - return False - - @classmethod - def _resolve_action_mode( + async def _resolve_action_mode_async( cls, *, requested_mode: str, question: str, effective_question: str, + asked_via: str, ) -> str: mode = cls._normalize_action_mode(requested_mode) if mode != ACTION_MODE_AUTO: return mode merged = f"{question} {effective_question}".strip() - if not cls._looks_like_write_intent(merged): + if not merged: return ACTION_MODE_READ - has_question_form = ("?" in merged) or ("?" in merged) or cls._contains_any(merged, QUESTION_HINTS) - if has_question_form: + try: + from ..ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner + + bundle = await WorkflowSemanticPlanner.aplan_message( + message_text=merged, + source_system=str(asked_via or "").strip() or "adaptive_rag_ack", + metadata={}, + ) + except Exception: + return ACTION_MODE_READ + + workflows = list(getattr(bundle, "workflows", []) or []) + if not workflows: + return ACTION_MODE_READ + + has_query_flow = any(str(getattr(item, "workflow_type", "") or "").strip() == "query_flow" for item in workflows) + has_non_query_flow = any(str(getattr(item, "workflow_type", "") or "").strip() != "query_flow" for item in workflows) + if not has_non_query_flow: + return ACTION_MODE_READ + if has_query_flow: return ACTION_MODE_READ_WRITE return ACTION_MODE_WRITE @@ -194,4 +198,3 @@ class AdaptiveRAGRequirementAckCoreMixin: reason = error_reason or "写入异常" return f"未完成入库:{reason}。我已记录本次失败,请稍后重试。" return "已收到需求,正在处理中。" - diff --git a/backend/app/services/adaptive_rag_service/mixin_requirement_ack_flow.py b/backend/app/services/adaptive_rag_service/mixin_requirement_ack_flow.py index c99a7d80..4f0e2698 100644 --- a/backend/app/services/adaptive_rag_service/mixin_requirement_ack_flow.py +++ b/backend/app/services/adaptive_rag_service/mixin_requirement_ack_flow.py @@ -2,7 +2,6 @@ from __future__ import annotations from .shared import * -from ..ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner class AdaptiveRAGRequirementAckFlowMixin: @@ -45,7 +44,6 @@ class AdaptiveRAGRequirementAckFlowMixin: linked_signal_id is not None and resolved_project_id is not None and action_taken in {"signal_created", "signal_deduped"} - and WorkflowSemanticPlanner.looks_like_signal_source_request(effective_question or question) ): try: source_orchestration_result = await SignalSourceOrchestrationService(self.db).orchestrate_from_signal( diff --git a/backend/app/services/adaptive_rag_service/shared_part_02.py b/backend/app/services/adaptive_rag_service/shared_part_02.py index f4ac6a25..087dd7ce 100644 --- a/backend/app/services/adaptive_rag_service/shared_part_02.py +++ b/backend/app/services/adaptive_rag_service/shared_part_02.py @@ -68,41 +68,6 @@ ACTION_MODE_AUTO = "auto" ACTION_MODE_READ = "read" ACTION_MODE_WRITE = "write" ACTION_MODE_READ_WRITE = "read_write" -REQUIREMENT_WRITE_VERB_KEYWORDS = ( - "新增", - "增加", - "添加", - "接入", - "支持", - "实现", - "创建", - "新建", - "记录", - "入库", - "同步", - "补充", -) -REQUIREMENT_WRITE_OBJECT_KEYWORDS = ( - "信号", - "需求", - "任务", - "项目", - "功能", - "接口", - "页面", - "按钮", - "规则", - "模块", - "卡路里", - "食谱", -) -WRITE_EXPRESSIONS = ( - "给", - "帮我", - "请帮", - "请给", - "麻烦", -) COUNT_HINTS = ("多少", "几个", "数量", "总数", "count") LIST_HINTS = ("哪些", "列表", "清单", "列出", "列一下", "分别", "名单", "都有哪些") STATUS_HINTS = ("进度", "状态", "阶段", "完成率", "完成度", "情况") diff --git a/backend/app/services/ai_pmo/ai_pmo_intent/__init__.py b/backend/app/services/ai_pmo/ai_pmo_intent/__init__.py index a4876d4f..3dfa2644 100644 --- a/backend/app/services/ai_pmo/ai_pmo_intent/__init__.py +++ b/backend/app/services/ai_pmo/ai_pmo_intent/__init__.py @@ -44,23 +44,7 @@ __all__ = [ "WORKFLOW_ROUTE_KEYS", "ROUTE_CANONICAL_INTENT_MAP", "LEGACY_INTENT_ROUTE_MAP", - "PROJECT_SIGNAL_DISCOVER_HINTS", - "FOLLOWUP_REFRESH_HINTS", - "FOLLOWUP_CANCEL_HINTS", - "FOLLOWUP_COMMIT_HINTS", - "QUESTION_HINTS", - "SMALLTALK_EXACT", - "SMALLTALK_SHORT_HINTS", - "SMALLTALK_STRUCTURED_HINTS", - "FOLLOWUP_CONFIRM_HINTS", - "FOLLOWUP_CONTINUE_HINTS", "FOLLOWUP_WORK_ITEM_ID_RE", - "SIGNAL_CREATE_HINTS", - "TASK_CREATE_HINTS", - "PROJECT_CREATE_HINTS", - "SESSION_SHORT_CONFIRM_WINDOW_SECONDS", - "SHORT_CONFIRM_MAX_MESSAGE_LENGTH", - "SHORT_CONFIRM_HINTS", "INTENT_LLM_FUNCTION_NAME", "INTENT_CANDIDATES", "WRITE_INTENTS", diff --git a/backend/app/services/ai_pmo/ai_pmo_intent/intent_resolver.py b/backend/app/services/ai_pmo/ai_pmo_intent/intent_resolver.py index 22333a45..78d7f954 100644 --- a/backend/app/services/ai_pmo/ai_pmo_intent/intent_resolver.py +++ b/backend/app/services/ai_pmo/ai_pmo_intent/intent_resolver.py @@ -6,53 +6,9 @@ from ..workflow_semantic_planner import WorkflowSemanticPlanner class IntentResolver: """Resolve raw text to canonical intent.""" - OPERATION_VERBS = ( - "新增", - "增加", - "添加", - "加", - "加一个", - "加个", - "新建", - "创建", - "建立", - "立项", - "接入", - "修复", - "优化", - "改成", - "改为", - "更新", - "分配", - "指派", - "归档", - "执行", - ) - - @staticmethod - def _looks_like_negated_write_statement(*, text: str) -> bool: - normalized = re.sub(r"\s+", "", str(text or "")).lower() - if not normalized: - return False - negation_tokens = ("别", "别再", "别乱", "不要", "不要再", "先别", "先不要", "禁止", "不能", "无需", "不用") - if not any(token in normalized for token in negation_tokens): - return False - write_tokens = ("创建", "新建", "新增", "增加", "添加", "加", "接入", "执行", "分配", "指派", "立项") - object_tokens = ("信号", "需求", "任务", "项目", "事项", "工单", "流程", "信号源") - if any(w in normalized for w in write_tokens) and any(o in normalized for o in object_tokens): - return True - return bool( - re.search( - r"(?:别|不要|先别|先不要|禁止|不能).{0,8}(?:创建|新建|新增|增加|添加|加|接入|执行|分配|指派|立项)", - normalized, - re.IGNORECASE, - ) - ) - @staticmethod def resolve(*, text: str, project_id: Optional[int]) -> CanonicalIntent: pretty = re.sub(r"\s+", " ", str(text or "")).strip() - normalized = re.sub(r"\s+", "", str(text or "")).lower() if not pretty: return CanonicalIntent(INTENT_IGNORE, 1.0, project_id, "", "", {"reason": "empty"}) bundle = WorkflowSemanticPlanner.plan_message( @@ -61,17 +17,9 @@ class IntentResolver: metadata={"project_id": project_id}, ) workflow_types = [item.workflow_type for item in bundle.workflows] - if IntentResolver._is_smalltalk_message(pretty=pretty, normalized=normalized): + diagnostics = bundle.diagnostics if isinstance(bundle.diagnostics, dict) else {} + if str(diagnostics.get("message_intent") or "").strip().lower() == "casual_chat": return CanonicalIntent(INTENT_IGNORE, 0.95, project_id, "", "", {"reason": "smalltalk"}) - if IntentResolver._looks_like_negated_write_statement(text=pretty): - return CanonicalIntent( - INTENT_QUERY, - 0.9, - project_id, - "", - pretty[:300], - {"reason": "negated_write_statement"}, - ) explicit_followup_target = FOLLOWUP_WORK_ITEM_ID_RE.search(pretty) if explicit_followup_target is not None: return CanonicalIntent( @@ -86,10 +34,6 @@ class IntentResolver: "semantic_bundle": bundle.to_dict(), }, ) - if any(token in pretty for token in FOLLOWUP_CONFIRM_HINTS): - return CanonicalIntent(INTENT_FOLLOWUP_CONFIRM, 0.82, project_id, "", "", {}) - if any(token in pretty for token in FOLLOWUP_CONTINUE_HINTS): - return CanonicalIntent(INTENT_FOLLOWUP_CONTINUE, 0.82, project_id, "", "", {}) if workflow_types and workflow_types[0] == "followup_flow": followup_action = bundle.workflows[0].workflow_action followup_intent = INTENT_FOLLOWUP_CONFIRM if followup_action == "confirm" else INTENT_FOLLOWUP_CONTINUE @@ -104,7 +48,7 @@ class IntentResolver: "semantic_bundle": bundle.to_dict(), }, ) - if WorkflowSemanticPlanner.looks_like_signal_source_request(pretty): + if "signal_source_flow" in workflow_types: return CanonicalIntent( INTENT_SIGNAL_SOURCE_ONBOARD, 0.92, @@ -151,7 +95,7 @@ class IntentResolver: ), }, ) - if "query_flow" in workflow_types or IntentResolver._looks_like_query(text=pretty): + if "query_flow" in workflow_types: return CanonicalIntent( INTENT_QUERY, 0.88, @@ -169,22 +113,6 @@ class IntentResolver: {"fallback": True, "reason": "default_query_fallback", "semantic_bundle": bundle.to_dict()}, ) - @staticmethod - def _looks_like_signal_create_request(*, text: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - bundle = WorkflowSemanticPlanner.plan_message( - message_text=str(text or ""), - source_system="compat_intent_resolver", - ) - return any(item.workflow_type == "project_signal_flow" for item in bundle.workflows) - - @staticmethod - def _looks_like_task_create_request(*, text: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - bundle = WorkflowSemanticPlanner.plan_message( - message_text=str(text or ""), - source_system="compat_intent_resolver", - ) - return any(item.workflow_type == "task_flow" for item in bundle.workflows) - @staticmethod def _extract_task_title(text: str) -> str: cleaned = re.sub(r"\s+", " ", str(text or "")).strip() @@ -215,27 +143,6 @@ class IntentResolver: return "" return fallback[:120] - @staticmethod - def _is_smalltalk_message(*, pretty: str, normalized: str) -> bool: - _ = normalized - return WorkflowSemanticPlanner._is_smalltalk_message(pretty) - - @staticmethod - def _looks_like_query(*, text: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - bundle = WorkflowSemanticPlanner.plan_message( - message_text=str(text or ""), - source_system="compat_intent_resolver", - ) - return len(bundle.workflows) == 1 and bundle.workflows[0].workflow_type == "query_flow" - - @staticmethod - def _looks_like_project_create_request(*, text: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - bundle = WorkflowSemanticPlanner.plan_message( - message_text=str(text or ""), - source_system="compat_intent_resolver", - ) - return any(item.workflow_type == "new_project_flow" for item in bundle.workflows) - @staticmethod def _build_signal_title(text: str, *, default: str) -> str: raw = str(text or "").strip() diff --git a/backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py b/backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py index 86fd945a..31b81c9c 100644 --- a/backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py +++ b/backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py @@ -1,7 +1,6 @@ from __future__ import annotations from .models import * -from ..workflow_semantic_planner import WorkflowSemanticPlanner class RouteResolver: """Map canonical intent + text into workflow route.""" @@ -58,7 +57,7 @@ class RouteResolver: return 1 @staticmethod - def looks_like_project_signal_discover(*, message_text: str, metadata: dict[str, Any]) -> bool: # semantic-lint: ignore - compat route helper over planner metadata + def requests_project_signal_discover_route(*, message_text: str, metadata: dict[str, Any]) -> bool: explicit_route = str(metadata.get("workflow_route") or "").strip().lower() if isinstance(metadata, dict) else "" if explicit_route == ROUTE_PROJECT_SIGNAL_DISCOVER: return True @@ -66,17 +65,20 @@ class RouteResolver: return explicit_action == ROUTE_ACTION_DISCOVER @staticmethod - def parse_followup_action( + def resolve_followup_route_action( *, metadata: Optional[dict[str, Any]] = None, canonical_intent: str, message_text: str = "", ) -> str: - return WorkflowSemanticPlanner.parse_followup_action( - metadata=metadata, - canonical_intent=canonical_intent, - message_text=message_text, - ) + _ = message_text + payload = metadata if isinstance(metadata, dict) else {} + explicit_action = str(payload.get("followup_action") or payload.get("action") or "").strip().lower() + if explicit_action in {"continue", "confirm", "commit", "cancel", "refresh"}: + return explicit_action + if canonical_intent == INTENT_FOLLOWUP_CONFIRM: + return ROUTE_ACTION_CONFIRM + return ROUTE_ACTION_CONTINUE @classmethod def from_canonical( @@ -91,7 +93,7 @@ class RouteResolver: if not route_key: route_key = LEGACY_INTENT_ROUTE_MAP.get(canonical_intent.name, ROUTE_QUERY_ANSWER) if canonical_intent.name == INTENT_SIGNAL_CREATE: - if cls.looks_like_project_signal_discover(message_text=message_text, metadata=metadata): + if cls.requests_project_signal_discover_route(message_text=message_text, metadata=metadata): route_key = ROUTE_PROJECT_SIGNAL_DISCOVER elif route_key not in {ROUTE_PROJECT_SIGNAL_CREATE, ROUTE_PROJECT_SIGNAL_DISCOVER}: route_key = ROUTE_PROJECT_SIGNAL_CREATE @@ -99,7 +101,7 @@ class RouteResolver: if route_key != ROUTE_TASK_CREATE: route_key = ROUTE_TASK_CREATE elif canonical_intent.name in {INTENT_FOLLOWUP_CONFIRM, INTENT_FOLLOWUP_CONTINUE}: - action = cls.parse_followup_action( + action = cls.resolve_followup_route_action( metadata={**metadata, "followup_context_present": True}, canonical_intent=canonical_intent.name, message_text=message_text, diff --git a/backend/app/services/ai_pmo/ai_pmo_intent/shared.py b/backend/app/services/ai_pmo/ai_pmo_intent/shared.py index 710c92a6..6db12c4e 100644 --- a/backend/app/services/ai_pmo/ai_pmo_intent/shared.py +++ b/backend/app/services/ai_pmo/ai_pmo_intent/shared.py @@ -8,22 +8,10 @@ from typing import Any, Optional from ....utils.project_command_utils import extract_project_create_title from ...signal.signal_source_orchestration import SignalSourceOrchestrationService -from ..workflow_semantic_planner.constants import ( - FOLLOWUP_CANCEL_HINTS, - FOLLOWUP_COMMIT_HINTS, - FOLLOWUP_CONFIRM_HINTS, - FOLLOWUP_CONTINUE_HINTS, - FOLLOWUP_REFRESH_HINTS, - FOLLOWUP_WORK_ITEM_ID_RE, - PROJECT_CREATE_HINTS, - PROJECT_SIGNAL_DISCOVER_HINTS, - QUESTION_HINTS, - SHORT_CONFIRM_HINTS, - SIGNAL_CREATE_HINTS, - SMALLTALK_EXACT, - SMALLTALK_SHORT_HINTS, - SMALLTALK_STRUCTURED_HINTS, - TASK_CREATE_HINTS, + +FOLLOWUP_WORK_ITEM_ID_RE = re.compile( + r"(?:work[_ -]?item|工作项|队列项|草稿)\s*(?:id|ID)?\s*[:#:]?\s*(\d+)", + re.IGNORECASE, ) INTENT_QUERY = "query.read_only" @@ -232,8 +220,6 @@ LEGACY_INTENT_ROUTE_MAP: dict[str, str] = { "signal_source.onboard": ROUTE_SIGNAL_SOURCE_ONBOARD, } -SESSION_SHORT_CONFIRM_WINDOW_SECONDS = 5 * 60 -SHORT_CONFIRM_MAX_MESSAGE_LENGTH = 10 INTENT_LLM_FUNCTION_NAME = "classify_intent_ai_pmo_kernel" INTENT_CANDIDATES = ( INTENT_SIGNAL_SOURCE_ONBOARD, diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py index b483f0e6..5986d877 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py @@ -7,13 +7,6 @@ import re from dataclasses import dataclass from typing import Iterable, Sequence -from ..workflow_semantic_planner.constants import ( - BACKGROUND_MARKERS, - CORRECTION_ACTION_PATTERNS, - INTENT_GROUP_KEYWORDS, - MULTI_SEGMENT_MARKERS, -) - INTENT_FUNCTION_SIMPLE = "classify_intent_ai_pmo_kernel" INTENT_FUNCTION_COMPLEX = "classify_intent_complex" @@ -33,10 +26,6 @@ def _normalize_text(text: str) -> str: return re.sub(r"\s+", "", str(text or "")).lower() -def _normalize_name(name: str) -> str: - return re.sub(r"[\s_\-]+", "", str(name or "")).lower() - - @dataclass(slots=True) class IntentRouteDecision: function_name: str @@ -59,52 +48,9 @@ class CorrectionDecision: confidence: float matched_keywords: list[str] - -def _count_intent_groups(text: str) -> int: - normalized = _normalize_text(text) - hits = 0 - for tokens in INTENT_GROUP_KEYWORDS.values(): - if any(_normalize_name(token) in normalized for token in tokens): - hits += 1 - return hits - - def is_complex_instruction(text: str, *, project_names: Sequence[str] | None = None) -> tuple[bool, list[str]]: - normalized = _normalize_text(text) - if not normalized: - return False, [] - - reasons: list[str] = [] - length_threshold = int(_env_float("AGENT_COMPLEX_LENGTH_THRESHOLD", 20)) - if len(normalized) > length_threshold: - reasons.append("length_gt_threshold") - - has_multi_segment = any(marker in text for marker in MULTI_SEGMENT_MARKERS) - if has_multi_segment: - reasons.append("multi_segment") - - has_background = any(marker in text for marker in BACKGROUND_MARKERS) - if has_background: - reasons.append("has_background") - - intent_group_hits = _count_intent_groups(text) - if intent_group_hits >= 3 or ( - intent_group_hits >= 2 - and ( - len(normalized) > max(14, length_threshold) - or has_multi_segment - or has_background - ) - ): - reasons.append("multi_intent_groups") - - for name in project_names or []: - candidate = _normalize_name(name) - if len(candidate) >= 2 and candidate in normalized: - reasons.append("contains_project_name") - break - - return bool(reasons), reasons + _ = (text, project_names) + return False, [] def select_intent_route(text: str, *, project_names: Sequence[str] | None = None) -> IntentRouteDecision: @@ -125,19 +71,7 @@ def select_intent_route(text: str, *, project_names: Sequence[str] | None = None def resolve_correction(text: str) -> CorrectionDecision: - normalized = _normalize_text(text) - if not normalized: - return CorrectionDecision(action="none", confidence=0.0, matched_keywords=[]) - - for action, tokens in CORRECTION_ACTION_PATTERNS: - matched = [token for token in tokens if _normalize_name(token) in normalized] - if matched: - confidence = 0.9 if len(matched) >= 2 else 0.78 - return CorrectionDecision( - action=action, - confidence=float(min(1.0, confidence)), - matched_keywords=matched, - ) + _ = text return CorrectionDecision(action="none", confidence=0.0, matched_keywords=[]) diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py index 6536d4c3..7acf2fb5 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py @@ -97,10 +97,6 @@ from ..ai_pmo_intent import ( IntentResolver, RouteResolver, ) -from ..workflow_semantic_planner.constants import ( - GENERIC_SIGNAL_SOURCE_MESSAGE_RE, - MODULE_KEYWORD_RULES, -) from ...ai_steward_service import AIStewardService from ...llm_service import llm_service from ...model.model_context_bundle import ModelContextBundleService diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py index 12fb3258..9d06e975 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_mixins_parse.py @@ -68,7 +68,7 @@ class TypedExecutorParseMixin: return "".join(str(value or "").split()).strip().lower() @classmethod - def _looks_like_raw_instruction(cls, *, candidate: str, source: str) -> bool: + def _candidate_echoes_source_instruction(cls, *, candidate: str, source: str) -> bool: normalized_candidate = cls._compact_text(candidate) normalized_source = cls._compact_text(source) if not normalized_candidate or not normalized_source: @@ -225,9 +225,9 @@ class TypedExecutorParseMixin: summary = cls._normalize_short_text(row.get("summary"), max_len=300) content = cls._normalize_short_text(row.get("content"), max_len=1800) - if cls._looks_like_raw_instruction(candidate=title, source=cleaned_message): + if cls._candidate_echoes_source_instruction(candidate=title, source=cleaned_message): title = "" - if cls._looks_like_raw_instruction(candidate=summary, source=cleaned_message): + if cls._candidate_echoes_source_instruction(candidate=summary, source=cleaned_message): summary = "" if not summary: diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_data.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_data.py index c8a2adae..7ae37484 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_data.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_data.py @@ -61,18 +61,15 @@ class KernelClosureDataMixin: if not question or not answer: return -1.0 score = 0.0 - merged = f"{question} {answer}" - if "信号源" in merged: - score += 3.0 - if any(token in merged for token in ("模块", "后端", "前端", "流程", "SSTC", "任务", "执行", "接口")): - score += 2.0 if any(token in answer for token in ("暂时无法生成可靠回答", "模型服务异常", "请稍后重试")): score -= 5.0 - if normalized_message and cls._is_generic_signal_source_message(normalized_message): - if "信号源" in question: + if normalized_message: + question_norm = cls._normalize_dialogue_text(question, max_len=500) + answer_norm = cls._normalize_dialogue_text(answer, max_len=900) + if question_norm and (normalized_message in question_norm or question_norm in normalized_message): score += 1.5 - if "项目" in answer: - score += 1.0 + if answer_norm and (normalized_message in answer_norm or answer_norm in normalized_message): + score += 0.8 created_at = cls._parse_datetime_like(getattr(row, "created_at", None)) if reference_at is not None and created_at is not None: gap_seconds = abs((reference_at - created_at).total_seconds()) diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py index 3d448c96..8889c3ed 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py @@ -13,21 +13,13 @@ for _name, _value in vars(_common).items(): class KernelClosurePayloadMixin: @staticmethod def _is_generic_signal_source_message(text: str) -> bool: - compact = "".join(str(text or "").split()).strip().lower() - if not compact: - return False - if bool(GENERIC_SIGNAL_SOURCE_MESSAGE_RE.match(compact)): - return True - return len(compact) <= 24 and "信号源" in compact and ("这个" in compact or "步骤" in compact) + _ = text + return False @classmethod def _extract_implementation_modules_from_text(cls, text: str) -> list[str]: - normalized = str(text or "").lower() - modules: list[str] = [] - for module_name, keywords in MODULE_KEYWORD_RULES: - if any(str(keyword).lower() in normalized for keyword in keywords): - modules.append(module_name) - return modules[:6] + _ = (cls, text) + return [] @staticmethod def _build_closure_dialogue_excerpt(turns: list[dict[str, str]]) -> str: @@ -72,11 +64,6 @@ class KernelClosurePayloadMixin: return f"机器人先提示条件不足,用户随后继续要求执行:{latest_user[:120]}" return "机器人提示条件不足,需补齐项目归属后继续执行信号源接入。" - if latest_user and cls._is_generic_signal_source_message(latest_user) and modules: - if len(modules) == 1 and str(modules[0]).strip() == "信号源接入": - return "用户要求将需求按步骤接入信号源,需要先补齐项目归属再继续执行。" - return f"同一消息闭环内,用户要求将需求按步骤接入信号源,重点模块:{'、'.join(modules[:4])}。" - if modules: return f"用户在本次消息闭环中推进信号源接入,涉及模块:{'、'.join(modules[:4])}。" @@ -136,7 +123,6 @@ class KernelClosurePayloadMixin: normalized_source_system == "feishu" and ( (not self._turns_have_assistant(turns)) - or (self._is_generic_signal_source_message(normalized_message) and len(turns) <= 2) ) ) if needs_semantic_turns: diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_routing.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_routing.py index a035e7ab..8981091c 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_routing.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_routing.py @@ -121,7 +121,7 @@ class KernelFollowupRoutingMixin: workflow_route: WorkflowRoute, ) -> tuple[dict[str, Any], dict[str, Any]]: explicit_target_id = self._extract_followup_work_item_id(message_text) - followup_action = RouteResolver.parse_followup_action( + followup_action = RouteResolver.resolve_followup_route_action( message_text=message_text, canonical_intent=intent.name, ) diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py index 4772f6de..c5272e5d 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py @@ -70,7 +70,7 @@ class KernelIngressRenderMixin: direct_reply_text = f"{mixed_sidecar_reply}\n\n{direct_reply_text}" else: direct_reply_text = mixed_sidecar_reply - if _looks_like_upstream_timeout_message(direct_reply_text): + if _is_upstream_timeout_message(direct_reply_text): logger.warning( "ai_pmo_upstream_timeout_reply_detected", tenant_id=int(self.tenant_id), @@ -288,8 +288,6 @@ class KernelIngressRenderMixin: has_webhook = "webhook" in normalized if not (has_long_connection and has_webhook): return None - if not any(token in normalized for token in TRANSPORT_MODE_CORRECTION_HINTS): - return None return "已按长连接模式处理飞书接入,当前不会走 webhook;这条说明不会再被当成信号源类型变更。" @staticmethod diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py index e022d1b5..2ad33c51 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py @@ -21,15 +21,17 @@ class KernelIntentLLMMixin: rule_intent: Optional[CanonicalIntent] = None, session_last_intent_override: Optional[str] = None, session_project_id_override: Optional[int] = None, + inherit_session_context: bool = True, ) -> Optional[CanonicalIntent]: - session_last_intent = ( - str(session_last_intent_override or "").strip() - if session_last_intent_override is not None - else str(getattr(session, "last_intent", "") or "").strip() if session is not None else "" - ) + if session_last_intent_override is not None: + session_last_intent = str(session_last_intent_override or "").strip() + elif inherit_session_context and session is not None: + session_last_intent = str(getattr(session, "last_intent", "") or "").strip() + else: + session_last_intent = "" if session_project_id_override is not None: session_project_id = _safe_int(session_project_id_override) - elif session is not None: + elif inherit_session_context and session is not None: session_project_id = _safe_int(getattr(session, "current_project_id", None)) else: session_project_id = None diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py index 79b0e491..44bf7b91 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py @@ -11,26 +11,6 @@ for _name, _value in vars(_common).items(): class KernelIntentResolveMixin: - _QUERY_FOLLOWUP_TOKENS = ( - "需求", - "mvp", - "方案", - "对应", - "这个", - "它", - "给我", - "补充", - "展开", - "细化", - "重答", - "重新回答", - "高级模型", - "更强模型", - "分析", - "评审", - "怎么做", - ) - @staticmethod def _within_recent_window(last_active_at: Optional[datetime], *, window_seconds: int) -> bool: if not isinstance(last_active_at, datetime): @@ -38,12 +18,10 @@ class KernelIntentResolveMixin: delta = _utc_now_naive() - last_active_at return 0 <= delta.total_seconds() <= float(window_seconds) - @classmethod - def _looks_like_query_scene_followup(cls, message_text: str) -> bool: - normalized = re.sub(r"\s+", "", str(message_text or "")).lower() - if not normalized: - return False - return any(token in normalized for token in cls._QUERY_FOLLOWUP_TOKENS) + @staticmethod + def _is_query_context_intent(intent_name: str) -> bool: + normalized = str(intent_name or "").strip().lower() + return normalized in {INTENT_QUERY, ROUTE_QUERY_ANSWER} @classmethod def _should_inherit_recent_feishu_session_context( @@ -52,21 +30,12 @@ class KernelIntentResolveMixin: message_text: str, session: Optional[ConversationSession], ) -> bool: - if session is None: - return False - if WorkflowSemanticPlanner.looks_like_explicit_new_requirement_topic(message_text): - return False - sender = str(getattr(session, "sender", "") or "").strip().lower() - if not sender.startswith("feishu:"): - return False - if not cls._within_recent_window( - getattr(session, "last_active_at", None), - window_seconds=SESSION_PROJECT_INHERIT_WINDOW_SECONDS, - ): - return False - if not str(getattr(session, "last_intent", "") or "").strip(): - return False - return cls._looks_like_query_scene_followup(message_text) + _ = (cls, message_text, session) + # OpenCow-style object binding does not allow bare recent-session query + # inheritance without an explicit bound object. Query context may still + # flow through reply anchors, but no longer through no-anchor token + # heuristics on the latest message text. + return False async def _resolve_intent( self, @@ -86,28 +55,6 @@ class KernelIntentResolveMixin: message_text=message_text, session=session, ) - if self._is_short_confirmation_message( - message_text=message_text, - session=session, - message_sent_at=message_sent_at, - session_snapshot=session_snapshot, - reply_anchor_present=reply_anchor_present, - ) and session is not None: - inherited = self._build_short_confirm_intent( - message_text=message_text, - session=session, - world_model=world_model, - session_snapshot=session_snapshot, - ) - logger.info( - "ai_pmo_short_confirm_inherited", - tenant_id=self.tenant_id, - sender=str(getattr(session, "sender", "") or ""), - inherited_from=str(getattr(session, "last_intent", "") or ""), - inherited_intent=str(inherited.name or ""), - project_id=_safe_int(getattr(session, "current_project_id", None)) or _safe_int(world_model.get("resolved_project_id")), - ) - return inherited, "session_inherited" # 模型唯一入口:不再使用规则/正则意图分流。 if not self._intent_llm_enabled(): @@ -124,6 +71,7 @@ class KernelIntentResolveMixin: session_project_id_override=( _safe_int(getattr(session, "current_project_id", None)) if inherit_session_context else None ), + inherit_session_context=inherit_session_context, ) if llm_intent is None: raise RuntimeError("intent_model_only_no_valid_llm_intent") diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py index 12dca14f..37bac352 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py @@ -68,10 +68,6 @@ class KernelIntentRulesMixin: or any(token in normalized for token in ("怎么", "如何", "为何", "为什么", "是什么", "有哪些", "吗", "呢", "是否", "是不是")) ) - @classmethod - def _looks_like_contextual_write_confirmation(cls, *, message_text: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - return WorkflowSemanticPlanner.looks_like_contextual_write_confirmation(message_text) - def _recover_session_followup_write_intent( self, *, @@ -80,47 +76,13 @@ class KernelIntentRulesMixin: world_model: dict[str, Any], rule_intent: CanonicalIntent, ) -> Optional[CanonicalIntent]: - if session is None: - return None - if rule_intent.name != INTENT_QUERY: - return None - if not self._within_recent_window( - getattr(session, "last_active_at", None), - window_seconds=SESSION_SHORT_CONFIRM_WINDOW_SECONDS, - ): - return None - if not (self._is_default_rule_query(rule_intent) or float(rule_intent.confidence or 0.0) <= 0.7): - return None - if not self._looks_like_contextual_write_confirmation(message_text=message_text): - return None - - inherited_name = str(getattr(session, "last_intent", "") or "").strip() - inherited_text = str(getattr(session, "last_message_text", "") or "").strip() - if inherited_name not in WRITE_INTENTS and inherited_text: - inherited_project_id = _safe_int(getattr(session, "current_project_id", None)) - inferred = self.intent_resolver.resolve(text=inherited_text, project_id=inherited_project_id) - if inferred.name in WRITE_INTENTS: - inherited_name = inferred.name - - if inherited_name not in WRITE_INTENTS: - return None - - project_id = _safe_int(getattr(session, "current_project_id", None)) or _safe_int(world_model.get("resolved_project_id")) - if project_id is not None and int(project_id) <= 0: - project_id = None - - canonical_text = inherited_text or message_text - return self._build_canonical_intent( - intent_name=inherited_name, - message_text=canonical_text, - project_id=project_id, - confidence=0.84, - metadata={ - "context_source": "session_write_recovered", - "inherited_from_intent": str(getattr(session, "last_intent", "") or "").strip(), - "trigger_message": re.sub(r"\s+", " ", str(message_text or "")).strip()[:120], - }, - ) + _ = (message_text, session, world_model, rule_intent) + # OpenCow-style routing requires object binding before any write action. + # This compat recovery path used to elevate low-context session followups + # into write intents without a bound object, which is exactly the class of + # bug we are removing. Keep the method as an inert compat shim so callers + # don't break, but never recover a write intent from session state alone. + return None @staticmethod def _has_required_slots(*, intent: CanonicalIntent, message_text: str) -> tuple[bool, dict[str, Any]]: @@ -223,40 +185,6 @@ class KernelIntentRulesMixin: arbitration_llm_confidence=float(llm_intent.confidence or 0.0), ), source - @classmethod - def _is_short_confirmation_message( - cls, - *, - message_text: str, - session: Optional[ConversationSession], - message_sent_at: Optional[Any] = None, - session_snapshot: Optional[dict[str, Any]] = None, - reply_anchor_present: bool = False, - ) -> bool: - if not reply_anchor_present: - return False - snapshot = session_snapshot if isinstance(session_snapshot, dict) else {} - if session is None and not snapshot: - return False - last_active_at = cls._coerce_naive_datetime(snapshot.get("last_active_at")) - if last_active_at is None and session is not None: - last_active_at = cls._coerce_naive_datetime(getattr(session, "last_active_at", None)) - sent_at = cls._coerce_naive_datetime(message_sent_at) or _utc_now_naive() - if last_active_at is None: - return False - delta = sent_at - last_active_at - if not (0 <= delta.total_seconds() <= float(SESSION_SHORT_CONFIRM_WINDOW_SECONDS)): - return False - last_intent = str(snapshot.get("last_intent") or (getattr(session, "last_intent", "") if session is not None else "") or "").strip() - if not last_intent: - return False - normalized = re.sub(r"\s+", "", str(message_text or "")).lower() - if not normalized: - return False - if len(normalized) > SHORT_CONFIRM_MAX_MESSAGE_LENGTH: - return False - return any(token in normalized for token in SHORT_CONFIRM_HINTS) - @staticmethod def _coerce_naive_datetime(value: Any) -> Optional[datetime]: if isinstance(value, datetime): @@ -343,39 +271,3 @@ class KernelIntentRulesMixin: extras, ) return CanonicalIntent(INTENT_IGNORE, safe_confidence, project_id, "", "", extras or {"reason": "unsupported_intent"}) - - def _build_short_confirm_intent( - self, - *, - message_text: str, - session: ConversationSession, - world_model: dict[str, Any], - session_snapshot: Optional[dict[str, Any]] = None, - ) -> CanonicalIntent: - snapshot = session_snapshot if isinstance(session_snapshot, dict) else {} - inherited_name = str(snapshot.get("last_intent") or getattr(session, "last_intent", "") or "").strip() - if inherited_name not in INTENT_CANDIDATES: - inherited_name = INTENT_QUERY - snapshot_project_id = _safe_int(snapshot.get("current_project_id")) - inherited_project_id = ( - snapshot_project_id - or _safe_int(getattr(session, "current_project_id", None)) - or _safe_int(world_model.get("resolved_project_id")) - ) - if inherited_project_id is not None and int(inherited_project_id) <= 0: - inherited_project_id = None - inherited_text = ( - str(snapshot.get("last_message_text") or "").strip() - or str(getattr(session, "last_message_text", "") or "").strip() - or message_text - ) - return self._build_canonical_intent( - intent_name=inherited_name, - message_text=inherited_text, - project_id=inherited_project_id, - confidence=0.86, - metadata={ - "context_source": "session_inherited", - "inherited_from_intent": str(snapshot.get("last_intent") or getattr(session, "last_intent", "") or "").strip(), - }, - ) diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py index 30cf355e..9d56416f 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py @@ -3,6 +3,7 @@ from __future__ import annotations from .... import ai_pmo_kernel_common_core as _common +from ....workflow_semantic_planner import WorkflowSemanticPlanner from app.utils.thread_context import build_feishu_thread_key for _name, _value in vars(_common).items(): @@ -19,18 +20,14 @@ class KernelWorldModelSessionMixin: delta = now - last_active_at return 0 <= delta.total_seconds() <= float(window_seconds) - def _extract_inherited_project_id(self, *, session: Optional[ConversationSession]) -> Optional[int]: - if session is None: - return None - session_project_id = _safe_int(getattr(session, "current_project_id", None)) - if session_project_id is None or int(session_project_id) <= 0: - return None - if not self._within_recent_window( - getattr(session, "last_active_at", None), - window_seconds=SESSION_PROJECT_INHERIT_WINDOW_SECONDS, - ): - return None - return session_project_id + def _extract_inherited_project_id( + self, + *, + session: Optional[ConversationSession], + message_text: str = "", + ) -> Optional[int]: + _ = (session, message_text) + return None async def _get_or_create_session( self, diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/constants.py b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/constants.py index 4eab709b..c07c022c 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/constants.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/constants.py @@ -3,12 +3,6 @@ from __future__ import annotations from .imports import re -from ..workflow_semantic_planner.constants import ( - GENERIC_SIGNAL_SOURCE_MESSAGE_RE, - MODULE_KEYWORD_RULES, - POLICY_GATE_RULES, - TRANSPORT_MODE_CORRECTION_HINTS, -) STATE_RECEIVED = "received" STATE_MODELED = "modeled" @@ -37,3 +31,7 @@ INTENT_LLM_STAGE2_MIN_REMAINING_DEFAULT_SECONDS = 12.0 WORLD_MODEL_STATIC_CACHE_TTL_SECONDS = 10 * 60 WORLD_MODEL_STATIC_CACHE_KEY_PREFIX = "wm:static" + +# OpenCow-style routing no longer relies on centralized semantic token warehouses. +# Keep the policy gate input structural-only unless a caller passes explicit gates. +POLICY_GATE_RULES: tuple[tuple[str, tuple[str, ...]], ...] = () diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/helpers.py b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/helpers.py index 017d0dfd..84203c88 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/helpers.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/helpers.py @@ -2,7 +2,6 @@ from __future__ import annotations -from .constants import TRANSPORT_MODE_CORRECTION_HINTS from .env import ( env_bool, env_float, @@ -42,7 +41,7 @@ def _intent_llm_stage2_min_remaining_seconds() -> float: return intent_llm_stage2_min_remaining_seconds() -def _looks_like_upstream_timeout_message(text: str) -> bool: +def _is_upstream_timeout_message(text: str) -> bool: lowered = str(text or "").strip().lower() if not lowered: return False @@ -55,12 +54,8 @@ def _looks_like_upstream_timeout_message(text: str) -> bool: def _should_force_low_tier_intent_route(*, message_text: str, correction_action: str) -> bool: - if str(correction_action or "").strip().lower() != "none": - return True - normalized = re.sub(r"\s+", "", str(message_text or "")).lower() - if not normalized: - return False - return any(token in normalized for token in TRANSPORT_MODE_CORRECTION_HINTS) + _ = message_text + return str(correction_action or "").strip().lower() != "none" def _should_escalate_to_high_intent_route( diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py index ee252eb9..043f07ed 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py @@ -172,9 +172,6 @@ from ..ai_pmo_intent import ( ROUTE_SIGNAL_SOURCE_ONBOARD, ROUTE_TASK_CREATE, WORKFLOW_ROUTE_KEYS, - SESSION_SHORT_CONFIRM_WINDOW_SECONDS, - SHORT_CONFIRM_HINTS, - SHORT_CONFIRM_MAX_MESSAGE_LENGTH, WRITE_INTENTS, CanonicalIntent, IntentResolver, diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py index 9315d67a..a8b33231 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py @@ -16,9 +16,6 @@ from app.services.ai_pmo.workflow_semantic_planner.constants import ( from app.services.mounted_ai_runtime_write_service import MountedAIRuntimeWriteService class KernelIngressFlowEntryMixin: - _MIXED_HINT_CONNECTORS = ("顺便", "另外", "同时", "并且", "还有", "再问") - _MIXED_HINT_QUESTION = ("?", "?", "问下", "多少", "几个", "进展", "状态", "吗", "呢", "如何", "怎么", "有没有") - _MIXED_HINT_REQUIREMENT = ("创建", "新建", "新增", "安排", "执行", "处理", "接入", "录入", "添加") _RAG_QUERY_SCENE_KEYS = ( "message_intent", "review_framework", @@ -32,48 +29,6 @@ class KernelIngressFlowEntryMixin: QUERY_SCENE_CORRECTION_ESCALATION, QUERY_SCENE_REQUIREMENT_PLAN, }) - _QUERY_SCENE_TOKEN_RULES = ( - ( - QUERY_SCENE_CORRECTION_ESCALATION, - { - "consultant_mode_preference": "challenge", - "force_thinking_mode": "deep", - }, - ("不对", "错了", "答错", "纠正", "更正", "重新回答", "重答", "高级模型", "更强模型"), - ), - ( - QUERY_SCENE_BUSINESS_REVIEW, - { - "review_framework": "five_lenses", - "consultant_mode_preference": "clarify", - }, - ("分析", "拆解", "评审", "评估", "复盘", "诊断", "怎么做", "mvp", "需求"), - ), - ( - QUERY_SCENE_BUSINESS_REVIEW, - { - "consultant_mode_preference": "advise", - "advise_subtype": "assignee", - }, - ("谁来做", "谁负责", "安排谁", "推荐谁", "负责人"), - ), - ( - QUERY_SCENE_REQUIREMENT_PLAN, - { - "consultant_mode_preference": "clarify", - "topic_switch": "new", - }, - ("新需求", "新项目", "做个", "想做个", "准备做", "创建需求"), - ), - ( - QUERY_SCENE_REQUIREMENT_PLAN, - { - "consultant_mode_preference": "clarify", - }, - ("需求", "方案", "范围", "排期", "补充", "细化", "展开", "mvp"), - ), - ) - @classmethod def _extract_route_scene_metadata(cls, route_metadata: Optional[dict[str, Any]]) -> dict[str, str]: metadata = route_metadata if isinstance(route_metadata, dict) else {} @@ -86,35 +41,8 @@ class KernelIngressFlowEntryMixin: @classmethod def _derive_query_scene_metadata(cls, text: str) -> dict[str, Any]: - normalized = re.sub(r"\s+", "", str(text or "")).lower() - if not normalized: - return {} - if any(token in normalized for token in ("最近怎么样", "在吗", "你好", "哈哈", "hi", "hello")): - return { - "message_intent": "casual_chat", - "consultant_mode_preference": "brainstorm", - } - for message_intent, extra_fields, tokens in cls._QUERY_SCENE_TOKEN_RULES: - if any(token in normalized for token in tokens): - return { - "message_intent": message_intent, - **extra_fields, - } return {} - @classmethod - def _looks_like_mixed_candidate(cls, *, text: str) -> bool: - normalized = re.sub(r"\s+", "", str(text or "")).lower() - if not normalized: - return False - has_question = any(token in normalized for token in cls._MIXED_HINT_QUESTION) - has_requirement = any(token in normalized for token in cls._MIXED_HINT_REQUIREMENT) - if not (has_question and has_requirement): - return False - if any(token in normalized for token in cls._MIXED_HINT_CONNECTORS): - return True - return "问" in normalized and "创建" in normalized - @staticmethod def _supports_mixed_sidecar(*, source_system: str) -> bool: return str(source_system or "").strip().lower() in {"feishu", "pmflow_manual", "ai_pmo_ui"} @@ -129,12 +57,8 @@ class KernelIngressFlowEntryMixin: semantic_bundle: Optional[dict[str, Any]], ) -> dict[str, Any]: context = rag_request_context if isinstance(rag_request_context, dict) else {} - derived = cls._derive_query_scene_metadata(message_text) - scene_metadata: dict[str, Any] = { - key: value - for key, value in derived.items() - if key in cls._RAG_QUERY_SCENE_KEYS and value not in (None, "") - } + _ = message_text + scene_metadata: dict[str, Any] = {} for key in cls._RAG_QUERY_SCENE_KEYS: value = context.get(key) if value in (None, ""): @@ -276,14 +200,15 @@ class KernelIngressFlowEntryMixin: message_text: str, project_hint_id: Optional[int], ) -> tuple[str, dict[str, Any]]: + normalized_text = str(message_text or "").strip() if not self._supports_mixed_sidecar(source_system=source_system): - return str(message_text or "").strip(), {} - if not self._looks_like_mixed_candidate(text=message_text): - return str(message_text or "").strip(), {} + return normalized_text, {} + if not normalized_text: + return normalized_text, {} rag_service = AdaptiveRAGService(self.db, tenant_id=self.tenant_id) try: - intent_result = await rag_service.classify_intent(str(message_text or "").strip()) + intent_result = await rag_service.classify_intent(normalized_text) except Exception as exc: logger.warning( "ai_pmo_mixed_precheck_failed", @@ -291,14 +216,14 @@ class KernelIngressFlowEntryMixin: source_system=source_system, error=str(exc), ) - return str(message_text or "").strip(), {} + return normalized_text, {} intent_label = str(intent_result.get("intent") or "").strip().lower() if intent_label != "mixed": - return str(message_text or "").strip(), {} + return normalized_text, {} - question_part = str(intent_result.get("question_part") or "").strip() or str(message_text or "").strip() - signal_part = str(intent_result.get("statement_part") or "").strip() or str(message_text or "").strip() + question_part = str(intent_result.get("question_part") or "").strip() or normalized_text + signal_part = str(intent_result.get("statement_part") or "").strip() or normalized_text sidecar_answer = "" try: conversation_key = source_message_id @@ -645,6 +570,19 @@ class KernelIngressFlowEntryMixin: ingest_event=ingest_event, ingress_options=ingress_options, ) + active_context_followup_result = await self._maybe_handle_active_context_followup( + source_system=source_system, + source_message_id=synthetic_message_id, + channel_ref=channel_ref, + source_sender_open_id=source_sender_open_id, + message_text=clean_text, + thread_context=thread_context, + sender=sender, + force_bind=True, + ) + if active_context_followup_result is not None: + await _safe_finalize_mounted_runtime(result=active_context_followup_result) + return active_context_followup_result if await self._should_route_to_draft_confirmation_session( session=session, sender=sender, @@ -727,26 +665,35 @@ class KernelIngressFlowEntryMixin: source_sender_open_id=source_sender_open_id, ingress_options=ingress_options, message_text=clean_text, + session=session, + sender=sender, ) if orphan_requirement_followup_result is not None: await _safe_finalize_mounted_runtime(result=orphan_requirement_followup_result) return orphan_requirement_followup_result - mixed_meta: dict[str, Any] = {} - if not self._is_short_confirmation_message( + active_context_followup_result = await self._maybe_handle_active_context_followup( + source_system=source_system, + source_message_id=synthetic_message_id, + channel_ref=channel_ref, + source_sender_open_id=source_sender_open_id, message_text=clean_text, - session=session, - message_sent_at=message_sent_at, - session_snapshot=session_snapshot, - ): - clean_text, mixed_meta = await self._prepare_mixed_sidecar_context( - source_system=source_system, - source_message_id=synthetic_message_id, - channel_ref=channel_ref, - source_sender_open_id=source_sender_open_id, - thread_context=thread_context, - message_text=clean_text, - project_hint_id=project_hint_id, - ) + thread_context=thread_context, + sender=sender, + force_bind=False, + ) + if active_context_followup_result is not None: + await _safe_finalize_mounted_runtime(result=active_context_followup_result) + return active_context_followup_result + mixed_meta: dict[str, Any] = {} + clean_text, mixed_meta = await self._prepare_mixed_sidecar_context( + source_system=source_system, + source_message_id=synthetic_message_id, + channel_ref=channel_ref, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + message_text=clean_text, + project_hint_id=project_hint_id, + ) perf_work_item_id = int(work_item.id) try: stage_started_at = time.perf_counter() @@ -1307,6 +1254,37 @@ class KernelIngressFlowEntryMixin: thread_context = cls._extract_thread_context_from_ingress_options(ingress_options) return bool(str(thread_context.get("reply_to_message_id") or "").strip()) + @staticmethod + async def _should_skip_no_anchor_draft_routing_by_bundle( + *, + message_text: str, + source_system: str, + ) -> bool: + text = str(message_text or "").strip() + if not text: + return False + try: + bundle = await WorkflowSemanticPlanner.aplan_message( + message_text=text, + source_system=str(source_system or "").strip() or "unknown", + metadata={}, + ) + except Exception: + return False + workflows = list(getattr(bundle, "workflows", []) or []) + if any( + str(getattr(item, "workflow_type", "") or "").strip() + in {"new_project_flow", "project_flow", "task_flow", "signal_source_flow", "project_signal_flow"} + for item in workflows + ): + return True + diagnostics = getattr(bundle, "diagnostics", None) + if not isinstance(diagnostics, dict): + return False + message_intent = str(diagnostics.get("message_intent") or "").strip().lower() + topic_switch = str(diagnostics.get("topic_switch") or "").strip().lower() + return message_intent == QUERY_SCENE_REQUIREMENT_PLAN and topic_switch == "new" + @classmethod def _should_route_to_draft_confirmation( cls, @@ -1321,31 +1299,19 @@ class KernelIngressFlowEntryMixin: if DraftConfirmHandler._extract_work_item_id(message_text) is not None: return True - thread_context = cls._extract_thread_context_from_ingress_options(ingress_options) - action, _ = DraftConfirmHandler.parse_action_from_text(message_text=message_text) - if str(thread_context.get("reply_to_message_id") or "").strip(): - return action in { - DraftConfirmHandler._ACTION_CONFIRM, - DraftConfirmHandler._ACTION_REVISE, - DraftConfirmHandler._ACTION_CANCEL, - } or WorkflowSemanticPlanner.looks_like_natural_draft_revision(message_text) - - if WorkflowSemanticPlanner.looks_like_project_create_request(message_text): - return False if ( - WorkflowSemanticPlanner.looks_like_signal_source_request(message_text) - or WorkflowSemanticPlanner.looks_like_signal_create_request(message_text) - or WorkflowSemanticPlanner.looks_like_task_create_request(message_text) - or WorkflowSemanticPlanner.is_legacy_business_command(message_text) - or WorkflowSemanticPlanner.is_query_like_question(message_text) - or WorkflowSemanticPlanner.looks_like_project_lookup_question(message_text) + str(getattr(session, "last_intent", "") or "").strip() != STATE_DRAFT_PENDING_CONFIRM + and len(str(message_text or "").strip()) <= 16 ): return False - return ( - str(getattr(session, "last_intent", "") or "").strip() == STATE_DRAFT_PENDING_CONFIRM - and WorkflowSemanticPlanner.looks_like_natural_draft_revision(message_text) - ) + thread_context = cls._extract_thread_context_from_ingress_options(ingress_options) + if str(thread_context.get("reply_to_message_id") or "").strip(): + if str(getattr(session, "last_intent", "") or "").strip() != STATE_DRAFT_PENDING_CONFIRM: + return False + return not str(message_text or "").strip().endswith(("?", "?")) + + return False async def _should_route_to_draft_confirmation_session( self, @@ -1358,34 +1324,290 @@ class KernelIngressFlowEntryMixin: message_text: str, ingress_options: Optional[dict[str, Any]], ) -> bool: - if WorkflowSemanticPlanner.looks_like_explicit_new_requirement_topic(message_text): - return False + from ..draft_confirm_handler import DraftConfirmHandler + + handler = DraftConfirmHandler(self) + thread_context = self._extract_thread_context_from_ingress_options(ingress_options) + normalized_text = str(message_text or "").strip() + if str(thread_context.get("reply_to_message_id") or "").strip(): + if str(getattr(session, "last_intent", "") or "").strip() == STATE_DRAFT_PENDING_CONFIRM: + return True + return await handler.has_active_draft_session( + sender=sender, + source_system=source_system, + channel_ref=channel_ref, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + ) if self._should_route_to_draft_confirmation( session=session, message_text=message_text, ingress_options=ingress_options, ): return True - - from ..draft_confirm_handler import DraftConfirmHandler - - action, _ = DraftConfirmHandler.parse_action_from_text(message_text=message_text) - if action == DraftConfirmHandler._ACTION_UNKNOWN and not WorkflowSemanticPlanner.looks_like_natural_draft_revision(message_text): + if not normalized_text: return False + if await self._should_skip_no_anchor_draft_routing_by_bundle( + message_text=normalized_text, + source_system=source_system, + ): + return False + if str(getattr(session, "last_intent", "") or "").strip() == STATE_DRAFT_PENDING_CONFIRM: + return True - handler = DraftConfirmHandler(self) - return await handler.has_active_draft_session( + has_active_draft_session = await handler.has_active_draft_session( sender=sender, source_system=source_system, channel_ref=channel_ref, source_sender_open_id=source_sender_open_id, - thread_context=self._extract_thread_context_from_ingress_options(ingress_options), + thread_context=thread_context, ) + return bool(has_active_draft_session) @staticmethod def _build_orphan_requirement_followup_reply() -> str: return "当前没有进行中的 A/B/C 收敛轮次,也没有待确认草稿。你先发一句新需求,我按新的 topic 接着收。" + @staticmethod + def _derive_active_context_kind( + *, + topic: Any, + draft: Any, + ) -> str: + draft_state = str(getattr(draft, "state", "") or "").strip().lower() + plan_status = str(getattr(topic, "plan_status", "") or "").strip().lower() + if draft_state in {"ready", STATE_DRAFT_PENDING_CONFIRM} or plan_status in { + STATE_DRAFT_PENDING_CONFIRM, + "awaiting_execute_confirm", + "plan_ready", + "draft_ready", + }: + return "draft_pending_confirm" + return "requirement_plan" + + async def _bind_active_context_target( + self, + *, + source_system: str, + source_message_id: str, + channel_ref: Optional[str], + source_sender_open_id: Optional[str], + thread_context: Optional[dict[str, Any]], + sender: Optional[str], + conversation_key: str, + ) -> Optional[tuple[Any, Any, str]]: + from ..draft_confirm_handler import DraftConfirmHandler + + handler = DraftConfirmHandler(self) + try: + active_mounted = await handler._resolve_active_mounted_draft( + sender=str(sender or ""), + source_system=source_system, + channel_ref=channel_ref, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + ) + except AttributeError: + if hasattr(self.db, "execute"): + raise + active_mounted = None + if active_mounted is not None: + draft, topic = active_mounted + return topic, draft, self._derive_active_context_kind(topic=topic, draft=draft) + + finder = getattr(self, "_find_active_requirement_plan_workspace", None) + if not callable(finder): + return None + topic, draft = await finder( + source_system=source_system, + source_message_id=source_message_id, + source_chat_id=channel_ref, + sender=None, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + conversation_key=conversation_key, + ) + if topic is None or draft is None: + return None + return topic, draft, self._derive_active_context_kind(topic=topic, draft=draft) + + @staticmethod + def _build_active_context_direct_reply_result(*, answer: str, reason_code: str) -> dict[str, Any]: + text = str(answer or "").strip() + return { + "handled": True, + "status": "answered", + "classification": "question", + "linked_project_ids": [], + "signal_id": None, + "created_project_id": None, + "auto_tasks": [], + "ai_trace_id": None, + "results": [], + "intent": INTENT_QUERY, + "direct_reply_required": True, + "direct_reply_text": text, + "reply_template_key": reason_code, + "work_item_id": None, + "decision_level": None, + "review_summary": None, + "workflow_route": ROUTE_QUERY_ANSWER, + } + + @staticmethod + def _extract_direct_answer_text(payload: Optional[dict[str, Any]]) -> str: + if not isinstance(payload, dict): + return "" + direct_reply = str(payload.get("direct_reply_text") or "").strip() + if direct_reply: + return direct_reply + answer = str(payload.get("answer") or "").strip() + if answer: + return answer + rag_result = payload.get("rag_result") + if isinstance(rag_result, dict): + return str(rag_result.get("answer") or "").strip() + return "" + + async def _maybe_handle_active_context_followup( + self, + *, + source_system: str, + source_message_id: str, + channel_ref: Optional[str], + source_sender_open_id: Optional[str], + message_text: str, + thread_context: Optional[dict[str, Any]], + sender: Optional[str], + force_bind: bool = False, + ) -> Optional[dict[str, Any]]: + if not str(message_text or "").strip(): + return None + is_feishu_source = str(source_system or "").strip().lower() == "feishu" + conversation_key = ( + build_feishu_conversation_scope_key( + source_chat_id=channel_ref, + source_sender_open_id=source_sender_open_id, + sender=None, + ) + if is_feishu_source + else source_message_id + ) + bound_context = await self._bind_active_context_target( + source_system=source_system, + source_message_id=source_message_id, + channel_ref=channel_ref, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + conversation_key=conversation_key, + sender=sender, + ) + if bound_context is None: + return None + topic, draft, active_context_kind = bound_context + try: + packet = await SemanticContextAssembler(self.db).assemble( + tenant_id=int(self.tenant_id), + operator_user_id=0, + topic_workspace_id=int(getattr(topic, "id")), + latest_user_message=message_text, + ingress_context={ + "candidate_kind": "active_context_followup", + "source_system": source_system, + "reply_anchor_present": bool(thread_context), + "active_context_kind": active_context_kind, + "allowed_context_scopes": [ + "current_requirement", + "current_draft", + "independent_workflow", + "clarify", + ], + "thread_context": thread_context or {}, + }, + ) + decision = await SemanticDecisionService(llm_service).decide(packet=packet) + gate_result = SemanticDecisionGatekeeper.evaluate( + mode_card=packet.mode_card, + decision=decision, + ) + except Exception as exc: + logger.warning( + "active_context_followup_decision_failed", + tenant_id=self.tenant_id, + source_system=source_system, + source_message_id=source_message_id, + error=str(exc), + ) + return None + + if not bool(getattr(gate_result, "accepted", False)): + return None + + allowed_actions = { + str(item).strip() + for item in (getattr(gate_result, "allowed_actions", None) or []) + if str(item or "").strip() + } + followup_scope = str(getattr(decision, "followup_scope", "") or "").strip().lower() + assistant_reply = str(getattr(decision, "assistant_reply", "") or "").strip() + + if followup_scope == "current_requirement": + if "context.followup.current_requirement" not in allowed_actions: + return None + builder = getattr(self, "_build_requirement_planning_direct_result", None) + if callable(builder): + direct_result = await builder( + message_text=message_text, + source_system=source_system, + source_message_id=source_message_id, + source_chat_id=channel_ref, + sender=None, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + conversation_key=conversation_key, + topic_switch=None, + ) + answer = self._extract_direct_answer_text(direct_result) + if answer: + return self._build_active_context_direct_reply_result( + answer=answer, + reason_code="requirement_plan_followup", + ) + if assistant_reply: + return self._build_active_context_direct_reply_result( + answer=assistant_reply, + reason_code="requirement_plan_followup", + ) + return None + + if followup_scope == "current_draft": + if "context.followup.current_draft" not in allowed_actions: + return None + from ..draft_confirm_handler import DraftConfirmHandler + + handler = DraftConfirmHandler(self) + return await handler.handle_text_reply( + message_text=message_text, + sender=str(sender or ""), + source_system=source_system, + source_message_id=source_message_id, + channel_ref=channel_ref, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + semantic_followup_override={ + "action": str(getattr(decision, "followup_action", "") or "").strip().lower(), + "revision_note": str(getattr(decision, "revision_note", "") or "").strip(), + "assistant_reply": assistant_reply, + }, + ) + + if followup_scope == "clarify" and "context.followup.clarify" in allowed_actions and assistant_reply: + return self._build_active_context_direct_reply_result( + answer=assistant_reply, + reason_code="active_context_followup_clarify", + ) + return None + async def _maybe_reset_session_project_context_for_new_requirement( self, *, @@ -1399,24 +1621,17 @@ class KernelIngressFlowEntryMixin: ) -> bool: if session is None: return False - if not WorkflowSemanticPlanner.looks_like_explicit_new_requirement_topic(message_text): - return False if _safe_int(getattr(session, "current_project_id", None)) is None: return False - await self._update_session( - tenant_id=self.tenant_id, - sender=sender, - session=session, - source_chat_id=channel_ref, - source_sender_open_id=source_sender_open_id, - source_message_id=source_message_id, - thread_context=thread_context, - current_project_id=None, - clear_current_project_id=True, - last_intent=str(getattr(session, "last_intent", "") or "").strip() or None, - last_message_text=str(getattr(session, "last_message_text", "") or "").strip() or None, + _ = ( + sender, + channel_ref, + source_sender_open_id, + source_message_id, + thread_context, + message_text, ) - return True + return False async def _maybe_handle_orphan_requirement_followup( self, @@ -1427,8 +1642,10 @@ class KernelIngressFlowEntryMixin: source_sender_open_id: Optional[str], ingress_options: Optional[dict[str, Any]], message_text: str, + session: Optional[ConversationSession] = None, + sender: Optional[str] = None, ) -> Optional[dict[str, Any]]: - if not WorkflowSemanticPlanner.looks_like_low_context_requirement_followup(message_text): + if str(getattr(session, "last_intent", "") or "").strip() != QUERY_SCENE_REQUIREMENT_PLAN: return None checker = getattr(self, "_has_active_requirement_plan_workspace", None) if not callable(checker): @@ -1456,6 +1673,20 @@ class KernelIngressFlowEntryMixin: ) if has_active_requirement_plan_workspace: return None + if session is not None and _safe_int(getattr(session, "current_project_id", None)) is not None: + await self._update_session( + tenant_id=self.tenant_id, + sender=str(sender or getattr(session, "sender", "") or ""), + session=session, + source_chat_id=channel_ref, + source_sender_open_id=source_sender_open_id, + source_message_id=source_message_id, + thread_context=thread_context, + current_project_id=None, + clear_current_project_id=True, + last_intent=str(getattr(session, "last_intent", "") or "").strip() or None, + last_message_text=str(getattr(session, "last_message_text", "") or "").strip() or None, + ) return { "handled": True, "status": "answered", diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_project.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_project.py index 8c92be80..fb4f21a7 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_project.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_project.py @@ -3,7 +3,6 @@ from __future__ import annotations from .shared import * # noqa: F401, F403 -from ..workflow_semantic_planner import WorkflowSemanticPlanner class KernelWorldModelCoreProjectMixin: @@ -39,8 +38,6 @@ class KernelWorldModelCoreProjectMixin: cleaned = re.sub(r"\s+", " ", str(message_text or "")).strip() if not cleaned: return False - if not WorkflowSemanticPlanner.looks_like_project_create_request(cleaned): - return False return bool(cls._extract_explicit_project_title_anchor(message_text=cleaned)) async def _resolve_project( @@ -97,7 +94,10 @@ class KernelWorldModelCoreProjectMixin: normalized_candidate = re.sub(r"\s+", "", str(candidate or "")).lower() if normalized_candidate and normalized_candidate in normalized_message: return project - inherited_project_id = self._extract_inherited_project_id(session=session) + inherited_project_id = self._extract_inherited_project_id( + session=session, + message_text=message_text, + ) if inherited_project_id is not None: self._record_world_model_query_table(query_tables, "projects") return ( diff --git a/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py index 74124d3d..d716b3a2 100644 --- a/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py +++ b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py @@ -2,6 +2,8 @@ from __future__ import annotations +import asyncio + from .shared import * # noqa: F401, F403 from .shared import _safe_int from ...adaptive_rag_service import AdaptiveRAGService @@ -10,6 +12,7 @@ from ...mounted_ai_runtime_write_service import MountedAIRuntimeWriteService from ...sstc_service_core import ALIGNED_STATUSES from ...task_persistence_core import build_task_create_draft, persist_task from ..workflow_semantic_planner import WorkflowSemanticPlanner +from app.config.ai_prompts import PMFLOW_REQUIREMENT_PLAN_DRAFT_PROMPT, PMFLOW_REQUIREMENT_PLAN_ROUND_PROMPT from app.models import MountedDraftWorkspace, MountedTopicWorkspace from app.utils.thread_context import build_feishu_conversation_scope_key, build_feishu_thread_key @@ -24,9 +27,24 @@ class ExecuteQueryProjectMixin: rag_service: Any, message_text: str, project_id: Optional[int], + message_intent: Optional[str] = None, ) -> Optional[dict[str, Any]]: - if not WorkflowSemanticPlanner.looks_like_project_lookup_question(message_text): - return None + normalized_intent = str(message_intent or "").strip().lower() + keyword_extractor = getattr(rag_service, "_extract_project_lookup_keyword", None) + extracted_keyword = "" + if callable(keyword_extractor): + try: + extracted_keyword = str(keyword_extractor(message_text) or "").strip() + except Exception: + extracted_keyword = "" + if normalized_intent not in {"project_lookup", "project_lookup_question"}: + has_project_subject = bool(project_id is not None or extracted_keyword) + has_lookup_shape = bool( + re.search(r"项目\s*#?\d+", str(message_text or ""), re.IGNORECASE) + or re.search(r"(项目列表|项目清单|当前项目)", str(message_text or ""), re.IGNORECASE) + ) + if not has_project_subject or not has_lookup_shape: + return None existence_handler = getattr(rag_service, "_try_fast_project_existence_answer", None) if not callable(existence_handler): return None @@ -54,10 +72,8 @@ class ExecuteQueryProjectMixin: message_text: str, message_intent: Optional[str], ) -> bool: - normalized_intent = str(message_intent or "").strip().lower() - if normalized_intent: - return normalized_intent == "casual_chat" - return WorkflowSemanticPlanner._looks_like_casual_chat_message(message_text) + _ = message_text + return str(message_intent or "").strip().lower() == "casual_chat" @classmethod def _should_use_requirement_clarify_answer( @@ -67,11 +83,10 @@ class ExecuteQueryProjectMixin: message_intent: Optional[str], consultant_mode_preference: Optional[str], ) -> bool: + _ = message_text normalized_intent = str(message_intent or "").strip().lower() normalized_mode = str(consultant_mode_preference or "").strip().lower() - if normalized_intent or normalized_mode: - return normalized_intent == "business_review" and normalized_mode == "clarify" - return WorkflowSemanticPlanner._looks_like_requirement_followup_clarify(message_text) + return normalized_intent == "business_review" and normalized_mode == "clarify" @classmethod def _should_use_requirement_planning_answer( @@ -103,11 +118,8 @@ class ExecuteQueryProjectMixin: advise_subtype=advise_subtype, ): return False - if normalized_intent == "requirement_plan": - return True - if normalized_intent or str(consultant_mode_preference or "").strip(): - return False - return WorkflowSemanticPlanner._should_keep_requirement_discussion_in_query(message_text) + _ = message_text + return normalized_intent == "requirement_plan" @classmethod def _should_use_requirement_advise_answer( @@ -118,16 +130,15 @@ class ExecuteQueryProjectMixin: consultant_mode_preference: Optional[str], advise_subtype: Optional[str], ) -> bool: + _ = message_text normalized_intent = str(message_intent or "").strip().lower() normalized_mode = str(consultant_mode_preference or "").strip().lower() normalized_subtype = str(advise_subtype or "").strip().lower() - if normalized_intent or normalized_mode or normalized_subtype: - return ( - normalized_intent == "business_review" - and normalized_mode == "advise" - and normalized_subtype != "assignee" - ) - return WorkflowSemanticPlanner._looks_like_requirement_scope_advise(message_text) + return ( + normalized_intent == "business_review" + and normalized_mode == "advise" + and normalized_subtype != "assignee" + ) @classmethod def _should_use_assignee_recommendation_answer( @@ -138,16 +149,15 @@ class ExecuteQueryProjectMixin: consultant_mode_preference: Optional[str], advise_subtype: Optional[str], ) -> bool: + _ = message_text normalized_intent = str(message_intent or "").strip().lower() normalized_mode = str(consultant_mode_preference or "").strip().lower() normalized_subtype = str(advise_subtype or "").strip().lower() - if normalized_intent or normalized_mode or normalized_subtype: - return ( - normalized_intent == "business_review" - and normalized_mode == "advise" - and normalized_subtype == "assignee" - ) - return WorkflowSemanticPlanner._looks_like_assignee_recommendation(message_text) + return ( + normalized_intent == "business_review" + and normalized_mode == "advise" + and normalized_subtype == "assignee" + ) @staticmethod def _build_lightweight_chat_answer(*, message_text: str) -> str: @@ -181,72 +191,7 @@ class ExecuteQueryProjectMixin: @classmethod def _detect_requirement_plan_profile(cls, *, message_text: str, card: dict[str, Any]) -> str: - existing = str(card.get("plan_profile") or "").strip().lower() - if existing in {"scoring", "generic", "it_project"}: - return existing - normalized = WorkflowSemanticPlanner._normalize_text( - " ".join( - part - for part in ( - message_text, - str(card.get("title") or ""), - str(card.get("goal") or ""), - str(card.get("source_material") or ""), - str(card.get("project_nature") or ""), - str(card.get("tech_route") or ""), - str(card.get("delivery_mode") or ""), - ) - if str(part or "").strip() - ) - ) - if any(token in normalized for token in ("打分", "评分", "分数", "趋势分")): - return "scoring" - if cls._looks_like_it_project_requirement(normalized): - return "it_project" - return "generic" - - @staticmethod - def _looks_like_it_project_requirement(normalized: str) -> bool: - text = str(normalized or "").strip().lower() - if not text: - return False - strong_tokens = ( - "it项目", - "技术项目", - "openclaw", - "自动化部署", - "批量部署", - "发布治理", - "部署治理", - "发布平台", - "部署平台", - "任务编排", - "环境接入", - "回滚", - "审批", - "平台运维", - "运维团队", - "fastapi", - "react", - "redis", - "celery", - "postgres", - "postgresql", - "k8s", - "kubernetes", - "helm", - "argocd", - "terraform", - "ansible", - "ci/cd", - "cicd", - "jenkins", - ) - if any(token in text for token in strong_tokens): - return True - has_delivery_shape = any(token in text for token in ("部署", "发布", "上线", "交付")) - has_platform_shape = any(token in text for token in ("自动化", "批量", "治理", "回滚", "审批", "环境")) - return has_delivery_shape and has_platform_shape + return "it_project" @staticmethod def _extract_requirement_plan_choice(message_text: str) -> Optional[str]: @@ -262,6 +207,24 @@ class ExecuteQueryProjectMixin: return str(message_text or "").strip() return re.sub(r"^\s*[ABCabc](?:[\.\,,。、::)\]】\s]+)?", "", str(message_text or "")).strip() + @classmethod + def _requirement_plan_model_choice_value( + cls, + *, + card: dict[str, Any], + field: str, + choice: Optional[str], + ) -> str: + if not choice: + return "" + option_maps = card.get("round_option_maps") if isinstance(card.get("round_option_maps"), dict) else {} + field_map = option_maps.get(str(field or "").strip()) if isinstance(option_maps, dict) else None + if not isinstance(field_map, dict): + return "" + value = str(field_map.get(str(choice or "").strip().upper()) or "").strip() + value = re.sub(r"^(?:先做|直接做|先按|先走|走)", "", value).strip() + return cls._compact_requirement_plan_label(value) + @staticmethod def _it_project_choice_value(*, field: str, choice: str) -> Optional[str]: mapping = { @@ -295,34 +258,6 @@ class ExecuteQueryProjectMixin: normalized_choice = str(choice or "").strip().upper() return mapping.get(normalized_field, {}).get(normalized_choice) - @staticmethod - def _generic_choice_value(*, field: str, choice: str) -> Optional[str]: - mapping = { - "source_material": { - "A": "用户当前需求描述", - "B": "会议纪要/文档", - "C": "录音/转写/回放", - }, - "target_user": { - "A": "核心执行人/内部 PM", - "B": "负责人/管理者", - "C": "整个团队/终端用户", - }, - "output_form": { - "A": "飞书总结", - "B": "飞书消息", - "C": "单独页面", - }, - "cadence": { - "A": "触发后半小时内", - "B": "当天内", - "C": "第二天", - }, - } - normalized_field = str(field or "").strip() - normalized_choice = str(choice or "").strip().upper() - return mapping.get(normalized_field, {}).get(normalized_choice) - @staticmethod def _compact_requirement_plan_label(value: Any) -> str: text = str(value or "").strip() @@ -536,21 +471,12 @@ class ExecuteQueryProjectMixin: if existing: return existing normalized = WorkflowSemanticPlanner._normalize_text(message_text) - profile = cls._detect_requirement_plan_profile(message_text=message_text, card=card) - if profile == "generic": - if any(token in normalized for token in ("会议纪要", "会议记录", "纪要")) and any( - token in normalized for token in ("待办", "todo") - ): - return "会议纪要待办提取工具" - if any(token in normalized for token in ("会议纪要", "会议记录", "纪要")): - return "会议纪要整理工具" - if profile == "it_project": - if "openclaw" in normalized and any(token in normalized for token in ("部署", "发布")): - return "OpenClaw 批量自动化部署平台" - if any(token in normalized for token in ("自动化部署", "批量部署")): - return "批量自动化部署平台" - if any(token in normalized for token in ("发布治理", "部署治理")): - return "发布治理平台" + if "openclaw" in normalized and any(token in normalized for token in ("部署", "发布")): + return "OpenClaw 批量自动化部署平台" + if any(token in normalized for token in ("自动化部署", "批量部署")): + return "批量自动化部署平台" + if any(token in normalized for token in ("发布治理", "部署治理")): + return "发布治理平台" if any(token in normalized for token in ("大a", "a股", "沪深")) and any( token in normalized for token in ("打分", "评分", "分数") ): @@ -560,6 +486,9 @@ class ExecuteQueryProjectMixin: match = re.search(r"做一个([^,。;;]{2,30})", str(message_text or "")) if match: return match.group(1).strip(" ,。;;") + about_match = re.search(r"关于([^,。;;]{2,30})", str(message_text or "")) + if about_match: + return about_match.group(1).strip(" ,。;;") return "需求收敛草稿" @classmethod @@ -568,22 +497,13 @@ class ExecuteQueryProjectMixin: if existing: return existing normalized = WorkflowSemanticPlanner._normalize_text(message_text) - profile = cls._detect_requirement_plan_profile(message_text=message_text, card=card) - if profile == "generic": - if any(token in normalized for token in ("会议纪要", "会议记录", "纪要")) and any( - token in normalized for token in ("待办", "todo") - ): - return "会后自动整理待办,减少信息散落,让负责人能快速跟进。" - return "先把第一版核心能力收清楚,再决定是否正式创建。" - if profile == "it_project": - if "openclaw" in normalized and any(token in normalized for token in ("部署", "发布")): - return "先把 OpenClaw 批量部署、执行追踪和回滚治理收成一条可复用流程,再决定是否正式创建。" - return "先把 IT 项目的第一版目标、技术路线和推进方式收清楚,再决定是否正式创建。" + if "openclaw" in normalized and any(token in normalized for token in ("部署", "发布")): + return "先把 OpenClaw 批量部署、执行追踪和回滚治理收成一条可复用流程,再决定是否正式创建。" if any(token in normalized for token in ("大a", "a股", "沪深")) and any( token in normalized for token in ("打分", "评分", "分数") ): return "每天产出一个可解释的 A 股趋势分数,辅助判断和记录。" - return "先把第一版核心能力收清楚,再决定是否正式创建。" + return "先把 IT 项目的第一版目标、技术路线和推进方式收清楚,再决定是否正式创建。" @classmethod def _extract_requirement_plan_updates(cls, *, message_text: str, card: dict[str, Any]) -> dict[str, Any]: @@ -602,105 +522,32 @@ class ExecuteQueryProjectMixin: if not card.get("goal"): updates["goal"] = cls._derive_requirement_plan_goal(message_text=raw, card=card) - if profile == "generic" and not card.get("source_material"): - if any(token in normalized for token in ("会议纪要", "会议记录", "纪要")): - updates["source_material"] = "会议纪要" - elif any(token in normalized for token in ("录音", "录音转写", "转写")): - updates["source_material"] = "会议录音/转写" - elif any(token in normalized for token in ("直播录屏", "回放")): - updates["source_material"] = "直播录屏/回放" - elif WorkflowSemanticPlanner.looks_like_explicit_new_requirement_topic(raw): - topic_tail = "" - if "关于" in normalized: - topic_tail = normalized.partition("关于")[2] - topic_tail = re.sub(r"[@_a-z0-9]+", "", topic_tail) - if len(topic_tail) >= 4: - updates["source_material"] = "用户当前需求描述" - elif choice and missing_fields and missing_fields[0] == "source_material": - mapped = cls._generic_choice_value(field="source_material", choice=choice) + if not card.get("project_nature"): + if any(token in normalized for token in ("内部提效工具", "脚本工具", "提效工具")): + updates["project_nature"] = "内部提效工具" + elif any(token in normalized for token in ("发布治理平台", "部署治理平台", "治理平台")): + updates["project_nature"] = "发布治理平台" + elif any(token in normalized for token in ("云原生交付底座", "交付底座")): + updates["project_nature"] = "云原生交付底座" + elif choice and missing_fields and missing_fields[0] == "project_nature": + mapped = cls._requirement_plan_model_choice_value( + card=card, + field="project_nature", + choice=choice, + ) or cls._it_project_choice_value(field="project_nature", choice=choice) if mapped: - updates["source_material"] = mapped + updates["project_nature"] = mapped - if profile == "scoring" and not card.get("scoring_basis"): - if any(token in normalized for token in ("技术指标", "均线", "成交量", "涨跌比", "量价", "macd", "rsi")): - updates["scoring_basis"] = "技术指标(均线、成交量、涨跌比等)" - elif any(token in normalized for token in ("情绪", "涨停家数", "北向", "北向资金", "舆情", "热度")): - updates["scoring_basis"] = "情绪/资金数据(涨停家数、北向资金、舆情热度等)" - elif any(token in normalized for token in ("规则", "已有逻辑", "现成逻辑", "自动化")): - updates["scoring_basis"] = "沿用现有打分规则并自动化" - - if profile == "it_project": - if not card.get("project_nature"): - if any(token in normalized for token in ("内部提效工具", "脚本工具", "提效工具")): - updates["project_nature"] = "内部提效工具" - elif any(token in normalized for token in ("发布治理平台", "部署治理平台", "治理平台")): - updates["project_nature"] = "发布治理平台" - elif any(token in normalized for token in ("云原生交付底座", "交付底座")): - updates["project_nature"] = "云原生交付底座" - elif choice and missing_fields and missing_fields[0] == "project_nature": - mapped = cls._it_project_choice_value(field="project_nature", choice=choice) - if mapped: - updates["project_nature"] = mapped - - if not card.get("target_user"): - target_match = re.search(r"(?:主要)?给([^,。;;]{1,24}?)(?:看|用)", cleaned_raw) - if target_match: - updates["target_user"] = target_match.group(1).strip() - elif any(token in cleaned_raw for token in ("平台运维", "运维团队")): - updates["target_user"] = "平台运维团队" - elif any(token in cleaned_raw for token in ("开发团队", "研发团队", "开发")): - updates["target_user"] = "开发团队" - elif any(token in cleaned_raw for token in ("项目交付", "交付团队")): - updates["target_user"] = "项目交付团队" - elif choice and missing_fields and missing_fields[0] == "target_user": - mapped = cls._it_project_choice_value(field="target_user", choice=choice) - if mapped: - updates["target_user"] = mapped - - if not card.get("tech_route"): - if all(token in normalized for token in ("fastapi", "react")) and any( - token in normalized for token in ("celery", "redis", "postgres", "postgresql") - ): - updates["tech_route"] = "任务编排平台版(FastAPI + React + Redis + Celery + PostgreSQL)" - elif any(token in normalized for token in ("shell", "ansible")) or "脚本增强" in normalized: - updates["tech_route"] = "脚本增强版(FastAPI + Shell/Ansible + PostgreSQL)" - elif any(token in normalized for token in ("k8s", "kubernetes", "helm", "argocd", "terraform")): - updates["tech_route"] = "云原生交付版(Kubernetes + Helm + ArgoCD/Terraform + FastAPI)" - elif "任务编排平台版" in cleaned_raw: - updates["tech_route"] = "任务编排平台版(FastAPI + React + Redis + Celery + PostgreSQL)" - elif choice and missing_fields and missing_fields[0] == "tech_route": - mapped = cls._it_project_choice_value(field="tech_route", choice=choice) - if mapped: - updates["tech_route"] = mapped - - if not card.get("first_scope"): - if any(token in cleaned_raw for token in ("批量执行闭环", "任务创建", "执行日志", "失败重试")): - updates["first_scope"] = cleaned_raw - elif any(token in cleaned_raw for token in ("环境接入闭环", "目标机器", "环境注册", "凭证管理")): - updates["first_scope"] = cleaned_raw - elif any(token in cleaned_raw for token in ("治理闭环", "审批", "回滚", "审计")): - updates["first_scope"] = cleaned_raw - elif choice and missing_fields and missing_fields[0] == "first_scope": - mapped = cls._it_project_choice_value(field="first_scope", choice=choice) - if mapped: - updates["first_scope"] = mapped - - if not card.get("delivery_mode"): - if any(token in normalized for token in ("poc", "验证版", "内部验证", "内部poc")): - updates["delivery_mode"] = "内部 PoC" - elif any(token in cleaned_raw for token in ("项目级试点", "试点")): - updates["delivery_mode"] = "项目级试点(带基础审批和回滚)" - elif any(token in cleaned_raw for token in ("平台化上线", "正式上线", "多团队接入")): - updates["delivery_mode"] = "平台化上线" - elif choice and missing_fields and missing_fields[0] == "delivery_mode": - mapped = cls._it_project_choice_value(field="delivery_mode", choice=choice) - if mapped: - updates["delivery_mode"] = mapped - - if profile != "it_project" and not card.get("target_user"): - target_match = re.search(r"(?:主要)?给([^,。;;]{1,24}?)(?:看|用)", raw) + if not card.get("target_user"): + target_match = re.search(r"(?:主要)?给([^,。;;]{1,24}?)(?:看|用)", cleaned_raw) if target_match: updates["target_user"] = target_match.group(1).strip() + elif any(token in cleaned_raw for token in ("平台运维", "运维团队")): + updates["target_user"] = "平台运维团队" + elif any(token in cleaned_raw for token in ("开发团队", "研发团队", "开发")): + updates["target_user"] = "开发团队" + elif any(token in cleaned_raw for token in ("项目交付", "交付团队")): + updates["target_user"] = "项目交付团队" elif "投顾" in raw: updates["target_user"] = "投顾" elif any(token in raw for token in ("研究员", "研究团队")): @@ -711,309 +558,1221 @@ class ExecuteQueryProjectMixin: updates["target_user"] = raw.strip("。;;") elif "自己" in raw: updates["target_user"] = "自己" - elif profile == "generic" and choice and missing_fields and missing_fields[0] == "target_user": - mapped = cls._generic_choice_value(field="target_user", choice=choice) + elif choice and missing_fields and missing_fields[0] == "target_user": + mapped = cls._requirement_plan_model_choice_value( + card=card, + field="target_user", + choice=choice, + ) or cls._it_project_choice_value(field="target_user", choice=choice) if mapped: updates["target_user"] = mapped - if profile != "it_project" and not card.get("output_form"): - if "飞书总结" in raw: - updates["output_form"] = "飞书总结" - elif "飞书日报" in raw: - updates["output_form"] = "飞书日报" - elif any(token in raw for token in ("飞书消息", "飞书通知")): - updates["output_form"] = "飞书消息" - elif any(token in raw for token in ("页面看板", "看板", "仪表盘")): - updates["output_form"] = "页面看板" - elif "简报" in raw: - updates["output_form"] = "简报" - elif profile == "generic" and choice and missing_fields and missing_fields[0] == "output_form": - mapped = cls._generic_choice_value(field="output_form", choice=choice) - if mapped: - updates["output_form"] = mapped - - if profile != "it_project" and not card.get("cadence"): - if any(token in normalized for token in ("每次会后半小时内", "会后半小时内")): - updates["cadence"] = "每次会后半小时内" - elif any(token in normalized for token in ("每日收盘后", "每天收盘后", "收盘后")): - updates["cadence"] = "每日收盘后" - elif "开盘前" in raw: - updates["cadence"] = "开盘前" - elif "盘中" in raw: - updates["cadence"] = "盘中" - elif any(token in normalized for token in ("实时", "准实时")): - updates["cadence"] = "实时" - elif any(token in normalized for token in ("每天", "每日")) and any( - token in normalized for token in ("更新", "发", "推送", "同步", "生成", "刷新", "跑") + if not card.get("tech_route"): + if all(token in normalized for token in ("fastapi", "react")) and any( + token in normalized for token in ("celery", "redis", "postgres", "postgresql") ): - updates["cadence"] = "每日" - elif profile == "generic" and choice and missing_fields and missing_fields[0] == "cadence": - mapped = cls._generic_choice_value(field="cadence", choice=choice) + updates["tech_route"] = "任务编排平台版(FastAPI + React + Redis + Celery + PostgreSQL)" + elif any(token in normalized for token in ("shell", "ansible")) or "脚本增强" in normalized: + updates["tech_route"] = "脚本增强版(FastAPI + Shell/Ansible + PostgreSQL)" + elif any(token in normalized for token in ("k8s", "kubernetes", "helm", "argocd", "terraform")): + updates["tech_route"] = "云原生交付版(Kubernetes + Helm + ArgoCD/Terraform + FastAPI)" + elif "任务编排平台版" in cleaned_raw: + updates["tech_route"] = "任务编排平台版(FastAPI + React + Redis + Celery + PostgreSQL)" + elif choice and missing_fields and missing_fields[0] == "tech_route": + mapped = cls._requirement_plan_model_choice_value( + card=card, + field="tech_route", + choice=choice, + ) or cls._it_project_choice_value(field="tech_route", choice=choice) if mapped: - updates["cadence"] = mapped + updates["tech_route"] = mapped - if profile != "it_project" and not card.get("first_scope") and any( - token in normalized - for token in ("先保留", "先只做", "别做太重", "范围够轻", "方向判断", "先抽", "先提", "不做复杂") - ) and not any(token in normalized for token in ("先别创建", "别创建", "先收第一版", "先收一版")): - updates["first_scope"] = raw + if not card.get("first_scope"): + if any(token in cleaned_raw for token in ("批量执行闭环", "任务创建", "执行日志", "失败重试")): + updates["first_scope"] = cleaned_raw + elif any(token in cleaned_raw for token in ("环境接入闭环", "目标机器", "环境注册", "凭证管理")): + updates["first_scope"] = cleaned_raw + elif any(token in cleaned_raw for token in ("治理闭环", "审批", "回滚", "审计")): + updates["first_scope"] = cleaned_raw + elif missing_fields and missing_fields[0] == "first_scope" and len(cleaned_raw.strip()) >= 4: + updates["first_scope"] = cleaned_raw + elif choice and missing_fields and missing_fields[0] == "first_scope": + mapped = cls._requirement_plan_model_choice_value( + card=card, + field="first_scope", + choice=choice, + ) or cls._it_project_choice_value(field="first_scope", choice=choice) + if mapped: + updates["first_scope"] = mapped + if updates.get("first_scope") and not card.get("delivery_mode"): + updates["delivery_mode"] = "项目级试点(带基础审批和回滚)" - if not card.get("success_signal"): - if profile == "scoring" and all(token in normalized for token in ("分数", "解释")): - updates["success_signal"] = "至少输出一个分数和一句解释" - elif profile == "generic" and any(token in normalized for token in ("待办", "责任人", "截止时间")): - updates["success_signal"] = "至少稳定提取待办、责任人和截止时间" + if not card.get("delivery_mode"): + if any(token in normalized for token in ("poc", "验证版", "内部验证", "内部poc")): + updates["delivery_mode"] = "内部 PoC" + elif any(token in cleaned_raw for token in ("项目级试点", "试点")): + updates["delivery_mode"] = "项目级试点(带基础审批和回滚)" + elif any(token in cleaned_raw for token in ("平台化上线", "正式上线", "多团队接入")): + updates["delivery_mode"] = "平台化上线" + elif choice and missing_fields and missing_fields[0] == "delivery_mode": + mapped = cls._requirement_plan_model_choice_value( + card=card, + field="delivery_mode", + choice=choice, + ) or cls._it_project_choice_value(field="delivery_mode", choice=choice) + if mapped: + updates["delivery_mode"] = mapped return {key: value for key, value in updates.items() if str(value or "").strip()} @classmethod def _requirement_plan_missing_fields(cls, card: dict[str, Any]) -> list[str]: - profile = cls._detect_requirement_plan_profile(message_text="", card=card) - ordered_fields = ( - ("project_nature", "target_user", "tech_route", "first_scope", "delivery_mode") - if profile == "it_project" - else - ("scoring_basis", "target_user", "output_form", "cadence", "first_scope") - if profile == "scoring" - else ("source_material", "target_user", "output_form", "cadence", "first_scope") - ) + ordered_fields = ("project_nature", "target_user", "tech_route", "first_scope", "delivery_mode") return [field for field in ordered_fields if not str(card.get(field) or "").strip()] @classmethod def _build_requirement_plan_ack(cls, *, updates: dict[str, Any], profile: str) -> str: parts: list[str] = [] - if profile == "it_project": - if str(updates.get("project_nature") or "").strip(): - parts.append(f"项目先按{cls._summarize_it_project_choice_label(field='project_nature', value=updates['project_nature'])}来收") - if str(updates.get("target_user") or "").strip(): - parts.append(f"主要服务{cls._summarize_it_project_choice_label(field='target_user', value=updates['target_user'])}") - if str(updates.get("tech_route") or "").strip(): - parts.append(f"技术路线先按{cls._summarize_it_project_choice_label(field='tech_route', value=updates['tech_route'])}") - if str(updates.get("first_scope") or "").strip(): - parts.append(f"第一版先收在{cls._summarize_it_project_choice_label(field='first_scope', value=updates['first_scope'])}") - if str(updates.get("delivery_mode") or "").strip(): - parts.append(f"推进方式先按{cls._summarize_it_project_choice_label(field='delivery_mode', value=updates['delivery_mode'])}") - if not parts: - return "" - return f"好,我先按这个方向继续记:{';'.join(parts[:2])}。" - if profile == "scoring" and str(updates.get("scoring_basis") or "").strip(): - parts.append(f"第一版按{updates['scoring_basis']}来打分") - if profile == "generic": - source_material = str(updates.get("source_material") or "").strip() - if source_material and source_material != "用户当前需求描述": - parts.append(f"输入先按{source_material}来") + _ = profile + if str(updates.get("project_nature") or "").strip(): + parts.append(f"项目先按{cls._summarize_it_project_choice_label(field='project_nature', value=updates['project_nature'])}来收") if str(updates.get("target_user") or "").strip(): - parts.append(f"主要给{updates['target_user']}用") - if str(updates.get("output_form") or "").strip(): - parts.append(f"先按{updates['output_form']}给结果") - if str(updates.get("cadence") or "").strip(): - parts.append(f"时效先记为{updates['cadence']}") + parts.append(f"主要服务{cls._summarize_it_project_choice_label(field='target_user', value=updates['target_user'])}") + if str(updates.get("tech_route") or "").strip(): + parts.append(f"技术路线先按{cls._summarize_it_project_choice_label(field='tech_route', value=updates['tech_route'])}") + if str(updates.get("first_scope") or "").strip(): + parts.append(f"第一版先收在{cls._summarize_it_project_choice_label(field='first_scope', value=updates['first_scope'])}") + if str(updates.get("delivery_mode") or "").strip(): + parts.append(f"推进方式先按{cls._summarize_it_project_choice_label(field='delivery_mode', value=updates['delivery_mode'])}") if not parts: return "" - return f"好,我先记下:{','.join(parts[:2])}。" + return f"好,我先按这个方向继续记:{';'.join(parts[:2])}。" @classmethod - def _build_requirement_plan_next_question( + def _requirement_plan_field_label(cls, field: str) -> str: + return { + "project_nature": "项目性质", + "target_user": "目标用户", + "tech_route": "技术路线", + "first_scope": "第一版范围", + "delivery_mode": "推进方式", + "source_material": "输入材料", + "output_form": "输出形式", + "cadence": "响应时效", + "scoring_basis": "打分依据", + }.get(str(field or "").strip(), str(field or "").strip()) + + @classmethod + def _summarize_requirement_plan_field_value(cls, *, profile: str, field: str, value: Any) -> str: + text = str(value or "").strip() + if not text: + return "" + if str(profile or "").strip() == "it_project": + return cls._summarize_it_project_choice_label(field=field, value=text) + return cls._compact_requirement_plan_label(text).rstrip("。.!!??") + + @classmethod + def _summarize_requirement_plan_latest_updates( cls, *, + updates: Optional[dict[str, Any]], + profile: str, + ) -> str: + if not isinstance(updates, dict): + return "" + ordered_fields = ( + "project_nature", + "target_user", + "tech_route", + "first_scope", + "delivery_mode", + "source_material", + "output_form", + "cadence", + "scoring_basis", + ) + parts: list[str] = [] + for field in ordered_fields: + summary = cls._summarize_requirement_plan_field_value( + profile=profile, + field=field, + value=updates.get(field), + ) + if not summary: + continue + label = cls._requirement_plan_field_label(field) + parts.append(f"{label}={summary}") + return ";".join(parts[:2]) + + @classmethod + def _summarize_requirement_plan_collected_fields(cls, *, card: dict[str, Any], profile: str) -> str: + ordered_fields = ( + "project_nature", + "target_user", + "tech_route", + "first_scope", + "delivery_mode", + "source_material", + "output_form", + "cadence", + "scoring_basis", + ) + parts: list[str] = [] + for field in ordered_fields: + summary = cls._summarize_requirement_plan_field_value( + profile=profile, + field=field, + value=card.get(field), + ) + if not summary: + continue + label = cls._requirement_plan_field_label(field) + parts.append(f"{label}={summary}") + return ";".join(parts[:3]) + + @classmethod + def _build_requirement_plan_round_packet( + cls, + *, + missing_field: str, + profile: str, + message_text: str, + card: Optional[dict[str, Any]], + latest_updates: Optional[dict[str, Any]] = None, + ) -> dict[str, Any]: + current_card = dict(card or {}) + return { + "profile": str(profile or "it_project").strip() or "it_project", + "missing_field": str(missing_field or "").strip(), + "latest_user_message": str(message_text or "").strip(), + "latest_user_reply_summary": cls._summarize_requirement_plan_latest_updates( + updates=latest_updates, + profile=profile, + ), + "current_context": { + "title": str(current_card.get("title") or "").strip(), + "goal": str(current_card.get("goal") or "").strip(), + "question_round": int(current_card.get("question_round") or 0), + }, + "collected_fields": { + key: str(current_card.get(key) or "").strip() + for key in ("project_nature", "target_user", "tech_route", "first_scope", "delivery_mode") + if str(current_card.get(key) or "").strip() + }, + "collected_field_summary": cls._summarize_requirement_plan_collected_fields( + card=current_card, + profile=profile, + ), + "abc_template_contract": { + "required_option_keys": ["A", "B", "C"], + "option_fields": ["title", "best_for", "suggested_capability", "rationale", "tradeoff"], + "recommendation_key_must_be_one_of": ["A", "B", "C"], + "tone": "自然 PMFlow 项目经理口吻,贴合当前业务,不要机械表单化", + }, + "reply_budget": cls._requirement_plan_round_budget(), + "field_goal": cls._requirement_plan_field_goal(missing_field), + "forbidden": [ + "不要输出 packet 中没有出现过的产品名或旧项目名", + "不要把 OpenClaw、部署、Kubernetes、发布治理等旧场景套到无关需求里", + "不要返回固定模板占位句", + "不要直接创建项目或承诺已执行", + ], + } + + @staticmethod + def _requirement_plan_field_goal(missing_field: str) -> str: + return { + "project_nature": "判断第一版更像轻量工具、业务平台,还是长期能力底座;具体选项必须按当前需求重写。", + "target_user": "判断第一版最应该先服务的用户角色或团队。", + "tech_route": "判断第一版能力/技术路线,建议要能支撑后续迭代但不过度设计。", + "first_scope": "判断第一版先保留哪些范围、字段或闭环,哪些先不做。", + "delivery_mode": "判断第一版用什么推进方式验证价值,比如轻量验证、项目试点或平台化推进。", + }.get(str(missing_field or "").strip(), "围绕当前需求补齐下一步最关键的收敛缺口。") + + @staticmethod + def _requirement_plan_round_budget() -> dict[str, int]: + return { + "max_chars": 1500, + "opening": 64, + "focus": 90, + "option_title": 24, + "option_best_for": 52, + "option_suggested_capability": 56, + "option_rationale": 56, + "option_tradeoff": 48, + "recommendation_reason": 72, + "reply_instruction": 36, + } + + @classmethod + def _requirement_plan_budget_limit(cls, *, packet: dict[str, Any], key: str, default: int) -> int: + budget = packet.get("reply_budget") if isinstance(packet.get("reply_budget"), dict) else {} + try: + value = int(budget.get(key) or 0) + except Exception: + value = 0 + return value if value > 0 else int(default) + + @staticmethod + def _compact_round_text(value: Any, *, limit: int = 160) -> str: + text = re.sub(r"\s+", " ", str(value or "")).strip() + if len(text) <= limit: + return text + return text[:limit].rstrip() + "..." + + @classmethod + def _validate_requirement_plan_round_payload( + cls, + *, + payload: Any, + packet: dict[str, Any], + ) -> dict[str, Any]: + if not isinstance(payload, dict): + raise ValueError("requirement_plan_round payload is not a dict") + options = payload.get("options") + if not isinstance(options, list) or len(options) != 3: + raise ValueError("requirement_plan_round options must contain A/B/C") + normalized_options: list[dict[str, str]] = [] + expected_keys = ["A", "B", "C"] + for index, expected_key in enumerate(expected_keys): + item = options[index] + if not isinstance(item, dict): + raise ValueError("requirement_plan_round option is not a dict") + key = str(item.get("key") or "").strip().upper() + if key != expected_key: + raise ValueError("requirement_plan_round option keys must be A/B/C in order") + normalized = { + "key": expected_key, + "title": cls._compact_round_text( + item.get("title"), + limit=cls._requirement_plan_budget_limit(packet=packet, key="option_title", default=24), + ), + "best_for": cls._compact_round_text( + item.get("best_for"), + limit=cls._requirement_plan_budget_limit(packet=packet, key="option_best_for", default=52), + ), + "suggested_capability": cls._compact_round_text( + item.get("suggested_capability"), + limit=cls._requirement_plan_budget_limit(packet=packet, key="option_suggested_capability", default=56), + ), + "rationale": cls._compact_round_text( + item.get("rationale"), + limit=cls._requirement_plan_budget_limit(packet=packet, key="option_rationale", default=56), + ), + "tradeoff": cls._compact_round_text( + item.get("tradeoff"), + limit=cls._requirement_plan_budget_limit(packet=packet, key="option_tradeoff", default=48), + ), + } + if not all(normalized[field] for field in ("title", "best_for", "suggested_capability", "rationale", "tradeoff")): + raise ValueError("requirement_plan_round option fields are incomplete") + normalized_options.append(normalized) + recommendation = payload.get("recommendation") + if not isinstance(recommendation, dict): + raise ValueError("requirement_plan_round recommendation is missing") + recommendation_key = str(recommendation.get("key") or "").strip().upper() + if recommendation_key not in {"A", "B", "C"}: + raise ValueError("requirement_plan_round recommendation key must be A/B/C") + normalized_payload = { + "opening": cls._compact_round_text( + payload.get("opening"), + limit=cls._requirement_plan_budget_limit(packet=packet, key="opening", default=64), + ), + "focus": cls._compact_round_text( + payload.get("focus"), + limit=cls._requirement_plan_budget_limit(packet=packet, key="focus", default=90), + ), + "options": normalized_options, + "recommendation": { + "key": recommendation_key, + "reason": cls._compact_round_text( + recommendation.get("reason"), + limit=cls._requirement_plan_budget_limit(packet=packet, key="recommendation_reason", default=72), + ), + }, + "reply_instruction": cls._compact_round_text( + payload.get("reply_instruction") or "你回 A / B / C,我就顺着这条继续往下收。", + limit=cls._requirement_plan_budget_limit(packet=packet, key="reply_instruction", default=36), + ), + } + if not normalized_payload["opening"] or not normalized_payload["focus"] or not normalized_payload["recommendation"]["reason"]: + raise ValueError("requirement_plan_round core fields are incomplete") + cls._guard_requirement_plan_round_against_internal_context_leaks(payload=normalized_payload) + cls._guard_requirement_plan_round_against_stale_context(payload=normalized_payload, packet=packet) + return normalized_payload + + @classmethod + def _guard_requirement_plan_round_against_internal_context_leaks( + cls, + *, + payload: dict[str, Any], + ) -> None: + rendered = WorkflowSemanticPlanner._normalize_text(json.dumps(payload, ensure_ascii=False)) + internal_terms = ( + "current_context", + "latest_user_message", + "latest_user_reply_summary", + "collected_fields", + "collected_field_summary", + "reply_budget", + "missing_field", + "abc_template_contract", + "field_goal", + "question_round", + "project_nature", + "target_user", + "tech_route", + "first_scope", + "delivery_mode", + "requirement_plan_round_packet", + "字段需判断", + ) + leaked = [term for term in internal_terms if WorkflowSemanticPlanner._normalize_text(term) in rendered] + if leaked: + raise ValueError(f"requirement_plan_round leaks internal packet terms: {', '.join(leaked[:3])}") + + @classmethod + def _guard_requirement_plan_round_against_stale_context( + cls, + *, + payload: dict[str, Any], + packet: dict[str, Any], + ) -> None: + rendered = WorkflowSemanticPlanner._normalize_text(json.dumps(payload, ensure_ascii=False)) + context_material = { + "latest_user_message": packet.get("latest_user_message"), + "latest_user_reply_summary": packet.get("latest_user_reply_summary"), + "current_context": packet.get("current_context"), + "collected_fields": packet.get("collected_fields"), + "collected_field_summary": packet.get("collected_field_summary"), + } + context = WorkflowSemanticPlanner._normalize_text(json.dumps(context_material, ensure_ascii=False)) + if cls._requirement_plan_packet_allows_deployment_context(packet=packet): + return + stale_terms = ( + "openclaw", + "批量自动化部署", + "发布治理平台", + "云原生交付底座", + "平台运维团队", + "项目交付团队", + "任务编排平台版", + "脚本增强版", + "云原生交付版", + "环境接入闭环", + "治理闭环", + "部署治理", + "kubernetes", + "k8s", + "helm", + "argocd", + "terraform", + ) + leaked = [term for term in stale_terms if term in rendered and term not in context] + if leaked: + raise ValueError(f"requirement_plan_round contains stale context terms: {', '.join(leaked[:3])}") + + @classmethod + def _requirement_plan_packet_allows_deployment_context(cls, *, packet: dict[str, Any]) -> bool: + context_material = { + "latest_user_message": packet.get("latest_user_message"), + "latest_user_reply_summary": packet.get("latest_user_reply_summary"), + "current_context": packet.get("current_context"), + "collected_fields": packet.get("collected_fields"), + "collected_field_summary": packet.get("collected_field_summary"), + } + context = WorkflowSemanticPlanner._normalize_text(json.dumps(context_material, ensure_ascii=False)) + deployment_hints = ( + "openclaw", + "批量自动化部署", + "自动化部署", + "部署治理", + "发布治理", + "发布平台", + "部署平台", + "云原生交付底座", + "云原生交付版", + "任务编排平台版", + "平台运维", + "环境接入闭环", + "回滚", + "审批", + "kubernetes", + "k8s", + "helm", + "argocd", + "terraform", + ) + return any(term in context for term in deployment_hints) + + @classmethod + def _render_requirement_plan_round_payload(cls, payload: dict[str, Any]) -> str: + lines = [str(payload["opening"]).strip(), str(payload["focus"]).strip(), ""] + for option in payload["options"]: + lines.extend( + [ + f"{option['key']}. {option['title']}", + f"更适合:{option['best_for']}", + f"推荐技术/能力:{option['suggested_capability']}", + f"我为什么会考虑这个:{option['rationale']}", + f"代价:{option['tradeoff']}", + "", + ] + ) + recommendation = payload["recommendation"] + lines.extend( + [ + f"⭐ 当前我更推荐:{recommendation['key']}", + f"原因:{recommendation['reason']}", + "", + str(payload["reply_instruction"]).strip() or "你回 A / B / C,我就顺着这条继续往下收。", + ] + ) + return "\n".join(lines).strip() + + @classmethod + def _validate_requirement_plan_round_render_for_feishu( + cls, + *, + rendered_text: str, + packet: dict[str, Any], + ) -> None: + text = str(rendered_text or "").strip() + required_markers = ("A. ", "B. ", "C. ", "⭐ 当前我更推荐:") + missing = [marker for marker in required_markers if marker not in text] + if missing: + raise ValueError("requirement_plan_round 渲染结果不完整,不能发送到飞书") + max_chars = cls._requirement_plan_budget_limit(packet=packet, key="max_chars", default=1500) + if len(text) > max_chars: + raise ValueError(f"requirement_plan_round 超出飞书文本预算:{len(text)}>{max_chars}") + + @classmethod + def _requirement_plan_context_allows_deployment_terms(cls, *, context_material: Any) -> bool: + context = WorkflowSemanticPlanner._normalize_text(json.dumps(context_material, ensure_ascii=False)) + deployment_hints = ( + "openclaw", + "批量自动化部署", + "自动化部署", + "部署治理", + "发布治理", + "发布平台", + "部署平台", + "云原生交付底座", + "云原生交付版", + "任务编排平台版", + "平台运维", + "环境接入闭环", + "回滚", + "审批", + "kubernetes", + "k8s", + "helm", + "argocd", + "terraform", + ) + return any(term in context for term in deployment_hints) + + @classmethod + def _build_requirement_plan_draft_packet( + cls, + *, + card: dict[str, Any], + revision_note: Optional[str] = None, + ) -> dict[str, Any]: + profile = cls._detect_requirement_plan_profile(message_text="", card=card) + current_card = dict(card or {}) + collected_fields = { + key: str(current_card.get(key) or "").strip() + for key in ( + "project_nature", + "target_user", + "tech_route", + "first_scope", + "delivery_mode", + "success_signal", + "source_material", + "output_form", + "cadence", + "scoring_basis", + ) + if str(current_card.get(key) or "").strip() + } + return { + "profile": profile, + "current_context": { + "title": str(current_card.get("title") or "").strip(), + "goal": str(current_card.get("goal") or "").strip(), + "question_round": int(current_card.get("question_round") or 0), + }, + "collected_fields": collected_fields, + "collected_field_summary": cls._summarize_requirement_plan_collected_fields( + card=current_card, + profile=profile, + ), + "pending_items": cls._build_requirement_plan_missing_info(card=current_card), + "known_constraints": { + "draft_kind": "formal_brief_for_confirmation", + "stay_in_current_business_context": True, + "do_not_leak_internal_keys": True, + "do_not_reuse_old_template_terms": True, + "external_signal_sources_only": True, + "external_signal_source_min_count": 3, + "external_signal_source_max_count": 5, + }, + "revision_request": str(revision_note or "").strip()[:500] or None, + } + + @staticmethod + def _normalize_requirement_plan_draft_list( + values: Any, + *, + item_limit: int = 72, + max_items: int = 5, + ) -> list[str]: + if isinstance(values, str): + raw_items = re.split(r"[;;,,\n]", values) + elif isinstance(values, (list, tuple, set)): + raw_items = list(values) + else: + raw_items = [] + items: list[str] = [] + for raw_item in raw_items: + text = re.sub(r"\s+", " ", str(raw_item or "")).strip() + if not text: + continue + text = text[:item_limit].rstrip(",,;;、 ") or text[:item_limit] + if text and text not in items: + items.append(text) + if len(items) >= max_items: + break + return items + + @classmethod + def _normalize_requirement_plan_draft_requirement_items( + cls, + value: Any, + ) -> list[dict[str, str]]: + if not isinstance(value, list): + return [] + items: list[dict[str, str]] = [] + for raw_item in value: + if not isinstance(raw_item, dict): + continue + section = cls._compact_round_text(raw_item.get("section"), limit=24) + title = cls._compact_round_text(raw_item.get("title"), limit=32) + description = cls._compact_round_text(raw_item.get("description"), limit=96) + if not section or not title or not description: + continue + items.append( + { + "section": section, + "title": title, + "description": description, + } + ) + if len(items) >= 8: + break + return items + + @classmethod + def _normalize_requirement_plan_draft_team_setup( + cls, + value: Any, + ) -> list[dict[str, str]]: + if not isinstance(value, list): + return [] + items: list[dict[str, str]] = [] + for raw_item in value: + if not isinstance(raw_item, dict): + continue + role = cls._compact_round_text(raw_item.get("role"), limit=24) + notes = cls._compact_round_text(raw_item.get("notes"), limit=96) + if not role and not notes: + continue + items.append({"role": role or "建议配置", "notes": notes or "待补充"}) + if len(items) >= 5: + break + return items + + @classmethod + def _normalize_requirement_plan_draft_external_signal_sources( + cls, + value: Any, + ) -> list[dict[str, str]]: + if not isinstance(value, list): + return [] + blocked_terms = ( + "飞书需求消息", + "需求消息", + "项目/任务/责任人数据", + "责任人", + "数据库", + "日志", + "工作流", + "审计", + "能力图谱", + "用户当前需求描述", + ) + items: list[dict[str, str]] = [] + seen: set[str] = set() + for raw_item in value: + if not isinstance(raw_item, dict): + continue + name = cls._compact_round_text(raw_item.get("name"), limit=28) + purpose = cls._compact_round_text(raw_item.get("purpose"), limit=72) + phase = cls._compact_round_text(raw_item.get("phase"), limit=20) + if not name or not purpose or not phase: + continue + normalized_name = WorkflowSemanticPlanner._normalize_text(name) + normalized_purpose = WorkflowSemanticPlanner._normalize_text(purpose) + if any( + WorkflowSemanticPlanner._normalize_text(term) in normalized_name + or WorkflowSemanticPlanner._normalize_text(term) in normalized_purpose + for term in blocked_terms + ): + continue + if normalized_name in seen: + continue + seen.add(normalized_name) + items.append({"name": name, "purpose": purpose, "phase": phase}) + if len(items) >= 5: + break + return items + + @classmethod + def _collect_requirement_topic_text_fragments(cls, value: Any) -> list[str]: + fragments: list[str] = [] + + def _walk(node: Any) -> None: + if node is None: + return + if isinstance(node, dict): + for item in node.values(): + _walk(item) + return + if isinstance(node, (list, tuple, set)): + for item in node: + _walk(item) + return + text = str(node or "").strip() + if text: + fragments.append(text) + + _walk(value) + return fragments + + @classmethod + def _extract_requirement_overlap_tokens(cls, text: Any) -> set[str]: + raw = str(text or "").strip().lower() + if not raw: + return set() + normalized = re.sub(r"\s+", "", raw) + ascii_tokens = { + token + for token in re.findall(r"[a-z0-9]{2,}", normalized, re.IGNORECASE) + if str(token or "").strip() + } + cjk_chunks = re.findall(r"[\u4e00-\u9fff]{2,}", normalized) + cjk_tokens: set[str] = set() + for chunk in cjk_chunks: + if len(chunk) <= 2: + cjk_tokens.add(chunk) + continue + for index in range(len(chunk) - 1): + cjk_tokens.add(chunk[index : index + 2]) + return ascii_tokens | cjk_tokens + + @classmethod + def _requirement_seed_overlap_ratio( + cls, + *, + message_text: str, + topic: Optional[MountedTopicWorkspace], + draft: Optional[MountedDraftWorkspace], + ) -> float: + message_tokens = cls._extract_requirement_overlap_tokens(message_text) + if not message_tokens: + return 0.0 + context_fragments: list[str] = [] + if topic is not None: + context_fragments.extend(cls._collect_requirement_topic_text_fragments(getattr(topic, "facts_json", None))) + context_fragments.extend(cls._collect_requirement_topic_text_fragments(getattr(topic, "goals_json", None))) + context_fragments.extend( + cls._collect_requirement_topic_text_fragments(getattr(topic, "constraints_json", None)) + ) + if draft is not None: + context_fragments.extend( + cls._collect_requirement_topic_text_fragments(getattr(draft, "requirement_card", None)) + ) + context_fragments.extend( + cls._collect_requirement_topic_text_fragments(getattr(draft, "draft_payload", None)) + ) + context_tokens: set[str] = set() + for fragment in context_fragments: + context_tokens.update(cls._extract_requirement_overlap_tokens(fragment)) + if not context_tokens: + return 0.0 + return len(message_tokens & context_tokens) / max(len(message_tokens), 1) + + @classmethod + def _guard_requirement_plan_draft_against_internal_context_leaks( + cls, + *, + payload: dict[str, Any], + ) -> None: + rendered = WorkflowSemanticPlanner._normalize_text( + " ".join(cls._collect_requirement_topic_text_fragments(payload)) + ) + internal_terms = ( + "current_context", + "collected_fields", + "collected_field_summary", + "known_constraints", + "question_round", + "requirement_plan_draft_packet", + ) + leaked = [term for term in internal_terms if WorkflowSemanticPlanner._normalize_text(term) in rendered] + if leaked: + raise ValueError(f"requirement_plan_draft leaks internal packet terms: {', '.join(leaked[:3])}") + + @classmethod + def _guard_requirement_plan_draft_against_stale_context( + cls, + *, + payload: dict[str, Any], + packet: dict[str, Any], + ) -> None: + rendered = WorkflowSemanticPlanner._normalize_text(json.dumps(payload, ensure_ascii=False)) + context_material = { + "current_context": packet.get("current_context"), + "collected_fields": packet.get("collected_fields"), + "collected_field_summary": packet.get("collected_field_summary"), + } + if cls._requirement_plan_context_allows_deployment_terms(context_material=context_material): + return + stale_terms = ( + "审批、审计、权限一次做满", + "人员能力图谱", + "工作流状态与审计日志", + "确认门禁", + "状态流转和回滚治理", + "部署目标清单", + "环境参数与凭证", + "执行日志与失败日志", + "平台运维团队", + "回滚治理", + "部署治理", + "kubernetes", + "k8s", + "helm", + "argocd", + "terraform", + ) + leaked = [term for term in stale_terms if term in rendered] + if leaked: + raise ValueError(f"requirement_plan_draft contains stale context terms: {', '.join(leaked[:3])}") + + @classmethod + def _validate_requirement_plan_draft_payload( + cls, + *, + payload: Any, + packet: dict[str, Any], + ) -> dict[str, Any]: + if not isinstance(payload, dict): + raise ValueError("requirement_plan_draft payload is not a dict") + + def _text(key: str, *, limit: int, default: str = "") -> str: + return cls._compact_round_text(payload.get(key) or default, limit=limit) + + requirement_items = cls._normalize_requirement_plan_draft_requirement_items(payload.get("requirement_items")) + if not requirement_items: + raise ValueError("requirement_plan_draft requirement_items are missing") + + team_setup = cls._normalize_requirement_plan_draft_team_setup(payload.get("team_setup")) + external_signal_sources = cls._normalize_requirement_plan_draft_external_signal_sources( + payload.get("external_signal_sources") + ) + validation_metrics_raw = payload.get("validation_metrics") if isinstance(payload.get("validation_metrics"), dict) else {} + validation_metrics = { + "product": cls._normalize_requirement_plan_draft_list( + validation_metrics_raw.get("product"), + item_limit=48, + max_items=5, + ) + } + + normalized_payload = { + "intro": _text("intro", limit=72, default="📋 我先按当前收敛结果,整理成一版需求草稿:"), + "title": _text("title", limit=80, default=str(packet.get("current_context", {}).get("title") or "待补充")), + "one_liner": _text("one_liner", limit=180), + "goal": _text("goal", limit=180, default=str(packet.get("current_context", {}).get("goal") or "")), + "project_nature": _text("project_nature", limit=120), + "target_user": _text("target_user", limit=120), + "signal_inputs": _text("signal_inputs", limit=180), + "known_info": _text("known_info", limit=220), + "tech_route": _text("tech_route", limit=180), + "first_scope": _text("first_scope", limit=180), + "core_flow": _text("core_flow", limit=180), + "delivery_mode": _text("delivery_mode", limit=160), + "success_signal": _text("success_signal", limit=180), + "risks": _text("risks", limit=240), + "pending_items": cls._normalize_requirement_plan_draft_list( + payload.get("pending_items"), + item_limit=48, + max_items=5, + ), + "closing": _text( + "closing", + limit=72, + default="如果你认可,我下一轮就按这版继续细化;如果要改,直接说改哪一项,我就在这版上改。", + ), + "summary": _text("summary", limit=220), + "positioning_one_liner": _text("positioning_one_liner", limit=160), + "target_customers": _text("target_customers", limit=120), + "external_signal_sources": external_signal_sources, + "requirement_items": requirement_items, + "missing_info": cls._normalize_requirement_plan_draft_list( + payload.get("missing_info"), + item_limit=48, + max_items=6, + ), + "mvp_items": cls._normalize_requirement_plan_draft_list( + payload.get("mvp_items"), + item_limit=48, + max_items=6, + ), + "team_setup": team_setup, + "validation_metrics": validation_metrics, + } + required_fields = ( + "title", + "one_liner", + "goal", + "project_nature", + "target_user", + "signal_inputs", + "known_info", + "tech_route", + "first_scope", + "delivery_mode", + "success_signal", + "risks", + "summary", + "positioning_one_liner", + "target_customers", + ) + missing = [field for field in required_fields if not str(normalized_payload.get(field) or "").strip()] + if missing: + raise ValueError(f"requirement_plan_draft core fields are incomplete: {', '.join(missing[:3])}") + if len(external_signal_sources) < 3: + raise ValueError("requirement_plan_draft external_signal_sources must contain at least 3 external sources") + cls._guard_requirement_plan_draft_against_internal_context_leaks(payload=normalized_payload) + cls._guard_requirement_plan_draft_against_stale_context(payload=normalized_payload, packet=packet) + return normalized_payload + + @classmethod + def _render_requirement_plan_draft_payload(cls, payload: dict[str, Any]) -> str: + pending = ";".join(payload.get("pending_items") or []) or "暂无,已经可以继续整理成正式创建草稿" + team_setup = payload.get("team_setup") if isinstance(payload.get("team_setup"), list) else [] + external_signal_sources = ( + payload.get("external_signal_sources") if isinstance(payload.get("external_signal_sources"), list) else [] + ) + team_text = ";".join( + f"{str(item.get('role') or '').strip()}:{str(item.get('notes') or '').strip()}".strip(":") + for item in team_setup + if str(item.get("role") or "").strip() or str(item.get("notes") or "").strip() + ) + lines = [ + str(payload.get("intro") or "").strip(), + "", + f"🧾【需求名称】{str(payload.get('title') or '').strip()}", + "", + f"🧭【一句话判断】{str(payload.get('one_liner') or '').strip()}", + "", + f"🎯【需求目标】{str(payload.get('goal') or '').strip()}", + "", + f"🏗️【项目性质】{str(payload.get('project_nature') or '').strip()}", + "", + f"👥【服务对象】{str(payload.get('target_user') or '').strip()}", + "", + f"📥【基础输入】{str(payload.get('signal_inputs') or '').strip()}", + "", + f"📝【当前已知信息】{str(payload.get('known_info') or '').strip()}", + "", + f"⚙️【技术路线】{str(payload.get('tech_route') or '').strip()}", + "", + f"🧱【第一版范围】{str(payload.get('first_scope') or '').strip()}", + ] + if external_signal_sources: + lines.extend(["", "🌐【外部信号源】"]) + lines.extend( + f"{idx}. {str(item.get('name') or '').strip()}:{str(item.get('purpose') or '').strip()}({str(item.get('phase') or '').strip()})" + for idx, item in enumerate(external_signal_sources, start=1) + if str(item.get("name") or "").strip() and str(item.get("purpose") or "").strip() + ) + if str(payload.get("core_flow") or "").strip(): + lines.extend(["", f"🔄【建议核心流程】{str(payload.get('core_flow') or '').strip()}"]) + if team_text: + lines.extend(["", f"👤【人员配置】建议最小配置:{team_text}"]) + recommended_name = str(payload.get("recommended_assignee_name") or "").strip() + recommended_reason = str(payload.get("recommended_assignee_reason") or "").strip() + recommended_unavailable = str(payload.get("recommended_assignee_unavailable_reason") or "").strip() + if recommended_name: + workload = payload.get("recommended_assignee_workload") + workload_text = "" + try: + if workload is not None and workload != "": + workload_value = float(workload) + workload_text = f"(当前负载 {int(workload_value) if workload_value.is_integer() else round(workload_value, 1)}%)" + except Exception: + workload_text = "" + lines.extend(["", f"👤【推荐负责人】{recommended_name}{workload_text}"]) + if recommended_reason: + lines.append(f"理由:{recommended_reason}") + elif recommended_unavailable: + lines.extend(["", f"👤【推荐负责人】{recommended_unavailable}"]) + lines.extend( + [ + "", + f"🚀【推进方式】{str(payload.get('delivery_mode') or '').strip()}", + "", + f"✅【成功标准】{str(payload.get('success_signal') or '').strip()}", + "", + f"⚠️【风险与依赖】{str(payload.get('risks') or '').strip()}", + "", + f"❓【待确认】还需要你拍板:{pending}", + "", + str(payload.get("closing") or "").strip(), + ] + ) + return "\n".join(line for line in lines if line is not None).strip() + + @classmethod + def _build_requirement_plan_draft_prompt( + cls, + *, + packet: dict[str, Any], + repair_reason: Optional[str] = None, + ) -> str: + prompt = PMFLOW_REQUIREMENT_PLAN_DRAFT_PROMPT.replace( + "{draft_packet}", + json.dumps(packet, ensure_ascii=False, indent=2), + ) + if not str(repair_reason or "").strip(): + return prompt + return ( + "系统纠错说明:上一次草稿输出被系统拒绝,请重写整个 JSON,并修复以下问题:\n" + f"- {str(repair_reason).strip()}\n" + "- 不要复述任何 packet / current_context / collected_fields / known_constraints 之类的内部键名。\n" + "- 不要把旧部署治理场景词带到当前无关需求里,比如审批、审计、权限一次做满、人员能力图谱、工作流状态与审计日志、确认门禁。\n" + "- 只输出修正后的 JSON,不要解释。\n\n" + f"{prompt}" + ) + + async def _generate_requirement_plan_draft_with_retry( + self, + *, + packet: dict[str, Any], + ) -> dict[str, Any]: + last_error: Optional[Exception] = None + for attempt in range(2): + prompt = self._build_requirement_plan_draft_prompt( + packet=packet, + repair_reason=str(last_error) if last_error is not None else None, + ) + try: + extraction = await asyncio.wait_for( + llm_service.generate_structured_object( + prompt, + db=self.db, + tenant_id=int(self.tenant_id), + function_name="pmflow_requirement_plan_draft", + use_prompt_profile=False, + temperature=0.0, + max_tokens=1800, + response_mode="fast", + ), + timeout=45, + ) + payload = extraction.payload if extraction is not None and isinstance(extraction.payload, dict) else None + return self._validate_requirement_plan_draft_payload(payload=payload, packet=packet) + except Exception as exc: # noqa: BLE001 + last_error = exc + if attempt >= 1: + raise + if last_error is not None: + raise last_error + raise ValueError("requirement_plan_draft generation failed") + + async def _build_it_project_requirement_plan_draft_artifact( + self, + *, + card: dict[str, Any], + revision_note: Optional[str] = None, + ) -> tuple[str, dict[str, Any]]: + packet = self._build_requirement_plan_draft_packet(card=card, revision_note=revision_note) + normalized_payload = await self._generate_requirement_plan_draft_with_retry(packet=packet) + owner = await self._recommend_requirement_plan_owner_for_draft(card=card) + if isinstance(owner, dict): + normalized_payload["recommended_assignee_id"] = _safe_int(owner.get("user_id")) + normalized_payload["recommended_assignee_name"] = str(owner.get("name") or "").strip()[:40] + normalized_payload["recommended_assignee_workload"] = float(owner.get("workload_ratio") or 0.0) + normalized_payload["recommended_assignee_reason"] = str(owner.get("reason") or "").strip()[:120] + else: + normalized_payload["recommended_assignee_unavailable_reason"] = "当前系统里还没检索到可靠负责人,先不冒然指定。" + rendered_text = self._render_requirement_plan_draft_payload(normalized_payload) + if str(normalized_payload.get("delivery_mode") or "").strip(): + delivery_mode_text = str(normalized_payload.get("delivery_mode") or "").strip() + delivery_mode_label = self._summarize_it_project_choice_label( + field="delivery_mode", + value=delivery_mode_text, + ) + if delivery_mode_label == "项目级试点": + card["delivery_mode"] = "项目级试点(带基础审批和回滚)" + elif delivery_mode_label: + card["delivery_mode"] = delivery_mode_label + else: + card["delivery_mode"] = delivery_mode_text + if str(normalized_payload.get("success_signal") or "").strip(): + card["success_signal"] = str(normalized_payload.get("success_signal") or "").strip() + return rendered_text, normalized_payload + + async def _generate_requirement_plan_confirmation_draft( + self, + *, + card: dict[str, Any], + revision_note: Optional[str] = None, + ) -> tuple[str, Optional[dict[str, Any]], dict[str, Any]]: + profile = self._detect_requirement_plan_profile(message_text="", card=card) + structured_draft: Optional[dict[str, Any]] = None + if profile == "it_project": + answer, structured_draft = await self._build_it_project_requirement_plan_draft_artifact( + card=card, + revision_note=revision_note, + ) + else: + answer = self._build_requirement_plan_draft_text(card=card) + project_creation_draft = self._build_requirement_plan_project_creation_draft( + card=card, + draft_text=answer, + structured_draft=structured_draft, + ) + return answer, structured_draft, project_creation_draft + + @classmethod + def _build_requirement_plan_round_prompt( + cls, + *, + packet: dict[str, Any], + repair_reason: Optional[str] = None, + ) -> str: + prompt = PMFLOW_REQUIREMENT_PLAN_ROUND_PROMPT.replace( + "{round_packet}", + json.dumps(packet, ensure_ascii=False, indent=2), + ) + if not str(repair_reason or "").strip(): + return prompt + return ( + "系统纠错说明:上一次输出被系统拒绝,请重写整个 JSON,并修复以下问题:\n" + f"- {str(repair_reason).strip()}\n" + "- 不要复述任何 packet / current_context / latest_user_message / collected_fields 之类的内部键名。\n" + "- 不要把旧部署治理场景词带到当前无关需求里,比如发布治理平台、平台运维团队、任务编排平台版、云原生交付底座。\n" + "- 只输出修正后的 JSON,不要解释。\n\n" + f"{prompt}" + ) + + async def _generate_requirement_plan_round_with_retry( + self, + *, + packet: dict[str, Any], + ) -> tuple[dict[str, Any], str]: + last_error: Optional[Exception] = None + for attempt in range(2): + prompt = self._build_requirement_plan_round_prompt( + packet=packet, + repair_reason=str(last_error) if last_error is not None else None, + ) + try: + extraction = await asyncio.wait_for( + llm_service.generate_structured_object( + prompt, + db=self.db, + tenant_id=int(self.tenant_id), + function_name="pmflow_requirement_plan_round", + use_prompt_profile=False, + temperature=0.0, + max_tokens=1400, + response_mode="fast", + ), + timeout=45, + ) + payload = extraction.payload if extraction is not None and isinstance(extraction.payload, dict) else None + normalized_payload = self._validate_requirement_plan_round_payload(payload=payload, packet=packet) + rendered_text = self._render_requirement_plan_round_payload(normalized_payload) + self._validate_requirement_plan_round_render_for_feishu(rendered_text=rendered_text, packet=packet) + return normalized_payload, rendered_text + except Exception as exc: # noqa: BLE001 + last_error = exc + if attempt >= 1: + raise + if last_error is not None: + raise last_error + raise ValueError("requirement_plan_round generation failed") + + async def _build_it_project_requirement_plan_next_question( + self, + *, missing_field: str, profile: str, message_text: str = "", card: Optional[dict[str, Any]] = None, + latest_updates: Optional[dict[str, Any]] = None, ) -> str: - if profile == "it_project": - current_card = dict(card or {}) - current_project_nature = cls._summarize_it_project_choice_label(field="project_nature", value=current_card.get("project_nature") or "当前方向") - current_target_user = cls._summarize_it_project_choice_label(field="target_user", value=current_card.get("target_user") or "当前团队") - current_tech_route = cls._summarize_it_project_choice_label(field="tech_route", value=current_card.get("tech_route") or "当前技术路线") - current_first_scope = cls._summarize_it_project_choice_label(field="first_scope", value=current_card.get("first_scope") or "当前范围") - if missing_field == "project_nature": - return ( - "🧭 我先按“批量自动化部署 OpenClaw”这类 IT 项目来收。\n" - "现在最关键的,不是立刻展开做法,而是先定它更像工具、平台,还是长期底座。\n\n" - "A. 先做内部提效工具\n" - "更适合:先减少重复部署和手工操作\n" - "推荐技术/能力:FastAPI + React + PostgreSQL\n" - "我为什么会考虑这个:开发快,适合先把 MVP 跑通\n" - "代价:后续任务编排和治理能力要继续补\n\n" - "B. 先做发布治理平台\n" - "更适合:多人协作、多环境发布、需要审批和回滚\n" - "推荐技术/能力:FastAPI + React + PostgreSQL + Redis\n" - "我当前更偏这个:更适合承接状态流转、执行追踪和治理规则\n" - "代价:前期设计复杂度高于 A\n\n" - "C. 直接做云原生交付底座\n" - "更适合:目标是多环境、多实例、规模化标准交付\n" - "推荐技术/能力:Kubernetes + Helm + ArgoCD/Terraform\n" - "我为什么暂时不主推:长期上限最高,但基础设施门槛也最大\n" - "代价:基础设施门槛和早期投入最大\n\n" - "⭐ 当前我更推荐:B\n" - "原因:你提的是批量自动化部署,通常核心矛盾不只是提效,而是发布治理和流程标准化。\n\n" - "你回 A / B / C,我就顺着这条继续往下收。" - ) - if missing_field == "target_user": - return ( - f"🧭 我先按“{current_project_nature}”这个方向继续收。\n" - "现在要先把服务对象定住,不然后面做执行体验、治理能力还是项目复制,重心会飘。\n\n" - "A. 开发团队\n" - "更适合:开发自己发起部署、追执行结果\n" - "推荐技术/能力:一键执行、日志查看、失败重试\n" - "我为什么会考虑这个:反馈最快,最适合先验证使用价值\n" - "代价:治理能力会相对弱一些\n\n" - "B. 平台运维团队\n" - "更适合:统一管理环境、凭证、发布窗口和回滚\n" - "推荐技术/能力:批量执行、审批、审计、回滚\n" - "我当前更偏这个:最符合部署治理场景,风险控制也更强\n" - "代价:前期需要更清晰的权限和流程边界\n\n" - "C. 项目交付团队\n" - "更适合:按项目模板复制环境或服务\n" - "推荐技术/能力:项目模板、环境参数、标准交付流程\n" - "我为什么会考虑这个:适合多项目复制交付\n" - "代价:前提是流程已经比较稳定\n\n" - "⭐ 当前我更推荐:B\n" - "原因:先把平台运维场景收清楚,后面的技术路线、权限边界和治理要求会更稳。\n\n" - "你回 A / B / C,我就顺着这条继续往下收。" - ) - if missing_field == "tech_route": - return ( - f"🧭 我先按“{current_target_user}要长期使用”这个方向继续收。\n" - "现在最关键的,不是再补更多背景,而是先把技术路线定住,因为它会直接影响第一版工期和后续扩展方式。\n\n" - "A. 先做脚本增强版\n" - "更适合:先快速验证批量执行闭环\n" - "推荐技术/能力:FastAPI + Shell/Ansible + PostgreSQL\n" - "我为什么会考虑这个:最快能落地,适合先把执行链跑通\n" - "代价:任务编排和可观测性上限一般\n\n" - "B. 先做任务编排平台版\n" - "更适合:长期要做批量任务、状态机、失败重试和执行追踪\n" - "推荐技术/能力:FastAPI + React + Redis + Celery + PostgreSQL\n" - "我当前更偏这个:最平衡,既能落地又保留平台化空间\n" - "代价:系统复杂度高于 A\n\n" - "C. 直接做云原生交付版\n" - "更适合:目标是多环境、多实例和规模化标准交付\n" - "推荐技术/能力:Kubernetes + Helm + ArgoCD/Terraform + FastAPI\n" - "我为什么暂时不主推:环境一致性和规模化能力最强,但门槛也最高\n" - "代价:对基础设施成熟度要求最高\n\n" - "⭐ 当前我更推荐:B\n" - "原因:它最适合把批量自动化部署做成长期可用的平台,而不是一次性脚本工具。\n\n" - "你回 A / B / C,我就顺着这条继续往下收。" - ) - if missing_field == "first_scope": - return ( - f"🧭 我先按“{current_tech_route}”继续往下收。\n" - "这一步要定的,是第一版到底先证明什么价值,避免一上来摊太大。\n\n" - "A. 批量执行闭环\n" - "更适合:先证明平台真正能跑通部署任务\n" - "推荐技术/能力:任务创建、执行日志、失败重试\n" - "我当前更偏这个:最能体现平台核心价值\n" - "代价:环境接入和治理能力会稍后补\n\n" - "B. 环境接入闭环\n" - "更适合:先把服务器、集群、仓库和凭证接进来\n" - "推荐技术/能力:目标注册、凭证管理、环境选择\n" - "我为什么会考虑这个:能为后续自动化执行打基础\n" - "代价:短期可见价值不如 A 直接\n\n" - "C. 治理闭环\n" - "更适合:团队已经进入正式上线阶段\n" - "推荐技术/能力:审批、回滚、审计、权限\n" - "我为什么会考虑这个:能先控制上线风险和责任边界\n" - "代价:没有稳定执行闭环时,容易先做重了\n\n" - "⭐ 当前我更推荐:A\n" - "原因:MVP 最先要证明“能执行、能追踪、失败能恢复”,这样平台主线才会立住。\n\n" - "你回 A / B / C,我就顺着这条继续往下收。" - ) - return ( - f"🧭 我先按“{current_first_scope}”继续收尾。\n" - "现在要拍板的,是先用什么方式把第一版推起来:先轻量验证、先试点,还是直接平台化。\n\n" - "A. 内部 PoC\n" - "更适合:先验证 1-2 个典型部署任务\n" - "推荐技术/能力:轻量试用、快速反馈、低风险验证\n" - "我为什么会考虑这个:最适合快速试错\n" - "代价:对真实治理问题暴露不够充分\n\n" - "B. 项目级试点\n" - "更适合:选一个真实项目先落地\n" - "推荐技术/能力:试点接入、基础审批、基础回滚\n" - "我当前更偏这个:最能检验真实价值,也能尽早暴露流程问题\n" - "代价:需要更明确的项目配合和负责人\n\n" - "C. 平台化上线\n" - "更适合:一开始就计划多团队接入\n" - "推荐技术/能力:统一权限、统一规范、统一环境管理\n" - "我为什么暂时不主推:长期价值最大,但前期投入和协作复杂度也最高\n" - "代价:前期投入和协作复杂度最高\n\n" - "⭐ 当前我更推荐:B\n" - "原因:PoC 太轻,平台化太重,项目级试点最适合项目管理推进和真实验收。\n\n" - "你回 A / B / C,我就顺着这条继续往下收。" - ) - if profile == "scoring": - if missing_field == "scoring_basis": - return "那我先确认一个最关键的问题:这个分数主要想根据什么来打,是技术指标、情绪资金,还是你已经有一套规则想自动化?" - if missing_field == "target_user": - return "那我继续往下收一格:这个结果主要给谁看,是投顾、自己,还是内部研究/交易团队?" - if missing_field == "output_form": - return "那我再确认一个点:你更想怎么接收这个结果,是飞书日报、页面看板,还是只推送一个分数加一句解释?" - if missing_field == "cadence": - return "更新节奏我也一起记一下:这件事希望什么时候更新,是每日收盘后、盘中,还是开盘前?" - return "最后再收一下范围:第一版你最想先保留哪些输出,哪些先不做,才算足够轻?" + packet = self._build_requirement_plan_round_packet( + missing_field=missing_field, + profile=profile, + message_text=message_text, + card=card, + latest_updates=latest_updates, + ) + normalized_payload, rendered_text = await self._generate_requirement_plan_round_with_retry(packet=packet) + if isinstance(card, dict): + option_maps = dict(card.get("round_option_maps") or {}) if isinstance(card.get("round_option_maps"), dict) else {} + option_maps[str(missing_field or "").strip()] = { + str(option["key"]).strip().upper(): str(option["title"]).strip() + for option in normalized_payload["options"] + if str(option.get("key") or "").strip() + } + card["round_option_maps"] = option_maps + card["last_round_options"] = { + "field": str(missing_field or "").strip(), + "options": list(normalized_payload["options"]), + "recommendation": dict(normalized_payload["recommendation"]), + } + return rendered_text - normalized = WorkflowSemanticPlanner._normalize_text(message_text) - if missing_field == "source_material": - if any(token in normalized for token in ("复盘", "直播")): - return "第一版你最想先解决复盘里的哪一步:切片、总结,还是可执行清单?" - if any(token in normalized for token in ("雷达", "监控", "预警")): - return "第一版你最想先盯哪类信号:政策、舆情,还是竞品动作?" - return ( - "我先把输入来源定一下,不然后面收范围时容易越聊越散。\n\n" - "A. 先按当前聊天里的需求描述来\n" - "更适合:你先把目标讲清,我边聊边收第一版\n\n" - "B. 先按会议纪要 / 文档来\n" - "更适合:已经有结构化材料,重点是提炼结果\n\n" - "C. 先按录音转写 / 回放来\n" - "更适合:信息还比较散,需要先转成可整理内容\n\n" - "你回 A / B / C,也可以直接说更准确的输入来源。" - ) - if missing_field == "target_user": - return ( - "我先把结果主要给谁用定一下,这会直接影响后面的内容粒度和表达方式。\n\n" - "A. 核心执行人 / 内部 PM\n" - "更适合:先服务负责跟进、拆动作、推动落地的人\n\n" - "B. 负责人 / 管理者\n" - "更适合:先看结论、风险和待拍板项\n\n" - "C. 整个团队 / 终端用户\n" - "更适合:需要直接同步所有相关方,或结果就是给最终使用者看的\n\n" - "你回 A / B / C,也可以直接说更准确的对象。" - ) - if missing_field == "output_form": - return ( - "结果最后怎么给你,我先一起定掉,不然后面范围会偏。\n\n" - "A. 飞书总结\n" - "更适合:一次性看完整结论和重点\n\n" - "B. 飞书消息\n" - "更适合:更轻、更快,直接在会话里收结果\n\n" - "C. 单独页面\n" - "更适合:后面要长期查看、筛选或继续编辑\n\n" - "你回 A / B / C,也可以直接说你想要的交付方式。" - ) - if missing_field == "cadence": - return ( - "时效我也一起记一下,先把你能接受的出结果节奏定住。\n\n" - "A. 触发后半小时内\n" - "更适合:希望尽快看到第一版结果\n\n" - "B. 当天内\n" - "更适合:接受汇总后统一产出\n\n" - "C. 第二天\n" - "更适合:允许补充整理和复核\n\n" - "你回 A / B / C,也可以直接说更具体的时效。" - ) - return "第一版先保留哪些字段最有价值,比如待办、责任人、截止时间,哪些先不做?" + async def _build_requirement_plan_next_question( + self, + *, + missing_field: str, + profile: str, + message_text: str = "", + card: Optional[dict[str, Any]] = None, + latest_updates: Optional[dict[str, Any]] = None, + ) -> str: + return await self._build_it_project_requirement_plan_next_question( + missing_field=missing_field, + profile=profile, + message_text=message_text, + card=card, + latest_updates=latest_updates, + ) @classmethod def _build_it_project_signal_sources(cls, *, card: dict[str, Any]) -> str: - normalized = WorkflowSemanticPlanner._normalize_text( - " ".join( - str(card.get(key) or "").strip() - for key in ("title", "goal", "project_nature", "tech_route", "first_scope", "delivery_mode") - if str(card.get(key) or "").strip() - ) - ) sources = ["飞书需求消息"] - if any(token in normalized for token in ("部署", "发布", "openclaw", "回滚", "环境")): + if cls._is_deployment_it_project(card=card): sources.extend( [ "部署目标清单(服务/环境/实例)", @@ -1023,10 +1782,21 @@ class ExecuteQueryProjectMixin: ] ) else: - sources.extend(["目标对象清单", "代码与配置来源", "执行反馈日志"]) + sources.extend(["项目/任务/责任人数据", "人员能力图谱", "工作流状态与审计日志"]) deduped = list(dict.fromkeys(sources)) return f"当前至少要接入:{';'.join(deduped)}" + @classmethod + def _is_deployment_it_project(cls, *, card: dict[str, Any]) -> bool: + normalized = WorkflowSemanticPlanner._normalize_text( + " ".join( + str(card.get(key) or "").strip() + for key in ("title", "goal", "tech_route", "first_scope") + if str(card.get(key) or "").strip() + ) + ) + return any(token in normalized for token in ("部署", "openclaw", "回滚", "环境", "镜像", "仓库", "k8s", "kubernetes")) + @classmethod def _build_it_project_known_info(cls, *, card: dict[str, Any]) -> str: facts: list[str] = [] @@ -1056,19 +1826,27 @@ class ExecuteQueryProjectMixin: delivery_mode = cls._compact_requirement_plan_label(str(card.get("delivery_mode") or "内部 PoC")) if not delivery_mode: delivery_mode = "内部 PoC" + title = str(card.get("title") or "当前 IT 项目").strip() + first_scope = cls._trim_sentence_tail(card.get("first_scope") or "核心工作流") + if cls._is_deployment_it_project(card=card): + return ( + f"先按面向{target_user}的{delivery_mode}推进," + f"先把{title}的批量部署、执行追踪和失败恢复跑成一条闭环。" + ) return ( f"先按面向{target_user}的{delivery_mode}推进," - "先把 OpenClaw 的批量部署、执行追踪和失败恢复跑成一条闭环。" + f"先把{title}的{first_scope}跑成一条可验证闭环。" ) @staticmethod def _trim_sentence_tail(value: Any) -> str: return str(value or "").strip().rstrip("。.!!??") - @staticmethod - def _build_it_project_core_flow(*, card: dict[str, Any]) -> str: - _ = card - return "提交部署需求 -> 生成部署任务 -> 选择目标环境 -> 执行部署 -> 回传结果/日志 -> 失败重试或回滚 -> 留痕审计" + @classmethod + def _build_it_project_core_flow(cls, *, card: dict[str, Any]) -> str: + if cls._is_deployment_it_project(card=card): + return "提交部署需求 -> 生成部署任务 -> 选择目标环境 -> 执行部署 -> 回传结果/日志 -> 失败重试或回滚 -> 留痕审计" + return "提交需求信号 -> 生成项目 brief -> 拆出待办/责任人/截止时间 -> 校验人员能力与负载 -> 进入确认门禁 -> 留痕审计" @classmethod def _build_it_project_staffing(cls, *, card: dict[str, Any]) -> str: @@ -1108,13 +1886,22 @@ class ExecuteQueryProjectMixin: def _build_it_project_risks_and_dependencies(cls, *, card: dict[str, Any]) -> str: delivery_mode = str(card.get("delivery_mode") or "").strip() tech_route = str(card.get("tech_route") or "").strip() - risks = [ - "部署目标清单是否稳定", - "环境访问权限与凭证归属", - "仓库/镜像/配置规范", - "凭证管理", - "失败重试与回滚策略", - ] + if cls._is_deployment_it_project(card=card): + risks = [ + "部署目标清单是否稳定", + "环境访问权限与凭证归属", + "仓库/镜像/配置规范", + "凭证管理", + "失败重试与回滚策略", + ] + else: + risks = [ + "需求事实是否足够清晰", + "责任人与截止时间是否可验证", + "人员能力图谱与负载数据是否完整", + "确认门禁和审计链路是否闭环", + "生成草稿是否能保留证据来源", + ] if "项目级试点" in delivery_mode: risks.append("试点项目配合度") if "平台化上线" in delivery_mode: @@ -1123,19 +1910,35 @@ class ExecuteQueryProjectMixin: risks.append("K8s/基础设施成熟度") return "当前主要风险在" + "、".join(risks) + @classmethod + def _build_it_project_default_success_signal(cls, *, card: dict[str, Any]) -> str: + if cls._is_deployment_it_project(card=card): + return "先保证能稳定创建部署任务、看到执行结果,并能识别失败原因" + return "先保证能稳定生成项目 brief、拆出待办/责任人/截止时间,并进入确认门禁" + @classmethod def _build_it_project_pending_items(cls, *, card: dict[str, Any]) -> list[str]: pending: list[str] = [] if not str(card.get("success_signal") or "").strip(): pending.append("成功判断标准还没完全定死") - pending.extend( - [ - "部署目标范围(服务/环境/实例)", - "仓库/镜像/配置来源", - "环境权限与凭证归属", - "试点负责人和投入人天", - ] - ) + if cls._is_deployment_it_project(card=card): + pending.extend( + [ + "部署目标范围(服务/环境/实例)", + "仓库/镜像/配置来源", + "环境权限与凭证归属", + "试点负责人和投入人天", + ] + ) + else: + pending.extend( + [ + "需求事实来源与证据口径", + "责任人字段来源", + "截止时间字段来源", + "试点负责人和投入人天", + ] + ) tech_route = str(card.get("tech_route") or "").strip() if any(token in tech_route for token in ("Kubernetes", "ArgoCD", "Terraform")): pending.append("现有 K8s / GitOps 基础设施成熟度") @@ -1143,95 +1946,56 @@ class ExecuteQueryProjectMixin: @classmethod def _build_requirement_plan_draft_text(cls, *, card: dict[str, Any]) -> str: - profile = cls._detect_requirement_plan_profile(message_text="", card=card) - pending = [] - if not str(card.get("success_signal") or "").strip(): - pending.append("成功判断标准还没完全定死") - if profile == "it_project": - pending = cls._build_it_project_pending_items(card=card) - lines = [ - "📋 我先按当前收敛结果,整理成一版更像正式 brief 的需求草稿:", - "", - f"🧾【需求名称】{str(card.get('title') or '待补充').strip()}", - "", - f"🧭【一句话判断】{cls._build_it_project_one_line_judgement(card=card)}", - "", - "🎯【需求目标】" - f"{cls._trim_sentence_tail(card.get('goal') or '待补充')}。" - " 这次先不追求把审批、审计、权限一次做满,而是先把主链路跑通。", - "", - f"🏗️【项目性质】这件事更像{str(card.get('project_nature') or '待补充').strip()},不像一次性脚本工具,后面需要承接状态流转和回滚治理。", - "", - f"👥【服务对象】第一版先服务{str(card.get('target_user') or '待补充').strip()},他们会最早发起部署、看执行结果、处理失败和判断是否回滚。", - "", - f"📡【信号源】{cls._build_it_project_signal_sources(card=card)}", - "", - f"📝【当前已知信息】{cls._build_it_project_known_info(card=card)}", - "", - "⚙️【技术路线】" - f"当前更适合走{str(card.get('tech_route') or '待补充').strip()}," - "因为它比脚本方案更稳,也没有一上来重到云原生底座。", - "", - "🧱【第一版范围】" - f"{cls._trim_sentence_tail(card.get('first_scope') or '先收一个最小可跑闭环')}。" - " 这版重点先放在任务创建、执行日志和失败重试。", - "", - f"🔄【建议核心流程】{cls._build_it_project_core_flow(card=card)}", - "", - f"👤【人员配置】{cls._build_it_project_staffing(card=card)}", - "", - "🚀【推进方式】" - f"先按{str(card.get('delivery_mode') or '待补充').strip()}推进," - "这样能尽快在真实项目里验证价值,也能更早暴露流程问题。", - "", - "✅【成功标准】" - f"{cls._trim_sentence_tail(card.get('success_signal') or '先保证能稳定创建部署任务、看到执行结果,并能识别失败原因')}," - "并且能在试点里跑完一次真实闭环。", - "", - f"⚠️【风险与依赖】{cls._build_it_project_risks_and_dependencies(card=card)}。", - "", - f"❓【待确认】还需要你拍板:{';'.join(pending) if pending else '暂无,已经可以继续整理成正式创建草稿'}", - "", - "如果你认可,我下一轮就按这版继续细化;如果要改,直接说改哪一项,我就在这版上改。", - ] - return "\n".join(lines).strip() - if profile == "generic": - lines = [ - "📋 我先把当前收敛结果整理成一版需求草稿,你直接看这版就行:", - "", - f"【需求名称】{str(card.get('title') or '待补充').strip()}", - f"【需求目标】{str(card.get('goal') or '待补充').strip()}", - f"【输入材料】{str(card.get('source_material') or '待补充').strip()}", - f"【目标用户】{str(card.get('target_user') or '待补充').strip()}", - f"【输出形式】{str(card.get('output_form') or '待补充').strip()}", - f"【响应时效】{str(card.get('cadence') or '待补充').strip()}", - f"【第一版范围】{str(card.get('first_scope') or '先保留最关键的结构化字段').strip()}", - f"【成功标准】{str(card.get('success_signal') or '先保证会后能稳定产出一份可执行的总结').strip()}", - f"【待确认】{';'.join(pending) if pending else '暂无,已经可以继续整理成正式创建草稿'}", - "", - "如果你认可,我下一轮就按这版继续细化;如果要改,直接说改哪一项,我就在这版上改。", - ] - else: - lines = [ - "📋 我先把当前收敛结果整理成一版需求草稿,你直接看这版就行:", - "", - f"【需求名称】{str(card.get('title') or '待补充').strip()}", - f"【需求目标】{str(card.get('goal') or '待补充').strip()}", - f"【目标用户】{str(card.get('target_user') or '待补充').strip()}", - f"【打分依据】{str(card.get('scoring_basis') or '待补充').strip()}", - f"【输出形式】{str(card.get('output_form') or '待补充').strip()}", - f"【更新频率】{str(card.get('cadence') or '待补充').strip()}", - f"【第一版范围】{str(card.get('first_scope') or '先保留一个分数、方向判断和一句解释').strip()}", - f"【成功标准】{str(card.get('success_signal') or '先保证每天能稳定给出分数和简短解释').strip()}", - f"【待确认】{';'.join(pending) if pending else '暂无,已经可以继续整理成正式创建草稿'}", - "", - "如果你认可,我下一轮就按这版继续细化;如果要改,直接说改哪一项,我就在这版上改。", - ] + pending = cls._build_it_project_pending_items(card=card) + lines = [ + "📋 我先按当前收敛结果,整理成一版更像正式 brief 的需求草稿:", + "", + f"🧾【需求名称】{str(card.get('title') or '待补充').strip()}", + "", + f"🧭【一句话判断】{cls._build_it_project_one_line_judgement(card=card)}", + "", + "🎯【需求目标】" + f"{cls._trim_sentence_tail(card.get('goal') or '待补充')}。" + " 这次先不追求把审批、审计、权限一次做满,而是先把主链路跑通。", + "", + f"🏗️【项目性质】这件事更像{str(card.get('project_nature') or '待补充').strip()},不像一次性脚本工具,后面需要承接状态流转和回滚治理。", + "", + f"👥【服务对象】第一版先服务{str(card.get('target_user') or '待补充').strip()},他们会最早补充需求、看收敛结果、处理待确认项和判断是否进入执行。", + "", + f"📡【信号源】{cls._build_it_project_signal_sources(card=card)}", + "", + f"📝【当前已知信息】{cls._build_it_project_known_info(card=card)}", + "", + "⚙️【技术路线】" + f"当前更适合走{str(card.get('tech_route') or '待补充').strip()}," + "因为它比脚本方案更稳,也没有一上来重到云原生底座。", + "", + "🧱【第一版范围】" + f"{cls._trim_sentence_tail(card.get('first_scope') or '先收一个最小可跑闭环')}。" + " 这版重点先放在可验证字段、责任边界和确认闭环。", + "", + f"🔄【建议核心流程】{cls._build_it_project_core_flow(card=card)}", + "", + f"👤【人员配置】{cls._build_it_project_staffing(card=card)}", + "", + "🚀【推进方式】" + f"先按{str(card.get('delivery_mode') or '待补充').strip()}推进," + "这样能尽快在真实项目里验证价值,也能更早暴露流程问题。", + "", + "✅【成功标准】" + f"{cls._trim_sentence_tail(card.get('success_signal') or cls._build_it_project_default_success_signal(card=card))}," + "并且能在试点里跑完一次真实闭环。", + "", + f"⚠️【风险与依赖】{cls._build_it_project_risks_and_dependencies(card=card)}。", + "", + f"❓【待确认】还需要你拍板:{';'.join(pending) if pending else '暂无,已经可以继续整理成正式创建草稿'}", + "", + "如果你认可,我下一轮就按这版继续细化;如果要改,直接说改哪一项,我就在这版上改。", + ] return "\n".join(lines).strip() @classmethod def _build_requirement_plan_requirement_items(cls, *, card: dict[str, Any]) -> list[dict[str, str]]: - profile = cls._detect_requirement_plan_profile(message_text="", card=card) items: list[dict[str, str]] = [] def _append(section: str, title: str, value: Any) -> None: @@ -1250,43 +2014,14 @@ class ExecuteQueryProjectMixin: _append("目标用户", "目标用户", card.get("target_user")) _append("第一版范围", "第一版范围", card.get("first_scope")) _append("成功标准", "成功标准", card.get("success_signal")) - if profile == "it_project": - _append("项目性质", "项目性质", card.get("project_nature")) - _append("技术路线", "技术路线", card.get("tech_route")) - _append("推进方式", "推进方式", card.get("delivery_mode")) - elif profile == "scoring": - _append("打分依据", "打分依据", card.get("scoring_basis")) - _append("输出形式", "输出形式", card.get("output_form")) - _append("更新频率", "更新频率", card.get("cadence")) - else: - _append("输入材料", "输入材料", card.get("source_material")) - _append("输出形式", "输出形式", card.get("output_form")) - _append("响应时效", "响应时效", card.get("cadence")) + _append("项目性质", "项目性质", card.get("project_nature")) + _append("技术路线", "技术路线", card.get("tech_route")) + _append("推进方式", "推进方式", card.get("delivery_mode")) return items[:12] @classmethod def _build_requirement_plan_missing_info(cls, *, card: dict[str, Any]) -> list[str]: - profile = cls._detect_requirement_plan_profile(message_text="", card=card) - if profile == "it_project": - return cls._build_it_project_pending_items(card=card) - - label_map = { - "source_material": "输入材料", - "target_user": "目标用户", - "output_form": "输出形式", - "cadence": "响应时效", - "scoring_basis": "打分依据", - "first_scope": "第一版范围", - "success_signal": "成功标准", - } - pending: list[str] = [] - for field in cls._requirement_plan_missing_fields(card): - label = label_map.get(str(field or "").strip()) - if label: - pending.append(label) - if not str(card.get("success_signal") or "").strip(): - pending.append("成功标准") - return list(dict.fromkeys(item for item in pending if str(item or "").strip())) + return cls._build_it_project_pending_items(card=card) @classmethod def _build_requirement_plan_project_creation_draft( @@ -1294,6 +2029,7 @@ class ExecuteQueryProjectMixin: *, card: dict[str, Any], draft_text: str, + structured_draft: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: profile = cls._detect_requirement_plan_profile(message_text="", card=card) title = str(card.get("title") or "需求收敛草稿").strip() or "需求收敛草稿" @@ -1301,35 +2037,92 @@ class ExecuteQueryProjectMixin: first_scope = str(card.get("first_scope") or "").strip() success_signal = str(card.get("success_signal") or "").strip() target_user = str(card.get("target_user") or "").strip() + structured = structured_draft if isinstance(structured_draft, dict) else {} summary_parts = [text for text in (goal, first_scope) if text] - summary = ";".join(dict.fromkeys(summary_parts))[:300] or title - positioning = "" - if profile == "it_project": + summary = str(structured.get("summary") or "").strip()[:300] or ";".join(dict.fromkeys(summary_parts))[:300] or title + positioning = str(structured.get("positioning_one_liner") or "").strip() + if not positioning and profile == "it_project": positioning = cls._build_it_project_one_line_judgement(card=card) if not positioning: positioning = first_scope or goal or title + target_customers = str(structured.get("target_customers") or "").strip()[:120] or target_user[:120] or None + requirement_items = ( + structured.get("requirement_items") + if isinstance(structured.get("requirement_items"), list) + else cls._build_requirement_plan_requirement_items(card=card) + ) + external_signal_sources = ( + structured.get("external_signal_sources") + if isinstance(structured.get("external_signal_sources"), list) + else [] + ) + for source in external_signal_sources[:5]: + if not isinstance(source, dict): + continue + source_name = str(source.get("name") or "").strip() + source_purpose = str(source.get("purpose") or "").strip() + source_phase = str(source.get("phase") or "").strip() + if not source_name or not source_purpose: + continue + requirement_items.append( + { + "section": "外部信号源", + "title": source_name, + "description": f"{source_purpose};{source_phase}".strip(";"), + } + ) + missing_info = ( + structured.get("missing_info") + if isinstance(structured.get("missing_info"), list) + else cls._build_requirement_plan_missing_info(card=card) + ) + mvp_items = ( + structured.get("mvp_items") + if isinstance(structured.get("mvp_items"), list) + else ([first_scope] if first_scope else []) + ) + validation_metrics = ( + structured.get("validation_metrics") + if isinstance(structured.get("validation_metrics"), dict) + else ({"product": [success_signal]} if success_signal else {}) + ) draft_payload = { "draft_source": "requirement_plan", "plan_profile": profile, - "title": title, + "title": str(structured.get("title") or "").strip() or title, "summary": summary, "positioning_one_liner": positioning[:200], - "target_customers": target_user[:120] or None, + "target_customers": target_customers, "original_text": str(draft_text or "").strip()[:4000] or summary, - "requirement_items": cls._build_requirement_plan_requirement_items(card=card), - "missing_info": cls._build_requirement_plan_missing_info(card=card), - "mvp_items": [first_scope] if first_scope else [], - "validation_metrics": ( - {"product": [success_signal]} - if success_signal - else {} - ), - "success_signal": success_signal or None, + "requirement_items": requirement_items, + "external_signal_sources": external_signal_sources, + "missing_info": missing_info, + "mvp_items": mvp_items, + "validation_metrics": validation_metrics, + "success_signal": str(structured.get("success_signal") or "").strip() or success_signal or None, "draft_text": str(draft_text or "").strip(), "requirement_card": dict(card), } - if profile == "it_project": + assignee_id = _safe_int(structured.get("recommended_assignee_id")) + assignee_name = str(structured.get("recommended_assignee_name") or "").strip()[:40] + assignee_reason = str(structured.get("recommended_assignee_reason") or "").strip()[:160] + assignee_workload = structured.get("recommended_assignee_workload") + if assignee_id is not None: + draft_payload["recommended_assignee_id"] = int(assignee_id) + if assignee_name: + draft_payload["recommended_assignee_name"] = assignee_name + if assignee_reason: + draft_payload["recommended_assignee_reason"] = assignee_reason + if assignee_workload is not None and assignee_workload != "": + try: + draft_payload["recommended_assignee_workload"] = float(assignee_workload) + except (TypeError, ValueError): + pass + team_setup = structured.get("team_setup") if isinstance(structured.get("team_setup"), list) else None + if team_setup is not None: + draft_payload["team_setup"] = team_setup + elif profile == "it_project": draft_payload["team_setup"] = [ { "role": "建议配置", @@ -1361,204 +2154,47 @@ class ExecuteQueryProjectMixin: if key not in {"plan_profile", "title", "goal"} } - @classmethod - def _topic_expects_requirement_plan_choice_reply(cls, *, topic: Optional[MountedTopicWorkspace]) -> bool: - if topic is None: - return False - plan_status = str(getattr(topic, "plan_status", "") or "").strip().lower() - if plan_status in {"awaiting_execute_confirm", "plan_ready", "suspended"}: - return False - pending_actions = getattr(topic, "pending_actions", None) - if isinstance(pending_actions, (list, tuple, set)) and "confirm_requirement_draft" in { - str(item or "").strip() for item in pending_actions - }: - return False - metadata = getattr(topic, "metadata_json", None) - if not isinstance(metadata, dict): - return False - last_prompt = str(metadata.get("plan_last_prompt") or "").strip() - if not last_prompt: - return False - normalized_prompt = WorkflowSemanticPlanner._normalize_text(last_prompt) - if "你回a/b/c" in normalized_prompt or "直接回a/b/c" in normalized_prompt: - return True - return bool(re.search(r"(^|\n)\s*[ABC]\.\s*", last_prompt)) - - @classmethod - def _should_continue_requirement_plan_from_workspace( - cls, + async def _backfill_query_semantic_bundle( + self, *, message_text: str, - topic: Optional[MountedTopicWorkspace] = None, - ) -> bool: - normalized = WorkflowSemanticPlanner._normalize_text(message_text) - if not normalized or WorkflowSemanticPlanner._looks_like_casual_chat_message(message_text): - return False - if WorkflowSemanticPlanner.looks_like_explicit_new_requirement_topic(message_text): - return False - # Active plan threads often continue with "A,xxx原因..." rather than a bare letter. - # Once a user is clearly responding to an A/B/C follow-up, keep the flow in plan mode. - if cls._extract_requirement_plan_choice(message_text): - return cls._topic_expects_requirement_plan_choice_reply(topic=topic) - if cls._requirement_plan_material_updates(message_text=message_text): - return True - return any(token in normalized for token in ("整理成草稿", "出草稿", "汇总一下", "按这版", "就这样先收")) - - @classmethod - def _looks_like_requirement_plan_seed(cls, *, message_text: str) -> bool: - normalized = WorkflowSemanticPlanner._normalize_text(message_text) - if not normalized: - return False - material_updates = cls._requirement_plan_material_updates(message_text=message_text) - has_updates = bool(material_updates) - if WorkflowSemanticPlanner.looks_like_explicit_new_requirement_topic(message_text): - topic_tail = "" - if "关于" in normalized: - topic_tail = normalized.partition("关于")[2] - topic_tail = re.sub(r"[@_a-z0-9]+", "", topic_tail) - has_topic_subject = len(topic_tail) >= 4 - return ( - has_updates - or WorkflowSemanticPlanner._looks_like_requirement_plan_chat(message_text) - or WorkflowSemanticPlanner.looks_like_project_create_request(message_text) - or has_topic_subject + source_system: str, + metadata: dict[str, Any], + requested_project_id: Optional[int], + source_chat_id: Optional[str], + source_sender_open_id: Optional[str], + thread_context: Optional[dict[str, Any]], + conversation_key: Optional[str], + active_requirement_plan_topic: Optional[MountedTopicWorkspace], + active_requirement_plan_draft: Optional[MountedDraftWorkspace], + ) -> dict[str, Any]: + planner_metadata = dict(metadata or {}) + if requested_project_id is not None: + planner_metadata.setdefault("project_id", int(requested_project_id)) + if source_chat_id: + planner_metadata.setdefault("source_chat_id", source_chat_id) + if source_sender_open_id: + planner_metadata.setdefault("source_sender_open_id", source_sender_open_id) + if conversation_key: + planner_metadata.setdefault("conversation_key", conversation_key) + if isinstance(thread_context, dict): + planner_metadata.setdefault("thread_context", dict(thread_context)) + if active_requirement_plan_topic is not None and active_requirement_plan_draft is not None: + planner_metadata.setdefault("active_requirement_plan_present", True) + planner_metadata.setdefault( + "active_requirement_plan_status", + str(getattr(active_requirement_plan_topic, "plan_status", "") or "").strip().lower() or None, ) - if has_updates: - return True - return ( - WorkflowSemanticPlanner._looks_like_requirement_plan_chat(message_text) - or WorkflowSemanticPlanner.looks_like_project_create_request(message_text) - or any(token in normalized for token in ("需求", "工具", "助手", "系统", "平台", "纪要", "复盘", "打分")) - ) - - @classmethod - def _looks_like_requirement_plan_continuation(cls, *, message_text: str) -> bool: - if cls._should_continue_requirement_plan_from_workspace(message_text=message_text): - return True - return WorkflowSemanticPlanner._looks_like_requirement_plan_slot_answer(message_text) - - @classmethod - def _collect_requirement_topic_text_fragments(cls, payload: Any) -> list[str]: - fragments: list[str] = [] - if isinstance(payload, dict): - for value in payload.values(): - fragments.extend(cls._collect_requirement_topic_text_fragments(value)) - return fragments - if isinstance(payload, (list, tuple, set)): - for item in payload: - fragments.extend(cls._collect_requirement_topic_text_fragments(item)) - return fragments - text = str(payload or "").strip() - if text: - fragments.append(text) - return fragments - - @classmethod - def _build_requirement_topic_overlap_tokens(cls, text: str) -> set[str]: - normalized = WorkflowSemanticPlanner._normalize_text(text) - if not normalized: - return set() - for stopword in ( - "想跟你聊", - "跟你聊", - "聊聊", - "聊个", - "聊需求", - "先不创建", - "先别创建", - "先不要创建", - "先收第一版", - "先收一版", - "第一版", - "关于", - "需求", - "项目", - "工具", - "系统", - "平台", - "方案", - "一个", - "这个", - "那个", - "我们", - "这次", - "现在", - ): - normalized = normalized.replace(stopword, "") - tokens: set[str] = { - token - for token in re.findall(r"[a-z][a-z0-9_-]{1,}", normalized) - if len(token) >= 2 - } - chinese_text = re.sub(r"[a-z0-9_-]+", "", normalized) - for index in range(len(chinese_text) - 1): - token = chinese_text[index : index + 2] - if len(token.strip()) == 2: - tokens.add(token) - return tokens - - @classmethod - def _compute_requirement_topic_overlap_score( - cls, - *, - topic: Optional[MountedTopicWorkspace], - draft: Optional[MountedDraftWorkspace], - message_text: str, - ) -> float: - context_parts: list[str] = [] - if topic is not None: - context_parts.extend(cls._collect_requirement_topic_text_fragments(topic.facts_json)) - context_parts.extend(cls._collect_requirement_topic_text_fragments(topic.goals_json)) - context_parts.extend(cls._collect_requirement_topic_text_fragments(topic.constraints_json)) - if draft is not None: - context_parts.extend(cls._collect_requirement_topic_text_fragments(draft.requirement_card)) - context_parts.extend(cls._collect_requirement_topic_text_fragments(draft.draft_payload)) - topic_tokens = cls._build_requirement_topic_overlap_tokens(" ".join(context_parts)) - message_tokens = cls._build_requirement_topic_overlap_tokens(message_text) - if not topic_tokens or not message_tokens: - return 0.0 - overlap = len(topic_tokens & message_tokens) - denominator = max(1, min(len(topic_tokens), len(message_tokens))) - return round(overlap / denominator, 4) - - @classmethod - def _looks_like_requirement_new_topic_candidate(cls, *, message_text: str) -> bool: - normalized = WorkflowSemanticPlanner._normalize_text(message_text) - if not normalized: - return False - if WorkflowSemanticPlanner.looks_like_explicit_new_requirement_topic(message_text): - return True - if cls._looks_like_requirement_plan_continuation(message_text=message_text): - return False - if not cls._looks_like_requirement_plan_seed(message_text=message_text): - return False - return ( - WorkflowSemanticPlanner._looks_like_requirement_plan_chat(message_text) - or WorkflowSemanticPlanner.looks_like_project_create_request(message_text) - or any(token in normalized for token in ("想做", "做个", "做一个", "有个需求", "关于")) - ) - - @classmethod - def _should_switch_requirement_topic_by_overlap( - cls, - *, - topic: MountedTopicWorkspace, - draft: Optional[MountedDraftWorkspace], - message_text: str, - ) -> bool: - if not cls._looks_like_requirement_new_topic_candidate(message_text=message_text): - return False - overlap_score = cls._compute_requirement_topic_overlap_score( - topic=topic, - draft=draft, + planner_metadata.setdefault( + "active_requirement_draft_state", + str(getattr(active_requirement_plan_draft, "state", "") or "").strip().lower() or None, + ) + bundle = await WorkflowSemanticPlanner.aplan_message( message_text=message_text, + source_system=source_system, + metadata=planner_metadata, ) - plan_status = str(getattr(topic, "plan_status", "") or "").strip().lower() - if plan_status in {"awaiting_execute_confirm", "plan_ready"}: - return overlap_score <= 0.12 - if plan_status == "options_ready": - return overlap_score <= 0.08 - return overlap_score == 0.0 + return bundle.to_dict() async def _find_active_requirement_plan_workspace( self, @@ -1609,7 +2245,7 @@ class ExecuteQueryProjectMixin: select(MountedDraftWorkspace).where( MountedDraftWorkspace.tenant_id == int(self.tenant_id), MountedDraftWorkspace.topic_workspace_id == int(topic.id), - MountedDraftWorkspace.state.in_(("collecting", "ready")), + MountedDraftWorkspace.state.in_(("collecting", "ready", "draft_pending_confirm")), ) ) ).scalar_one_or_none() @@ -1756,40 +2392,6 @@ class ExecuteQueryProjectMixin: ) return topic is not None and draft is not None - async def _infer_requirement_plan_topic_switch( - self, - *, - message_text: str, - source_system: str, - source_message_id: str, - source_chat_id: Optional[str], - sender: Optional[str], - source_sender_open_id: Optional[str], - thread_context: Optional[dict[str, Any]], - conversation_key: Optional[str], - message_intent: Optional[str], - ) -> Optional[str]: - if str(message_intent or "").strip().lower() != "requirement_plan": - return None - topic, draft = await self._find_active_requirement_plan_workspace( - source_system=source_system, - source_message_id=source_message_id, - source_chat_id=source_chat_id, - sender=sender, - source_sender_open_id=source_sender_open_id, - thread_context=thread_context, - conversation_key=conversation_key, - ) - if topic is None: - return None - if self._should_switch_requirement_topic_by_overlap( - topic=topic, - draft=draft, - message_text=message_text, - ): - return "new" - return None - async def _build_requirement_planning_direct_result( self, *, @@ -1817,12 +2419,26 @@ class ExecuteQueryProjectMixin: if topic is None or draft is None: profile = self._detect_requirement_plan_profile(message_text=message_text, card={}) card = self._extract_requirement_plan_updates(message_text=message_text, card={"plan_profile": profile}) - next_question = self._build_requirement_plan_next_question( - missing_field=self._requirement_plan_missing_fields(card)[0] if self._requirement_plan_missing_fields(card) else "first_scope", - profile=profile, - message_text=message_text, - card=card, - ) + try: + next_question = await self._build_requirement_plan_next_question( + missing_field=self._requirement_plan_missing_fields(card)[0] if self._requirement_plan_missing_fields(card) else "first_scope", + profile=profile, + message_text=message_text, + card=card, + latest_updates=card, + ) + except Exception as exc: + logger.warning( + "requirement_plan_round_generation_failed", + tenant_id=self.tenant_id, + source_system=source_system, + source_message_id=source_message_id, + error=str(exc), + ) + return { + "status": "failed_safe", + "error_message": f"需求收敛选项生成失败:{str(exc)[:180]}", + } return self._build_direct_query_result( answer=f"可以,我们先不创建项目,先把这件事收成一版需求。\n\n{next_question}", scene_label="Mounted Runtime Plan", @@ -1831,7 +2447,10 @@ class ExecuteQueryProjectMixin: ) card = dict(draft.requirement_card or {}) - if force_new_topic and not self._looks_like_requirement_plan_seed(message_text=message_text): + has_material_input = bool(self._requirement_plan_material_updates(message_text=message_text)) + has_choice_reply = bool(self._extract_requirement_plan_choice(message_text)) + has_nontrivial_seed_text = len(self._sanitize_requirement_plan_text(message_text).strip()) >= 6 + if force_new_topic and not (has_material_input or has_choice_reply or has_nontrivial_seed_text): topic.plan_status = "collecting" topic.pending_actions = ["continue_requirement_plan"] topic.metadata_json = { @@ -1892,11 +2511,23 @@ class ExecuteQueryProjectMixin: topic.pending_actions = ["confirm_requirement_draft"] if should_emit_draft else ["continue_requirement_plan"] topic.readiness_score = 1.0 if should_emit_draft else readiness if should_emit_draft: - answer = self._build_requirement_plan_draft_text(card=card) - project_creation_draft = self._build_requirement_plan_project_creation_draft( - card=card, - draft_text=answer, - ) + structured_draft: Optional[dict[str, Any]] = None + try: + answer, structured_draft, project_creation_draft = await self._generate_requirement_plan_confirmation_draft( + card=card + ) + except Exception as exc: + logger.warning( + "requirement_plan_draft_generation_failed", + tenant_id=self.tenant_id, + source_system=source_system, + source_message_id=source_message_id, + error=str(exc), + ) + return { + "status": "failed_safe", + "error_message": f"需求草稿生成失败:{str(exc)[:180]}", + } draft.state = "ready" draft.draft_version = int(draft.draft_version or 0) + 1 draft.requirement_card = card @@ -1929,12 +2560,26 @@ class ExecuteQueryProjectMixin: } return result - next_question = self._build_requirement_plan_next_question( - missing_field=missing_fields[0] if missing_fields else "first_scope", - profile=profile, - message_text=message_text, - card=card, - ) + try: + next_question = await self._build_requirement_plan_next_question( + missing_field=missing_fields[0] if missing_fields else "first_scope", + profile=profile, + message_text=message_text, + card=card, + latest_updates=updates, + ) + except Exception as exc: + logger.warning( + "requirement_plan_round_generation_failed", + tenant_id=self.tenant_id, + source_system=source_system, + source_message_id=source_message_id, + error=str(exc), + ) + return { + "status": "failed_safe", + "error_message": f"需求收敛选项生成失败:{str(exc)[:180]}", + } ack = self._build_requirement_plan_ack(updates=updates, profile=profile) answer = f"{ack}\n\n{next_question}" if ack else f"可以,我们先不创建项目,先把这件事收成一版需求。\n\n{next_question}" draft.state = "collecting" @@ -2094,6 +2739,150 @@ class ExecuteQueryProjectMixin: "如果你要,我可以继续把推荐负责人写进草稿或再细分成主负责人与协作人。" ) + async def _recommend_requirement_plan_owner_for_draft( + self, + *, + card: dict[str, Any], + ) -> Optional[dict[str, Any]]: + from app.models import SkillTag, User + + current_card = dict(card or {}) + combined_text = WorkflowSemanticPlanner._normalize_text( + " ".join( + str(current_card.get(key) or "").strip() + for key in ( + "title", + "goal", + "project_nature", + "target_user", + "tech_route", + "first_scope", + "delivery_mode", + "success_signal", + ) + if str(current_card.get(key) or "").strip() + ) + ) + if not combined_text: + return None + + try: + user_rows = ( + await self.db.execute( + select(User, UserWorkload) + .outerjoin(UserWorkload, UserWorkload.user_id == User.id) + .where( + and_( + User.tenant_id == int(self.tenant_id), + User.is_active.is_(True), + User.status == "active", + ) + ) + .order_by(User.id.asc()) + ) + ).all() + except Exception as exc: # noqa: BLE001 + logger.warning( + "requirement_plan_owner_lookup_failed", + tenant_id=int(self.tenant_id), + error=str(exc), + ) + return None + if not user_rows: + return None + + user_ids = [int(user.id) for user, _ in user_rows] + binding_rows = [] + if user_ids: + try: + binding_rows = ( + await self.db.execute( + select(UserSkillBinding, SkillTag) + .join(SkillTag, SkillTag.id == UserSkillBinding.skill_tag_id) + .where( + and_( + UserSkillBinding.tenant_id == int(self.tenant_id), + UserSkillBinding.user_id.in_(user_ids), + ) + ) + ) + ).all() + except Exception as exc: # noqa: BLE001 + logger.warning( + "requirement_plan_owner_skill_lookup_failed", + tenant_id=int(self.tenant_id), + error=str(exc), + ) + binding_rows = [] + + binding_map: dict[int, list[tuple[str, str, float]]] = {} + matched_skill_tokens: set[str] = set() + for binding, skill in binding_rows: + token = WorkflowSemanticPlanner._normalize_text(getattr(skill, "name", "") or getattr(skill, "slug", "")) + if token and token in combined_text: + matched_skill_tokens.add(str(getattr(skill, "name", "") or getattr(skill, "slug", ""))) + binding_map.setdefault(int(binding.user_id), []).append( + ( + str(getattr(skill, "name", "") or getattr(skill, "slug", "")).strip(), + token, + float(getattr(binding, "proficiency", 0) or 0), + ) + ) + + candidates: list[dict[str, Any]] = [] + for user, workload in user_rows: + role_text = str(getattr(user, "role", "") or "") + role_tokens = {token.strip().lower() for token in re.split(r"[,/| ]+", role_text) if token.strip()} + is_managerial = bool(role_tokens & {"boss", "admin"}) or str(getattr(user, "name", "")).strip() in {"系统管理员", "老板"} + workload_ratio = float(getattr(workload, "workload_ratio", 0) or getattr(user, "workload", 0) or 0) + skills = binding_map.get(int(user.id), []) + matched_skills = [skill_name for skill_name, skill_token, _ in skills if skill_token and skill_token in combined_text] + avg_proficiency = (sum(skill_prof for _, _, skill_prof in skills) / len(skills)) if skills else 0.0 + score = len(matched_skills) * 30 + avg_proficiency * 0.25 - min(workload_ratio, 100.0) * 0.18 + if not skills: + score -= 8 + if is_managerial: + score -= 30 + candidates.append( + { + "user_id": int(user.id), + "name": str(getattr(user, "name", "") or f"成员{getattr(user, 'id', '')}").strip(), + "role": role_text, + "workload_ratio": round(workload_ratio, 1), + "matched_skills": matched_skills, + "score": round(score, 2), + "is_managerial": is_managerial, + } + ) + + ranked = sorted( + [item for item in candidates if not item["is_managerial"]] or candidates, + key=lambda item: (-item["score"], item["workload_ratio"], item["name"]), + ) + if not ranked: + return None + + primary = ranked[0] + backups = ranked[1:3] + if primary["matched_skills"]: + reason = f"系统检索到他命中技能 {', '.join(primary['matched_skills'][:3])},当前负载约 {primary['workload_ratio']}%。" + elif matched_skill_tokens: + reason = f"系统检索到他在现有能力图谱里更接近这类任务,当前负载约 {primary['workload_ratio']}%。" + else: + reason = f"系统先按当前负载和角色分布给出保守建议;他当前负载约 {primary['workload_ratio']}%。" + if backups: + reason += " 备选:" + "、".join( + f"{item['name']}(负载 {item['workload_ratio']}%)" for item in backups + ) + return { + "user_id": int(primary["user_id"]), + "name": str(primary["name"]), + "role": str(primary["role"]), + "workload_ratio": float(primary["workload_ratio"]), + "matched_skills": list(primary["matched_skills"]), + "reason": reason[:160], + } + @staticmethod def _build_direct_query_result(*, answer: str, scene_label: str, reason_code: str, query_type: str) -> dict[str, Any]: text = str(answer or "").strip() @@ -2227,19 +3016,17 @@ class ExecuteQueryProjectMixin: or semantic_diagnostics.get("force_thinking_mode") or "" ).strip() or None - explicit_new_requirement_topic = WorkflowSemanticPlanner.looks_like_explicit_new_requirement_topic(message_text) - if explicit_new_requirement_topic: - # "新需求 / 另一个需求" is a stronger signal than any stale upstream classify label. - # Force the conversation into a fresh requirement-plan topic so follow-up A/B/C replies - # cannot accidentally continue an unrelated historical draft in the same chat. - topic_switch = "new" - message_intent = "requirement_plan" - consultant_mode_preference = "clarify" - advise_subtype = None has_active_requirement_plan_workspace = False active_requirement_plan_topic: Optional[MountedTopicWorkspace] = None active_requirement_plan_draft: Optional[MountedDraftWorkspace] = None - if not message_intent: + has_requirement_anchor = bool( + isinstance(thread_context, dict) + and any( + str(thread_context.get(key) or "").strip() + for key in ("root_message_id", "reply_to_message_id", "thread_id") + ) + ) + if has_requirement_anchor: active_requirement_plan_topic, active_requirement_plan_draft = await self._find_active_requirement_plan_workspace( source_system=source_system, source_message_id=source_message_id, @@ -2252,19 +3039,40 @@ class ExecuteQueryProjectMixin: has_active_requirement_plan_workspace = ( active_requirement_plan_topic is not None and active_requirement_plan_draft is not None ) - if has_active_requirement_plan_workspace and self._should_continue_requirement_plan_from_workspace( - message_text=message_text, - topic=active_requirement_plan_topic, - ): + if has_active_requirement_plan_workspace: message_intent = "requirement_plan" - consultant_mode_preference = consultant_mode_preference or "clarify" - if not topic_switch and explicit_new_requirement_topic: - topic_switch = "new" - message_intent = message_intent or "requirement_plan" - consultant_mode_preference = consultant_mode_preference or "clarify" - if not topic_switch: - topic_switch = await self._infer_requirement_plan_topic_switch( - message_text=message_text, + topic_switch = str(topic_switch or "continue").strip().lower() or "continue" + consultant_mode_preference = str(consultant_mode_preference or "exploratory").strip().lower() or "exploratory" + normalized_message_intent = str(message_intent or "").strip().lower() + has_current_semantic_bundle = isinstance(semantic_bundle, dict) and bool(semantic_bundle) + has_current_rag_semantics = bool( + isinstance(rag_request_context, dict) + and any( + str(rag_request_context.get(key) or "").strip() + for key in ( + "message_intent", + "consultant_mode_preference", + "advise_subtype", + "topic_switch", + "thinking_mode", + ) + ) + ) + needs_semantic_backfill = ( + not has_current_semantic_bundle + and not has_current_rag_semantics + and not has_active_requirement_plan_workspace + ) + if not normalized_message_intent: + needs_semantic_backfill = True + elif normalized_message_intent in {"business_review", "requirement_plan"} and not str( + consultant_mode_preference or "" + ).strip(): + needs_semantic_backfill = True + elif normalized_message_intent == "requirement_plan" and not str(topic_switch or "").strip(): + needs_semantic_backfill = True + if needs_semantic_backfill: + active_requirement_plan_topic, active_requirement_plan_draft = await self._find_active_requirement_plan_workspace( source_system=source_system, source_message_id=source_message_id, source_chat_id=source_chat_id, @@ -2272,30 +3080,79 @@ class ExecuteQueryProjectMixin: source_sender_open_id=source_sender_open_id, thread_context=thread_context, conversation_key=conversation_key, - message_intent=message_intent, ) - if not message_intent and self._looks_like_requirement_plan_continuation(message_text=message_text): - if not has_active_requirement_plan_workspace: - active_requirement_plan_topic, active_requirement_plan_draft = await self._find_active_requirement_plan_workspace( - source_system=source_system, - source_message_id=source_message_id, - source_chat_id=source_chat_id, - sender=None, - source_sender_open_id=source_sender_open_id, - thread_context=thread_context, - conversation_key=conversation_key, - ) - has_active_requirement_plan_workspace = ( - str(topic_switch or "").strip().lower() != "new" - and active_requirement_plan_topic is not None - and active_requirement_plan_draft is not None - ) - if has_active_requirement_plan_workspace and self._should_continue_requirement_plan_from_workspace( + has_active_requirement_plan_workspace = ( + active_requirement_plan_topic is not None and active_requirement_plan_draft is not None + ) + semantic_bundle = await self._backfill_query_semantic_bundle( + message_text=message_text, + source_system=source_system, + metadata=metadata, + requested_project_id=requested_project_id if requested_project_id is not None else intent.project_id, + source_chat_id=source_chat_id, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + conversation_key=conversation_key, + active_requirement_plan_topic=active_requirement_plan_topic if has_requirement_anchor else None, + active_requirement_plan_draft=active_requirement_plan_draft if has_requirement_anchor else None, + ) + semantic_diagnostics = ( + semantic_bundle.get("diagnostics") if isinstance(semantic_bundle.get("diagnostics"), dict) else {} + ) + refreshed_message_intent = str(semantic_diagnostics.get("message_intent") or "").strip() or None + refreshed_consultant_mode = ( + str(semantic_diagnostics.get("consultant_mode_preference") or "").strip().lower() or None + ) + refreshed_advise_subtype = ( + str(semantic_diagnostics.get("advise_subtype") or "").strip().lower() or None + ) + refreshed_topic_switch = str(semantic_diagnostics.get("topic_switch") or "").strip().lower() or None + refreshed_thinking_mode = str(semantic_diagnostics.get("force_thinking_mode") or "").strip() or None + if refreshed_message_intent: + message_intent = refreshed_message_intent + if refreshed_consultant_mode: + consultant_mode_preference = refreshed_consultant_mode + if refreshed_advise_subtype: + advise_subtype = refreshed_advise_subtype + if refreshed_topic_switch: + topic_switch = refreshed_topic_switch + if refreshed_thinking_mode: + thinking_mode = refreshed_thinking_mode + metadata = {**metadata, "semantic_bundle": semantic_bundle} + normalized_message_intent = str(message_intent or "").strip().lower() + if ( + has_active_requirement_plan_workspace + and not has_requirement_anchor + and normalized_message_intent == "requirement_plan" + and not self._extract_requirement_plan_choice(message_text) + ): + nontrivial_seed = self._sanitize_requirement_plan_text(message_text).strip() + overlap_ratio = self._requirement_seed_overlap_ratio( message_text=message_text, topic=active_requirement_plan_topic, - ): - message_intent = "requirement_plan" - consultant_mode_preference = consultant_mode_preference or "clarify" + draft=active_requirement_plan_draft, + ) + if len(nontrivial_seed) >= 6 and overlap_ratio < 0.18: + topic_switch = "new" + explicit_new_requirement_topic = ( + str(message_intent or "").strip().lower() == "requirement_plan" + and str(topic_switch or "").strip().lower() == "new" + ) + if not has_active_requirement_plan_workspace and str(message_intent or "").strip().lower() == "requirement_plan": + active_requirement_plan_topic, active_requirement_plan_draft = await self._find_active_requirement_plan_workspace( + source_system=source_system, + source_message_id=source_message_id, + source_chat_id=source_chat_id, + sender=None, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + conversation_key=conversation_key, + ) + has_active_requirement_plan_workspace = ( + str(topic_switch or "").strip().lower() != "new" + and active_requirement_plan_topic is not None + and active_requirement_plan_draft is not None + ) if self._should_use_lightweight_chat_answer( message_text=message_text, message_intent=message_intent, @@ -2375,6 +3232,7 @@ class ExecuteQueryProjectMixin: rag_service=rag_service, message_text=message_text, project_id=requested_project_id if requested_project_id is not None else intent.project_id, + message_intent=message_intent, ) if project_lookup_result is not None: return project_lookup_result diff --git a/backend/app/services/ai_pmo/consultant_dispatch.py b/backend/app/services/ai_pmo/consultant_dispatch.py index 77e7f709..03625e1e 100644 --- a/backend/app/services/ai_pmo/consultant_dispatch.py +++ b/backend/app/services/ai_pmo/consultant_dispatch.py @@ -42,19 +42,6 @@ CONSULTANT_CHANNEL_STRATEGIES: dict[str, dict[str, Any]] = { } _QUERY_OPERATION_CODES = {"query.answer", "query.read_only"} -_SCOPE_EXPANSION_RE = re.compile( - r"(整个系统|全部重做|全量重构|一次替换所有|一次性全部|推翻重来|从零重写|所有模块都|整体改造)", - re.IGNORECASE, -) -_BRAINSTORM_RE = re.compile( - r"(想.idea|想法|brainstorm|头脑风暴|一起想|给我几个方案|给几个方向|发散一下|有哪些思路)", - re.IGNORECASE, -) -_ADVISE_RE = re.compile( - r"(建议|怎么做|如何做|优先|取舍|方案|还是|更适合|更应该|值不值得|要不要)", - re.IGNORECASE, -) - _MISSING_FIELD_LABELS = { "project_id": "归属项目", "task_title": "任务标题", @@ -63,15 +50,6 @@ _MISSING_FIELD_LABELS = { "project_scope": "项目范围", } -_LONG_REPLY_SECTION_HINTS = ( - "结论:", - "依据:", - "不确定项:", - "事实:", - "建议:", - "待确认项:", -) - _DOMAIN_NEXT_ACTION_FALLBACKS: dict[str, dict[str, str]] = { DOMAIN_RAG: { MODE_ADVISE: "继续追问具体模块、入口函数或文件路径,我会把调用链收窄到那一段。", @@ -121,14 +99,12 @@ def _normalize_action_text(text: str) -> str: return re.sub(r"\s+", " ", str(text or "")).strip() -def _looks_like_long_answer(text: str) -> bool: +def _is_multiline_or_long_answer(text: str) -> bool: raw = str(text or "") normalized = _normalize_action_text(raw) if not normalized: return False - if "\n" in raw or len(normalized) > 120: - return True - return any(token in normalized for token in _LONG_REPLY_SECTION_HINTS) + return "\n" in raw or len(normalized) > 120 def _domain_next_action(domain: str, mode: str) -> str: @@ -182,18 +158,6 @@ def _derive_allow_execute( return normalized_status in {"succeeded", "answered", "captured", "completed"} -def _looks_like_scope_expansion(message_text: str) -> bool: - return bool(_SCOPE_EXPANSION_RE.search(str(message_text or ""))) - - -def _looks_like_brainstorm(message_text: str) -> bool: - return bool(_BRAINSTORM_RE.search(str(message_text or ""))) - - -def _looks_like_advice(message_text: str) -> bool: - return bool(_ADVISE_RE.search(str(message_text or ""))) - - def _derive_mode( *, intent_name: str, @@ -204,20 +168,17 @@ def _derive_mode( allow_execute: bool, primary_operation_code: Optional[str], ) -> str: + _ = message_text if missing_fields: return MODE_CLARIFY - if _looks_like_scope_expansion(message_text): - return MODE_CHALLENGE if str(risk_level or "").strip().lower() == "high" and not allow_execute and not _is_query_like( primary_operation_code=primary_operation_code, intent_name=intent_name, ): return MODE_CHALLENGE - if _looks_like_brainstorm(message_text): - return MODE_BRAINSTORM if allow_execute and not _is_query_like(primary_operation_code=primary_operation_code, intent_name=intent_name): return MODE_EXECUTE - if _looks_like_advice(message_text) or _is_query_like(primary_operation_code=primary_operation_code, intent_name=intent_name): + if _is_query_like(primary_operation_code=primary_operation_code, intent_name=intent_name): return MODE_ADVISE if str(status or "").strip().lower() == "pending_manual": return MODE_CHALLENGE @@ -225,7 +186,7 @@ def _derive_mode( def _build_clarify_next_action(*, missing_fields: list[str], direct_reply_text: str) -> str: - if direct_reply_text and not _looks_like_long_answer(direct_reply_text): + if direct_reply_text and not _is_multiline_or_long_answer(direct_reply_text): return _normalize_action_text(direct_reply_text)[:120] labels = [_MISSING_FIELD_LABELS.get(item, item) for item in missing_fields] suffix = "、".join([label for label in labels if label]) or "关键信息" @@ -246,7 +207,7 @@ def _build_next_action( direct_reply_text=direct_reply_text, ) if mode == MODE_CHALLENGE: - if direct_reply_text and not _looks_like_long_answer(direct_reply_text): + if direct_reply_text and not _is_multiline_or_long_answer(direct_reply_text): return _normalize_action_text(direct_reply_text)[:120] if str(risk_level or "").strip().lower() == "high": return "建议先拆分范围,再确认是否继续推进高风险动作。"[:120] @@ -261,7 +222,7 @@ def _build_next_action( if domain == DOMAIN_SIGNAL: return "可继续确认归属项目,或查看后续自动处理结果。" return "可继续查看结果,或补充更多执行细节。" - if direct_reply_text and not _looks_like_long_answer(direct_reply_text): + if direct_reply_text and not _is_multiline_or_long_answer(direct_reply_text): return _normalize_action_text(direct_reply_text)[:120] return _domain_next_action(domain=domain, mode=mode) diff --git a/backend/app/services/ai_pmo/draft_confirm_handler.py b/backend/app/services/ai_pmo/draft_confirm_handler.py index 1ef75ef7..ec892bf6 100644 --- a/backend/app/services/ai_pmo/draft_confirm_handler.py +++ b/backend/app/services/ai_pmo/draft_confirm_handler.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import datetime +from datetime import datetime, timedelta from typing import Any, Optional from . import ai_pmo_kernel_common_core as _common @@ -13,7 +13,10 @@ for _name, _value in vars(_common).items(): from ..ai_steward_service import AIStewardService from ..mounted_ai_runtime_write_service import MountedAIRuntimeWriteService -from .workflow_semantic_planner import WorkflowSemanticPlanner +from .ai_pmo_typed_executor_execute import mixin_execute_query_project +from .semantic_context.assembler import SemanticContextAssembler +from .semantic_context.gatekeeper import SemanticDecisionGatekeeper +from .semantic_context.semantic_decision_service import SemanticDecisionService from ..signal.draft_generator_service import DraftGeneratorService from ...models import MountedDraftWorkspace, MountedTopicWorkspace from ...models.feishu_reply_outbox import FeishuReplyOutbox @@ -25,7 +28,14 @@ class DraftConfirmHandler: _ACTION_CONFIRM = "confirm" _ACTION_REVISE = "revise" _ACTION_CANCEL = "cancel" + _ACTION_ANSWER_ONLY = "answer_only" _ACTION_UNKNOWN = "unknown" + _SEMANTIC_FOLLOWUP_ACTIONS = (_ACTION_CONFIRM, _ACTION_REVISE, _ACTION_CANCEL, _ACTION_ANSWER_ONLY) + _EXPLICIT_CONFIRM_COMMANDS = frozenset({"确认", "confirm", "ok", "好的", "好", "yes", "y"}) + _EXPLICIT_CANCEL_COMMANDS = frozenset({"取消", "cancel", "不用了", "算了"}) + _DEFAULT_DISPLAY_FEEDBACK_REPLY = ( + "收到,这是展示形式反馈:卡片可以更完整,聊天直发我会控制得更短;当前草稿内容我先不重起。" + ) _WORK_ITEM_ID_RE = re.compile(r"(?:work[_ -]?item|工作项|草稿)\s*(?:id|ID)?\s*[:#:]?\s*(\d+)", re.IGNORECASE) @@ -35,11 +45,6 @@ class DraftConfirmHandler: self.tenant_id = int(kernel.tenant_id) self.draft_generator = DraftGeneratorService(self.db, tenant_id=self.tenant_id) - @classmethod - def parse_action_from_text(cls, *, message_text: str) -> tuple[str, str]: - action, note = WorkflowSemanticPlanner.parse_draft_action(message_text=message_text) - return action, note - async def handle_text_reply( self, *, @@ -51,11 +56,12 @@ class DraftConfirmHandler: source_sender_open_id: Optional[str], expected_version: Optional[int] = None, thread_context: Optional[dict[str, Any]] = None, + semantic_followup_override: Optional[dict[str, str]] = None, ) -> dict[str, Any]: - action, revision_note = self.parse_action_from_text(message_text=message_text) explicit_work_item_id = self._extract_work_item_id(message_text) - looks_like_revision = WorkflowSemanticPlanner.looks_like_natural_draft_revision(message_text) - + normalized_thread_context = self._normalize_thread_context(thread_context) + has_reply_anchor = bool(str(normalized_thread_context.get("reply_to_message_id") or "").strip()) + normalized_text = str(message_text or "").strip().lower() target = await self._resolve_pending_draft( sender=sender, source_system=source_system, @@ -65,7 +71,10 @@ class DraftConfirmHandler: thread_context=thread_context, ) if target is None: - if explicit_work_item_id is not None or action != self._ACTION_UNKNOWN or looks_like_revision: + if explicit_work_item_id is not None or ( + has_reply_anchor + and normalized_text in (self._EXPLICIT_CONFIRM_COMMANDS | self._EXPLICIT_CANCEL_COMMANDS) + ): return { "handled": True, "status": "failed_safe", @@ -90,6 +99,46 @@ class DraftConfirmHandler: } target_work_item, target_ingest = target + action = self._ACTION_UNKNOWN + revision_note = "" + active_mounted = await self._resolve_target_mounted_draft( + sender=sender, + source_system=source_system, + channel_ref=channel_ref, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + target_work_item_id=int(target_work_item.id), + ) + semantic_followup = ( + { + "action": str((semantic_followup_override or {}).get("action") or "").strip().lower(), + "revision_note": str((semantic_followup_override or {}).get("revision_note") or "").strip(), + "assistant_reply": str((semantic_followup_override or {}).get("assistant_reply") or "").strip(), + } + if isinstance(semantic_followup_override, dict) + else await self._resolve_semantic_followup_decision( + message_text=message_text, + source_system=source_system, + thread_context=thread_context, + active_mounted=active_mounted, + ) + ) + if semantic_followup is not None: + action = str(semantic_followup.get("action") or action).strip().lower() or action + revision_note = str(semantic_followup.get("revision_note") or revision_note).strip() + if action == self._ACTION_ANSWER_ONLY: + return { + "handled": True, + "status": "pending_manual", + "followup_reply": str(semantic_followup.get("assistant_reply") or "").strip() + or self._DEFAULT_DISPLAY_FEEDBACK_REPLY, + "target_work_item_id": int(target_work_item.id), + "target_state": str(target_work_item.state or "").strip() or None, + "target_intent": str(target_work_item.intent or "").strip() or None, + "project_id": _safe_int(target_work_item.project_id), + "signal_id": _safe_int(target_work_item.signal_id), + "workflow_route": RouteResolver.normalize_route_key(str(target_work_item.intent or "")), + } if action == self._ACTION_CONFIRM: return await self.confirm_draft( target_work_item=target_work_item, @@ -112,6 +161,18 @@ class DraftConfirmHandler: target_work_item=target_work_item, reason="用户取消草稿", ) + if explicit_work_item_id is None and not has_reply_anchor: + return { + "handled": False, + "status": "ignored", + "followup_reply": "", + "target_work_item_id": None, + "target_state": None, + "target_intent": None, + "project_id": None, + "signal_id": None, + "workflow_route": None, + } return { "handled": True, "status": "pending_manual", @@ -124,6 +185,131 @@ class DraftConfirmHandler: "workflow_route": RouteResolver.normalize_route_key(str(target_work_item.intent or "")), } + async def _resolve_target_mounted_draft( + self, + *, + sender: str, + source_system: str, + channel_ref: Optional[str], + source_sender_open_id: Optional[str], + thread_context: Optional[dict[str, Any]], + target_work_item_id: int, + ) -> Optional[tuple[MountedDraftWorkspace, MountedTopicWorkspace]]: + if not hasattr(self.db, "execute"): + return None + active_mounted = await self._resolve_active_mounted_draft( + sender=sender, + source_system=source_system, + channel_ref=channel_ref, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + ) + if active_mounted is not None: + return active_mounted + return await self._resolve_mounted_draft_by_work_item_id(work_item_id=int(target_work_item_id)) + + async def _resolve_semantic_followup_decision( + self, + *, + message_text: str, + source_system: str, + thread_context: Optional[dict[str, Any]], + active_mounted: Optional[tuple[MountedDraftWorkspace, MountedTopicWorkspace]], + ) -> Optional[dict[str, str]]: + if active_mounted is None: + return None + _, mounted_topic = active_mounted + try: + packet = await SemanticContextAssembler(self.db).assemble( + tenant_id=int(self.tenant_id), + operator_user_id=0, + topic_workspace_id=int(mounted_topic.id), + latest_user_message=message_text, + ingress_context={ + "candidate_kind": "draft_followup", + "source_system": source_system, + "reply_anchor_present": bool(self._normalize_thread_context(thread_context)), + "allowed_followup_actions": list(self._SEMANTIC_FOLLOWUP_ACTIONS), + "thread_context": self._normalize_thread_context(thread_context), + }, + ) + decision = await SemanticDecisionService(llm_service).decide(packet=packet) + followup_action = str(getattr(decision, "followup_action", "") or "").strip().lower() + if followup_action not in self._SEMANTIC_FOLLOWUP_ACTIONS: + return None + gate_result = SemanticDecisionGatekeeper.evaluate( + mode_card=packet.mode_card, + decision=decision, + ) + if not bool(gate_result.accepted): + return None + allowed_actions = { + str(item).strip() + for item in (getattr(packet.mode_card, "allowed_actions", None) or []) + if str(item or "").strip() + } + action_code = ( + "draft.followup.answer_only" + if followup_action == self._ACTION_ANSWER_ONLY + else f"draft.followup.{followup_action}" + ) + if action_code not in allowed_actions: + return None + revision_note = str(getattr(decision, "revision_note", "") or "").strip() + assistant_reply = str(getattr(decision, "assistant_reply", "") or "").strip() + except Exception as exc: + logger.warning( + "draft_confirm_semantic_followup_failed", + tenant_id=self.tenant_id, + topic_workspace_id=_safe_int(getattr(mounted_topic, "id", None)), + error=str(exc), + ) + return None + + if followup_action == self._ACTION_REVISE and not revision_note: + return { + "action": self._ACTION_REVISE, + "revision_note": "", + "assistant_reply": assistant_reply, + } + if followup_action == self._ACTION_ANSWER_ONLY and not assistant_reply: + assistant_reply = self._DEFAULT_DISPLAY_FEEDBACK_REPLY + return { + "action": followup_action, + "revision_note": revision_note, + "assistant_reply": assistant_reply, + } + + async def _resolve_mounted_draft_by_work_item_id( + self, + *, + work_item_id: int, + ) -> Optional[tuple[MountedDraftWorkspace, MountedTopicWorkspace]]: + if not hasattr(self.db, "execute"): + return None + row = ( + await self.db.execute( + select(MountedDraftWorkspace, MountedTopicWorkspace) + .join( + MountedTopicWorkspace, + MountedTopicWorkspace.id == MountedDraftWorkspace.topic_workspace_id, + ) + .where( + and_( + MountedDraftWorkspace.tenant_id == int(self.tenant_id), + MountedTopicWorkspace.tenant_id == int(self.tenant_id), + MountedDraftWorkspace.active_work_item_id == int(work_item_id), + MountedTopicWorkspace.status == "active", + ) + ) + .order_by(MountedDraftWorkspace.id.desc()) + .limit(1) + ) + ).first() + if row is None: + return None + return row + async def handle_api_decision( self, *, @@ -523,13 +709,20 @@ class DraftConfirmHandler: original_message = str((locked.draft_content or {}).get("original_text") or "").strip() intent_name = RouteResolver.canonical_intent_for_route(RouteResolver.normalize_route_key(str(locked.intent or ""))) - generated = await self.draft_generator.generate_and_persist( - work_item=locked, - intent_name=intent_name, - message_text=original_message, - world_model=locked.context_snapshot if isinstance(locked.context_snapshot, dict) else {}, - revision_note=revision_note, - ) + if self._is_requirement_plan_draft_payload(locked.draft_content): + generated = await self._revise_requirement_plan_draft( + locked=locked, + target_ingest=target_ingest, + revision_note=revision_note, + ) + else: + generated = await self.draft_generator.generate_and_persist( + work_item=locked, + intent_name=intent_name, + message_text=original_message, + world_model=locked.context_snapshot if isinstance(getattr(locked, "context_snapshot", None), dict) else {}, + revision_note=revision_note, + ) locked.state = STATE_DRAFT_PENDING_CONFIRM locked.reply_template_key = "draft_pending_confirm" await self.db.flush() @@ -553,6 +746,73 @@ class DraftConfirmHandler: "draft_content": locked.draft_content if isinstance(locked.draft_content, dict) else None, } + @staticmethod + def _is_requirement_plan_draft_payload(draft: Any) -> bool: + return isinstance(draft, dict) and str(draft.get("draft_source") or "").strip() == "requirement_plan" + + async def _revise_requirement_plan_draft( + self, + *, + locked: WorkItem, + target_ingest: IngestEvent, + revision_note: str, + ) -> dict[str, Any]: + draft_payload = locked.draft_content if isinstance(locked.draft_content, dict) else {} + base_card = draft_payload.get("requirement_card") if isinstance(draft_payload.get("requirement_card"), dict) else {} + if not base_card: + raise RuntimeError("requirement_plan_draft_missing_card") + + runtime = _RequirementPlanDraftRuntime(self.db, tenant_id=int(self.tenant_id)) + merged_card = dict(base_card) + merged_card.update(runtime._extract_requirement_plan_updates(message_text=revision_note, card=merged_card)) + if not str(merged_card.get("title") or "").strip(): + merged_card["title"] = runtime._derive_requirement_plan_title(message_text=revision_note, card=merged_card) + if not str(merged_card.get("goal") or "").strip(): + merged_card["goal"] = runtime._derive_requirement_plan_goal(message_text=revision_note, card=merged_card) + + rendered_text, _, project_creation_draft = await runtime._generate_requirement_plan_confirmation_draft( + card=merged_card, + revision_note=revision_note, + ) + next_version = int(locked.draft_version or 0) + 1 + locked.draft_content = dict(project_creation_draft) + locked.draft_version = next_version + locked.draft_expires_at = _utc_now_naive() + timedelta(hours=24) + locked.execution_result = { + **(locked.execution_result if isinstance(locked.execution_result, dict) else {}), + "status": "draft_pending_confirm", + "draft": dict(project_creation_draft), + "draft_version": next_version, + "draft_expires_at": locked.draft_expires_at.isoformat() if locked.draft_expires_at else None, + "revision_note": str(revision_note or "")[:500] or None, + } + + mounted_draft = ( + await self.db.execute( + select(MountedDraftWorkspace) + .where( + and_( + MountedDraftWorkspace.tenant_id == int(self.tenant_id), + MountedDraftWorkspace.active_work_item_id == int(locked.id), + ) + ) + .order_by(MountedDraftWorkspace.id.desc()) + .limit(1) + ) + ).scalar_one_or_none() + if mounted_draft is not None: + mounted_draft.state = "ready" + mounted_draft.draft_version = next_version + mounted_draft.requirement_card = dict(merged_card) + mounted_draft.draft_payload = dict(project_creation_draft) + mounted_draft.last_ready_at = _utc_now_naive() + + return { + "draft": dict(project_creation_draft), + "draft_text": rendered_text, + "draft_version": next_version, + } + async def cancel_draft( self, *, @@ -963,13 +1223,6 @@ class DraftConfirmHandler: return None return _safe_int(matched.group(1)) - @classmethod - def _extract_revision_note(cls, text: str) -> str: - action, note = WorkflowSemanticPlanner.parse_draft_action(message_text=text) - if action != cls._ACTION_REVISE: - return "" - return str(note or "")[:500] - @staticmethod def _safe_float(value: Any) -> Optional[float]: try: @@ -1134,6 +1387,21 @@ class DraftConfirmHandler: if body: requirement_lines.append(f"{prefix}{body}") _append_list_section("需求清单", requirement_lines) + external_signal_sources = ( + draft.get("external_signal_sources") if isinstance(draft.get("external_signal_sources"), list) else [] + ) + external_signal_lines: list[str] = [] + for item in external_signal_sources[:5]: + if not isinstance(item, dict): + continue + name = str(item.get("name") or "").strip() + purpose = str(item.get("purpose") or "").strip() + phase = str(item.get("phase") or "").strip() + if name and purpose and phase: + external_signal_lines.append(f"{name}:{purpose}({phase})") + elif name and purpose: + external_signal_lines.append(f"{name}:{purpose}") + _append_list_section("外部信号源", external_signal_lines) _append_list_section( "待确认信息", @@ -1147,7 +1415,7 @@ class DraftConfirmHandler: return "\n".join(lines).strip() @staticmethod - def _looks_like_project_creation_reextract_failure(error: Exception) -> bool: + def _is_project_creation_reextract_failure(error: Exception) -> bool: raw = str(error or "").strip() normalized = re.sub(r"\s+", " ", raw).lower() return any( @@ -1264,7 +1532,7 @@ class DraftConfirmHandler: return False @classmethod - def _looks_like_rich_project_creation_draft(cls, draft: dict[str, Any]) -> bool: + def _is_rich_project_creation_draft(cls, draft: dict[str, Any]) -> bool: if not isinstance(draft, dict): return False title = str(draft.get("title") or draft.get("project_title") or "").strip() @@ -1404,7 +1672,7 @@ class DraftConfirmHandler: assignee_id = _safe_int(draft.get("recommended_assignee_id")) if assignee_id is not None and assignee_id > 0: requested_member_user_ids = [int(assignee_id)] - if self._looks_like_rich_project_creation_draft(draft): + if self._is_rich_project_creation_draft(draft): logger.info( "draft_confirm_project_creation_use_structured_draft_directly", tenant_id=int(self.tenant_id), @@ -1426,7 +1694,7 @@ class DraftConfirmHandler: requested_member_user_ids=requested_member_user_ids or None, ) except Exception as exc: - if not self._looks_like_project_creation_reextract_failure(exc): + if not self._is_project_creation_reextract_failure(exc): raise logger.warning( "draft_confirm_project_creation_reextract_failed_fallback", @@ -1565,6 +1833,12 @@ class DraftConfirmHandler: message_text = summary return signal + +class _RequirementPlanDraftRuntime(mixin_execute_query_project.ExecuteQueryProjectMixin): + def __init__(self, db: Any, *, tenant_id: int) -> None: + self.db = db + self.tenant_id = int(tenant_id) + @staticmethod def _build_execution_route(*, work_item: WorkItem) -> WorkflowRoute: normalized_key = RouteResolver.normalize_route_key(str(work_item.intent or "")) diff --git a/backend/app/services/ai_pmo/semantic_context/assembler.py b/backend/app/services/ai_pmo/semantic_context/assembler.py index cd3c251a..089d2c17 100644 --- a/backend/app/services/ai_pmo/semantic_context/assembler.py +++ b/backend/app/services/ai_pmo/semantic_context/assembler.py @@ -9,6 +9,7 @@ from ....models import MountedConversationRun, MountedDraftWorkspace, MountedTop from .mode_card_service import build_mode_card from .packet_schema import ( SemanticContextDraftSnapshot, + SemanticContextIngress, SemanticContextPacket, SemanticContextThread, SemanticContextTopic, @@ -109,6 +110,7 @@ class SemanticContextAssembler: operator_user_id: int, topic_workspace_id: int, latest_user_message: str, + ingress_context: Optional[dict[str, Any]] = None, ) -> SemanticContextPacket: topic_row = await self._load_topic( tenant_id=tenant_id, @@ -162,6 +164,44 @@ class SemanticContextAssembler: recent_turns=recent_turns, open_questions=self._normalize_list(topic_row.open_questions), ) + mode_card = build_mode_card( + topic_mode=topic_row.mode, + plan_status=topic_row.plan_status, + ) + ingress = None + if isinstance(ingress_context, dict): + ingress = SemanticContextIngress( + candidate_kind=str(ingress_context.get("candidate_kind") or "").strip() or None, + source_system=str(ingress_context.get("source_system") or "").strip() or None, + reply_anchor_present=bool(ingress_context.get("reply_anchor_present")), + active_context_kind=str(ingress_context.get("active_context_kind") or "").strip() or None, + allowed_followup_actions=self._normalize_list(ingress_context.get("allowed_followup_actions")), + allowed_context_scopes=self._normalize_list(ingress_context.get("allowed_context_scopes")), + thread_context=self._normalize_mapping(ingress_context.get("thread_context")), + ) + if ingress.candidate_kind == "draft_followup": + merged_allowed = list(mode_card.allowed_actions) + for action in ( + "draft.followup.confirm", + "draft.followup.revise", + "draft.followup.cancel", + "draft.followup.answer_only", + ): + if action not in merged_allowed: + merged_allowed.append(action) + mode_card.allowed_actions = merged_allowed + if ingress.candidate_kind == "active_context_followup": + merged_allowed = list(mode_card.allowed_actions) + for action in ( + "context.followup.current_requirement", + "context.followup.current_draft", + "context.followup.independent_workflow", + "context.followup.clarify", + ): + if action not in merged_allowed: + merged_allowed.append(action) + mode_card.allowed_actions = merged_allowed + return SemanticContextPacket( packet_version="v1", tenant_id=int(tenant_id), @@ -171,8 +211,6 @@ class SemanticContextAssembler: topic=topic, draft=draft, thread=thread, - mode_card=build_mode_card( - topic_mode=topic_row.mode, - plan_status=topic_row.plan_status, - ), + mode_card=mode_card, + ingress=ingress, ) diff --git a/backend/app/services/ai_pmo/semantic_context/decision_schema.py b/backend/app/services/ai_pmo/semantic_context/decision_schema.py index 8b4124e5..f377d7b4 100644 --- a/backend/app/services/ai_pmo/semantic_context/decision_schema.py +++ b/backend/app/services/ai_pmo/semantic_context/decision_schema.py @@ -15,12 +15,38 @@ class ModeCard(BaseModel): exit_conditions: list[str] = Field(default_factory=list) +class DecisionWorkflowPlan(BaseModel): + model_config = ConfigDict(extra="ignore") + + workflow_type: Literal[ + "query_flow", + "new_project_flow", + "project_flow", + "task_flow", + "signal_source_flow", + "project_signal_flow", + "followup_flow", + ] + workflow_action: str + target_scope: Literal["global", "project", "thread"] = "global" + operation_codes: list[str] = Field(default_factory=list) + requires_confirmation: bool = False + confidence: float = 0.0 + clarification_needed: bool = False + metadata: dict[str, Any] = Field(default_factory=dict) + + class SemanticDecision(BaseModel): model_config = ConfigDict(extra="ignore") topic_relation: Literal["continue_current_topic", "new_topic", "uncertain"] mode_decision: Literal["stay_plan", "ready_confirm", "request_execute", "answer_only"] + followup_scope: Literal["none", "current_requirement", "current_draft", "independent_workflow", "clarify"] = "none" + followup_action: Literal["none", "confirm", "revise", "cancel", "answer_only"] = "none" + revision_note: str = "" draft_patch: dict[str, Any] = Field(default_factory=dict) + workflow_plans: list[DecisionWorkflowPlan] = Field(default_factory=list) + scene_metadata: dict[str, Any] = Field(default_factory=dict) assistant_reply: str = "" requires_confirmation: bool = False requested_actions: list[str] = Field(default_factory=list) diff --git a/backend/app/services/ai_pmo/semantic_context/packet_schema.py b/backend/app/services/ai_pmo/semantic_context/packet_schema.py index e5b04691..093475e2 100644 --- a/backend/app/services/ai_pmo/semantic_context/packet_schema.py +++ b/backend/app/services/ai_pmo/semantic_context/packet_schema.py @@ -53,6 +53,18 @@ class SemanticContextDraftSnapshot(BaseModel): required_confirmations: list[str] = Field(default_factory=list) +class SemanticContextIngress(BaseModel): + model_config = ConfigDict(extra="ignore") + + candidate_kind: Optional[str] = None + source_system: Optional[str] = None + reply_anchor_present: bool = False + active_context_kind: Optional[str] = None + allowed_followup_actions: list[str] = Field(default_factory=list) + allowed_context_scopes: list[str] = Field(default_factory=list) + thread_context: Optional[Any] = None + + class SemanticContextPacket(BaseModel): model_config = ConfigDict(extra="ignore") @@ -65,3 +77,4 @@ class SemanticContextPacket(BaseModel): draft: SemanticContextDraftSnapshot | None = None thread: SemanticContextThread mode_card: ModeCard + ingress: SemanticContextIngress | None = None diff --git a/backend/app/services/ai_pmo/semantic_context/semantic_decision_service.py b/backend/app/services/ai_pmo/semantic_context/semantic_decision_service.py index 0b575939..f8c5db31 100644 --- a/backend/app/services/ai_pmo/semantic_context/semantic_decision_service.py +++ b/backend/app/services/ai_pmo/semantic_context/semantic_decision_service.py @@ -17,8 +17,9 @@ class SemanticDecisionService: @staticmethod def build_prompt(packet: SemanticContextPacket) -> str: - return PMFLOW_SEMANTIC_CONTEXT_DECISION_PROMPT.format( - semantic_packet=json.dumps(packet.model_dump(mode="json"), ensure_ascii=False, indent=2) + return PMFLOW_SEMANTIC_CONTEXT_DECISION_PROMPT.replace( + "{semantic_packet}", + json.dumps(packet.model_dump(mode="json"), ensure_ascii=False, indent=2), ) async def decide(self, *, packet: SemanticContextPacket) -> SemanticDecision: diff --git a/backend/app/services/ai_pmo/workflow_semantic_planner/constants.py b/backend/app/services/ai_pmo/workflow_semantic_planner/constants.py index c2f14b1c..cae063de 100644 --- a/backend/app/services/ai_pmo/workflow_semantic_planner/constants.py +++ b/backend/app/services/ai_pmo/workflow_semantic_planner/constants.py @@ -1,622 +1,13 @@ from __future__ import annotations -import re - SEMANTIC_VERSION = "2026-03-27.workflow-v1" -INTENT_QUERY = "query.read_only" -INTENT_PROJECT_CREATE = "project.create" -INTENT_PROJECT_UPDATE = "project.update" -INTENT_PROJECT_ARCHIVE = "project.archive" -INTENT_PROJECT_REOPEN = "project.reopen" -INTENT_PROJECT_DELETE = "project.delete" -INTENT_TASK_CREATE = "task.create" -INTENT_TASK_UPDATE = "task.update" -INTENT_TASK_ASSIGN = "task.assign" -INTENT_TASK_PRIORITIZE = "task.prioritize" -INTENT_TASK_STATUS_TRANSITION = "task.status_transition" -INTENT_TASK_DELETE = "task.delete" -INTENT_SIGNAL_CREATE = "signal.create" -INTENT_SIGNAL_SOURCE_ONBOARD = "signal_source.onboard" -INTENT_FOLLOWUP_CONFIRM = "followup.confirm" -INTENT_FOLLOWUP_CONTINUE = "followup.continue" -INTENT_IGNORE = "ignore" - -ROUTE_QUERY_ANSWER = "query.answer" -ROUTE_PROJECT_CREATE = "project_create.create" -ROUTE_TASK_CREATE = "task.create" -ROUTE_SIGNAL_SOURCE_ONBOARD = "signal_source.onboard" -ROUTE_PROJECT_SIGNAL_CREATE = "project_signal.create" -ROUTE_PROJECT_SIGNAL_DISCOVER = "project_signal.discover" -ROUTE_PROJECT_SIGNAL_COMMIT = "project_signal.commit" -ROUTE_PROJECT_SIGNAL_CANCEL = "project_signal.cancel" -ROUTE_PROJECT_SIGNAL_REFRESH = "project_signal.refresh" -ROUTE_FOLLOWUP_CONFIRM = "followup.confirm" -ROUTE_FOLLOWUP_CONTINUE = "followup.continue" - -WORKFLOW_QUERY = "query_flow" -WORKFLOW_NEW_PROJECT = "new_project_flow" -WORKFLOW_PROJECT = "project_flow" -WORKFLOW_TASK = "task_flow" -WORKFLOW_PROJECT_SIGNAL = "project_signal_flow" -WORKFLOW_SIGNAL_SOURCE = "signal_source_flow" -WORKFLOW_FOLLOWUP = "followup_flow" - QUERY_SCENE_BUSINESS_REVIEW = "business_review" QUERY_SCENE_CORRECTION_ESCALATION = "correction_escalation" QUERY_SCENE_REQUIREMENT_PLAN = "requirement_plan" MESSAGE_GATE_AUTO = "auto_execute" MESSAGE_GATE_PENDING = "pending_confirmation" -MESSAGE_GATE_FAILED_SAFE = "failed_safe" REPLY_MODE_CARD = "aggregate_card" REPLY_MODE_TEXT = "aggregate_text" - -BACKGROUND_MARKERS = ("因为", "所以", "用来", "为了", "方便", "希望", "背景", "场景") -MULTI_SEGMENT_MARKERS = (",", ",", "、", ";", ";", "并且", "同时", "然后") - -INTENT_GROUP_KEYWORDS: dict[str, tuple[str, ...]] = { - "project": ("项目", "立项", "创建项目", "新增项目", "新建项目"), - "signal": ("信号", "信号源", "接入", "onboard", "source"), - "task": ("任务", "分配", "指派", "执行", "部署"), - "change": ("修改", "更新", "改为", "改成", "撤销", "回滚", "归档"), -} - -CORRECTION_ACTION_PATTERNS: tuple[tuple[str, tuple[str, ...]], ...] = ( - ("undo_last", ("撤销", "撤回", "undo", "作废上一步", "取消上一步")), - ("rollback", ("回滚", "恢复到", "还原", "回退")), - ("replace_target", ("改为", "改成", "替换为", "不是", "不对", "纠正")), - ("reroute", ("重新识别", "重新判断", "重新路由", "再判断一次")), -) - -TRANSPORT_MODE_CORRECTION_HINTS = ( - "不是", - "不再", - "改为", - "改成", - "替换为", - "取代", - "现在都", - "现在是", -) - -CONTEXTUAL_CONFIRM_HINTS = ( - "来吧", - "那就", - "就这么办", - "就按这个", - "可以", - "好的", - "不错", - "开始吧", - "行", - "没问题", - "同意", - "确认", -) -CONTEXTUAL_DEICTIC_HINTS = ( - "这个", - "这个项目", - "这条", - "它", - "按这个", - "照这个", -) -CONTEXTUAL_WRITE_ACTION_HINTS = ( - "创建", - "新建", - "新增", - "增加", - "添加", - "执行", - "开始", - "继续处理", - "继续执行", - "推进", - "落地", -) - -BUSINESS_REVIEW_ANALYSIS_HINTS = ( - "分析", - "分析下", - "怎么做", - "如何做", - "方案", - "规划", - "梳理", - "评审", - "mvp", - "首期交付", - "需求", - "要做一个", - "想做一个", - "给我需求对应的mvp", - "我需要对应的需求mvp", - "需求mvp", - "对应的mvp", -) -BUSINESS_REVIEW_TARGET_HINTS = ( - "平台", - "系统", - "产品", - "业务", - "api", - "llm", - "模型", - "聚合", - "需求", - "mvp", - "方案", -) -ASSIGNEE_RECOMMENDATION_HINTS = ( - "分配给谁", - "谁做比较合适", - "谁更合适", - "推荐负责人", - "负责人怎么定", - "谁来负责", - "给谁做合适", -) -REQUIREMENT_PLAN_CHAT_HINTS = ( - "聊聊需求", - "聊需求", - "先聊需求", - "想聊需求", - "想跟你聊聊需求", - "一起聊需求", - "讨论需求", - "先不创建", - "先别创建", - "先不要创建", - "先不要直接创建", - "别直接创建", - "先不入库", - "先别入库", - "先不要入库", -) -REQUIREMENT_TOPIC_SWITCH_NEW_HINTS = ( - "聊个新需求", - "聊一个新需求", - "聊聊新需求", - "有个新需求", - "这是个新需求", - "这是新需求", - "这次是新需求", - "现在是新需求", - "换个新需求", - "换一个新需求", - "切到新需求", - "切个新需求", - "另一个需求", - "换个需求", - "换一个需求", - "新的需求", - "不是这个需求", - "不是刚才那个需求", - "别沿用刚才那版", - "不要沿用刚才那版", - "另起一个需求", - "重新开一个需求", - "单开一个需求", - "跟上一个无关", - "和上一个无关", - "不是延续刚才那个", - "不是接着刚才那个", -) -REQUIREMENT_TOPIC_SWITCH_NEW_PATTERNS = ( - re.compile(r"(?:聊|说|讨论|讲).{0,6}(?:个|一个)?新需求", re.IGNORECASE), - re.compile(r"(?:这是|这次是|现在是|我有|这边有|还有|另外有).{0,8}(?:个|一个)?新需求", re.IGNORECASE), - re.compile(r"(?:切|换|转).{0,4}(?:到|个|一个)?(?:新需求|新的需求|新话题|newtopic|topic)", re.IGNORECASE), - re.compile(r"(?:另起|单开|重新开|新开|开个新|拉个新|起个新).{0,6}(?:需求|话题|topic)", re.IGNORECASE), - re.compile(r"(?:跟上一个无关|和上一个无关|不是延续刚才那个|不是接着刚才那个)", re.IGNORECASE), -) -CORRECTION_ESCALATION_HINTS = ( - "重新回答", - "重答", - "调用高级模型", - "高级模型", - "更强模型", - "用更强模型", - "高配模型", -) - -NON_REQUIREMENT_EXACT = { - "收到", - "收到啦", - "好的", - "ok", - "okk", - "okay", - "yes", - "no", - "1", - "2", - "3", - "thanks", - "thx", - "赞", - "辛苦", - "你是谁", - "你是誰", - "你是谁?", - "你是誰?", - "说话", - "在吗", - "在不在", - "今天什么日期", - "今天几号", - "今天星期几", - "现在几点", - "当前日期", -} -NON_REQUIREMENT_CONTAINS = ( - "你是谁", - "什么身份", - "身份确认", - "今天什么日期", - "当前日期", - "日期疑问", - "询问日期", - "现在几点", - "几点了", - "今天周几", - "今天星期几", - "说话", - "在吗", - "在不在", -) -REQUIREMENT_HINTS = ( - "需求", - "优化", - "改一下", - "调整", - "修复", - "bug", - "故障", - "报错", - "项目", - "页面", - "接口", - "功能", - "排版", - "样式", - "任务", - "立项", - "上线", - "配置", -) -LOW_VALUE_CLOSURE_HINTS = ("缺少项目归属", "必须先确认项目", "无法创建", "不能创建") - -GENERIC_SIGNAL_SOURCE_MESSAGE_RE = re.compile( - r"^(?:请)?(?:按|按照)?(?:这个|上述|这条)?(?:分步|步骤)?(?:骤)?(?:把|将)?(?:它|该需求|这条需求)?(?:加入|接入|录入).{0,24}信号源(?:中)?$", - re.IGNORECASE, -) - -SOURCE_ONBOARDING_ACTION_RE = re.compile( - r"(新增|增加|添加|接入|配置|注册|建立|加个|加一个|加).{0,8}(信号源|来源|监控源|channel|source|webhook|飞书|github|url|rss)", - re.IGNORECASE, -) - -MODULE_KEYWORD_RULES: tuple[tuple[str, tuple[str, ...]], ...] = ( - ("用户管理与权限", ("用户管理", "角色权限", "crud", "批量导入", "成员管理")), - ("技能图谱", ("技能图谱", "技能标签", "能力图谱", "雷达图")), - ("工作量管理", ("工作量", "负载", "产能", "workload")), - ("SSTC 工作流", ("sstc", "charter", "signal", "task", "execution", "release", "review", "工作流")), - ("AI 能力编排", ("ai能力", "智能", "自动化", "推荐", "编排", "agent", "llm")), - ("信号源接入", ("信号源", "source", "onboard", "接入")), - ("飞书集成", ("飞书", "feishu", "群聊", "机器人")), - ("消息台与详情页", ("消息台", "详情页", "展开详情", "摘要", "时间戳", "按钮")), -) - -POLICY_GATE_RULES: tuple[tuple[str, tuple[str, ...]], ...] = ( - ("delete_or_archive", ("删除项目", "删除信号", "归档项目", "归档", "archive", "清空项目")), - ("budget_overrun", ("超预算", "突破预算", "额外预算", "加预算")), - ("cross_project_staffing", ("跨项目抢人", "抽调", "抢占关键人力", "挪人")), - ("charter_rewrite", ("改写charter", "success criteria", "成功标准改", "范围重大变更")), - ("critical_path_change", ("关键路径", "里程碑改动", "影响多个项目", "跨项目依赖调整")), -) - -PROJECT_SIGNAL_DISCOVER_HINTS = ( - "候选", - "先分析", - "先看候选", - "给我确认", - "待确认", - "再让我确认", - "确认后入库", - "先不要入库", - "先别入库", -) -FOLLOWUP_REFRESH_HINTS = ("换一批", "再来一批", "刷新候选", "重新找", "再找一组", "换一组") -FOLLOWUP_CANCEL_HINTS = ("取消", "先不做", "先别做", "不要了", "算了", "reject", "驳回") -FOLLOWUP_COMMIT_HINTS = ("确认", "继续处理", "继续执行", "同意", "批准", "approve") -FOLLOWUP_CONFIRM_HINTS = ("确认归属", "确认项目", "确认到项目", "确认关联", "确认这条") -FOLLOWUP_CONTINUE_HINTS = ("继续处理", "继续执行", "接着处理", "往下做", "继续推进") -DRAFT_CONFIRM_EXACT_HINTS = ("确认", "好的", "就这样", "ok", "okay", "同意", "没问题", "y", "yes") -DRAFT_REVISION_PREFIX_HINTS = ("修改", "改成", "改为", "换成", "改") -DRAFT_NATURAL_REVISION_HINTS = ( - "第一版", - "首期", - "先做", - "先别做", - "只做", - "主要给", - "给运营", - "给团队", - "目标客户", - "用户", - "痛点", - "麻烦", - "能力", - "功能", - "数据", - "来源", - "信号源", - "团队", - "角色", - "负责人", - "技能", - "指标", - "目标", - "先接", - "补充", - "加上", - "加个", - "再加", - "再补", -) -FOLLOWUP_WORK_ITEM_ID_RE = re.compile(r"(?:work[_ -]?item|工作项|队列项|草稿)\s*(?:id|ID)?\s*[:#:]?\s*(\d+)", re.IGNORECASE) -FOLLOWUP_CONTINUE_RE = re.compile(r"(继续处理|继续推进|继续执行|接着处理|继续跑)", re.IGNORECASE) -FOLLOWUP_CREATE_CONTINUE_RE = re.compile(r"(继续创建|接着创建|继续建|接着建)", re.IGNORECASE) -FOLLOWUP_STATUS_RE = re.compile( - r"(进度|状态|处理到哪|处理了吗|现在怎么样|怎么样了|结果呢|是否已经创建|已经创建了吗|创建了吗|创建成功了吗|建好了吗|建成了吗)", - re.IGNORECASE, -) -FOLLOWUP_REFERENCE_RE = re.compile( - r"(刚才|刚刚|前面|之前|上次|上一条|我刚才要求|我刚才说|我说的|那个)", - re.IGNORECASE, -) - -QUESTION_HINTS = ( - "?", - "?", - "怎么", - "如何", - "为何", - "为什么", - "什么", - "是什么", - "哪些", - "有哪些", - "特点", - "技术特点", - "能力", - "介绍", - "说明", - "多少", - "是否", - "帮我看", - "状态", - "风险", - "能做什么", - "可以做什么", -) -OBVIOUS_QUESTION_RE = re.compile( - r"([??]$|吗$|呢$|呀$|吧$|怎么|如何|为什么|多少|几号|几点|周几|有没有|是否|是不是|是什么|什么|哪些|有哪些|在哪|哪一个|哪几个|特点|能力|介绍|说明|风险)", - re.IGNORECASE, -) -QUERY_INTENT_RE = re.compile( - r"(哪些|有哪些|特点|技术特点|能力|介绍|说明|怎么|如何|是什么|什么|能做什么|可以做什么|项目有哪些风险)", - re.IGNORECASE, -) -SMALLTALK_EXACT = { - "收到", - "收到啦", - "好的", - "ok", - "okk", - "yes", - "no", - "谢谢", - "thanks", - "thx", - "在吗", - "在不在", - "怎么样了", - "咋样了", - "辛苦了", -} -SMALLTALK_SHORT_HINTS = ("怎么样了", "咋样了", "在吗", "辛苦了", "收到", "谢谢", "ok") -SMALLTALK_STRUCTURED_HINTS = ("项目", "信号", "任务", "风险", "进度", "状态", "work_item", "工作项", "#") -FRONT_NOISE_HINTS = SMALLTALK_SHORT_HINTS -FRONT_NOISE_STRUCTURED_HINTS = SMALLTALK_STRUCTURED_HINTS - -PROJECT_INTENT_RE = re.compile(r"(立项|新项目|我们要做|需要开发|启动|规划)", re.IGNORECASE) -SIGNAL_FACT_RE = re.compile(r"(竞品|上线|发布|反馈|风险|bug|故障|告警|投诉)", re.IGNORECASE) -TIMELINE_RE = re.compile(r"(\d+\s*(天|周|月)|本周|本月|月底|下周|下月|Q[1-4]|\d{1,2}月\d{1,2}日)") -OWNER_RE = re.compile(r"(由|负责人|owner|owner:|@)(\S+)") -INDEPENDENT_REQUIREMENT_RE = re.compile( - r"(独立需求|单独需求|单开|另起|新增需求|新建项目|新增项目|单独立项|开个新|做个新)", - re.IGNORECASE, -) -SIMPLE_SIGNAL_REQUEST_RE = re.compile( - r"(新增|增加|加一个|加个|补充|支持|实现|接入|优化|修复|改进).{0,18}(功能|能力|接口|页面|按钮|流程|识别|提醒|校验|报表|字段|规则|模块)", - re.IGNORECASE, -) -PROJECT_COMPLEXITY_RE = re.compile( - r"(立项|新项目|项目章程|里程碑|预算|排期|roadmap|跨团队|跨部门|季度目标|年度目标|okr)", - re.IGNORECASE, -) -QUESTION_REQUIREMENT_VERB_RE = re.compile( - r"(新增|增加|加一个|加个|支持|接入|实现|优化|修复|改进|调整|补充|完善|同步|对齐|落库|入库)", - re.IGNORECASE, -) -QUESTION_REQUIREMENT_OBJECT_RE = re.compile( - r"(功能|能力|接口|页面|按钮|流程|校验|报表|字段|规则|模块|任务|项目|需求|通知|提醒|配置|归属|队列|信号)", - re.IGNORECASE, -) - -SIGNAL_CREATE_HINTS = ("新增一个信号", "新增一条信号", "增加一个信号", "录入一个信号", "关注", "跟踪") -TASK_CREATE_HINTS = ( - "创建任务", - "新建任务", - "新增任务", - "增加任务", - "添加任务", - "加个任务", - "加一个任务", - "安排任务", - "分配任务", - "派个任务", -) -PROJECT_CREATE_HINTS = ( - "创建项目", - "新建项目", - "建立项目", - "做个项目", - "立项", - "开个项目", - "新增项目", - "新增个项目", - "新增一个项目", - "增加项目", - "增加个项目", - "增加一个项目", - "添加项目", - "添加一个项目", - "加个项目", - "加一个项目", -) -NATURAL_PROJECT_CREATE_HINTS = ( - "想做个", - "想做一个", - "想弄个", - "想弄一个", - "想搞个", - "想搞一个", - "做个工具", - "做一个工具", - "做个小工具", - "做一个小工具", - "做个助手", - "做一个助手", - "做个系统", - "做一个系统", - "做个平台", - "做一个平台", - "做个产品", - "做一个产品", - "搞个工具", - "搞一个工具", - "弄个工具", - "弄一个工具", - "给运营用的小工具", - "给团队用的小工具", - "给销售用的小工具", - "希望做个", - "打算做个", - "准备做个", -) -PROJECT_CREATE_QUERY_GUARD_HINTS = ( - "先帮我分析", - "先帮我梳理", - "帮我分析", - "分析一下", - "分析下", - "先分析", - "怎么做", - "如何做", - "先聊方案", - "给我mvp", - "先给我mvp", - "先梳理一期", -) -PROJECT_CREATE_DEFERRED_CONFIRM_HINTS = ( - "先别直接创建", - "先别创建项目", - "先别建项目", - "不要直接创建", - "先不要直接创建", - "先不要创建", - "先别入库", - "最后确认再入库", - "补充几轮再确认", - "等我确认再入库", - "我再补充几轮", - "我还在补范围", - "还在补范围", - "范围还没定", -) -SHORT_CONFIRM_HINTS = ( - "是", - "好", - "对", - "确认", - "新增", - "加", - "嗯", - "yes", - "ok", - "好的", - "是的", - "行", - "可以", - "没问题", -) -HEALTHCHECK_RE = re.compile( - r"^(?:ping|pong|health(?:check)?|在线吗|活着吗|存活吗|还在吗|服务正常吗)\??$", - re.IGNORECASE, -) -SHORT_CONFIRM_RE = re.compile( - r"^(?:收到|好的|好|ok|okay|明白|了解|知道了|收到啦|嗯嗯|行|辛苦了|thanks?|thx)[!!.。]?$", - re.IGNORECASE, -) - -PROJECT_CREATE_REGEX = re.compile( - r"(?:新增|增加|添加|新建|创建|建立|开个|开一个|建个|建一个|做个|做一个|加个|加一个|立项)(?:一个|个)?(?:[^,。!?,.!?]{0,24})项目", - re.IGNORECASE, -) -NATURAL_PROJECT_CREATE_REGEX = re.compile( - r"(?:想|想要|希望|打算|准备).{0,8}(?:做|弄|搞|整).{0,8}(?:个|一个)?.{0,20}(?:工具|小工具|助手|系统|平台|产品|应用|服务)", - re.IGNORECASE, -) -TASK_CREATE_REGEX = re.compile( - r"(?:给|帮|请).{0,24}(?:新增|增加|添加|创建|新建|加).{0,16}(?:任务|工单|待办)", - re.IGNORECASE, -) -SIGNAL_CREATE_REGEX = re.compile( - r"(?:给|帮|请).{0,20}(?:新增|增加|添加|补充|加).{0,20}(?:信号|需求|事项)", - re.IGNORECASE, -) -PROJECT_LIST_QUERY_RE = re.compile( - r"(现在|当前|目前|系统里|系统中|项目里|项目中)?.{0,4}(有哪些项目|项目列表|项目清单|哪(些|几个)项目)", - re.IGNORECASE, -) -PROJECT_ID_QUERY_RE = re.compile( - r"项目\s*id\s*[::]?\s*(\d+).{0,12}(有吗|存在吗|还在吗|是否存在|是不是)", - re.IGNORECASE, -) -LEGACY_COMMAND_PROJECT_CREATE_RE = re.compile( - r"(创建|新建|建立|开个|开一个|建个|建一个|做个|做一个).{0,6}项目", - re.IGNORECASE, -) -LEGACY_COMMAND_VERB_RE = re.compile( - r"(创建|新建|建立|开个|开一个|建个|建一个|做个|做一个|修改|更新|改成|改为|设为|设成|分配|指派|归档|重开|重新打开|恢复|标记完成|完成|提醒|通知|调整工时|调整工作量)", - re.IGNORECASE, -) -LEGACY_COMMAND_OBJECT_RE = re.compile( - r"(项目|任务|负责人|优先级|状态|通知|提醒|工时|工作量)", - re.IGNORECASE, -) -CREATE_ACTION_RE = re.compile(r"(新增|新建|创建|建立|接入|配置|录入|加入|放到|放进|立项)", re.IGNORECASE) -CORRECTION_CUE_RE = re.compile( - r"(不对|错了|理解错|不是这个|不是这个意思|我说的是|我要的是|不是啊|不是新增 md|不是写 markdown)", - re.IGNORECASE, -) -EXTERNAL_INTEGRATION_RE = re.compile( - r"(github|gitlab|jira|slack|webhook|飞书|企业微信|企微|lark|api|接入|授权|同步)", - re.IGNORECASE, -) - -__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/ai_pmo/workflow_semantic_planner/service.py b/backend/app/services/ai_pmo/workflow_semantic_planner/service.py index c6a369fe..388d0470 100644 --- a/backend/app/services/ai_pmo/workflow_semantic_planner/service.py +++ b/backend/app/services/ai_pmo/workflow_semantic_planner/service.py @@ -1,751 +1,201 @@ from __future__ import annotations +import asyncio import hashlib import re from typing import Any, Iterable, Optional +from ..semantic_context.decision_schema import DecisionWorkflowPlan, ModeCard, SemanticDecision +from ..semantic_context.packet_schema import ( + SemanticContextIngress, + SemanticContextPacket, + SemanticContextThread, + SemanticContextTopic, +) +from ..semantic_context.semantic_decision_service import SemanticDecisionService +from ...llm_service.service import LLMService from .constants import ( - ASSIGNEE_RECOMMENDATION_HINTS, - BACKGROUND_MARKERS, - BUSINESS_REVIEW_ANALYSIS_HINTS, - BUSINESS_REVIEW_TARGET_HINTS, - CONTEXTUAL_CONFIRM_HINTS, - CONTEXTUAL_DEICTIC_HINTS, - CONTEXTUAL_WRITE_ACTION_HINTS, - CORRECTION_ESCALATION_HINTS, - CREATE_ACTION_RE, - DRAFT_CONFIRM_EXACT_HINTS, - DRAFT_NATURAL_REVISION_HINTS, - DRAFT_REVISION_PREFIX_HINTS, - EXTERNAL_INTEGRATION_RE, - FOLLOWUP_CANCEL_HINTS, - FOLLOWUP_CREATE_CONTINUE_RE, - FOLLOWUP_COMMIT_HINTS, - FOLLOWUP_CONFIRM_HINTS, - FOLLOWUP_CONTINUE_HINTS, - FOLLOWUP_CONTINUE_RE, - FOLLOWUP_REFERENCE_RE, - FOLLOWUP_REFRESH_HINTS, - FOLLOWUP_STATUS_RE, - FOLLOWUP_WORK_ITEM_ID_RE, - GENERIC_SIGNAL_SOURCE_MESSAGE_RE, - HEALTHCHECK_RE, - INTENT_FOLLOWUP_CONFIRM, - INTENT_FOLLOWUP_CONTINUE, - INTENT_IGNORE, - INTENT_PROJECT_ARCHIVE, - INTENT_PROJECT_CREATE, - INTENT_PROJECT_DELETE, - INTENT_PROJECT_REOPEN, - INTENT_PROJECT_UPDATE, - INTENT_QUERY, - INTENT_SIGNAL_CREATE, - INTENT_SIGNAL_SOURCE_ONBOARD, - INTENT_TASK_ASSIGN, - INTENT_TASK_CREATE, - INTENT_TASK_DELETE, - INTENT_TASK_PRIORITIZE, - INTENT_TASK_STATUS_TRANSITION, - INTENT_TASK_UPDATE, MESSAGE_GATE_AUTO, MESSAGE_GATE_PENDING, - MULTI_SEGMENT_MARKERS, - NATURAL_PROJECT_CREATE_HINTS, - NATURAL_PROJECT_CREATE_REGEX, - PROJECT_CREATE_DEFERRED_CONFIRM_HINTS, - PROJECT_CREATE_HINTS, - PROJECT_CREATE_QUERY_GUARD_HINTS, - PROJECT_CREATE_REGEX, - PROJECT_ID_QUERY_RE, - PROJECT_LIST_QUERY_RE, - PROJECT_SIGNAL_DISCOVER_HINTS, - QUERY_SCENE_BUSINESS_REVIEW, - QUERY_SCENE_CORRECTION_ESCALATION, - QUERY_SCENE_REQUIREMENT_PLAN, - QUESTION_HINTS, - REQUIREMENT_PLAN_CHAT_HINTS, - REQUIREMENT_TOPIC_SWITCH_NEW_HINTS, - REQUIREMENT_TOPIC_SWITCH_NEW_PATTERNS, REPLY_MODE_CARD, REPLY_MODE_TEXT, - ROUTE_FOLLOWUP_CONFIRM, - ROUTE_FOLLOWUP_CONTINUE, - ROUTE_PROJECT_CREATE, - ROUTE_PROJECT_SIGNAL_CANCEL, - ROUTE_PROJECT_SIGNAL_COMMIT, - ROUTE_PROJECT_SIGNAL_CREATE, - ROUTE_PROJECT_SIGNAL_DISCOVER, - ROUTE_PROJECT_SIGNAL_REFRESH, - ROUTE_QUERY_ANSWER, - ROUTE_SIGNAL_SOURCE_ONBOARD, - ROUTE_TASK_CREATE, SEMANTIC_VERSION, - SIGNAL_CREATE_HINTS, - SIGNAL_CREATE_REGEX, - SHORT_CONFIRM_RE, - SMALLTALK_EXACT, - SMALLTALK_SHORT_HINTS, - SMALLTALK_STRUCTURED_HINTS, - SOURCE_ONBOARDING_ACTION_RE, - TASK_CREATE_HINTS, - TASK_CREATE_REGEX, - LEGACY_COMMAND_OBJECT_RE, - LEGACY_COMMAND_PROJECT_CREATE_RE, - LEGACY_COMMAND_VERB_RE, - OBVIOUS_QUESTION_RE, - QUERY_INTENT_RE, - WORKFLOW_FOLLOWUP, - WORKFLOW_NEW_PROJECT, - WORKFLOW_PROJECT, - WORKFLOW_PROJECT_SIGNAL, - WORKFLOW_QUERY, - WORKFLOW_SIGNAL_SOURCE, - WORKFLOW_TASK, ) from .models import ReplyContract, WorkflowBundle, WorkflowPlan, workflow_slot class WorkflowSemanticPlanner: - _WRITE_VERBS = ( - "新增", - "增加", - "添加", - "加", - "新建", - "创建", - "建立", - "立项", - "接入", - "修复", - "优化", - "改成", - "改为", - "更新", - "分配", - "指派", - "归档", - "执行", + _SCENE_METADATA_KEYS = ( + "message_intent", + "review_framework", + "consultant_mode_preference", + "force_thinking_mode", + "advise_subtype", + "topic_switch", ) - _FOLLOWUP_ALLOWED_ACTIONS = ("continue", "confirm", "commit", "cancel", "refresh") - _FOLLOWUP_CONTEXT_KEYS = ("target_work_item_id", "followup_allowed_actions", "reply_to_message_id", "thread_anchor") @staticmethod def _normalize_text(text: str) -> str: return re.sub(r"\s+", "", str(text or "")).lower() - @classmethod - def _has_question_structure(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized: - return False - return "?" in normalized or "?" in normalized or any(token in normalized for token in QUESTION_HINTS) - - @classmethod - def _looks_like_casual_chat_message(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized: - return False - if cls._is_smalltalk_message(text): - return True - if any( - token in normalized - for token in ( - "陪我聊", - "聊两句", - "最近有点累", - "先不推进需求", - "今天你在线吗", - "你在线吗", - "早上好", - "下午好", - "晚上好", - ) - ): - return True - if any(token in normalized for token in ("天气", "下雨", "温度", "散步", "走走")) and not any( - keyword in normalized for keyword in ("项目", "任务", "信号", "风险", "进度", "状态", "工作项", "系统") - ): - return True - return False - - @classmethod - def _looks_like_requirement_followup_clarify(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized or cls._has_question_structure(text): - return False - if cls._looks_like_casual_chat_message(text): - return False - has_followup_anchor = any( - token in normalized - for token in ( - "按上面的方向", - "按上面的思路", - "照上面的方向", - "先按上面", - "先按这个方向", - "继续", - "往下收", - "往下做", - ) - ) - has_scope_narrowing = any( - token in normalized - for token in ( - "别太重", - "轻一点", - "先收一版", - "先收一下", - "先缩一下", - "先聚焦", - "先只做", - "轻量版", - ) - ) - return (has_followup_anchor and has_scope_narrowing) or ("上面" in normalized and "别太重" in normalized) - - @classmethod - def _looks_like_requirement_scope_advise(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized: - return False - if cls._looks_like_casual_chat_message(text): - return False - has_scope_token = any( - token in normalized - for token in ( - "范围怎么收", - "范围怎么定", - "范围先收", - "先做什么", - "先保留什么", - "怎么收才稳", - "怎么做才稳", - "第一版先做", - "首期先做", - ) - ) - has_phase_token = any(token in normalized for token in ("mvp", "第一版", "首期")) - return has_scope_token and has_phase_token - - @classmethod - def _looks_like_requirement_plan_chat(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized: - return False - if cls._looks_like_casual_chat_message(text): - return False - if any(token in normalized for token in REQUIREMENT_PLAN_CHAT_HINTS): - return True - has_chat_verb = any( - token in normalized - for token in ("聊聊", "聊下", "聊一下", "讨论", "梳理", "收敛", "想跟你聊") - ) - has_requirement_target = any( - token in normalized - for token in ("需求", "想法", "功能", "方案", "mvp", "第一版", "产品") - ) - return has_chat_verb and has_requirement_target - - @classmethod - def looks_like_explicit_new_requirement_topic(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized: - return False - if cls._looks_like_casual_chat_message(text): - return False - if any(token in normalized for token in REQUIREMENT_TOPIC_SWITCH_NEW_HINTS): - return True - if any(pattern.search(str(text or "")) for pattern in REQUIREMENT_TOPIC_SWITCH_NEW_PATTERNS): - return True - if "现在是" in normalized and any(token in normalized for token in ("另一个需求", "新的需求", "新话题")): - return True - if "不对" in normalized and any(token in normalized for token in ("另一个需求", "不是这个需求", "别沿用", "不要沿用")): - return True - return False - - @classmethod - def looks_like_requirement_plan_choice_reply(cls, text: str) -> bool: - return bool(re.match(r"^\s*([ABCabc])(?:[\.\,,。、::)\]】\s]|$)", str(text or ""))) - - @classmethod - def looks_like_low_context_requirement_followup(cls, text: str) -> bool: - raw = str(text or "").strip() - if not raw: - return False - if cls._looks_like_casual_chat_message(raw): - return False - if cls.looks_like_explicit_new_requirement_topic(raw): - return False - if cls._has_question_structure(raw) or cls.is_obvious_question(raw): - return False - if cls.looks_like_requirement_plan_choice_reply(raw): - return True - normalized = cls._normalize_text(raw) - if normalized in DRAFT_CONFIRM_EXACT_HINTS: - return True - return normalized in {"你自行推荐", "你来推荐", "你来定", "你决定"} - - @classmethod - def _looks_like_requirement_plan_slot_answer(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized: - return False - if cls._looks_like_casual_chat_message(text): - return False - if cls._has_question_structure(text) and not any( - token in normalized for token in ("草稿", "汇总", "整理", "先按", "第一版") - ): - return False - slot_hits = 0 - slot_hits += int(any(token in normalized for token in ("技术指标", "均线", "成交量", "涨跌比", "macd", "rsi", "规则"))) - slot_hits += int(any(token in normalized for token in ("情绪", "涨停家数", "北向", "北向资金", "舆情", "热度"))) - slot_hits += int( - any( - token in normalized - for token in ( - "投顾", - "研究员", - "研究团队", - "交易员", - "交易团队", - "负责人", - "研发负责人", - "内部pm", - "pm", - "给我看", - "给团队看", - "给谁看", - "给谁用", - ) - ) - ) - slot_hits += int(any(token in normalized for token in ("飞书日报", "飞书消息", "飞书总结", "简报", "看板", "页面", "仪表盘", "一句解释"))) - slot_hits += int(any(token in normalized for token in ("每天", "每日", "收盘后", "开盘前", "盘中", "实时", "会后", "半小时", "当天内"))) - slot_hits += int(any(token in normalized for token in ("第一版", "先保留", "先只做", "别做太重", "先做成", "先按"))) - has_planning_prefix = any( - token in normalized - for token in ("先按", "主要给", "先给", "先做成", "先保留") - ) - has_generic_target_user_shape = any( - token in normalized - for token in ("给内部", "给团队", "负责人用", "负责人看", "研发负责人", "内部pm") - ) - has_generic_cadence_shape = any( - token in normalized - for token in ("会后", "半小时", "当天内", "整理出来", "整理出", "产出") - ) - compact_length = len(normalized) - if cls._looks_like_requirement_plan_chat(text) and slot_hits <= 1 and not has_generic_target_user_shape and not has_generic_cadence_shape: - return False - return ( - slot_hits >= 2 - or (has_planning_prefix and slot_hits >= 1) - or (slot_hits >= 1 and compact_length <= 10) - or (has_generic_target_user_shape and slot_hits >= 1) - or (has_generic_cadence_shape and slot_hits >= 1) - ) - - @classmethod - def _derive_query_scene_metadata(cls, text: str) -> dict[str, Any]: - normalized = cls._normalize_text(text) - if not normalized: - return {} - if cls._looks_like_casual_chat_message(text): - return { - "message_intent": "casual_chat", - "consultant_mode_preference": "brainstorm", - } - if cls.looks_like_explicit_new_requirement_topic(text): - return { - "message_intent": QUERY_SCENE_REQUIREMENT_PLAN, - "consultant_mode_preference": "clarify", - "topic_switch": "new", - } - if cls._looks_like_requirement_followup_clarify(text): - return { - "message_intent": QUERY_SCENE_BUSINESS_REVIEW, - "consultant_mode_preference": "clarify", - } - if cls._looks_like_requirement_scope_advise(text): - return { - "message_intent": QUERY_SCENE_BUSINESS_REVIEW, - "consultant_mode_preference": "advise", - "advise_subtype": "scope", - } - if cls._looks_like_requirement_plan_chat(text): - return { - "message_intent": QUERY_SCENE_REQUIREMENT_PLAN, - "consultant_mode_preference": "clarify", - } - if cls._looks_like_requirement_plan_slot_answer(text): - return { - "message_intent": QUERY_SCENE_REQUIREMENT_PLAN, - "consultant_mode_preference": "clarify", - } - - has_correction_cue = any( - token in normalized - for token in ( - "不对", - "错了", - "答错", - "纠正", - "更正", - "重新回答", - "重答", - ) - ) - has_escalation_hint = any(token in normalized for token in CORRECTION_ESCALATION_HINTS) - if has_escalation_hint and (has_correction_cue or "高级模型" in normalized or "更强模型" in normalized): - return { - "message_intent": QUERY_SCENE_CORRECTION_ESCALATION, - "consultant_mode_preference": "challenge", - "force_thinking_mode": "deep", - } - has_review_analysis_hint = any(token in normalized for token in BUSINESS_REVIEW_ANALYSIS_HINTS) - has_review_target_hint = any(token in normalized for token in BUSINESS_REVIEW_TARGET_HINTS) - if has_review_analysis_hint and has_review_target_hint: - return { - "message_intent": QUERY_SCENE_BUSINESS_REVIEW, - "review_framework": "five_lenses", - "consultant_mode_preference": "clarify", - } - if cls._looks_like_assignee_recommendation(text): - return { - "message_intent": QUERY_SCENE_BUSINESS_REVIEW, - "consultant_mode_preference": "advise", - "advise_subtype": "assignee", - } - if cls._should_keep_requirement_discussion_in_query(text): - return { - "message_intent": QUERY_SCENE_BUSINESS_REVIEW, - "consultant_mode_preference": "clarify", - } - return {} - @classmethod def _build_query_bundle_diagnostics( cls, *, - intent_name: Optional[str] = None, - workflow_route: Optional[str] = None, scene_metadata: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: diagnostics: dict[str, Any] = {} - if str(intent_name or "").strip(): - diagnostics["intent_name"] = str(intent_name).strip() - if str(workflow_route or "").strip(): - diagnostics["workflow_route"] = str(workflow_route).strip() - for key in ("message_intent", "review_framework", "consultant_mode_preference", "force_thinking_mode", "advise_subtype", "topic_switch"): - value = None if not isinstance(scene_metadata, dict) else scene_metadata.get(key) - if value not in (None, ""): - diagnostics[key] = value + payload = scene_metadata if isinstance(scene_metadata, dict) else {} + for key in cls._SCENE_METADATA_KEYS: + value = payload.get(key) + if value in (None, ""): + continue + diagnostics[key] = value return diagnostics @classmethod - def _is_smalltalk_message(cls, text: str) -> bool: - compact = re.sub(r"[\s\??!!,,。~~、]+", "", str(text or "")).strip().lower() - if not compact: - return True - if any(keyword in compact for keyword in SMALLTALK_STRUCTURED_HINTS): - return False - if compact in SMALLTALK_EXACT: - return True - if len(compact) <= 8 and any(keyword in compact for keyword in SMALLTALK_SHORT_HINTS): - return True - return False - - @classmethod - def _looks_like_project_create(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized: - return False - if any(token in normalized for token in PROJECT_CREATE_QUERY_GUARD_HINTS): - return False - if any(token in normalized for token in PROJECT_CREATE_HINTS): - return True - if PROJECT_CREATE_REGEX.search(normalized): - return True - if any(token in normalized for token in NATURAL_PROJECT_CREATE_HINTS): - return True - return bool(NATURAL_PROJECT_CREATE_REGEX.search(normalized)) - - @classmethod - def _looks_like_assignee_recommendation(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized: - return False - if any(token in normalized for token in ASSIGNEE_RECOMMENDATION_HINTS): - return True - if not cls._has_question_structure(text): - return False - return any(token in normalized for token in ASSIGNEE_RECOMMENDATION_HINTS) - - @classmethod - def _should_keep_requirement_discussion_in_query(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized: - return False - if cls.looks_like_explicit_new_requirement_topic(text): - return True - if any(token in normalized for token in PROJECT_CREATE_QUERY_GUARD_HINTS): - return True - if any(token in normalized for token in PROJECT_CREATE_DEFERRED_CONFIRM_HINTS): - return True - if cls._looks_like_requirement_plan_chat(text): - return True - if cls._looks_like_requirement_plan_slot_answer(text): - return True - return cls._looks_like_assignee_recommendation(text) - - @classmethod - def _looks_like_task_create(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - return any(token in normalized for token in TASK_CREATE_HINTS) or bool(TASK_CREATE_REGEX.search(normalized)) - - @classmethod - def _looks_like_signal_create(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if any(token in normalized for token in PROJECT_SIGNAL_DISCOVER_HINTS): - return True - return any(token in normalized for token in SIGNAL_CREATE_HINTS) or bool(SIGNAL_CREATE_REGEX.search(normalized)) - - @classmethod - def looks_like_project_create_request(cls, text: str) -> bool: - return cls._looks_like_project_create(text) - - @classmethod - def looks_like_task_create_request(cls, text: str) -> bool: - return cls._looks_like_task_create(text) - - @classmethod - def looks_like_signal_create_request(cls, text: str) -> bool: - return cls._looks_like_signal_create(text) - - @classmethod - def looks_like_signal_source_request(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if len(normalized) < 3: - return False - if GENERIC_SIGNAL_SOURCE_MESSAGE_RE.search(normalized): - return True - return bool(SOURCE_ONBOARDING_ACTION_RE.search(normalized)) - - @staticmethod - def is_healthcheck_message(text: str) -> bool: - return bool(HEALTHCHECK_RE.search(str(text or "").strip())) - - @staticmethod - def is_short_confirmation_noise(text: str) -> bool: - return bool(SHORT_CONFIRM_RE.search(str(text or "").strip())) - - @classmethod - def wants_followup_continue(cls, text: str) -> bool: - raw = str(text or "").strip() - if not raw: - return False - if FOLLOWUP_CREATE_CONTINUE_RE.search(raw): - return bool(FOLLOWUP_REFERENCE_RE.search(raw)) - return bool(FOLLOWUP_CONTINUE_RE.search(raw)) - - @staticmethod - def wants_followup_status(text: str) -> bool: - return bool(FOLLOWUP_STATUS_RE.search(str(text or ""))) - - @classmethod - def looks_like_external_connect_request(cls, text: str) -> bool: - raw = str(text or "").strip() - if not raw: - return False - return bool(CREATE_ACTION_RE.search(raw) and EXTERNAL_INTEGRATION_RE.search(raw)) - - @classmethod - def is_legacy_business_command(cls, text: str) -> bool: - raw = str(text or "").strip() - if not raw: - return False - if cls.is_query_like_question(raw): - return False - if LEGACY_COMMAND_PROJECT_CREATE_RE.search(raw): - return True - return bool(LEGACY_COMMAND_VERB_RE.search(raw) and LEGACY_COMMAND_OBJECT_RE.search(raw)) - - @classmethod - def looks_like_project_lookup_question(cls, text: str) -> bool: - raw = str(text or "").strip() - if not raw: - return False - if cls.is_legacy_business_command(raw): - return False - normalized = cls._normalize_text(raw) - if PROJECT_LIST_QUERY_RE.search(raw): - return True - if PROJECT_ID_QUERY_RE.search(raw): - return True - if "项目" not in normalized: - return False - return any(token in normalized for token in ("有吗", "有没有", "有无", "是否有", "是否存在", "存在吗", "是什么", "哪些")) - - @staticmethod - def extract_project_lookup_keyword(text: str) -> str: - raw = str(text or "").strip() - if not raw: - return "" - cleaned = re.sub( - r"(请问|麻烦问下|问下|现在|当前|目前|系统里|系统中|项目里|项目中|有没有|有无|有吗|是否有|是否存在|存在吗|这个|那个|一个|个|项目|列表|清单|有|吗|呢|呀|啊|么)", - " ", - raw, - flags=re.IGNORECASE, - ) - cleaned = re.sub(r"[,。,.;;::!??()()\\[\\]【】]+", " ", cleaned) - cleaned = re.sub(r"\s+", " ", cleaned).strip(" ??") - return cleaned[:40] - - @classmethod - def is_query_like_question(cls, text: str) -> bool: - raw = str(text or "").strip() - if not raw: - return False - normalized = cls._normalize_text(raw) - has_question_structure = bool(OBVIOUS_QUESTION_RE.search(raw) or QUERY_INTENT_RE.search(raw)) - if not has_question_structure: - return False - has_operation_verb = bool(LEGACY_COMMAND_VERB_RE.search(raw)) - imperative_tokens = ("帮我", "请", "麻烦", "直接", "立刻", "马上", "执行", "处理", "安排") - has_imperative = any(token in normalized for token in imperative_tokens) - return not has_operation_verb and not has_imperative - - @classmethod - def is_obvious_question(cls, text: str) -> bool: - raw = str(text or "").strip() - if not raw: - return False - if cls.is_query_like_question(raw): - return True - if cls.is_legacy_business_command(raw): - return False - if cls.looks_like_project_lookup_question(raw): - return True - return bool(OBVIOUS_QUESTION_RE.search(raw)) - - @classmethod - def looks_like_contextual_write_confirmation(cls, text: str) -> bool: - normalized = cls._normalize_text(text) - if not normalized: - return False - if cls._has_question_structure(text): - return False - has_write_action = any(token in normalized for token in CONTEXTUAL_WRITE_ACTION_HINTS) - if not has_write_action: - return False - has_confirm = any(token in normalized for token in CONTEXTUAL_CONFIRM_HINTS) - has_deictic = any(token in normalized for token in CONTEXTUAL_DEICTIC_HINTS) - return has_confirm or has_deictic - - @classmethod - def _looks_like_followup(cls, text: str, *, metadata: Optional[dict[str, Any]] = None) -> bool: - payload = metadata if isinstance(metadata, dict) else {} - if any(str(payload.get(key) or "").strip() for key in cls._FOLLOWUP_CONTEXT_KEYS): - return True - if FOLLOWUP_WORK_ITEM_ID_RE.search(str(text or "")): - return True - normalized = cls._normalize_text(text) - followup_tokens = (*FOLLOWUP_CONFIRM_HINTS, *FOLLOWUP_CONTINUE_HINTS, *FOLLOWUP_CANCEL_HINTS, *FOLLOWUP_REFRESH_HINTS) - return any(token in normalized for token in followup_tokens) or cls.wants_followup_continue(text) or cls.wants_followup_status(text) - - @classmethod - def parse_followup_action( + def _build_cold_start_packet( cls, *, + message_text: str, + source_system: str, metadata: Optional[dict[str, Any]] = None, - canonical_intent: str, - message_text: str = "", - ) -> str: - payload = metadata if isinstance(metadata, dict) else {} - explicit_action = str(payload.get("followup_action") or payload.get("action") or "").strip().lower() - if explicit_action in cls._FOLLOWUP_ALLOWED_ACTIONS: - return explicit_action - followup_context_present = bool( - payload.get("followup_context_present") - or payload.get("target_work_item_id") - or payload.get("followup_allowed_actions") - or payload.get("reply_to_message_id") - or payload.get("thread_anchor") + ) -> SemanticContextPacket: + payload = dict(metadata or {}) + scene_context = { + "project_id": payload.get("project_id"), + "session_last_intent": payload.get("session_last_intent"), + "session_project_id": payload.get("session_project_id"), + "world_model_project_id": payload.get("world_model_project_id"), + "requested_count": payload.get("requested_count"), + } + return SemanticContextPacket( + packet_version="v1", + tenant_id=0, + operator_user_id=0, + topic_workspace_id=0, + latest_user_message=str(message_text or "").strip(), + topic=SemanticContextTopic( + topic_workspace_id=0, + topic_key="cold_start", + source_system=str(source_system or "").strip().lower() or "unknown", + mode="CHAT", + status="active", + metadata=scene_context, + ), + draft=None, + thread=SemanticContextThread( + thread_key=None, + conversation_scope_key=None, + thread_summary=None, + recent_turns=[], + open_questions=[], + ), + mode_card=ModeCard( + workflow_mode="CHAT", + mode_state="IDLE", + allowed_actions=["workflow.bundle.compose"], + forbidden_actions=[], + exit_conditions=[], + ), + ingress=SemanticContextIngress( + candidate_kind="cold_start_route", + source_system=str(source_system or "").strip().lower() or None, + reply_anchor_present=bool(payload.get("reply_to_message_id") or payload.get("thread_anchor")), + active_context_kind=None, + allowed_context_scopes=[], + thread_context=payload, + ), ) - if followup_context_present: - normalized = cls._normalize_text(message_text) - if any(token in normalized for token in FOLLOWUP_REFRESH_HINTS): - return "refresh" - if any(token in normalized for token in FOLLOWUP_CANCEL_HINTS): - return "cancel" - if any(token in normalized for token in FOLLOWUP_COMMIT_HINTS): - return "commit" - if canonical_intent == INTENT_FOLLOWUP_CONFIRM: - return "confirm" - return "continue" @classmethod - def parse_draft_action(cls, *, message_text: str) -> tuple[str, str]: - raw_text = str(message_text or "").strip() - text = FOLLOWUP_WORK_ITEM_ID_RE.sub("", raw_text, count=1).strip() - lowered = cls._normalize_text(text) - if any(text.startswith(prefix) for prefix in DRAFT_REVISION_PREFIX_HINTS): - note = re.sub(r"^(?:修改|改成|改为|换成|改)\s*", "", text).strip() - return "revise", note or text - if cls._looks_like_scope_narrowing_revision(text): - return "revise", text - if any(hint in text for hint in FOLLOWUP_CANCEL_HINTS): - return "cancel", "" - if lowered in DRAFT_CONFIRM_EXACT_HINTS: - return "confirm", "" - if cls.looks_like_natural_draft_revision(text): - return "revise", text - return "unknown", "" + async def _decide_cold_start_semantics( + cls, + *, + message_text: str, + source_system: str, + metadata: Optional[dict[str, Any]] = None, + ) -> SemanticDecision: + packet = cls._build_cold_start_packet( + message_text=message_text, + source_system=source_system, + metadata=metadata, + ) + decision = await SemanticDecisionService(LLMService()).decide(packet=packet) + if "workflow.bundle.compose" not in { + str(item).strip() + for item in decision.requested_actions + if str(item or "").strip() + }: + decision.requested_actions = [*decision.requested_actions, "workflow.bundle.compose"] + if not decision.workflow_plans: + raise ValueError("cold_start_route returned no workflow_plans") + return decision @classmethod - def _looks_like_scope_narrowing_revision(cls, text: str) -> bool: - raw = str(text or "").strip() - if not raw: - return False - normalized = cls._normalize_text(raw) - if not normalized: - return False - if any(token in normalized for token in ("取消", "不要了", "算了", "reject", "先不做了", "先别做了")): - return False - if "先别做" in normalized and any(token in normalized for token in ("太大", "太多", "这么大", "这么多")): - return True - if any(token in normalized for token in ("第一版", "首期", "先只做", "只做")) and any( - token in normalized - for token in ("能力", "功能", "数据", "团队", "指标", "切片", "总结", "清单", "模块", "范围", "三件事", "这几项", "这些") - ): - return True - return False + def _bundle_from_cold_start_decision( + cls, + *, + decision: SemanticDecision, + source_system: str, + message_text: str, + ) -> WorkflowBundle: + workflows: list[WorkflowPlan] = [] + for item in decision.workflow_plans: + normalized = item if isinstance(item, DecisionWorkflowPlan) else DecisionWorkflowPlan.model_validate(item) + workflows.append( + WorkflowPlan( + workflow_type=normalized.workflow_type, + workflow_action=normalized.workflow_action, + target_scope=normalized.target_scope, + operation_codes=[str(code) for code in normalized.operation_codes if str(code or "").strip()], + requires_confirmation=bool(normalized.requires_confirmation), + confidence=float(normalized.confidence or decision.confidence or 0.0), + clarification_needed=bool(normalized.clarification_needed), + metadata=dict(normalized.metadata or {}), + ) + ) + if not workflows: + raise ValueError("cold_start_route bundle has no workflows") + primary_workflow = workflows[0].workflow_type + return WorkflowBundle( + bundle_id=cls._bundle_id( + source_system=source_system, + message_text=message_text, + workflows=workflows, + ), + semantic_version=SEMANTIC_VERSION, + source_system=str(source_system or "").strip().lower() or "unknown", + primary_workflow=primary_workflow, + workflows=workflows, + message_gate=cls._gate_for_workflows(workflows), + reply_contract=cls._reply_contract( + source_system=source_system, + primary_workflow=primary_workflow, + ), + diagnostics=cls._build_query_bundle_diagnostics( + scene_metadata=dict(decision.scene_metadata or {}), + ), + ) @classmethod - def looks_like_natural_draft_revision(cls, text: str) -> bool: - raw = str(text or "").strip() - if not raw: - return False - normalized = cls._normalize_text(raw) - if not normalized: - return False - if cls.is_obvious_question(raw) or cls._has_question_structure(raw): - return False - if cls._is_smalltalk_message(raw): - return False - if cls._looks_like_scope_narrowing_revision(raw): - return True - if any(hint in raw for hint in FOLLOWUP_CANCEL_HINTS): - return False - if normalized in DRAFT_CONFIRM_EXACT_HINTS: - return False - if any(token in normalized for token in DRAFT_NATURAL_REVISION_HINTS): - return True - if len(normalized) < 8: - return False - if any(marker in raw for marker in MULTI_SEGMENT_MARKERS): - return True - return any(marker in normalized for marker in BACKGROUND_MARKERS) + async def aplan_message( + cls, + *, + message_text: str, + source_system: str, + metadata: Optional[dict[str, Any]] = None, + ) -> WorkflowBundle: + decision = await cls._decide_cold_start_semantics( + message_text=message_text, + source_system=source_system, + metadata=metadata, + ) + return cls._bundle_from_cold_start_decision( + decision=decision, + source_system=source_system, + message_text=str(message_text or "").strip(), + ) @classmethod def _bundle_id( @@ -776,154 +226,6 @@ class WorkflowSemanticPlanner: return MESSAGE_GATE_PENDING return MESSAGE_GATE_AUTO - @classmethod - def _plan_from_intent( - cls, - *, - intent_name: str, - message_text: str, - source_system: str, - workflow_route: str = "", - project_id: Optional[int] = None, - metadata: Optional[dict[str, Any]] = None, - ) -> WorkflowBundle: - payload = dict(metadata or {}) - normalized_route = str(workflow_route or payload.get("workflow_route") or "").strip().lower() - query_scene_metadata = cls._derive_query_scene_metadata(message_text) - workflows: list[WorkflowPlan] = [] - slots = [slot for slot in [workflow_slot("project_id", project_id)] if slot is not None] - should_hold_requirement_discussion = cls._should_keep_requirement_discussion_in_query(message_text) - if intent_name == INTENT_QUERY: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_QUERY, - workflow_action="answer", - target_scope="project" if project_id is not None else "global", - operation_codes=[ROUTE_QUERY_ANSWER], - requires_confirmation=False, - confidence=0.88, - slots=slots, - metadata=dict(query_scene_metadata), - ) - ) - elif intent_name == INTENT_PROJECT_CREATE and should_hold_requirement_discussion: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_QUERY, - workflow_action="answer", - target_scope="project" if project_id is not None else "global", - operation_codes=[ROUTE_QUERY_ANSWER], - requires_confirmation=False, - confidence=float(payload.get("confidence") or 0.88), - slots=slots, - metadata=dict(query_scene_metadata), - ) - ) - elif intent_name == INTENT_PROJECT_CREATE: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_NEW_PROJECT, - workflow_action="create", - target_scope="global", - operation_codes=["project.create"], - requires_confirmation=bool(payload.get("requires_confirmation")), - confidence=float(payload.get("confidence") or 0.9), - slots=slots, - ) - ) - elif intent_name in {INTENT_PROJECT_UPDATE, INTENT_PROJECT_ARCHIVE, INTENT_PROJECT_REOPEN, INTENT_PROJECT_DELETE}: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_PROJECT, - workflow_action=str(intent_name.split(".", 1)[1] if "." in intent_name else "update"), - target_scope="project", - operation_codes=[intent_name], - requires_confirmation=bool(payload.get("requires_confirmation")), - confidence=float(payload.get("confidence") or 0.82), - slots=slots, - ) - ) - elif intent_name in {INTENT_TASK_CREATE, INTENT_TASK_UPDATE, INTENT_TASK_ASSIGN, INTENT_TASK_PRIORITIZE, INTENT_TASK_STATUS_TRANSITION, INTENT_TASK_DELETE}: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_TASK, - workflow_action=str(intent_name.split(".", 1)[1] if "." in intent_name else "create"), - target_scope="project" if project_id is not None else "global", - operation_codes=[intent_name if intent_name != INTENT_TASK_CREATE else ROUTE_TASK_CREATE], - requires_confirmation=bool(payload.get("requires_confirmation")), - confidence=float(payload.get("confidence") or 0.82), - slots=slots, - ) - ) - elif intent_name == INTENT_SIGNAL_SOURCE_ONBOARD: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_SIGNAL_SOURCE, - workflow_action="onboard", - target_scope="project" if project_id is not None else "global", - operation_codes=[ROUTE_SIGNAL_SOURCE_ONBOARD], - requires_confirmation=bool(payload.get("requires_confirmation")), - confidence=float(payload.get("confidence") or 0.92), - slots=slots, - ) - ) - elif intent_name == INTENT_SIGNAL_CREATE: - route_to_action = { - ROUTE_PROJECT_SIGNAL_DISCOVER: "discover", - ROUTE_PROJECT_SIGNAL_COMMIT: "commit", - ROUTE_PROJECT_SIGNAL_CANCEL: "cancel", - ROUTE_PROJECT_SIGNAL_REFRESH: "refresh", - } - workflow_action = route_to_action.get(normalized_route, "create") - operation_codes = [normalized_route or ROUTE_PROJECT_SIGNAL_CREATE] - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_PROJECT_SIGNAL, - workflow_action=workflow_action, - target_scope="project" if project_id is not None else "global", - operation_codes=operation_codes, - requires_confirmation=workflow_action in {"discover", "commit", "cancel", "refresh"} or bool(payload.get("requires_confirmation")), - confidence=float(payload.get("confidence") or 0.8), - slots=slots, - ) - ) - elif intent_name in {INTENT_FOLLOWUP_CONFIRM, INTENT_FOLLOWUP_CONTINUE}: - followup_action = cls.parse_followup_action( - metadata={**payload, "followup_context_present": True}, - canonical_intent=intent_name, - message_text=message_text, - ) - work_item_id = payload.get("target_work_item_id") - followup_slots = [slot for slot in [*slots, workflow_slot("target_work_item_id", work_item_id)] if slot is not None] - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_FOLLOWUP, - workflow_action=followup_action, - target_scope="thread", - operation_codes=[ROUTE_FOLLOWUP_CONFIRM if followup_action == "confirm" else ROUTE_FOLLOWUP_CONTINUE], - requires_confirmation=False, - confidence=float(payload.get("confidence") or 0.82), - slots=followup_slots, - ) - ) - else: - return cls.plan_message(message_text=message_text, source_system=source_system, metadata=payload) - primary_workflow = workflows[0].workflow_type - return WorkflowBundle( - bundle_id=cls._bundle_id(source_system=source_system, message_text=message_text, workflows=workflows), - semantic_version=SEMANTIC_VERSION, - source_system=str(source_system or "").strip().lower() or "unknown", - primary_workflow=primary_workflow, - workflows=workflows, - message_gate=cls._gate_for_workflows(workflows), - reply_contract=cls._reply_contract(source_system=source_system, primary_workflow=primary_workflow), - diagnostics=cls._build_query_bundle_diagnostics( - intent_name=intent_name, - workflow_route=normalized_route or None, - scene_metadata=query_scene_metadata, - ), - ) - @classmethod def plan_message( cls, @@ -932,160 +234,17 @@ class WorkflowSemanticPlanner: source_system: str, metadata: Optional[dict[str, Any]] = None, ) -> WorkflowBundle: - text = str(message_text or "").strip() - payload = dict(metadata or {}) - normalized = cls._normalize_text(text) - query_scene_metadata = cls._derive_query_scene_metadata(text) - workflows: list[WorkflowPlan] = [] - if not text or cls._looks_like_casual_chat_message(text): - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_QUERY, - workflow_action="answer", - target_scope="global", - operation_codes=[ROUTE_QUERY_ANSWER], - requires_confirmation=False, - confidence=0.55, - clarification_needed=False, - metadata=dict(query_scene_metadata), + try: + asyncio.get_running_loop() + except RuntimeError: + return asyncio.run( + cls.aplan_message( + message_text=message_text, + source_system=source_system, + metadata=metadata, ) ) - elif cls._looks_like_followup(text, metadata=payload): - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_FOLLOWUP, - workflow_action=cls.parse_followup_action( - metadata={**payload, "followup_context_present": True}, - canonical_intent=str(payload.get("intent_name") or INTENT_FOLLOWUP_CONTINUE), - message_text=text, - ), - target_scope="thread", - operation_codes=[ROUTE_FOLLOWUP_CONTINUE], - requires_confirmation=False, - confidence=0.84, - slots=[ - slot - for slot in [ - workflow_slot( - "target_work_item_id", - int(FOLLOWUP_WORK_ITEM_ID_RE.search(text).group(1)) - if FOLLOWUP_WORK_ITEM_ID_RE.search(text) - else payload.get("target_work_item_id"), - ) - ] - if slot is not None - ], - ) - ) - else: - project_like = cls._looks_like_project_create(text) - signal_source_like = cls.looks_like_signal_source_request(text) - task_like = cls._looks_like_task_create(text) - signal_like = cls._looks_like_signal_create(text) - question_like = cls._has_question_structure(text) - hold_requirement_discussion = cls._should_keep_requirement_discussion_in_query(text) - deferred_project_confirm = any(token in normalized for token in PROJECT_CREATE_DEFERRED_CONFIRM_HINTS) - if hold_requirement_discussion: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_QUERY, - workflow_action="answer", - target_scope="global", - operation_codes=[ROUTE_QUERY_ANSWER], - requires_confirmation=False, - confidence=0.88 if question_like else 0.84, - metadata=dict(query_scene_metadata), - ) - ) - if project_like and not hold_requirement_discussion: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_NEW_PROJECT, - workflow_action="create", - target_scope="global", - operation_codes=["project.create"], - requires_confirmation=deferred_project_confirm, - confidence=0.9, - ) - ) - if signal_source_like: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_SIGNAL_SOURCE, - workflow_action="onboard", - target_scope="project" if project_like else "global", - operation_codes=[ROUTE_SIGNAL_SOURCE_ONBOARD], - requires_confirmation=False, - confidence=0.92, - ) - ) - if task_like and not project_like: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_TASK, - workflow_action="create", - target_scope="project", - operation_codes=[ROUTE_TASK_CREATE], - requires_confirmation=False, - confidence=0.82, - ) - ) - if signal_like and not signal_source_like: - workflow_action = "discover" if any(token in normalized for token in PROJECT_SIGNAL_DISCOVER_HINTS) else "create" - route_code = ROUTE_PROJECT_SIGNAL_DISCOVER if workflow_action == "discover" else ROUTE_PROJECT_SIGNAL_CREATE - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_PROJECT_SIGNAL, - workflow_action=workflow_action, - target_scope="project", - operation_codes=[route_code], - requires_confirmation=workflow_action == "discover", - confidence=0.78, - ) - ) - if not workflows: - workflows.append( - WorkflowPlan( - workflow_type=WORKFLOW_QUERY if question_like or not any(token in normalized for token in cls._WRITE_VERBS) else WORKFLOW_QUERY, - workflow_action="answer", - target_scope="global", - operation_codes=[ROUTE_QUERY_ANSWER], - requires_confirmation=False, - confidence=0.88 if question_like else 0.6, - metadata=dict(query_scene_metadata), - ) - ) - primary_workflow = workflows[0].workflow_type - return WorkflowBundle( - bundle_id=cls._bundle_id(source_system=source_system, message_text=text, workflows=workflows), - semantic_version=SEMANTIC_VERSION, - source_system=str(source_system or "").strip().lower() or "unknown", - primary_workflow=primary_workflow, - workflows=workflows, - message_gate=cls._gate_for_workflows(workflows), - reply_contract=cls._reply_contract(source_system=source_system, primary_workflow=primary_workflow), - diagnostics=cls._build_query_bundle_diagnostics(scene_metadata=query_scene_metadata), - ) - - @classmethod - def plan_from_intent( - cls, - *, - intent_name: str, - message_text: str, - source_system: str, - workflow_route: str = "", - project_id: Optional[int] = None, - metadata: Optional[dict[str, Any]] = None, - ) -> WorkflowBundle: - return cls._plan_from_intent( - intent_name=intent_name, - message_text=message_text, - source_system=source_system, - workflow_route=workflow_route, - project_id=project_id, - metadata=metadata, - ) + raise RuntimeError("WorkflowSemanticPlanner.plan_message cannot run inside an active event loop; use aplan_message") @classmethod def deserialize_bundle(cls, value: Any) -> Optional[WorkflowBundle]: @@ -1109,9 +268,19 @@ class WorkflowSemanticPlanner: requires_confirmation=bool(item.get("requires_confirmation")), confidence=float(item.get("confidence") or 0.0), slots=[ - workflow_slot(str(slot.get("name") or "").strip(), slot.get("value"), source=str(slot.get("source") or "semantic_planner")) + workflow_slot( + str(slot.get("name") or "").strip(), + slot.get("value"), + source=str(slot.get("source") or "semantic_planner"), + ) for slot in (slots_raw if isinstance(slots_raw, list) else []) - if isinstance(slot, dict) and workflow_slot(str(slot.get("name") or "").strip(), slot.get("value"), source=str(slot.get("source") or "semantic_planner")) is not None + if isinstance(slot, dict) + and workflow_slot( + str(slot.get("name") or "").strip(), + slot.get("value"), + source=str(slot.get("source") or "semantic_planner"), + ) + is not None ], clarification_needed=bool(item.get("clarification_needed")), metadata=dict(item.get("metadata") or {}), @@ -1132,62 +301,5 @@ class WorkflowSemanticPlanner: diagnostics=dict(value.get("diagnostics") or {}), ) - @classmethod - def classify_scene(cls, *, capture_result: dict[str, Any]) -> str: - bundle = cls.deserialize_bundle(capture_result.get("semantic_bundle")) - if bundle is not None and bundle.workflows: - if any(item.workflow_type != WORKFLOW_QUERY for item in bundle.workflows): - return "execute" - return "qa" - status = str(capture_result.get("status") or "").strip().lower() - classification = str(capture_result.get("classification") or "").strip().lower() - intent = str(capture_result.get("intent") or "").strip().lower() - workflow_route = str(capture_result.get("workflow_route") or "").strip().lower() - execute_intents = { - INTENT_PROJECT_CREATE, - INTENT_PROJECT_UPDATE, - INTENT_PROJECT_ARCHIVE, - INTENT_PROJECT_REOPEN, - INTENT_PROJECT_DELETE, - INTENT_TASK_CREATE, - INTENT_TASK_UPDATE, - INTENT_TASK_ASSIGN, - INTENT_TASK_PRIORITIZE, - INTENT_TASK_STATUS_TRANSITION, - INTENT_TASK_DELETE, - INTENT_SIGNAL_CREATE, - INTENT_SIGNAL_SOURCE_ONBOARD, - INTENT_FOLLOWUP_CONFIRM, - INTENT_FOLLOWUP_CONTINUE, - } - execute_routes = { - ROUTE_PROJECT_CREATE, - ROUTE_TASK_CREATE, - ROUTE_SIGNAL_SOURCE_ONBOARD, - ROUTE_PROJECT_SIGNAL_CREATE, - ROUTE_PROJECT_SIGNAL_DISCOVER, - ROUTE_PROJECT_SIGNAL_COMMIT, - ROUTE_PROJECT_SIGNAL_CANCEL, - ROUTE_PROJECT_SIGNAL_REFRESH, - ROUTE_FOLLOWUP_CONFIRM, - ROUTE_FOLLOWUP_CONTINUE, - } - if intent in execute_intents or workflow_route in execute_routes or classification in {"project", "signal", "command", "new_project", "project_signal"}: - return "execute" - if status == "error" or classification == "error": - return "error" - if status == "answered" or classification in {"question", "qa"}: - return "qa" - if status in {"captured", "ignored"}: - return "execute" - return "qa" - - @classmethod - def is_action_request_capture(cls, *, capture_result: dict[str, Any]) -> bool: - bundle = cls.deserialize_bundle(capture_result.get("semantic_bundle")) - if bundle is not None and bundle.workflows: - return any(item.workflow_type != WORKFLOW_QUERY for item in bundle.workflows) - return False - __all__ = ["WorkflowSemanticPlanner"] diff --git a/backend/app/services/integrations/feishu_integration_service/mixin_receive_routing.py b/backend/app/services/integrations/feishu_integration_service/mixin_receive_routing.py index 07c578f9..b86f5553 100644 --- a/backend/app/services/integrations/feishu_integration_service/mixin_receive_routing.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_receive_routing.py @@ -1,7 +1,6 @@ from __future__ import annotations from .shared import * -from ...ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner class FeishuReceiveRoutingMixin: async def _route_scene_reply( @@ -152,7 +151,27 @@ class FeishuReceiveRoutingMixin: if not isinstance(capture_result, dict): return "error" _ = message_content - return WorkflowSemanticPlanner.classify_scene(capture_result=capture_result) + semantic_bundle = capture_result.get("semantic_bundle") + if isinstance(semantic_bundle, dict): + workflows = semantic_bundle.get("workflows") + if isinstance(workflows, list) and workflows: + workflow_types = { + str(item.get("workflow_type") or "").strip() + for item in workflows + if isinstance(item, dict) + } + if any(workflow_type and workflow_type != "query_flow" for workflow_type in workflow_types): + return "execute" + return "qa" + status = str(capture_result.get("status") or "").strip().lower() + classification = str(capture_result.get("classification") or "").strip().lower() + if status == "error" or classification == "error": + return "error" + if classification in {"project", "signal", "command", "new_project", "project_signal"}: + return "execute" + if status in {"captured", "ignored"}: + return "execute" + return "qa" @staticmethod def _build_error_reply_text() -> str: diff --git a/backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py b/backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py index 0016e5d3..ce3111f3 100644 --- a/backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py @@ -1,7 +1,6 @@ from __future__ import annotations from .shared import * -from ...ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner class FeishuReplyLLMCoreMixin: async def _reply_with_active_role_prompt( @@ -121,7 +120,7 @@ class FeishuReplyLLMCoreMixin: capture_result, reason="模型返回空内容", ) - elif self._looks_like_provider_error(reply_text): + elif self._is_provider_error(reply_text): reply_text = self._build_llm_failure_reply_text( capture_result, reason=reply_text, @@ -228,7 +227,17 @@ class FeishuReplyLLMCoreMixin: *, capture_result: Dict[str, Any], ) -> bool: - return WorkflowSemanticPlanner.is_action_request_capture(capture_result=capture_result) + semantic_bundle = capture_result.get("semantic_bundle") + if not isinstance(semantic_bundle, dict): + return False + workflows = semantic_bundle.get("workflows") + if not isinstance(workflows, list) or not workflows: + return False + return any( + str(item.get("workflow_type") or "").strip() not in {"", "query_flow"} + for item in workflows + if isinstance(item, dict) + ) @staticmethod def _build_consultant_dispatch_reply(capture_result: Dict[str, Any]) -> Optional[str]: @@ -245,8 +254,13 @@ class FeishuReplyLLMCoreMixin: _ = message_content if not cls._is_action_request_message(capture_result=capture_result): return None - bundle = WorkflowSemanticPlanner.deserialize_bundle(capture_result.get("semantic_bundle")) - workflow_types = [item.workflow_type for item in (bundle.workflows if bundle is not None else [])] + semantic_bundle = capture_result.get("semantic_bundle") + workflows = semantic_bundle.get("workflows") if isinstance(semantic_bundle, dict) else [] + workflow_types = [ + str(item.get("workflow_type") or "").strip() + for item in workflows + if isinstance(item, dict) + ] project_like = "new_project_flow" in workflow_types or "project_flow" in workflow_types signal_like = "signal_source_flow" in workflow_types or "project_signal_flow" in workflow_types if project_like and signal_like: @@ -283,7 +297,7 @@ class FeishuReplyLLMCoreMixin: return text @staticmethod - def _looks_like_provider_error(text: str) -> bool: + def _is_provider_error(text: str) -> bool: lowered = str(text or "").strip().lower() if not lowered: return False diff --git a/backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py b/backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py index 891b1d5a..708c43b4 100644 --- a/backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py @@ -4,6 +4,7 @@ from .shared import * class FeishuReplyOutboxMixin: _CARD_MESSAGE_PREFIX = "__pmflow_feishu_card__:" + _PLAIN_TEXT_MESSAGE_LIMIT = 12000 @classmethod def _is_card_outbox_message(cls, value: Optional[str]) -> bool: @@ -26,6 +27,31 @@ class FeishuReplyOutboxMixin: return parsed return None + @staticmethod + def _is_requirement_plan_reply_text(value: Optional[str]) -> bool: + text = str(value or "") + return any(marker in text for marker in ("A. ", "B. ", "C. ", "⭐ 当前我更推荐:")) + + @classmethod + def _guard_requirement_plan_reply_integrity(cls, value: Optional[str]) -> None: + text = str(value or "").strip() + if not cls._is_requirement_plan_reply_text(text): + return + required_markers = ("A. ", "B. ", "C. ", "⭐ 当前我更推荐:") + if any(marker not in text for marker in required_markers): + raise ValueError("incomplete_requirement_plan_reply") + + @classmethod + def _normalize_reply_outbox_message_text(cls, message_text: Optional[str]) -> str: + raw_text = str(message_text or "") + if cls._is_card_outbox_message(raw_text): + return raw_text[:12000] + normalized = raw_text.strip() + cls._guard_requirement_plan_reply_integrity(normalized) + if len(normalized) > cls._PLAIN_TEXT_MESSAGE_LIMIT and cls._is_requirement_plan_reply_text(normalized): + raise ValueError("requirement_plan_reply_exceeds_plain_text_outbox_limit") + return normalized[: cls._PLAIN_TEXT_MESSAGE_LIMIT] or "已收到,正在处理。" + async def _safe_reply_text( self, *, @@ -116,10 +142,7 @@ class FeishuReplyOutboxMixin: ) -> int: async with AsyncSessionLocal() as db: normalized_source_message_id = (source_message_id or "")[:128] or None - if self._is_card_outbox_message(message_text): - normalized_message_text = (message_text or "")[:12000] - else: - normalized_message_text = (message_text or "")[:2000] or "已收到,正在处理。" + normalized_message_text = self._normalize_reply_outbox_message_text(message_text) reply_hash = self._build_reply_hash( source_message_id=normalized_source_message_id, message_text=normalized_message_text, @@ -382,8 +405,8 @@ class FeishuReplyOutboxMixin: text = text.replace("```", "").strip() if text.lower().startswith("json"): text = text[4:].strip() - looks_like_provider_error = getattr(cls, "_looks_like_provider_error", None) - has_provider_error = callable(looks_like_provider_error) and bool(looks_like_provider_error(text)) + provider_error_detector = getattr(cls, "_is_provider_error", None) + has_provider_error = callable(provider_error_detector) and bool(provider_error_detector(text)) if not text or has_provider_error: error_builder = getattr(cls, "_build_error_reply_text", None) if callable(error_builder): diff --git a/backend/app/services/integrations/feishu_outbox/service.py b/backend/app/services/integrations/feishu_outbox/service.py index 7a3614c9..2ac0b38e 100644 --- a/backend/app/services/integrations/feishu_outbox/service.py +++ b/backend/app/services/integrations/feishu_outbox/service.py @@ -14,6 +14,7 @@ from ....models import FeishuReplyOutbox class FeishuOutboxService: """复用 reply_hash 幂等策略的 outbox 写入入口。""" + _PLAIN_TEXT_MESSAGE_LIMIT = 12000 @staticmethod def _build_reply_hash(*, source_message_id: Optional[str], message_text: str) -> Optional[str]: @@ -24,6 +25,28 @@ class FeishuOutboxService: raw = f"{source}{body}".encode("utf-8") return hashlib.md5(raw, usedforsecurity=False).hexdigest() + @staticmethod + def _is_requirement_plan_reply_text(value: Optional[str]) -> bool: + text = str(value or "") + return any(marker in text for marker in ("A. ", "B. ", "C. ", "⭐ 当前我更推荐:")) + + @classmethod + def _guard_requirement_plan_reply_integrity(cls, value: Optional[str]) -> None: + text = str(value or "").strip() + if not cls._is_requirement_plan_reply_text(text): + return + required_markers = ("A. ", "B. ", "C. ", "⭐ 当前我更推荐:") + if any(marker not in text for marker in required_markers): + raise ValueError("incomplete_requirement_plan_reply") + + @classmethod + def _normalize_outbox_text(cls, value: Optional[str]) -> str: + normalized = str(value or "").strip() + cls._guard_requirement_plan_reply_integrity(normalized) + if len(normalized) > cls._PLAIN_TEXT_MESSAGE_LIMIT and cls._is_requirement_plan_reply_text(normalized): + raise ValueError("requirement_plan_reply_exceeds_plain_text_outbox_limit") + return normalized[: cls._PLAIN_TEXT_MESSAGE_LIMIT] or "已收到,正在处理。" + @classmethod async def enqueue( cls, @@ -39,7 +62,7 @@ class FeishuOutboxService: normalized_chat_id = str(chat_id or "").strip()[:128] if not normalized_chat_id: return None - normalized_text = str(text or "").strip()[:2000] or "已收到,正在处理。" + normalized_text = cls._normalize_outbox_text(text) normalized_source_message_id = (str(source_message_id or "").strip()[:128] or None) hash_source = normalized_source_message_id or (str(dedupe_seed or "").strip()[:128] or None) reply_hash = cls._build_reply_hash(source_message_id=hash_source, message_text=normalized_text) diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py index 16ec94cb..2c95d78f 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py @@ -6,7 +6,7 @@ from .shared import * class FeishuPipelineContextPrefillAMixin: @staticmethod - def _looks_like_placeholder_project_text(text: str) -> bool: + def _is_placeholder_project_text(text: str) -> bool: normalized = re.sub(r"\s+", "", str(text or "")).lower() if not normalized: return True @@ -33,7 +33,7 @@ class FeishuPipelineContextPrefillAMixin: ) @staticmethod - def _looks_like_project_request_artifact(text: str) -> bool: # semantic-lint: ignore - project context artifact detector, not workflow routing + def _is_project_request_artifact(text: str) -> bool: normalized = re.sub(r"\s+", "", str(text or "")).lower() if not normalized: return False @@ -60,7 +60,7 @@ class FeishuPipelineContextPrefillAMixin: ) @staticmethod - def _looks_like_unusable_project_context(text: str) -> bool: + def _is_unusable_project_context(text: str) -> bool: normalized = re.sub(r"\s+", "", str(text or "")).lower() if not normalized: return True @@ -209,7 +209,7 @@ class FeishuPipelineContextPrefillAMixin: text = self._strip_feishu_mention_prefix(str(row.message_text or "")) if not text: continue - if self._looks_like_unusable_project_context(text): + if self._is_unusable_project_context(text): continue return text[:4000] return "" @@ -224,19 +224,19 @@ class FeishuPipelineContextPrefillAMixin: return fields embedded_context = self._extract_embedded_project_context(message) - if not embedded_context or self._looks_like_unusable_project_context(embedded_context): + if not embedded_context or self._is_unusable_project_context(embedded_context): return fields current_description = str(fields.get("description") or "").strip() if ( - self._looks_like_placeholder_project_text(current_description) - or self._looks_like_project_request_artifact(current_description) + self._is_placeholder_project_text(current_description) + or self._is_project_request_artifact(current_description) ): current_description = "" current_scope = str(fields.get("project_scope") or "").strip() if ( - self._looks_like_placeholder_project_text(current_scope) - or self._looks_like_project_request_artifact(current_scope) + self._is_placeholder_project_text(current_scope) + or self._is_project_request_artifact(current_scope) ): current_scope = "" @@ -258,11 +258,11 @@ class FeishuPipelineContextPrefillAMixin: suggested_name = normalize_project_title_candidate( str(project_info.get("suggested_name") or "").strip() ) - if self._looks_like_placeholder_project_text(suggested_name): + if self._is_placeholder_project_text(suggested_name): suggested_name = "" title = title or suggested_name description = description or str(project_info.get("suggested_description") or "").strip()[:4000] - if self._looks_like_unusable_project_context(description): + if self._is_unusable_project_context(description): description = "" project_scope = project_scope or self._extract_project_scope(embedded_context, description=description) diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_b.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_b.py index 02edf566..7f4d3017 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_b.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_b.py @@ -18,7 +18,7 @@ class FeishuPipelineContextPrefillBMixin: ) -> dict[str, Any]: if fields.get("ready"): return fields - if not source_chat_id or not self._looks_like_contextual_project_request(message): + if not source_chat_id or not self._is_contextual_project_request(message): return fields turns = await self._load_recent_chat_turns( @@ -39,7 +39,7 @@ class FeishuPipelineContextPrefillBMixin: if recent_question: context_blocks.append(f"最近用户问题:{recent_question}") question_hint = recent_question - if recent_answer and not self._looks_like_unusable_project_context(recent_answer): + if recent_answer and not self._is_unusable_project_context(recent_answer): context_blocks.append(f"最近系统回答:{recent_answer}") answer_hint = recent_answer @@ -48,8 +48,8 @@ class FeishuPipelineContextPrefillBMixin: base_description = str(fields.get("description") or "").strip() if ( - self._looks_like_placeholder_project_text(base_description) - or self._looks_like_project_request_artifact(base_description) + self._is_placeholder_project_text(base_description) + or self._is_project_request_artifact(base_description) ): base_description = "" if not answer_hint and not base_description: @@ -59,8 +59,8 @@ class FeishuPipelineContextPrefillBMixin: heuristic_description = base_description or answer_hint[:4000] heuristic_scope = str(fields.get("project_scope") or "").strip() if ( - self._looks_like_placeholder_project_text(heuristic_scope) - or self._looks_like_project_request_artifact(heuristic_scope) + self._is_placeholder_project_text(heuristic_scope) + or self._is_project_request_artifact(heuristic_scope) ): heuristic_scope = "" if not heuristic_scope and heuristic_description: @@ -85,14 +85,14 @@ class FeishuPipelineContextPrefillBMixin: project_info = await self._extract_project_prefill(prefill_source) current_description = str(fields.get("description") or "").strip() if ( - self._looks_like_placeholder_project_text(current_description) - or self._looks_like_project_request_artifact(current_description) + self._is_placeholder_project_text(current_description) + or self._is_project_request_artifact(current_description) ): current_description = "" current_scope = str(fields.get("project_scope") or "").strip() if ( - self._looks_like_placeholder_project_text(current_scope) - or self._looks_like_project_request_artifact(current_scope) + self._is_placeholder_project_text(current_scope) + or self._is_project_request_artifact(current_scope) ): current_scope = "" title = ( @@ -103,7 +103,7 @@ class FeishuPipelineContextPrefillBMixin: description = current_description or str(project_info.get("suggested_description") or "").strip() if not description and answer_hint: description = answer_hint[:4000] - if self._looks_like_unusable_project_context(description): + if self._is_unusable_project_context(description): description = "" project_scope = current_scope if not project_scope: @@ -190,12 +190,12 @@ class FeishuPipelineContextPrefillBMixin: project_scope: str, ) -> str: normalized_title = normalize_project_title_candidate(title) - if normalized_title and not cls._looks_like_placeholder_project_text(normalized_title): + if normalized_title and not cls._is_placeholder_project_text(normalized_title): return normalized_title for candidate in (project_scope, description): cleaned = str(candidate or "").strip() - if not cleaned or cls._looks_like_placeholder_project_text(cleaned): + if not cleaned or cls._is_placeholder_project_text(cleaned): continue normalized = normalize_project_title_candidate(cleaned[:48]) if normalized: @@ -212,10 +212,10 @@ class FeishuPipelineContextPrefillBMixin: fallback_text: str, ) -> str: normalized_description = str(description or "").strip() - if cls._looks_like_placeholder_project_text(normalized_description): + if cls._is_placeholder_project_text(normalized_description): normalized_description = "" normalized_scope = str(project_scope or "").strip() - if cls._looks_like_placeholder_project_text(normalized_scope): + if cls._is_placeholder_project_text(normalized_scope): normalized_scope = "" fragments: list[str] = [] diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_core_base.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_core_base.py index 88a40231..e358ac02 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_core_base.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_core_base.py @@ -2,7 +2,6 @@ from __future__ import annotations from .shared import * -from ...ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner class FeishuPipelineCoreBaseMixin: @@ -34,32 +33,12 @@ class FeishuPipelineCoreBaseMixin: @staticmethod def _is_healthcheck_message(message: str) -> bool: - return WorkflowSemanticPlanner.is_healthcheck_message(message) - - @staticmethod - def _is_short_confirmation_message(message: str) -> bool: - return WorkflowSemanticPlanner.is_short_confirmation_noise(message) + return bool(HEALTHCHECK_RE.search(str(message or "").strip())) @staticmethod def _is_correction_message(message: str) -> bool: return bool(CORRECTION_CUE_RE.search(str(message or "").strip())) - @staticmethod - def _looks_like_continue_followup(message: str) -> bool: - return WorkflowSemanticPlanner.wants_followup_continue(message) - - @staticmethod - def _looks_like_task_create_request(message: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - return WorkflowSemanticPlanner.looks_like_task_create_request(message) - - @staticmethod - def _looks_like_project_create_request(message: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - return WorkflowSemanticPlanner.looks_like_project_create_request(message) - - @staticmethod - def _looks_like_external_connect_request(message: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - return WorkflowSemanticPlanner.looks_like_external_connect_request(message) - @staticmethod def _can_execute_high_value_action(user: Optional[User]) -> bool: if user is None: diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_gate_core.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_gate_core.py index 46ea2629..5e0b5f4a 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_gate_core.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_gate_core.py @@ -2,6 +2,7 @@ from __future__ import annotations from .shared import * +from ...ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner class FeishuPipelineIntentGateCoreMixin: @@ -27,9 +28,23 @@ class FeishuPipelineIntentGateCoreMixin: "correction_match": None, } - explicit_project_request = self._looks_like_project_create_request(text) - explicit_task_request = self._looks_like_task_create_request(text) - explicit_external_request = self._looks_like_external_connect_request(text) + semantic_bundle = await WorkflowSemanticPlanner.aplan_message( + message_text=text, + source_system="feishu_signal_pipeline", + metadata={ + "project_id": inferred_project_id, + "source_chat_id": source_chat_id, + "source_sender_open_id": source_sender_open_id, + "sender": sender, + "source_message_id": source_message_id, + "thread_context": dict(thread_context or {}), + }, + ) + workflow_types = {str(item.workflow_type or "").strip().lower() for item in semantic_bundle.workflows} + query_only = bool(semantic_bundle.workflows) and workflow_types == {"query_flow"} + explicit_project_request = "new_project_flow" in workflow_types + explicit_task_request = "task_flow" in workflow_types + explicit_external_request = "signal_source_flow" in workflow_types correction_match: Optional[dict[str, Any]] = None if self.db is not None and len(text) >= 4: try: @@ -76,16 +91,6 @@ class FeishuPipelineIntentGateCoreMixin: "fields": {}, "correction_match": correction_match, } - if self._is_short_confirmation_message(text): - return { - "intent": INTENT_SMALLTALK, - "canonical_action": None, - "clarification_template": "", - "risk_level": "low", - "fields": {}, - "correction_match": correction_match, - } - if ( correction_match and float(correction_match.get("score") or 0.0) >= 0.84 @@ -175,17 +180,7 @@ class FeishuPipelineIntentGateCoreMixin: "correction_match": correction_match, } - if self._is_query_like_question(text): - return { - "intent": INTENT_QUERY, - "canonical_action": None, - "clarification_template": "", - "risk_level": "low", - "fields": {}, - "correction_match": correction_match, - } - - if self._is_obvious_question(text): + if query_only: return { "intent": INTENT_QUERY, "canonical_action": None, @@ -193,6 +188,7 @@ class FeishuPipelineIntentGateCoreMixin: "risk_level": "low", "fields": {}, "correction_match": correction_match, + "semantic_bundle": semantic_bundle.to_dict(), } return { @@ -257,7 +253,7 @@ class FeishuPipelineIntentGateCoreMixin: } @staticmethod - def _looks_like_contextual_project_request(message: str) -> bool: # semantic-lint: ignore - context prefill helper for missing fields only + def _is_contextual_project_request(message: str) -> bool: normalized = re.sub(r"\s+", "", str(message or "")).lower() if not normalized: return False diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_lookup_and_block.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_lookup_and_block.py index 879649fc..ee09bb0a 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_lookup_and_block.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_lookup_and_block.py @@ -2,21 +2,50 @@ from __future__ import annotations from .shared import * -from ...ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner class FeishuPipelineLookupAndBlockMixin: @staticmethod - def _looks_like_project_create_command(message: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - return WorkflowSemanticPlanner.looks_like_project_create_request(message) + def _extract_project_lookup_keyword(message: str) -> str: + text = str(message or "").strip() + if not text: + return "" + text = text.replace("?", "?") + + direct_patterns = [ + r"(?:是否有|有没有|有无)([\u4e00-\u9fffA-Za-z0-9_-]{1,24})(?:项目|系统)?", + r"(?:是否存在|存在)([\u4e00-\u9fffA-Za-z0-9_-]{1,24})(?:项目|系统)?", + ] + for pattern in direct_patterns: + match = re.search(pattern, text) + if not match: + continue + value = str(match.group(1) or "").strip(" ,。,.!??::;;“”\"'()()") + value = QUESTION_NOISE_RE.sub("", value).strip() + if len(value) >= 2: + return value[:24] + + anchored_matches = list(re.finditer(r"([\u4e00-\u9fffA-Za-z0-9_-]{1,24})(?:项目|系统)", text)) + for match in reversed(anchored_matches): + value = str(match.group(1) or "").strip(" ,。,.!??::;;“”\"'()()") + value = QUESTION_NOISE_RE.sub("", value).strip() + if len(value) >= 2: + return value[:24] + + return "" @classmethod - def _looks_like_project_lookup_question(cls, message: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - return WorkflowSemanticPlanner.looks_like_project_lookup_question(message) - - @classmethod - def _is_legacy_business_command(cls, message: str) -> bool: - return WorkflowSemanticPlanner.is_legacy_business_command(message) + def _is_project_lookup_question(cls, message: str) -> bool: + raw = str(message or "").strip() + if not raw: + return False + if PROJECT_ID_QUERY_RE.search(raw) or PROJECT_LIST_QUERY_RE.search(raw): + return True + if "项目" not in raw and "系统" not in raw: + return False + if "?" not in raw and "?" not in raw and not any(token in raw for token in ("有没有", "是否有", "有无", "存在", "列表", "清单")): + return False + return bool(cls._extract_project_lookup_keyword(raw)) async def _maybe_block_legacy_command_request( self, @@ -31,16 +60,18 @@ class FeishuPipelineLookupAndBlockMixin: projects: list[dict[str, Any]], process_started_at: float, ) -> Optional[dict[str, Any]]: - if not self._is_legacy_business_command(message): - return None - - _ = sender, source_chat_id, source_sender_open_id, captured_at, inferred_project_id, projects - return await self._build_legacy_command_blocked_capture( - message=message, - feishu_message_id=feishu_message_id, - duration_ms=int((time.perf_counter() - process_started_at) * 1000), - reason="旧 command-first 规划链路已下线,请改用 GLM 分析后直接调用显式接口。", + _ = ( + message, + sender, + source_chat_id, + source_sender_open_id, + feishu_message_id, + captured_at, + inferred_project_id, + projects, + process_started_at, ) + return None async def _maybe_answer_project_lookup_question( self, @@ -50,7 +81,7 @@ class FeishuPipelineLookupAndBlockMixin: process_started_at: float, projects: list[dict[str, Any]], ) -> Optional[dict[str, Any]]: - if not self._looks_like_project_lookup_question(message): + if not self._is_project_lookup_question(message): return None linked_project_ids: list[int] = [] @@ -71,7 +102,7 @@ class FeishuPipelineLookupAndBlockMixin: reply_text = self._format_project_lookup_hit_rows(projects[:8], prefix="当前项目") linked_project_ids = [int(item["id"]) for item in projects[:8] if _safe_int(item.get("id"))] else: - keyword = WorkflowSemanticPlanner.extract_project_lookup_keyword(message) + keyword = self._extract_project_lookup_keyword(message) matched_rows = self._match_projects_by_keyword(projects=projects, keyword=keyword) if matched_rows: linked_project_ids = [int(item["id"]) for item in matched_rows if _safe_int(item.get("id"))] @@ -195,9 +226,7 @@ class FeishuPipelineLookupAndBlockMixin: return True if cls._is_healthcheck_message(raw_message): return True - if cls._is_short_confirmation_message(raw_message): - return True - if cls._looks_like_project_lookup_question(raw_message): + if cls._is_project_lookup_question(raw_message): return True if "长连接" in normalized and any(token in normalized for token in ("已通", "都已通", "正常", "在线", "已恢复")): return True diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py index 1a5b69ac..56bc9795 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py @@ -35,17 +35,14 @@ class FeishuPipelineProcessMessageDefaultMixin: intent_for_response = "statement" force_signal_fastpath = False rag_question_part = message + intent_result = await rag_service.classify_intent(message) + intent = str(intent_result.get("intent") or "statement").lower() + intent_for_response = intent + rag_question_part = str(intent_result.get("question_part") or "").strip() or message + rag_statement_part = str(intent_result.get("statement_part") or "").strip() - if self._is_obvious_question(message): - intent_result: dict[str, Any] = {} - try: - intent_result = await rag_service.classify_intent(message) - except Exception: - intent_result = {} - intent = str(intent_result.get("intent") or "question").lower() - rag_question_part = str(intent_result.get("question_part") or "").strip() or message - rag_statement_part = str(intent_result.get("statement_part") or "").strip() - if intent == "mixed" or self._is_requirement_like_question(message): + if intent == "question": + if self._is_requirement_like_question(message): mixed_signal_part = ( rag_statement_part or self._convert_requirement_question_to_signal_text( @@ -88,89 +85,34 @@ class FeishuPipelineProcessMessageDefaultMixin: inferred_project_id=None, feishu_message_id=feishu_message_id, process_started_at=process_started_at, - route_reason="明显问句直接走 RAG 直答通道,不进入决策队列。", - intent_label="question_obvious", + route_reason="识别为问答消息,已走 RAG 直答通道,不进入决策队列。", + intent_label=intent, normalized_chat_type=normalized_chat_type, ) - else: - intent_result = await rag_service.classify_intent(message) - intent = str(intent_result.get("intent") or "statement").lower() - intent_for_response = intent - rag_question_part = str(intent_result.get("question_part") or "").strip() or message - rag_statement_part = str(intent_result.get("statement_part") or "").strip() - if intent == "question": - if self._is_requirement_like_question(message): - mixed_signal_part = ( - rag_statement_part - or self._convert_requirement_question_to_signal_text( - message=message, - fallback_content=message_content, - ) - ) - mixed_rag_result = await rag_service.answer_question( - question=rag_question_part, - asked_by=None, - asked_via="feishu", - project_id=None, - source_chat_id=source_chat_id, - source_message_id=feishu_message_id, - conversation_key=conversation_key, - source_sender_open_id=source_sender_open_id, - thread_context=thread_context, - message_intent="mixed", - mixed_signal_part=mixed_signal_part, - ) - sidecar_rag_result = mixed_rag_result - intent_for_response = "question_requirement" - message = mixed_signal_part - force_signal_fastpath = True - else: - rag_result = await rag_service.answer_question( - question=rag_question_part, - asked_by=None, - asked_via="feishu", - project_id=None, - source_chat_id=source_chat_id, - source_message_id=feishu_message_id, - conversation_key=conversation_key, - source_sender_open_id=source_sender_open_id, - thread_context=thread_context, - ) - return await self._finalize_direct_qa_capture_result( - message=message, - rag_result=rag_result, - inferred_project_id=None, - feishu_message_id=feishu_message_id, - process_started_at=process_started_at, - route_reason="识别为问答消息,已走 RAG 直答通道,不进入决策队列。", - intent_label=intent, - normalized_chat_type=normalized_chat_type, - ) - - if intent == "mixed": - mixed_signal_part = ( - rag_statement_part - or self._convert_requirement_question_to_signal_text( - message=message, - fallback_content=message_content, - ) + if intent == "mixed": + mixed_signal_part = ( + rag_statement_part + or self._convert_requirement_question_to_signal_text( + message=message, + fallback_content=message_content, ) - mixed_rag_result = await rag_service.answer_question( - question=rag_question_part, - asked_by=None, - asked_via="feishu", - project_id=None, - source_chat_id=source_chat_id, - source_message_id=feishu_message_id, - conversation_key=conversation_key, - source_sender_open_id=source_sender_open_id, - thread_context=thread_context, - message_intent="mixed", - mixed_signal_part=mixed_signal_part, - ) - message = mixed_signal_part - sidecar_rag_result = mixed_rag_result + ) + mixed_rag_result = await rag_service.answer_question( + question=rag_question_part, + asked_by=None, + asked_via="feishu", + project_id=None, + source_chat_id=source_chat_id, + source_message_id=feishu_message_id, + conversation_key=conversation_key, + source_sender_open_id=source_sender_open_id, + thread_context=thread_context, + message_intent="mixed", + mixed_signal_part=mixed_signal_part, + ) + message = mixed_signal_part + sidecar_rag_result = mixed_rag_result try: if force_signal_fastpath: diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_a.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_a.py index 270593ca..0345a611 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_a.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_a.py @@ -2,7 +2,6 @@ from __future__ import annotations from .shared import * -from ...ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner class FeishuPipelineProcessMessageHelpersAMixin: @@ -175,11 +174,3 @@ class FeishuPipelineProcessMessageHelpersAMixin: text = re.sub(r"\n{2,}", "\n", text) text = re.sub(r"\s+", " ", text).strip() return text[:2000] - - @classmethod - def _is_obvious_question(cls, message: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - return WorkflowSemanticPlanner.is_obvious_question(message) - - @classmethod - def _is_query_like_question(cls, message: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - return WorkflowSemanticPlanner.is_query_like_question(message) diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_serialization.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_serialization.py index e7d152a4..c276d15b 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_serialization.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_serialization.py @@ -68,9 +68,9 @@ class FeishuPipelineSerializationMixin: "ready": False, } if ( - self._looks_like_placeholder_project_text(project_fields["title"]) - or self._looks_like_placeholder_project_text(project_fields["description"]) - or self._looks_like_placeholder_project_text(project_fields["project_scope"]) + self._is_placeholder_project_text(project_fields["title"]) + or self._is_placeholder_project_text(project_fields["description"]) + or self._is_placeholder_project_text(project_fields["project_scope"]) ): project_fields = await self._maybe_prefill_project_fields_from_context( message=str(item.raw_message or ""), diff --git a/backend/app/services/integrations/feishu_signal_pipeline/shared_core.py b/backend/app/services/integrations/feishu_signal_pipeline/shared_core.py index 380752dc..d743fa72 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/shared_core.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/shared_core.py @@ -41,33 +41,6 @@ from ...ai_pmo.ai_pmo_kernel import ( gate_execution as kernel_gate_execution, resolve_correction as kernel_resolve_correction, ) -from ...ai_pmo.workflow_semantic_planner.constants import ( - CORRECTION_CUE_RE, - CREATE_ACTION_RE, - EXTERNAL_INTEGRATION_RE, - FOLLOWUP_CONTINUE_RE, - FOLLOWUP_CREATE_CONTINUE_RE, - FOLLOWUP_REFERENCE_RE, - FOLLOWUP_STATUS_RE, - HEALTHCHECK_RE, - INDEPENDENT_REQUIREMENT_RE, - LEGACY_COMMAND_OBJECT_RE, - LEGACY_COMMAND_PROJECT_CREATE_RE, - LEGACY_COMMAND_VERB_RE, - OBVIOUS_QUESTION_RE, - OWNER_RE, - PROJECT_COMPLEXITY_RE, - PROJECT_ID_QUERY_RE, - PROJECT_INTENT_RE, - PROJECT_LIST_QUERY_RE, - QUERY_INTENT_RE, - QUESTION_REQUIREMENT_OBJECT_RE, - QUESTION_REQUIREMENT_VERB_RE, - SHORT_CONFIRM_RE, - SIGNAL_FACT_RE, - SIMPLE_SIGNAL_REQUEST_RE, - TIMELINE_RE, -) from ...context_cache_service import context_cache_service from ...notification_service import NotificationService from ...operation_log_service import log_operation diff --git a/backend/app/services/signal/draft_generator_service.py b/backend/app/services/signal/draft_generator_service.py index 48b9854d..06c20ee8 100644 --- a/backend/app/services/signal/draft_generator_service.py +++ b/backend/app/services/signal/draft_generator_service.py @@ -153,7 +153,7 @@ class DraftGeneratorService: max_tokens=max_tokens, error=str(exc), ) - if self._looks_like_provider_budget_rejection(exc): + if self._is_provider_budget_rejection(exc): break continue payload = extraction.payload if extraction is not None and isinstance(extraction.payload, dict) else {} @@ -199,7 +199,7 @@ class DraftGeneratorService: return ordered @staticmethod - def _looks_like_provider_budget_rejection(exc: Exception) -> bool: + def _is_provider_budget_rejection(exc: Exception) -> bool: lowered = str(exc or "").lower() return "400 bad request" in lowered and any( token in lowered diff --git a/backend/app/services/signal/signal_capture_service/mixin_analysis_normalize.py b/backend/app/services/signal/signal_capture_service/mixin_analysis_normalize.py index 48fb862c..77843667 100644 --- a/backend/app/services/signal/signal_capture_service/mixin_analysis_normalize.py +++ b/backend/app/services/signal/signal_capture_service/mixin_analysis_normalize.py @@ -204,26 +204,11 @@ class SignalCaptureAnalysisNormalizeMixin: if not content: return True normalized = re.sub(r"\s+", "", content) - if content in NON_REQUIREMENT_EXACT: - return True - if normalized in NON_REQUIREMENT_EXACT: - return True - if any(k in normalized for k in NON_REQUIREMENT_CONTAINS) and not any( - hint in normalized for hint in REQUIREMENT_HINTS - ): - return True if len(content) <= 2 and content.isdigit(): return True - non_requirement_keywords = [ - "收到", - "辛苦了", - "赞", - "哈哈", - "good", - "nice", - "ok", - ] - if len(content) <= 8 and any(k in content for k in non_requirement_keywords): + # Keep this structural-only: very short acknowledgements/noise can be dropped, + # but anything longer should go to the model/kernel instead of keyword heuristics. + if len(normalized) <= 3 and not any(token in content for token in ("?", "?", "项目", "需求", "任务", "信号")): return True if ONLY_PUNCT_OR_SPACE_RE.match(content): return True diff --git a/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py b/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py index fb9c1e36..2ee5392b 100644 --- a/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py +++ b/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py @@ -11,7 +11,7 @@ class SignalCaptureEntryMixin: self.llm = LLMService() @classmethod - def _should_process_feishu_high_accuracy( + async def _should_process_feishu_high_accuracy( cls, *, message_text: str, @@ -20,7 +20,7 @@ class SignalCaptureEntryMixin: if not clean_text: return False, "empty" - bundle = WorkflowSemanticPlanner.plan_message( + bundle = await WorkflowSemanticPlanner.aplan_message( message_text=clean_text, source_system="feishu", ) @@ -131,7 +131,7 @@ class SignalCaptureEntryMixin: should_sync_process = bool(force_sync) sync_reason = "force_sync" if should_sync_process else "" if not should_sync_process: - should_sync_process, sync_reason = self._should_process_feishu_high_accuracy( + should_sync_process, sync_reason = await self._should_process_feishu_high_accuracy( message_text=cleaned_message, ) @@ -145,21 +145,13 @@ class SignalCaptureEntryMixin: source_message_id=source_message_id, thread_context=thread_context, ) - should_sync_process = kernel._is_short_confirmation_message( - message_text=cleaned_message, - session=enqueue_session, - message_sent_at=message_sent_at, - session_snapshot=session_snapshot, - ) - if should_sync_process: - sync_reason = "short_confirmation" if should_sync_process: semantic_bundle = None normalized_sync_reason = str(sync_reason or "").strip().lower() should_forward_semantic_bundle = bool(force_sync) or normalized_sync_reason.startswith("semantic:") if should_forward_semantic_bundle: - candidate_bundle = WorkflowSemanticPlanner.plan_message( + candidate_bundle = await WorkflowSemanticPlanner.aplan_message( message_text=cleaned_message, source_system="feishu", ) @@ -407,23 +399,26 @@ class SignalCaptureEntryMixin: ) except Exception: pass - try: - await self.db.commit() - except Exception as exc: - self._emit_ai_think( - "capture_kernel_commit_failed", - trace_id=trace_id, - error=str(exc), - ) - await self._rollback_capture_failure(trace_id=trace_id) - return self._build_kernel_error_result( - trace_id=trace_id, - reason="kernel_commit_failed", - error=str(exc), - ) + if hasattr(self.db, "commit"): + try: + await self.db.commit() + except Exception as exc: + self._emit_ai_think( + "capture_kernel_commit_failed", + trace_id=trace_id, + error=str(exc), + ) + await self._rollback_capture_failure(trace_id=trace_id) + return self._build_kernel_error_result( + trace_id=trace_id, + reason="kernel_commit_failed", + error=str(exc), + ) return pipeline_result async def _rollback_capture_failure(self, *, trace_id: str) -> None: + if not hasattr(self.db, "rollback"): + return try: await self.db.rollback() except Exception as rollback_exc: diff --git a/backend/app/services/signal/signal_capture_service/mixin_pipeline_cache.py b/backend/app/services/signal/signal_capture_service/mixin_pipeline_cache.py index af1526ba..8539b5e6 100644 --- a/backend/app/services/signal/signal_capture_service/mixin_pipeline_cache.py +++ b/backend/app/services/signal/signal_capture_service/mixin_pipeline_cache.py @@ -111,7 +111,10 @@ class SignalCapturePipelineCacheMixin: return pipeline_result source_orchestration_result: Optional[Dict[str, Any]] = None - if WorkflowSemanticPlanner.looks_like_signal_source_request(cleaned_message): + if ( + str(pipeline_result.get("intent") or "").strip().lower() == INTENT_SIGNAL_SOURCE_ONBOARD + or str(pipeline_result.get("workflow_route") or "").strip().lower() == ROUTE_SIGNAL_SOURCE_ONBOARD + ): try: source_orchestration_result = await SignalSourceOrchestrationService(self.db).orchestrate_from_signal( tenant_id=tenant_id, diff --git a/backend/app/services/signal/signal_capture_service/shared.py b/backend/app/services/signal/signal_capture_service/shared.py index f8e8739b..52a0f257 100644 --- a/backend/app/services/signal/signal_capture_service/shared.py +++ b/backend/app/services/signal/signal_capture_service/shared.py @@ -41,11 +41,6 @@ from ...llm_service import LLMService from ...notification_service import NotificationService from ... import workload as workload_service from ...adaptive_rag_service import AdaptiveRAGService -from ...ai_pmo.workflow_semantic_planner.constants import ( - NON_REQUIREMENT_CONTAINS, - NON_REQUIREMENT_EXACT, - REQUIREMENT_HINTS, -) from ...ai_orchestration_run import AIOrchestrationRunService from ...context_cache_service import context_cache_service from ..signal_source_orchestration import SignalSourceOrchestrationService diff --git a/backend/app/services/signal/signal_source_orchestration/mixin_entry_orchestrate.py b/backend/app/services/signal/signal_source_orchestration/mixin_entry_orchestrate.py index 55c196c0..ab99d877 100644 --- a/backend/app/services/signal/signal_source_orchestration/mixin_entry_orchestrate.py +++ b/backend/app/services/signal/signal_source_orchestration/mixin_entry_orchestrate.py @@ -1,16 +1,11 @@ from __future__ import annotations from .shared import * -from ...ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner class SignalSourceEntryOrchestrateMixin: def __init__(self, db: AsyncSession) -> None: self.db = db - @classmethod - def looks_like_signal_source_request(cls, text: str) -> bool: # semantic-lint: ignore - compat wrapper over workflow_semantic_planner - return WorkflowSemanticPlanner.looks_like_signal_source_request(text) - async def orchestrate_from_signal( self, *, @@ -61,17 +56,6 @@ class SignalSourceEntryOrchestrateMixin: real_signal_id = signal_ref.id persisted_signal_id = int(real_signal_id) if real_signal_id and int(real_signal_id) > 0 else None - if not self.looks_like_signal_source_request(initiating_text): - return await self._handle_non_source_request( - tenant_id=tenant_id, - project_id=project_id, - persisted_signal_id=persisted_signal_id, - operator_user_id=operator_user_id, - trace_id=trace_id, - response_mode=response_mode, - run_service=run_service, - ) - plan, planner_status, planner_diagnostics = await self._plan_onboarding( tenant_id=tenant_id, project=project_ref, diff --git a/backend/app/services/signal/signal_source_orchestration/mixin_planning_locator_roles.py b/backend/app/services/signal/signal_source_orchestration/mixin_planning_locator_roles.py index 1a346fbc..ae7874e5 100644 --- a/backend/app/services/signal/signal_source_orchestration/mixin_planning_locator_roles.py +++ b/backend/app/services/signal/signal_source_orchestration/mixin_planning_locator_roles.py @@ -63,14 +63,6 @@ class SignalSourcePlanningLocatorRolesMixin: "复合", "复合信号源", } - for kind, hints in SOURCE_KIND_HINTS: - if kind != source_kind: - continue - generic_tokens.update( - re.sub(r"[^a-zA-Z0-9\u4e00-\u9fff]+", "", str(token)).lower() - for token in hints - ) - break return normalized in generic_tokens @staticmethod @@ -92,4 +84,3 @@ class SignalSourcePlanningLocatorRolesMixin: "请创建必要任务、分配合适负责人,并通知相关成员。" f"补充上下文:{str(initiating_text or '').strip()[:240]}" ) - diff --git a/backend/app/services/signal/signal_source_orchestration/mixin_planning_onboard.py b/backend/app/services/signal/signal_source_orchestration/mixin_planning_onboard.py index 6597f163..9b564e12 100644 --- a/backend/app/services/signal/signal_source_orchestration/mixin_planning_onboard.py +++ b/backend/app/services/signal/signal_source_orchestration/mixin_planning_onboard.py @@ -155,7 +155,12 @@ class SignalSourcePlanningOnboardMixin: fallback_text: str, requested_via: str, ) -> dict[str, Any]: - source_kind = self._normalize_source_kind(source_payload.get("source_kind"), text=fallback_text, requested_via=requested_via) + source_kind = self._normalize_source_kind( + source_payload.get("source_kind"), + text=fallback_text, + requested_via=requested_via, + source_payload=source_payload, + ) source_name = self._clean_source_name_candidate(str(source_payload.get("source_name") or "").strip()) if not source_name: source_name = self._extract_source_name(fallback_text, default=f"{source_kind}信号源") @@ -183,20 +188,36 @@ class SignalSourcePlanningOnboardMixin: } @staticmethod - def _normalize_source_kind(raw_kind: Any, *, text: str, requested_via: str) -> str: + def _normalize_source_kind(raw_kind: Any, *, text: str, requested_via: str, source_payload: Optional[dict[str, Any]] = None) -> str: cleaned = str(raw_kind or "").strip().lower() - for kind, hints in SOURCE_KIND_HINTS: - if cleaned == kind: - return kind - if any(token in cleaned for token in hints): - return kind - text_lower = str(text or "").lower() - for kind, hints in SOURCE_KIND_HINTS: - if any(token in text_lower for token in hints): - return kind + payload = source_payload if isinstance(source_payload, dict) else {} + config = payload.get("config") if isinstance(payload.get("config"), dict) else {} + if cleaned in SUPPORTED_SOURCE_KINDS: + return cleaned + via = str(requested_via or "").strip().lower() if via in {"feishu", "pmflow", "github", "browser"}: return via + + if any(str(payload.get(key) or config.get(key) or "").strip() for key in ("repo", "repository")): + return "github" + if any(str(payload.get(key) or config.get(key) or "").strip() for key in ("channel_id", "chat_id")): + return "feishu" + if any(str(payload.get(key) or config.get(key) or "").strip() for key in ("webhook_url",)): + return "webhook" + if any(str(payload.get(key) or config.get(key) or "").strip() for key in ("url",)): + return "url" + + explicit_locator = SignalSourcePlanningOnboardMixin._extract_explicit_source_locator( + source_payload=payload, + text=text, + ) + if explicit_locator.startswith("repo:"): + return "github" + if explicit_locator.startswith("chat:"): + return "feishu" + if explicit_locator.startswith(("http://", "https://")): + return "webhook" if cleaned == "webhook" else "url" return "custom" @classmethod @@ -268,4 +289,3 @@ class SignalSourcePlanningOnboardMixin: if normalized in {"", "项目id", "给项目id", "复合", "复合信号源", "信号源", "来源", "监控源"}: return "" return cleaned - diff --git a/backend/app/services/signal/signal_source_orchestration/shared.py b/backend/app/services/signal/signal_source_orchestration/shared.py index b2995bf0..c5380077 100644 --- a/backend/app/services/signal/signal_source_orchestration/shared.py +++ b/backend/app/services/signal/signal_source_orchestration/shared.py @@ -34,16 +34,8 @@ from ...model.prompt_profile import prompt_profile_service from ...llm_service import llm_service from ....utils.timezone import now_cn_naive -from ...ai_pmo.workflow_semantic_planner.constants import SOURCE_ONBOARDING_ACTION_RE -SOURCE_KIND_HINTS: tuple[tuple[str, tuple[str, ...]], ...] = ( - ("feishu", ("飞书", "lark", "群聊", "chat")), - ("github", ("github", "repo", "仓库", "pull request", "issue")), - ("webhook", ("webhook", "回调", "回传")), - ("url", ("url", "链接", "网页", "站点", "rss")), - ("browser", ("浏览器", "标签页", "页面")), - ("pmflow", ("pmflow", "内置ai", "ai管家", "steward")), -) +SUPPORTED_SOURCE_KINDS = ("feishu", "github", "webhook", "url", "browser", "pmflow", "custom") DEFAULT_WORKER_ROLE_KEYS = ("pm_dispatcher", "tech_architect", "delivery_coach") MAX_SOURCE_NAME_LEN = 120 MAX_SOURCE_IDENTIFIER_LEN = 180 diff --git a/backend/tests/test_adaptive_rag_mainline_contract.py b/backend/tests/test_adaptive_rag_mainline_contract.py index e51f7978..d60c3bdd 100644 --- a/backend/tests/test_adaptive_rag_mainline_contract.py +++ b/backend/tests/test_adaptive_rag_mainline_contract.py @@ -9,8 +9,10 @@ from app.services.adaptive_rag_service.mixin_deep_guard_rules import AdaptiveRAG from app.services.adaptive_rag_service.mixin_deterministic_query_core import ( AdaptiveRAGDeterministicQueryCoreMixin, ) +from app.services.adaptive_rag_service.mixin_requirement_ack_core import AdaptiveRAGRequirementAckCoreMixin from app.services.adaptive_rag_service.mixin_retrieval_vector import AdaptiveRAGRetrievalVectorMixin -from app.services.adaptive_rag_service.shared_part_02 import DEEP_RETRIEVAL_STEPS, DEEP_VECTOR_TOP_K +from app.services.adaptive_rag_service.shared_part_02 import ACTION_MODE_READ, DEEP_RETRIEVAL_STEPS, DEEP_VECTOR_TOP_K +from app.services.ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner class _FakeAdaptiveRAGService( @@ -168,6 +170,49 @@ async def test_answer_entry_feishu_write_mode_still_goes_base_mainline() -> None assert int(service.base_kwargs["deep_vector_top_k"]) == DEEP_VECTOR_TOP_K +@pytest.mark.asyncio +async def test_requirement_ack_auto_mode_respects_structured_query_bundle(monkeypatch: pytest.MonkeyPatch) -> None: + class _ModeService(AdaptiveRAGRequirementAckCoreMixin): + pass + + async def _fake_aplan_message(cls, *, message_text: str, source_system: str, metadata=None): + _ = (cls, message_text, source_system, metadata) + return WorkflowSemanticPlanner.deserialize_bundle( + { + "bundle_id": "semantic-test", + "semantic_version": "v1", + "source_system": "adaptive_rag_ack", + "primary_workflow": "query_flow", + "workflows": [ + { + "workflow_type": "query_flow", + "workflow_action": "answer", + "target_scope": "global", + "operation_codes": ["query.answer"], + "requires_confirmation": False, + "confidence": 0.91, + "clarification_needed": False, + "metadata": {}, + } + ], + "message_gate": "auto", + "reply_contract": {"mode": "text", "template_key": "workflow_bundle", "summary": "query_flow"}, + "diagnostics": {"message_intent": "question"}, + } + ) + + monkeypatch.setattr(WorkflowSemanticPlanner, "aplan_message", classmethod(_fake_aplan_message)) + + mode = await _ModeService._resolve_action_mode_async( + requested_mode="auto", + question="先不要创建任务,告诉我当前进展和风险", + effective_question="先不要创建任务,告诉我当前进展和风险", + asked_via="pmflow_manual", + ) + + assert mode == ACTION_MODE_READ + + def test_query_runtime_planner_prefers_fast_path_for_list_question_even_if_requested_deep() -> None: class _FakePlannerService(AdaptiveRAGAnswerMainlineMixin): pass diff --git a/backend/tests/test_ai_pmo_ingress_mainline.py b/backend/tests/test_ai_pmo_ingress_mainline.py index 42c2e201..a99a5793 100644 --- a/backend/tests/test_ai_pmo_ingress_mainline.py +++ b/backend/tests/test_ai_pmo_ingress_mainline.py @@ -1,4 +1,5 @@ import asyncio +import json from datetime import datetime, timedelta from types import SimpleNamespace @@ -35,13 +36,17 @@ from app.services.ai_pmo.ai_pmo_kernel.mixins.world_model import ( from app.services.ai_pmo.ai_pmo_typed_executor_execute import mixin_execute_query_project from app.services.ai_pmo.ai_pmo_typed_executor_execute import mixin_execute_mutations from app.services.ai_pmo.ai_pmo_intent import CanonicalIntent +from app.services.ai_pmo.workflow_semantic_planner.models import ReplyContract, WorkflowBundle, WorkflowPlan from app.services.ai_pmo.draft_confirm_handler import DraftConfirmHandler from app.services.adaptive_rag_service import mixin_deterministic_query_core from app.services.adaptive_rag_service import mixin_fast_answer_project_main +from app.services.integrations.feishu_integration_service import mixin_reply_outbox as feishu_mixin_reply_outbox +from app.services.integrations.feishu_outbox.service import FeishuOutboxService from app.services.llm_service import ChatCompletionResult, StructuredExtractionResult from app.services.llm_service import mixin_tier_escalation from app.services.llm_service.mixin_tier_escalation import LLMTierEscalationMixin from app.models.base import Base +from app.models.feishu_reply_outbox import FeishuReplyOutbox from app.models.mounted_ai_runtime import MountedDraftWorkspace, MountedTopicWorkspace from app.utils.thread_context import build_feishu_conversation_scope_key, build_feishu_thread_key @@ -88,6 +93,488 @@ async def requirement_plan_db_session(): await engine.dispose() +@pytest_asyncio.fixture +async def feishu_outbox_db_session(): + engine = create_async_engine("sqlite+aiosqlite:///:memory:") + async with engine.begin() as conn: + await conn.run_sync( + lambda sync_conn: Base.metadata.create_all( + sync_conn, + tables=[FeishuReplyOutbox.__table__], + ) + ) + maker = async_sessionmaker(engine, expire_on_commit=False) + async with maker() as session: + yield session + await engine.dispose() + + +def _build_long_requirement_plan_reply_text() -> str: + repeated = ",".join(["覆盖用户偏好、衣橱标签、天气温度、场景约束、历史反馈"] * 10) + return ( + "🧭 我先按“换季系统自动推荐搭配衣服”这类 IT 项目来收。\n" + "现在最关键的是先把第一版定位定住。\n\n" + f"A. 轻量推荐工具\n更适合:{repeated}\n推荐技术/能力:{repeated}\n我为什么会考虑这个:{repeated}\n代价:{repeated}\n\n" + f"B. 业务平台能力\n更适合:{repeated}\n推荐技术/能力:{repeated}\n我为什么会考虑这个:{repeated}\n代价:{repeated}\n\n" + f"C. 长期能力底座\n更适合:{repeated}\n推荐技术/能力:{repeated}\n我为什么会考虑这个:{repeated}\n代价:{repeated}\n\n" + f"⭐ 当前我更推荐:B\n原因:{repeated}\n\n你回 A / B / C,我就顺着这条继续往下收。" + ) + + +def _install_requirement_plan_round_llm(monkeypatch, captured: list[dict[str, object]] | None = None) -> None: + def _extract_round_packet(prompt: str) -> dict[str, object]: + marker = "requirement_plan_round_packet:" + if marker not in prompt: + return {} + raw = prompt.split(marker, 1)[1].strip() + try: + payload = json.loads(raw) + except Exception: + return {} + return payload if isinstance(payload, dict) else {} + + def _extract_draft_packet(prompt: str) -> dict[str, object]: + marker = "requirement_plan_draft_packet:" + if marker not in prompt: + return {} + raw = prompt.split(marker, 1)[1].strip() + try: + payload = json.loads(raw) + except Exception: + return {} + return payload if isinstance(payload, dict) else {} + + async def fake_generate_structured_object(prompt: str, **kwargs): # noqa: ANN001 + if captured is not None: + captured.append({"prompt": prompt, "kwargs": dict(kwargs)}) + function_name = str(kwargs.get("function_name") or "") + packet = _extract_round_packet(prompt) + context_blob = json.dumps(packet, ensure_ascii=False) + is_scoring_context = any( + token in context_blob + for token in ("大A趋势打分", "趋势打分", "投顾", "研究团队", "交易团队") + ) + if function_name == "pmflow_requirement_plan_draft": + draft_packet = _extract_draft_packet(prompt) + draft_blob = json.dumps(draft_packet, ensure_ascii=False) + if "换季系统自动推荐搭配衣服" in draft_blob or "个性化穿搭推荐平台" in draft_blob: + payload = { + "intro": "📋 我先按当前收敛结果,整理成一版更贴近当前业务的需求草稿:", + "title": "换季系统自动推荐搭配衣服", + "one_liner": "先面向普通消费者做一版换季穿搭推荐试用,先把温度、场景和单品搭配建议跑成可验证闭环。", + "goal": "先验证用户是否愿意使用换季搭配推荐,并收集最基础的点击、收藏和反馈信号。", + "project_nature": "这件事更像个性化穿搭推荐平台的第一版,不是部署平台,也不是内部流程治理工具。", + "target_user": "第一版先服务普通消费者,重点看他们是否会点击、收藏和采纳推荐结果。", + "signal_inputs": "当前至少要用到:用户当前需求描述;季节/温度信息;衣物标签;基础反馈数据。", + "known_info": "目前已经明确:这是换季穿搭推荐需求;先按个性化穿搭推荐平台收敛;第一版先给普通消费者试用;技术路线先按规则推荐版;范围先保留温度、场景、单品搭配建议。", + "tech_route": "当前更适合先走规则推荐版,用季节、温度、场景和衣物标签跑通首轮推荐。", + "first_scope": "第一版先保留温度、场景、单品搭配建议,先验证推荐是否被点击、收藏和采纳。", + "core_flow": "用户进入换季推荐入口 -> 结合温度/场景生成搭配建议 -> 展示单品组合 -> 记录点击/收藏/反馈 -> 继续优化推荐规则", + "delivery_mode": "先做小范围真实用户试用,优先看真实点击、收藏和主观反馈。", + "success_signal": "先保证能稳定给出换季搭配建议,并拿到点击、收藏或反馈中的至少一类真实用户信号。", + "risks": "当前主要风险在冷启动标签质量、搭配规则是否过于死板,以及首轮反馈样本是否足够。", + "pending_items": ["衣物标签口径是否已有现成标准", "首轮试用用户范围和反馈采集方式", "是否需要同步接商品库或衣橱数据"], + "closing": "如果你认可,我下一轮就按这版继续细化;如果要改,直接说改哪一项,我就在这版上改。", + "positioning_one_liner": "面向普通消费者的小范围换季穿搭推荐试用,先跑通基础推荐闭环。", + "summary": "先用规则推荐跑通换季穿搭建议,再根据真实点击、收藏和反馈决定是否扩到更完整的平台能力。", + "target_customers": "普通消费者", + "external_signal_sources": [ + {"name": "天气 API", "purpose": "补充实时温度、体感和降雨变化", "phase": "首版优先接入"}, + {"name": "淘宝开放平台", "purpose": "补充可推荐商品的品类、颜色和季节属性", "phase": "首版优先接入"}, + {"name": "得物开放数据", "purpose": "补充年轻用户偏好的款式热度和潮流变化", "phase": "第二阶段接入"}, + ], + "requirement_items": [ + {"section": "需求目标", "title": "验证用户价值", "description": "验证用户是否愿意使用换季搭配推荐,并留下点击、收藏或反馈。"}, + {"section": "服务对象", "title": "首批用户", "description": "第一版先服务普通消费者。"}, + {"section": "技术路线", "title": "规则推荐版", "description": "先基于季节、温度、场景和衣物标签生成推荐。"}, + {"section": "第一版范围", "title": "最小闭环", "description": "先保留温度、场景、单品搭配建议。"}, + ], + "missing_info": ["衣物标签标准", "试用用户范围", "反馈采集方式"], + "mvp_items": ["温度标签", "场景标签", "单品搭配建议", "基础点击/收藏反馈"], + "team_setup": [{"role": "建议配置", "notes": "产品/运营 1名、后端/算法 1名、前端 1名,先支撑小范围试用即可。"}], + "validation_metrics": {"product": ["推荐点击率", "收藏率", "用户主观反馈"]}, + } + elif any(token in draft_blob for token in ("大A趋势打分", "趋势打分", "投顾", "研究团队", "交易团队")): + payload = { + "intro": "📋 我先按当前收敛结果,整理成一版更贴近当前业务的需求草稿:", + "title": "每日 A 股趋势打分工具", + "one_liner": "面向投顾与研究团队的轻量级趋势打分工具,先把每日评分、评分解释和使用反馈跑成闭环。", + "goal": "先验证趋势打分结果是否有参考价值,并让投顾或研究团队能快速查看、比对和反馈。", + "project_nature": "这件事更像一个投研辅助小工具,核心是先把每日趋势评分与解释能力跑通。", + "target_user": "第一版先服务投顾与研究团队,优先满足他们查看评分、理解原因和给反馈的需要。", + "signal_inputs": "当前至少要用到:行情数据、基础技术指标、评分规则、投顾或研究团队反馈。", + "known_info": "目前已经明确:这是每日 A 股趋势打分工具;当前按投研辅助工具收敛;主要给投顾或研究团队看;技术路线先按评分服务版;第一版先保留每日评分、评分解释和重点股票列表。", + "tech_route": "当前更适合先走评分服务版,用规则与指标服务生成每日趋势分数和解释。", + "first_scope": "第一版先保留每日趋势评分、评分解释和重点股票列表,先验证结果是否可用。", + "core_flow": "拉取行情与指标 -> 计算每日趋势分数 -> 输出评分解释与重点股票 -> 投顾查看与反馈 -> 根据反馈调整评分规则", + "delivery_mode": "先做小范围投研试用,优先看投顾和研究团队是否会实际参考这份评分结果。", + "success_signal": "先保证能稳定产出每日评分结果,并让投顾或研究团队留下查看、引用或反馈中的至少一类真实使用信号。", + "risks": "当前主要风险在评分口径是否足够稳定、解释是否被投顾认可,以及反馈样本是否足够支持后续优化。", + "pending_items": ["评分口径是否需要区分短线和中线", "重点股票列表的筛选边界", "反馈采集方式与频率"], + "closing": "如果你认可,我下一轮就按这版继续细化;如果要改,直接说改哪一项,我就在这版上改。", + "positioning_one_liner": "面向投顾与研究团队的每日趋势评分试用,先跑通评分与解释闭环。", + "summary": "先用轻量评分服务跑通每日趋势打分,再根据真实使用反馈决定是否扩到更完整的投研分析平台。", + "target_customers": "投顾、研究团队", + "external_signal_sources": [ + {"name": "Tushare API", "purpose": "补充日线行情、成交量和基础财务指标", "phase": "首版优先接入"}, + {"name": "同花顺 iFinD", "purpose": "补充板块热度与市场情绪相关指标", "phase": "第二阶段接入"}, + {"name": "Wind API", "purpose": "补充更完整的因子与指数成分数据", "phase": "第二阶段接入"}, + ], + "requirement_items": [ + {"section": "需求目标", "title": "验证评分可用性", "description": "验证每日趋势打分是否真的能帮助投顾或研究团队更快做初筛。"}, + {"section": "服务对象", "title": "首批用户", "description": "第一版先服务投顾与研究团队。"}, + {"section": "技术路线", "title": "评分服务版", "description": "先基于规则与指标服务生成每日评分与解释。"}, + {"section": "第一版范围", "title": "最小闭环", "description": "先保留每日评分、评分解释和重点股票列表。"}, + ], + "missing_info": ["评分口径边界", "重点股票筛选标准", "反馈采集方式"], + "mvp_items": ["每日趋势评分", "评分解释", "重点股票列表", "基础反馈记录"], + "team_setup": [{"role": "建议配置", "notes": "产品/投研 1名、后端/数据 1名、前端 1名,先支撑小范围投研试用即可。"}], + "validation_metrics": {"product": ["评分查看率", "投顾引用率", "研究团队反馈数"]}, + } + elif "批量自动化部署" in draft_blob or "openclaw" in draft_blob: + payload = { + "intro": "📋 我先按当前收敛结果,整理成一版更像正式 brief 的需求草稿:", + "title": "OpenClaw 批量自动化部署平台", + "one_liner": "先按面向平台运维团队的项目级试点推进,先把 OpenClaw 批量部署、执行追踪和失败恢复跑成一条闭环。", + "goal": "先把 IT 项目的第一版目标、技术路线和推进方式收清楚,再决定是否正式创建。", + "project_nature": "这件事更像发布治理平台,需要稳定承接批量执行、状态追踪和治理边界。", + "target_user": "第一版先服务平台运维团队,他们会最早使用、反馈和承担执行确认。", + "signal_inputs": "当前至少要接入:飞书需求消息;部署目标清单(服务/环境/实例);代码仓库/镜像信息;环境参数与凭证;执行日志与失败日志。", + "known_info": "目前已经明确:当前要解决的是 OpenClaw 批量自动化部署;当前按发布治理平台收敛;主要服务平台运维团队;技术路线先按任务编排平台版;第一版先按批量执行闭环推进;推进方式先按项目级试点(带基础审批和回滚)。", + "tech_route": "当前更适合走任务编排平台版(FastAPI + React + Redis + Celery + PostgreSQL),先把批量执行、状态追踪和失败重试稳定下来。", + "first_scope": "第一版先做批量执行闭环,包含任务创建、执行日志和失败重试。", + "core_flow": "提交部署需求 -> 生成部署任务 -> 选择目标环境 -> 执行部署 -> 回传结果/日志 -> 失败重试或回滚 -> 留痕审计", + "delivery_mode": "先按项目级试点(带基础审批和回滚)推进,在真实项目里验证价值和流程问题。", + "success_signal": "先保证能稳定创建部署任务、看到执行结果、识别失败原因,并在试点里跑完一次真实闭环。", + "risks": "当前主要风险在部署目标范围、环境访问权限与凭证归属、仓库/镜像/配置规范、凭证管理,以及失败重试与回滚策略。", + "pending_items": ["部署目标范围(服务/环境/实例)", "仓库/镜像/配置来源", "环境权限与凭证归属", "试点负责人和投入人天"], + "closing": "如果你认可,我下一轮就按这版继续细化;如果要改,直接说改哪一项,我就在这版上改。", + "positioning_one_liner": "面向平台运维团队的发布治理平台试点,先跑通批量执行与失败恢复闭环。", + "summary": "先做发布治理平台试点,把批量执行闭环在真实项目里跑通,再继续扩治理能力。", + "target_customers": "平台运维团队", + "external_signal_sources": [ + {"name": "CMDB API", "purpose": "补充部署目标实例、环境和服务清单", "phase": "首版优先接入"}, + {"name": "GitLab API", "purpose": "补充仓库、分支、提交与流水线信息", "phase": "首版优先接入"}, + {"name": "Harbor Registry API", "purpose": "补充镜像标签、仓库和发布时间", "phase": "首版优先接入"}, + ], + "requirement_items": [ + {"section": "项目性质", "title": "发布治理平台", "description": "需要稳定承接批量执行、状态追踪和治理边界。"}, + {"section": "服务对象", "title": "平台运维团队", "description": "第一版先服务平台运维团队。"}, + {"section": "技术路线", "title": "任务编排平台版", "description": "用 FastAPI + React + Redis + Celery + PostgreSQL 承接任务编排。"}, + {"section": "第一版范围", "title": "批量执行闭环", "description": "先包含任务创建、执行日志和失败重试。"}, + ], + "missing_info": ["部署目标范围", "凭证归属", "试点负责人"], + "mvp_items": ["任务创建", "执行日志", "失败重试", "回滚能力"], + "team_setup": [{"role": "建议配置", "notes": "需求负责人/PM 1名;后端工程师 1名;前端工程师 1名;平台/运维工程师 1名;测试/试点验收 1名;试点负责人 1名。"}], + "validation_metrics": {"product": ["任务创建成功率", "部署执行完成率", "失败原因可识别率"]}, + } + else: + payload = { + "intro": "📋 我先按当前收敛结果,整理成一版更像正式 brief 的需求草稿:", + "title": "企业级项目管理系统里的需求收敛功能", + "one_liner": "先按面向项目管理团队的项目级试点推进,先把需求收敛、待确认项和范围确认闭环跑通。", + "goal": "先把 IT 项目的第一版目标、技术路线和推进方式收清楚,再决定是否正式创建。", + "project_nature": "这件事更像发布治理平台式的业务协作能力,需要承接多人协作、状态流转和留痕。", + "target_user": "第一版先服务项目管理团队,他们会最早补充需求、看收敛结果和处理待确认项。", + "signal_inputs": "当前至少要接入:飞书需求消息;项目/任务/责任人数据;需求变更记录;状态流转记录。", + "known_info": "目前已经明确:当前要解决的是企业级项目管理系统里的需求收敛功能;当前按发布治理平台收敛;主要服务项目管理团队;技术路线先按任务编排平台版;第一版先按待办、责任人、截止时间推进;推进方式先按项目级试点。", + "tech_route": "当前更适合走任务编排平台版(FastAPI + React + Redis + Celery + PostgreSQL),先把需求收敛和待确认项管理稳定下来。", + "first_scope": "待办、责任人、截止时间。", + "core_flow": "提交需求信号 -> 生成需求 brief -> 拆出待办/责任人/截止时间 -> 校验待确认项 -> 人工确认后继续推进", + "delivery_mode": "先按项目级试点(带基础审批和回滚)推进,尽快在真实项目里验证需求收敛价值。", + "success_signal": "先保证能稳定生成项目 brief、拆出待办/责任人/截止时间,并能支撑一轮真实确认闭环。", + "risks": "当前主要风险在需求事实是否足够清晰、责任人与截止时间来源是否稳定,以及确认规则是否一致。", + "pending_items": ["需求事实来源与证据口径", "责任人字段来源", "截止时间字段来源", "试点负责人和投入人天"], + "closing": "如果你认可,我下一轮就按这版继续细化;如果要改,直接说改哪一项,我就在这版上改。", + "positioning_one_liner": "面向项目管理团队的需求收敛试点,先跑通待办/责任人/截止时间闭环。", + "summary": "先把需求收敛主链路跑通,再根据试点结果决定是否扩到更完整的平台能力。", + "target_customers": "项目管理团队", + "external_signal_sources": [ + {"name": "飞书开放平台", "purpose": "补充消息原文、评论和消息元数据", "phase": "首版优先接入"}, + {"name": "Jira API", "purpose": "补充需求单、负责人和截止时间字段", "phase": "首版优先接入"}, + {"name": "Confluence API", "purpose": "补充需求背景文档和评审记录", "phase": "第二阶段接入"}, + ], + "requirement_items": [ + {"section": "项目性质", "title": "业务协作平台能力", "description": "承接多人协作、状态流转和范围确认。"}, + {"section": "服务对象", "title": "项目管理团队", "description": "第一版先服务项目管理团队。"}, + {"section": "技术路线", "title": "任务编排平台版", "description": "用 FastAPI + React + Redis + Celery + PostgreSQL 承接收敛链路。"}, + {"section": "第一版范围", "title": "核心字段", "description": "先保留待办、责任人、截止时间。"}, + ], + "missing_info": ["需求事实来源", "责任人字段来源", "截止时间字段来源"], + "mvp_items": ["需求 brief", "待办字段", "责任人字段", "截止时间字段"], + "team_setup": [{"role": "建议配置", "notes": "需求负责人/PM 1名;后端工程师 1名;前端工程师 1名;测试/试点验收 1名。"}], + "validation_metrics": {"product": ["brief 生成成功率", "字段抽取可用率", "确认闭环完成率"]}, + } + elif "换季系统自动推荐搭配衣服" in context_blob: + payload = { + "opening": "🧭 我先按“换季系统自动推荐搭配衣服”这类 IT 项目来收。", + "focus": "现在最关键的,是先定它更像一次性推荐小工具、可持续运营平台,还是长期穿搭决策能力。", + "options": [ + { + "key": "A", + "title": "先做穿搭推荐小工具", + "best_for": "先验证用户是否愿意按天气、季节和已有衣橱接收搭配建议", + "suggested_capability": "基础规则推荐、衣物标签、简单反馈", + "rationale": "落地快,能先验证推荐结果是否真的有用", + "tradeoff": "个性化和长期学习能力会比较弱", + }, + { + "key": "B", + "title": "先做个性化穿搭推荐平台", + "best_for": "需要持续沉淀用户偏好、衣橱数据和搭配反馈", + "suggested_capability": "用户画像、衣物标签体系、推荐策略、反馈闭环", + "rationale": "更贴近你说的自动推荐搭配衣服,也方便后续扩展", + "tradeoff": "第一版要先把数据口径和推荐边界说清楚", + }, + { + "key": "C", + "title": "直接做穿搭决策底座", + "best_for": "后续要接入多品牌、多场景和长期运营策略", + "suggested_capability": "多源数据接入、推荐服务、策略实验、效果评估", + "rationale": "长期上限更高", + "tradeoff": "早期投入最大,容易还没验证需求就做重", + }, + ], + "recommendation": {"key": "B", "reason": "它最符合“换季系统自动推荐搭配衣服”的持续价值,但还不会一开始做成过重底座。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + elif is_scoring_context and '"missing_field": "project_nature"' in prompt: + payload = { + "opening": "🧭 我先按“每日 A 股趋势打分工具”这类 IT 项目来收。", + "focus": "现在最关键的,是先定它更像一个轻量投研工具、可复用评分服务,还是长期分析底座。", + "options": [ + { + "key": "A", + "title": "先做轻量打分工具", + "best_for": "先验证每日评分结果是否有参考价值", + "suggested_capability": "日线数据拉取、评分规则、结果展示", + "rationale": "开发快,适合先验证评分是否真的有人用", + "tradeoff": "后续扩展多维因子和策略实验时需要补能力", + }, + { + "key": "B", + "title": "先做评分服务平台", + "best_for": "需要持续沉淀评分规则、指标口径和反馈闭环", + "suggested_capability": "评分服务、规则配置、反馈记录", + "rationale": "更方便后续持续优化评分逻辑", + "tradeoff": "第一版会比 A 多一点规则管理成本", + }, + { + "key": "C", + "title": "直接做投研分析底座", + "best_for": "后续要承接多策略、多因子和多团队复用", + "suggested_capability": "因子计算、策略实验、分析接口", + "rationale": "长期上限更高", + "tradeoff": "早期投入最大,需求没跑通前容易做重", + }, + ], + "recommendation": {"key": "B", "reason": "先按评分服务把规则、结果和反馈闭环收住,更适合后续持续优化。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + elif '"missing_field": "project_nature"' in prompt: + is_deployment_prompt = "批量自动化部署" in context_blob + subject = "OpenClaw 批量自动化部署平台" if is_deployment_prompt else "当前需求" + option_b_title = "先做发布治理平台" if is_deployment_prompt else "先做业务协作平台" + option_b_best_for = "多人协作、状态流转、需要审批和回滚" if is_deployment_prompt else "多人协作、状态流转、需要审批和留痕" + option_b_capability = "FastAPI + React + PostgreSQL + Redis" if is_deployment_prompt else "FastAPI + React + PostgreSQL + 审批流 + 审计日志" + payload = { + "opening": f"🧭 我先按“{subject}”这类 IT 项目来收。", + "focus": "现在最关键的,不是立刻展开做法,而是先定它更像工具、平台,还是长期底座。", + "options": [ + { + "key": "A", + "title": "先做内部提效工具", + "best_for": "先减少重复操作,把第一版价值快速跑通", + "suggested_capability": "FastAPI + React + PostgreSQL", + "rationale": "开发快,适合先验证 MVP", + "tradeoff": "后续任务编排和治理能力要继续补", + }, + { + "key": "B", + "title": option_b_title, + "best_for": option_b_best_for, + "suggested_capability": option_b_capability, + "rationale": "更适合承接长期治理和执行追踪", + "tradeoff": "前期设计复杂度高于 A", + }, + { + "key": "C", + "title": "直接做云原生交付底座" if is_deployment_prompt else "直接做长期能力底座", + "best_for": "目标是规模化、标准化和长期复用", + "suggested_capability": "平台能力、策略配置、审计与扩展接口", + "rationale": "长期上限更高", + "tradeoff": "早期投入最大", + }, + ], + "recommendation": {"key": "B", "reason": "它更适合把当前需求收成可持续的平台能力,同时不会一开始做成过重底座。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + elif '"missing_field": "target_user"' in prompt: + if is_scoring_context: + payload = { + "opening": "🧭 我先按“轻量打分工具”这个方向继续收。", + "focus": "现在要先把第一版服务对象定住,不然后面的评分口径会飘。", + "options": [ + {"key": "A", "title": "投顾", "best_for": "先服务一线需要快速看市场趋势和候选股票的人", "suggested_capability": "每日评分卡、重点股票列表、简短解释", "rationale": "最贴近直接使用评分结果的人", "tradeoff": "会更关注结果可读性和更新节奏"}, + {"key": "B", "title": "研究团队", "best_for": "先服务需要验证评分逻辑和指标口径的人", "suggested_capability": "评分拆解、指标明细、回看能力", "rationale": "更容易帮助我们先把评分口径校准", "tradeoff": "第一版结果会更偏分析工具而不是实战辅助"}, + {"key": "C", "title": "交易团队", "best_for": "直接结合交易节奏看信号", "suggested_capability": "盘前信号、风险提示、更新频率控制", "rationale": "最能验证时效价值", "tradeoff": "对稳定性和时效要求最高"}, + ], + "recommendation": {"key": "B", "reason": "先按研究团队校准评分口径,再扩到更直接的投顾使用会更稳。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + elif "换季系统自动推荐搭配衣服" in context_blob or "个性化穿搭推荐平台" in context_blob: + payload = { + "opening": "🧭 我先按“个性化穿搭推荐平台”这个方向继续收。", + "focus": "现在要先把第一版服务对象定住,不然后面推荐口径会飘。", + "options": [ + {"key": "A", "title": "普通消费者", "best_for": "先验证换季搭配推荐是否有用", "suggested_capability": "用户偏好、天气/季节标签、搭配反馈", "rationale": "最贴近最终使用者", "tradeoff": "需要处理偏好差异"}, + {"key": "B", "title": "穿搭运营团队", "best_for": "先由运营维护搭配规则和推荐口径", "suggested_capability": "规则配置、款式标签、人工校验", "rationale": "更容易控制第一版质量", "tradeoff": "自动化程度会弱一些"}, + {"key": "C", "title": "品牌导购团队", "best_for": "结合商品推荐和导购场景", "suggested_capability": "商品库、搭配卡片、导购反馈", "rationale": "更适合商业转化", "tradeoff": "会更依赖商品数据"}, + ], + "recommendation": {"key": "A", "reason": "先服务普通消费者最能验证自动推荐是否真的成立。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + else: + opening_subject = "云原生交付底座" if "云原生交付底座" in context_blob else "发布治理平台" + payload = { + "opening": f"🧭 我先按“{opening_subject}”这个方向继续收。", + "focus": "现在要先把服务对象定住,不然后面体验和边界会飘。", + "options": [ + {"key": "A", "title": "开发团队", "best_for": "先让开发自己使用", "suggested_capability": "轻量入口、日志查看", "rationale": "反馈快", "tradeoff": "治理弱"}, + {"key": "B", "title": "平台运维团队", "best_for": "统一管理流程和风险", "suggested_capability": "审批、审计、回滚", "rationale": "更稳", "tradeoff": "边界要清楚"}, + {"key": "C", "title": "项目交付团队", "best_for": "按项目复制流程", "suggested_capability": "模板、参数、交付流程", "rationale": "适合多项目", "tradeoff": "依赖流程稳定"}, + ], + "recommendation": {"key": "B", "reason": "它更适合承接责任边界和治理闭环。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + elif '"missing_field": "tech_route"' in prompt: + if is_scoring_context: + payload = { + "opening": "🧭 我先按“主要给投顾看”这个方向继续收。", + "focus": "现在最关键的,是先把第一版技术路线定住。", + "options": [ + {"key": "A", "title": "先做规则打分版", "best_for": "快速验证每日评分闭环", "suggested_capability": "FastAPI + PostgreSQL + 指标规则计算", "rationale": "最快把评分结果和解释跑通", "tradeoff": "后续扩展复杂因子时要继续补"}, + {"key": "B", "title": "先做评分服务版", "best_for": "长期沉淀评分规则、反馈和版本管理", "suggested_capability": "FastAPI + Redis + PostgreSQL + 评分服务", "rationale": "更适合稳定承接每日评分和后续优化", "tradeoff": "复杂度高于 A"}, + {"key": "C", "title": "直接做投研分析底座", "best_for": "多团队共用、多策略扩展和实验", "suggested_capability": "因子服务、分析接口、策略实验层", "rationale": "长期上限更高", "tradeoff": "首版投入最大"}, + ], + "recommendation": {"key": "B", "reason": "先做评分服务版,更适合稳定承接每日评分和后续反馈优化。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + else: + payload = { + "opening": "🧭 我先按“平台运维团队要长期使用”这个方向继续收。", + "focus": "现在最关键的,是先把第一版技术路线定住。", + "options": [ + {"key": "A", "title": "先做脚本增强版", "best_for": "快速验证闭环", "suggested_capability": "FastAPI + Shell/Ansible + PostgreSQL", "rationale": "最快落地", "tradeoff": "平台化弱"}, + {"key": "B", "title": "先做任务编排平台版", "best_for": "长期做任务状态和追踪", "suggested_capability": "FastAPI + React + Redis + Celery + PostgreSQL", "rationale": "平衡落地和扩展", "tradeoff": "复杂度高于 A"}, + {"key": "C", "title": "直接做长期能力底座", "best_for": "多团队标准化复用", "suggested_capability": "FastAPI + React + PostgreSQL + 规则配置 + 审计日志", "rationale": "上限高", "tradeoff": "门槛最高"}, + ], + "recommendation": {"key": "B", "reason": "它最适合把任务编排和治理能力留出空间。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + elif '"missing_field": "first_scope"' in prompt: + if is_scoring_context: + payload = { + "opening": "🧭 我先按“评分服务版”继续往下收。", + "focus": "这一步要定第一版先证明什么价值。", + "options": [ + {"key": "A", "title": "每日评分闭环", "best_for": "先证明每日分数能稳定产出并被查看", "suggested_capability": "每日评分、重点股票列表、查看记录", "rationale": "最能体现首版核心价值", "tradeoff": "解释能力先做轻一点"}, + {"key": "B", "title": "评分解释闭环", "best_for": "先证明用户能理解分数为什么这么给", "suggested_capability": "评分解释、指标拆解、结果回看", "rationale": "更适合投研场景验证可解释性", "tradeoff": "产出节奏会比 A 慢一点"}, + {"key": "C", "title": "反馈优化闭环", "best_for": "先把用户反馈接回来用于后续优化", "suggested_capability": "反馈记录、评分修正、版本对比", "rationale": "利于后续持续调优", "tradeoff": "首版会多一层反馈管理复杂度"}, + ], + "recommendation": {"key": "A", "reason": "MVP 最先要证明每日评分结果能稳定产出并被实际查看。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + else: + payload = { + "opening": "🧭 我先按“任务编排平台版”继续往下收。", + "focus": "这一步要定第一版到底先证明什么价值。", + "options": [ + {"key": "A", "title": "批量执行闭环", "best_for": "先证明任务能跑通", "suggested_capability": "任务创建、执行日志、失败重试", "rationale": "最能体现核心价值", "tradeoff": "治理稍后补"}, + {"key": "B", "title": "责任字段闭环", "best_for": "先把责任人和截止时间稳定下来", "suggested_capability": "字段抽取、责任校验、确认门禁", "rationale": "更适合项目管理试点", "tradeoff": "自动执行能力稍后补"}, + {"key": "C", "title": "治理闭环", "best_for": "先控制上线风险", "suggested_capability": "审批、审计、权限", "rationale": "责任边界清楚", "tradeoff": "可能做重"}, + ], + "recommendation": {"key": "A", "reason": "MVP 最先要证明核心闭环能跑起来。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + else: + payload = { + "opening": "🧭 我先按“当前范围”继续收尾。", + "focus": "现在要拍板第一版推进方式。", + "options": [ + {"key": "A", "title": "内部 PoC", "best_for": "低风险试错", "suggested_capability": "快速反馈", "rationale": "最轻", "tradeoff": "真实问题暴露少"}, + {"key": "B", "title": "项目级试点", "best_for": "真实项目验证", "suggested_capability": "试点接入、基础审批、基础回滚", "rationale": "更能验证价值", "tradeoff": "需要负责人配合"}, + {"key": "C", "title": "平台化上线", "best_for": "多团队接入", "suggested_capability": "统一权限、统一规范", "rationale": "长期价值最大", "tradeoff": "投入最高"}, + ], + "recommendation": {"key": "B", "reason": "项目级试点最适合真实验收。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + return StructuredExtractionResult( + payload=payload, + source_field="content", + provider="fake", + model="fake", + validation="ok", + ) + + monkeypatch.setattr( + mixin_execute_query_project.llm_service, + "generate_structured_object", + fake_generate_structured_object, + ) + + +def _install_requirement_plan_query_bundle( + monkeypatch, + *, + message_intent: str = "requirement_plan", + consultant_mode_preference: str = "clarify", + topic_switch: str | None = None, +) -> None: + async def _fake_aplan_message(*, message_text: str, source_system: str, metadata=None): + payload = dict(metadata or {}) + _ = message_text + effective_topic_switch = topic_switch + if effective_topic_switch is None: + explicit_topic_switch = str(payload.get("topic_switch") or "").strip() + if explicit_topic_switch: + effective_topic_switch = explicit_topic_switch + if effective_topic_switch is None: + has_thread = isinstance(payload.get("thread_context"), dict) and bool(payload.get("thread_context")) + has_active_requirement = bool(payload.get("active_requirement_plan_present")) + effective_topic_switch = "continue" if has_thread or has_active_requirement else "new" + return WorkflowBundle( + bundle_id="bundle-plan-query", + semantic_version="v1", + source_system=source_system, + primary_workflow="query_flow", + workflows=[ + WorkflowPlan( + workflow_type="query_flow", + workflow_action="answer", + target_scope="global", + operation_codes=["query.answer"], + requires_confirmation=False, + confidence=0.92, + metadata={"message_intent": message_intent}, + ) + ], + message_gate="auto", + reply_contract=ReplyContract(mode="text", template_key="workflow_bundle", summary="query_flow"), + diagnostics={ + "message_intent": message_intent, + "consultant_mode_preference": consultant_mode_preference, + "topic_switch": effective_topic_switch, + }, + ) + + monkeypatch.setattr( + mixin_execute_query_project.WorkflowSemanticPlanner, + "aplan_message", + _fake_aplan_message, + ) + + @pytest.mark.asyncio async def test_process_text_route_uses_pmflow_manual_ingress(monkeypatch) -> None: captured: dict[str, object] = {} @@ -197,7 +684,7 @@ async def test_prepare_mixed_sidecar_context_passes_mixed_contract(monkeypatch) channel_ref="pmflow-chat", source_sender_open_id="ou-pmflow-1", thread_context={"reply_to_message_id": "reply-1"}, - message_text="顺便问下现在进展如何,并且新增任务确认流", + message_text="新增任务确认流,也同步一下当前进展", project_hint_id=22, ) @@ -273,6 +760,128 @@ async def test_process_ingress_prioritizes_draft_pending_confirm(monkeypatch) -> assert "草稿" in result["direct_reply_text"] +@pytest.mark.asyncio +async def test_process_ingress_binds_active_draft_before_query_for_regenerate_request(monkeypatch) -> None: + class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + self.draft_called = False + + async def _prepare_ingress_envelope(self, **kwargs): + _ = kwargs + return { + "early_result": None, + "clean_text": "再出一版草稿", + "synthetic_message_id": "s-msg-regen-1", + "closure_payload": {}, + "ingest_event": SimpleNamespace(id=101), + "operator": SimpleNamespace(id=8), + "work_item": SimpleNamespace(id=56), + "session": SimpleNamespace(last_intent="query.read_only"), + } + + async def _maybe_handle_draft_confirmation_ingress(self, **kwargs): + self.draft_called = True + _ = kwargs + return { + "handled": True, + "status": "answered", + "classification": "command", + "direct_reply_required": True, + "direct_reply_text": "我先基于当前草稿再整理一版。", + } + + def _is_short_confirmation_message(self, **kwargs): + _ = kwargs + return False + + async def _prepare_mixed_sidecar_context(self, **kwargs): + _ = kwargs + raise AssertionError("active draft regenerate request should not fall back to query sidecar") + + async def _fake_has_active_draft_session(self, **kwargs): + _ = self + _ = kwargs + return True + + monkeypatch.setattr(DraftConfirmHandler, "has_active_draft_session", _fake_has_active_draft_session) + + kernel = FakeKernel() + result = await kernel.process_ingress( + source_system="feishu", + source_message_id="feishu-regen-1", + message_text="再出一版草稿", + sender="tester", + channel_ref="chat-1", + source_sender_open_id="ou_xxx", + ) + + assert kernel.draft_called is True + assert result["status"] == "answered" + assert "当前草稿" in result["direct_reply_text"] + + +@pytest.mark.asyncio +async def test_process_ingress_binds_active_requirement_before_query_for_short_followup(monkeypatch) -> None: + class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + self.bound = False + + async def _prepare_ingress_envelope(self, **kwargs): + _ = kwargs + return { + "early_result": None, + "clean_text": "信号源呢?", + "synthetic_message_id": "s-msg-req-followup-1", + "closure_payload": {}, + "ingest_event": SimpleNamespace(id=102), + "operator": SimpleNamespace(id=8), + "work_item": SimpleNamespace(id=57), + "session": SimpleNamespace(last_intent="query.read_only"), + } + + async def _maybe_handle_active_context_followup(self, **kwargs): + if kwargs["force_bind"] is not True: + raise AssertionError("active requirement followup should return before non-forced retry") + self.bound = True + return { + "handled": True, + "status": "answered", + "classification": "question", + "direct_reply_required": True, + "direct_reply_text": "我先把外部信号源补到当前需求里。", + } + + async def _should_route_to_draft_confirmation_session(self, **kwargs): + _ = kwargs + raise AssertionError("active requirement followup should bypass draft confirmation routing") + + async def _prepare_mixed_sidecar_context(self, **kwargs): + _ = kwargs + raise AssertionError("active requirement followup should not fall back to query sidecar") + + def _is_short_confirmation_message(self, **kwargs): + _ = kwargs + return False + + kernel = FakeKernel() + result = await kernel.process_ingress( + source_system="feishu", + source_message_id="feishu-req-followup-1", + message_text="信号源呢?", + sender="tester", + channel_ref="chat-1", + source_sender_open_id="ou_xxx", + ) + + assert kernel.bound is True + assert result["status"] == "answered" + assert "外部信号源" in result["direct_reply_text"] + + @pytest.mark.asyncio async def test_process_ingress_records_mounted_runtime_for_draft_confirmation(monkeypatch) -> None: captured: dict[str, object] = {"calls": []} @@ -613,52 +1222,53 @@ def test_should_route_to_draft_confirmation_requires_anchor_or_explicit_id() -> ) is True -def test_short_confirmation_requires_reply_anchor() -> None: - session = SimpleNamespace( - last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM, - last_active_at=datetime.now(), - ) - sent_at = datetime.now() + timedelta(seconds=1) - - assert ( - KernelIntentRulesMixin._is_short_confirmation_message( - message_text="确认", - session=session, - message_sent_at=sent_at, - session_snapshot=None, - reply_anchor_present=False, - ) - is False - ) - assert ( - KernelIntentRulesMixin._is_short_confirmation_message( - message_text="确认", - session=session, - message_sent_at=sent_at, - session_snapshot=None, - reply_anchor_present=True, - ) - is True - ) - - -def test_natural_revision_routes_to_draft_confirmation_without_reply_anchor() -> None: +def test_natural_revision_does_not_route_to_draft_confirmation_without_reply_anchor() -> None: session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM) assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( session=session, message_text="第一版先做直播录屏和评论回放切片,再加亮点和掉点总结,最后给一份复盘清单。", ingress_options=None, - ) is True + ) is False -def test_scope_narrowing_revision_routes_to_draft_confirmation_without_reply_anchor() -> None: +def test_scope_narrowing_revision_does_not_route_to_draft_confirmation_without_reply_anchor() -> None: session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM) assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( session=session, message_text="第一版先别做太大,先把三件事做好就行:直播录屏和评论回放切片、亮点和掉点自动总结、给团队一份能直接执行的复盘清单。", ingress_options=None, + ) is False + + +def test_continue_current_draft_does_not_route_to_draft_confirmation_without_reply_anchor() -> None: + session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM) + + assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( + session=session, + message_text="按这版继续", + ingress_options=None, + ) is False + + +def test_continue_current_draft_does_not_route_to_draft_confirmation_without_parse_action_dependency() -> None: + session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM) + + assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( + session=session, + message_text="按这版继续", + ingress_options=None, + ) is False + + +def test_reply_anchor_routes_to_draft_confirmation_without_revision_heuristics() -> None: + session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM) + + assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( + session=session, + message_text="保持当前草稿结构并补完整信息", + ingress_options={"thread_context": {"reply_to_message_id": "msg-123"}}, ) is True @@ -699,9 +1309,13 @@ async def test_should_route_to_draft_confirmation_session_does_not_hijack_explic called = True return True - monkeypatch.setattr(DraftConfirmHandler, "has_active_draft_session", _fake_has_active_draft_session) - kernel = FakeKernel() + monkeypatch.setattr(DraftConfirmHandler, "has_active_draft_session", _fake_has_active_draft_session) + monkeypatch.setattr( + kernel.__class__, + "_should_skip_no_anchor_draft_routing_by_bundle", + staticmethod(lambda **kwargs: asyncio.sleep(0, result=True)), + ) should_route = await kernel._should_route_to_draft_confirmation_session( session=SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM), sender="tester", @@ -731,6 +1345,38 @@ def test_should_inherit_recent_feishu_session_context_skips_explicit_new_require assert should_inherit is False +def test_should_inherit_recent_feishu_session_context_skips_draft_pending_confirm_session() -> None: + session = SimpleNamespace( + sender="feishu:tester", + last_active_at=datetime.now(), + last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM, + last_message_text="上一版草稿已经出来了", + ) + + should_inherit = KernelIntentResolveMixin._should_inherit_recent_feishu_session_context( + message_text="这个怎么做", + session=session, + ) + + assert should_inherit is False + + +def test_should_inherit_recent_feishu_session_context_skips_non_query_last_intent() -> None: + session = SimpleNamespace( + sender="feishu:tester", + last_active_at=datetime.now(), + last_intent="signal_source.onboard", + last_message_text="给项目2接一个 RTMP 信号源", + ) + + should_inherit = KernelIntentResolveMixin._should_inherit_recent_feishu_session_context( + message_text="这个怎么做", + session=session, + ) + + assert should_inherit is False + + @pytest.mark.asyncio async def test_explicit_new_requirement_clears_inherited_session_project_context() -> None: class _FakeDB: @@ -762,14 +1408,14 @@ async def test_explicit_new_requirement_clears_inherited_session_project_context message_text="跟你聊个新需求,关于换季系统自动推荐搭配衣服", ) - assert cleared is True - assert session.current_project_id is None + assert cleared is False + assert session.current_project_id == 104 assert session.last_intent == "query.read_only" @pytest.mark.asyncio @pytest.mark.parametrize("message_text", ["A", "确认", "你自行推荐"]) -async def test_orphan_requirement_followup_returns_direct_reprompt_when_no_active_session(message_text: str) -> None: +async def test_orphan_requirement_followup_without_active_requirement_context_is_ignored(message_text: str) -> None: class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin): def __init__(self) -> None: self.tenant_id = 1 @@ -788,10 +1434,48 @@ async def test_orphan_requirement_followup_returns_direct_reprompt_when_no_activ message_text=message_text, ) - assert result is not None - assert result["handled"] is True - assert result["direct_reply_required"] is True - assert "没有进行中的 A/B/C 收敛轮次" in str(result["direct_reply_text"] or "") + assert result is None + + +@pytest.mark.asyncio +async def test_orphan_requirement_followup_clears_stale_session_project_context() -> None: + class _FakeDB: + async def flush(self): + return None + + class FakeKernel( + mixin_entry.KernelIngressFlowEntryMixin, + ai_pmo_kernel_mixins_world_model_session.KernelWorldModelSessionMixin, + ): + def __init__(self) -> None: + self.db = _FakeDB() + self.tenant_id = 1 + + async def _has_active_requirement_plan_workspace(self, **kwargs): + _ = kwargs + return False + + kernel = FakeKernel() + session = SimpleNamespace( + current_project_id=104, + last_intent="query.read_only", + last_message_text="A", + ) + + result = await kernel._maybe_handle_orphan_requirement_followup( + source_system="feishu", + source_message_id="msg-orphan-reply-stale-project", + channel_ref="chat-active", + source_sender_open_id="ou_active", + ingress_options=None, + message_text="A", + session=session, + sender="tester", + ) + + assert result is None + assert session.current_project_id == 104 + assert session.last_intent == "query.read_only" @pytest.mark.asyncio @@ -817,6 +1501,482 @@ async def test_orphan_requirement_followup_skips_guard_when_active_requirement_s assert result is None +def test_world_model_session_project_inheritance_rejects_low_context_followup() -> None: + class FakeKernel(ai_pmo_kernel_mixins_world_model_session.KernelWorldModelSessionMixin): + pass + + kernel = FakeKernel() + session = SimpleNamespace( + sender="feishu:chat-1:ou_dm_1", + last_intent="query.read_only", + current_project_id=104, + last_active_at=datetime.now(), + ) + + assert kernel._extract_inherited_project_id(session=session, message_text="A") is None + assert kernel._extract_inherited_project_id(session=session, message_text="你自行推荐") is None + + +def test_world_model_session_project_inheritance_rejects_stale_low_context_previous_turn() -> None: + class FakeKernel(ai_pmo_kernel_mixins_world_model_session.KernelWorldModelSessionMixin): + pass + + kernel = FakeKernel() + session = SimpleNamespace( + sender="feishu:chat-1:ou_dm_1", + last_intent="query.read_only", + current_project_id=104, + last_message_text="A", + last_active_at=datetime.now(), + ) + + assert kernel._extract_inherited_project_id(session=session, message_text="这个怎么做") is None + + +def test_draft_confirmation_router_keeps_requirement_plan_field_list_in_plan_flow() -> None: + session = SimpleNamespace(last_intent="query.read_only") + + assert ( + mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( + session=session, + message_text="待办、责任人、截止时间", + ingress_options={"thread_context": {"reply_to_message_id": "msg-plan-root"}}, + ) + is False + ) + + +@pytest.mark.asyncio +async def test_active_requirement_followup_prefers_current_requirement_context(monkeypatch) -> None: + captured: dict[str, object] = {} + + class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + async def _find_active_requirement_plan_workspace(self, **kwargs): + captured["workspace_lookup"] = kwargs + return ( + SimpleNamespace(id=81, plan_status="collecting"), + SimpleNamespace(id=82, state="collecting"), + ) + + async def _build_requirement_planning_direct_result(self, **kwargs): + captured["direct_kwargs"] = kwargs + return { + "handled": True, + "status": "answered", + "classification": "question", + "direct_reply_required": True, + "direct_reply_text": "我先按当前需求把信号输入这一块补进去。", + "reply_template_key": "requirement_plan_followup", + } + + class FakeSemanticContextAssembler: + def __init__(self, db) -> None: + self.db = db + + async def assemble(self, **kwargs): + captured["packet_kwargs"] = kwargs + return SimpleNamespace( + mode_card=SimpleNamespace( + allowed_actions=[ + "reply.compose", + "context.followup.current_requirement", + "context.followup.independent_workflow", + ], + forbidden_actions=[], + ) + ) + + class FakeSemanticDecisionService: + def __init__(self, _llm_service) -> None: + pass + + async def decide(self, *, packet): + _ = packet + return SimpleNamespace( + topic_relation="continue_current_topic", + mode_decision="stay_plan", + followup_scope="current_requirement", + followup_action="none", + revision_note="", + assistant_reply="我先按当前需求把信号输入这一块补进去。", + requires_confirmation=False, + requested_actions=["reply.compose", "context.followup.current_requirement"], + ) + + monkeypatch.setattr(mixin_entry, "SemanticContextAssembler", FakeSemanticContextAssembler) + monkeypatch.setattr(mixin_entry, "SemanticDecisionService", FakeSemanticDecisionService) + + kernel = FakeKernel() + result = await kernel._maybe_handle_active_context_followup( + source_system="feishu", + source_message_id="msg-followup-1", + channel_ref="chat-active", + source_sender_open_id="ou_active", + message_text="信号源呢?", + thread_context=None, + sender="tester", + ) + + assert result is not None + assert result["handled"] is True + assert "信号输入" in result["direct_reply_text"] + assert captured["packet_kwargs"]["latest_user_message"] == "信号源呢?" + assert captured["packet_kwargs"]["ingress_context"]["candidate_kind"] == "active_context_followup" + assert captured["direct_kwargs"]["message_text"] == "信号源呢?" + + +@pytest.mark.asyncio +async def test_active_context_followup_prefers_active_mounted_draft_before_requirement_lookup(monkeypatch) -> None: + captured: dict[str, object] = {} + + class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + async def _find_active_requirement_plan_workspace(self, **kwargs): + _ = kwargs + raise AssertionError("active mounted draft should bind before requirement workspace lookup") + + class FakeSemanticContextAssembler: + def __init__(self, db) -> None: + self.db = db + + async def assemble(self, **kwargs): + captured["packet_kwargs"] = kwargs + return SimpleNamespace( + mode_card=SimpleNamespace( + allowed_actions=["reply.compose", "context.followup.current_draft"], + forbidden_actions=[], + ) + ) + + class FakeSemanticDecisionService: + def __init__(self, _llm_service) -> None: + pass + + async def decide(self, *, packet): + _ = packet + return SimpleNamespace( + topic_relation="continue_current_topic", + mode_decision="stay_plan", + followup_scope="current_draft", + followup_action="revise", + revision_note="补充外部信号源。", + assistant_reply="", + requires_confirmation=False, + requested_actions=["reply.compose", "context.followup.current_draft"], + ) + + async def _fake_resolve_active_mounted_draft(self, **kwargs): + _ = (self, kwargs) + return ( + SimpleNamespace(id=92, state="draft_pending_confirm", active_work_item_id=812), + SimpleNamespace(id=91, plan_status="draft_pending_confirm"), + ) + + async def _fake_handle_text_reply(self, **kwargs): + _ = self + captured["draft_kwargs"] = kwargs + return { + "handled": True, + "status": "pending_manual", + "followup_reply": "我先在当前草稿里补外部信号源。", + } + + monkeypatch.setattr(mixin_entry, "SemanticContextAssembler", FakeSemanticContextAssembler) + monkeypatch.setattr(mixin_entry, "SemanticDecisionService", FakeSemanticDecisionService) + monkeypatch.setattr(DraftConfirmHandler, "_resolve_active_mounted_draft", _fake_resolve_active_mounted_draft) + monkeypatch.setattr(DraftConfirmHandler, "handle_text_reply", _fake_handle_text_reply) + + kernel = FakeKernel() + result = await kernel._maybe_handle_active_context_followup( + source_system="feishu", + source_message_id="msg-followup-draft-priority-1", + channel_ref="chat-active", + source_sender_open_id="ou_active", + message_text="再出一版草稿", + thread_context=None, + sender="tester", + ) + + assert result is not None + assert result["handled"] is True + assert "当前草稿" in result["followup_reply"] + assert captured["packet_kwargs"]["topic_workspace_id"] == 91 + assert captured["packet_kwargs"]["ingress_context"]["active_context_kind"] == "draft_pending_confirm" + assert captured["draft_kwargs"]["semantic_followup_override"]["action"] == "revise" + + +@pytest.mark.asyncio +async def test_active_context_followup_routes_short_draft_module_question_to_current_draft(monkeypatch) -> None: + captured: dict[str, object] = {} + + class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + async def _find_active_requirement_plan_workspace(self, **kwargs): + captured["workspace_lookup"] = kwargs + return ( + SimpleNamespace(id=91, plan_status="awaiting_execute_confirm"), + SimpleNamespace(id=92, state="ready"), + ) + + class FakeSemanticContextAssembler: + def __init__(self, db) -> None: + self.db = db + + async def assemble(self, **kwargs): + captured["packet_kwargs"] = kwargs + return SimpleNamespace( + mode_card=SimpleNamespace( + allowed_actions=["reply.compose", "context.followup.current_draft"], + forbidden_actions=[], + ) + ) + + class FakeSemanticDecisionService: + def __init__(self, _llm_service) -> None: + pass + + async def decide(self, *, packet): + _ = packet + return SimpleNamespace( + topic_relation="continue_current_topic", + mode_decision="stay_plan", + followup_scope="current_draft", + followup_action="revise", + revision_note="补充当前草稿里的信号源输入与接入边界。", + assistant_reply="", + requires_confirmation=False, + requested_actions=["reply.compose", "context.followup.current_draft"], + ) + + async def _fake_handle_text_reply(self, **kwargs): + _ = self + captured["draft_kwargs"] = kwargs + return { + "handled": True, + "status": "pending_manual", + "followup_reply": "我先在当前草稿里补信号源输入。", + } + + monkeypatch.setattr(mixin_entry, "SemanticContextAssembler", FakeSemanticContextAssembler) + monkeypatch.setattr(mixin_entry, "SemanticDecisionService", FakeSemanticDecisionService) + monkeypatch.setattr(DraftConfirmHandler, "handle_text_reply", _fake_handle_text_reply) + + kernel = FakeKernel() + result = await kernel._maybe_handle_active_context_followup( + source_system="feishu", + source_message_id="msg-followup-2", + channel_ref="chat-active", + source_sender_open_id="ou_active", + message_text="信号源呢?", + thread_context=None, + sender="tester", + ) + + assert result is not None + assert result["handled"] is True + assert "当前草稿" in result["followup_reply"] + assert captured["draft_kwargs"]["message_text"] == "信号源呢?" + assert captured["draft_kwargs"]["semantic_followup_override"] == { + "action": "revise", + "revision_note": "补充当前草稿里的信号源输入与接入边界。", + "assistant_reply": "", + } + + +@pytest.mark.asyncio +async def test_active_context_followup_does_not_hijack_explicit_signal_source_request(monkeypatch) -> None: + captured: dict[str, object] = {} + + class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + async def _find_active_requirement_plan_workspace(self, **kwargs): + captured["workspace_lookup"] = kwargs + return ( + SimpleNamespace(id=91, plan_status="awaiting_execute_confirm"), + SimpleNamespace(id=92, state="ready"), + ) + + class FakeSemanticContextAssembler: + def __init__(self, db) -> None: + self.db = db + + async def assemble(self, **kwargs): + captured["packet_kwargs"] = kwargs + return SimpleNamespace( + mode_card=SimpleNamespace( + allowed_actions=["reply.compose", "context.followup.independent_workflow"], + forbidden_actions=[], + ) + ) + + class FakeSemanticDecisionService: + def __init__(self, _llm_service) -> None: + pass + + async def decide(self, *, packet): + _ = packet + return SimpleNamespace( + topic_relation="continue_current_topic", + mode_decision="answer_only", + followup_scope="independent_workflow", + followup_action="none", + revision_note="", + assistant_reply="", + requires_confirmation=False, + requested_actions=["reply.compose", "context.followup.independent_workflow"], + ) + + monkeypatch.setattr(mixin_entry, "SemanticContextAssembler", FakeSemanticContextAssembler) + monkeypatch.setattr(mixin_entry, "SemanticDecisionService", FakeSemanticDecisionService) + kernel = FakeKernel() + result = await kernel._maybe_handle_active_context_followup( + source_system="feishu", + source_message_id="msg-followup-3", + channel_ref="chat-active", + source_sender_open_id="ou_active", + message_text="给项目ID: 2 接一个 YouTube 录屏加评论回放的信号源,先不要执行,先出草稿。", + thread_context=None, + sender="tester", + ) + + assert result is None + assert captured["packet_kwargs"]["latest_user_message"] == "给项目ID: 2 接一个 YouTube 录屏加评论回放的信号源,先不要执行,先出草稿。" + + +@pytest.mark.asyncio +async def test_find_active_requirement_plan_workspace_includes_draft_pending_confirm_state( + requirement_plan_db_session, +) -> None: + class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin): + def __init__(self) -> None: + self.db = requirement_plan_db_session + self.tenant_id = 7 + + executor = FakeExecutor() + conversation_scope_key = build_feishu_conversation_scope_key( + source_chat_id="chat-draft-pending", + source_sender_open_id="ou-draft-pending", + sender=None, + ) + topic = MountedTopicWorkspace( + tenant_id=7, + topic_key="feishu:chat-draft-pending:message:msg-draft-pending-root", + source_system="feishu", + channel_ref="chat-draft-pending", + conversation_scope_key=conversation_scope_key, + mode="plan", + status="active", + plan_status="draft_pending_confirm", + metadata_json={"plan_mode": True}, + facts_json={"title": "换季穿搭助手"}, + ) + requirement_plan_db_session.add(topic) + await requirement_plan_db_session.flush() + requirement_plan_db_session.add( + MountedDraftWorkspace( + tenant_id=7, + topic_workspace_id=int(topic.id), + state="draft_pending_confirm", + active_work_item_id=321, + draft_version=1, + requirement_card={"title": "换季穿搭助手"}, + draft_payload={"title": "换季穿搭助手"}, + required_confirmations=["需求草稿需要人工确认后才允许正式创建"], + ) + ) + await requirement_plan_db_session.flush() + + found_topic, found_draft = await executor._find_active_requirement_plan_workspace( + source_system="feishu", + source_message_id="msg-followup-pending", + source_chat_id="chat-draft-pending", + sender=None, + source_sender_open_id="ou-draft-pending", + thread_context=None, + conversation_key=conversation_scope_key, + ) + + assert found_topic is not None + assert found_draft is not None + assert int(found_topic.id) == int(topic.id) + assert str(found_draft.state) == "draft_pending_confirm" + + +def test_world_model_session_project_inheritance_keeps_meaningful_followup() -> None: + class FakeKernel(ai_pmo_kernel_mixins_world_model_session.KernelWorldModelSessionMixin): + pass + + kernel = FakeKernel() + session = SimpleNamespace( + sender="feishu:chat-1:ou_dm_1", + last_intent="query.read_only", + current_project_id=104, + last_active_at=datetime.now(), + ) + + assert kernel._extract_inherited_project_id(session=session, message_text="这个怎么做") is None + + +@pytest.mark.asyncio +async def test_world_model_resolve_project_does_not_bind_low_context_session_project() -> None: + class _ProjectListResult: + def scalars(self): + return SimpleNamespace(all=lambda: []) + + class _ProjectOneResult: + def scalar_one_or_none(self): + return SimpleNamespace(id=104, title="跨境直播复盘", name="跨境直播复盘") + + class _FakeDB: + def __init__(self) -> None: + self.execute_calls = 0 + + async def execute(self, _query): + self.execute_calls += 1 + if self.execute_calls == 1: + return _ProjectListResult() + return _ProjectOneResult() + + class FakeKernel( + mixin_project.KernelWorldModelCoreProjectMixin, + ai_pmo_kernel_mixins_world_model_session.KernelWorldModelSessionMixin, + ): + def __init__(self) -> None: + self.db = _FakeDB() + self.tenant_id = 1 + + kernel = FakeKernel() + session = SimpleNamespace( + sender="feishu:chat-1:ou_dm_1", + last_intent="query.read_only", + current_project_id=104, + last_active_at=datetime.now(), + ) + + project = await kernel._resolve_project( + message_text="A", + project_hint_id=None, + query_tables=set(), + session=session, + ) + + assert project is None + assert kernel.db.execute_calls == 1 + + def test_mounted_runtime_query_business_review_uses_plan_mode_and_non_executor_agent() -> None: mode = mixin_entry.KernelIngressFlowEntryMixin._resolve_mounted_runtime_mode_override( workflow_route="query.answer", @@ -1097,6 +2257,8 @@ def test_coerce_rag_business_review_scene_keeps_rag_ask_on_query_chain() -> None rag_request_context={ "asked_via": "pmflow_manual", "action_mode": "auto", + "message_intent": "business_review", + "consultant_mode_preference": "clarify", }, semantic_bundle=None, world_model={}, @@ -1243,11 +2405,27 @@ def test_coerce_feishu_assignee_recommendation_to_query_chain_from_direct_metada assert coerced.metadata["workflow_route"] == "query.answer" +def test_should_inherit_recent_feishu_session_context_is_disabled_for_query_session() -> None: + session = SimpleNamespace( + sender="feishu:tester", + last_active_at=datetime.now(), + last_intent="query.read_only", + last_message_text="上一轮在聊项目画像", + ) + + should_inherit = KernelIntentResolveMixin._should_inherit_recent_feishu_session_context( + message_text="我需要对应的需求mvp", + session=session, + ) + + assert should_inherit is False + + @pytest.mark.asyncio -async def test_kernel_intent_resolve_inherits_recent_feishu_session_context_without_reply_anchor() -> None: +async def test_kernel_intent_resolve_does_not_inherit_recent_feishu_session_context_without_reply_anchor() -> None: captured: dict[str, object] = {} - class FakeResolver(KernelIntentResolveMixin): + class FakeResolver(KernelIntentResolveMixin, KernelIntentRulesMixin): def __init__(self) -> None: self.tenant_id = 1 @@ -1286,8 +2464,298 @@ async def test_kernel_intent_resolve_inherits_recent_feishu_session_context_with ) assert source == "llm_only" - assert captured["session_last_intent_override"] == "query.read_only" - assert captured["session_project_id_override"] == 21 + assert captured["session_last_intent_override"] == "" + assert captured["session_project_id_override"] is None + assert captured["inherit_session_context"] is False + + +@pytest.mark.asyncio +async def test_kernel_intent_resolve_short_confirm_does_not_inherit_query_session_intent() -> None: + captured: dict[str, object] = {} + + class FakeResolver(KernelIntentResolveMixin, KernelIntentRulesMixin): + def __init__(self) -> None: + self.tenant_id = 1 + + def _is_short_confirmation_message(self, **kwargs): + _ = kwargs + return True + + @staticmethod + def _intent_llm_enabled() -> bool: + return True + + async def _resolve_intent_with_llm(self, **kwargs): + captured.update(kwargs) + return CanonicalIntent( + name="query.read_only", + confidence=0.81, + project_id=None, + signal_title="", + signal_summary="", + metadata={}, + ) + + resolver = FakeResolver() + session = SimpleNamespace( + sender="feishu:chat-1:ou_dm_1", + last_intent="query.read_only", + current_project_id=21, + last_active_at=datetime.now(), + ) + + intent, source = await resolver._resolve_intent( + message_text="确认", + session=session, + world_model={}, + thread_context={"reply_to_message_id": "msg-1"}, + ) + + assert source == "llm_only" + assert intent.name == "query.read_only" + assert captured["inherit_session_context"] is True + + +@pytest.mark.asyncio +async def test_kernel_intent_resolve_short_confirm_stays_on_llm_path_with_bound_context() -> None: + captured: dict[str, object] = {} + + class FakeResolver(KernelIntentResolveMixin, KernelIntentRulesMixin): + def __init__(self) -> None: + self.tenant_id = 1 + + @staticmethod + def _intent_llm_enabled() -> bool: + return True + + async def _resolve_intent_with_llm(self, **kwargs): + captured.update(kwargs) + return CanonicalIntent( + name="followup.confirm", + confidence=0.91, + project_id=21, + signal_title="", + signal_summary="", + metadata={"followup_action": "confirm"}, + ) + + resolver = FakeResolver() + session = SimpleNamespace( + sender="feishu:chat-1:ou_dm_1", + last_intent="task.create", + current_project_id=21, + last_message_text="给项目 21 新建任务:补齐信号源配置", + last_active_at=datetime.now(), + ) + + intent, source = await resolver._resolve_intent( + message_text="确认", + session=session, + world_model={}, + thread_context={"reply_to_message_id": "msg-1"}, + ) + + assert source == "llm_only" + assert intent.name == "followup.confirm" + assert intent.project_id == 21 + assert captured["inherit_session_context"] is True + assert captured["session_last_intent_override"] == "task.create" + + +@pytest.mark.asyncio +async def test_kernel_intent_resolve_suppresses_session_context_for_low_context_followup() -> None: + captured: dict[str, object] = {} + + class FakeResolver(KernelIntentResolveMixin): + def __init__(self) -> None: + self.tenant_id = 1 + + def _is_short_confirmation_message(self, **kwargs): + _ = kwargs + return False + + @staticmethod + def _intent_llm_enabled() -> bool: + return True + + async def _resolve_intent_with_llm(self, **kwargs): + captured.update(kwargs) + return CanonicalIntent( + name="query.read_only", + confidence=0.79, + project_id=None, + signal_title="", + signal_summary="", + metadata={}, + ) + + resolver = FakeResolver() + session = SimpleNamespace( + sender="feishu:chat-1:ou_dm_1", + last_intent="query.read_only", + current_project_id=104, + last_active_at=datetime.now(), + ) + + _, source = await resolver._resolve_intent( + message_text="A", + session=session, + world_model={}, + thread_context=None, + ) + + assert source == "llm_only" + assert captured["session_last_intent_override"] == "" + assert captured["session_project_id_override"] is None + assert captured["inherit_session_context"] is False + + +def test_recover_session_followup_write_intent_skips_query_parent_even_if_previous_text_looks_like_write() -> None: + class FakeResolver(KernelIntentRulesMixin, KernelIntentResolveMixin): + def __init__(self) -> None: + self.tenant_id = 1 + self.intent_resolver = SimpleNamespace(resolve=lambda **kwargs: CanonicalIntent("project.create", 0.9, 12, "", "", {})) + + resolver = FakeResolver() + session = SimpleNamespace( + last_intent="query.read_only", + last_message_text="帮我创建一个新项目,名字叫 Alpha", + current_project_id=12, + last_active_at=datetime.now(), + ) + rule_intent = CanonicalIntent("query.read_only", 0.6, None, "", "", {"fallback": True, "reason": "default_query_fallback"}) + + recovered = resolver._recover_session_followup_write_intent( + message_text="确认", + session=session, + world_model={}, + rule_intent=rule_intent, + ) + + assert recovered is None + + +def test_recover_session_followup_write_intent_is_disabled_even_for_write_parent() -> None: + class FakeResolver(KernelIntentRulesMixin, KernelIntentResolveMixin): + def __init__(self) -> None: + self.tenant_id = 1 + self.intent_resolver = SimpleNamespace(resolve=lambda **kwargs: CanonicalIntent("project.create", 0.9, 12, "", "", {})) + + resolver = FakeResolver() + session = SimpleNamespace( + last_intent="project.create", + last_message_text="帮我创建一个新项目,名字叫 Alpha", + current_project_id=12, + last_active_at=datetime.now(), + ) + rule_intent = CanonicalIntent("query.read_only", 0.6, None, "", "", {"fallback": True, "reason": "default_query_fallback"}) + + recovered = resolver._recover_session_followup_write_intent( + message_text="确认", + session=session, + world_model={}, + rule_intent=rule_intent, + ) + + assert recovered is None + + +@pytest.mark.asyncio +async def test_kernel_intent_resolve_suppresses_session_context_after_low_context_previous_turn() -> None: + captured: dict[str, object] = {} + + class FakeResolver(KernelIntentResolveMixin): + def __init__(self) -> None: + self.tenant_id = 1 + + def _is_short_confirmation_message(self, **kwargs): + _ = kwargs + return False + + @staticmethod + def _intent_llm_enabled() -> bool: + return True + + async def _resolve_intent_with_llm(self, **kwargs): + captured.update(kwargs) + return CanonicalIntent( + name="query.read_only", + confidence=0.79, + project_id=None, + signal_title="", + signal_summary="", + metadata={}, + ) + + resolver = FakeResolver() + session = SimpleNamespace( + sender="feishu:chat-1:ou_dm_1", + last_intent="query.read_only", + current_project_id=104, + last_message_text="A", + last_active_at=datetime.now(), + ) + + _, source = await resolver._resolve_intent( + message_text="这个怎么做", + session=session, + world_model={}, + thread_context=None, + ) + + assert source == "llm_only" + assert captured["session_last_intent_override"] == "" + assert captured["session_project_id_override"] is None + assert captured["inherit_session_context"] is False + + +@pytest.mark.asyncio +async def test_kernel_intent_resolve_no_longer_calls_short_confirmation_helper() -> None: + captured: dict[str, object] = {} + + class FakeResolver(KernelIntentResolveMixin): + def __init__(self) -> None: + self.tenant_id = 1 + + def _is_short_confirmation_message(self, **kwargs): + _ = kwargs + raise AssertionError("legacy short confirmation helper should not be used") + + @staticmethod + def _intent_llm_enabled() -> bool: + return True + + async def _resolve_intent_with_llm(self, **kwargs): + captured.update(kwargs) + return CanonicalIntent( + name="query.read_only", + confidence=0.79, + project_id=None, + signal_title="", + signal_summary="", + metadata={}, + ) + + resolver = FakeResolver() + session = SimpleNamespace( + sender="feishu:chat-1:ou_dm_1", + last_intent="project.create", + current_project_id=104, + last_message_text="帮我创建一个新项目", + last_active_at=datetime.now(), + ) + + _, source = await resolver._resolve_intent( + message_text="确认", + session=session, + world_model={}, + thread_context={"reply_to_message_id": "reply-1"}, + ) + + assert source == "llm_only" + assert captured["session_last_intent_override"] == "project.create" + assert captured["session_project_id_override"] == 104 + assert captured["inherit_session_context"] is True @pytest.mark.asyncio @@ -1395,6 +2863,76 @@ async def test_kernel_intent_llm_prompt_guides_natural_problem_statement_to_proj assert "先帮我分析" in prompt +@pytest.mark.asyncio +async def test_kernel_intent_llm_prompt_can_suppress_stale_session_project(monkeypatch) -> None: + captured: dict[str, object] = {} + + class FakeResolver(KernelIntentLLMMixin): + def __init__(self) -> None: + self.tenant_id = 1 + self.db = object() + + def _build_canonical_intent(self, *, intent_name, message_text, project_id, confidence, metadata): # noqa: ANN001 + return CanonicalIntent( + name=intent_name, + confidence=confidence, + project_id=project_id, + signal_title="", + signal_summary=message_text[:300], + metadata=metadata, + ) + + async def fake_generate_structured_object(prompt: str, **kwargs): # noqa: ANN001 + captured["prompt"] = prompt + captured["kwargs"] = kwargs + return StructuredExtractionResult( + payload={ + "family": "query", + "action": "answer", + "intent": "query.read_only", + "confidence": 0.74, + "project_id": 0, + "requested_count": 1, + "requires_confirmation": False, + "parameters": {}, + "reasoning": "低上下文输入,不能继承过期项目。", + }, + source_field="generated_text", + provider="mock", + model="mock-model", + raw_text='{"intent":"query.read_only"}', + validation="ok", + ) + + monkeypatch.setattr( + "app.services.ai_pmo.ai_pmo_kernel.mixins.intent.ai_pmo_kernel_mixins_intent_llm.llm_service.generate_structured_object", + fake_generate_structured_object, + ) + + resolver = FakeResolver() + session = SimpleNamespace( + sender="feishu:chat-1:ou_dm_1", + last_intent="query.read_only", + current_project_id=104, + last_active_at=datetime.now(), + ) + intent = await resolver._resolve_intent_with_llm( + message_text="A", + session=session, + world_model={}, + rule_intent=None, + session_last_intent_override="", + session_project_id_override=None, + inherit_session_context=False, + ) + + assert intent is not None + prompt = str(captured["prompt"] or "") + assert "- session_last_intent: " in prompt + assert "- session_project_id: None" in prompt + assert "104" not in prompt + + def test_reply_renderer_formats_project_delete_pending_and_success() -> None: renderer = ReplyRenderer( state_waiting_manual="waiting_manual", @@ -1438,6 +2976,74 @@ def test_reply_renderer_formats_project_delete_pending_and_success() -> None: assert "已删除项目#12《重复项目》" in success["text"] +@pytest.mark.asyncio +async def test_feishu_outbox_service_enqueue_preserves_requirement_plan_text_above_2000_chars( + feishu_outbox_db_session, +) -> None: + text = _build_long_requirement_plan_reply_text() + assert len(text) > 2000 + + outbox_id = await FeishuOutboxService.enqueue( + feishu_outbox_db_session, + tenant_id=3, + chat_id="oc_requirement_plan_budget", + text=text, + source_message_id="msg-requirement-plan-budget-service", + ) + + row = ( + await feishu_outbox_db_session.execute( + select(FeishuReplyOutbox).where(FeishuReplyOutbox.id == int(outbox_id or 0)) + ) + ).scalar_one() + assert row.message_text == text + assert "C. 长期能力底座" in row.message_text + assert "⭐ 当前我更推荐:B" in row.message_text + + +@pytest.mark.asyncio +async def test_feishu_reply_outbox_mixin_enqueue_preserves_requirement_plan_text_above_2000_chars( + monkeypatch, + feishu_outbox_db_session, +) -> None: + maker = async_sessionmaker(feishu_outbox_db_session.bind, expire_on_commit=False) + + class FakeReply(feishu_mixin_reply_outbox.FeishuReplyOutboxMixin): + def __init__(self) -> None: + self.tenant_id = 3 + + @staticmethod + def _build_mention_prefix(*, open_id=None, display_name=None): # noqa: ANN001 + _ = (open_id, display_name) + return "" + + @staticmethod + async def _notify_outbox_ready(db, *, outbox_id): # noqa: ANN001 + _ = (db, outbox_id) + return None + + monkeypatch.setattr(feishu_mixin_reply_outbox, "AsyncSessionLocal", maker) + text = _build_long_requirement_plan_reply_text() + assert len(text) > 2000 + + outbox_id = await FakeReply()._enqueue_reply_outbox( + chat_id="oc_requirement_plan_budget", + message_text=text, + trace_id="trace-requirement-plan-budget", + source_message_id="msg-requirement-plan-budget-mixin", + ) + + async with maker() as verify_session: + row = ( + await verify_session.execute( + select(FeishuReplyOutbox).where(FeishuReplyOutbox.id == int(outbox_id)) + ) + ).scalar_one() + assert row.message_text == text + assert "C. 长期能力底座" in row.message_text + assert "⭐ 当前我更推荐:B" in row.message_text + + @pytest.mark.asyncio async def test_finalize_waiting_manual_passes_execution_result_to_renderer(monkeypatch) -> None: captured: dict[str, object] = {} @@ -1602,6 +3208,8 @@ async def test_execute_query_intent_honors_rag_request_context(monkeypatch) -> N self.db = object() self.tenant_id = 3 + _install_requirement_plan_query_bundle(monkeypatch) + _install_requirement_plan_query_bundle(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() intent = SimpleNamespace( @@ -1825,7 +3433,41 @@ async def test_execute_query_intent_does_not_rollback_healthy_session_on_rag_fai def _build_query_error_message(exc: Exception) -> str: return f"query failed: {exc}" + async def _fake_aplan_message(*, message_text: str, source_system: str, metadata=None): + _ = (message_text, metadata) + return WorkflowBundle( + bundle_id="bundle-plan-rollback", + semantic_version="v1", + source_system=source_system, + primary_workflow="query_flow", + workflows=[ + WorkflowPlan( + workflow_type="query_flow", + workflow_action="answer", + target_scope="global", + operation_codes=["query.answer"], + requires_confirmation=False, + confidence=0.9, + metadata={"message_intent": "requirement_plan"}, + ) + ], + message_gate="auto", + reply_contract=ReplyContract(mode="text", template_key="workflow_bundle", summary="query_flow"), + diagnostics={ + "message_intent": "requirement_plan", + "consultant_mode_preference": "clarify", + "topic_switch": "new", + }, + ) + + _install_requirement_plan_round_llm(monkeypatch) + _install_requirement_plan_query_bundle(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + monkeypatch.setattr( + mixin_execute_query_project.WorkflowSemanticPlanner, + "aplan_message", + _fake_aplan_message, + ) executor = FakeExecutor() intent = SimpleNamespace(project_id=12, metadata={}) @@ -1888,6 +3530,83 @@ async def test_execute_query_intent_short_circuits_casual_chat_without_rag(monke assert str(result["rag_result"]["query_type"]) == "chat" +@pytest.mark.asyncio +async def test_execute_query_intent_backfills_semantic_bundle_for_casual_chat_without_legacy_helpers( + monkeypatch, +) -> None: + planner_calls: list[dict[str, object]] = [] + + class FakeAdaptiveRAGService: + def __init__(self, db, tenant_id: int): + _ = (db, tenant_id) + + async def answer_question(self, **kwargs): + _ = kwargs + raise AssertionError("casual chat should not enter heavy RAG path") + + class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 3 + + async def _fake_aplan_message(*, message_text: str, source_system: str, metadata=None): + planner_calls.append( + { + "message_text": message_text, + "source_system": source_system, + "metadata": dict(metadata or {}), + } + ) + return WorkflowBundle( + bundle_id="bundle-casual", + semantic_version="v1", + source_system=source_system, + primary_workflow="query_flow", + workflows=[ + WorkflowPlan( + workflow_type="query_flow", + workflow_action="answer", + target_scope="global", + operation_codes=["query.answer"], + requires_confirmation=False, + confidence=0.95, + metadata={"message_intent": "casual_chat"}, + ) + ], + message_gate="auto", + reply_contract=ReplyContract(mode="text", template_key="workflow_bundle", summary="query_flow"), + diagnostics={ + "message_intent": "casual_chat", + "consultant_mode_preference": "brainstorm", + }, + ) + + def _legacy_helper_should_not_run(*args, **kwargs): # noqa: ANN002, ANN003 + raise AssertionError("legacy semantic helper should not run") + + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + monkeypatch.setattr( + mixin_execute_query_project.WorkflowSemanticPlanner, + "aplan_message", + _fake_aplan_message, + ) + executor = FakeExecutor() + intent = SimpleNamespace(project_id=None, metadata={}) + + result = await executor._execute_query_intent( + intent=intent, + message_text="早上好,今天你在线吗", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-chat-backfill-1", + ) + + assert planner_calls, "missing semantic bundle should be backfilled through aplan_message" + assert result["status"] == "succeeded" + assert "我在线" in str(result["answer"] or "") + assert str(result["rag_result"]["query_type"]) == "chat" + + @pytest.mark.asyncio async def test_execute_query_intent_short_circuits_requirement_followup_clarify_without_rag(monkeypatch) -> None: class FakeAdaptiveRAGService: @@ -1951,7 +3670,41 @@ async def test_execute_query_intent_short_circuits_requirement_planning_without_ self.db = object() self.tenant_id = 3 + async def _fake_aplan_message(*, message_text: str, source_system: str, metadata=None): + _ = (message_text, metadata) + return WorkflowBundle( + bundle_id="bundle-plan-direct", + semantic_version="v1", + source_system=source_system, + primary_workflow="query_flow", + workflows=[ + WorkflowPlan( + workflow_type="query_flow", + workflow_action="answer", + target_scope="global", + operation_codes=["query.answer"], + requires_confirmation=False, + confidence=0.91, + metadata={"message_intent": "requirement_plan"}, + ) + ], + message_gate="auto", + reply_contract=ReplyContract(mode="text", template_key="workflow_bundle", summary="query_flow"), + diagnostics={ + "message_intent": "requirement_plan", + "consultant_mode_preference": "clarify", + "topic_switch": "new", + }, + ) + + _install_requirement_plan_round_llm(monkeypatch) + _install_requirement_plan_query_bundle(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + monkeypatch.setattr( + mixin_execute_query_project.WorkflowSemanticPlanner, + "aplan_message", + _fake_aplan_message, + ) executor = FakeExecutor() intent = SimpleNamespace(project_id=None, metadata={}) @@ -1966,11 +3719,89 @@ async def test_execute_query_intent_short_circuits_requirement_planning_without_ answer = str(result["answer"] or "") assert result["status"] == "succeeded" assert "不创建" in answer - assert "哪一步" in answer + assert "这类 IT 项目来收" in answer + assert "⭐ 当前我更推荐:B" in answer assert answer.count("?") <= 1 assert str(result["rag_result"]["query_type"]) == "plan" +@pytest.mark.asyncio +async def test_execute_query_intent_backfills_semantic_bundle_for_requirement_plan_without_legacy_helpers( + monkeypatch, +) -> None: + planner_calls: list[dict[str, object]] = [] + + class FakeAdaptiveRAGService: + def __init__(self, db, tenant_id: int): + _ = (db, tenant_id) + + async def answer_question(self, **kwargs): + _ = kwargs + raise AssertionError("requirement planning should not enter heavy RAG path") + + class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 3 + + async def _fake_aplan_message(*, message_text: str, source_system: str, metadata=None): + planner_calls.append( + { + "message_text": message_text, + "source_system": source_system, + "metadata": dict(metadata or {}), + } + ) + return WorkflowBundle( + bundle_id="bundle-plan", + semantic_version="v1", + source_system=source_system, + primary_workflow="query_flow", + workflows=[ + WorkflowPlan( + workflow_type="query_flow", + workflow_action="answer", + target_scope="global", + operation_codes=["query.answer"], + requires_confirmation=False, + confidence=0.91, + metadata={"message_intent": "requirement_plan"}, + ) + ], + message_gate="auto", + reply_contract=ReplyContract(mode="text", template_key="workflow_bundle", summary="query_flow"), + diagnostics={ + "message_intent": "requirement_plan", + "consultant_mode_preference": "clarify", + "topic_switch": "new", + }, + ) + + _install_requirement_plan_round_llm(monkeypatch) + _install_requirement_plan_query_bundle(monkeypatch) + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + monkeypatch.setattr( + mixin_execute_query_project.WorkflowSemanticPlanner, + "aplan_message", + _fake_aplan_message, + ) + executor = FakeExecutor() + intent = SimpleNamespace(project_id=None, metadata={}) + + result = await executor._execute_query_intent( + intent=intent, + message_text="我们想做一个给电动车品牌用的直播复盘工具,先别创建项目,我还在补范围。", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-chat-backfill-plan-1", + ) + + assert planner_calls, "missing requirement metadata should be backfilled through aplan_message" + assert result["status"] == "succeeded" + assert "不创建" in str(result["answer"] or "") + assert str(result["rag_result"]["query_type"]) == "plan" + + @pytest.mark.asyncio async def test_execute_query_intent_requirement_plan_scene_short_circuits_without_rag(monkeypatch) -> None: class FakeAdaptiveRAGService: @@ -1986,6 +3817,8 @@ async def test_execute_query_intent_requirement_plan_scene_short_circuits_withou self.db = object() self.tenant_id = 3 + _install_requirement_plan_round_llm(monkeypatch) + _install_requirement_plan_query_bundle(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() intent = SimpleNamespace( @@ -2006,9 +3839,9 @@ async def test_execute_query_intent_requirement_plan_scene_short_circuits_withou answer = str(result["answer"] or "") assert result["status"] == "succeeded" - assert "主要想根据什么来打" in answer - assert "三件事" not in answer - assert answer.count("?") <= 1 + assert "这类 IT 项目来收" in answer + assert "⭐ 当前我更推荐:B" in answer + assert "A." in answer and "B." in answer and "C." in answer assert str(result["rag_result"]["query_type"]) == "plan" @@ -2030,7 +3863,41 @@ async def test_execute_query_intent_requirement_plan_followup_persists_progress_ self.db = requirement_plan_db_session self.tenant_id = 3 + async def _fake_aplan_message(*, message_text: str, source_system: str, metadata=None): + _ = (message_text, metadata) + return WorkflowBundle( + bundle_id="bundle-plan-draft-friendly", + semantic_version="v1", + source_system=source_system, + primary_workflow="query_flow", + workflows=[ + WorkflowPlan( + workflow_type="query_flow", + workflow_action="answer", + target_scope="global", + operation_codes=["query.answer"], + requires_confirmation=False, + confidence=0.92, + metadata={"message_intent": "requirement_plan"}, + ) + ], + message_gate="auto", + reply_contract=ReplyContract(mode="text", template_key="workflow_bundle", summary="query_flow"), + diagnostics={ + "message_intent": "requirement_plan", + "consultant_mode_preference": "clarify", + "topic_switch": "new", + }, + ) + + _install_requirement_plan_round_llm(monkeypatch) + _install_requirement_plan_query_bundle(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + monkeypatch.setattr( + mixin_execute_query_project.WorkflowSemanticPlanner, + "aplan_message", + _fake_aplan_message, + ) executor = FakeExecutor() initial_intent = SimpleNamespace( @@ -2065,7 +3932,7 @@ async def test_execute_query_intent_requirement_plan_followup_persists_progress_ ) second = await executor._execute_query_intent( intent=followup_intent, - message_text="先按技术指标来,主要给投顾看。", + message_text="A,主要给投顾看。", operator_user_id=999, source_system="feishu", source_message_id="msg-plan-reply-1", @@ -2073,28 +3940,41 @@ async def test_execute_query_intent_requirement_plan_followup_persists_progress_ assert first["status"] == "succeeded" assert second["status"] == "succeeded" - assert "根据什么来打" in str(first["answer"] or "") - assert "我先记下" in str(second["answer"] or "") - assert "技术指标" in str(second["answer"] or "") - assert "主要想根据什么来打" not in str(second["answer"] or "") - assert "怎么接收" in str(second["answer"] or "") or "输出" in str(second["answer"] or "") + assert "这类 IT 项目来收" in str(first["answer"] or "") + assert "⭐ 当前我更推荐:B" in str(first["answer"] or "") + assert "技术路线定住" in str(second["answer"] or "") + assert "A." in str(second["answer"] or "") + assert "B." in str(second["answer"] or "") + assert "C." in str(second["answer"] or "") topic = ( await requirement_plan_db_session.execute( - select(MountedTopicWorkspace).where(MountedTopicWorkspace.tenant_id == 3) + select(MountedTopicWorkspace) + .where( + MountedTopicWorkspace.tenant_id == 3, + MountedTopicWorkspace.channel_ref == "chat-plan-1", + ) + .order_by(MountedTopicWorkspace.id.desc()) + .limit(1) ) ).scalar_one() draft = ( await requirement_plan_db_session.execute( - select(MountedDraftWorkspace).where(MountedDraftWorkspace.tenant_id == 3) + select(MountedDraftWorkspace) + .join(MountedTopicWorkspace, MountedTopicWorkspace.id == MountedDraftWorkspace.topic_workspace_id) + .where( + MountedDraftWorkspace.tenant_id == 3, + MountedTopicWorkspace.channel_ref == "chat-plan-1", + ) + .order_by(MountedDraftWorkspace.id.desc()) + .limit(1) ) ).scalar_one() card = draft.requirement_card or {} assert topic.mode == "plan" assert draft.state == "collecting" - assert card["scoring_basis"] - assert "技术指标" in str(card["scoring_basis"]) + assert card["project_nature"] == "轻量打分工具" assert "投顾" in str(card["target_user"]) @@ -2116,6 +3996,8 @@ async def test_execute_query_intent_requirement_plan_builds_feishu_friendly_draf self.db = requirement_plan_db_session self.tenant_id = 3 + _install_requirement_plan_round_llm(monkeypatch) + _install_requirement_plan_query_bundle(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() @@ -2151,33 +4033,41 @@ async def test_execute_query_intent_requirement_plan_builds_feishu_friendly_draf ) await executor._execute_query_intent( intent=intent_followup, - message_text="先按技术指标来,主要给投顾看。", + message_text="A", operator_user_id=999, source_system="feishu", source_message_id="msg-plan-reply-2a", ) await executor._execute_query_intent( intent=intent_followup, - message_text="先做成飞书日报,每天收盘后给一个分数和一句解释。", + message_text="主要给投顾看。", operator_user_id=999, source_system="feishu", source_message_id="msg-plan-reply-2b", ) + await executor._execute_query_intent( + intent=intent_followup, + message_text="任务编排平台版,用 FastAPI + React + Redis + Celery + PostgreSQL。", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-reply-2c", + ) final_result = await executor._execute_query_intent( intent=intent_followup, message_text="第一版就先保留分数、方向判断和一句解释,别做太重。", operator_user_id=999, source_system="feishu", - source_message_id="msg-plan-reply-2c", + source_message_id="msg-plan-reply-2d", ) answer = str(final_result["answer"] or "") assert final_result["status"] == "succeeded" - assert "需求草稿" in answer - assert "【需求名称】" in answer - assert "【打分依据】" in answer - assert "【输出形式】" in answer - assert "【更新频率】" in answer + assert "📋 我先按当前收敛结果" in answer + assert "🧾【需求名称】" in answer + assert "🏗️【项目性质】" in answer + assert "👥【服务对象】" in answer + assert "⚙️【技术路线】" in answer + assert "🧱【第一版范围】" in answer assert str(final_result["rag_result"]["query_type"]) == "draft" assert final_result["reply_template_key"] == "draft_pending_confirm" assert final_result["draft_artifact"]["promote_to_confirmation"] is True @@ -2195,7 +4085,7 @@ async def test_execute_query_intent_requirement_plan_builds_feishu_friendly_draf assert draft.state == "ready" assert "每日 A 股趋势打分工具" in str((draft.draft_payload or {}).get("title") or "") assert draft.active_work_item_id is None - assert draft.latest_source_message_id == "msg-plan-reply-2c" + assert draft.latest_source_message_id == "msg-plan-reply-2d" assert draft.latest_reply_message_id is None assert topic.plan_status == "awaiting_execute_confirm" assert topic.pending_actions == ["confirm_requirement_draft"] @@ -2219,6 +4109,8 @@ async def test_execute_query_intent_explicit_new_requirement_topic_suspends_prev self.db = requirement_plan_db_session self.tenant_id = 3 + _install_requirement_plan_round_llm(monkeypatch) + _install_requirement_plan_query_bundle(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() @@ -2265,8 +4157,10 @@ async def test_execute_query_intent_explicit_new_requirement_topic_suspends_prev ) ).scalars().all() + answer = str(switch_result["answer"] or "") assert len(rows) == 2 - assert "切到新需求" in str(switch_result["answer"] or "") + assert "🧭 我先按" in answer + assert "A." in answer and "B." in answer and "C." in answer assert rows[0].status == "suspended" assert rows[0].plan_status == "suspended" assert rows[1].status == "active" @@ -2291,6 +4185,8 @@ async def test_execute_query_intent_explicit_new_it_requirement_overrides_stale_ self.db = requirement_plan_db_session self.tenant_id = 3 + _install_requirement_plan_round_llm(monkeypatch) + _install_requirement_plan_query_bundle(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() @@ -2375,7 +4271,7 @@ async def test_execute_query_intent_explicit_new_it_requirement_overrides_stale_ first_answer = str(first["answer"] or "") second_answer = str(second["answer"] or "") - assert "🧭 我先按“批量自动化部署 OpenClaw”这类 IT 项目来收。" in first_answer + assert "🧭 我先按“OpenClaw 批量自动化部署平台”这类 IT 项目来收。" in first_answer assert "云原生交付底座" in first_answer assert "目标对象" not in first_answer assert "A股趋势打分" not in first_answer @@ -2419,6 +4315,7 @@ async def test_execute_query_intent_explicit_new_requirement_topic_reuses_curren self.db = requirement_plan_db_session self.tenant_id = 3 + _install_requirement_plan_round_llm(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() @@ -2521,6 +4418,7 @@ async def test_execute_query_intent_explicit_new_requirement_topic_with_subject_ self.db = requirement_plan_db_session self.tenant_id = 3 + _install_requirement_plan_round_llm(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() @@ -2589,7 +4487,8 @@ async def test_execute_query_intent_explicit_new_requirement_topic_with_subject_ answer = str(result["answer"] or "") assert "你先发一句这次真正想解决什么问题" not in answer - assert "主要给谁用" in answer + assert "🧭 我先按“换季系统自动推荐搭配衣服”这类 IT 项目来收。" in answer + assert "⭐ 当前我更推荐:B" in answer assert "会议纪要" not in answer assert "输入先按用户当前需求描述来" not in answer assert "A." in answer @@ -2617,6 +4516,7 @@ async def test_execute_query_intent_low_overlap_requirement_seed_switches_new_to self.db = requirement_plan_db_session self.tenant_id = 3 + _install_requirement_plan_round_llm(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() @@ -2721,6 +4621,7 @@ async def test_execute_query_intent_requirement_plan_accepts_fragmented_short_an self.db = requirement_plan_db_session self.tenant_id = 3 + _install_requirement_plan_round_llm(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() @@ -2754,7 +4655,7 @@ async def test_execute_query_intent_requirement_plan_accepts_fragmented_short_an ) second = await executor._execute_query_intent( intent=fragmented_followup_intent, - message_text="技术指标吧", + message_text="A", operator_user_id=999, source_system="feishu", source_message_id="msg-plan-reply-3a", @@ -2768,40 +4669,31 @@ async def test_execute_query_intent_requirement_plan_accepts_fragmented_short_an ) fourth = await executor._execute_query_intent( intent=fragmented_followup_intent, - message_text="飞书日报就行", + message_text="B", operator_user_id=999, source_system="feishu", source_message_id="msg-plan-reply-3c", ) - fifth = await executor._execute_query_intent( - intent=fragmented_followup_intent, - message_text="每天收盘后发", - operator_user_id=999, - source_system="feishu", - source_message_id="msg-plan-reply-3d", - ) final_result = await executor._execute_query_intent( intent=fragmented_followup_intent, message_text="第一版先保留分数、方向判断和一句解释就够了", operator_user_id=999, source_system="feishu", - source_message_id="msg-plan-reply-3e", + source_message_id="msg-plan-reply-3d", ) - assert "根据什么来打" in str(first["answer"] or "") - assert "给谁看" in str(second["answer"] or "") + assert "这类 IT 项目来收" in str(first["answer"] or "") + assert "服务对象定住" in str(second["answer"] or "") assert str(second["rag_result"]["query_type"]) == "plan" - assert "怎么接收" in str(third["answer"] or "") + assert "技术路线定住" in str(third["answer"] or "") assert str(third["rag_result"]["query_type"]) == "plan" - assert "什么时候更新" in str(fourth["answer"] or "") + assert "第一版先证明什么价值" in str(fourth["answer"] or "") assert str(fourth["rag_result"]["query_type"]) == "plan" - assert "先保留哪些输出" in str(fifth["answer"] or "") - assert str(fifth["rag_result"]["query_type"]) == "plan" final_answer = str(final_result["answer"] or "") - assert "需求草稿" in final_answer - assert "【需求名称】" in final_answer - assert "【第一版范围】" in final_answer + assert "📋 我先按当前收敛结果" in final_answer + assert "🧾【需求名称】" in final_answer + assert "🧱【第一版范围】" in final_answer assert str(final_result["rag_result"]["query_type"]) == "draft" draft = ( @@ -2810,15 +4702,14 @@ async def test_execute_query_intent_requirement_plan_accepts_fragmented_short_an ) ).scalar_one() card = draft.requirement_card or {} - assert card["scoring_basis"] + assert card["project_nature"] == "轻量打分工具" assert "投顾" in str(card["target_user"] or "") - assert "飞书日报" in str(card["output_form"] or "") - assert "收盘后" in str(card["cadence"] or "") + assert "评分服务版" in str(card["tech_route"] or "") assert "方向判断" in str(card["first_scope"] or "") @pytest.mark.asyncio -async def test_execute_query_intent_requirement_plan_generalizes_for_meeting_minutes_tool( +async def test_execute_query_intent_requirement_plan_defaults_to_it_pm_template_for_current_system( monkeypatch, requirement_plan_db_session, ) -> None: @@ -2828,13 +4719,14 @@ async def test_execute_query_intent_requirement_plan_generalizes_for_meeting_min async def answer_question(self, **kwargs): _ = kwargs - raise AssertionError("generic requirement plan should not enter heavy RAG path") + raise AssertionError("PMFlow IT requirement plan should not enter heavy RAG path") class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin): def __init__(self) -> None: self.db = requirement_plan_db_session self.tenant_id = 3 + _install_requirement_plan_round_llm(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() @@ -2861,61 +4753,67 @@ async def test_execute_query_intent_requirement_plan_generalizes_for_meeting_min first = await executor._execute_query_intent( intent=root_intent, - message_text="想跟你聊个需求,我们开完会以后待办经常散掉,想做个小工具自动从会议纪要里提待办,先别创建,先收第一版。", + message_text="想跟你聊个需求,做一个企业级项目管理系统里的需求收敛功能,先别创建,先收第一版。", operator_user_id=999, source_system="feishu", source_message_id="msg-plan-meeting-root", ) second = await executor._execute_query_intent( intent=followup_intent, - message_text="先给内部 PM 和研发负责人用。", + message_text="B,先做发布治理平台。", operator_user_id=999, source_system="feishu", source_message_id="msg-plan-meeting-2", ) third = await executor._execute_query_intent( intent=followup_intent, - message_text="飞书总结就行,不用单独页面。", + message_text="B,主要给项目管理团队用。", operator_user_id=999, source_system="feishu", source_message_id="msg-plan-meeting-3", ) fourth = await executor._execute_query_intent( intent=followup_intent, - message_text="每次会后半小时内整理出来。", + message_text="B,走任务编排平台版,用 FastAPI + React + Redis + Celery + Postgres。", operator_user_id=999, source_system="feishu", source_message_id="msg-plan-meeting-4", ) final_result = await executor._execute_query_intent( intent=followup_intent, - message_text="第一版先抽待办、责任人和截止时间,不做复杂优先级。#meeting-plan-debug-tag", + message_text="待办、责任人、截止时间。#meeting-plan-debug-tag", operator_user_id=999, source_system="feishu", source_message_id="msg-plan-meeting-5", ) - assert "主要给谁用" in str(first["answer"] or "") or "主要给谁看" in str(first["answer"] or "") + assert "这类 IT 项目来收" in str(first["answer"] or "") + assert "先定它更像工具、平台,还是长期底座" in str(first["answer"] or "") assert "A." in str(first["answer"] or "") assert "B." in str(first["answer"] or "") assert "C." in str(first["answer"] or "") - assert "怎么给结果" in str(second["answer"] or "") or "怎么接收" in str(second["answer"] or "") or "最后怎么给你" in str(second["answer"] or "") + assert "⭐ 当前我更推荐:B" in str(first["answer"] or "") + assert "平台运维团队" in str(second["answer"] or "") + assert "任务编排平台版" in str(third["answer"] or "") assert "A." in str(second["answer"] or "") assert "B." in str(second["answer"] or "") assert "C." in str(second["answer"] or "") - assert "多久整理出来" in str(third["answer"] or "") or "时效" in str(third["answer"] or "") + assert "批量执行闭环" in str(fourth["answer"] or "") + assert "⭐ 当前我更推荐:A" in str(fourth["answer"] or "") assert "A." in str(third["answer"] or "") assert "B." in str(third["answer"] or "") assert "C." in str(third["answer"] or "") - assert "先保留哪些字段" in str(fourth["answer"] or "") or "第一版先保留哪些字段" in str(fourth["answer"] or "") final_answer = str(final_result["answer"] or "") - assert "需求草稿" in final_answer - assert "【输入材料】会议纪要" in final_answer - assert "【目标用户】内部 PM 和研发负责人" in final_answer - assert "【输出形式】飞书总结" in final_answer - assert "【响应时效】每次会后半小时内" in final_answer - assert "【第一版范围】第一版先抽待办、责任人和截止时间,不做复杂优先级。" in final_answer + assert "📋 我先按当前收敛结果,整理成一版更像正式 brief 的需求草稿:" in final_answer + assert "🧭【一句话判断】" in final_answer + assert "【输入材料】" not in final_answer + assert "【响应时效】" not in final_answer + assert "🏗️【项目性质】这件事更像发布治理平台" in final_answer + assert "👥【服务对象】第一版先服务项目管理团队" in final_answer + assert "⚙️【技术路线】当前更适合走任务编排平台版" in final_answer + assert "🧱【第一版范围】待办、责任人、截止时间" in final_answer + assert "🚀【推进方式】先按项目级试点(带基础审批和回滚)推进" in final_answer assert "#meeting-plan-debug-tag" not in final_answer assert str(final_result["rag_result"]["query_type"]) == "draft" @@ -2925,13 +4823,768 @@ async def test_execute_query_intent_requirement_plan_generalizes_for_meeting_min ) ).scalar_one() card = draft.requirement_card or {} - assert card["source_material"] == "会议纪要" - assert "内部 PM 和研发负责人" in str(card["target_user"] or "") - assert card["output_form"] == "飞书总结" - assert card["cadence"] == "每次会后半小时内" + assert card["plan_profile"] == "it_project" + assert card["project_nature"] == "发布治理平台" + assert "项目管理团队" in str(card["target_user"] or "") + assert "FastAPI" in str(card["tech_route"] or "") + assert card["delivery_mode"] == "项目级试点(带基础审批和回滚)" assert "待办" in str(card["first_scope"] or "") +@pytest.mark.asyncio +async def test_execute_query_intent_requirement_plan_injects_context_to_llm_for_abc_options( + monkeypatch, + requirement_plan_db_session, +) -> None: + class FakeAdaptiveRAGService: + def __init__(self, db, tenant_id: int): + _ = (db, tenant_id) + + async def answer_question(self, **kwargs): + _ = kwargs + raise AssertionError("LLM-backed ABC requirement plan should not enter heavy RAG path") + + class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin): + def __init__(self) -> None: + self.db = requirement_plan_db_session + self.tenant_id = 3 + + captured: list[dict[str, object]] = [] + _install_requirement_plan_round_llm(monkeypatch, captured) + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + executor = FakeExecutor() + + intent = SimpleNamespace( + project_id=None, + metadata={ + "source_chat_id": "chat-plan-outfit", + "source_sender_open_id": "ou-plan-outfit", + "message_intent": "requirement_plan", + "consultant_mode_preference": "clarify", + }, + ) + + result = await executor._execute_query_intent( + intent=intent, + message_text="跟你聊个新需求,关于换季系统自动推荐搭配衣服,先别创建,先收第一版。", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-outfit-root", + ) + + answer = str(result["answer"] or "") + assert result["status"] == "succeeded" + assert captured, "requirement_plan ABC options must be generated through the LLM round service" + assert str(captured[0]["kwargs"].get("function_name")) == "pmflow_requirement_plan_round" + assert "换季系统自动推荐搭配衣服" in str(captured[0]["prompt"]) + assert '"missing_field": "project_nature"' in str(captured[0]["prompt"]) + assert "A. 先做穿搭推荐小工具" in answer + assert "B. 先做个性化穿搭推荐平台" in answer + assert "C. 直接做穿搭决策底座" in answer + assert "⭐ 当前我更推荐:B" in answer + assert "批量自动化部署" not in answer + assert "发布治理平台" not in answer + assert "OpenClaw" not in answer + assert str(result["rag_result"]["query_type"]) == "plan" + + followup_intent = SimpleNamespace( + project_id=None, + metadata={ + "source_chat_id": "chat-plan-outfit", + "source_sender_open_id": "ou-plan-outfit", + "thread_context": { + "root_message_id": "msg-plan-outfit-root", + "reply_to_message_id": "msg-plan-outfit-root", + }, + }, + ) + second = await executor._execute_query_intent( + intent=followup_intent, + message_text="B", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-outfit-2", + ) + assert second["status"] == "succeeded" + + draft = ( + await requirement_plan_db_session.execute( + select(MountedDraftWorkspace).where(MountedDraftWorkspace.tenant_id == 3) + ) + ).scalar_one() + card = draft.requirement_card or {} + assert card["project_nature"] == "个性化穿搭推荐平台" + assert card["project_nature"] != "发布治理平台" + + +@pytest.mark.asyncio +async def test_execute_query_intent_requirement_plan_generates_contextual_outfit_draft_instead_of_old_it_template( + monkeypatch, + requirement_plan_db_session, +) -> None: + class FakeAdaptiveRAGService: + def __init__(self, db, tenant_id: int): + _ = (db, tenant_id) + + async def answer_question(self, **kwargs): + _ = kwargs + raise AssertionError("requirement plan draft should not enter heavy RAG path") + + class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin): + def __init__(self) -> None: + self.db = requirement_plan_db_session + self.tenant_id = 3 + + calls: list[dict[str, object]] = [] + + def _extract_packet(prompt: str, marker: str) -> dict[str, object]: + if marker not in prompt: + return {} + raw = prompt.split(marker, 1)[1].strip() + try: + payload = json.loads(raw) + except Exception: + return {} + return payload if isinstance(payload, dict) else {} + + async def fake_generate_structured_object(prompt: str, **kwargs): # noqa: ANN001 + function_name = str(kwargs.get("function_name") or "") + calls.append({"function_name": function_name, "prompt": prompt}) + if function_name == "pmflow_requirement_plan_round": + packet = _extract_packet(prompt, "requirement_plan_round_packet:") + context_blob = json.dumps(packet, ensure_ascii=False) + if '"missing_field": "project_nature"' in prompt: + payload = { + "opening": "🧭 我先按“换季系统自动推荐搭配衣服”这类 IT 项目来收。", + "focus": "现在最关键的是先把第一版定位定住。", + "options": [ + {"key": "A", "title": "先做穿搭推荐小工具", "best_for": "先验证换季推荐有没有真实使用价值", "suggested_capability": "规则推荐、基础标签、简单反馈", "rationale": "落地快,适合先跑通最小闭环", "tradeoff": "个性化能力会偏弱"}, + {"key": "B", "title": "先做个性化穿搭推荐平台", "best_for": "持续沉淀用户偏好、衣橱数据和搭配反馈", "suggested_capability": "用户画像、衣物标签、推荐策略、反馈闭环", "rationale": "更贴近自动推荐搭配衣服的长期价值", "tradeoff": "第一版要先说清边界"}, + {"key": "C", "title": "直接做穿搭决策底座", "best_for": "后续要接多品牌和多场景运营", "suggested_capability": "多源接入、推荐服务、效果评估", "rationale": "长期上限最高", "tradeoff": "早期投入最大"}, + ], + "recommendation": {"key": "B", "reason": "它最贴近这类需求的持续价值,同时不会一开始做得过重。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + elif '"missing_field": "target_user"' in prompt: + payload = { + "opening": "🧭 我先按“个性化穿搭推荐平台”这个方向继续收。", + "focus": "现在先把第一版服务对象定住,不然后面推荐口径会飘。", + "options": [ + {"key": "A", "title": "普通消费者", "best_for": "先验证换季搭配推荐是否真的有帮助", "suggested_capability": "偏好反馈、天气/季节标签、收藏行为", "rationale": "最贴近最终使用者,能尽快拿到真实反馈", "tradeoff": "需要处理偏好差异"}, + {"key": "B", "title": "穿搭运营团队", "best_for": "先由运营维护搭配规则和推荐口径", "suggested_capability": "规则配置、款式标签、人工校验", "rationale": "更容易控制第一版质量", "tradeoff": "自动化程度会弱一些"}, + {"key": "C", "title": "品牌导购团队", "best_for": "结合商品推荐和导购场景做转化验证", "suggested_capability": "商品库、搭配卡片、导购反馈", "rationale": "更适合商业转化场景", "tradeoff": "会更依赖商品数据"}, + ], + "recommendation": {"key": "A", "reason": "先服务普通消费者,最能验证自动推荐是否真的成立。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + elif '"missing_field": "tech_route"' in prompt and "换季系统自动推荐搭配衣服" in context_blob: + payload = { + "opening": "🧭 我先按“普通消费者先试用”这个方向继续收。", + "focus": "现在先把第一版能力路径定住,避免一开始做重。", + "options": [ + {"key": "A", "title": "规则推荐版", "best_for": "先用季节、温度、场景和衣物标签跑通推荐闭环", "suggested_capability": "标签规则、搭配模板、简单反馈", "rationale": "最快验证推荐是否有用", "tradeoff": "后续个性化能力要继续补"}, + {"key": "B", "title": "轻量模型推荐版", "best_for": "希望更早引入用户偏好和相似搭配学习", "suggested_capability": "基础召回、轻量排序、反馈学习", "rationale": "推荐体验会更自然", "tradeoff": "第一版调参成本更高"}, + {"key": "C", "title": "多模态推荐底座", "best_for": "一开始就要处理图片理解和风格迁移", "suggested_capability": "图文向量、风格标签、多模态检索", "rationale": "长期上限更高", "tradeoff": "首版复杂度太高"}, + ], + "recommendation": {"key": "A", "reason": "先用规则和标签跑通首轮推荐,更符合当前验证阶段。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + else: + payload = { + "opening": "🧭 我先按“规则推荐版”继续收尾。", + "focus": "最后定一下第一版先保留哪些推荐范围。", + "options": [ + {"key": "A", "title": "先保留温度、场景、单品搭配建议", "best_for": "先把最基础的换季推荐闭环跑通", "suggested_capability": "温度标签、场景标签、单品匹配规则", "rationale": "最容易验证用户是否愿意使用", "tradeoff": "短期内还没有风格迁移"}, + {"key": "B", "title": "加入用户偏好和历史收藏", "best_for": "希望一开始就看个性化提升", "suggested_capability": "偏好特征、收藏反馈、简单排序", "rationale": "推荐会更贴近个人习惯", "tradeoff": "冷启动成本更高"}, + {"key": "C", "title": "加入图片理解和风格迁移", "best_for": "要做更强的视觉搭配能力", "suggested_capability": "图像理解、风格识别、向量检索", "rationale": "长期价值更高", "tradeoff": "第一版会明显做重"}, + ], + "recommendation": {"key": "A", "reason": "先把温度、场景、单品搭配建议跑通,最适合验证首轮价值。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + } + return StructuredExtractionResult( + payload=payload, + source_field="content", + provider="fake", + model="fake", + validation="ok", + ) + if function_name == "pmflow_requirement_plan_draft": + return StructuredExtractionResult( + payload={ + "intro": "📋 我先按当前收敛结果,整理成一版更贴近当前业务的需求草稿:", + "title": "换季系统自动推荐搭配衣服", + "one_liner": "先面向普通消费者做一版换季穿搭推荐试用,先把温度、场景和单品搭配建议跑成可验证闭环。", + "goal": "先验证用户是否愿意使用换季搭配推荐,并收集最基础的点击、收藏和反馈信号。", + "project_nature": "这件事更像个性化穿搭推荐平台的第一版,不是部署平台,也不是内部流程治理工具。", + "target_user": "第一版先服务普通消费者,重点看他们是否会点击、收藏和采纳推荐结果。", + "signal_inputs": "当前至少要用到:用户当前需求描述;季节/温度信息;衣物标签;基础反馈数据。", + "known_info": "目前已经明确:这是换季穿搭推荐需求;先按个性化穿搭推荐平台收敛;第一版先给普通消费者试用;技术路线先按规则推荐版;范围先保留温度、场景、单品搭配建议。", + "tech_route": "当前更适合先走规则推荐版,用季节、温度、场景和衣物标签跑通首轮推荐。", + "first_scope": "第一版先保留温度、场景、单品搭配建议,先验证推荐是否被点击、收藏和采纳。", + "core_flow": "用户进入换季推荐入口 -> 结合温度/场景生成搭配建议 -> 展示单品组合 -> 记录点击/收藏/反馈 -> 继续优化推荐规则", + "delivery_mode": "先做小范围真实用户试用,优先看真实点击、收藏和主观反馈。", + "success_signal": "先保证能稳定给出换季搭配建议,并拿到点击、收藏或反馈中的至少一类真实用户信号。", + "risks": "当前主要风险在冷启动标签质量、搭配规则是否过于死板,以及首轮反馈样本是否足够。", + "pending_items": ["衣物标签口径是否已有现成标准", "首轮试用用户范围和反馈采集方式", "是否需要同步接商品库或衣橱数据"], + "closing": "如果你认可,我下一轮就按这版继续细化;如果要改,直接说改哪一项,我就在这版上改。", + "positioning_one_liner": "面向普通消费者的小范围换季穿搭推荐试用,先跑通基础推荐闭环。", + "summary": "先用规则推荐跑通换季穿搭建议,再根据真实点击、收藏和反馈决定是否扩到更完整的平台能力。", + "target_customers": "普通消费者", + "external_signal_sources": [ + {"name": "天气 API", "purpose": "补充实时温度、体感和降雨变化", "phase": "首版优先接入"}, + {"name": "淘宝开放平台", "purpose": "补充可推荐商品的品类、颜色和季节属性", "phase": "首版优先接入"}, + {"name": "得物开放数据", "purpose": "补充年轻用户偏好的款式热度和潮流变化", "phase": "第二阶段接入"}, + {"name": "小红书趋势榜", "purpose": "补充当季穿搭内容趋势和流行关键词", "phase": "第二阶段接入"}, + {"name": "微信导出回流", "purpose": "补充用户转发、收藏后的采纳反馈", "phase": "首版优先接入"}, + ], + "requirement_items": [ + {"section": "需求目标", "title": "验证用户价值", "description": "验证用户是否愿意使用换季搭配推荐,并留下点击、收藏或反馈。"}, + {"section": "服务对象", "title": "首批用户", "description": "第一版先服务普通消费者。"}, + {"section": "技术路线", "title": "规则推荐版", "description": "先基于季节、温度、场景和衣物标签生成推荐。"}, + {"section": "第一版范围", "title": "最小闭环", "description": "先保留温度、场景、单品搭配建议。"}, + ], + "missing_info": ["衣物标签标准", "试用用户范围", "反馈采集方式"], + "mvp_items": ["温度标签", "场景标签", "单品搭配建议", "基础点击/收藏反馈"], + "team_setup": [{"role": "建议配置", "notes": "产品/运营 1名、后端/算法 1名、前端 1名,先支撑小范围试用即可。"}], + "validation_metrics": {"product": ["推荐点击率", "收藏率", "用户主观反馈"]}, + }, + source_field="content", + provider="fake", + model="fake", + validation="ok", + ) + raise AssertionError(f"unexpected function_name: {function_name}") + + monkeypatch.setattr( + mixin_execute_query_project.llm_service, + "generate_structured_object", + fake_generate_structured_object, + ) + async def _fake_recommend_requirement_plan_owner_for_draft(self, *, card: dict[str, object]): # noqa: ANN001 + _ = self, card + return { + "user_id": 18, + "name": "陈后端", + "role": "后端开发", + "workload_ratio": 28.0, + "reason": "系统检索到他当前负载较低,且技能更接近推荐与数据接入。", + "matched_skills": ["Python", "FastAPI", "数据接入"], + } + + monkeypatch.setattr( + mixin_execute_query_project.ExecuteQueryProjectMixin, + "_recommend_requirement_plan_owner_for_draft", + _fake_recommend_requirement_plan_owner_for_draft, + raising=False, + ) + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + executor = FakeExecutor() + + root_intent = SimpleNamespace( + project_id=None, + metadata={ + "source_chat_id": "chat-plan-outfit-draft", + "source_sender_open_id": "ou-plan-outfit-draft", + "message_intent": "requirement_plan", + "consultant_mode_preference": "clarify", + }, + ) + followup_intent = SimpleNamespace( + project_id=None, + metadata={ + "source_chat_id": "chat-plan-outfit-draft", + "source_sender_open_id": "ou-plan-outfit-draft", + "thread_context": { + "root_message_id": "msg-plan-outfit-draft-root", + "reply_to_message_id": "msg-plan-outfit-draft-root", + }, + }, + ) + + first_result = await executor._execute_query_intent( + intent=root_intent, + message_text="跟你聊个新需求,关于换季系统自动推荐搭配衣服,先别创建,先收第一版。", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-outfit-draft-root", + ) + await executor._execute_query_intent( + intent=followup_intent, + message_text="B", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-outfit-draft-2", + ) + await executor._execute_query_intent( + intent=followup_intent, + message_text="A", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-outfit-draft-3", + ) + await executor._execute_query_intent( + intent=followup_intent, + message_text="A", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-outfit-draft-4", + ) + final_result = await executor._execute_query_intent( + intent=followup_intent, + message_text="A", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-outfit-draft-5", + ) + + final_answer = str(final_result["answer"] or "") + assert first_result["status"] == "succeeded" + assert any(str(call.get("function_name")) == "pmflow_requirement_plan_draft" for call in calls) + assert "换季系统自动推荐搭配衣服" in final_answer + assert "普通消费者" in final_answer + assert "规则推荐版" in final_answer + assert "小范围真实用户试用" in final_answer + assert "🌐【外部信号源】" in final_answer + assert "天气 API" in final_answer + assert "淘宝开放平台" in final_answer + assert "得物开放数据" in final_answer + assert "小红书趋势榜" in final_answer + assert "👤【推荐负责人】陈后端" in final_answer + assert "系统检索到他当前负载较低" in final_answer + assert "人员能力图谱" not in final_answer + assert "工作流状态与审计日志" not in final_answer + assert "审批、审计、权限一次做满" not in final_answer + assert "确认门禁" not in final_answer + assert str(final_result["rag_result"]["query_type"]) == "draft" + + +@pytest.mark.asyncio +async def test_execute_query_intent_requirement_plan_retries_when_llm_leaks_internal_packet_keys( + monkeypatch, + requirement_plan_db_session, +) -> None: + class FakeAdaptiveRAGService: + def __init__(self, db, tenant_id: int): + _ = (db, tenant_id) + + async def answer_question(self, **kwargs): + _ = kwargs + raise AssertionError("requirement plan should not enter heavy RAG path") + + class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin): + def __init__(self) -> None: + self.db = requirement_plan_db_session + self.tenant_id = 3 + + calls: list[dict[str, object]] = [] + + async def fake_generate_structured_object(prompt: str, **kwargs): # noqa: ANN001 + _ = kwargs + calls.append({"prompt": prompt}) + if len(calls) == 1: + return StructuredExtractionResult( + payload={ + "opening": "🧭 我先按“换季系统自动推荐搭配衣服”这类 IT 项目来收。", + "focus": "current_context 里已经有标题,这轮先收第一版定位。", + "options": [ + { + "key": "A", + "title": "先做穿搭推荐小工具", + "best_for": "先快速验证换季推荐是否有用", + "suggested_capability": "规则推荐、基础标签、简单反馈", + "rationale": "latest_user_message 说明这是个新需求,适合先做轻量闭环", + "tradeoff": "后续还要补个性化能力", + }, + { + "key": "B", + "title": "先做个性化穿搭推荐平台", + "best_for": "持续沉淀用户偏好和衣橱反馈", + "suggested_capability": "用户画像、衣物标签、反馈闭环", + "rationale": "collected_fields 还不多,先把主方向定住更重要", + "tradeoff": "第一版要先说清数据边界", + }, + { + "key": "C", + "title": "直接做穿搭决策底座", + "best_for": "后续接多品牌和多场景运营", + "suggested_capability": "多源接入、推荐服务、策略实验", + "rationale": "project_nature 如果一开始做重,上限会更高", + "tradeoff": "早期投入最大", + }, + ], + "recommendation": {"key": "B", "reason": "reply_budget 允许的范围内,B 更贴近长期价值。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + }, + source_field="content", + provider="fake", + model="fake", + validation="ok", + ) + return StructuredExtractionResult( + payload={ + "opening": "🧭 我先按“换季系统自动推荐搭配衣服”这类 IT 项目来收。", + "focus": "现在先把第一版定位定住,再往下收目标用户和范围。", + "options": [ + { + "key": "A", + "title": "先做穿搭推荐小工具", + "best_for": "快速验证换季搭配推荐有没有真实使用价值", + "suggested_capability": "规则推荐、季节标签、基础反馈", + "rationale": "落地快,适合先把推荐闭环跑通", + "tradeoff": "个性化学习能力会偏弱", + }, + { + "key": "B", + "title": "先做个性化穿搭推荐平台", + "best_for": "持续沉淀用户偏好、衣橱数据和搭配反馈", + "suggested_capability": "用户画像、衣物标签、推荐策略、反馈闭环", + "rationale": "更贴近自动推荐搭配衣服这件事的长期价值", + "tradeoff": "第一版要先约束数据口径和推荐边界", + }, + { + "key": "C", + "title": "直接做穿搭决策底座", + "best_for": "后续要接多品牌、多场景和长期运营策略", + "suggested_capability": "多源数据接入、推荐服务、效果评估", + "rationale": "长期上限最高", + "tradeoff": "早期投入和依赖都更重", + }, + ], + "recommendation": {"key": "B", "reason": "它最贴近这类需求的长期价值,同时不会一开始就做得过重。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + }, + source_field="content", + provider="fake", + model="fake", + validation="ok", + ) + + monkeypatch.setattr( + "app.services.ai_pmo.ai_pmo_typed_executor_execute.mixin_execute_query_project.llm_service.generate_structured_object", + fake_generate_structured_object, + ) + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + executor = FakeExecutor() + + intent = SimpleNamespace( + project_id=None, + metadata={ + "source_chat_id": "chat-plan-repair-internal-keys", + "source_sender_open_id": "ou-plan-repair-internal-keys", + "message_intent": "requirement_plan", + "consultant_mode_preference": "clarify", + }, + ) + + result = await executor._execute_query_intent( + intent=intent, + message_text="跟你聊个新需求,关于换季系统自动推荐搭配衣服,先别创建,先收第一版。", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-repair-internal-keys-root", + ) + + answer = str(result["answer"] or "") + assert result["status"] == "succeeded" + assert len(calls) == 2 + assert "current_context" not in answer + assert "latest_user_message" not in answer + assert "collected_fields" not in answer + assert "reply_budget" not in answer + assert "project_nature" not in answer + assert "⭐ 当前我更推荐:B" in answer + + +@pytest.mark.asyncio +async def test_execute_query_intent_requirement_plan_retries_stale_deployment_context_for_outfit_requirement( + monkeypatch, + requirement_plan_db_session, +) -> None: + class FakeAdaptiveRAGService: + def __init__(self, db, tenant_id: int): + _ = (db, tenant_id) + + async def answer_question(self, **kwargs): + _ = kwargs + raise AssertionError("requirement plan should not enter heavy RAG path") + + class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin): + def __init__(self) -> None: + self.db = requirement_plan_db_session + self.tenant_id = 3 + + calls: list[str] = [] + + async def fake_generate_structured_object(prompt: str, **kwargs): # noqa: ANN001 + _ = kwargs + calls.append(prompt) + if '"missing_field": "project_nature"' in prompt: + return StructuredExtractionResult( + payload={ + "opening": "🧭 我先按“换季系统自动推荐搭配衣服”这类 IT 项目来收。", + "focus": "现在最关键的是先把第一版定位定住。", + "options": [ + { + "key": "A", + "title": "先做穿搭推荐小工具", + "best_for": "先快速验证搭配建议是否有用", + "suggested_capability": "规则推荐、天气标签、基础反馈", + "rationale": "适合先把最小闭环跑通", + "tradeoff": "个性化会偏弱", + }, + { + "key": "B", + "title": "先做个性化穿搭推荐平台", + "best_for": "持续沉淀用户偏好和衣橱反馈", + "suggested_capability": "用户画像、衣物标签、推荐策略", + "rationale": "更贴近自动推荐搭配衣服的长期价值", + "tradeoff": "第一版要先说清边界", + }, + { + "key": "C", + "title": "直接做穿搭决策底座", + "best_for": "后续要接更多品牌和场景", + "suggested_capability": "多源接入、策略实验、效果评估", + "rationale": "长期上限最高", + "tradeoff": "早期投入最大", + }, + ], + "recommendation": {"key": "B", "reason": "它更符合这类需求的长期价值,但不会一开始做得过重。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + }, + source_field="content", + provider="fake", + model="fake", + validation="ok", + ) + if '"missing_field": "target_user"' in prompt and len(calls) == 2: + return StructuredExtractionResult( + payload={ + "opening": "🧭 我先按“发布治理平台”这个方向继续收。", + "focus": "现在要先把服务对象定住,不然后面治理边界会飘。", + "options": [ + { + "key": "A", + "title": "平台运维团队", + "best_for": "统一管理发布流程和回滚风险", + "suggested_capability": "审批、审计、回滚", + "rationale": "更适合承接部署治理闭环", + "tradeoff": "会偏离真实用户场景", + }, + { + "key": "B", + "title": "项目交付团队", + "best_for": "按项目复制部署流程", + "suggested_capability": "模板、参数、发布编排", + "rationale": "更适合多项目交付", + "tradeoff": "仍然偏旧部署场景", + }, + { + "key": "C", + "title": "开发团队", + "best_for": "自己发版自己看结果", + "suggested_capability": "日志查看、执行入口", + "rationale": "反馈快", + "tradeoff": "治理弱", + }, + ], + "recommendation": {"key": "A", "reason": "它最适合部署治理和回滚闭环。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + }, + source_field="content", + provider="fake", + model="fake", + validation="ok", + ) + return StructuredExtractionResult( + payload={ + "opening": "🧭 我先按“个性化穿搭推荐平台”这个方向继续收。", + "focus": "现在先把第一版服务对象定住,不然后面推荐口径会飘。", + "options": [ + { + "key": "A", + "title": "普通消费者", + "best_for": "先验证换季搭配推荐是否真的有帮助", + "suggested_capability": "偏好反馈、天气/季节标签、收藏行为", + "rationale": "最贴近最终使用者,能尽快拿到真实反馈", + "tradeoff": "需要处理偏好差异", + }, + { + "key": "B", + "title": "穿搭运营团队", + "best_for": "先由运营维护搭配规则和推荐口径", + "suggested_capability": "规则配置、款式标签、人工校验", + "rationale": "更容易控制第一版质量", + "tradeoff": "自动化程度会弱一些", + }, + { + "key": "C", + "title": "品牌导购团队", + "best_for": "结合商品推荐和导购场景做转化验证", + "suggested_capability": "商品库、搭配卡片、导购反馈", + "rationale": "更适合商业转化场景", + "tradeoff": "会更依赖商品数据", + }, + ], + "recommendation": {"key": "A", "reason": "先服务普通消费者,最能验证自动推荐是否真的成立。"}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + }, + source_field="content", + provider="fake", + model="fake", + validation="ok", + ) + + monkeypatch.setattr( + "app.services.ai_pmo.ai_pmo_typed_executor_execute.mixin_execute_query_project.llm_service.generate_structured_object", + fake_generate_structured_object, + ) + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + executor = FakeExecutor() + + root_intent = SimpleNamespace( + project_id=None, + metadata={ + "source_chat_id": "chat-plan-repair-stale-deployment", + "source_sender_open_id": "ou-plan-repair-stale-deployment", + "message_intent": "requirement_plan", + "consultant_mode_preference": "clarify", + }, + ) + followup_intent = SimpleNamespace( + project_id=None, + metadata={ + "source_chat_id": "chat-plan-repair-stale-deployment", + "source_sender_open_id": "ou-plan-repair-stale-deployment", + "thread_context": { + "root_message_id": "msg-plan-repair-stale-deployment-root", + "reply_to_message_id": "msg-plan-repair-stale-deployment-root", + }, + }, + ) + + first = await executor._execute_query_intent( + intent=root_intent, + message_text="跟你聊个新需求,关于换季系统自动推荐搭配衣服,先别创建,先收第一版。", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-repair-stale-deployment-root", + ) + second = await executor._execute_query_intent( + intent=followup_intent, + message_text="B", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-repair-stale-deployment-2", + ) + + assert first["status"] == "succeeded" + assert second["status"] == "succeeded" + assert len(calls) == 3 + second_answer = str(second["answer"] or "") + assert "普通消费者" in second_answer + assert "穿搭运营团队" in second_answer + assert "品牌导购团队" in second_answer + assert "发布治理平台" not in second_answer + assert "平台运维团队" not in second_answer + assert "部署治理" not in second_answer + + +@pytest.mark.asyncio +async def test_execute_query_intent_requirement_plan_round_compacts_render_within_feishu_budget( + monkeypatch, + requirement_plan_db_session, +) -> None: + class FakeAdaptiveRAGService: + def __init__(self, db, tenant_id: int): + _ = (db, tenant_id) + + async def answer_question(self, **kwargs): + _ = kwargs + raise AssertionError("requirement plan should not enter heavy RAG path") + + class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin): + def __init__(self) -> None: + self.db = requirement_plan_db_session + self.tenant_id = 3 + + repeated = ",".join(["覆盖用户偏好、衣橱标签、天气温度、场景约束、历史反馈"] * 12) + + async def fake_generate_structured_object(prompt: str, **kwargs): # noqa: ANN001 + _ = (prompt, kwargs) + return StructuredExtractionResult( + payload={ + "opening": "🧭 我先按“换季系统自动推荐搭配衣服”这类 IT 项目来收。" + repeated, + "focus": "现在最关键的是先定第一版定位。" + repeated, + "options": [ + { + "key": "A", + "title": "轻量推荐工具", + "best_for": repeated, + "suggested_capability": repeated, + "rationale": repeated, + "tradeoff": repeated, + }, + { + "key": "B", + "title": "业务平台能力", + "best_for": repeated, + "suggested_capability": repeated, + "rationale": repeated, + "tradeoff": repeated, + }, + { + "key": "C", + "title": "长期能力底座", + "best_for": repeated, + "suggested_capability": repeated, + "rationale": repeated, + "tradeoff": repeated, + }, + ], + "recommendation": {"key": "B", "reason": repeated}, + "reply_instruction": "你回 A / B / C,我就顺着这条继续往下收。", + }, + source_field="content", + provider="fake", + model="fake", + validation="ok", + ) + + monkeypatch.setattr( + "app.services.ai_pmo.ai_pmo_typed_executor_execute.mixin_execute_query_project.llm_service.generate_structured_object", + fake_generate_structured_object, + ) + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + executor = FakeExecutor() + + intent = SimpleNamespace( + project_id=None, + metadata={ + "source_chat_id": "chat-plan-over-budget", + "source_sender_open_id": "ou-plan-over-budget", + "message_intent": "requirement_plan", + "consultant_mode_preference": "clarify", + }, + ) + + result = await executor._execute_query_intent( + intent=intent, + message_text="跟你聊个新需求,关于换季系统自动推荐搭配衣服,先别创建,先收第一版。", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-plan-over-budget-root", + ) + + assert result["status"] == "succeeded" + answer = str(result["answer"] or "") + assert len(answer) <= 1600 + assert "A. 轻量推荐工具" in answer + assert "B. 业务平台能力" in answer + assert "C. 长期能力底座" in answer + assert "⭐ 当前我更推荐:B" in answer + + @pytest.mark.asyncio async def test_execute_query_intent_requirement_plan_it_project_runs_five_round_pm_template( monkeypatch, @@ -2950,6 +5603,7 @@ async def test_execute_query_intent_requirement_plan_it_project_runs_five_round_ self.db = requirement_plan_db_session self.tenant_id = 3 + _install_requirement_plan_round_llm(monkeypatch) monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) executor = FakeExecutor() @@ -3009,15 +5663,9 @@ async def test_execute_query_intent_requirement_plan_it_project_runs_five_round_ source_system="feishu", source_message_id="msg-plan-it-5", ) - final_result = await executor._execute_query_intent( - intent=followup_intent, - message_text="B,先做项目级试点,带基础审批和回滚。", - operator_user_id=999, - source_system="feishu", - source_message_id="msg-plan-it-6", - ) + final_result = fifth - assert "🧭 我先按“批量自动化部署 OpenClaw”这类 IT 项目来收。" in str(first["answer"] or "") + assert "🧭 我先按“OpenClaw 批量自动化部署平台”这类 IT 项目来收。" in str(first["answer"] or "") assert "A. 先做内部提效工具" in str(first["answer"] or "") assert "先定它更像工具、平台,还是长期底座" in str(first["answer"] or "") assert "⭐ 当前我更推荐:B" in str(first["answer"] or "") @@ -3028,7 +5676,7 @@ async def test_execute_query_intent_requirement_plan_it_project_runs_five_round_ assert "⭐ 当前我更推荐:B" in str(second["answer"] or "") assert "🧭 我先按“平台运维团队要长期使用”这个方向继续收。" in str(third["answer"] or "") - assert "现在最关键的,不是再补更多背景,而是先把技术路线定住" in str(third["answer"] or "") + assert "第一版技术路线定住" in str(third["answer"] or "") assert "FastAPI + React + Redis + Celery + PostgreSQL" in str(third["answer"] or "") assert "⭐ 当前我更推荐:B" in str(third["answer"] or "") @@ -3036,16 +5684,12 @@ async def test_execute_query_intent_requirement_plan_it_project_runs_five_round_ assert "批量执行闭环" in str(fourth["answer"] or "") assert "⭐ 当前我更推荐:A" in str(fourth["answer"] or "") - assert "🧭 我先按“批量执行闭环”继续收尾。" in str(fifth["answer"] or "") - assert "项目级试点" in str(fifth["answer"] or "") - assert "⭐ 当前我更推荐:B" in str(fifth["answer"] or "") - final_answer = str(final_result["answer"] or "") assert "📋 我先按当前收敛结果,整理成一版更像正式 brief 的需求草稿:" in final_answer assert "🧭【一句话判断】" in final_answer assert "🏗️【项目性质】这件事更像发布治理平台" in final_answer assert "👥【服务对象】第一版先服务平台运维团队" in final_answer - assert "📡【信号源】当前至少要接入:" in final_answer + assert "📥【基础输入】当前至少要接入:" in final_answer assert "飞书需求消息" in final_answer assert "部署目标清单" in final_answer assert "📝【当前已知信息】目前已经明确:" in final_answer @@ -3242,7 +5886,39 @@ async def test_execute_query_intent_short_circuits_requirement_advise_by_text_wi self.db = object() self.tenant_id = 3 + async def _fake_aplan_message(*, message_text: str, source_system: str, metadata=None): + _ = (message_text, metadata) + return WorkflowBundle( + bundle_id="bundle-advise-shortcut", + semantic_version="v1", + source_system=source_system, + primary_workflow="query_flow", + workflows=[ + WorkflowPlan( + workflow_type="query_flow", + workflow_action="answer", + target_scope="global", + operation_codes=["query.answer"], + requires_confirmation=False, + confidence=0.92, + metadata={"message_intent": "business_review"}, + ) + ], + message_gate="auto", + reply_contract=ReplyContract(mode="text", template_key="workflow_bundle", summary="query_flow"), + diagnostics={ + "message_intent": "business_review", + "consultant_mode_preference": "advise", + "advise_subtype": "scope", + }, + ) + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + monkeypatch.setattr( + mixin_execute_query_project.WorkflowSemanticPlanner, + "aplan_message", + _fake_aplan_message, + ) executor = FakeExecutor() intent = SimpleNamespace(project_id=None, metadata={}) @@ -3278,7 +5954,39 @@ async def test_execute_query_intent_short_circuits_assignee_recommendation_witho _ = (message_text, project_id) return "如果现在先给一个保守负责人,我更倾向 AI工程师。" + async def _fake_aplan_message(*, message_text: str, source_system: str, metadata=None): + _ = (message_text, metadata) + return WorkflowBundle( + bundle_id="bundle-assignee-shortcut", + semantic_version="v1", + source_system=source_system, + primary_workflow="query_flow", + workflows=[ + WorkflowPlan( + workflow_type="query_flow", + workflow_action="answer", + target_scope="global", + operation_codes=["query.answer"], + requires_confirmation=False, + confidence=0.92, + metadata={"message_intent": "business_review"}, + ) + ], + message_gate="auto", + reply_contract=ReplyContract(mode="text", template_key="workflow_bundle", summary="query_flow"), + diagnostics={ + "message_intent": "business_review", + "consultant_mode_preference": "advise", + "advise_subtype": "assignee", + }, + ) + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + monkeypatch.setattr( + mixin_execute_query_project.WorkflowSemanticPlanner, + "aplan_message", + _fake_aplan_message, + ) executor = FakeExecutor() intent = SimpleNamespace(project_id=None, metadata={}) @@ -3361,7 +6069,38 @@ async def test_execute_query_intent_short_circuits_project_existence_lookup_with self.db = object() self.tenant_id = 3 + async def _fake_aplan_message(*, message_text: str, source_system: str, metadata=None): + _ = (message_text, metadata) + return WorkflowBundle( + bundle_id="bundle-project-lookup-shortcut", + semantic_version="v1", + source_system=source_system, + primary_workflow="query_flow", + workflows=[ + WorkflowPlan( + workflow_type="query_flow", + workflow_action="answer", + target_scope="global", + operation_codes=["query.answer"], + requires_confirmation=False, + confidence=0.92, + metadata={"message_intent": "project_lookup_question"}, + ) + ], + message_gate="auto", + reply_contract=ReplyContract(mode="text", template_key="workflow_bundle", summary="query_flow"), + diagnostics={ + "message_intent": "project_lookup_question", + "consultant_mode_preference": "fact_retrieval", + }, + ) + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + monkeypatch.setattr( + mixin_execute_query_project.WorkflowSemanticPlanner, + "aplan_message", + _fake_aplan_message, + ) executor = FakeExecutor() intent = SimpleNamespace(project_id=None, metadata={}) @@ -3408,9 +6147,8 @@ async def test_should_route_to_draft_confirmation_session_uses_active_mounted_dr captured["kwargs"] = kwargs return True - monkeypatch.setattr(DraftConfirmHandler, "has_active_draft_session", _fake_has_active_draft_session) - kernel = FakeKernel() + monkeypatch.setattr(DraftConfirmHandler, "has_active_draft_session", _fake_has_active_draft_session) should_route = await kernel._should_route_to_draft_confirmation_session( session=SimpleNamespace(last_intent="query.answer"), sender="tester", @@ -3461,8 +6199,94 @@ async def test_should_route_to_draft_confirmation_session_does_not_hijack_smallt ingress_options=None, ) + assert should_route is True + assert called is True + + +@pytest.mark.asyncio +async def test_should_route_to_draft_confirmation_session_keeps_pending_confirm_session_without_keywords( + monkeypatch, +) -> None: + class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + async def _fake_has_active_draft_session(self, **kwargs): + _ = (self, kwargs) + return False + + monkeypatch.setattr(DraftConfirmHandler, "has_active_draft_session", _fake_has_active_draft_session) + + kernel = FakeKernel() + should_route = await kernel._should_route_to_draft_confirmation_session( + session=SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM), + sender="tester", + source_system="feishu", + channel_ref="chat-active", + source_sender_open_id="ou_active", + message_text="按这版继续", + ingress_options=None, + ) + + assert should_route is True + + +@pytest.mark.asyncio +async def test_should_route_to_draft_confirmation_session_with_active_draft_does_not_require_revision_heuristics( + monkeypatch, +) -> None: + class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + async def _fake_has_active_draft_session(self, **kwargs): + _ = (self, kwargs) + return True + + kernel = FakeKernel() + monkeypatch.setattr(DraftConfirmHandler, "has_active_draft_session", _fake_has_active_draft_session) + should_route = await kernel._should_route_to_draft_confirmation_session( + session=SimpleNamespace(last_intent="query.answer"), + sender="tester", + source_system="feishu", + channel_ref="chat-active", + source_sender_open_id="ou_active", + message_text="保持当前草稿结构并补完整信息", + ingress_options=None, + ) + + assert should_route is True + + +@pytest.mark.asyncio +async def test_should_route_to_draft_confirmation_session_still_skips_explicit_independent_request_with_active_draft( + monkeypatch, +) -> None: + class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + async def _fake_has_active_draft_session(self, **kwargs): + _ = (self, kwargs) + return True + + monkeypatch.setattr(DraftConfirmHandler, "has_active_draft_session", _fake_has_active_draft_session) + + kernel = FakeKernel() + should_route = await kernel._should_route_to_draft_confirmation_session( + session=SimpleNamespace(last_intent="query.answer"), + sender="tester", + source_system="feishu", + channel_ref="chat-active", + source_sender_open_id="ou_active", + message_text="给项目ID: 2 接一个 YouTube 录屏加评论回放的信号源,先不要执行,先出草稿。", + ingress_options=None, + ) + assert should_route is False - assert called is False def test_mounted_runtime_agent_name_uses_plan_coach_for_scope_advise() -> None: diff --git a/backend/tests/test_ai_pmo_signal_source_onboard_contract.py b/backend/tests/test_ai_pmo_signal_source_onboard_contract.py index 105f9180..768e17f3 100644 --- a/backend/tests/test_ai_pmo_signal_source_onboard_contract.py +++ b/backend/tests/test_ai_pmo_signal_source_onboard_contract.py @@ -9,6 +9,7 @@ from app.services.ai_pmo.ai_pmo_typed_executor_discovery.mixin_discover import ( from app.services.ai_pmo.ai_pmo_typed_executor_execute.mixin_execute_signal_source import ( ExecuteSignalSourceMixin, ) +from app.services.signal.signal_source_orchestration import SignalSourceOrchestrationService def test_signal_source_onboard_idempotency_key_prefers_message_key() -> None: @@ -185,3 +186,27 @@ async def test_project_signal_discovery_uses_project_id_without_name_error() -> assert result["status"] == "pending_confirmation" assert result["project_id"] == 2 assert result["candidate_signals"][0]["title"] == "信号A" + + +def test_signal_source_kind_uses_explicit_repo_locator_without_hint_table() -> None: + assert ( + SignalSourceOrchestrationService._normalize_source_kind( + None, + text="请把这个来源接进来", + requested_via="", + source_payload={"repo": "acme/reco-engine"}, + ) + == "github" + ) + + +def test_signal_source_kind_uses_channel_config_without_hint_table() -> None: + assert ( + SignalSourceOrchestrationService._normalize_source_kind( + None, + text="先挂到当前群", + requested_via="", + source_payload={"config": {"channel_id": "oc_abc123"}}, + ) + == "feishu" + ) diff --git a/backend/tests/test_ai_pmo_task_intent_contract.py b/backend/tests/test_ai_pmo_task_intent_contract.py index 08eb396f..99590194 100644 --- a/backend/tests/test_ai_pmo_task_intent_contract.py +++ b/backend/tests/test_ai_pmo_task_intent_contract.py @@ -18,9 +18,38 @@ from app.services.ai_pmo.ai_pmo_intent import ( ) from app.services.ai_pmo.ai_pmo_kernel_common_core.imports import WORKFLOW_ROUTE_KEYS from app.services.ai_pmo.ai_pmo_typed_executor_execute.service import TypedExecutorExecuteMixin +from app.services.ai_pmo.semantic_context.decision_schema import SemanticDecision +from app.services.ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner -def test_intent_resolver_recognizes_task_create() -> None: +def _planner_decision(*, workflows: list[dict]) -> SemanticDecision: + return SemanticDecision( + topic_relation="new_topic", + mode_decision="answer_only", + workflow_plans=workflows, + scene_metadata={}, + requested_actions=["workflow.bundle.compose"], + confidence=0.92, + ) + + +def test_intent_resolver_recognizes_task_create(monkeypatch: pytest.MonkeyPatch) -> None: + async def _fake_decide(cls, *, message_text: str, source_system: str, metadata=None): + _ = (message_text, source_system, metadata) + return _planner_decision( + workflows=[ + { + "workflow_type": "task_flow", + "workflow_action": "create", + "target_scope": "project", + "operation_codes": ["task.create"], + "confidence": 0.92, + } + ] + ) + + monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide)) + intent = IntentResolver.resolve( text="请在项目#12 新建任务:修复登录超时", project_id=12, @@ -47,13 +76,54 @@ def test_route_resolver_builds_task_create_route() -> None: assert route.key == ROUTE_TASK_CREATE -def test_intent_resolver_negated_write_statement_falls_back_to_query_read_only() -> None: +def test_intent_resolver_negated_write_statement_follows_semantic_query_bundle(monkeypatch: pytest.MonkeyPatch) -> None: + async def _fake_decide(cls, *, message_text: str, source_system: str, metadata=None): + _ = (message_text, source_system, metadata) + return _planner_decision( + workflows=[ + { + "workflow_type": "query_flow", + "workflow_action": "answer", + "target_scope": "global", + "operation_codes": ["query.answer"], + "confidence": 0.86, + } + ] + ) + + monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide)) + intent = IntentResolver.resolve( text="先不要创建任务,告诉我当前进展和风险", project_id=7, ) assert intent.name == INTENT_QUERY - assert str((intent.metadata or {}).get("reason") or "") == "negated_write_statement" + assert str((intent.metadata or {}).get("reason") or "") != "negated_write_statement" + + +def test_intent_resolver_followup_tokens_no_longer_override_semantic_query(monkeypatch: pytest.MonkeyPatch) -> None: + async def _fake_decide(cls, *, message_text: str, source_system: str, metadata=None): + _ = (cls, message_text, source_system, metadata) + return _planner_decision( + workflows=[ + { + "workflow_type": "query_flow", + "workflow_action": "answer", + "target_scope": "global", + "operation_codes": ["query.answer"], + "confidence": 0.86, + } + ] + ) + + monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide)) + + intent = IntentResolver.resolve( + text="确认这条", + project_id=9, + ) + + assert intent.name == INTENT_QUERY def test_route_resolver_builds_query_answer_route() -> None: @@ -74,20 +144,20 @@ def test_route_resolver_builds_query_answer_route() -> None: def test_route_resolver_followup_action_requires_structured_metadata() -> None: - action = RouteResolver.parse_followup_action( + action = RouteResolver.resolve_followup_route_action( canonical_intent=INTENT_FOLLOWUP_CONTINUE, message_text="先取消这个工作项", ) assert action == ROUTE_ACTION_CONTINUE -def test_route_resolver_followup_action_allows_message_parsing_with_context() -> None: - action = RouteResolver.parse_followup_action( +def test_route_resolver_followup_action_no_longer_parses_message_text_with_context() -> None: + action = RouteResolver.resolve_followup_route_action( canonical_intent=INTENT_FOLLOWUP_CONTINUE, message_text="先取消这个工作项", metadata={"followup_context_present": True, "target_work_item_id": 8}, ) - assert action == ROUTE_ACTION_CANCEL + assert action == ROUTE_ACTION_CONTINUE def test_kernel_common_imports_exposes_workflow_route_keys() -> None: diff --git a/backend/tests/test_draft_confirm_handler.py b/backend/tests/test_draft_confirm_handler.py index b09c01de..e398ac0d 100644 --- a/backend/tests/test_draft_confirm_handler.py +++ b/backend/tests/test_draft_confirm_handler.py @@ -20,31 +20,6 @@ from app.services.integrations.feishu_integration_service.mixin_reply_llm_core i FeishuReplyLLMCoreMixin, ) - -def test_parse_action_confirm() -> None: - action, note = DraftConfirmHandler.parse_action_from_text(message_text="确认") - assert action == "confirm" - assert note == "" - - -def test_parse_action_revise_with_note() -> None: - action, note = DraftConfirmHandler.parse_action_from_text(message_text="修改 改成每周跟进") - assert action == "revise" - assert note == "改成每周跟进" - - -def test_parse_action_cancel() -> None: - action, note = DraftConfirmHandler.parse_action_from_text(message_text="取消") - assert action == "cancel" - assert note == "" - - -def test_parse_action_yes_as_confirm() -> None: - action, note = DraftConfirmHandler.parse_action_from_text(message_text="yes") - assert action == "confirm" - assert note == "" - - def test_draft_confirm_handler_thread_anchor_match() -> None: ingest = type( "Ingest", @@ -185,6 +160,47 @@ def test_feishu_reply_llm_core_action_ack_uses_semantic_bundle_only() -> None: assert ack == "已收到,项目创建和信号源接入请求已记录。" +def test_feishu_reply_llm_core_action_ack_no_longer_calls_planner_action_classifier( + monkeypatch: pytest.MonkeyPatch, +) -> None: + _ = monkeypatch + + ack = FeishuReplyLLMCoreMixin._build_action_request_ack_reply( + message_content="随便一句不带动作的话", + capture_result={ + "status": "captured", + "semantic_bundle": { + "bundle_id": "semantic-1", + "semantic_version": "2026-03-27.workflow-v1", + "source_system": "feishu", + "primary_workflow": "signal_source_flow", + "workflows": [ + { + "workflow_type": "signal_source_flow", + "workflow_action": "onboard", + "target_scope": "global", + "operation_codes": ["signal_source.onboard"], + "requires_confirmation": False, + "confidence": 0.92, + "slots": [], + "clarification_needed": False, + "metadata": {}, + } + ], + "message_gate": "auto_execute", + "reply_contract": { + "mode": "aggregate_card", + "template_key": "workflow_bundle", + "summary": "signal_source_flow", + }, + "diagnostics": {}, + }, + }, + ) + + assert ack == "已收到,信号源接入请求已记录。" + + def test_consultant_dispatch_contract_marks_incomplete_task_create_as_clarify() -> None: contract = build_consultant_dispatch_contract( intent_name="task.create", @@ -945,6 +961,121 @@ async def test_draft_confirm_handler_revise_without_note_requests_feedback(monke assert "修改" in result["followup_reply"] +@pytest.mark.asyncio +async def test_draft_confirm_handler_revise_requirement_plan_bypasses_generic_draft_generator(monkeypatch) -> None: + class _FakeResult: + def __init__(self, locked): + self._locked = locked + + def scalar_one_or_none(self): + return self._locked + + class _FakeDB: + def __init__(self, locked): + self._locked = locked + + async def execute(self, query): # noqa: ARG002 + return _FakeResult(self._locked) + + async def flush(self): + return None + + locked = SimpleNamespace( + id=42, + state=STATE_DRAFT_PENDING_CONFIRM, + intent="project.create", + project_id=None, + signal_id=None, + draft_version=2, + draft_expires_at=None, + confirm_message_id=None, + owner_user_id=77, + draft_content={ + "draft_source": "requirement_plan", + "plan_profile": "it_project", + "title": "换季穿搭助手", + "original_text": "做一个换季穿搭助手。", + "requirement_card": { + "plan_profile": "it_project", + "title": "换季穿搭助手", + "goal": "先验证换季穿搭推荐是否有用。", + "project_nature": "轻量级推荐工具", + "target_user": "穿搭新手个体用户", + "tech_route": "规则推荐版", + "first_scope": "基础属性匹配", + "delivery_mode": "小范围试用", + "question_round": 5, + }, + }, + execution_result={}, + ) + handler = DraftConfirmHandler(type("Kernel", (), {"db": _FakeDB(locked), "tenant_id": 1})()) + captured: dict[str, object] = {} + + async def _unexpected_generate_and_persist(**kwargs): # noqa: ANN001 + raise AssertionError(f"generic draft generator should not run for requirement_plan drafts: {kwargs}") + + async def _fake_revise_requirement_plan_draft(self, **kwargs): # noqa: ANN001 + captured["requirement_revise_kwargs"] = kwargs + locked.draft_version = 3 + locked.draft_content = { + **locked.draft_content, + "draft_text": ( + "📋 我先按当前收敛结果,整理成一版需求草稿:\n\n" + "🌐【外部信号源】\n" + "1. 天气 API:提供实时温度和体感变化。\n" + "2. 淘宝开放平台:补充当季商品与类目属性。\n" + "3. 得物开放数据:补充年轻用户偏好的款式热度。\n" + "4. 小红书趋势榜:补充换季搭配内容趋势。\n" + "5. 微信收藏/点击回流:补充用户采纳反馈。\n\n" + "👤【推荐负责人】陈后端(当前负载 28%)\n" + "理由:系统检索到他当前负载较低,且技能更接近推荐与数据接入。" + ), + "recommended_assignee_id": 18, + "recommended_assignee_name": "陈后端", + "recommended_assignee_workload": 28.0, + "recommended_assignee_reason": "系统检索到他当前负载较低,且技能更接近推荐与数据接入。", + "external_signal_sources": [ + {"name": "天气 API", "purpose": "提供实时温度和体感变化", "phase": "首版优先接入"}, + {"name": "淘宝开放平台", "purpose": "补充当季商品与类目属性", "phase": "首版优先接入"}, + {"name": "得物开放数据", "purpose": "补充年轻用户偏好的款式热度", "phase": "第二阶段接入"}, + ], + } + return { + "draft_text": str(locked.draft_content["draft_text"]), + "draft_content": dict(locked.draft_content), + "draft_version": 3, + } + + monkeypatch.setattr(handler.draft_generator, "generate_and_persist", _unexpected_generate_and_persist) + monkeypatch.setattr( + DraftConfirmHandler, + "_revise_requirement_plan_draft", + _fake_revise_requirement_plan_draft, + raising=False, + ) + + result = await handler.revise_draft( + target_work_item=SimpleNamespace( + id=42, + state=STATE_DRAFT_PENDING_CONFIRM, + intent="project.create", + project_id=None, + signal_id=None, + ), + target_ingest=SimpleNamespace(payload={"message_text": "做一个换季穿搭助手。"}), + revision_note="补充外部信号源和推荐负责人。", + expected_version=2, + ) + + assert result["handled"] is True + assert result["status"] == "draft_pending_confirm" + assert "🌐【外部信号源】" in result["followup_reply"] + assert "天气 API" in result["followup_reply"] + assert "👤【推荐负责人】陈后端" in result["followup_reply"] + assert captured["requirement_revise_kwargs"]["revision_note"] == "补充外部信号源和推荐负责人。" + + @pytest.mark.asyncio async def test_draft_confirm_handler_ignores_non_draft_reply_without_target(monkeypatch) -> None: kernel = type("Kernel", (), {"db": object(), "tenant_id": 1})() @@ -998,7 +1129,7 @@ async def test_draft_confirm_handler_confirm_without_target_returns_failed_safe( @pytest.mark.asyncio -async def test_draft_confirm_handler_natural_revision_without_target_returns_failed_safe(monkeypatch) -> None: +async def test_draft_confirm_handler_natural_revision_without_target_and_without_anchor_is_ignored(monkeypatch) -> None: kernel = type("Kernel", (), {"db": object(), "tenant_id": 1})() handler = DraftConfirmHandler(kernel) @@ -1015,12 +1146,362 @@ async def test_draft_confirm_handler_natural_revision_without_target_returns_fai source_message_id="msg-1", channel_ref="chat-1", source_sender_open_id="ou_xxx", + thread_context=None, + ) + + assert result["handled"] is False + assert result["status"] == "ignored" + assert result["followup_reply"] == "" + + +@pytest.mark.asyncio +async def test_draft_confirm_handler_without_target_and_without_anchor_confirm_is_ignored(monkeypatch) -> None: + kernel = type("Kernel", (), {"db": object(), "tenant_id": 1})() + handler = DraftConfirmHandler(kernel) + + async def _fake_resolve_pending_draft(**kwargs): + _ = kwargs + return None + + monkeypatch.setattr(handler, "_resolve_pending_draft", _fake_resolve_pending_draft) + + result = await handler.handle_text_reply( + message_text="确认", + sender="林宇航", + source_system="feishu", + source_message_id="msg-1", + channel_ref="chat-1", + source_sender_open_id="ou_xxx", + thread_context=None, + ) + + assert result["handled"] is False + assert result["status"] == "ignored" + assert result["followup_reply"] == "" + + +@pytest.mark.asyncio +async def test_draft_confirm_handler_display_feedback_uses_semantic_answer_only(monkeypatch) -> None: + kernel = type("Kernel", (), {"db": SimpleNamespace(execute=object()), "tenant_id": 1})() + handler = DraftConfirmHandler(kernel) + + async def _fake_resolve_pending_draft(**kwargs): + _ = kwargs + return ( + SimpleNamespace( + id=42, + state=STATE_DRAFT_PENDING_CONFIRM, + intent="project.create", + project_id=None, + signal_id=None, + draft_version=2, + ), + SimpleNamespace(payload={"message_text": "原始需求"}), + ) + + async def _fake_resolve_active_mounted_draft(**kwargs): + _ = kwargs + return ( + SimpleNamespace(id=8, topic_workspace_id=77, active_work_item_id=42, state="ready"), + SimpleNamespace(id=77, mode="plan", plan_status="awaiting_execute_confirm"), + ) + + class _FakeAssembler: + def __init__(self, db): + self.db = db + + async def assemble(self, **kwargs): + return SimpleNamespace( + mode_card=SimpleNamespace( + allowed_actions=["reply.compose", "draft.followup.answer_only"], + forbidden_actions=[], + ) + ) + + class _FakeDecisionService: + def __init__(self, llm_service): + self.llm_service = llm_service + + async def decide(self, *, packet): + _ = packet + return SimpleNamespace( + topic_relation="continue_current_topic", + mode_decision="answer_only", + followup_action="answer_only", + revision_note="", + assistant_reply="收到,这是展示形式反馈:卡片可以长,聊天直发需要更短,我不会重起草当前草稿。", + requires_confirmation=False, + requested_actions=["reply.compose", "draft.followup.answer_only"], + ) + + async def _unexpected_confirm(**kwargs): # noqa: ANN001 + raise AssertionError(f"confirm_draft should not run for display feedback: {kwargs}") + + async def _unexpected_revise(**kwargs): # noqa: ANN001 + raise AssertionError(f"revise_draft should not run for display feedback: {kwargs}") + + async def _unexpected_cancel(**kwargs): # noqa: ANN001 + raise AssertionError(f"cancel_draft should not run for display feedback: {kwargs}") + + monkeypatch.setattr(handler, "_resolve_pending_draft", _fake_resolve_pending_draft) + monkeypatch.setattr(handler, "_resolve_active_mounted_draft", _fake_resolve_active_mounted_draft) + monkeypatch.setattr("app.services.ai_pmo.draft_confirm_handler.SemanticContextAssembler", _FakeAssembler, raising=False) + monkeypatch.setattr( + "app.services.ai_pmo.draft_confirm_handler.SemanticDecisionService", + _FakeDecisionService, + raising=False, + ) + monkeypatch.setattr(handler, "confirm_draft", _unexpected_confirm) + monkeypatch.setattr(handler, "revise_draft", _unexpected_revise) + monkeypatch.setattr(handler, "cancel_draft", _unexpected_cancel) + + result = await handler.handle_text_reply( + message_text="是这种的可以很长,但是聊天内容发的那种就不行这么长", + sender="杨宇", + source_system="feishu", + source_message_id="msg-1", + channel_ref="chat-1", + source_sender_open_id="ou_xxx", thread_context={"reply_to_message_id": "reply-1"}, ) assert result["handled"] is True - assert result["status"] == "failed_safe" - assert "当前没有可确认草稿" in result["followup_reply"] + assert result["status"] == "pending_manual" + assert "展示形式反馈" in result["followup_reply"] + assert "不会重起草" in result["followup_reply"] + + +@pytest.mark.asyncio +async def test_draft_confirm_handler_uses_work_item_bound_mounted_draft_for_semantic_followup(monkeypatch) -> None: + kernel = type("Kernel", (), {"db": SimpleNamespace(execute=object()), "tenant_id": 1})() + handler = DraftConfirmHandler(kernel) + captured: dict[str, object] = {} + + async def _fake_resolve_pending_draft(**kwargs): + _ = kwargs + return ( + SimpleNamespace( + id=42, + state=STATE_DRAFT_PENDING_CONFIRM, + intent="project.create", + project_id=None, + signal_id=None, + draft_version=2, + ), + SimpleNamespace(payload={"message_text": "原始需求"}), + ) + + async def _fake_resolve_active_mounted_draft(**kwargs): + _ = kwargs + return None + + async def _fake_resolve_mounted_draft_by_work_item_id(**kwargs): + captured["mounted_lookup"] = kwargs + return ( + SimpleNamespace(id=8, topic_workspace_id=77, active_work_item_id=42, state="draft_pending_confirm"), + SimpleNamespace(id=77, mode="plan", plan_status="draft_pending_confirm"), + ) + + async def _fake_semantic_followup(**kwargs): + captured["semantic_kwargs"] = kwargs + return { + "action": "revise", + "revision_note": "补充外部信号源。", + "assistant_reply": "", + } + + async def _fake_revise_draft(**kwargs): + captured["revise_kwargs"] = kwargs + return { + "handled": True, + "status": "draft_pending_confirm", + "followup_reply": "我先按当前草稿补外部信号源。", + } + + async def _unexpected_confirm(**kwargs): # noqa: ANN001 + raise AssertionError(f"confirm_draft should not run here: {kwargs}") + + async def _unexpected_cancel(**kwargs): # noqa: ANN001 + raise AssertionError(f"cancel_draft should not run here: {kwargs}") + + monkeypatch.setattr(handler, "_resolve_pending_draft", _fake_resolve_pending_draft) + monkeypatch.setattr(handler, "_resolve_active_mounted_draft", _fake_resolve_active_mounted_draft) + monkeypatch.setattr(handler, "_resolve_mounted_draft_by_work_item_id", _fake_resolve_mounted_draft_by_work_item_id) + monkeypatch.setattr(handler, "_resolve_semantic_followup_decision", _fake_semantic_followup) + monkeypatch.setattr(handler, "revise_draft", _fake_revise_draft) + monkeypatch.setattr(handler, "confirm_draft", _unexpected_confirm) + monkeypatch.setattr(handler, "cancel_draft", _unexpected_cancel) + + result = await handler.handle_text_reply( + message_text="按这版继续", + sender="林宇航", + source_system="feishu", + source_message_id="msg-continue-1", + channel_ref="chat-1", + source_sender_open_id="ou_xxx", + thread_context=None, + ) + + assert result["handled"] is True + assert result["status"] == "draft_pending_confirm" + assert "当前草稿" in result["followup_reply"] + assert captured["mounted_lookup"] == {"work_item_id": 42} + assert captured["semantic_kwargs"]["active_mounted"][1].id == 77 + assert captured["revise_kwargs"]["revision_note"] == "补充外部信号源。" + + +@pytest.mark.asyncio +async def test_draft_confirm_handler_target_bound_semantic_override_revises_without_parser_fallback(monkeypatch) -> None: + kernel = type("Kernel", (), {"db": object(), "tenant_id": 1})() + handler = DraftConfirmHandler(kernel) + + async def _fake_resolve_pending_draft(**kwargs): + _ = kwargs + return ( + SimpleNamespace( + id=42, + state=STATE_DRAFT_PENDING_CONFIRM, + intent="project.create", + project_id=None, + signal_id=None, + draft_version=2, + ), + SimpleNamespace(payload={"message_text": "原始需求"}), + ) + + async def _fake_resolve_target_mounted_draft(**kwargs): + _ = kwargs + return ( + SimpleNamespace(id=8, topic_workspace_id=77, active_work_item_id=42, state="draft_pending_confirm"), + SimpleNamespace(id=77, mode="plan", plan_status="draft_pending_confirm"), + ) + + async def _fake_revise_draft(**kwargs): + return { + "handled": True, + "status": "draft_pending_confirm", + "followup_reply": f"按当前草稿重起:{kwargs['revision_note']}", + } + + monkeypatch.setattr(handler, "_resolve_pending_draft", _fake_resolve_pending_draft) + monkeypatch.setattr(handler, "_resolve_target_mounted_draft", _fake_resolve_target_mounted_draft) + monkeypatch.setattr(handler, "revise_draft", _fake_revise_draft) + + result = await handler.handle_text_reply( + message_text="把外部信号源补上", + sender="林宇航", + source_system="feishu", + source_message_id="msg-override-1", + channel_ref="chat-1", + source_sender_open_id="ou_xxx", + thread_context={"reply_to_message_id": "reply-1"}, + semantic_followup_override={ + "action": "revise", + "revision_note": "补充外部信号源。", + "assistant_reply": "", + }, + ) + + assert result["handled"] is True + assert result["status"] == "draft_pending_confirm" + assert "补充外部信号源" in result["followup_reply"] + + +@pytest.mark.asyncio +async def test_draft_confirm_handler_target_bound_without_semantic_action_returns_pending_manual(monkeypatch) -> None: + kernel = type("Kernel", (), {"db": object(), "tenant_id": 1})() + handler = DraftConfirmHandler(kernel) + + async def _fake_resolve_pending_draft(**kwargs): + _ = kwargs + return ( + SimpleNamespace( + id=42, + state=STATE_DRAFT_PENDING_CONFIRM, + intent="project.create", + project_id=None, + signal_id=None, + draft_version=2, + ), + SimpleNamespace(payload={"message_text": "原始需求"}), + ) + + async def _fake_resolve_target_mounted_draft(**kwargs): + _ = kwargs + return ( + SimpleNamespace(id=8, topic_workspace_id=77, active_work_item_id=42, state="draft_pending_confirm"), + SimpleNamespace(id=77, mode="plan", plan_status="draft_pending_confirm"), + ) + + monkeypatch.setattr(handler, "_resolve_pending_draft", _fake_resolve_pending_draft) + monkeypatch.setattr(handler, "_resolve_target_mounted_draft", _fake_resolve_target_mounted_draft) + + result = await handler.handle_text_reply( + message_text="把目标用户改成平台运维团队", + sender="林宇航", + source_system="feishu", + source_message_id="msg-no-semantic-action-1", + channel_ref="chat-1", + source_sender_open_id="ou_xxx", + thread_context={"reply_to_message_id": "reply-1"}, + semantic_followup_override={ + "action": "", + "revision_note": "", + "assistant_reply": "", + }, + ) + + assert result["handled"] is True + assert result["status"] == "pending_manual" + assert "请回复“确认”、“修改 + 你的意见”或“取消”" in result["followup_reply"] + + +@pytest.mark.asyncio +async def test_draft_confirm_handler_target_bound_without_anchor_and_without_semantic_action_is_ignored(monkeypatch) -> None: + kernel = type("Kernel", (), {"db": object(), "tenant_id": 1})() + handler = DraftConfirmHandler(kernel) + + async def _fake_resolve_pending_draft(**kwargs): + _ = kwargs + return ( + SimpleNamespace( + id=42, + state=STATE_DRAFT_PENDING_CONFIRM, + intent="project.create", + project_id=None, + signal_id=None, + draft_version=2, + ), + SimpleNamespace(payload={"message_text": "原始需求"}), + ) + + async def _fake_resolve_target_mounted_draft(**kwargs): + _ = kwargs + return ( + SimpleNamespace(id=8, topic_workspace_id=77, active_work_item_id=42, state="draft_pending_confirm"), + SimpleNamespace(id=77, mode="plan", plan_status="draft_pending_confirm"), + ) + + monkeypatch.setattr(handler, "_resolve_pending_draft", _fake_resolve_pending_draft) + monkeypatch.setattr(handler, "_resolve_target_mounted_draft", _fake_resolve_target_mounted_draft) + + result = await handler.handle_text_reply( + message_text="收到,辛苦了", + sender="林宇航", + source_system="feishu", + source_message_id="msg-no-anchor-ignore-1", + channel_ref="chat-1", + source_sender_open_id="ou_xxx", + thread_context=None, + semantic_followup_override={ + "action": "", + "revision_note": "", + "assistant_reply": "", + }, + ) + + assert result["handled"] is False + assert result["status"] == "ignored" def test_kernel_ingress_mixin_auto_safe_int_regression() -> None: diff --git a/backend/tests/test_feishu_pipeline_queue_contract.py b/backend/tests/test_feishu_pipeline_queue_contract.py index eb92a61b..c816a121 100644 --- a/backend/tests/test_feishu_pipeline_queue_contract.py +++ b/backend/tests/test_feishu_pipeline_queue_contract.py @@ -1,3 +1,4 @@ +import asyncio import inspect from types import SimpleNamespace @@ -355,6 +356,40 @@ async def test_intent_gate_query_does_not_inherit_project_context(monkeypatch) - async def test_feishu_question_like_messages_use_sync_high_accuracy_path(monkeypatch) -> None: calls = {"sync": 0, "session": 0, "cache": 0} + class _FakeBundle: + def __init__(self) -> None: + self.primary_workflow = "query_flow" + self.workflows = [SimpleNamespace(workflow_type="query_flow", workflow_action="answer")] + self.diagnostics = {} + + def to_dict(self) -> dict[str, object]: + return { + "bundle_id": "semantic-query", + "semantic_version": "2026-03-27.workflow-v1", + "source_system": "feishu", + "primary_workflow": self.primary_workflow, + "workflows": [ + { + "workflow_type": "query_flow", + "workflow_action": "answer", + "target_scope": "global", + "operation_codes": ["query.answer"], + "requires_confirmation": False, + "confidence": 0.9, + "slots": [], + "clarification_needed": False, + "metadata": {}, + } + ], + "message_gate": "auto_execute", + "reply_contract": { + "mode": "aggregate_card", + "template_key": "workflow_bundle", + "summary": "query_flow", + }, + "diagnostics": {}, + } + class _FakeKernel: @staticmethod def _build_source_message_id(*, source_system: str, source_message_id, channel_ref: str, sender: str, message_text: str): @@ -395,6 +430,11 @@ async def test_feishu_question_like_messages_use_sync_high_accuracy_path(monkeyp monkeypatch.setattr(mixin_capture_entry, "AIPMOKernelService", _FakeKernel) monkeypatch.setattr(ProactiveSignalCaptureService, "_sync_feishu_recent_cache", _noop_sync_recent_cache) + monkeypatch.setattr( + mixin_capture_entry.WorkflowSemanticPlanner, + "aplan_message", + classmethod(lambda cls, **kwargs: asyncio.sleep(0, result=_FakeBundle())), + ) service = ProactiveSignalCaptureService(object()) result = await service.capture_from_feishu( @@ -417,6 +457,27 @@ async def test_feishu_question_like_messages_use_sync_high_accuracy_path(monkeyp async def test_feishu_business_review_query_forwards_semantic_bundle_into_kernel(monkeypatch) -> None: captured: dict[str, object] = {} + class _FakeBundle: + def __init__(self) -> None: + self.primary_workflow = "query_flow" + self.workflows = [SimpleNamespace(workflow_type="query_flow", workflow_action="answer")] + self.diagnostics = { + "message_intent": "business_review", + "consultant_mode_preference": "clarify", + } + + def to_dict(self) -> dict[str, object]: + return { + "primary_workflow": "query_flow", + "workflows": [ + { + "workflow_type": "query_flow", + "workflow_action": "answer", + } + ], + "diagnostics": dict(self.diagnostics), + } + class _FakeKernel: @staticmethod def _build_source_message_id(*, source_system: str, source_message_id, channel_ref: str, sender: str, message_text: str): @@ -454,6 +515,11 @@ async def test_feishu_business_review_query_forwards_semantic_bundle_into_kernel monkeypatch.setattr(mixin_capture_entry, "AIPMOKernelService", _FakeKernel) monkeypatch.setattr(ProactiveSignalCaptureService, "_sync_feishu_recent_cache", _noop_sync_recent_cache) + monkeypatch.setattr( + mixin_capture_entry.WorkflowSemanticPlanner, + "aplan_message", + classmethod(lambda cls, **kwargs: asyncio.sleep(0, result=_FakeBundle())), + ) service = ProactiveSignalCaptureService(object()) result = await service.capture_from_feishu( @@ -477,6 +543,27 @@ async def test_feishu_business_review_query_forwards_semantic_bundle_into_kernel async def test_force_sync_feishu_query_flow_still_forwards_semantic_bundle_into_kernel(monkeypatch) -> None: captured: dict[str, object] = {} + class _FakeBundle: + def __init__(self) -> None: + self.primary_workflow = "query_flow" + self.workflows = [SimpleNamespace(workflow_type="query_flow", workflow_action="answer")] + self.diagnostics = { + "message_intent": "business_review", + "consultant_mode_preference": "clarify", + } + + def to_dict(self) -> dict[str, object]: + return { + "primary_workflow": "query_flow", + "workflows": [ + { + "workflow_type": "query_flow", + "workflow_action": "answer", + } + ], + "diagnostics": dict(self.diagnostics), + } + class _FakeDB: async def commit(self) -> None: return None @@ -521,6 +608,11 @@ async def test_force_sync_feishu_query_flow_still_forwards_semantic_bundle_into_ monkeypatch.setattr(mixin_capture_entry, "AIPMOKernelService", _FakeKernel) monkeypatch.setattr(ProactiveSignalCaptureService, "_sync_feishu_recent_cache", _noop_sync_recent_cache) + monkeypatch.setattr( + mixin_capture_entry.WorkflowSemanticPlanner, + "aplan_message", + classmethod(lambda cls, **kwargs: asyncio.sleep(0, result=_FakeBundle())), + ) service = ProactiveSignalCaptureService(_FakeDB()) result = await service.capture_from_feishu( @@ -730,8 +822,20 @@ def test_capture_from_feishu_no_longer_references_legacy_fallback() -> None: assert "_capture_from_feishu_legacy_flow" not in source -def test_should_process_feishu_high_accuracy_treats_requirement_plan_as_sync() -> None: - should_sync, reason = mixin_capture_entry.SignalCaptureEntryMixin._should_process_feishu_high_accuracy( +@pytest.mark.asyncio +async def test_should_process_feishu_high_accuracy_treats_requirement_plan_as_sync(monkeypatch) -> None: + class _FakeBundle: + primary_workflow = "query_flow" + workflows = [SimpleNamespace(workflow_type="query_flow", workflow_action="answer")] + diagnostics = {"message_intent": "requirement_plan"} + + monkeypatch.setattr( + mixin_capture_entry.WorkflowSemanticPlanner, + "aplan_message", + classmethod(lambda cls, **kwargs: asyncio.sleep(0, result=_FakeBundle())), + ) + + should_sync, reason = await mixin_capture_entry.SignalCaptureEntryMixin._should_process_feishu_high_accuracy( message_text="@PMFlow 想跟你聊聊需求,做一个自动给每日的大A趋势打分的小工具,先不创建,先收敛第一版。", ) @@ -739,8 +843,20 @@ def test_should_process_feishu_high_accuracy_treats_requirement_plan_as_sync() - assert reason == "semantic:requirement_plan" -def test_should_process_feishu_high_accuracy_treats_requirement_plan_slot_answer_as_sync() -> None: - should_sync, reason = mixin_capture_entry.SignalCaptureEntryMixin._should_process_feishu_high_accuracy( +@pytest.mark.asyncio +async def test_should_process_feishu_high_accuracy_treats_requirement_plan_slot_answer_as_sync(monkeypatch) -> None: + class _FakeBundle: + primary_workflow = "query_flow" + workflows = [SimpleNamespace(workflow_type="query_flow", workflow_action="answer")] + diagnostics = {"message_intent": "requirement_plan"} + + monkeypatch.setattr( + mixin_capture_entry.WorkflowSemanticPlanner, + "aplan_message", + classmethod(lambda cls, **kwargs: asyncio.sleep(0, result=_FakeBundle())), + ) + + should_sync, reason = await mixin_capture_entry.SignalCaptureEntryMixin._should_process_feishu_high_accuracy( message_text="@PMFlow 先按技术指标来,主要给投顾看。", ) @@ -752,6 +868,18 @@ def test_should_process_feishu_high_accuracy_treats_requirement_plan_slot_answer async def test_capture_from_feishu_requirement_plan_skips_async_ack_and_forwards_semantic_bundle(monkeypatch) -> None: captured: dict[str, object] = {} + class _FakeBundle: + primary_workflow = "query_flow" + workflows = [SimpleNamespace(workflow_type="query_flow", workflow_action="answer")] + diagnostics = {"message_intent": "requirement_plan"} + + def to_dict(self) -> dict[str, object]: + return { + "primary_workflow": "query_flow", + "workflows": [{"workflow_type": "query_flow", "workflow_action": "answer"}], + "diagnostics": dict(self.diagnostics), + } + class _FakeDB: async def commit(self) -> None: return None @@ -812,6 +940,11 @@ async def test_capture_from_feishu_requirement_plan_skips_async_ack_and_forwards monkeypatch.setattr(mixin_capture_entry, "AIPMOKernelService", _FakeKernel) monkeypatch.setattr(mixin_capture_entry, "MountedAIRuntimeWriteService", _FakeMountedRuntimeWriteService) monkeypatch.setattr(ProactiveSignalCaptureService, "_sync_feishu_recent_cache", _noop_sync_recent_cache) + monkeypatch.setattr( + mixin_capture_entry.WorkflowSemanticPlanner, + "aplan_message", + classmethod(lambda cls, **kwargs: asyncio.sleep(0, result=_FakeBundle())), + ) service = ProactiveSignalCaptureService(_FakeDB()) result = await service.capture_from_feishu( @@ -836,6 +969,18 @@ async def test_capture_from_feishu_requirement_plan_skips_async_ack_and_forwards async def test_capture_from_feishu_requirement_plan_slot_answer_stays_sync_without_async_ack(monkeypatch) -> None: captured: dict[str, object] = {} + class _FakeBundle: + primary_workflow = "query_flow" + workflows = [SimpleNamespace(workflow_type="query_flow", workflow_action="answer")] + diagnostics = {"message_intent": "requirement_plan"} + + def to_dict(self) -> dict[str, object]: + return { + "primary_workflow": "query_flow", + "workflows": [{"workflow_type": "query_flow", "workflow_action": "answer"}], + "diagnostics": dict(self.diagnostics), + } + class _FakeDB: async def commit(self) -> None: return None @@ -896,6 +1041,11 @@ async def test_capture_from_feishu_requirement_plan_slot_answer_stays_sync_witho monkeypatch.setattr(mixin_capture_entry, "AIPMOKernelService", _FakeKernel) monkeypatch.setattr(mixin_capture_entry, "MountedAIRuntimeWriteService", _FakeMountedRuntimeWriteService) monkeypatch.setattr(ProactiveSignalCaptureService, "_sync_feishu_recent_cache", _noop_sync_recent_cache) + monkeypatch.setattr( + mixin_capture_entry.WorkflowSemanticPlanner, + "aplan_message", + classmethod(lambda cls, **kwargs: asyncio.sleep(0, result=_FakeBundle())), + ) service = ProactiveSignalCaptureService(_FakeDB()) result = await service.capture_from_feishu( diff --git a/backend/tests/test_feishu_receive_orchestration_contract.py b/backend/tests/test_feishu_receive_orchestration_contract.py index 29f444d5..a2285581 100644 --- a/backend/tests/test_feishu_receive_orchestration_contract.py +++ b/backend/tests/test_feishu_receive_orchestration_contract.py @@ -71,6 +71,44 @@ def test_classify_message_scene_does_not_infer_execute_from_raw_text_only() -> N assert scene == "qa" +def test_classify_message_scene_uses_bundle_without_planner_classifier(monkeypatch: pytest.MonkeyPatch) -> None: + _ = monkeypatch + + scene = FeishuIntegrationService._classify_message_scene( + capture_result={ + "semantic_bundle": { + "bundle_id": "semantic-qa", + "semantic_version": "2026-03-27.workflow-v1", + "source_system": "feishu", + "primary_workflow": "query_flow", + "workflows": [ + { + "workflow_type": "query_flow", + "workflow_action": "answer", + "target_scope": "global", + "operation_codes": ["query.answer"], + "requires_confirmation": False, + "confidence": 0.9, + "slots": [], + "clarification_needed": False, + "metadata": {}, + } + ], + "message_gate": "auto_execute", + "reply_contract": { + "mode": "aggregate_card", + "template_key": "workflow_bundle", + "summary": "query_flow", + }, + "diagnostics": {}, + } + }, + message_content="帮我创建项目和信号源", + ) + + assert scene == "qa" + + @pytest.mark.asyncio async def test_handle_message_receive_orchestrates_capture_and_finalize(monkeypatch) -> None: service = FeishuIntegrationService(db=object()) diff --git a/backend/tests/test_semantic_context_assembler.py b/backend/tests/test_semantic_context_assembler.py index ba4353e8..4216998e 100644 --- a/backend/tests/test_semantic_context_assembler.py +++ b/backend/tests/test_semantic_context_assembler.py @@ -139,3 +139,101 @@ async def test_semantic_context_assembler_builds_packet_from_runtime_state(db_se assert packet.thread.recent_turns[0].role == "user" assert packet.thread.recent_turns[-1].role == "assistant" assert packet.thread.open_questions == ["当前是不是新需求 topic"] + + +@pytest.mark.asyncio +async def test_semantic_context_assembler_adds_draft_followup_ingress_actions(db_session) -> None: + topic = MountedTopicWorkspace( + tenant_id=7, + topic_key="feishu:chat_semantic:root:om_root_followup", + source_system="feishu", + channel_ref="chat_semantic", + conversation_scope_key="feishu:chat_semantic:ou_semantic", + thread_key="thread_semantic", + root_message_id="om_root_followup", + mode="plan", + status="active", + plan_status="awaiting_execute_confirm", + facts_json={"title": "换季穿搭推荐"}, + goals_json=["确认草稿"], + constraints_json={}, + open_questions=[], + pending_actions=[], + readiness_score=0.8, + metadata_json={"last_message_preview": "请确认当前草稿"}, + ) + db_session.add(topic) + await db_session.flush() + + packet = await SemanticContextAssembler(db_session).assemble( + tenant_id=7, + operator_user_id=99, + topic_workspace_id=int(topic.id), + latest_user_message="是这种的可以很长,但是聊天内容发的那种就不行这么长", + ingress_context={ + "candidate_kind": "draft_followup", + "source_system": "feishu", + "reply_anchor_present": True, + "allowed_followup_actions": ["confirm", "revise", "cancel", "answer_only"], + "thread_context": {"reply_to_message_id": "om_reply_1"}, + }, + ) + + assert packet.ingress is not None + assert packet.ingress.candidate_kind == "draft_followup" + assert packet.ingress.reply_anchor_present is True + assert "draft.followup.answer_only" in packet.mode_card.allowed_actions + assert "draft.followup.revise" in packet.mode_card.allowed_actions + + +@pytest.mark.asyncio +async def test_semantic_context_assembler_adds_active_context_followup_actions(db_session) -> None: + topic = MountedTopicWorkspace( + tenant_id=7, + topic_key="feishu:chat_semantic:root:om_root_active_followup", + source_system="feishu", + channel_ref="chat_semantic", + conversation_scope_key="feishu:chat_semantic:ou_semantic", + thread_key="thread_semantic", + root_message_id="om_root_active_followup", + mode="plan", + status="active", + plan_status="collecting", + facts_json={"title": "AI 直播复盘工具"}, + goals_json=["继续收敛信号源模块"], + constraints_json={}, + open_questions=["信号源这块第一版怎么收"], + pending_actions=[], + readiness_score=0.5, + metadata_json={"last_message_preview": "先把这版需求收清楚"}, + ) + db_session.add(topic) + await db_session.flush() + + packet = await SemanticContextAssembler(db_session).assemble( + tenant_id=7, + operator_user_id=99, + topic_workspace_id=int(topic.id), + latest_user_message="信号源呢?", + ingress_context={ + "candidate_kind": "active_context_followup", + "source_system": "feishu", + "reply_anchor_present": False, + "active_context_kind": "requirement_plan", + "allowed_context_scopes": [ + "current_requirement", + "current_draft", + "independent_workflow", + "clarify", + ], + "thread_context": {}, + }, + ) + + assert packet.ingress is not None + assert packet.ingress.candidate_kind == "active_context_followup" + assert packet.ingress.active_context_kind == "requirement_plan" + assert "context.followup.current_requirement" in packet.mode_card.allowed_actions + assert "context.followup.current_draft" in packet.mode_card.allowed_actions + assert "context.followup.independent_workflow" in packet.mode_card.allowed_actions + assert "context.followup.clarify" in packet.mode_card.allowed_actions diff --git a/backend/tests/test_semantic_decision_service.py b/backend/tests/test_semantic_decision_service.py index ecf4c7c3..746518a8 100644 --- a/backend/tests/test_semantic_decision_service.py +++ b/backend/tests/test_semantic_decision_service.py @@ -1,5 +1,6 @@ from __future__ import annotations +from app.config.ai_prompts import PMFLOW_SEMANTIC_CONTEXT_DECISION_PROMPT from app.services.ai_pmo.semantic_context.decision_schema import ( DecisionGateResult, ModeCard, @@ -58,3 +59,18 @@ def test_semantic_decision_gatekeeper_blocks_forbidden_action() -> None: assert result.accepted is False assert "forbidden_action:project.create" in result.blocked_reasons assert result.next_plan_status == "active" + + +def test_semantic_context_decision_prompt_avoids_business_semantic_examples() -> None: + forbidden_fragments = ( + "信号源呢?", + "负责人呢?", + "技术路线呢?", + "商业模式呢?", + "给项目2接入", + "GitHub webhook", + "RTMP/WebRTC", + ) + + for fragment in forbidden_fragments: + assert fragment not in PMFLOW_SEMANTIC_CONTEXT_DECISION_PROMPT diff --git a/backend/tests/test_semantic_lint.py b/backend/tests/test_semantic_lint.py index a91e1d20..baccba3a 100644 --- a/backend/tests/test_semantic_lint.py +++ b/backend/tests/test_semantic_lint.py @@ -32,7 +32,7 @@ def test_semantic_lint_flags_new_scattered_hint_tables(tmp_path) -> None: assert problems[0].rule_id == "semantic_hint_constant" -def test_semantic_lint_allows_workflow_semantic_planner_directory(tmp_path) -> None: +def test_semantic_lint_flags_workflow_semantic_planner_directory(tmp_path) -> None: module = _load_semantic_lint_module() target = tmp_path / "backend" / "app" / "services" / "ai_pmo" / "workflow_semantic_planner" / "foo.py" target.parent.mkdir(parents=True, exist_ok=True) @@ -44,7 +44,8 @@ def test_semantic_lint_allows_workflow_semantic_planner_directory(tmp_path) -> N baseline_path=Path("scripts/semantic_lint_baseline.txt"), ) - assert problems == [] + assert len(problems) == 1 + assert problems[0].rule_id == "semantic_hint_constant" def test_semantic_lint_repo_has_no_compat_baseline_exceptions() -> None: diff --git a/backend/tests/test_workflow_semantic_planner.py b/backend/tests/test_workflow_semantic_planner.py index 9ae72ac6..4b547bc3 100644 --- a/backend/tests/test_workflow_semantic_planner.py +++ b/backend/tests/test_workflow_semantic_planner.py @@ -1,278 +1,119 @@ +from __future__ import annotations + +import asyncio +from typing import Any + import pytest -from app.services.ai_pmo.workflow_semantic_planner import ( - WorkflowSemanticPlanner, -) +from app.services.ai_pmo.semantic_context.decision_schema import SemanticDecision +from app.services.ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner -def test_workflow_semantic_planner_builds_multi_workflow_bundle() -> None: +def _decision(*, workflows: list[dict[str, Any]], scene_metadata: dict[str, Any] | None = None) -> SemanticDecision: + return SemanticDecision( + topic_relation="new_topic", + mode_decision="answer_only", + workflow_plans=workflows, + scene_metadata=scene_metadata or {}, + requested_actions=["workflow.bundle.compose"], + confidence=0.91, + ) + + +def test_workflow_semantic_planner_plan_message_uses_model_decision(monkeypatch: pytest.MonkeyPatch) -> None: + async def _fake_decide(cls, *, message_text: str, source_system: str, metadata: dict[str, Any] | None = None): + assert message_text == "帮我创建一个项目,并把这个需求接入信号源" + assert source_system == "feishu" + return _decision( + workflows=[ + { + "workflow_type": "new_project_flow", + "workflow_action": "create", + "target_scope": "global", + "operation_codes": ["project.create"], + "confidence": 0.93, + }, + { + "workflow_type": "signal_source_flow", + "workflow_action": "onboard", + "target_scope": "global", + "operation_codes": ["signal_source.onboard"], + "confidence": 0.92, + }, + ] + ) + + monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide)) + bundle = WorkflowSemanticPlanner.plan_message( message_text="帮我创建一个项目,并把这个需求接入信号源", source_system="feishu", ) - workflow_types = [item.workflow_type for item in bundle.workflows] - - assert bundle.source_system == "feishu" assert bundle.primary_workflow == "new_project_flow" - assert workflow_types == ["new_project_flow", "signal_source_flow"] + assert [item.workflow_type for item in bundle.workflows] == ["new_project_flow", "signal_source_flow"] assert bundle.reply_contract.mode == "aggregate_card" -def test_workflow_semantic_planner_derives_query_bundle() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="现在项目进展怎么样?", - source_system="pmflow_manual", - ) +@pytest.mark.asyncio +async def test_workflow_semantic_planner_aplan_message_preserves_scene_metadata(monkeypatch: pytest.MonkeyPatch) -> None: + async def _fake_decide(cls, *, message_text: str, source_system: str, metadata: dict[str, Any] | None = None): + assert message_text == "想跟你聊个新需求" + return _decision( + workflows=[ + { + "workflow_type": "query_flow", + "workflow_action": "answer", + "target_scope": "global", + "operation_codes": ["query.answer"], + "confidence": 0.88, + "metadata": {"message_intent": "requirement_plan"}, + } + ], + scene_metadata={ + "message_intent": "requirement_plan", + "consultant_mode_preference": "clarify", + "topic_switch": "new", + }, + ) - assert bundle.primary_workflow == "query_flow" - assert [item.workflow_type for item in bundle.workflows] == ["query_flow"] - assert bundle.message_gate == "auto_execute" + monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide)) - -def test_workflow_semantic_planner_helpers_cover_legacy_entry_semantics() -> None: - assert WorkflowSemanticPlanner.looks_like_project_lookup_question("现在有哪些项目?") is True - assert WorkflowSemanticPlanner.extract_project_lookup_keyword("请问项目 智能风控平台 有吗?") == "智能风控平台" - assert WorkflowSemanticPlanner.is_legacy_business_command("把项目状态改成已完成") is True - assert WorkflowSemanticPlanner.is_legacy_business_command("项目现在状态怎么样?") is False - assert WorkflowSemanticPlanner.wants_followup_continue("继续处理刚才那个") is True - assert WorkflowSemanticPlanner.wants_followup_status("它现在怎么样了?") is True - - -def test_workflow_semantic_planner_contextual_write_confirmation_is_stateful() -> None: - assert WorkflowSemanticPlanner.looks_like_contextual_write_confirmation("那就按这个继续执行") is True - assert WorkflowSemanticPlanner.looks_like_contextual_write_confirmation("这个项目现在怎么样?") is False - - -def test_workflow_semantic_planner_routes_new_business_review_with_five_lenses() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="我要做一个大模型 API 聚合平台,帮我分析下要怎么做", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert [item.workflow_type for item in bundle.workflows] == ["query_flow"] - assert bundle.message_gate == "auto_execute" - assert bundle.workflows[0].metadata["message_intent"] == "business_review" - assert bundle.workflows[0].metadata["review_framework"] == "five_lenses" - assert bundle.diagnostics["message_intent"] == "business_review" - assert bundle.diagnostics["consultant_mode_preference"] == "clarify" - - -def test_workflow_semantic_planner_marks_correction_escalation_queries() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="不对,调用高级模型重新回答我的问题", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert bundle.workflows[0].metadata["message_intent"] == "correction_escalation" - assert bundle.workflows[0].metadata["force_thinking_mode"] == "deep" - assert bundle.diagnostics["message_intent"] == "correction_escalation" - assert bundle.diagnostics["consultant_mode_preference"] == "challenge" - - -def test_workflow_semantic_planner_treats_natural_problem_statement_as_new_project_candidate() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="最近直播复盘太慢了,我想做个给运营用的小工具,能自动切片、总结亮点掉点,再给复盘清单。", - source_system="feishu", - ) - - assert bundle.primary_workflow == "new_project_flow" - assert [item.workflow_type for item in bundle.workflows] == ["new_project_flow"] - assert bundle.workflows[0].workflow_action == "create" - - -def test_workflow_semantic_planner_keeps_natural_solution_consulting_in_query_flow() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="我们最近直播复盘很慢,先帮我分析一下第一版应该怎么做,不要直接创建。", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert [item.workflow_type for item in bundle.workflows] == ["query_flow"] - - -def test_workflow_semantic_planner_keeps_deferred_creation_request_in_new_project_flow() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="最近直播复盘太慢了,想做个给运营团队用的小工具,先不要直接创建,我再补充几轮,最后确认再入库。", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert [item.workflow_type for item in bundle.workflows] == ["query_flow"] - assert bundle.workflows[0].metadata["message_intent"] == "requirement_plan" - assert bundle.workflows[0].metadata["consultant_mode_preference"] == "clarify" - - -def test_workflow_semantic_planner_keeps_scope_framing_discussion_in_query_flow() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="我们想做一个面向医疗器械 BD 的政策雷达工具,先聚焦华东三省,先别创建项目,我还在补范围。", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert [item.workflow_type for item in bundle.workflows] == ["query_flow"] - assert bundle.workflows[0].metadata["message_intent"] == "requirement_plan" - assert bundle.workflows[0].metadata["consultant_mode_preference"] == "clarify" - - -def test_workflow_semantic_planner_marks_explicit_new_requirement_topic_switch() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="不对啊,现在是另一个需求,先不要沿用刚才那版。", + bundle = await WorkflowSemanticPlanner.aplan_message( + message_text="想跟你聊个新需求", source_system="feishu", ) assert bundle.primary_workflow == "query_flow" assert bundle.workflows[0].metadata["message_intent"] == "requirement_plan" - assert bundle.workflows[0].metadata["topic_switch"] == "new" assert bundle.diagnostics["topic_switch"] == "new" -def test_workflow_semantic_planner_marks_chatty_new_requirement_phrase_as_topic_switch() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="想跟你聊个新需求关于批量自动化部署openclaw的", - source_system="feishu", +def test_workflow_semantic_planner_sync_wrapper_fails_inside_running_loop() -> None: + async def _runner() -> None: + with pytest.raises(RuntimeError): + WorkflowSemanticPlanner.plan_message(message_text="test", source_system="feishu") + + asyncio.run(_runner()) + + +def test_workflow_semantic_planner_no_longer_exposes_legacy_semantic_helpers() -> None: + legacy_names = ( + "plan_from_intent", + "classify_scene", + "is_action_request_capture", + "parse_followup_action", + "extract_project_lookup_keyword", + "wants_followup_continue", + "wants_followup_status", + "looks_like_project_create_request", + "looks_like_task_create_request", + "looks_like_signal_source_request", + "looks_like_external_connect_request", + "looks_like_contextual_write_confirmation", + "is_query_like_question", + "is_obvious_question", ) - assert bundle.primary_workflow == "query_flow" - assert bundle.workflows[0].metadata["message_intent"] == "requirement_plan" - assert bundle.workflows[0].metadata["topic_switch"] == "new" - assert bundle.diagnostics["topic_switch"] == "new" - - -def test_workflow_semantic_planner_routes_assignee_recommendation_back_to_query_flow() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="那你觉得分配给谁做比较合适?", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert [item.workflow_type for item in bundle.workflows] == ["query_flow"] - assert bundle.workflows[0].metadata["message_intent"] == "business_review" - assert bundle.workflows[0].metadata["consultant_mode_preference"] == "advise" - - -def test_workflow_semantic_planner_routes_plain_assignee_recommendation_without_question_mark() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="那你觉得这件事分配给谁做比较合适", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert bundle.workflows[0].metadata["message_intent"] == "business_review" - assert bundle.workflows[0].metadata["consultant_mode_preference"] == "advise" - - -def test_workflow_semantic_planner_marks_casual_chat_as_chat_query() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="最近有点累,今天先不推进需求了,陪我简单聊两句。", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert bundle.workflows[0].metadata["message_intent"] == "casual_chat" - assert bundle.workflows[0].metadata["consultant_mode_preference"] == "brainstorm" - - -def test_workflow_semantic_planner_marks_requirement_followup_clarify_as_business_review() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="那就先按上面的方向继续,但是别太重", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert bundle.workflows[0].metadata["message_intent"] == "business_review" - assert bundle.workflows[0].metadata["consultant_mode_preference"] == "clarify" - - -def test_workflow_semantic_planner_marks_requirement_scope_advise_as_business_review() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="如果第一版先做 MVP,这个需求范围怎么收才比较稳?", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert bundle.workflows[0].metadata["message_intent"] == "business_review" - assert bundle.workflows[0].metadata["consultant_mode_preference"] == "advise" - - -def test_workflow_semantic_planner_routes_requirement_chat_into_requirement_plan() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="想跟你聊聊需求,关于自动给每日的大A趋势打分", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert [item.workflow_type for item in bundle.workflows] == ["query_flow"] - assert bundle.workflows[0].metadata["message_intent"] == "requirement_plan" - assert bundle.workflows[0].metadata["consultant_mode_preference"] == "clarify" - assert bundle.diagnostics["message_intent"] == "requirement_plan" - - -@pytest.mark.parametrize( - "message_text", - [ - "技术指标吧", - "主要给投顾看", - "飞书日报就行", - "每天收盘后发", - "先给内部 PM 和研发负责人用", - "飞书总结就行", - "每次会后半小时内整理出来", - ], -) -def test_workflow_semantic_planner_treats_short_requirement_slot_answers_as_requirement_plan( - message_text: str, -) -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text=message_text, - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert bundle.workflows[0].metadata["message_intent"] == "requirement_plan" - assert bundle.workflows[0].metadata["consultant_mode_preference"] == "clarify" - - -def test_workflow_semantic_planner_routes_generic_requirement_chat_into_requirement_plan() -> None: - bundle = WorkflowSemanticPlanner.plan_message( - message_text="想跟你聊个需求,我们开完会以后待办经常散掉,想做个小工具自动从会议纪要里提待办,先别创建,先收第一版。", - source_system="feishu", - ) - - assert bundle.primary_workflow == "query_flow" - assert bundle.workflows[0].metadata["message_intent"] == "requirement_plan" - assert bundle.workflows[0].metadata["consultant_mode_preference"] == "clarify" - - -def test_workflow_semantic_planner_treats_natural_draft_details_as_revision() -> None: - action, note = WorkflowSemanticPlanner.parse_draft_action( - message_text="第一版先做直播录屏和评论回放切片,再加亮点和掉点总结,最后给一份可执行复盘清单。" - ) - - assert action == "revise" - assert note == "第一版先做直播录屏和评论回放切片,再加亮点和掉点总结,最后给一份可执行复盘清单。" - - -def test_workflow_semantic_planner_does_not_treat_smalltalk_as_revision() -> None: - action, note = WorkflowSemanticPlanner.parse_draft_action(message_text="收到,辛苦了") - - assert action == "unknown" - assert note == "" - - -def test_workflow_semantic_planner_treats_scope_narrowing_as_revision_not_cancel() -> None: - message = "第一版先别做太大,先把三件事做好就行:直播录屏和评论回放切片、亮点和掉点自动总结、给团队一份能直接执行的复盘清单。" - - action, note = WorkflowSemanticPlanner.parse_draft_action(message_text=message) - - assert action == "revise" - assert note == message - - -def test_workflow_semantic_planner_keeps_terminal_stop_phrase_as_cancel() -> None: - action, note = WorkflowSemanticPlanner.parse_draft_action(message_text="这个需求先别做了,取消吧。") - - assert action == "cancel" - assert note == "" + for name in legacy_names: + assert not hasattr(WorkflowSemanticPlanner, name), name diff --git a/scripts/semantic_lint.py b/scripts/semantic_lint.py index 5659b49d..ec29adde 100644 --- a/scripts/semantic_lint.py +++ b/scripts/semantic_lint.py @@ -17,7 +17,6 @@ DEFAULT_SCAN_ROOTS = ( Path("backend/app/api/ai_pmo_api"), ) ALLOWLIST_PREFIXES = ( - Path("backend/app/services/ai_pmo/workflow_semantic_planner"), Path("backend/app/config/ai_prompt_registry.py"), Path("backend/app/config/ai_prompts.py"), Path("backend/app/config/ai_prompts_catalog"), diff --git a/工单全字段报表_2026-04-07.xlsx b/工单全字段报表_2026-04-07.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c17b8b3c4ea8718e392de239011c5200d9b77757 GIT binary patch literal 636009 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

IBZ6*y)6zM4|c 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℞FG+;sV6%C;QsWPBy5WP`DOEuPiN=H@n_$D6~{`h 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≪^S=s7xey)$ll8{t%`+xFtAP>|J?I;vPBtZtKyG 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 G!oEsDfaRhg1gC!OD^-6=f&3bOb@(V*Ny` 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%0DXCi^`=#Nee^Xijzwd+dC>p>i2y5c3J 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]b`r(15`RDHw(O;fiyBx;-XKgK zVlnCqc+NPO+;EuoYpA|NH}53M8aQl69%VWEo<2Y1H`JY-%gMO4Xg&4(RZU+ZYF1~? z`s%hNbLE1*IeyxAwR;geGP05KTaeR^Df|1$dP}wYW46fO^_lN&0%*f^aNk)n>Su2; zvKdBCen09dp>-s?o_SBJmt&4lYW02TAE~wybz{9>wZ+PE`X{1aQ|f#us$XS&0QZ4H zGx_H8q&K~cWCFBGNw<)>Zdfczp=z)$gB}gMnSm571mNkd0s`LxRZ=SCe8N;n*e8DH zA|CDItp(GNgnBw9rF*!!>Jt24iAUT?_E3~{;xr0j5NH`KOPc1!4B@Z(KQIom6vFhh z>QLVnv_3#}w~pl2PXa0BiaYSRIU`(}AU?dw@D!9JB-{LG1#?Ws_#!J*7#kq?o=v8( za1j29pRN$6qsu#}s&NoA4*K4e_5sT^$f1n&lR~#dbI`7KB}8(9V|PQ}+WXU4v-xa*(0?D3N|;07X$X0 zsXui-?+VAZd+ljI+>@*D?d}GaoaN&|4cLKSk|>~N`ABO;8Zu^DUs?ST zb?kHXEU|3Mku*wYBLwODahk7*^9MIMISBu%w8o9;eJ78yHE1^ z*7HB+I$vvfT${Sx5^6Y%ZxW2?SnuEd)(WUztQh}ck@X}sIsvDcF7y@k6mjW2t*QeG zFUFq2RwcPmlcAFBb>Uwd^Iw3D#P(|QPk;%yg@=Zc*6hGYbiZ_|@BZM(hdOZO1ENNU7**H! zF2dPBU`l_Pi4^}Km*EPGELhY;mXY;#^$Pje?@Ou=or??MS!@=I0|;jM(9-CNt=R=q z-=M``+OWHYOZDs#VhqnaLLE%cpD59=o*WSDOVgTkh;*>TcJNSjI@R9|LvCPL!$Tg$ z74ju+REBxw;yAapLf?lty0bA|R>HE}z(H{c+qcVs^_> z9^|aqza%Zw0?js#8+vz3@#Dt_<85`j6-q5iiVXkJC{!Fx;uh0-K9O$YLiQBP+Xwr% zT7CXDt~SP<1iJVmm;4jzfAyB0*DpB~&F;09M88r>m^JowkLle!kX>B{mXy1qNt^Yu z=b6?ysrT1Yl$6h70|`AaQvSd7%ePjS)GJItP%yhO=PHQ;QvmHnMI*wIOvxJ5gz9Sf zB5c{|crX+AdiIKQHaKu9+HvAgN?*HAG9pGd>)W%dvBk`i#K8wPS)}i~fV!j%2Y6Pk zHhCmKrO$Q1n!(hWCn@HiSTmhX{Qo_VSq7hXQsrUZal?n{3`Ne3YLml%y757vcuH zX9>%30!>!Y3Ebr!1dQlH25B2$i(LdMB0t%5>H}8~^h}3*vUur?2g+O+GLRb|$5XSd z4XeW+ zKw8(hhvEH})-4M(;*#CxmIk09qI*|2_EY~^gw|=@3`E58uGUo5of4sqQOIQs+!X7P zL|NE|Ayqe>+y5{2kr7v`Bku70Tu???U=jP#MS|kMI7Q4K#EWYJ*T<=Imgh>)%{dA} z14b2xP44-^SN7R_%$>O1(s3_0le4)p7}%Szw1s;k77QD?3VuO(ZhRoGz`>gC{`d`P ziaaR}*RIClN|E*xOc)1%5EJ_7osy*rfS7JdK@SFp3G!KG(($kyjU<(qz$XA~xZXul ziHL5ctPu|jd`GLYwvYga4F#8ZI=5RK*hVuO1nKDTl>8Lq#t6WQTxb7Jse3%^YB1N7 zByFS|`>IIP5{GqLlGX}dbtHCefv*Zw`aK+VyZmS;Dspxl0%0c@Q zbh>_fBPSFR@b+kblhaL?rc&+XUPXp5zIndjXjw299Ebxg*$78coP(8#6jK-XSvfph0h@)ieDHH_5 zHxCe~kUD9%0Sy%D{Y}GN(R|?N@spHc0G%wMI9W!CK%+6yG$%O7Jq

)8qKUVY5;LZtiJtZiNgx0`zX2{ezJpsmC!aruQ zfs{&^NL2yUcJ{OO;#DY?RzT1I*dXa`d^)5}(?%9zq&OJx>yxI@%Nol7Mn%sWU{thQ z#1|7^3jw@70INmoXHvi*{3V4sw;r^mrGYEVX$GPi5jRKpAhI{IZ3a@0V}P*WZ`i=c zGCHy#TtVw9vZKVgpZL3YEF)%>PnJ766AJ_!h{gc>U8*$)<2GZ$N2QiHy#OQE=^#j>Ba2q z&&t}8)ZExro7d;1)G4Kg{^N+$v_7~A+x>J!aX0uNp-t zpB=?yq*?3kmtX0NZ#%LKWVA}B)y?Vp&C&{Y<7jR1t3bM~w0@8g&_8AO(^)WJQI)=c zbGv9aRom%}z3+02=qeCZdDQRTztweCX=0PUiwFJX2`1Y1_+X;yN3F}v%W8kfGsh&h z%9Y6_tUke#J&65XN-P9<#Cgn*`w#TR@%RJfZo|@g*A;wYFcKdXVuA*7s#lLk3Rq@U_aLNTdju&*8OJH-LLL=Aw9d=FPv- z^rU^Zq@B-VXYS{u?8dm#S@${WN@R|c_ufn@F^Msiro@yUZI-HV!TOU@K~jZ`WxVRM z$?xEMd=-j9APp&mw5Ub#bi_Hz^r~KxR@DSh%jJaRUP)2<0%aNcJF*1W2v_0zT=~yG z4OF!N0Xd_pjx!QE9W?;P-wQ{jBho%W&VJVn#+fdv7R~W&P~KyX^PKUt+Co262n@n` zoE>pCoBr7Bh1O6{JiEeTP-sxRL~^L$Pccez*at+e=+GtlIYa6-AO|U(jRg48yRao3 z=@uyXOjBYc?w!JQozd^Jx-H1c%!hCaZH@wzb;PUm03pr>eUm?Ns|<`husW=0Sh_EZTDa)`2FWfrm>7q%nEh)16D{E z7rBv-C0uzW4iKG&(xe}@FW%)Q(YW1QA;?*2Os;E%*t>zMh1={3nW+aR* zm5rUljkzf)Fz$4$NX1Kno3@&@T(7k8-#>DmYcGP+8^VoF`a&I#jJ-u()2h7GPB{W# z{RW?H{Zb1Fv^Go`R^!&}!{eA3&HZnJA3Uj@40B_#1YL5nb<(_+KVHJ;Z4}!LC(Fk5 zFxLFvx|fxCdz0|hEclLkE~H`v?x^Q?MK$dAJ|7}GGUWXK*xSL1UYdk{j%>m#HKz1H z{X#{XBXGRtxwzt7*6RGr>rB*R69{QbeIH40U4^J!vBR=HuA|Ga6%+7uC!~Bs2{Nvc zSI%^pU!7=be09GYd~sK{*1Tb*yq1zSloi)5taHt}8ni0$7|^Xh`kwSu z(j^fGp^L_g(kB~#dVi?+e#MiWKq=SR7`&3J5(9S}fZ2D;8|z_j=E(gC`}E(eg%^9g z7@PCj#|cjhq0@yUkLLt0tNC{xL(TSIU!SecHWeUTme>>^R;(q3$mhrA!-CB9um3az z-#5mUN9r)>;li7(9SYzdZ7T&|UenjwHL#)|^}~jl`n|=yjsi{>M|e^oGg4Q*4G_~t_mQIo362lB+HwnKd3PU_bY1VrKAMA&=ukU zr*1yjx*RBQ2PXkC!MB^xKF%zm9($i|FqjAmnX!h+){>*2a{PE)BV_J0h6UPx3tTQd z>h_7x0R7}qo-)cjFL55)Df`jpm5MPReMnQHraWNo-1;BskN29Q7~Fa2vEXA@%S7D~ zz~^6c3NUM3XMx%aWHt{6C{Hv8b!sl;9PcKr&ibj5ESwic_PucTVxnGqgj%UKgPZi* zUhcl(^Kjl#I1EGZDAX2SsQ^t;>jT^)&cBDH+)ipZOphw0UG027Kyfc$ntHe{XW09f zvL1LI^qf9NcY&Dj#P@HLLt|&PXYHaBT?lbR2gS?rrsr0#^#(2iVKPt}Cdi41@q3x< z=N$W<-P5%!bky&mI9CP=#vZTNA3n({!rHc~LDCY*xQ_&Jz`P#<2kFvbaUe+FNw3(} znEN>(DD1=p6AepgEpPLM(s?9eIUD*iPE*fvFwm>-q=!TwR>(WT%;_u=pI!CF2`Wjt zh|O<%lnUbn&@JrKWT>jL1J%R~RG^yp+>VnKVkVPsW($yj3;bWY({UAMPu@MY-W_YZ z-Nqka^%O?t_|AHG+h6p)TrTQ0oyp#fA8#*N>>i)eJhP=dSG`}6N_x4|r67REZLX^s z>F!J)suXHlYy1*nMk&9vkUvWh(vj4O_IN>lk&1oqSe(yyWGM{l+JNvU1ya&SgC;BO ztSOi(>u7%n9xh=j!Z-TNsw1Yepc9Qy4-N9uS^#<)_I)@^F{Eq@I^-vqZ>qwZ0~FN{ z5{iEOIVfNKnX^&3VlCcy%11lQZIfnUa09};iJJ}zo+{e4H zh*2lJOZpbjeW1BE>^ve)8c{fQq&BTbLC})TL86B0YQuq@Ds9xfS{lC2ts_`s&HMy& zNFCT_c1UcJ*Z~LDA)PytjsE&ComVL2XT=Ymx#;JRgeAl z&yaZ|$Lr@Xm7GzkOk#tHE)(rJ_aGi9^hM*DOZ#`pH`3OPbvbHJmSw{(h zbPo(U9{+cMDjRcqKVPg?d4!4~LbgHWT)XC+`CQFyXQO#}ktA85iSFWxH@`4ec0H`^ z=IQlAPJ2t?~&v0mRPaA-v3Z7SJm8;NM~mc{=NQT5en((wM799+dW{7ZeO8Y0;v zFzr*~%s7qw{Q`wMN$2GPI#IZ|k9gCdKrTqp19p~?;Jx0F-eWbl*KfCL)b2Kwwq(Nd zzVU2{$2a;rel5<~%m5SqZkkQwo+XDSv$N3+NP;)-Dj4ockE@#6E6o}FZ~6JX%+Krj z9ZFX_kX9E*60%k{w9-0iW9m@!DppvtQ6mm%_I__*W?fDA6C#b&ZG3W0AI_PNvi~Is z2w%7{D8ua#Xjm=_d)p?JP-NN*8dIUL=Gw~TK`sxGF5?3D4++}_mSQO#5ZV-M8Quch z20*bO{4H`0-@05<_q72)V31C<;d{bg!^sHq0h(trv?gY+HYQTE>1FOdM4VMS#UFUljJ3q{+||z|4h+di7e#B z+TNY$&>iVuwUIfP^uF6E=~MgU#f`>v+N879?VpCH&iKP$3@G4Y+?w8y!N$3w=}WoajV(Q2QF${fd#{#Iq*L))65 zr{-2Q|F;I`JFERIen|Z!YrJLf4}d7BOX$Id3}ljWM+{<8{f%ThSSyywagZFs6l?bh z0C4>O0k~YgukpkBmJWdC%2N#Vmm;}^BGCh61{c;;+O1DVwrW<^zw8WwscjaEcPiTx z)S}w6DqEaM`5^`Xq>=j_T950#S1KM4JDXIe8NvH_UK`^nq0DLAbfShyT6nYSr2d@2 z@8ww%88{Vos+;M-sMSGDd+lqEZ0GVl5!A8wPnXyg0+hS=MDh8_i1C5UQ#Hb3-r~ja z&^p^2J3)jHEU}V!hkkIK`FvC+2s~;n1f+DCR#z2puqnbpPgL;QFNlJ5eqyV=DI`<_J>nmu9u!$_scclG_jOvx(NY@U-4kfY8=!Qa=Cm?NTLd)Ice+3KjbMeTP0i; zivVrPSaQI{jGe@JI4XW9m}hj&qE;~hGN*9KF`lspwM~I6hfAB42y&lHb7gGEonge( zG^@r*Ar-XKP7{M^jM{5#?3NM4u&n8fi%N}t49h4V@n(rDlGv-FX6F0&MIfX?k-M7okbMjq4KdfR%=Av_)MvvVCqqzCZB_0fbP-m}P__1^? za1sv)a8uc5J4F<~OcrxJBhcI~!21sX~KZ+5iP(Lgaa%WG=T3`3Yv z^nESj37n0c=W~Om&`x8EXQe7fjj4Ew%)}L8>?0$f7KR3-Ii|u2K1_w9W3N;Jrb!SO z%aRr)^rRTtayH3+H*Vk*N=Kjos2h0oJ}s>w2T+C1{{xEWOEc8+X z(nxZf>pZh{mrFJ4U7>3~;4riz56Ux#g2*jIDfEGae|M-@aZs0G%8*pPiG*7v8k1bq zr&HNB?H9mx_%N&6P(xItGxU2*7c`7#BklGu4>zr?u}=)!D}K$%WI`L${T8TsYf#S4 zMor*wnBV&B;WjaL`Uwb>Zp^2Etno_aZrb+v9%kPYsM?70@qF9z(?0vRv_3Y__QP$R z7A;H1{du2z1PYDiXM5RDqG+Z$tp*@P>^fdOxqK*b`Ui65f->Ti{RDR+pD-t_|L*;Z zhH~$-P`@>(nGYbSk?W1i`#BeU%LuxxN%XUT2)k4P##F<4E}E1sEy1HPH1 z1xuSd3#uWks?}*bZ877t>Z0S0>o(HQT4waG4wdLUn9HbgKQHRu=g>Arwvat-m z%jv1O>LOIe&L+B*Q4q#=Ftvc%1+F9$&U6||(OBsgZb2zL!nP2Gau09;$8@>{U&U(V z+IToVxt~nTGw3I!>@RFQQQ<#8HNqKUK8|UtWQ>8O!l;t*#3uU{H?N_1eL5X`kixY5 zKS(i$QkuMIYdAUH08VSG@_M`h=I~DgSiQM=m#RN78utoZ4;$~K-Q5d2sO}$}CYq_! zr|GIij;EYQ94&HOCrHk*h!eF_9CP{amrOKEo|oz_I~)4y&`^^7f5P*HnlCuk_(!^Y z{1zYMET%b(i%gnHu^FfaeTrK9gf`OibJz!NxXLd0_??u-?{@t4r8F(>FJ;sqMlh~_wuC*G;c@ui`C@n&j{rW}-n}{c~PC z=#O`%Ly7aH%Kqc0EV=jSF_DMV)T;iO(tRPZN&FU_Vc*HJ+ju&D1JTms9=_WVV~ZO~ z^a8o0SM{!G$T3M2`4GC_@jp3$VEkdLQ0d?=*;;1$lB~h7W*`gUYVv@d~-f z9A(*{RpnLGmM>Mmpm3^EWp1f)!o84ZE91Vo4t`BTa!=0r6ytB6c~$-meb$?)VDp13 zE(d3o*cAbp3;czDTQGj{EV(jRBe?a*Mm>>R3ue89+wiv2l|A*EG*%h%lqhF2Aj!?Y zxDU?i*0*&9CRo2?m1)D{sJ{kHW~g<7lmR;2{ae?0fnNd<2ea0k*APS-UNT}<`h*5z z5|-}KDkglIktcIRVT24+I^P&eXD5w&Q_bD~4*`MT%ObeTR!l{P1v)&dPs^01;e6gk^{ z+MjJ$Z>wTdIq7)%|EPM)sH(m%To|Mq=@O*7OS-$eySux)rMtT&r5iy)>6Y$JDG|8) z;O~F$`+ji<=ja%F@3rTe^Qjdf6d%K`+hdNxif|LQ9sla~1QTHKmviYWlSiG)asLed z?KOLdMSx_z3WBGF12K0H_t!ujy49RrurTNrK@1Sg_*GNc5Ylc09yPrv#(oYD;yv~{ zDhmYWS)icMa;dyVNeV2U_#~u0uzz-tl!_YPxPo`D6L(ZeN0|nuM3&N~mQ{u$jGNsG zPYStWQji-TRSAS;lT+yxc7VUH{*q!03s9Yk?6d|xLD9;HLcd`5Ex(8%*GAbPGO0q) z`~b=EDIl8Cj(CyOk8xsHc8O`UnITWmqAQpdeMK?nbyy}oX)Gp@Y#Z?qWWXAaEk z-8Z8#Gq&d(?LPN$7C_DKAX+!5Z%WKZDI10E-1F{aWcVTQp-2?Qgx6$TsThKYUnkO@ zJY7$U8j%t=oLM^%0~**JKh%;GdzCN&<6l|F8@3ZSKN^=Z0H`>LTLFQOGifQ4aZo|v z46LZV8g(pU!hm8HwUs7fLH*Z>4?&w)Wub+krd+M#UucBjiwn~L8{YgJW2`d9Zq+4d)rs$@HD5h2`boGoasxyS<_Bh0B(>s@M{Z@a0qL7C3nFG+4 zyq~R=)t15x-3mrB8Va!KhgR24>@mzL$6H!wmVYos;qiZI_Wi^D&OvGITi93wy-VW% z#TWO1Gs=bn0776Z4y_v?1UBv7?&*B_AfoJC3r^HrlICqXv<~;5z=7&0+Aa6#q8=QY zKe%#{kV`uY%j^yc6udoi-5!N5uRGVn2i@8EsZE`o76M)-POn=NwfF!s&g~)KGn=%z z_e;j1i9Cs|uVj4uF7m)|*qW*K);Tqy7B>g3`*Eu4y$>e?%7VXvd>3(wC>&+MRZ|Mt zVnL3mH07ttaxA%1%^;OOM1f?=oZxm6m`Up!EoU0&zt&%Cg-Tx^BMf?8>U1{N8qRWW zfAKTD3YbqFrPsy(oeX$cW{gV=*yz|<^BDHO>1XtBy)kI%ZawGheY%B7?)|e?R@=)C zoP;gle@~nI+H%Wx)4b93OuL!&e6`~7T35O8>i(-^qpJytjp_FMSMSPQ|D=cQntpHB z&QR0qi-7;rxlgY_?=8f^3;*!*Hs`)Tz~dhQyT9ePA(vTuWFPb*<-? zPxv>#YA^gyFKsxyWfXRW%P+c;ySw)dTFtEER;!xWy-4=5*rYZlTK8XzhSb;Y2i93( zXX^Z0A3byep8GqA{;o4~-L5aRw$eYJrq?n0H73cftUv6X&@rE0jxXA^`ra2-8yw^R zIs5GKWW<*K;&$@+zI8eO`TVT4wfoh-JK!hjx>G>?Z3sBKgH7+OgI?Zl* z(}q6eZm!*PH8*?lrz3;)m+Xx$*2nCP?)$sVXg>P1qjZh5ye?B!=bH&!O~3mPJD#%& z&5p{~rVkfizD{}{fgf<2gwSrdjV8g6%(i3Q#sCLSdz9J1WIe3(vmBPbJnboov#m* zb+5LWcUt(cMe*OgFanKlcppnEEPbfK{&c9keo+{EEr{J*h7j?^r}h`(cO`%cIE^s@ z_d5yfInme)Xe|31zcw`bWJ9sO+tAVsH9YA(80*{% zeE%lx?g=!C)8AsqKlJSWshfO}WUk+5#xpI?R7UY*)Bxq+2d8gu;WSe`H0^ukmJ~j` zh>E|V`0s9+Il20wd$u6_60IlBs!Tr9O&apk^FG^dJ@!vL>Wpyyt+1cr+EL7m{CW7W zmD$*2aLMU=y4p?;p}JcKUlu&r$|}v@Bp+3;cb&zc}G9= zH2+?H8~7X5?{9qpkEFqvMzKojXkKAum|OPrJLQqZVB(CScin*ByYcd`@8eeL5NkSK z#72tOm2`n`f6wH2acA~8)7tX0z{y0wBkNuD*-!F}>tS23yGp-`$H5*S1eZmh2nPoJ zb|d>D4SmSAYvcHyHd@AR5udSk%G}A%+%b%d0iR`Q6|2}R%M)W5OK9Sfcd9I|Fv;@O zY5*O+1*W7N9-uulsOsw?JuJnJr;u~?1d+EuL&2i*VOUZOkF7Z`EFBQwKM0Jl>$ND#MHPNfqO^DlvX zHbs>PTe4MjuDOUf{MT>6V_!YfGzUy-b;@>ra@s-mS=!dVOWFwrq1)gpiEebC|R% zLf@fy&1@;k7sF5z7@O=K3>YiGF~H&_Z*iJGydGicX|`O)FnC=v{z|gL)iFU({nBvz zxOmvw+S9auW_z;m*C1?UefsEL+jW#Bh@qq8&^uNZ{ZzSXT1LBMg!~KnAG#Y3K}P15 znQ>tny{rsz%6k=0X94VJqjBM+>>Moi_PT~qKPyM2+WkgFLYDT)+eW!_S_^lB{@JJJ z!iFjY5$!UP(umLE5y;;c!cGf!$;=Nc0Q^Lm+x75}JxY7OusFG(FqvU-D)BavG%<*D zR?R?Z*NS2!5m0iAu{uL`6jf*rn}{Qe7;w9bCDv}sNHi5BHrd)t7x6novN)$f|2~e( zi8YJWjppDimYKF}+17fE+Jc}IOD~dZ(Z=hjPN{7y2pSAn3c4|lAE7}e7)_2P{PYg* zlXfF{LeOv=t~$5%4T2QcsP$+34{Y{x&T-lGs`KTS^KxqYbw38bZ}uIPR}tWHk<11+ zRU{@GkxG#3LY7yh1`@nybKZ51BhV+}A|T|_uHo1<)fyOic9)7q$-(g1qq;S&Rypib znxnN)JIa7TB52TsprlhXlQHb@hM=4;TRaR3ahMZzNgXlG6Z0dTtPEKje7 z*(|vYN)CpwVCugKLCPUobmwu7S}YFyz%wYr5Qb@Y@t3iAM9pA}e6MbL*=W+8#~8Ef z0qSwe$@hKasy%i=K(5+t9joB$%9l%Lk-jOGeL(QnN50PVY}5HI+Z6a{kTCR^c=V*1*z z9qzc=yC39VxxdJ`(g-q_{d~|Y#PXt+vb0X*FM0G1NzE(2t-EUA1wecHYY-SZ}r|#?&9%fqSj$dgtD_~@$~uvrM*ptTDv&$ zH{E$s)`I*noc5|)N>?_ua zb-EvIf5uW@6vHi8cR1`OHCAGJ8O^|<9*L>}Pu92;1@y}moUzUJJ!t;3!?rFRIJe!j^c_gfqnxsg9g z*{iBNR@KNftdpy?i|=l67kv>Z&sgEQ+tB}Ox)zf!7 z9kW4ryqmW|HWkqhfVc}MUUgxn@<_zeVG)?D-)&U&LtoZ<;|_2F zVzr(jSE=E%#`nPvBbtSmNIoqn8zjbS(rQ(opdA4PB3Q(m0ugcZp8_%d!zNn&-P)c_ zO!=2IqUUy(6KeySzt)d?JL|Io9=(|`(yg}Iz2G5Shzxbv9N;JV!EbWQllHRHDB@KoUhoSkE z|FOc?xp~qqqLY&$i)bYoC4WM=uQ( zvCX}a0H*sC&O4q^jps)}uqKCMm*Zf!iz)sc_gff=9^H%pCeQ1E)z;xF?CIy6SF?2R zA9W++&j>OHs`~npp5}X>AhV74@hh7*Mw8h5kceemeI1C0tI1^?=f4!tVcEa9ElEK< zwrU=%hEy1%DTF!i0Giq?3N~GJe29KC+rW`7oj0;>j@w{cqw-Xu?tEc23Cv&{kie)Q ziBm$Q9DMA-Q0u`!HNbnqGg!l9?+t!XhP*Cirf5`^&iyFip(GO%z^zBv!}rig$RTy8 zUDZ!EB~Bz8E$(qdr&ein3yH=4ozD^-$qqHfuvSzi%|xb)UxZOBi3OyA-Flz*^^*mG zDi1CKM@d^ln4l^9LLUL~45n-bBO_Qz96En0jJyY<-z<+S5%_q9y)m7XAjA59^!V`5 zhHvMH=x11Cz%v$1&fQZEt)fBfCltC!Ka$aQOYNwhY3 zk39FYc7Jf$$9=oLP>(@X-ZDKsy@#roSDDbeiz%SY<>g`9;%R&^;Mo6HA+gXd!o~cbBCfLNq0P9S?I;ps9uu@@BCB$!3nKAzd!qe zWviL{IvG!^UE~Jx`RWG6u=0i79bu6z{nU*h!o%2(&I}D z;dUc4L+Pcsm?(>~FDn?ZWyXNcJnv1XuY61uW>BjCl2_RQG^Qy7z#h}9o4Beg$mkwf zVwze)CaS!eV3$=2_5~yuA*05XR34j2F%pBCI0U?~^m(twmtR}E9xJ+hofU@+e-7Nl zbURp#4`O%Z>x%`Bv1>xm6jbsTBq`;#1YH-%HMep>k!iDFDw_!IvHo7$B`^Zfg9xfc zr8BbTOS`&}#9RZvBZKK|osE&eFlR6^6pi>F18J(VPu&#(vH5>kFLuCAVR2jt4f}koC3x z{*HarIvC6j4}NJpS)!QdaxT3&3Spl&e>Pimy0PibzVpz3F>(aRD*YR0f*Oa|6a|Hp zrU;($FF0Mgvs|s!F2~vz>x)2kQ2-7id`q|s50xrM(Hs%bU!%O(E5@4z7RosM8einj zU;86+{rxq#0Ee3pkcOICv=m&Zy%{fk)fVTZaY(q-J7L6`|7I$xYc+V$^N3qvRC7QH zGj|c2Is-m-CLybGWIdp83wah)FKmFhf~>0seHgAn(0Yf;YKO{5hiFz=Oo{vL;X^~# zt&qa$@IeeVXiE1zx)e z_tt(kHLp$5&WwX!C(w2-$#4f!E+o}W2Lpk19Ts`18##yk4W{!sd{qeD(l>V|KT9V7 z!nQkP!~KF6Vv%ck;=@{#OLzu|@`G)ssAX$X(dO~!8cLoh=ghv*&i!SuB4-m%gx4Dj z>=XI-?gl`8ly)#0S5bsm=pn_oK;6G*;C<}qx|`BMqmMT@TlHJ<`V10s4R8MnL~R`o^tD>$<4|`hV)-5NkmH zUjfqp2Sk{_Dhk3u8h>pbv$hJE2hI!pL_JYh%W9LW^G=H%$%s9VDUI4Z29h>qo(V8J z0FR{s4|puqrA{Bd200hXH8;qE=fp+Ul*+B*HroUMRxt2tF95>#W;0};N0T)z_A?p zsP57ZXL%Tz2S^vdKyV$vX%PvUJZs}j8HMUnq7^hpw2r%YbWSqToqKh79$R1R_Yd)D z=+Utn$j)MLi+Z1opPzNcOisA&U${;39r3o!?y~M6zh-mJO?)7=J-Fk{{-{p4rl;S- zp{eQH@Iz7rN8uyvtaK%GNG{h@dq8bFrJd93)p;ThY+#{?3Bt4)Ss;i%5#sU%w*xmg zSPj0yeo>^VticJ0BGX_CPy>214O8C{Uw~{cg4rwL_TSQ|tl8?X)8+yH`&QP9r2Yoe zbv&f1UD4WnQIH-KVoBx}DI06dG7x%5g{oDEj@5zl%sjw-7}ZhG#Pjx>Vb?Ta2C5g< zcvkm54_{yhAiX?Ih{sD@Em8Od2OIN#fBee`Br%lc5(n1bZ z5yL3fc;gB)|3E>B9&(K}`b8I!;L6HxTrmi-)Z`ZdV=SEKRQQR_hzsaNOqpE)A*;M0 z-P`o70o6ce#lLx;M7CI|Jdw1!g`Rt2fTWHY@HZyf7-{yI9wPJ6nCY~LWTjXqQZhLv zx?*TRj%C9BlHeg&8L0XFIPd{9DTwO!on5#VY$Cv&Va5Qk_EJDjB`9=M^jYRX?fB<; zf2nppL|Z6m4aOzm?-0i@FdWe^%nuEvIga|KjzOjOUGD*dnxO6gTfeSu(!gISQx1iX zZJ}CtcJ<=#^T+MCj zVVSLP*BEjuc&y^3-ERb&rN?HG=}ZE=T^RGM?kLz8Q$HUP3O(U`TH@0OMDkjbVqNM{ z*HcbhlGCS%LB89o84#DTj>*OtrO^d>iT3ZhyFzKXJG3ZL(g}=Y64Ecrg_jMKx>V=E<^!QCOxu&9exMs_ zegXL!N|}%p6P?ObZtBQXa2N~U1q@!JmA->#pZ_lNbGt`hzerVZ8k)0-#U`_7mSCrp z2~!d1OdaEYN21Zw%YEr@F$=?&)`=a3l@j&d`^JE*-FZpK8j+i50m&&q`ZFwoykSOF2jAh`Zm zwb`vpK~2vdd0zH!NNoL)lUrjEPihp}Y6mZB73B&#nZyAEU|d{NM8a1S4c0yoD(TV^ zT#S%x1Nlr1N%o^@sopyPBe)3J8ZH!)%lIL{!_a96=I9a)>98!NEZ54ayvD~b$t5cWM#<#$7sL%x#tZM0^ieJs>0{Hy8s=+F7CF< zrvrtcGr2U&JU&)|(}|v?-Cf2sZjwLvIaT<*bl>&fG7SpaOTCR@s}TIgqMm&crrHDHEjVel(F)*mY+a=U&osrO@^qkd z-|rppNT<|=+nm}!Q(uN5Jf8pn#=mW=0X-g08eVt8Ue8Ofofr6l)3FVAuXoU_8S~w& zPa8{pqF(z|k2{I9`gfPkDz5^T{SN9xgI|x)(f=t8JOzL%y9c*i6V4~T*TW6|Yb>0~v zR?i;GdMtC_CEdP;%6S;2h3gU8;?yMKPN@=Qim6Aey@}{oGmN$#`-DoYLl&pT+Zo3| z454=@d9Ca+97gDm0VF3ADkU!Eu0kvq!*hfqG+TSC9LKmU`^WD~>tig`iQaerspnNW zBe68Rpq=)C@FVnrFa~l2_5!@^Lr;Ca~w~0+HzPP9557+Z|CQKTuwYy<{AX{N;M}~jK`|GeCxXWD|ug> zE|!;v6d1tF>X3haF*$-xdlCM8GP)iYbB7??8~qdo^HZz#d>SG53B`xVzoYHxq@TvL zX=Of%ZZ9Z)`h;VjnBo#Z|hNe>ny*-w;3s*gE?FKDj-dehE!#sSdsJBm!9 zpLLw5_cH*tOIjsrUi?;I7h$>&bA@BS4{P7geu_S(AH`HB=3n@i=Wz<1n8_Hiw2Q4r zO!!ZSI{``zI(`GQs%#GAX_+ncq{P5H4__~KY7&Tu`rlAZk8g#hy zB{m#$E6a?cv0f3m+kOnir+F9^)klx!{;Rhb4=mdJAsE@Bu>_@npHrHXpsC^BMhX-xFN?!9*ZPXloyYT~vDu*_P~RS@xj z1Y+)=1cFS4`m8Z!qO`ijMDutoHUFc`GNu^By|3urU?YZF`3EGfQDxIycS@^y&1fUq zXmGccsB+;0~wL@{PHedx#RvXp^iE!rEK^PE$d`189n*@?-{;4fzT{WE;6y6$uZp5%9Cyw0fsw zR7Rg#;(yRfGYuVAr%}l@vOpcAY4id0?{kx_ybe;-vyi`tiEJeeBT%aQq0=zDvB@+L(Voh1PS(yS7K#?hZT~eZClEixhE;d}A6K*t4 zA{ZA?_QvXnl)wI(W}sL^8C^M(zeCUwa2<<>kQ7g|m&k5lIf)=k@{(=^{B=KU-~4qJ z^UQHItDnOcLag$}&OrV;PVR1h9L09vdD~AGj-5qqw@WsQmUV5@wjKnf{!%livoteA zT778Bt7rS2g00s60m$!L20q;Zz&&VAbETvja@Tit3jn15+j%SofNXXW(deyWPy}7b z07zfC6NS3LW@YIQv0lfA6Ey1yn+)r%SDa-XmY|qvhtD2BvQIiCsgDqB_>l~-u?-NY z{rGST9@0)6)4t6xgu23pv$ z>1k~GUR53FK-rahE4$LiRKS-d1`ma(71TUH`4A^5Ialx%$9Wpn9S4GH9)-$fVHk*C zl(QDA%b^QZ4b|>NZSu@@Gs8*nLMuwI_q!gEIXok3jwS+35jK6@Cwp+84D4Vo7;nUJ zd^^SJqpL2F0ULta73R7VNOPWBLwO1=_=Vk{jJbY*2O#gpio48u;HVr)Z0LqjDaiB? zIG8Z8w7C6pE=RwsFt3>|h0>nV8T+>17y1I@a=^H{NJtqQC?!%*OwC8auP@Mg#QI{% zukQIfFkoH0x}W^|_40Jm>*w(6>DT(xymshnxueJYCjLz!; zW7!YnQ~($3@cvyElt1=O(LNqpEx^PHgi>CGJ)(zr755v=8yx05xg1Pl*B1Ik7^KzL zm(sj6+T0JU?wzMr?*+l+l5Q*oebO z@4;}&n4jM`Yv#d_#eu}IEOD3Js2X~*cahV=J)4k@S0S+g6S$A4|M+k+@x5uSlhUU% zUwLQ*lw6Vc?XQnSFW&NyKAj}rQYQvo83SOVefLZQvkcN3}s{31K^^3f5 zOuuV`zppE3IBf%o?k}GVS8J2LZ&z05I!w3=Y;>|c!O)|jm>=pVrL8?xpDEkl9)v8n z_hYOFn#d-&+!VS5TtF=!@h{h6?IM?ifQ_W`$_1GnUoT8MU))UG%Ir~kARW7(2S6T$ zyHw-movyxM?Tk)*kjpaT*22m&lf@DEP@uQ!((Q3GH?VVpJmo&I#t}qyKvcs$>zrjp z9S8Y4M2LmHGI7&T*sew02=LS<3Ie{7@3b5`)m^2#ym)Xx6Yt{p*2I55k(HsLEa<;w zvIFchR#JPRnj|y@zAL8MH@90nWnW*nSZYnJ%+YF%wCho&pVK~;`8mZzShag~N|_Nc z8e$Ft^_w_-FdLed0D&WQU-Le#pCVj!8JzJV=sgyBmZAdN7IOY$t>P{s(-V827M3#L9jvoz?Q|zN7O}2k(gU5CZ&J zIW1bFV3`LTyXODd<*+7yB7j3{eYe0ijRGZ6frp$rf+k}8Z^CU%2Am7^f0RFsi!rQ) zG{@(qqx5R>UKRMxZ9I8czLYB~O}IAJ=hC^%jtO$?O|GjbOE@aC^nrjPPoj}f(x}wnZ^t_S?HN*B0q{U| zs$`_tQzrxiRO&-if`mcTW-~-8n`HxkJ_KQ4mVNH~@m&xwgZ1|zl-P1@Q~~rXV5xT`v`$912`ozZ zQa}ZNGb*8^v_{6lnt&P|{ri8(Zqh%n*)C0|Kqx^G>?7nVTx=1D7rR}*RGgy?zh=T9 z_6U^7HKme1@B68t7A`!8eB9axD`xd!vsUwcXc2hy0(SwPuz41Mf}^L19D|8j;cW{2 z2E(Ye666CQk!)qKy>vE7bbY7_@65w~{Q*(tFJZEYUKQTCn;(M*j&TwZVWtEna)`3)(6ES%+w(XnXvn%-~V@;^;kM%oPc>p##BL~#y z!=B%6)EV58j6Y%8!eW3gf#M;l+ZbnJrJ(Fca43C`0dvvK^7MN5IIO&wn+k)n+NusR^cI>7`RT&PC zkR5nyHM$q1(0irHg`G#6&Q)DTlf4f=WmzgJQWwy{6qZ#KQG9$hR7y6>3@?>EX@x8@ z&$tT_`#DF5i;4SP5xuFIu0kqOIPm9PA%!BuN<>yD738{Sf>B97cw1Keh9VOC8_(@j z&5%e@0JZ?EF${JoCD2;K_ZdAZ7Pi++y((#w>tCUv6p-b+X(V}yMS2W-3$ z;#q^uZqU6KbwO;nJ5v)K>v@bF27ISyj}<;gciWWM-v9h<*(XbPtr@55kGu5KT(1O6 zI_YHN&F!DdsTUcX^86OKTn2t;>)h>4IH5-mNl2W0t5hy+7s-)!91g(vCS|Fbb#@$ul*P`>F4w z-BF#!Xq?_uqpRe2FA~YRgesi_q|<(byN8m>$xz_mjyqyD83!=*a@3^z8Xg%-fY+x}doz|es>0#uL z>%C9BjelJ#qka^&&oS2By9?0wKD4zy2|rHvtT)i^^*lPeb2^G=RiDWCy|8bx}?t)w(Xw6Ls^ftt7 zYNQl+;XM-( zGLbO@9)8Y-)G65h+`f9ZH#w|d@AabKnoXK%d)m*AMFf`vZ;2kN19y+}bsC2sX%YK? zJWc&XhSSFIM)h6bLnHLvz|JIKt4ufGr$Na-*7LHLQJu`!&3HnsiKtC71v;je-t4_P zo`bqyc3C&&JRTD`0x+2MLL0os?Os)`$MW8UZR+~jXZ$^Lbd@UnFBSKj9Pg|`!Qj^O zMQd#afIy>0MM)P|b}-4}T?x<)Et$S11#T$14(NuCX}L)jlSu(L^iFjqiEIuS3>G<& zto)^t*rcix7E;KLM6y~ee1fVzfhHkpddZ{`1*`q`OykT@1h(xoW2(3WGH{)PVzvUR z_V^iCDYUM*kEOWZr;4eFlgRWzniO1l)?ACCa7XQW;*?oQDKy|+l^2<(pil>k7ybKH^m?4HjbVS}dpOdtSTU zpA@g{82rxTL&y|CC%}l6(ah^ewnT6#=dqBdEd zO;X9BbDo54Ws=T=Z{n6(26LKVUdF1kMWLW%PDg`0^k1rWa7&$oBNjp|n#bU7r<{J* z%I#zt?2*a2VorWcMGvu{!L$k|z>ZSq@^X@a%_G;{9hky(y$%2}vY;nnnbCn;#cPvx zaJP+1Y!2M2A5T)jBY*y!%^e~(u;T5YXiymH28M&;QXR#mGY$EK25|S2Q6`GGqgd$+ z!*>6d2Om30V~??; zAf7hB$Jbd5Wk#AvG21UaEzrDJXVn2WKko=JrZ7dAL~%bC*qd_!C=E9AQ`QlYVGJ z#3KZ9)988}nj`1U-`nZ;<2ESou>f~a?m#Ljr^J7-;j}}kG7BmZXnLg*KeXMHH>CO( zY20(bo4GJzZJtDxMjQG4OM0tAdU`K)UBKEe;K-0a*V{)txjZ*_2b1?~Wn-cAdoVfO ze4xM9ba!-r^(rbR9Wyk)3KDjbEDAm za-P=xG0OP0%0_GfiHBI!ls=!NT~=wu0XZKvFy=a;&`AO@$iPV|F1Rk-@o@_hBUy__ zJaKe9dMa?Jw`}dt@A1f~wh)L?z8r^&k4kO!-=b>QW`Y$eIipQS^#;yB3tHCDQ1*jy z!Tn^ZJ^cbxKnVm*g}%o_NKPJU6y2wpRp-It!Vc7q{@5HIM^h+NdxMG>a;!ER`j&FI z^y9g5i`Y&RkAkhP@B%gti1q|h`h-npreK{}KCUJNqWUGHpHdVW`30fl9VbOhZF@_R z5%BJI{$DoTpH@YhwDiw0Atx9T(duj{3Ty$=`~oO3J`fAZQuhl}!PZ+EHb%h~K&plz zJ%pe;cxPq8JOcOnB%KRm-$8X(!^%ud?clF9e|$M%9R@P-~E15#GFwJThK3YP;+vu7{ z>?gE|Tj(pet)*vD_LV(DkS-eSLuDSsw;C(6l!AoLx;61gKhHThL#ei><57{5nNEDBN&YB2RTuJm|K!;6XdcT*3tF11O|m+vEI_t zO|h~5c|H4gcp%9HC|c|d@T&u($D5i-)!+Ii0XT0dr36751$Y;5B+pT7z>8%lIG7J- zmYWK}R@xP)ZifhI=?P@yTRA3j)mr)t!kATa7Xak>w{7rdKocZ52+{<#O9_V1bp)!b z#Ty2CLr$LZs@v;hFcl0{OVkVA!cM5vvQ~dc{P-PQQD$6eyw#{n-2UqLJzXQ-4fGgr ztq8Zyhr*kjJf|;!u5Lq?%E~QAC;iX_J!o10S1ors;@Bs z%hF8Xd0Gf0l;)QBLaxLTy)22;QlmN}6B7xs^?>3@%}++8#v}>~OKr{;a^6{=0qIN< z=*V8D;LV357Ha&#;MecF>Rjl4Y%n%($|6k+>1rJVspPoxIuB6{~Mr`l2hD(I87nB_!>~0CfoE>1@6a_zN$uK#00nc zicv%b8bu(nT-Hz#?H|hrKOEER{}{?VE&}Ys(%UYQ7FeoC@RH#F^kc^~a^$Y$9CR-#HW7_VBlA&l|9lavK5qadrMGr9NR4aSO>&*s;HOMri zAtBt}k%GQ=`Q-m?FZIsRnMITm6GPXxKu)1M4);eo@DPggW)2or}i5LB8KlZ11Ht|ev5F)!J-Q3f8u$O zySMsnd(ARp$YwkDiC!N%L=5ZOzo1iCaKSX@8z<3E^s>-{Z!gujRh3!Kxl7OoKe+VI zeV^jeK$o=JOl$^R67>OIO3MFofQEs`lqMVXoZO5E&Htuyl*d%L3Z<$8J-!_=E%P=7j3Kf7Qha0Cn2rM}q;ERZVdx)-dR|2FfS$x}Ui8~TQS(S+f zN4<<3_vgO@k7qAOEtEB$AM|*9-yV;$+J^~AKj85Ye!4j{0V=)3#tK^w)S*dz6+g7i zvB~c(SzM~ogf*Y5KR9%fM8zl;`*P(j)O}nTz;eU+RQ#K{VBv8Skt+EtgjJdd!j3fK z9#;cz`uinb)if@UCI$pY=4mzXd_FV@#Ur_lUHA?>oo}qFlKmkTN^7RW!r^u^G5;Vg zlWm7vVJbB=ygYHgV&F8?P$$1Og9Lmj@g+s1@*6K#Y#8S4Cl zW*|cXo)lm(U*68N$*e>+DZzjS&a{aI%%s4XHpLf~c*E~Xm^{_sLpV#B{zyGMmj{r*%4C4@xw`O-y}KUdS_Bmy7x(X8VK-X0|NY{cLcwlzUFs(TqG5Z{CVCO zBUsF8nlf87U1cG)d7-(?BoYgG27BIF9Q<@bhY@ZBx2?o`@WZJct0*>5cb5bk{Jp4{ zMEj`Z_$_(Py<=GuLja${7PgWaTV|rqMI==5`HvO2k2#dZcau# z2A}5CmvSOo!p{_!#>~1GM|l z#+~GyacLE>o>-vJdFg;&q#W%nnQaxCr|APc8hP*oyvXbcCc4f9UpQ^o$Htny{u6CN z5_GMs-Qa!~zb@@QZdk^U5?;+LO5IXAnXt{N#(9L$(#ZCntT=eFVHq#Sf+7!wusND} zGzN?~P?Csdtx(QknK4p78=Y1MP2W_#V)OSY*8&NMXxT<+Y+V-1XiK+PVp*$NG}}Ou zaUz8VZWd!?+m$YpUpB92dm8VJV~^1i;hF4ehQ3E{x9|7e+kJ4lFy1g+JDHIR#UV9b zlPpCmSQr8Ba4rt@7BbL%;e59`C2(3(&~^WL)iK4gXQ+25k?eCY;eMY_&)}y=(Dh5e zrLtvz%(E;gdTJ<<@0rH{O|8JI(d5ac$YopGPuyW^VtRY3a$-+p?b#sWG=Q}jS=$7Y zWHf`P*2AsG`!dg!XSOi>MGlA^mLFh1Qx8D7bbV4 zj_|UnRfP<4+R@F%Wl8}xk&QwT}r*X#tIE;x&P#>5){oH7$_Jv&Ute?1eCf zjOu#D;;MGyh(d9Ds*o&eWvg7Loyr~AQGdKVFU2=3yW^QgJ**tt!6bv8IVV!>^onfX zg7Q7^&svWZAE>?av}loJ_gY|Di};{_xq|!iDAB!a@Oku1Dga1$H02pUk{TJnqnrba z?dKDcKLTXVWxtEKrJ{8|K5ZNO>2sD%o)mBAbDlQC4_jcSpZJC&pCg5$;N-{6E^z(r zC?8VF!;*L$!jS0imBNp5faZ#d)V6Ks5^zOp@I{Jkdf+Rx?P^YrB%4Ufez#={1RKb1 zSc8VcE1JpeeC5Xl)+kMP_$<+H4%~zFzTyfb!dQBF^wpV;fz2XV&iO^^ri z;+L5ld#PWuarS+#%q&Qw7-&k#f}67?njEgq1Z}9pA{C|@Xk2V88c;Yi5cJlT+^fZ_ z$5YgaM+7VEt=z+?(#_3Ta5LCs)1NEN(dOl{N^#prP3*cRPlkVc`KkWBfu3|mKIh1A z^ZP)tU2ds~zD&i^&bsW3oQc*Xc0q+9UcMRJ8 zIv;QMhXXfv93lq%D0{NSuTpy`lpq{L<1wU-GQ}Dd1aLT8|+RV?pqX?}%-8D3+oX%JXg4eGZWt5U?HwSeOQ6tzn;!AcX2u^L8u zq0EpYXU2@N2vD2qQ_Ks<9}qS?JO#sW@zmyW@1+Dx3U z(NKPCT5@1)ov;GRSao5V2Lz3O_K%andbEt;OnQOE}nkC)<)(x+} zP`w$bA120Z^)k>hF?6DK$Tx zyel88VqGu?a50HO%xjWrWrCR@^UUFjSjD$A2|XXRY-PKn+w8|m#|oLdIo1!{O#XE7 zQ@n%^&B*pay|d8EKVbs?Tj2RHag^yp`L9iS^4$oue!=0l=RkXwa>lFF+$!m1lE0x* zN4*c7nM4QLQe!btTe|R87;0(C6QePK(=l@Pm1;iP17+bm=T9r}Xw0q}&3^D9_;D*< zi+y>f@*!IFOD52dnHa+bBkzac=ado4m7*~L!C4Mgon{bdvw?jVh#ECKb_OM_J{4Z} zCXWvP0DU-M-ZlX-Y}ITI1=NG`xJs?}i*hs&CkSA-^P!|v1yJdG{C!3Hp0<5+S zaVd4+YBD&=Oo6LW$Y7O8_ne^HB2fq=|9QT)ldG_C(cN3#dXODP)N3%DY#{KIJ#kAr z6vp%1eKW54=hVSlg7J>uyZ7mN)J*p8AFZM}_#LSZlP8uma-J5>u$EWZ)W^{#x3rVB zKMgmJ^V@&+CPrlE?wZRh83d1m(@PmXeRS(* zp-?WYU$efF#^#kh3U<2D9cLvM{lZj{9XKQQQyl7eJC)&+DwGa|bar@2V9GCP(DXcv zN552sGu)0*?T=mIDukbfd@so`ND2+ zh;BdBL+eEJu69HQ-4N~-+)x4lWRGT1&}nyjH>m%Yfqf%PsSS&ZRn1AYP4L# znt2S?rHNO+zK#uI#6e1d##Ll%%58 zhgdunWXz0OMh8PZX0+Y99pSuxM zv2cRTf6iJ}=6gE^UkT2NK1y|bh|W;{+8&X4tlANy5PTiDvt)s8MjUyxSQq?+YQLqS zNOI$2M{~NA$D9tyL>H=6ONjJB5}Vx7-Z%UZ1y#4FN8ASoY&#Z}7YTe2?V}4M#!dRd z*+r|Br6}pLmkGdWvxCXT;a_-A-y5ily-R4SY@pgxBndAVr*{g=Yg%lj#k31A;)9Q3 zcc-$-Vv0MskYZDTwPQj<2i=5=-VJ$ADGfgZB@8}`cWH?k(hCg4c`BaS)Tpvx_ z2oMCnSyvl8lekTH#}lm@@!8%{uOWIucBGBu87wHef1kV_KHI@E*Tt&17oRtJvr`?Q zn+08K3-T`3B}jPPT49b&f~E!GmDRu-*<3?2gf`96K%F>3FC|^S!9XY=QRZC|a@(N$ zPLeo&AM%?yeQgE+TK#%W;KD~W_YW{Xdx!C z&B_mFJ~j^+hn5)#6Iida{o_?JS3#8Eu&TsJ^ol3^r~r=#NGZ2jVQb;Clhjlo>lzU(k(URc6Er@a5y|m+8)wiP3cTRqL1l}n&keGOV(t8xVx|Lv zC9;U#OP#px^wbIhJP)@^?;b~eVrkBo9$sC=2fZf!V;ty?VX4+?v0pdV{lF4oz%+^X z$0P@99}QmYE+>pvnx6MIvwmUIhV=b*({3tE0rTi|Qkd<@CIcN+7`iRCv;qFFG^xcD(WF@YUnh~ zwQauyH%k}0_K5e0X_$jJFy4{_sM+^FSXE%9nVhh|p}~xs!|#Ul%15x7u`KZ@JsT~D zl1hE&J2K(fBB#%th~_M$Z<_r1mA+vSwj-4EMx%`S=Riv085KKZXH`9t0)6b(jt18^DE@t+ z@IGEilw2)rl zfg)ZTE5(gX%jRKe%z^e53T&~Z=0vSBH6v5Vs)!;-j|z|lYiM?+Fwlb#OW)Ij|z9J7HnnR<_lNfRZ%PTIx~oyN9ZIR-IFVJ%Zt zP)UVk6sq?stY8v>p<3;-s^ibn-Aqq!Y_3mrChEk)Zgt-w$He&#uNL0w2VEsB@$L(4 zZn_q1V!~+_CctHB4eKTF;5FZ|`i;@~>ILhBP@&|!k(vr6CsJ3)=CV7{&TJ-v73f8i zh-#b@=!Or$IYP1fE!nH$DBkc)5P>9R9Y$A-HkCelp^MN~dRlywEQ=!i(F0^zCH4-r=jI?P`+3VQ4dH3Z0 zmmBpO{c1$s0{s>8l8ZS0T^wkbncj$1ctHaFh4R?oqV;QdzG(B2rHjBXFHm8ITKovI z|Hj@Szw8MgBwm2j1SgtHMaj&Pgs7JoVH0hR{5yfLTAn%#Pr=*#@PQWNh+(KLB&8`cI&k?TNm8Y2K-0z^~yTMP(QO&IjwU@tl8g4sw za9gYrRcy09G8He{wLJ#N5gY}ZMxYlY4`P04il$R&)?wkK?+iYqWyg)s zk!>mtOvQC zBGQZ5;w(^LYcUlsfC5|NZ$>%Wyw{Va_K;;ylniO^*;;_=#W?xF(`ES4nghzYP0X>_ z(Qyazk^ZLe0kIqPK`YT%S6mEh=7egWGTN+1E|9R8fv7FinW7TyolUlmoM)I@#ARy$IRMBkGgDq&W`WckMprfXLjk=#tUSxp*YZ&58rwTTN3M&vYS7i6 zu~3eG3}dML?S%8UKab*POUKqYgc#mtFbjjuUvi_>msP)4^zY->*XMv0#as7|kFF zDd$>b|Mq5sl)kiV{~XP5q?)p_#-3LAerC-}IH&#}wL|Fq#1$gt9KevsGi4vp3rm;4 zES~s}-4^pN*ZRyOJXFlIPqa!O+pZw`3vK-s8S#JYWq970k>;i$F$BI?q0k&7&d14_ zM5?=WXRxqu?D?2Sa5+w>srNgoAvw8IB~}b3n&B9AcLtN*>N_qTvW-M-v`<-{@lG|d zm@*O>(fGdI|H3eRLF7Pe2SA4LX0IYkKT7+Dt@D2^H~j8X@zCM)2CHxsDO+OqT?PF z;(-X&`soA)cPQva+!xloBvvoO4R&5|>sK2b!Eiq}<*`xfaORp_#V^kP&p)i)DkFId~g>yJQrQJrIMyUzG!dfAyqDn2!&#S zC;vQ)8wv)eL$mBB4z!U+%coLNrqW@{BMs*&L1VGX$6MxkIZuqE!_}}Jfy=;+fWQHs zMFhWEf;x0)$*%m|$ThQ@WWg0&$l(=(EA*Py-{l2iY)oIn5fYzeJj2uB(_?j!T&x(A z(|d$cM!d9Y+$XjGHQg@%XeFMWA*S$dFkC!-M7{9f=&r-z%Rhq0j|m3aHm5mm@BL@j z3kTl5%3C7Xe;?p;{C!UF;djrH3#Ml5UpTgw)rk#fP(P^Jr(h%P!OGf$`r+fAo_!o= za%+7*BTgccYuxFv)1Y3@_E7UxGen_ND7{-!8Y*H;)hd zK3aF0mzO2cPaWUSef{=USR2|XqjN^^5;#fxZykCaT^Q!gX_kMIM9*u*#ksKFC#vL&NE@TH3j4x@$Wu6*4{cbS98ZCYb~QZYnRL58TC4EoWc~$i;xA#cyps zOID@)t&tgxNY7poThQfcikM*&%8*L5fCpy+eSoN^{^&sq<@`DzDp-1zxx7B*c8}5q z{(q>oKb4DXi2z~*{alib5%rGyGvjymH};O|@4;Oo2O4$*5QHbE8>5k~W7fcFfC6Fj{>SQ0Le zzCMh$`Mi-=Mf!Qb@13#-+g@tw9R=wj`FEro-J83)r_&lj>DY(Id1fty}%#DI_G6Nyr9} znZHDxik&xKw4F=FnY9<8$Cx2;qMp28m5p&r^1?9fIw7GwO8!h+F>Zvuzan7ZrB}Rk zEYBdXk~&-ib@R^|>xP44kF*pb`hN3Q07oo@$&qAzJe8+K<8J86 zES=%>7S~zg1tS&SEua0IkmT47vB~EzmrKDlT}p1;RnJ%LE4aSOiHVE$k}576$RkLD zKtF4misS(LS^HSF8EPDr6Z2J!WoWqL33e<3y=Qxy16{quZxNkxK@{Qc7r_$Ex4VF9 zA61H@j%%0~S z^U*rV*l_S^DcT4?z)(Y5ERV4P0uA| zkxnW2=){D7-XzV8#G)s>3eV;Xy@>*pRI4&{Bn7XN6vIaJkLp(K+EJ*K;KcGaC0NX8kE{xTW9)rxS@#dSt!r{!-a&Zn>j-iD&o6a)L=Hlbrv8|C zR|U_szRmYISKhgJH+JdJ*PZ-e@mVAJ3rtBPGUYbK?d*;o*W$n3vqS0K*B*&4!Ul3X zSJb9q7I9C`{urzo zg{x#FAye8)(W^EwC+5%_#zV`4ru;c8_K=qFJ338tyyBoG{QPX;3rVmVE4v4f}8 zM+>U>A++h2z76?-%Lpn*Sbz@TC5WAn^Uzcf{CRva&l~cK2gGsTkXgtVp`{^HCEyID zX25)dPGHaXhRdTFu4kbvZ;luv)SRW8{SOAOBjIwE6>Hi`N`vZ-dJZ_w&t+Gxhd>GU zlm64v2K&tl^*7mf5|n$mIH#B2p$~eFcNiM|YiMz8Y4Q1aV*iwkv&3=ieMPo! zBs}}OGe8fi#M_j9aEjD)=<#nnfbqd(8HXEL>?MuNQP+CU{ok?7jRyo9qn7&%?VV|#j&Swr zhfl;pB+=}27$XV9bgJa6$D1R+beyYYk-!~{#kOQ=Ss;pFEkaBOc$8$LXCCEyd6%qx z3gA%=jw^X^p!hTkY4KqV)c=Kqe67@=-Y2F@@}Set3b|}aBaKlWgLedB& zwxpv{nT96!8;MrmIBcZZ^D%5E_;zV@ZY^Ru+l>m>F0bUl$d`)|r7eDks0tsOxXE_o z74Ao7!&EKPLhUCidePhhnUh{KmIb1!$e5%1mQWW8NpI=L{37Pkv}6|KX_>{kKBD+G z@y)wD;D9D#7Idy#&4i^9+ix?9;#K?r3p|>QZVN(DCf7>(tFo7J%HKCb9^a=%-yOWE zJ)Z6{7A8TQ^|k(s9&q$K@QM@xPnN!+%P9$Om{f7!IQRJQuH?I6oj}J5RC9d`n=-LG zezO(5W=BfwpFrj{@3|tyEQRHl)r#}9l*Q~}A78s;CukG6O?rB1QNFC51lt?toVJWT zjL!n;O4+`x&7+tJQw{h|MjF0C^v2j<&mcdIoaKJ8t3d#PId=#@$Qr1Jh5Oc3vQzKx zQFEmawGU=~3Xuu1YTWb0RD&;zb#+>%bTSiWr}KYf%QOSPWEK&kYIf?-e~bF|k5=rM zUCP(b#fI+}Y$bcOolQGrU2{mSp-mbEIG){KA&jk#yV^w)5zBjo!lwG0Abh{bV+}@q zLAM=(z*y){%YSS;an;*6um0;b7Khqe`PgoUO4!lcOEZ^iJ6J|Qxexeeg_2-f1Lg-{ zQPCjwNdFgm(yX0?b(0vV9RAoV<~jTcn^OAYbM5^xaQbP#w%BOFdP1M_E3SI;mw>Cn z$K~610SIlucQqbYW^Oy)#}A?%hPwDi6b!n}U=rYbt(X4J>LZ?HCGI~N!DQmGz$Voe zcP?Q8E9d3iJ#6}irSn{$_!)v$;pT%CHSff`ME~d{l{CI0b;1?RJ-S_=s7*u7jvw8T z_HblHBi>uY87>N!*bPKV>ZXh{JS+awN)Wp9@ws09qI;XB%>nA=HN$v+B!HK(ZmoGv zhn=zgXtuh<*4O1o%&}5*&E(2UdPYgVqiJ$*UXw9qY(ndf1E@K|g^3Oapl08{rmJ0# zrE4nE+iv9kZ5`Q*%+Rjrf?)(yVH~}(9R%b-yP6%Yq&i^Yj&KP-WIT|^WV6(~C0sWI z9T-<+FXe|OQ7afDQ1R@0aPc-zlrv<1w3!i-2%o7sq^-jE!K^{cHgE8Fp?4moinc*}A3bFAy1FBfOT-~q0EvYTCT7PoNXkZIYr z0vXb?32(kbX7e@7CI2--uG!tX7H0_LwZ{=1DZAKyCR9ZL3%(IyuKucMm>YmBKc-hg@3U@8%vaCNbAPi#-XgD z2^3;|42QJbFui14|7f~Y{FFP&Syq&efwqZQvr%muefq?*Y2RD|qM~zi=WG`CI%S18 z)nfBB`mn4NC{%#T+Dl%pik2!Mb>+oXhsW7&W^vcVY=UGlUL*M>WDi3b!n#<#MnbOO zCO4NyD;GDfR6yy{7K_xw@#yN!S~E6hh|q&44>3fEPyLX6EMff}Q$E*C`)RY6rth+_ z17_l-2?c?}x%hx!%PS{HI-Z>}j{?&1?D^w6Tb$M-CZ~^5*=lA}Jtg*MMT5$WXQbgO ziXHG|L$r=bd%UXn)vLI^CBE-G(3G=_{LvOZ3{I73GO1*fUuJjeAcE$lD z60Y?JxDk-grG-kD6KWIS550wQDzEd6gZ|+lz^$*Ll-bi#R!+Ah<*N0rY_2Bs6BWP9 z1Trq%+t3;wq{T`ijt%;0M+|4?(i_pZ>}Mu}w<)o|n@3GtG)d=0B)(fRC`rL9;ZLqK zTc&Q2WDbfXfu2hQ_+izC^_B$`zXpQs;)netBSu%0vNaBf zI0=paw6-0Hle$Nr#Yxw={6D)@PI37WTA0|}_|8bP|NMx0lr?A6YKb-kVvpZjOq-h; zTf~_B(E*ab-T%5y73pE~0}jWP8*n)E&JYgA-4Tu-a5#?g8`gbHQ2f!^W2;2(vzB1l z+S>Kg_B6c8$n8So6%tt@_&19EwG*`C*G5@&gwjji&0o;l+flbG3|@(kUY*kCp{;JH zC^jKD1(G?RMPehhB^~izWbh+NO?8u(wKJVXS~8DPxf+vMATCnhql{k_>(wR|olM9G9Mlj|ZkdduRV!PB-tTyAf5z)-fDpNG-% zv`YSgevh>42Y#keYEQ=Gb_IKUK|4_QC{qga1DXZ$b^d6Eh|Bh;zoUv;l*=E`FfyZ` zF(p#NEfvi{M1U#r15Bx1ftjdpQV~Tl7}Mp93%IBDFx2Ro!UI?dpqE?q!4=70YjE>X z$cMUUq-DtR6V+rwX>s%ky@BDAk1H@*>sR+zYmKI?D%w)E{FjE4sSjjD+gchpxSsm! zgyjDkqE<@be6L_YFopXs0o;p59g=?A`PI6 zWc{&t@Ue*ZKT$hZ!+%^vZ+DsD=x~JoRn;rc5Y<1Oc8^_O8~3w5wExHu(ctapiPucy zptGoB`ZP-4YNc%Gr(%{RaWkLb1?{ED0CovNex=<(?V#a~A+{lPYQa4GF}?iX1=N-w@*LV;8)n-^=>) z(%Q&n5La$g{y(KkhV!a|l+rn?Wchd9psGT8I{FBq(ER_lm7ziFh>?QEV_Yy>>xl9u zbi(k#a^QsUo!Rxs!#iOg|6^kg=TC>r1&W<0Q!vyDBO3v0kNH+hE@~I(Mlc`yxOp=d zj+4q@^Hx4rXj=2pC1DmT-y1e1-pnRUDsGq8(L8ERc9)!5Yr)s zadUADk*Bt81jDZkH(Ba)GGFv_o*64@6oMCb+aF8JRO;Co709GP({PHZg!Gl$wR#&wW2-Y6 zkU0lf4)Qx~8f3Y>OK{1fdUcy-fu6uINr!uDUUfUbU(!5st;+HtkmkQ_XVKOz_q|~m zOKb$7LZ+L-&oxV$9^)si&1@1b)wf5B4riWaF?M$Dx@RI^EnH4l0abA*v)V|4Dz>=N zdCSg5?6T_foBDpCP~xAia1-zJ(`A@0Hqn}5poF8*GQspb7Y|A9pbbnfY^p3-(=KxVK9}&~ zO=tdJFaWQMg*az8!t7^p7@C`-u!P`zmv+Zr!9JItHYq5X+m+`HSXGU=+%2#d)Qi zYW(o?s1VG#pWef)U>e&u?!*Xd?xh4iueB&+s`PfPz3bF55w=Q0y}aHH$aiF#8R z1OQeUb%6lDe@xMFn0L?yS8h2FB{Z<5Z5!AmB(w;>;#<{jS!a~@e~h^L*7#art}AxC z2o`JMLv;Fd15MmELp&@N0I{;4K`g(5A%&uOOk!yR$I^urD27CHsam3DIME&|RpwZ< zvbK55X1WFau}N5b63v-{ONks@T0YCIxC7K;&U7S4lx@uzw3!dO8BA{+>YO%x!* zua9YMm$dDqh$IK|U^@U%`aaWEYRZfpEw<2Orp8c^PLoV*5mWC?5GdnAGDX~_N@2*Y zWA?Bs+Mo_4jAP^R_6Ei?+@&~c`8uj`PKE{c{~1NmE4m z&mf8~ULG#z=JtMLEdB0Zz)tLKd~A>j!B32<>o>(Q4g8L&-Yn8$tW#rsik}{iWCUp( zbFX&vc7E`h>ImgdbbsctYRqh{$3TU`z_>}V*^bGUQ%U&uio8s9tkwu=%XvuKeh}kP z{u|R2zS#gVJ)H%&X9_KrP~ISskY>_=!nW0SOQI&GDHa9kMVz5brT+BjE3*4jd2AWb zndj$o*;4XVlYvPg0+cPnJBhTApt3`CwyG9Fo5-w$%_N@0i04e8n$T-+ePl;UL0vo&9z}CEk^+pShjs*=*&F5j_9uTXV~8bO7Vu zDXqT04=C5{_agXT=qN-Cag@IzK9xJseix$smt-%4t`WheDjX(Ed7tDj0|ug+yJgM# zRP(@xt$^07dJ_qlNg?iEujKhC(-7~~Ik(mXbNxYUZRmd#MA||KY zU`Y%yxED88Gax6qB4x`jX6Et-tJm>zy;^&(Nv31H>$S7nYJs0Tb&3)a4zTrUICm^| zrn@tlQ8&b0vIRB0rd9Gd@D0d#nFtW>8aYK?K^H>S;g?ngu2R&T@&jv2F%CHyFe&?3 zjFJy0%C-F$%Ff@hh%3q?P>Et}e&`Mr*oajpNGgR-Qz$V(q%29hwi}pZz{rk z9j6tSU;9v^+UH0oKW(*FqvPE@3PEj3;SY=(6MbKcU-eoU{Dr|mg8Nu<|I75TrOxuB zDGuGY@c!SXI8kqu4cUgwhgDc+m^j;ciVMkL%yG)v!}%=FJR<2UCg(RbmZQ-zGi{!s zRKcPcm7*JTVxw^bui|_tzQlFm+TSg3qH|P^%(2I%Z8zw{2eq0O$k^&{fWYLv%8NbW zXtQDw(0*|O{@*AfA6xN1)luJfU)E=QaMj0`n_sJx9p}YyZ~*>pBwLfueNg2k)+c23 zl~bUrJGae9#u5I_fD<}O3{L`WVhy4HXUj$Rgh3cdh}lch-ezhz`i4)zGHq}v5XQ5= z0hI3zqG6#SxL?1A$8Vv5J{~D0IJs7T zvDqaVZdX5eS}*@Txcj`hOU*-975-7%o&F>4oLgN39X;nJ-6sj*V?&l4hgBI{O(O)S ze#=lzTIJno5BVQ7RXBjg`PBrW^qCrBl8aimFk6>0_9($GXDIQttiX#bv0iW~HbQiT7EWO(l%pp?{bMiT96GGLw3h7|vq* z$1UnmJ8Xz&Y`GiCIi9{KEVM1O0{Y;Cy;>4P^PU*4xuZzO#KlkGEKl{NfF-}Go^}?8 zb=_vrXn=7VfRT!m=8E^+DqIPnPvdu5{`bd~dulRu0JnT=cf(KY{lca!w_TIzHVa~K0oiv-+1s~FfN^0d}lD2ujXsNfB$Z4nB7H!DbG>P!5soQav5lN^EM|fl zl?CR#i|25TUOmBj%9QGCm-STuZh{pcAR>ikFQ>yMnWXEj4nN^e9BD*OUaUfKeW*WO z5Vgzjj#reXr{#Z*9aqUzm_|LD;=so|P0aBt8CvF8lx_;rV~iv>Rf(@O)NC258>SJ0 zfXt=<1f-jsXjkD(HZ5yKj@G{i&1@!+xf{M^hUQnekob6LzZX*a$*1>?yL$OUg@xBw zDRS}AUcG^rrVqD}8l_AVTNJPcWgnjfqiZ<%0UOSzf1}bO6)S4d6Y=jl-?Ri<`_lS0 z*txm=x;-`bEZ};)*(rA4+VPKi#kPH&@x;hZdi3V4I%&HW=|98kAMeImrMB%FUOpM0 zo^)Un@@?%RR>ST|;_p-u@L%Dr1TC%v;N@&sdVfrg-xkQd+?^ffI9_br*Bx#?T>S2_ z-=xdu6nw5bOwze9;X3;zij7%gV||*QU(MYf87J#Zx+Qk9M&fS7ouFlPzJL{2LvTwo z^;+S#*)@CNo%%Ix=_l*or;*pSZ={)nC3Z+BFMUiO42L2jt3(6ti-su4QlVBZdBPXol_q5?A3n@xE&F$m`K_uo!XUUQ?Vq*`g;8cQS4Dx z=)S*pI_rg~xT;;~Hqes)(Y2&Hg2M4<_B|qGC73u9N>wwucpQ7sn5-!!_DMJD+(-7a z@0ls2j6i?xwYt(zgIY+R*rIoGFJWXGA4sQ#tEAJ7I(|2iILbTG{A@U~bEOQxZn?qjO6vc{RUk2y; zh1;D;qvn3fd{129iW^uGqkt59h!*hQGqxG68=5v<2GIf<#H*q`pX@}I zxfepbcKToFzwS@JJl8rOGvCGi{hp{=D;a)wn$~YjuS!#Dx@Q(yHUz(($GPk%s~h=8 zzr$3~CG@_Kk`6OB`lx($k%%qM;a75!wX&~_G}tdT`a#Opn=C>ONr?5KxPGMZDVy|U zW4=Zp2B<8*g#J$m$Q=2uO_F&`Yt(&}iCgNX!S!k5Xvq+yPl)}pUda+KTwV4J0&nKu z!NrZ_6-=t!K_+F)2;cZbSkkPos8cI+J2gO<-K1h3JMdzX$etxGFf1!ML#^8d+&Y9i zyD;djxde&;=T|iIeH;MAZp+tDMSviIMe~d7@iV zl%K=wn* zqyzZkKL-fDh;5j+z1PH_j}7+Z?0;L;naR;kYdU#@(0H~r`gDd7j< zm|AL#N;0vjEix7Jbm|BYl{jhTkQxCs2)ZO*h-@(NyOw)cex{-^QzXoNI6Gr%tQC(Bb4=0u>M{+-;H5e$w;LY z!ZU}}%ttxZdE(zfzbfJV3qoJB&aN3AAbX@}D@XLlooN7mjPSm4n6h*ql2|RsyJ`%a3mkf-c?y z(LwJ1TueB_;UqJAPXRVdPmdGcUgQGu%gn7BkB2*o%av~wVdhMM#L^`rqG;xNl^#Lg z0q1B{EB8uH?*lfUL_m@yr{?^RLAR~GHr8IgUB7oK5LKb|;4D|Grg==-SOpJ!0jG#~ zS%`f2h$+Yy0@a<%CkdIH!rsg>Qdaj5QAqJb3a;kYma-3K$i@dyTD;5S)E@pR zWr3CN?3(YPUg|pQ)v9WdA`em5SU}V@Ek{goGE;<8N-7p9v+xVav~h_BB&snLlZ1TS z+wWzdcx0KK@&Ke8m-*puA!qz7JXW5zX~3}jS2VSoyhZI|PE9))w5q6n=22MN584aw zN6*+8oy2)wzh02vS0wTFl5u5&lcWE+p2{~#S&Vj0Du1SI@02>4ko2pxn2@wEg%6xp z*UC44{={0OOu{dKv4OGlT4= zgt;S>a9`X9SaKj)y9+c}w(u=%d0Dizi?RQUqun9`Aq}vc3_OaSkhTB~Zp6ZJM%O75 z1xRtd4(T|wJc_e0hYCp(^7Yp6Y}WkKh%#+%|HDa| zJ&4Vv_&F09EFZEdDnbA@m(PB*qQK^|I_{J-4oxdMSyv=ykbdzpFpW;X3Wrgc)rTEv zvmQzTqt{0NlZ{dNNY$9+ornn~Yu^b`@gi^n{JCYAL z)oq9KvL<*M)*zY>UG7T45bUX*BHxTZ?U9j-+d%4hkf`W5mOdHyEs=LjT?$oHL>Aly#v!K^=7QKpJN-m@J8yXr@HDGAk->Pjs*3*or=D)io^HaI z?*jY-zUzZhd&RTLgo%Ce?{Th4|D;WD{rliy>3{n+NEh52Za=TAMjof2OwV>dix*>x zGOaa5%ne1f`!oa}h}&Gtq@!OyO#lSM<1F017_+}vHCK^ph?eksip~l4xlERddxbuu zes6m{HJsE5$DpkFgch6>MBnJA`)isukWhtXF2q(5cr`1OS_a3Mw-blkQv0E76=50Q z?Uu;UA3iS$neIf&O^RZU=U_sNmiQp%dI`60UBLuy!)}3{{sDDGtg)Wth>D^mGCNU{ zPOJn%zFoq+Oi!kIq@q_6=!dR5=ACtWLD4@&-{)YAe z>R8KXb*Bm&Mf^y%cNnu>5p^Q*Z^s>8OSM{mU=H%*fz@zEWwOGCf-VS+^u@u=H{O6H zfzpWIhT984kS=VK-h&mcjroA5cn;msEO$;#KZ`~+%1;%aA-rP3CS}|>E#HG`siPE^v7_yFX~fz2#p8bpd{%OsptrlB9adRi0x#q) z+SY9$mlz^BAI6_{VqHh!|aP5;(hs;5Q76eZn3XQ$if^9 z+zKXf5#O4Fj&U*1X(OEWT9kHU9^#VM_~ zpJAyRpRGgy{AD@(mUpXZ=V~zzLHwY*8X$IOPhc>i=YnZQM27=3rs6EXL0l34qGT44 zzR~Q9S_qH#M{xFuClyCzDo>Y&{`^yqKur+0RkZ9e<=JL!EM6hzDWut2#|GrDt*~da~ zbIeG1*X`=SJR?}H7i&|((yTBF{9)ye7_J3S-z)6v4*oP)9@D-TUjhSAc0F%1$f08M!t2uQU z!I?qnKWxrhCdA*VF}fmkxpdR$SP^{HGZ~oj6ohk*_dm5vin`TbuQeX+c-J-uTueRA zt>81DT9Y@9c;5E!GVvK~Jw}ujwcKBdU-hK^co~$I?M$Moki}qjythSl&8{j-?JKQ7 zo#$5XOZ%nf(5GcIEn7V2{XO*{|IOT{c)Yn+>K}NTZwUIcJtOSXaWnfdy}7;Ce8;f*(BNs$(1EYR z`R&%B5Bby3zgI5)y^BseEY;ATe^YR*Ty@$!o&8J06Xth$fi~hCur?QL_=)a>5R zKNL%ME01&gf0l2CQL?=S=P;m-`f|79IYiIzbUK60JnN{zEOHJ}?wfZ`hRq#Kb^(7@ zGUoU@YFSq294};j8tQzr`Jb3F7kcU(hg3CHo&4$4O5Tk9_)O~YBuq|@wcXJIGf}SCcS^$|I>_CU?p}xRdVR! zT>C0uKUKG>{nomXB(8Y$2!rpC*!(o@YFmPaE3@dfYdD?%(0^+@exbu}cvXuI8%91n zzWHde)$d`fdD++MMxm<3I+o&tvq+s_iW6aAi_=^hS<^AXE1SVpn?KEL&CSFQb!la5 zl)U$wZmV+Wh*Cza=WCrEO@85r^f)rYwD6tRH@{v6D%qp^oWJHU>TvqWclUW4+4=2v zi>anL>jOjsr|)buE+Jo*Y$pM#99id0#LnLbgOM+#b}W@sAtSmO~jpwC{N|BHkaNck5FM`kAmr&tj##UPn93tOdI7+QOG>Z`~mb5nHR zCUa@8=!vqfCi5n1(cY4Ym_z%2iki20W_B z98Q~hnosVu44%}maBU-4l{pu24#)eJAt`c?w1 zui0oRyT$?~bWhfa9+f^0sLXkR$WDD|D4!C@nr2olfc6WvKkL62&g39lUy?PESZBiS zu%|DUV?IjaSMqYUh{qE=eI0#S{zlP>dp;c++i5c-`>j;4-v!yPFBx<0euTA3e55My zKe9giAGhY19g>KJfd8>lA6SD`G^H5Qv<=kv!u9G|3zBDKJXswK=9Ou=s);xXlHsCv z^=5ell7?AFCE}Jr`HW+xqG*g@-xSZA-*!hnTQu!8(Jy66!&TKK6b}I3RDB#?NVaRG zVph`^$Vug>Pv$JY(THyAl{@qk)8XTnDx5hwK`kWBaWWznS_kv%t6FR6N)qM2%2FI} zAu!Ku(WV!Gd1fI_rW-FceeNt?!6&s>wSplkRR?~*l=E=1Q{*v)J}S)9%7o>RzR72b zJ3Ks%fyKIx(A-Muw*GXI6R8p}tqvZS=lOBz=9wLnuqD9b)_}(iQ_-Ym#Md!UKMFUk zXT?gHp$jX@9l``X!z|6Y-_S6kOd{6R*i{Jp6&clX#3IAP%7k^)T$ccb!WyJOWM9){ zYz#>f6Nc={#4oJjLwVK6HRl<;Zo*UDk9yOaXy<*~q%0M9wgamed|wrm5Ol;M)jdT1 z=Cz>zVuhI+H1I4+D2-nF6O{#ymfo7XdSVbbS|77gyfOD+BmcM1_OgW~zCvH^6tw6@ zrTMWVlj!L}BG4^Bm*W?-458^7%m`k&x?qWm6p$(NNA;j5M?{o%$!`mR1WoW+f~Hul zGfBG@4P8uL(v!eZEIw#nOW?3)%6XS%7H{^9NQOca4=W{B(P36PCq zOvs$0N^vEIsUZ&wWO(6O8lvVtn5Lyj7{PANB0ED9v~mTN7k~r;DT$zyeSI zp9R+7Cgwf=QYtvyVl0t9`JLaQRDnq_zc)h~h^ht$SLxEsQ5Rt6AmCadec5IN0M|-0 z8GEyM?EO=60NzQz_MEtOyw*t8c9#Tw2uJI*Fzu5RRcrGm!!#)(vaIO7R90D%Nxn?I%bpLY&EflRSF5(+Ixr=zLASDR@`n z>fFj(gU`4`Y*uAHkD8Wr825C-8pS{=1PYo3`q^6g?T{$m!Zz9s!J+@}NiG@g!tVHQ zQ(NZ#|28#IFmiT%bQ3a^BtVJ@!5-7z6`(gu#{J^%?i*c#4L|E_0-m{-o=No`pT&Aj z=4NJz*%XZm9tdG*&mjz{2|2rn2M>fWMCGT`3`(|wl+A)G4r+FOq$iGuVSPU+UHMojU}~a;#Cxrc(u<6C1~@$}E7fbF zm;b;(^o6CgI(a1r5P>1ze*(kM{|OBFK0D8WSr!QHT9SMt+*qM1fr~D*{hV;w5aueO zf`<;TK*OXJK0*aBSLH@r^PAgP@;%mow{nE=R=v->RoK@qdca#D+s#)Bz;)3BbercP zp$4G%X7#Lm#rx)3_~*fu#u09&IvzRvUm8j{p(=`Rk?or> z_+Ip(QaEa!X@Tu+X|Y48EP~YmSDpW}MgcgJu51yrmEDl8_V-++;6oBJM}bdx{$fBa zAKllKyIjdKD@4X;uE645gv!CP*!+k0UcDl4o{>3dlM$aro|NLAnOR2&X$6{R)!cga zZVOcG`e~KYTcAaLlKL6-AZt#T45D;^AXB^h6&$nS{4;<|Uv>!A8)+=xL!rJz?hgyV zJ@KD)m1hpAyb4Yn0Qa0_6^5$?pTkpPW@EgRs2m)w)Svl6SX(lrl9W7wdk$&oF#Cgt z&S-X8(Jo)}tHvzw_($++#Ko+1kAiS(Jkv9r1(~3E%3G6YM?+ACMB9@kCKX!=%qi$s z12rWBY)v(=ziWx?<@vj)*i5s|KyymyfKnf@nnQB zL+T#S%#f(eC^*++zbR)e3$=CyBeQmER^(AFuz4X$Kr+w^ujVW()xmW7$1xJ!P_Zz{ z4?V>~>sHX3^?5$S^Gf@FEuep7N<|bc1)*C~<-Ua5_9|Zkr*5~vTK}yQ%$Z@#;PlHB zzfawzL>!&Py_?OJS#EUi66|6IU4R`8h>8gbAfjUEzMG&@g=n|@xN46$TiZ`tdE$RT z<@uke81xxcSsHL>B5H~xXN9M$u~RYlP;tf?k(B{MJ`I}C^>Iu4pb#M_1Ki2)o|UBc239JXd=MomFHn+JDh$E*B>^RA z5T-+#RX7hV$&ZLMA(+@b4uHK`j*9=z<4J8@V#{LscsOUI_!}a11UxzX-vRUc|CFQq zrWQwOR@=ER%tl}Sri{=3YZ@O}nYk++V)*nLB63t0@_x=n{vW2^GODho2^J=32pS;4 zgS)#14{pKT-QC@TyL<59?(XjH1b27%_Q~_!``v#zC#-eWo;}mkU0qfEy_z538qC9= zqgdd%# zXz#eWod;}2EP_BV!%%9TMRZ&@6{<6ezEp(;^1nyjYAwi?Xad=jfViFiPYzNIx|B-~ zV-P@0A|t$176=1>2US*4!D@c7a-qz8_;T00KO}qpDonDhe;tBe8*o~Gg#(lcPONDACH2zHDvf%#~;SPD(h!cuZ9A1(V)vI}mq3q)ARfpd%U z@7zwCe_;`u!w1go>`f6gKsn@(LyAcKP{Q9_ik0_aE?Z%VLTZTFChmVOaai%Bu2yn? z>b<*V?I{#N4_bs-ftBYg_Y$A^`k%Zb__IRXNYJl*xkZK>`Z>^x1-#EAQ=pMKNL9GAnY249N!`XwZ=IO^Hi~G3y;EBjB``!cSEh-uO@+7`>j9BE{NAWg*CP zG1B$R!l>^0_Sw*xGTt2z=U>M2eCM#5d#)_)GDhqDS!2a*+*h4nHVffcw5OEqk|&HY zgYxjs6l;VxX+qny#p-`$19qD%Y9%v_L#zNEJBG8^8euJFT%YC)A{W2FPPs?}@ZyL+ z0#rhPw$dKjl4tc9xL?oQ9KrxBJR24{F#l`3C&4Ps@mEvPh7gjJ;l^>a@RrNwADprc zi!^<7?DTK>sToVjWP!XIp-d}qewwBVi2r&#YOc|)TtP`= zOT67@AZcvTr$h(NV}_ci8>eZj|AkT$xw%Oh5PRao{;LZ#{^nhjY_t2NYHbk$+KpU| zZu^U3W1~4D4KzTz0rOA0u~a9X3VKvkB-8&rYEy8g3c@7KwBZ_|<^oN%^<06sDrHQp z1n5!u8pwxW^1qL*xSRQ;>gWAZJz8%w#&ZeoILcaV`@du~KCvMX8SMj!>@6=_h4eDK zIeE~(xP&c19DP8VQQi#(A` zU@dy$7`_w2L;0|`e#N7l-sEO(2BcrCOHIzF3ipd5A7uc+OxLv})k!TEjZ6U2znfR) ze{>Ou|3eqi*yBC!a9@?hVV6=nd3W1-!|X{}Lsbs4F>Jq$Iw)y$E3UB?m9|JOMoFM~ z^N6`=zL)u;%nOijlHU)!J<8uNVc@Pez94N-A-Vc`LbJzun$jIxCxK-pH(s#9dJad~QVg^1Ax$?FJ$@KEA!bn616vYs_=lj=dx{ z9d>6_g!Z_H{^3=*mriH(Mu$0flF2**>>2EJocH?!*Mmjiq3$O<6i#02*%>=b++!FV z<$^{<28--t#9yJkVxmnu%0koRG{GIUxh0l=#1{YOG=c=)ak31$iI_LeeDJJB{-eWh|Dh z`F}xf*HkY?m504za5GhyiJJwc;{vkusSkR*5IhnH1@EGeIna!y9^&eyr`aL&`cRkGgX<5J&t zmNVXt(>*6S>|33Wt9xAf05c&%keN{7yF#k1X2u(;q}f=KQ?V*>jpS0kQ6?Ko;8(I- zNUzB$3F-G3a9-(kTGV*y%17RcP9)%h`%a6gvz&s3MgJX~7m!WLTmO?yD*o{i`}6|H zCMCh#AiY4HG_hdFQ83@ER_cp5L2%x^tQ1*_+RQdkZccyA=ZlwMmT2s^GW!VvP{E-1 zs4{6mO9E}B&e3DVYkOKthGz|Ne7XM#vyW3H+3ZZAKM5oQl46Omc+kyGt(SUNSpR?) zp1c*r_bVHS|HmP!j~INCvmdY+cAAOD41y#P^z?s_QdC!=yi;;N6gh7?-4A|9rq>r( z18Rk?1wYk=T`b*gK@jL5kgU%F_?uaovMQ0fmdKs=4_i=vmiN=erdl%D-{kc*9ouy* zz|Gr&)KQE3LR{In*5=aA{-kUBn6~`{yJP&m`UaeUr$TF>$GzU-jR2!rS2rX@#7jSM z5cnLNDS_~J{xMO?1e-)(VD4kX`?~YqslnE`^BMSk6E-PNPvALOT@)iWchI9nEHmm! zEAkt{il~P^c}XAOv^#o!f*era1L|dB(EB$gHd4hWBw4`a50Yp))J$XenEwXGIKL=y z;d{ukRz%YPb%L{Lby*`+G!2Lav!JUn>xV)kRBy}Z6MK%j$7M;age3zniU zkA6(@^e;DfeRBTz(6KxSDHojL=K6<}PkfpnbRN@yGThnqx63B)Xri4`!z%IpnHY;C zkao-$VnSM-Ba{ehu#mSfc@T{Z0^o?2Bqax{i6L0(m0_^-#)#`2MdVsCAXHDvdvWm9 zqC%06f`d(dyOByY@*P>~2@0EKQ0NSn5}mDeTpQlcZ#*SQ6N+M^UU5St?Hg-G9rH<; ze$1iO;p38)-!D3rKB1M?IMr-3*l>m6R``w^jd)OaBhi!ecgw5)$ml&DiS?@KCCO%- zoxbq#vghp*1~6N>x$~&G*tzeqXU)K~qD@PwQpXyjdOY~WlDj9Pk_eokZ<};3d!q0r z=gXGYv&Wik+(N4aU$OoQi-t@$@e3M>iAw8A41%C@>eU&PL3r(5ESsRHMzR~(V9|py z8g(trhTX9R1f4YXzw{|xsguTC$!#34sD*)D{HM)A3LwGS(ZIjzs6*}-*sqh-1Tc~ zE!LV#+@eEk+;5NjE^Smiy~0Hynq5=J{J(&Dn$?5E#B4@F50Y@rYY%S-GfV%Nx5Rb# z&C9_=_e;vI*j421N2{Bau(EF|Ra`-*ij24zL-0_h*CM)>Dnu35Zu&_B`p@R|Ga?Za zF5~fDBq`$c7R%fuP3z404F@#xW+Pd>R246#BO~hd7T+LywU|y;lsD9#=L;5`ELU#P z2mYR5??E*!=M=;=MGWAHUsmoM+FB#=;;>w~9xR}-rF7OYH130tdCm=wheD~9ja5DG zyLqY#3)kzhqmQiX7Ea!krVGe#4Yh1Y;4#Zk$G$Lh*2}lUS$KU`*NI()a%qq}IGF0r zXREDV0F>Rz_|i*XFWI|I_M0mzT>KgnPpcjT%{#3|(PF(=JlF5uY5mro@6O}S56a>` zYm0&LZ{MFodOxij!?w^)E3XvvxNy%Gbo5W^+OuJkzU`h{ak8j+H!a`X+vj4nbhqsH zB2r2t>l?(j;pTbTi}#xFaNiTw#xopbSxBgu=t%qU_z#-*w+vO}@n8-wMfbN3#BI;V zTgH2JzIR^txGfuKlf(KE)!#EZ*vLhjE;3Px4V?sN8AIZ|9(cz@->-rGg2&59m`v6s zG}5~<{I_55-!lIS40|ti^*{jMsKRIRBC1v1UQWctr0DKf_j28|A~C`(9mp!Ah#DXw z{9xh}ii{BI8{5bs`k7BScc4F7er4oK6q8cRd>KRTC#=V_E*jq{8o!JgswOCC8&4UN z(|8=wP~V8xbpLSJ;zS#>UP?wY}6`--&b@mWm@K{>zdg- zLU_W(MyvTPgO}&}C2zPU53{ZQU@9^)(}p-nN6qrwfw;C&PE(b*?)TJ?_{1<_)xnfo zOzR%m~p%6;@DUO=ubd0&bJs`Fq>Id1S)jUTrG&XSzDo+BO0agR?n06+NS^dej|-55??4%q<13ID)Hh_*Io zw`c3SyG%?ad0VIarJb+o2)uTY-aK#@E@T=x|rasri9EcrVPy;qDdyMEp$XmO^EEmHdH_KOQP0a6c38vCi zY*5>^fTd+|8OVP`Tuo8Bhc|y3&eNqH-~i8nxTzDC)UOTMc^_=mR9!eK;OJ_Eunib1 zLP9XKw7wj|ZP$Wr8M?bD*~~an%KV`Gyk$5$ovhwTUTj#D!(#ve3IBb-5Jbtg&zMQ+ zHuFtgpXe&P22rxdN2URzOKw1=$%eYFL-?aaC^$_Qqj*YSBq1s%Lwg67M#NFnh)sdW zk9bj(AaVq7a=}T8_*G}y>w#X?rd>PD$P)_^@u`3kv7+OPHtjo&jOgi_dvk{;+AXmH ziM}`q_l|0K$`TEa%OCH@)-57DHSTvv>1wObP-oAK{g+0m0 zFyAL5PY&T*w%Z&C1)D*lfC^{*YutE#TzIyNHD;^?^H<{bRe3K$)r=##7kjg=>JL0; z(d-+!L(P4W`mM;bys`SqS#6(#5G4R;`u%hgf5tJZKi@@?MH|bHUT&3?Flf)5<`Kcx=3>h@ z2y!*+*)0eBOw_oCrT+nVN?2mp0%Ad#@35@I(ItloIuI4!-%jmkKTI5JDz{s4(}-4YjWw`@;2Qy)liZ>aJC6eoZ6P)cGUyv zMES?CaAJ<}I0c7;gF&3}xEO~6*`K6`P`Dy`U@E3P!b`i?G6BV>ESR&kPM`LC5G2hlVuIKsG$zdgs?jxluAgL*|DLxZ7s>b z49L>8JMh~b{VFfVxdZE$B|#Ca#iZ3W;lgp#TF!X1Mk_Fs^E<*>MTA*#dN3e@#4pR& z?%*G3r5b>9i>rzGh1|OlZ09!b!hC21;`I?N(D|DKmTCQ(_Eu<7=G6CMK9&FZzr#UJ zf;6shR@+zrAt0H&I3+H?;F|#$d?23^0{Pax&-!+qdRC5i!TVg^yc#%>h2L(~-dD%A zt+QHgx$iTy8l7|O@77TjBMboZk{Ypf1y!aYY}H`~uzaEEsSlFi% zVlaF-IXCbDi_}BFoF^>1oQ?SS5GSMb^zG7 zm%@sAs5vrcrtx)y$NZZbuxt56DRi=A*RGf(_`ncwNo|?vGdJNJB^n_urg4nNPM?;& zFN2*<57%{{>=%H#~RvBwJwzkW++!^HV89HO$4 zS_baH51RM@L6RRF$?5xH7ZCsdE`K(~j&72c<;M%#+~oPYeOK7`k~zim7vPTXTRDv5 zooaH=wY_w{*cP4WvrlsW4pQSi3o>9LhmZUa$aKE0HpTlUc)w+8BP_p%z!D9RCTeuw zZ_`CbD|k?FoLR}SW!$y7PcoZ2Z1zoTcZBax+PB9~D-nh_5e=`Ex209qc{S75H#j^O z*K$1UwfDPY#uKnVR{s>(Fj0=<>vz@bMLG`4|7?tm*1TuoNZ*QSj6CIdoXgLLg6y_h;Mu>HD( z+5ovc5Z2S)%2>dz9G+9hJ}5aKL*w^NYYsqGMVK7AQ7p*2X`N!_bY-nd^I=LL)p0!m zWb(rD{{ObST#FRNh@FEj=PF`7z0`=SzNW9DM>>H=?lAtFy9J$TKcD4D-6c;2_RRjR z?WMJesd+ykq=y|q!LuOChpjlAQ3-G`3nqn?hdGcy!f*0EIOrV6mTLLroc zI#kk|u1@UXXZ2nhPFK9@+*a;Ry&vZa7FgfS`o?jA)clmrkyCc z%QAn>de^<%f^^r~jk{SXKtkyB_W=-nDEl!~PL&kX1>+gZVD*ueQ1%zB+?i_A;3!?X1 z$lgi!*1-(X0z6ovacXrq+3rTC&{uTt9Q(7C@dos=oVMVvO;cA}yiVt?a@8FE9X`M9 z_vVPObJZfKxL~BQLuvOmju|mc;Krk$3$gk);g9>9y$a zn{`c6ByC_HN+hwWTi|MO8eqn#;1jb6x}8zR$Bun+=Jl8(_5{hs~A^K z9&N)`NU5729xYoFa2g~fo^7$88&0S*f2E{5)=t8qOO4%$l_+p2_#L7k6#{$)CmvwA zuNJ5dXP{8^J=52a8oCU!4RhZi83-#VFebR_jqjZDK(ePJ;fQov4?@jMTb50U_Y;h7 zqUfBAcK;qk>i>OumC7*8_$d%!WdOrMl>aL$7a*!zAmWVV+*DPM#EPXJ;(xrOIcSUh z8g2{a@!2@U9KaZl)47(GJRx*fLsS}EVvV2_ZY=%)1MO`orfdO5o~_8?3HQ2{BPJVO zCM-XOGRcHezV~QI*m>-z%O}O)Mkkf$`6lO@=Gw)LmWvEVsA4n(W3&Z>y~u3@(aCEV^?<=HCb$ngsncs^6ZO zm{a>pMY_>6riss+jCzq1v|y+Wk2qV9@SVRMoxWK&G=q&U>3qG(vX5npp_qsx{op)- z$F%A0roG<-kwFL8a&v6F-Whmr@Z0nl7dV}%{QjUq78$|q?wD$R=HW^cw@F=rGeccr zP+~JtLR5WV)c-Vkdp1SKc~AG+0Wf4U9i%tP5VecXGTd^=f+aIZs~qzg1I>O3bg@f% zJl22KCIt_M4aNuE@(UP_1V*R9*aB!v?hiS!HBkPF`p8GP0@ z`6*s}o)~e(ip4g$`G7eS>X(@IVs2PZX=IO&N;CaA7uF5_p4K@tZ0{C*orBaSx6BOl zPuS3&}ItAx*m|hMF0Ql(#F3{6R}HDmi)K4 zK|Q}6fW?K`vB?m8GPZK!`M&Y`@VccB#qB@aRQbn#Z{8k5e@{Ig-fYwB-d+|)vrIqb z*?rriVaL=> zLmT`JX!AamD>@co+F!&+{|wZ}#!w)cUXp7AZwPP^gdM*RHF_vm8xtT^rCWUd-chfm89#Bza~NG2?8L^(c0l*VU3 ztboWRu{VmdCuY#aWP;)#4`39U@W8g$iDCvL$xTR$^Id$gsw99uwO%@bY^e(CdTVS7 z`{3-e+K0?RoFdL>fr8$yER+hT<)HG8^{sCvLsJ>q3}{wSN#ui(?KU*?uoZu4 zOWFrm*=D%;HP;GU>Tr79YF)Fw?~gSQ3aA#1ZPH(^_HTRSQRlXZGAgVU?$7DyG>Q;u zZV@-ivhdD5+ZyZ*I=l`{`cJ++^UXxHa@KFO`Y?nV~?0aK+&2~Jsdo?3$9FhgSPi;7Bth`Gk z9+FX7f0rorE-UwJZgnwm(JQ^5dw$A&YAET$_EuFsxG#~cuimbBM<5_~kZt(f#R>`K zX+08KtGs3OvB1%VroTMoPHJEu;$z=kyjj0oU+32+V=B{f{ZCMB0yOb_rD z|2{PYAv5j*kshG!faMvQi`fwHZ3~&~7bh9ZZ8dydfdFx+FInaZsc$-3c2XZoxi+b* z(t-Ityd+Hpa-#4au>b?yNl8?kPxU>IG$C5e1tME)wE>N(k!+RY1y~<-2k5;8BuAI@ zeR>FaRtU$1I^|wH>BZ_KR%3*7?(`S?NfbMRMm&la=Cd%7xaL<9?RAzp1e5aAYENR#x^A0AR`{E z7*k8s9#WHkKVhCr3S5jr(k$qhOkOXPx+J~FxW`sPUzL_5Stxd^ssKJySig^7$q zKq_Dse*ICn7(=o4s;dFapnxw2b9mICkNbiufS znE;uLFt!#6=Mw$>&mp0h*?v_5opm0$|3#6;hXzOqNEcC4;Pyk!{kVm>3Jaer47&Z! z@CsWSP^PsiaL^P#Q;zsZxnu20_T^}{G`qJSeE0Z>)3rA=o?)Kw7<{7nip=i!ot6UA zGv7CO{;qZHo=f}tk%#Nt_mt}Wu<>2%=KYBkiA0WxU`HBrGsa)TF7>Ho3X7awLDRn$ z{YVIi{5J~!`jLK*o^* zbyKmiF2ojjwx#A{1cBptYT1loSq1i}M8B zV%734F3nfvT*L+o5W?_(&*`m_ejp)$1>hmP4Ao4%%F4W(1o|L&`LIij)bLBue44H= z+`&v=ECy$P2DB@rW#~ZQ{0fKCLPfd%RSt{EA(=AD^f98L_nDh1ccRAja-jmkwRv)Zy{y^DEGsxdhoweqXYV;$Fy}>2u@k)(|$fty0B^S zNr|>FA!mI4AYdjSw`L6tgU+W+8z=Yt!rhqpi}z0&SO)NSQ(_$fjL<^HrWxa@Pn33* zzmrHf9Aa*zdQ1eZqUfvrz2ra`s0&w=P&wzGhCuM#mSiLMfmHX0i&P(@gzE~IW(8F z6X6F}777ol=Gkb$WpS~7RJ~*0Mc93FmdGK9B<>YLyH8D~j z;}w8o@wgY5i&>U00R8a?lek15IuH5JB@SpJuMam4k$=maepW*W`)9V)5?ryWX3%Ese4y<9BJ?H0YF-(t0vG~;$3S{?Kkr% z8Hr2D?V!`6Q%E(Kf|spu4{%#U=+$OThPXe}mHwai`Lidwp51=}&e~DaiV=&T*zehg zVVT?$cw~<7zD7yKd4tlUv_hlb%U#zaaQ8tgb8a%chgoh&3c=6iUUewNe#Ft}=W{zHVkHYlL3ye6QtpRa&j_KvD*+$=bW`(a{<_O&L^7{bR>CyX>)4<64 za4aqz!RTC5^-6pqk4K)fb5--BmZRxUZ6k8ig%wXj*F%6)%ywDIse`LJhS*9su7qxd zm{h1dA92yZzaj5`V3N6 z#9`v+@40`(>&pmzlsMm*LSjZ*Td zDh8OL zh#9lnsGJ(;WW3B8=$}MP$r!>H@a=|j@-l%Cbhk{sbzI7h&EJ>ZT@bozE}|ufR5gA6 zjH+v9rFjU46mClEc(95a?_WAyur|3Va=msQP-{+2gl`3vo_ENXzx5<%*rk(+r3xSy zWu=2bA{vi17g{-u^A=fYc=Mb>pQOk`48`WyRjH5A;!Rj_q~vqhlg*w{-`s6jeYGtg z|8{`;*uPFWAZavs(q>1<#Ci0mNtu?(eK#mL;K4_slEyy34AyHRR(+3r4u#E9{bS&K zg}nhASa%+?#D!IO7Ci}B7{`jH??)z9Zi79V6@YHTF}EinUImF%W;}vZrUE)4QY_>d z@v@oz57kB2B-tw6F6BREJ{FbVxfP1xYX?yz9(1u`->Srl)wOsXz}NR+pO+t=_); zo4w9n)I3?&>xGWN=wRM2@coL0O=ozPpUPz3n-7l;6b z%Mq?5N~vG$EfQ^958K8wN9K0ddX`uXLTxHh(mp1B#UX#bdy5-yOKV$;`t#fSSc6@| zvr@tHI{SH(FAkxJRFH*O&>8&VkA22-(x}I72E^0vGhcPApbY!^F&33XLG#M+W9(U| zuKfF?j<%m`ZhvL<9bb(0M+P!PBtC|I?#6lmiyf(quMA4tqMrAmWCHLp)9XT0i}NH4}4Wl~Upn_D9ta1hzcp$YKHVkBF>X4A$Eiti(b?Ixpk7 z63_z?O0;$+pG_rNN0^A3xv^P3RrkqtL6zd~KdkaGztCn8rSla7p1a8Op5KYFl*M zczAROhY%6E^NI?yRZZ8CYZYhNd-2mJNS%5Oa_=G-(oPF>Pmd>jUI?%or!4O$1xA@F zDT8r`0Zh(`VbC>QP`w%c5I;=8oW8VX(T!<4W%^<&HP;PA(`=)J?xy22>F-;kP_RhL zVYG07;8#PR0|Q}9{TXvY{ud7251PJC%=~?@+i@7 zWY9AcUk&k&96pobR*;8@@E;Bx2!WiX5Hmzu{rpC+27yIttubBVEaTrnT|Gv%sSsXq z6Q8AZELd|>A(qbu#*8HQ!Xe^J<`QH$(JP$CGfTeS0mJ~!4Bo}wn!G+3m%GhgwW+3u z+98t>iG@Ef+SGI>$J`#W8Q!bB4QvKqBmBuQ8>9Vs76{7jFU0mo7e?T;*`o^t0h@81 z{SUQY_LOH|t0OQ(P^Lwvz{&0d`BUW`@qC!&s=-eC;e42*4EO)28|+EGqH)1SKzBi ztY7p8gH3OfA(DgOVFOJMGIqOalcyLtXoBmXEPvg*8VUVdGk?>>2`NcI>uU~Rki-lq zDV#bRh%znhbkK5N$O58d+1*CT_-a4dbU=!zdp+v7<~jF;>uq1v_^U)uaqZSRGg5)A zO_ed5X}?L!CgN=gsQV5+aSSqmKE1_gsI z?;G;#Hj=(ts}?xiti)gw1%K>Fyx3n22s2ZKwtyi1u%+!=6b#vxr5adZh^CZ7W7C`% zrsoWJG5mJj_1ZV)RZu>(Box~KX-w(gjw{a)B>7oaifuC{WK6pEgWh!!X|~s-`s8;1 z4{|L&rUdm1?4U*eAgz@e7#hmI-K*Oe8B-;&4+x;ZI0Z_L3Nz8-&HUm6%ThWU^;YL3 zANEXd58`(7?@LgFGp*^&GX-B6evyR;L>Y&b5vvMt^NSLttPP`)X8ItVu@WA{y8zQK z9A+X~Xgh@gT6P{{FoI31)s!H)Zud=)~5j7tzFv4r>$EuO=|hHw{klHRRzh$xFs z14ycJ<+ytJ!f4s&c2P0#vvfMeh{f$1`)O*-&!K(glRf-C6iV)zQev>rTz%4|F!BnM z6Z3J~*C)xwsESG;Pk^kAl9f($&8d{b)e>}65kQa%a?dlRn2BN~^7fsSt0cgqnbSv{ zlzY_=Nu=lHu0>;F97ZGT-K&mP56UIcVDKr(PmYeO6&fMhY`6hxDPf6!wG`Vr?z3Pz zxbsOczM?)?@?S~5E13I8rE}iXHQq^2xB8mOhL-)Xw>hRY7&0HvXRi5zwm*2~Q(*I4 zjs?%VheR;C$MFa=utV+&gw5| z;P9#jL71uV_vB1J8a|QOS56NrgcIPU0BaPj3ars6uto-7Ss0rJ>2GCD5_1OO%ks|& zYJep&5Qani-x3-9TcZ2lq)e|jXdHY-ZKy~kaQALxbWZK-8*82hF6AhtmMcx}wf09= z=hp73)z@PzYu9jdJ8>gnB2NqF4oD~Zsg;Y7Nek7Gw9Q||ESdsBXwU;4sMaIJE#kj@ z;Or%?igAOKMu8_{EzIT-wa6p!s zV2ZqRVtR10#5#^Yz@aHnL~9f!#Xrh|624zFk}>!ckP?B#UD3W9Z2*?ILtZ{lZyZ~vmlp;6ND zglE73^g~;@K|)_qxF?&TI+Qe?*=U zdvf&I0yJ%S=3o6;Uq6(9X2B;Ykz`qu4nAD}89k%l9y8zJlbXX&9qnQ#SQm66Y~l#> zmS{$_OBeK4^2JWY@tRgq+b-x?xls)wfP@d+Yei#EV?ScaR7>2U*2ezJBe2<%(aK86 z<&a_6fHKBw*cT{cc0Qx_BacKMb?^r*3dKr&gqJ<;f(HA730Hn@0xjAliOO^9oOP1b z5rJnNf?1$J{1k!etJGxEU@TJ*@^P ziI;o;3|QxXLYriou-y?_Erm97F<+|CoSd~f=XRXCIJ!DJp32*A+rP`(ZU3|*U_3xO zDn5FOIqG+;C`w#x(nl~a&aiF|Pq%s4y>s@ue_F9_t95-=KYw$2Ha~BD9$;HvzML*C zxM$OHf0dz=lCm@zr~h;iAlQA=c0{jxKNy@5ycg9Io$=@V)|uu&1wTZ%m@QjSOqxFS z2tj(U1_KhMii(ZhQb&lW4tvrn+}kbYGJSTox*4264QO+h{<}V}5+Y3U>>vPV#lN{C zO}>o>%x`}av2^h>9#F)wk#pw~urcyhv++i`cX2ph>(@*GeR5KXt`1!=-5aJ$8wdqP;o^9H&8ihand_U<(3QtKqq^Js*lY^R;e{ znz)Jdm~VSG+~1BnS`?_ecf0TMeS0LE^IbE4>u6Kfh9)6;TWH`Sb(p>B7_C}@A)BXX z;&Q%M*uPmDSB^^T;3!B@O2b@c+AbR#5@RiB8Zv;2pegt8m`^Wyb(-3me46l{wU3L> zgM7*$+$R+igAFFL7=t`e*@G@bfN#&sgItc7Q&Bd)Sp%Rp;y%uyJS zN}ns=^B$ghEq`|(Q_$sbu46zizJZG|1K(B3@jPq4|8ahhbG9wuxcd-Y7z8&X<)=Xf z4Yx3gd~KQSaoLAD=YO3sbN__&HQc*!!U6H@NCTsoIdjuBwUI?R(yL6HXZ1DGACFK} zSWF^&M{N8@>@7 zj9>p0>gt7I6=-aHhcDK~IldkZ;hE^~>maF4w-*wcziaZfcz6ZnO86_HpdxZ9Uzu50 z>%H zvB&fZ-Ij?~oVN2M@;d$^ph6}W#a8N|3MpqgOUwK9o~h#f5D6>!Z9v=WwPwT0duMFH z`+E5J{J>kr)aU7Tg7j^``{iX}c@Q9Z?_qbOxIJemZ>&9LRlM8wtem&sj-Q-wthK%( zJt@CkEu4E9-Z`(l7ggNdnRmeOF!2I>qW7BH32ztAo|O()T;|H#hy}0rC$0_``)9=G z9kwjZ=Z*Js-qy5+`?C|JuOf5_x%d*yFXGLZiX0IKPv@_wBIc*;H*zvcFInz z>t8b-d+vDKQ7_C%-Kdn&It-V*Nz=lm^)|xAiF#Aa$;OQH%FFO+L8Ag;gH?m}k^7;H zy!AQ8I@xjZ?N$vJpLb&Es&gG4#q00)nYRhv*SEF(%c2d;39G=vFFj{8TOPMTg7hYL z%X^s60(G{Q}Xn38;UJrmw2i+eCc$1LuK%$!Rr!N`^eew@>kE%Q~R2xoWnhnjLZ4uc)XaU&9mW; zKU;8z$(>56q0X9*xA%2KQo*{*^%!qQ?Q7XaYwgx)hWFyzd{&zMJ_Wn8Rvx4C2$X_M zq#}|Phy8w=pu3y%L}$+p)sfbca5w>rXCwecoET0DhJ3l&{007L4}|<&a1j(!_&e#R z&+HfAQUBbllCDV+Q4IPN-MbjBkZ=hMRa0P1iz#6=u{nfwXh{PD{kw_+gsb-&wT>G( zZ9NdAk+_n^5(+990_CR^3T(mh3+~vSHMW9sfYh z#i2qyfjk;io{PL?P}4k57#kEPQ~mc`Q_)}vr*X_grQj2lt_27JGfR`%;J)h$3W$zo zm%9=hZ3|0moF${^luj@aJpuR+d}%_J-|iqLHIOn?<^1cK3tNDy12-?(AEQmk(x0yh zmBsYX8ML+BCS9$oo;JI$x(DWdAFrk1A=Wc%oMyVWwz{<1KZgE{}jtkjQ#w{9u4Sj@QB*f%XDc=m#Lfj|Muvj)2>U`fK}R(@w&bJ#`s z&3*Y;brBg&QD_00D~pOR7!3;9>si+ypX90OQrUxOR4RT%vK{sXo1eu^p3ODMt6O7Ud`Z5EMs*KFUDK@;SO_ z|M%hacieg`Y(niQpYNO+(4J7+B3lLlGSdnrk_rXHgfOU9A%g#SD~Sj(RoRIM3|Sr{ zK@2_d=A{aY)w+5AZHzD8@((03U7B?uaQm;2X%8R*`^yY~+rON6X|Ugl5)mkVVmQDL z84Ajw^^3Xcjs!)P!1C$l5yLM>pjTTQd=AV351_@4&85%0;4dC7V3I?D28j ze-vH@vo={^;KTMD$I|0O2Z#xc+eIv+G!BAL<_svYZhHBj6AI`uhNPADZ2Hjwri+ya z)LJM(5DA-}4#HyH*`5}8o+&A?s6l6kpW*{@{y_$Z)RwT{{V*X30Vsu@27_u5EQ3Iw ze+qLiE^0hCNbJxZo8ley|0D+58_%Kni@Mlqc+edkpe&6O`(Nwq{D(2kgUL-Yn5D;n z1CG`4_tsgimm0(u2vPnzO%n?zzf zC;YN2*{vOsiw4>cM8;WA=uiMgjD}x^6e|(LsQCaZ2g$lG{HI4ppTA5=kLOW|swbGj z`mlvZBBz8`ef$4*aqX-^r3n;7sQNd7DVYG|;s`_xfC_@{cyijZ)?}lz%V4)HyMOb2 zjC=%6f~tiOn%7&{y+C1OxKQNWjIUJY2Za{B>id^co9%1u1C58)>&GwprsrGxI;NzP zZ|&yyNV4lc*O|8GGB4!Q5wQzTL^~zce@*3MU*A8!s54m5M4@4uW`2HWwIpiC zRd5wv)jKSBBWNf#rdq`*hBBz*4?z0$N5It8u~P|w6@L;6t8AQ42blKCxSIsBvOZiD zR+r-ew()*Zu?P?7Wn?Z6D(-+iVrV5rZAm?17(FmbxIinL{B;_-!LLS;jS{;FEA;|q znPQQf>&?U#S@g3HCdMZCHlGr$7}-9qqIoWOj==?3P^e#7qSlc(FAZD^{4`jfD{h-FMf%-cJ`C14kUy@ZgHZW|=VFK5#f}9|S zZ4~q9PvyJC`?T@}^@N2+k$Sjsb?4VeQz*v-7PY%m3xSK1=K#o60WDSuuf0Dr-jz+zOi%igKthr~QkC3aAC# z#@yVg>>!D!-4aN#QP1iO{5ileUsZMfv@I=%O7>MpZ8Mad-&f~RgI@6GhzMsbcVYK z>JQqn1O35%qL^cYcuJdHXUhI)g)(Ld%pPg?S$)LxJHDWRRNf!w)DthJvnGf4f6&yw zMBV%k!X~gJ3Ekx&4Ap;L6_OE0gK-2GocNc?JZCEQ_$t~aiYH#|mAKr`r_gXZLiPx!PM ze{i_Q4r!fQI=3;+HX-|-q8UN?$NUt<9)?WUVLx?EUmI##b^~y#mGw#f;-Mf0xk4y} zQe)4RBVsW9JZTAwSh}29ur3wN!az1hHKCZ)cQRLB3+eC$(=C2Zn$pcx@>-0PWVGK^ z3!| zPIs(}$SOY#Yfvq8swaNW_yO3)Nf=hFB*~}TL7Im)0ZXCY^3#y=OA%j%n^l5hJVnt4UN&$;H8O?u+j zN03eGH>;jJc;y$yz2EHru_!08ze;0;UW<&qXyuz9DxaybKSNXdR!)!HKTK`^*xbJ_ zK%C4aCs^*wX7Myo$Bx>SEle6Di%h^u@49B<2YdG*PzagIAS*U%JMut(M0E5aZEPeE z%SQ>B{^nFMs9<++mr78+#%o*b3?LZ249S~>dShd5mIe4G>p+@}&;FtXoMgU_RzRC> z=k)F8ym^>FM?3hRx>98L<$LFQ+4uSV*EYBjxcw)0-m`S?<(8JkHrrb7L*e_*cG4N! zI{Sgt@f-+j_Ladfd%!tRbul!K~P3iJL@|Cx=sPALpX zmq)pKOA=<9{743Q`2Qe?X7sY;q&IQ?e{_9iSd?AYHeFKE-QC^Y-QC??(%qnRcXvog ziImda(w)*Eh{Sgd-tY78`{m5x2+ltC-q%`Zt$EsUxjGq{la@{h6>9)(@HYU_QfzgS zdBEU#rP3uk#){xpy`ADYZ=(LpvhZXQi9AnUgqhS5R9#Yb^-pmFL&o8TvAofIVS6+> zRP0F=kKHA~hZ#mdlcIpa<)Gt&%PdVwX0PsCYG8vN;Bio#$(vj34Wk9Ifnj28>6O^R zj{9p0m9b+Os`4ufra7FXyxu1WeE6L`wv&{@J-0QI5f0cIOsD_2sok9h*yOUUj@-KX zDaEzYJsKcWYK=hk6NKZvx?vWvx*_hSzzzL8jo+N{4vuO3tT!=7aqF z6V44o;(m4GQZ#GSie2p!1QL@Vxy=W36W}!&*M5e|VDNJM($8_G6G4McMw}EWew984 zD{U`iGxl1D)@BcS#a7WIE3#dg1>yNw9V@7r*hN5Ccoz1>3d!Ljp5#O<=g8k2T(xSk z5QfVwc)geriGCr7y(9yhzglPEj7tn~KA z7^BOj*RW&SJobgda-RbC7D&iI#soUlw2Om^PwI5IzU2_7N}}o^55Tdu5Lbd0!!lf} zediTjZW1LWl#h^J!OMn;!@Nuy3dMgrpmxPD_Aae-t7V=(yi66E9}kA{dp!0pR1sU3 zBfsQ~=Z~oXzd0~LOTCdCLB!`^#-i|fz#LGJkh{xi2`6v6BdD`_QG|2l%QXXxepRt*76R;NAJXkMb)(xw#ij*{%(i(0s#Qy=XpF!z;SB+3nP{SWYHaeQ1re8 zDK%}AllMjPdTrc*N3;4AoLIW}O#=B?Q30S+*H15bJUG7YkFeV|yLdU<*zOv8N4zv8 z^pxqrlWO+WMv#6$6wXzKR5!gc-69hUR;Y3{+C*KC0Muu(O#bHhHD0p-Q`IJBfGG!6 z^I=?U)9zNTTKZ$y5nPtisFz4#p@wM*TGdcUNU6wN*@m zu7GhayQn;=VU5BNd`-4%t11JZeh?7Rh%sQCx&t%MPaMQqV~@?2A%|6^CX({D*AkR= z#ua9a3Ej>J`;ARw@rgiGz&^U{j2zAo6gR}#C5G{9DKvLuk&H!p+3+Lvfx71})5F_u zC&y@^7OEwsY{;Wo4j}&RWaKCL#V`TO=#YZ9O+#OPhhGWfQBLzV~gb#(szE!r!idq+ToI z*WFkD-PWbo+u8Z~NUC!9=vg!*Tc$Ewyi);=pzI3Wzg<`!45)G?7M#;X6to{x7hKQ^ zph>5#X57N&V2l(fEI9!_=9Pnfmv_R3NcSTUyC_@d;1>p4MxO&D?jr$7PhvO2cA)mT zV6AEp_tTl-yTk=lPhF&$)CE)@I*t?CBoe=+9ZOD$#9ElKtZ#1lIq|VfRQ2VxQUmP~ zPFGEM#kztkT^~LsFNpA8h=i~Vp?dPDN6wR9;pvHTzctWVGtlZ`5NdE;(0H?ma5I}$ z)XQL93&N4*lfX_^w^kJ=^9eP@bE{EVoo|%?`=v@Uyfdxb2V(L!2rDqdD=Q4)3o%^* zu!5o0=K*K+6frE>lwDtKFHPlpt-oHXU31BK&h?rDB`}j49O<9z-LuEr_0gQBu3loo z0Co4;gspLIb-uvK^BKAHFxme2&s-`r-P+E?2%Vp~gR$x3Gw#)?`Ov-%$vJZ}g72sm zqa2mop>V9Mnujlx0@H zI)Vd`%G)|ZQcm4K@Z3*#d}!nISeYj-4{jM&YCDy*0Gu*jSh(UtehocQY_6pvD#+2af{WcT8yJc=TMi<+okdT<`57sVMMIx3k_{eojokP4m?%liGZ<(_XPxQB$4m|TD~`Pgu# z@^jg}=cgKEVo?`1yF>}2VC%vE(~|;DK+Ex8x}q-0T9v&Q=O9%LHOOc*Q?J@B;hF|< zjdn=H%uLjP%&0YltVxZ0SD$-q#$E+gmrwEeQ7@8yDm)#7@! z4Y@huxY;GlYXrr&2#4pw-PZ&5+1buZ!g!aho}VB|ndD+22=NsSw!n*;N*U6fw3@Jm zFe^`60FDG5n^r2s%lygjUV`dKyqt(N7If^ZLC1cdIHz5P7lETC-_taK02-wAQjMp) zMjM4hbzRt}fufM^Q=gPbwgmfI{~;R+O*@l7GJn5g^rL_dLcL_Sm`*J4dp|*(`S8$6TTZ~XWy7Xa1-32g60mKl zR|1qZS@H!74(z7&i(z1qw-o)O(mM-?h_wn&3;cRIUuEx0R@d?4#GS-rr)~WRmWL{; z>Q+9KJ+4~kDNk}#DN@3d@UaT4&j5Gy-Ns!aJ0=-8isrD`Xeq7GO!t-I{fPMo#)t1^ zlRv{2W|!d7Rd>*jE|XU8WUk<;SAYJUVPUw5RYnDbPZM-bOY(x~rbJgUz{+OTzO5_` z{uuB<;6i1%iB^8QP#1N+Fc)cnn`G6-Da(lX7jK2SlM92ZWwq3xN2z2X&gBoYr&V+e z!_$c~N~-7%Nac+dZunFxaE)>PrO*fIGKley^Wo1-iW}K_jl`#_;z2Tlq)~-+85lbY z#R&Y-_~mq&iEA=MYpwB|7u(PGr=E zjs^G*k(>KKbpV(1aUQ4+^5iwj!|7bq7NR`^?ka(&SC4D`&84WP&F8$RTN%LVpS_q3s_Tt|0>O7G&qfV&9|1w)_Wcl-*;-`>8-ONRG3D@u z2Oscq@i>amkk`Xg=4gho-C#+JWi4QWje?SMo8B&wVg?$3j7f$^vh?E4QIP_Ps1(#^ z>k1{YM^9D|(Wb3f{5DEei@~ML06sf$w7&({1KBEKcDMlEVG7^QmcVZVZ;;U-4%;zN z1loEkpBt2Q=i?HFxeFmc|CP2kvGov%9Z;kv4RU^@h>`m&=v)+eDd?=#n3H0&IMkcZ zJrC|XrN5R4mu8{>vq?9f&izy);_L$qKcU~YN=Gqe(<=i1M_yBbU{UX`pa({0H5QvO z{ns$`?)!v$=KPzyqQosHZXu@6%$E6ko65i9uFOa@RfQ#r3ckeX{5W%B(9M8y{4n5r z?)ZH|4tT+s?lggV8Eya!)cs6!xHRH(K6!lMShmaAlT_(J~cYx9EO*8@N=Uy6Pih zwQNC`uv1zf6~T3?w6T12gjI!X7DHBe1s?8=Xzl}HX{wMZbc&F0+GHOP>0@*IP8*L} z6(e5sVSQnl$qaCDuI6n*GBuYGb)#AXoZKjVr9qvO0q>g>oJITh@Abvow(Bh(*Gl)t z4U>9%+p_S7t-~pqxyA_)Ao^SRtjVrNHf!v$VYzV_jj}d@A6w|cKk&!$PrnBrB<$z7 z|uif*7EC^sul*KcD?wtuqzTM_RFS5N3fU= zYO0L{WP<>t!ZGj&VE#TF63Tu0xa>Iz?<0}8C|G^VDCuhxNPP(vo5-AGZo5Q6bJs-9 zQ#mGh!+w{I1O3-8;c|D^`(?*)BOvlU`B9TA0>%+!#5JNFK>&1KMsLr@^NX%e#~zpW zhC@AJ)6z?@?`nt>y15Ex8^E@{^aBl;J>NS&KV{~x284S3Uv35ImlBkFlCi{ z2T3St$4&}}FWnbVIki%CBLZ{D8LVUlE)juQVecT%h98uDY!?44<)Skj%=CjAkZ+@3 z@P!h;%LE|qsMAGwYt;)r;s7|-%n(=d)|hcIiekq1lSdr1>7ikDEf*yJX`dXBX$S4y z+|Y2AM%JjBdJS>aRJyJGPTD={=@}hskf{);~N?#>2Pf*ANxNu&-mUH64Xa-r3l_ zPrDubc@3XDrgBg6zs+^{qh)%0_0vcxZB}QqrBpc68PhY1%KAi*%kGu2GSoah>7nLX zL0*G${+n5#Qs{4vez4xAW`Z*#^ZJ(xa**uKJ&9ger!GdO{MA7*IR+{(q|`%p`T~&` zQuZX7hsLyyr~>t~gUgBudtxaQ{7TZ*l*q|f#VHYA4_QFYc`X8nUWrpr9nUE}Wr4BL zRwSoRqM%R8GH|A;)JFjCJ1NknDMvJuVmanyF%LGp4bgBb<@7(9xtIE=HOL;~>il1g!HYfi^&PeQk)%m!Y0)bFzJ*Cbbh4ROlLKe}Lu7~j02O)@g4!JF zSa42^d%_DMpJ{>a^*<+z@1WK_2zSFKO8=Pr5OlPBuu_xThJLP3{3=iPl?P5$IB9Z}s$X5Rc!O&G&(nNDOKp>yB)v{!yCYkH1rxeVGJQBWFCl$=T57v^M#VW~ieo0= zIGpP-oEILlJLAwnvSNeV;vgXRmVi$${LJnRCCD%eGNT@oV!?RBlpH&)*~B` zkZ)3;kfXG%<`I{;1a?gpa;b!Tq0`@Xcp6kA8C9F76kfNDNHj)Z{<8(AQ>rVcWx0tK zU~007$a&KXj)?Bt(nf@re+4`?A(plvx!`X)GV>$~XxQ&QOsZ^DfN+rMpKwrkH0#`Y$YTwiB|;$$%!`fb z*NJ~%S@Y@Kd0oZA%a6%{vdK*^KxpjTw8kvY^ujq}ytC8OFk!sfkxsoudf}jOXp$@% zL2AGK=V)o+^UZnrkhO5Fd=?97KZD!#IdR`Q1yG)*r)oR6P zE%WkBcESK7%DGDQ${d4RB-& z@ll7ntN)NJlZ46F28bY={Ua_%8@6Tg2@=^EHD3nRR7C8TUOVs^GOA@AR5GecWuo^f z?WT0M_mDY$aoDW5BZof$cdHJQyULI=$lXds`yL&LDW{UgU=>|`rZBq>!>}rqP?-Kq z`i?!Bj7o{>9UANBmEK{=*v!K9hiyrP+Io>;&d_N1nLi*e3q(^=9Pls2PkA?X-m{W| zw9&D^Pn<4}Gw^(m4j-Mlr^cT8k^=!|2PzOd`Xv;a#7?3Ls-W`^bxa3YqcZ^}W*n2* zj4Y(-09V;3$wo>lT@d&IEeRiENHtr$sn9PmHbA|3#l@RQ+V(&VkGTm7qP)t5-%gB@ z(djq3IBiffx|_)YI_`T)ik1ZL?91Afpf5qkH7ASYP+yA&s}te%82Kmnv)&u#eBC&@*S-e#>)Kx_@uThp)nX6UhGSnx~$&Mr3yMJRB+$4YCyv)9GvQ+i~OsXZ9pPL=Acg)*Lc z9h@aSNCB`wO#fJ*Q5bf$LD2RY7hrqaKGK}&7xFa9U?$}!Y*Yw|tTQI%<1P554y}NM z7siTCVd#5s);xyBmhU&fmBj+MvchlyS5{_~m90B*CTIkqQ0R!!$>-3Har>biw(lT& z$Lz4tUO57FzhzP6R|6}_uOpVr!CMBAz6aY)?L|Opjo4(mO@_6UhYyzs08k{EEQfs_`TDP!0EX?^jS5Io;j|3}33n6rOn`GPl_wZclOkARlb6%^#NE%ti# z`6YkIC;)w+xNtdF3B48vrF8qlIF}g(tqiF6CVLmoQG*0$3s5j?H#;N~CkSA+0IhnX z_eQJgVwJuxQ2QAX$1R@A3~GC*YTTn}Owkvo&p^ zlEXK(T20tl{)G!PBwaf|L*g3;d{%|fwG)B!97IW!EW4_;AP5FR479Q6q9=fi@|i5F zRo;DW;HFD}X@OEtVHLb>!`aIW`7%d(9qKNOX%Cy5&4;W{oK^o)KaxiLqtU8*Bp=;g zl`B*Tr`|eLPst%1F^dj>0vZ10CG!H;j=Y#- zFKDGEKhV*WFCf=Z4U3}e6szzVb$2pDqwk5L)UcyYOASVi--3cVRIu7&(&sfr&E=i@ zn%5w{Y=KnyDfpV+cr8b0j1A~@hEV?dxl!{vRWrC)C(Qr|!Kl@~DtMSg*`Xm8i--a1 z7tzRpRV?`~K*+9Ud`9Bv!iUET&d|xw0mFyYkt-lxuKHE)VD|RM>m45{K zWwZkmE4n*K$eo%ArE&0L$;faX;<(NOUSfQ1J-HBT+b`d{0CnA1GiwP+G8Vht`LGWP zpzhiSxD}9AqvOB&K#yLoXGv9$emgU#N2e7t&k}q*O@X?S>OkxMlNXcVud|NX9$%~W z=0;K?n};tS?gpqfV_9us*@~K`lOq>inzNnw4|`g{wSh@lKcne&k}54Qr!pvh@J+U5 zSHB#aM6oM%9A((yUs00MVz=sa60nr=+|~LL53#guzzi5XRjYh5A{^%wG=;7svH@ky zEQ2Ne0kUQQ$=n&&;ZA{??(#qfkjpXogPHH+A@_*@yUoo0T4!&p9YSq1$8J{OBGq)! z2&ib7L_*Uswc=E@v}J-;W}PGq>y;abjA*`spdYR4A!_`pBwJe;;86~)uFkFk5U?Uq zJ%HOmFRQQ&NfE}cG-*Pn7AHx*bWOgJloi}A)xj%tUBKQf0C8gc6V+!lP72#(N{G&& zwyk@F&+(rOSK8unIoYXNQ4INyKTs1j-Bl4k$bj2YTAOb?0? zOAj^6A0w{6`B>GQSL`5}OH%OYZMX+};+fp1YD=WeLA>+}9f-op0#Wbg055HgyI%77 ztyXYK89Pdp#2!4sqeqz5nPDh(ODdagS_i45;v;NpqYYe*Frc~VF5%*fd=cKWNXb?K zHBz5WGfg}8APp^MOTw4-Y`KY;SqgeL6?`Y4&@9@>8+xOnO%<$hydTQ@kz(L4`?`L@ zbAg%PC6M9_5M1`^j3*vhFp4r}f#~;MoNBtgkk8R7ZSmt=I`Tlb0&j)L5DZ@(W<=}|K#&s zI2bI(+T9!)gM=+C&CwZ{`)jd_L;DO+DPiVX+~5S{Wx{67{_tf;a6>1v#0beRTUEM= znuxlPpE6!lOlOI8)2-Fi!4Jhucp}sPB=oY%Z9@VAxS_)A1)pLcEi3}KVX0974;g}a za@m-D$S!m@4?!RX9I`kGQ1&qt6TR%@>LHPtR@v~VfISwNcUk+w8g35E9U+LrMeDo9 zVA%c&*nxQF7!b!$N|w$qgnkMK;TunbvQ-uuL}(Ib*oYpw1pq1e)$$vd_fgk8)AG@i z)7fmfk#F?6zuxX5(0$(PRb9?BbA(}x$K~Mu>GR7*#6}AB!izp|MUB5VsqA$F(wmSB z9QMoxavI9#4A7J1@)vZB#`v)dxU$AIbcOt-__d$UjHaQoLNggMayjW(n7ezP&XABs z&#zCea`KpoKM*mJDoDX=6qd-c;nJ#XQ}A_NoUcENX6xzV;rqVB$Ti; zg}@}+?F`CN`6~0dnaU>Z5st0|imq_uPi3tx-73E<=+_50j1RN?Ot$UE@G7McDeQN) zVH+y%IA-cJ@GBD(j!DXIi|M7a6%qsVv3mN-4eb!kf znjzxM6@89I7Xh%x0S?epzyVre+k9lp&ZO$GXxPD-hx81|1P3%uMy?IOSo;(N^%TWgA{LE>H+fABz+>e8Y3qJ&7L*~j^1UVfyFNHF~C+6 zW(*v?C$chfvu0oTGI+S2liBwOiJw^LAfOL%d;3oFutIJzsR)aBGRW8;(!#2?fb^ro z+iuV9hI=n2gA;>x!!!L~Mm@F&xk3Co;?v4m=F+#ysCXr%6ong)Hi(tNXEi-yva*E% zP8xqqYJc2e!)I#){P3aAAI$zqi$-A>$>j8JvX%6iY^{ojYaHvziIq(Ag=y!Kb@?8r!DMwGL?R^!Z^Vyb{u5S#46Et_*vQj z9A%cP-v^0uYcUi=N1UguGGQm}Er0WpCIFWQ!Mc;Pzy*P?V^enF0Ic7aC9iRW#z!Xw{ewCNOZg?~u-LDUjF{ALapOJm5k1^~4rR)GSg~ zm|PvayV~J@ik8jM+4zuwMv$0_BsCra5L1!7iK(~`>NFm^d9N5JG>vmlFZ6Vb_?8U6 z8VWvF^YU}7DgN+IhG-U7X3|=^_e?P2!lXpbCrv-NwmAG0w9eaF-ge>Td3kV&UE|Je zY&6!ZTm1i*+!yI}Cl-n&jT&^gi-Q#R2AF?0iUhm6thY=bF+Cd`$OjX1iO3c(a#J%- zh8~-JVPg7a#%sz&4AA9xYx+#zwDuiJ87Fr~v?}1qy^E8{3N`?#DMX|B(?jZn7P#OP zP3$tzk zX^5sl6viY#VH`1htgZSALXK}g>l%_ce^#*A(tiTFrcF$L5=2n(@JbVaLaCciW{oJ0 zg!>GhIazDMeLVh%lZhxsSV$eV6Dbjd8 zIPU|z9MpHI6Oh}TTCj#VA|Ep_#ep0qfs6&96Amgxs-OVflnGF@w>0^362)skaH3mz zLT@zyd<|_4h_9g}kL!t6u4)mz$ylGD3IbK3#0x-3^K|i;v1e5{Mxq?cQ(CowOZ%X> z9rjnGod%XFr~%`6`3ZjBcEJPS6e9+y#=@yw_&-5wfc?J0E{hKS{a)dBEUik2pL#H9 zTn`HfBFBAUi$cIOU@)UwL+vxZGl6{CPZAR$yu8$&Pb}?Fnt}@=y+`OXuH=c1>IqFl zMXOKxEHZ_$!o>Y2E?ErFwmjGa+7@)c;a2;yJ6G3#k?HtdCYeQQ@y`c01Dtr?vJ-F| z^0B9qb#HK%1KJy6;C>2vyPw7_25@xuiGen4u+|BJMM~>W!o(G2DhCP9t|v}2{S2}R z1Nx&6V;osT23;Lh2uq}9f~N^XhV5YdNf$%o2+Q~^PxTXbynZT-$Evh#(w+TVw?D}* zlc)@rB7rkN=jwx0r-Jv^>OV`5rU&K^L{tK0(YAf2RwKBa_+Ti_MZ)7A?FBKnLN2|6 zE63tt*870`k8wfrBLd1voC-i;-C6V|f{t|hudt@ml1#^bZK2arD3?VxvGm;aZEdUw z+7`Lr-aqN@J{hH6|7+~UBy`Lm_{-=4D^i}^uFUH^Q>EPQhfB-?<`=w#`m`6c{a2Sx zt(dBZDL3_I9!2-k9pM+wRVjbR%Gg&x+hZBp8~dt-Z$VDvs!3zqfP7WG{SzbG`!p{k z8U(z(djsBlHmr;QOrCrW^OdnJIQA8Uk;F@DKEX0}j_Wr(6hL6UYU)*rrBE9Pk+<4! z!jL-a^acIk^pM`8VSX?UXS@^n2mKOVgL(!EePh)hZS`owKE6IV0)# zdmVqn*FKl9diPD7cj`^LvANi>@Foo9@CA>4pR|{RPYgDqNMz<&;Ipijf8n{k${>Z% zhLQk@+4j;IfL1Olfdmfieb8Amm~(Vp{g@&|k=r72c;bh#@A^Do7UVVrk_ zfTpnHZ+a96cpbFozY>UYR1*pDb?)iW>Y{9pM7+3NfNW5pcf@CdcJl-GwQ0W9+tPr_ z#QjZW683XiN^3wG2v4KG(-;6HhYF9eV$l>yK#1yh;{_sU3`_xYC5Ch5p;mTkwktvw zEz}?$#ZT*KV*ECQRexgg{MWe3CQsV z$$#j@pNbAzB|?l%*z4D(AvhweE^8(5vP6X=!naz)BCvR;E0jpd2etN%%>Ew?JkNsj z40U4{r9x;ZO58I6vVm0p<2x4&*(Rd8u3lv`6efyrMc#J@{kA>q{kiX)z{s3;zP7m_ z?262AGnJCZznH1_D&~N3g57X!Tef|%-J1T3(}| zi z+Eb4}K@-{DAqCk;sDzgwXNU|XBl|icmPif)7e;S8km3>_hY0_py)ri<$`x48DyX&N zcFK)^mm(ev%I@qngX!1KkmdzXi^>j=wh1La%pce!>EC9dl8KO837736u!x ztLg(*{2oWfCg3Ho*G(kCyp_V(#pE@fgE&$n(+ihY-sB)$@MV-0;es;qr4V4C1POT} z=wM(8j?3N>BnY_}lkmWR1c?@n5ATX8)gVc@;4`L6@QcejVMGvYz-T6f;I$(pK52fp z2P-wj1B;C@uu|($r2iztQI!5|iilkf9qgnoAwT$}@0*}iDizp@H8nB&03EKj4X$28 z5vFf3$XcY4nJ4G#z^Z%W!e2dIWGxw6nWIk%yERgR82q^X6A0U28J*XV5P|P7uT->> zX!UnX(JkZY%+33r1r^3f`OnVWKquFwq3d6kJVwV$b|F3#rAy5Ba}EEp6(! zaIzB@u4X>fO5y6smc&%>0rM=bX$`+XLO=_KIKq+AZ-iMdi4nG z1ln(OdItnpu5@K&W^c*X(5OK(K{z3>aI9H?)cFT9s950+mYs7)O&0B>4)x_k4kjsC z6(@ox!LZ!@QQ@stYG`w*DLg&Uswa7T40Br-seN=t_uQ}cxd~1$5??cL< zVSk4Z3Yla5MP~FA78GJgnGy8uaDP=RG5R%1@#*~*mdKCg^M&Sx4HSNq$vfd?X^bJ0 zupOMx3!S;Z4qoHA%{$lo`HAzY2EKbGBzf~%T_c|5KPT^5>~H*6m%;Kd)d!Wjj&R^Ug_lxSM+@^|W#{)k&RfdrGOXwBj_YIK2=cZb zgL{$5Ka{NF*dOZ%+VSll{N&SHnK?m}P<9HA-!}Evh&%%$m;dVr#MEKuR1WvQ02_|K z7pO-hB(Z)cD9%&f?nOmc8!ZbvV*Ng(wJsj^&=TJZ|b9o%MWl z!PT54CKPlt7Sf&Fl=X@nTW0@-y8O-GSvF`VTct@ikC%}=E~PS*;mFw&y0{6Lz7q4M zG%BOX866IN0lYjK2ee1ZAaYD6{^*O2{0urB_LMhT&lig8II5o|>CLAbJMpgT>*Yjq@`p5rg%l`!ot1RAYA*Y_X}$c|q8?ir)l4-+q|X@wS|U3Bf>WR05| z-uUn&v?;aLHox%!XceeT{AQIYradMAJOnNS=ojYOef>Hl)DqPLft0lNcmNLZ`Y+6M zEBPT+F5p&)X^hr{8XJ(#39PHQ0E3*jo9eX^MeU%g0)uGS>QUR{wdsc;77%cz5NKOT zThw6fXw60&Ob5({y<0iv-@6n%-5>|r1a zm45L*xfj4nX3la8G}YR5t(D5j^@4dql#KqfhPNeGDDWs}&-v#GW@wlHnG~!N68!o6 z+rgb#$2>cMb_7hSwmx8O-M#H^n{+o|iMii6J^NGWcNge9wY2ajte43z-(Ca57SL!M z=H{2+-1B-EIHQ;LUMvg6U31;b?I zgSGG@umG=7a~|bsTt(ssPvfnymkfV87BmaUYQiv&=_x|sBGE^mYI!KS)o1Sce9BXl zV7y}vY;u!$IpV7W{P0te_ zL|LCO+nHR)hcpoCU-CyT+a&;TV+%rbz&P+=$JVw0Co#N#(xya^DWppJN0s4175@OW zgy-#ygkT5d(=%0rL3|VgJ&$)x8;-1%0kk(S+ zFB#j?&ovjPQR^SlA8zetQ~o`Tm1XsMjq? ziRI7ItK$&=Yx(nqe+$5rnF8T?^vY9xw4pr|LC?-`#zkS>A90`tw5MaL-%5^`&sQO= z4lgwuFuU{sO_8s|`HiiOWI`KIJo!fe;nG|n#7Bkdvn;}Es_&7X%qX6FaOou|vQ+0} z7|dpdvdY)?sa6-S0GojxXGs~goh#64X$*<1s&M5&HlI?CmXvJ}>Pu>@Zfw?BvE8;f z0NJCfi15g3RyPNQHlk}H5T6xy^H~!G+R9t&?U)n;8R`KmJTG!H}{DpRgl4cYZf5p8fvA~fb z?_Bq93l-zv&F59}dGDg`#NlU-_5@TDGiSHS#}cMAhzZUwI=_BxoX-83%)i+56`+bZ z^BR=Adb$@n?i)XBhrTFo5%f<_oOcY3#SOu_<$dPz4+6r!(?o!IxQt{msgd@|`a~Mn56k$*X5&%TIZ*-wvepFBJ2E~Z^)Uhx=bLRmb)GH1u~1~2 z@-v%PCXcp`V2ngo+s^4D5x<x8__DFp5)u=~@Q?tAs_Em%(D(WvfaP-q zmQO+bZTY}|ewW000-7~aCgr{$z|`_kf+N3AnF>p?SB?EN6&(6%TfRA+62I>P{&Bqi z2i|uBQ|MRqd~4n8L)oG#G*hOQ_770*s-Dk98-FrubaNNS3^WXbS`w7Z6sX%BEN6qc z`VB%npZ{gTS{0}l+X@c+4ur;6n=T#z*(uxs$381I;Mngdj)d0~Nx1X2l1Q-LE#6dn ztbr@O(ONpBa#h`bQl)0u!F$6A3ZhS8Ntvo=1^BoqT$9oc@+7dN43w%og4>7rZfv-X zz+8P!#(`#3LGA7PJuiQk)XIAMegGHXnxuJPj_w0*2_D^kqIuA4mF80)LZ4wkj@`KQ z0i>&F53L?QrN=W=D4XRNI4LhqA9PDip%X>{By(mHqisIf?3C?jye?)wcr_dwCl)%j;2zM9-`GI^NY=`aCYTvRY)w&SISTpuGlo;d3-)U?o0HuZHTWRsq0{mrPLgb>m+njn> z0Z6`Uq=H6UvT;7U7IYO@Yu?h&=AM8)o zU=6HX6-p0B!12>brW;J~BC?QG+}mE96n(dTV0Wr_kNw3h_QG%9!s07bGK z(+(u?ZH0_fl8!V(pzBGWW1>;bT!g|>0V>bghKeHsBEtw`ke->CC7s|+IuPWYzQ?N^ z{HO2avi1C#8DQNQf^H~jTIzcuPmC9vEQa(Jc|F@=QtcEZM%0)Li+=@px7;M@gO~C> zdLI5NihL=X1P^1c<;=y1Nv<+b%vFeovw~AnTY*2SliQ)F%G-UdXs)#UD6q_DSXacwgWfh}^d{s-#uH?*_y_ zsgjc?8Fbqv6X=(I2AA<}egdlJdCl9RL#7-D0#=MQAXHgq`ZD+|gjYMW;CJg-n9|2(7KxN#w+Q z2Q#01v=*i@7>&&^918UgW||C%Rn}h+lhG*9A;|OBw4AE^vzjm1?B(-^-e73PD?4ah zmEL7o##BAZgfVIpEM;1cg z3_vn&la~}x%DTJYZ(=>A7ZpO+bzg*}Uncq$MqttWw}5(YoHl0Nkc-D?QZ_thf8C*4VT3@#X?^0)lT_beE$ z)#G6_Vpzx0UDpsQ-_=Wn`9$_^xPhGbo+Z{fCrRm+p0%XD5ygwds zwlL;VvekE_q}XB<>rL9?N3&?QATW+vyAq}tm<7%WLn{5b*!HTD9KxJJ0sI$9sqbx% zg{y3kk5kgHd4@dXG21>@*SRgeB+xHbg%Y%R64vK zIY>4+JfAraL;n|Vn*9ezH??_@*LyVd04@PEDHap&lJ(q=# zznmw{iaKhDHua1;9htGbCJnx1AZQ0R(w&C?>t$dJ$P9DKBP&BLH+hMa^5uLBXe$Fcv znrUaxftbke58)}s4eh;ZWOp~Bp;Mz2smd)5wyj)mB@t+Uki$hC^GA z(S0Eyqas0d74E$ub=p5STF`EHKkt@Z%(jnbJzPtp)w4{~>^JiJ$Z6Gl_)zyVUf4Aa z;rp$g=?J4qU84694$&Qp%4kvIyCNr54w487NQ^@SXGghc8%*G%n(TUlzt9=Z~cBMTx?{H zpHGhtLBgv^pJ4Om)M90s^ZO?ti&12(z|lNuBWN9v&pL2zaqc2-TUY6tUt zX?(FK7C7T);9I72G|u;YQBlVmq4?<8*cYCyB0r(d9i^w;GZz+2)P)x{iF~&)XYdd~ zg%U`_RQawa*kd3IBg;w_zK)=w+@KQuN8NYNk|x_y{r3}86-7Qz#fTpt{4jFX?vs){ zMU>(m@y%4$jO{hKf{lNSO)HQc1e;T_R?4cqH}{!o{Dvlp3rqETsA@9kO6TU6bl$rR z)LDeelk7-no`kE?Gq<#s>H4qvK;d2|ioh?qX1)Ki^q>}ND>zfMli?eO(QSDD0~J&L z*^6v6m(n*k&4arB*KV%%J(qL7!^=b9ZkPKRJ~21)g>&Aki|*-O!i4SG= zfKB3t$CUI#9Q2R&R`2|o>4TU(U%#?hvVCvfC}$QtXzl4*%+c}?MO_iVkG5k^QARVg z{o#1vRY^1CV;BPmgLx6c3KvAhQ7VqpVsCZOdX;6!&w=?GJV_^f8nBPlrjd{`MjKWtNSW-{J*eE1pkH1>&+e^ZCE zd$qIYogK5oQc!^Halzt~t!9I7M4-NWQ%1U1Pow2c`cg&P;(EkS)^7Kmt%ngE722{5XS!|Hk9X_(UOlE+xof=L zuH~;k<>d>WiWTg?wxmkR4QHf(TYLWfsZPJsvy8c$uiJAe{*S}d;79f@uQYts)t?qY zEZtqOJ?wXi>tr~`uJ1qO`CIzl4+;x2{VvZs-%q{aQ0mIycHaDEYxXxk58wKc+bS8D zUip}CuAfHt5$eKdt;x?S$M)}w;`-DVCtcJ0g;%%i9}MW#ANFN`+zX=0Ces*wdl*{c zuH&94wV&~bIlm`LILN)1u#fU%*4f9GZgW^Qd#SwGdc1L*(>Ee!7qroM)qC6k3p4B? zPwT1hTh-|4qGeL+lUqNNT+i_O=(M?ZN-e%0m*5xtZSe8A{!+D6_;Ko*+pGJ_p=5} zf`BS2vD))>Vc2WL`lWQk}BPhiG2$UMEGEFmwvBXs6UhoTtyu={DhLzj&57dL4IeI3$CxiqsoAZxizG zayR*Mvz>PEyju58nEPTaRE;A#$gup2BcYYus+COI(ZkDNE3@CJ)7UuO)*slZX*UU7 zjJ}oCN6v4>G93J?Jddh}{j~aif44gZe}P+6$0U=B=_e+W_cI=N#natHBrXe4R}j3v zQlP12SnG8Sy1S&iyStT8DQOU;yL{*Rd*AoQ`-dT17{jyA-fPV@=Ui*sV6(w}RLPdIo=*BE zZHuk5STlmVP|sv!vjJ;{Unw7kXNJ|F04C}t$(9;imXxXbmr@YY8e^BJC8}3~4bb0H z?i88bBfU4xh@@4~U7`j9|G%Y<~l{?tQPy7;=!yOvGa%ilPtKdu{hjA;0rDgEe@7eLYA+ua!@H@4UMXF3 ze=;nf(|CrSfr8Cw`_jBJLpUp-d9j4=tJuEWsGOk4lIlb*A8-*=+T6_;ymes5+>I)u zxiuk+rsZ%?H}W{$a3cAu{#E^IGw&czFE(wGpuX;n6vvU` zSjX%-X|o6yVaDm;m+mA9Wos2YsuUNn5{y7tVF*(c$@o>5?2WjYdzQQ$1AU9@TSZky z`T=UdD`(v@UF3penp_N0M*)i;Lvy~zZM)&nM`6543o&KX!~({#spBduY${5~i;GLU zamaUoK&KLU^yCB&UX*ZczfRfUr3nDUDaR$RtEPfSqwUD2Bo~g5&0A-7E7Cmg7E=n4_3opbYlH3?Jj@Ro%RT_ecfAM zmbb?inQBv3vE+(M3kr+7$jcaGiiTSREQj0^!zsd(10+!Ok;54|tQeEwgmSl8UZfxh zTMkV@14llc!+vLEEis0>)SNMt@<6wG4qDDdPOK<^pPUA`zHUTdLi=Qa6{lK}1K}BA zJlZO!!~ekf@c9j#szjCWwlFX=Ha%dd``QPn_f-)XeuIFX)OIBF4BlSUBoRH;=A{BR zER7{*Vle~TnNugy&c%*9HKm274#uTEw?gmViKGF#TLun!UhQB&IRj>+(BVhsl-CQd z`kgh;O*TI^AM_CiK0T}(-Q{anXOD~8<@a!@U*FdIiY*w~ZLx1<{gpgZJxuYu=3o7q zLFg!aU*U3ZeRXX$i<%LcjepCl6!^XG#oNhqJLk%wHoalSyccIu@2}m}>7kDv=Aoq% zpVHPbM(%3gQoXm6jMED$j8^JLT-_JTYKrT*ti#mNb##%H;1DE~Xr@|U_7=*t>V**I z=V;Vzp!{4^mi9&?N!^Q~6^t;c=ItRS2ou-%0-4 zqs~0)ZtsbR8j3^+?S#=pg6W4KF^H(xoF%%6S?1%@J+W$4x~ZCsU9Difg!(Zjlu88| zw^VqmVwEqtR2lH%B*j6Gz>U1{>fZWh%o^WJR-5K=qyBqeaK#9WkoTpGIjZq2B;_pu z+MFSd`c+Pj(w@0iRF0-19GaX|+j1&S9^=IbqQt`}c0Z(|s8T0@Dvk zP%0<|TBzk_0VG~28_==Y@$e}iC%4p5+Uj5Fdx#IOK5VO^M?Le%- zK`;LFK-ROWD`&{aOYM$KL{pTlIL!k2!yH##M_SqlGtSy~+0EC&t8L%eUaeYtn#Ze7 zL^19U2J|P+;C!%#<_?Gw%caTMG`EqIGqU(~@tssS&e;udl%Zm!^A4Vykn&xWnuy`~ zZjhyO-0ucI4DK2!#STx6kW3U-r6d^yAl4*i|(xWA>!~K@HkH=OjQq+s7@EH z)N(~p{z*+L7=b_rGI6Cp7e||lMbrK8)t(@g4vawvv&LiCHJl>exHn}{>n-B?Ije23 z49>H?hLV{DDYSSpyC5?5kLiM~&taThKnyOd@54Xnd(XLiuGpWo>Qx+A`z<$rS~)@d z?LXwpL7&1?^b{3@79T;d6UD7w*;Gw;56&o>6rO<|H#byHF=s%OZd=**mzMo11>vg*x$~8O9m#rlxA;(o%$8%bCq~hj|PiEQX z+UI(|76hDpqQVQf{XB7?5^JG~viRQNa?g!@R-PB6(RY{mCsVQ8`R)WKWA!h!Mw^45 zZ>O7`(eAzd$@#`yq!tm`sYC8lr^mf~yv)dOgUtHA-v6hyVvCo4!D&&S$HQ zAR`U?@I~a;Wym=1a@(igErGy7my_JQ(6cYcV_Cy)VnzWmzb-`z(^7nXE+|fy=oXvC z*)48;L)hVybY-#SXb%k}T-Oe9akwD0r^O9q1ZCno(DH!I1fh*9*)hLIXkrpx|I2UC zy)$k18jRP1-`bAwM%wNr$IqQ3i&W4@b)y_&r|=02{fcJl7%9GhtYdLkSn{1uD*ciy z85L$ZTP5hU7lrR&%b;p9nYQgakYo_dr$0~?IVpt0XsT$cu`WgL=#pT2aJ|hGCn?gw zc!O4+hZPvup`(=!ZYC*q`(+muq@xQ{;!YUyeUO@ch)8M#>$6+T4JiGRV5(Q(gK&5A z-;%jb#CXI4Dt2P!)mG{iG6_8{I){R4c7;k~pr?sl2!M|jPb7p;-g|k_3pJt|Tu_`? z%}7(i7rRgKzT?E>3B0FyzkiFY3VOfDlMdYWrEIJV6u65DF9~?h?IEqNO1GZ^1o1oh zeZ-}oGa*;7JMrzo73zXwehrqz}_hmZh}lf`vkxGe}-DM&x9I5zWLh4+3zX z7IJh{aOkRScbrK_iRRRzdosfncFYZv`I7>+)gKGgqf;gAJ7O9@iKGptCD*)otkEgZ z^4HDuA372_VShRE>;rJc%$rsS97(lX7(+>I5dT%#$z{;QdSt$2^NB#o7E6JkLoz8vR$$u0L~yJa*X$ls8%E6F8T>lAjy_2pQcElcFgWr?)(N{>+7S zcJhpbnMp2RZaW`EWhQrkH+g0j3i=q+b#|sQRynoPKtHLfGfTtI=Ue1o)17x8nG=78 zuGNmSHpQ>_Ic`1wOOJQ2_p9da(eYfCZ0%D!vDk*A{&UY2MT3%b!jGJc#_S)$B*%Y! zEdxqo(zKm#ZVj?G%QNQ->+CNG459-Y@cz=3B2ne#xG9 zBJ=L{-*rDsQJWwpr%4Irr2@X_&HQn;_`{F)O}JByHqwh0WP<0^?~*EbODtsW8d4n@ zXXw457{U4|Oys^#{6#1SBKL*xlLMnpPZKN}19h6vglI za_=0fhzY${Rl`zZ_mBvbWUg)Ge$Vd;)u(n=7%H=BKl338EheV>HhJ??NeAK5wH#$hq|c&)%R9F%>X{kqkb@GfidRmvj~ zAV`y-WKgcPU21Cr(cb>l0V-Np0)~Z9prUn0q0%MoQU@UoNB}FW^KXT@8G%pJf;DSl z2f(9nP;seKNbx5EAPRCQCa{Hhka~nnJvErB4k}yMWMc=}I6#$9)ww9l9N8*U9l_S- zXUcV|BZ9g*eth_ejXuAj zs3I(@Lijv7q``H*DP3(D#*@6mIEua;S#627cO|9$va0&Brn$h*2jmP3E-)`Juh6U+ z+Jfu+DN6FDD=X8ZBA@Vq??}0Kapq_)kf+Xn@&5K|ZV)58eU8^@>kHY(xq!LVER)NW z5`v@{*L$w~2{a?!5$5n2D-|$-^07V~{2qcQ7N{&NIDHXi&On|ii6P6x>QE$=ef?i{ zjWkqI>k^`;E-LA#2rtcH)!-dameW&43xC)%tw^*?e#GStw?thHe-aigoX0V_G1{dB zNSBQNBeGa`L*^jX=a^V5k?C^ER~4N z2NQuXQQ%~usr`HJP>fvo3|GK=5A7u3BpU%zR8gimD@ImA4gFOTG$&ayO;RGKOgiSU zhA5|48U~h?3*Vtpb}50YNxDR0EcRm%_)vnC{Hn8$4Xl2nyrK5JMyK8RBlLfy8U4-$4!Cj(VYIm=At6{ej8aYe2pUo3F=EsTW(pi846&BeIV z5t4gSa=p;z+epCbnnD6w-os>`T*?P2ONJDz>}|y6z3M?t4rPgEXYZ7nY^9CP?$~5L zaa8T-g1BY(GA_)p6)%G9d1`q=BIFCXlB`$WTnhK4dF3-G4lN$MQI`l7<}a7D+9}4} z0Lvq5s;Gu)>H6wy5C zYPu>h1o-5aI{n&QuDzbR&wT9SBp);C1Ft0$`Z>%e7;*{(7|*^WXOU)1XFFaIAFQ2i zSwFjXd+(a>Ud;=V8G5fc(MDrqi7TwAS%(ra&okt7ij?Z5(2%HDGXcR0m4Pw@&wTqZ z*Ok97E#d`<5{0>@cpm?#vbZaM=DGb%4I7k`I+y>>0 z+;G>VB8sV^C9aj|p$YKIfYs$lL5Vu9%S%1)g%9*C761b~dp$_%V(+@L{ z4(6nw5O>-nM>FcR7f@zCr$K`SrPr6%n7H|T>pjjE*MC1mQiF6Hgsi@bP?Bq|K%uAMh?cMU5L3M1qip9Wa z@$FEJFq*$SO;z#O-{BN|zO`Ska^Cb`zk$hgH+bKvOb1Ns&EOC=bq86<3k|BX89bl^M;xDCGR7@tc{ z&nnmJBVdnM!9Q!i%NM7o1)zvdI&vZ<1XF?}kvxBXFa~k`qa^WsL==C?S}fl*93+VG ze9CuR_czCWo7=l78Ze!A{f#6Rt`K*Ay&^a-R$7w&bJZn>WD>V_D*gmipr~Q|b z`QW2{(;$qffmR>l0V{YDhHcE$8Q;6BP9GH24P-$yjepS)lhF;I#wl2tf|PNy1)7H|!ft>23f`rG51PtI?QcyZ!v?(Lr-|OwIl0 z^2&Kpe?jB@((l|45(X7G@L}k-Wje+0kRJdoA!htoS(1u^lwl({Y+~R~E>QO(9WiS1 zLK%!sW4fE5^A|Fe%5$byfQsh;9iiwTzQHQxN(LI&U>CR#H~v>j+pxxtgwQ1r1mikF z6o9S9eZMz%mvM0V*6wZJ+ViPMC>z3|%ea zDAVhM0A&Pmhl5-GKSFnEJ&2Z=&shscT^(*>)S;kQ7!En>n?x#4Lr+aoZrTh&xPEYa zJ~{#eE{-wgGm!tHq~~7V;W;2+Nh3g;BXNgjhMEwzsQ5#oQ>fPzrZobV4Wl+msH}-5 zR(cO%7$zxH?13pJIZb7(IMOzBPY`)1HH=Wg0k66#=FEazlx)m$7;F1TdW-NIEHnu) zMk<>m8fW75JL44d+~7lo)ouT9Q%p)?u{m;J4Pq@|3S=??pv8$)BorhR%pvy=XicGE zTZD{TAROmPTMH(q2;a+mA)># zvX9il5HLRUu-X@wbRiU}5MJ|I7F;M%SA>H6dr+JuQbrz0#2iZ`NO+Ay&8B8+r||6< zB+oAnvn{s8s9Ai``dkU~b*Gk+`Aw#Ng>6F^<)BXcA3Yxdfn9am++?gK1Zne)F;x3w z*(sCHyZK;mgzzL_3p^(0w-jMHCAd?k7t_*MTXIjJsdHus*|bNoV{iZJg`nzI)$#@I8UZ=r!zTbl^=n&*$p| z;v7nfug*Amz9QRj5p)pTPsG!(F3}ZDtwhPxo|trmv%fC!M>bMXhYSTM|rCifYJQYb-}Ze=(EPAD0QM*4JsSw35Iwb?KnQG}>UhrnE!p;lfyMN$xZhFqD6 z^^FR6o27PqRmbO$L~q|sN1&yr)Zx`u#}^lu=b7A-fW+^CG`Vq|_l?vMJc}D24-Q?C zgs3Q9ku-5CO@A`nERsuNzOQesA|j_3l;u4QGGnBx5SJnf&dFTa7J*lylDZDyj8|4R zST(OYCcN|pDyJ9)scidoWV8$j$ne;5qQKpCBg!Wl7jxM1)5Fm5r`J{eJNzv5q|* zCGEH49P#3uAg|BOHv8ZEv6az6hnC1-rye6~*J5xqJ~y=9f8K_>3ZfQ2P(> zlBNM4rN?U(!;T->e_sON;C8kg51d|$3p48uK`M(L`>cjO%DX&A$a(+tU@^@L&d44u z<{qW)by0{XzRS7$!2N;5PTyBHA*46B#^v1bq zWwOQYel2xN(mGrqJKqcuhR~HwB=F{5U#TO;DC-tU*VcEPkc6mZXs20?BCOHe2E@(8 z-xMdS4-mO(D-|8oE%cpbb_4DR-Kwn`9Q)Ilnb`O4dpAJqOWYgPZwv*{L#39&?#1Jl z2s2$&qi!!IB~LP|-+lXE&O6zQgE-UP9U>Tx^Qa? z;=d32POHI;LrS40B(-VX6QxO>_*jsg8WS9B93NRE`hEeT<-|m4sP77gD={ZL5}MZd zE+A7*w`v-|@~iZqZNnV|!P13fQ?K-sqtPYSnHCDdr=N{QTk)ai+4x^dKTRYcDZwpy zu&5+KS-3;8v9S1F)L7ntm+8NnfyHY`VngxWr{hgDWwVcgK;eH-+Z<1IcVNlJeMWNZ z=htxcGGANI%3HT_J0Z9+Z((itHaPcd`&=7ocDz(AVtLLX8P6v}2SdMa{2Xii=Q<}V z+j9?*@xxEmh+WxypZEehe(+zk#=OqY=iFc{J)Zyo9Ye_1BkArWd1^#Iv9Ys3_|tWJ zwN?MC!^yQ#8{0#mWt-zhhyU74GQOwdR*O{$boj4EHiXZ@<_OufYDTk&R@qx*j$3_} zc2x$#ymvnlnNKJdw|Oz&1`FRv)0DPJmKNoWpTwd4(95U?-e6@sSPt0gCY7_PRMV>2 zKaRqdwwEQx`u`ECO=&uxfRD9m&C;Vj$h{W~dMlmIzdtGK|Mco&rO z9h4J1rPM1$xmuME^e>v^UU9YGUtW+at9iln__>Kc`bH7=qx_bx z{qN0LXnx&~F!GRnK@(8^G$U)!hgJmKKMlJdk}YKpHaT&Z(<)L%_iaPne?HM&qKq#v zmKc*?J7k=~>MVDmV^1air@i&7q^6iADSu*u+wU-rjdyhoR{kVs9PbH-yh!GXyq1fu=f}waQgAfd}+$ zMQ4f|6@H(n)A{rFa)OgfiGJ<~^KxEniPa&y0tLM+bpwLJp@aav6J0#eh+^9lh3lAT z^WFv8>JdQ8)}+8a+f=WlqXrbqQg`sy`945(LB`hb{$#WZLAlP0P(ok^8gUAqCmJNU zLq#9yj*GItAT@)Yidu1^OL-Gmv{KOGUsp_$R7KBn?2iV<)@UM-*1%-^nism?1L{Ev7);!WM~@W zz+W9AyRkjlwY5t9FybuKvmJW~P!lW|-1anzzq$iipK$u07E7-&#>x1e&#)Dpx#X+6 z8phkWwZ5hoL681h~jv z@Cjx4p{+T?ITysdD^aCL$QfG6EIi47rNBAYiu_WQi9|D~FH()9K%p)}Ll_A@EaoI< zs*Ihgpx(76WP&cB;i0j}r4-u9i#TWmrhf07B^_{Tq%iOQRQakBC@Ir}x6LUy$-jaJ z$|&-#eXEbRr07CKv(Pr=z>F;wgUho6sO)VgZrsK?$V$%pW zTP1p8k(YWgPEpEHT9}Mt^HPL8I|=xisW3dnrmG1#KNTYq)=pWBk*DT=W%g%ES*BHJ zt552CkOvgc3FAso-;2ZFuE|YorGp+eb4#aq4^5y0&|C6W+C&-(Ftn502sQ*?awGP3 zBv^QqHb`s@c&w7Hm6!S5KUO?k6di<5vnosCG>T&2FTvZs`=yCrQS^_Q|3Y=#@RJ9a zRy{qk&%LA-TJ$E~%hx<}gql`~lfCTFI*P*i)g5B$sK8}|ozAX+y>7BpPN9F9j!Np$ z(>5w9L<8c%6wI%#&qfLEe`N}5_S|r%7WyF0ztASgA=%KHqb}+!a=*+Uw>+r6FI#!> z98`-1TVUbCw|y}>DkFZ6od%Q5THwF(a7mn4N3_~^hN13HE<9dR>M{%U;c94mv+QiWK|~;Rb}XnpYmH+F+=l#iW>yQJ z%izMqaEPWUV1Dp$UH!P9se79p`no#{7~ceYEly@WYjAbn^vm+#_qMp-ix~#o(m3q( ze{OgD&fVpIhw9tb%h~L6#%^nj@VE$%hL7V@s-2l(kMGVVd zu7|NtS!rv0vq1>7?U_;}^G}$03@!WpRNQZgbPx z>1@4nDe&|Rcj;pqmqr%(BqQ&NP=`Q~P{j#9`Pj^V<6{5qdr-s-ix6U!U< zdH9^v5ng@Ik0-YJ_=oY1)WIg@W$^lx)Omi1&jYn+Cv37Vqh9fB^Zsyrf0O%bZ?;Th zfWnF4W{>~V=jcDaA02-iOuC0gJ)piI-Z^A*Z!-V+&k}g-x-qX)uLAJ|k}m&knml0K z$w$>IQ1{z{#;TUOxEvMh>$B;#p*4v8O!u*W6_xZmW=4vN7G~>e!4%&o$M0>U&njDk zH-~GV+j~SUeHJQSlWu#VyDxY1HShCJ9n@Y!T>GF50FF5KNv*j(;)>#EhuGtqj@gyr2JOwqZlM`nvj4J{^gbS`AEr*aP%z_|ma`D@cIu^ndU=SnF?M&Ql?Y0 z>c~sN42;v%sUpVD4k5nbEA@t++*Yfy4Vop11&XZ;f2AYxnNPY(KQq5t#=yMOuw5S_!f+V|Mj+R)E_aP+zy{MGF;J5A>HRFFTl3x1kjTb78P{6USmX3d^|+Ju3=?S(Z#Lto(>1o# z{Knskg7(i(1v~iJ_Z{S}&&R#pe+ttq3@u6=l^4q-rUD_C%AMgI= z*>ba?@ly2t-QouE;;Wz0)zgd5lAZsZ|JBssSjpE7G%+oYET4=Ss&46Hfu}toE3|fT z!_NyV7*}IZ_AecG-d8WfwPYV!@5IQDl9rd+W`~nr|JF{&IsRC#Cq_iS?%R4i*yEb^ z#-Q|l5-$O?le@VDy0gD3H!*zx9;u>WYs$Gvw?UtvcvJ{jb92bSM0b$+jJ`I=DuMDq8< zFutQ8Iinl@@yY0bO-lF}eWa)TXAj(%TJL&}O2Vm!6co2a0m2<-z z+A7qBssw3>SWCFmqa7^??1nwSnHeACOBFsB&*V<(Jn8|L!XJ0P9@*-na+QF&xf!S$g)TUadUP7$~_c|B-;cAveojC1; z^m)wnlp0bNl>}om&t8*J9_;(wA|DtHS(Kz<(%W>joI`ZsmRj&Btjo`vh#NDbNgE5iJt21M#JF*O-XigeBSXu@;4b zyR>>j+qJ?o9%ug4&r3&kcqNh!Qvwodo`|zLD*wq4%6_yhrOMS2i|=Dy9h-}X0>$A&)7%;;+^l*YD6hFVm!DO z--OA^xI;gxx#lORXE#!|WKH}UPM)NQA1GC`i2#IZBz&uUrHtX<93?7Winwv~Ma&at zAw>O<6(Nm$8DWd<+bcim@RU2B6lq-FUIK574wn@;EV(PD`3~DeG1_WwGCkLo< zbrBzpb1^iln_T&!lJlrFs!)zOR}$i-%Lef5i|5f7hg+HQoAS8vCzv2Uyp&o(yyzlR z5uG*1r-Z@H?j->cJ_5~ye^BjeyAYxlDR@E!f0BGeJ(D?!M$-ec#()otzV^4CqB zNvfAu=HQw=t^QMzOovTDA-?YVm)qXCZbaW-oU=J}3?5m#2}E>#tec}5mBj)F#t-;oIuD=pMDUBh8gIEt9Kwr_C)(K!eqTI|FJa~8xm zKN>VaDJDUgX!wnB{3#1{^AIyl!vP&Fa0m93x`loutNkX1Mviurw;0$0fw3)iC#dC0 z2SlfDO$sk@^`Gwvd`X78dd)@M5grI2fd@au)FQ0F1kPs8%2HO52$#)>0r;&}O$%58 z#P;yX@FM;OJT960rDv(}+Fd~e3Tu3zdSQOIU;6)wmsLgAHY9~yd4>6(FAzF*>?3e| zQTor?ISD6tTt_+B@h40fpnlr$iGI`@oA!MmJPR#OpxyI< z=fgU$z3;i1uSNU$!`B~2tFs=slk&~g#Z0#hQgO4esy_VI7&M2_@**Tmrucj<^5JjI zTz+k1&WFM57XP_3do(BzRrtLy45(+EM+Xo_V7o;zy_6JPxAl|vK(X3_FnMJp4hpRR zXc?+yPg`xq2e2Q z%igu|)BJN#_Hiyi2Ncq5_5)U2a%%x)lk)SYfG6w9(vXGAZ$f|8 z@Z$q0tDGm64)&3eGW-V9vv1BFo#6fcb~w`cUY@7;$6sM&7X;k7FD7G142 z6@hdiX1iyfVvA!^OqOUXiQtRlA`74b)owbNbbFEhS z!j`pF?>>%TA1P0U-!>}5@A45RY|4B{Vi$3z9oQV^O303!pLy{!J$3%Z8H8e*N(+m1Q%6}2BMCR#C&)INSEJ^RY*KQ*Nfc+P7h=Ld(Xx=BO7mZ+ zD$7Wl9)&}SSHDGm^DF@3A(z;dmSCb=KgA|8)9(ky5=+tkojNY>MS7!<;@|)ihdVuU z;ZHGWRt_N*!XhkJfeyR8*o7d!byf*UKu5D^8LHsco1@G^v48kxQ=w!Hs3<`1jGKK( zIoce$Fp(+JI(!#kDhOJL$p|(G0?KOEfd<-Wu8nvsqR=N4d9!cL2HXcmtMV)#nvhbv zAbaf#!H*1x?VB6YEm86GDoUj1MG$Mw%-^zh_8dJI{QcG{LGVe;fuz?KFGQR`ZA0Vd zCQob^k!%8=sPLE0JCnoBeGAL_ZJ^ucnjp@Sji9%wBj>z~o6Yl&_0`Pq!L}3g<1b<1 z)l89~Li`^hyqL2x#rS}Oa5#m{2WXjFL=B_`so>%FR=o8uD}Hx`t1DvOG6e~CJIaK; z=hf;X&V^b)mw?YU##>2Q8=_wFSY97oTweadhC#3N(l`Zl=OsufaDA!vElTxeK^vuP zfyKX~3|_~xtdy_Nv^x`RVkmFq(dZEYdgS|ZDxj5|<3cUKjl_NAPYxQjvCg0TkG~t9 z`Td=@>IYJaH!(~`(5i0Q8+M%w&JpuWSae!cTkH*ufm-L2hQ4IWtQ{S+8PxiBAcJZy~ZZ;$>_Lme=$3^KSt&yqu1p zRBF9VoUX1juBJapsA=%2fG@79cuhG$jrqK$$G%tHM_*WxitM`XFt`&6j(VQ9+5A3c zW~sRheBWMaB#w6yv)SnT(i7P2v3&^i98CYZdE{Eysax=Hd=0#6su+8{$=a{-z8t$G z5Y^6QoiMFOw@~pstwV|7)tH;({S>g&>HJG8p9Ax+h68y3%^oLPrK~Bmggn|C!NM#E zTP&ZxL?s^EcuolGp;a!wQY3>7l_!h^$Y}cX>ppKeQ3RSmfNlR?? zU4U~fFISXUBfPjS_pQ*3)*VvIH(~0_kJw}HSn5~-suzRpbh#Af18nPPj-m2{(G$03 zjBH;vN@3zJaxrd|g$8IRL;qX2+N4Gg%I87Y<^@$#8o=DKfDR}pw%_O#A}}(rEh`k? zG+L1jTc5`(M~@`-B%7Tzv^Mx=b?~PPC(`ZiQ|60L``q2@kN?{P5c^*b zfL2PP<+dofr$%qVNxWK~5-Yl$s!!d+rd3{w)s5AxKOgt*v&UIRC73&4NlhxP!WxVIw%nEnbFx7XG z$TXb(Rwxw`HINe2qJ?vOl(syARLYp42ciRaK3hvtZIZeO*tda>KMiAf@C8arEYD<} zq5VPxngFyk(p{0F3j_EMphIksxzU}dPV7beKIggpb$w~8H{_bN{ta6g&UqN9a zm~thhTnj6-8Se7OXiTJd6!GF5o@E}W#d+ewAQZV+UL3q*=m<;h$d5ACFNuw;^}b7_ zpz*0AnHZ}=kCS)psshmNRHIxH(d3b)s9}!M7ij2FR`i4xI<)K;Xohpdf5IIDb_h9f z8r8w))Z$9Q#_4$GDjBNFK$UJPC^t1Y=$=}IpzFni(rl8xB4ujHgZ24JZP}RsVpwX_ zt>I61U>o?LnIcb%6ZK|QR|B2=?H5V{&Umak70`vLwb?P>7Mu_!*3m!C9FA8Kj}9i6 zJr4-%rld4=kUMubfy6mJdxys+^WU9I zGRqAusU*_&LsAl+VwXQku`w>TSh%|cYE?Na=j~L4_!cQ~Z%&NNsqK2`Xa)n#cm;H^gP+$fMD@7skk}izE zY#;0T;RUom&{+{NL;WQYi!x6CwiBf*RR*X)5xV~4-|KyqjLOErouQH_} zLK87OiR+i`#F#}i0rl^K-1mfqj)MLu^i1Aj+;A~3jr&lAlKvzxO|VEX#NL55t8g!g zpF~#~;5x&pd~bql!T`5Ozl{N$X{qXi=Z};`WzP^J=VSD%tXTZ6_sest*gwzIPLhBT zCC;0>sG<6VB2wap7FwEwf!P4yC?jIdQb2_~ZfhH&+Y;7MDlXxlaDqOW%zxb0mP?7zJrnZUdtQ6dPox1K!P+yrXLQ zIB^cFqzB^Se=8{w7rH{y^v^7Mkz2~+CERLLTk$+tJw>MfRWTVF+TB_xL)t474-j{9q%CeAh5vT{mYMdaqTh=R4P(bw%-aptv&T;cRxv99o&rV z`)1#yH|}NKB5JMCZQ8sz+Y*}neMD=Vn!Y}HYO9^@Q0?6}eEHi&=D7Aav$OAd%2A&e ze%Af{wy3^Jp*x-{a&7!HUCT2*-$vhX>rWj{<0w9=d}KH$Dk?|ch(dIDVQ;qn4$Y`U z4LL9rEM%cVjVa8F8U)673Z$Cn6f`K5WTFPvm_Bbc&jWFEi!(l1mBM;f5YO|iDg{KV zQVvT}L=P7?cF_WrNl0lhhACvxlUhxT6w#Mw-HNMhmw`+-Wwnd{=42fX)5qxQB7tfN z*ox`Z?Ga;8$SK-krCk77a+~Q&Q(_sUhKzI#(7?0v^r{dV;mgvfou#I@xL6wClbL86 z1158*fb39Vq*V$?=!sSo50F^mii=nQMeb5c`)2jpw>SpNe;d#wL8ILeO%hHT5zr(9 z$l{87Q(Ak?28aZI(|@C(a%*X$gA?ZSwg0{K>JU)ByZ(Fgc5tnKhIeGE@`fx?_kwH& zVF&S@3KL*a4(%puO?QN@Oaqq0qkyzEJqAYE&iYRxv`D?s2|CI$jV(=`s(9Mxm1H%j zR%}3|Ey0rpuFK8Qjb)@Ru97fqqqdZ%y}2^Bf1^c#WynaKB=<#IObBrQC$dBXPfy>( z=Z)5QZK@#ZBP@Amnz~hE&_JwS-VXB!RZ&BC$2vIgGXpOC&dR_K3MwB%&y}~J>gjc` zZF3>d$BhZ7wX{)m zwDnX?mkGKk-^z6wRfg0G-C2ceEnPj|5{^S^rLYcJY8|w>_w^U->LY4PkWRx%=hT%Z z?p?yBfipX~gu@=oPwFPe0}3978|BeF$S#&7XFAte7lkXFZq>-il)_8@HxVGHq!1qd zoJ)Y<#IoWIKY+}Tsr)Y!oGHG6b68LZYms+|H)nV?UYvTTJePMHHQ&tg0mzX1(`inQ z$ok`RfbUk)*oIzSd;R>&v-s}7%fWVU-q+PNzRj-%>P@iHjQcfHxwX~!JXUQV@@+P| zVv*SIQVo9AX1jSRaf|kT7xgi7ywyLs#CRMY>-8id<|zWbd$g)nwQ*W`zBS+7R{EnP z)&}MF>IS#AK-v;)tz$b)3)xwEgo@YoP}^-gBJAzk^I|!5an|Xf@mu1^I~XR#k-Gtu2mToWp+hQA z9Vw?e-X$A95xIuyHO@ghOJ;|`@U>V846szRVi%pg8(>-ZpA|>!SlnUt;xkg>4-Q@f z5Dew%lh9ziiNM=H3cs&rtuau3cAElAKLTs6E(TYQul{%@z@~&OjyX)@yolkoI&Ia# z=x;SPW6WF}o%Nr+g&+DPJ0Iw9IygQ{mXL+fP3P9q%6K?h=tHKoGa)MS@>6klX8Rz^ z?6xsiFETqLfv-L=!I+fBH{=!;`%3OR4wKKLNxs@`7(jaV=2IRBm@7dVbK?FPW@nhp zNEv)XfVPn8(a{n?v7!C9VwMEOs~4eF&}(u2_14Bj?2-_D zqljX(%|ntq3A<3V)IpBo!MMuBqM({uy0QQW zZ`3^h!W$kR%g36`N5l`Yvz=dKs;K0^bA9WX{&&n^*P3)}1!`(S9GPp{5cG&6PGBZ0nA1o<MhsRugd$+r8 z$>aKbMuDTxZ*#eZ$xVeUEVopWwOQH9;lh4OS3a^zbTcd|EPLs&iQ&}K!GZMDNO9CA zB;ymg1OrAixV9o(DbshrYKzj3WVmE3MT#@6HkQ{P;kA?}EJY%RmyyKuVV>gMn7|cc zP;u6&(nz87gx5AWUW^!s`4dp21qiL0+0!=Ki$cZ%my3rO#&db^6OH zsdpl75Qg|cath*J4p}yx+h%@WLJFq6}>ROg)VccN@*|@tq1PShL zAp{HV!Cis{cX!v|PLSXZ!68VnAVGs$fB@gxoO|E7_k8#LeK6Pz_E_Dpit6sFS+nM< zLm;nxy!h3+^DAH{_GEl~LNmYFbBizb5@&qW=HguIY4&mZfpMokxJR#=s+0wK*5gKc zx3_9=Ay@OYwDq8E&eQF_aWz3yGzwZOt&doc32QM-;G??i7|k0)W+!&=CsMMn{+a^k z$@4<^I|+>JmSVWL&R=Ncs0f`&7}D7}b zMPO+kpnONM&zDRLHqi!JEMi+5AuSd`DypGJr7}Ji=xI0%i?vcz zB}slqv?<{I*N#~6l%dTiy$yl~?g7-A#E5nPi_jXFR!~6OT&jc1cRo z(+*%G4~6~Rye=$kd$|YVNc;$bp*ns&1t^3dU3C#epgO2PEPuS1^A+D=^p)b4x*C&M{$9$|>V<}V6a=x5(t_n-evXO802dek zcv5=zz!YvnBe|u*$bsm9TRuzN>-FHzNtGBnzd8LS-(`!LbKBISqx#khU_2A$`r^wS zA9iT4!tMPeq6j2T!B2olLjXyhY@%vdB+5&EOy1I_O~sG89REgx1IFV9vH}_8Ak57u z1-T<&F-AdpbAedHVr8A+=Iv;U`71YbWz~aRs*tv%OU%316l$qSr1eO>h^{PG#F(yf zM-+{~Lo!`SzDoprlc0pG4?+6&RJBwv8AQUSO{$rEj%FZKT#O;Kmno=CYM(+anbi0W zI-Fkv!xl;(Qlx4losmFrZ3U}ytP=3GX+t-Fono@p@juG%QtFVi`4NB8roHS2p-Mpt z0i3v=R3w`FwWdw7uqqKhhc4Cdogj<@Sx}F$mL$(Zb&`=xM*ESh{tL?S?x5`VRg8%K zxK>z7-vwdj!v}|BuG}uhl3VW$C`5_ov>5Py1pWTE1WPReXRv8<8nAD}u+VS^2?RhN zJ)e!bzyC`d*o*bfysCs1!-b+#YLP9Dj^%e z7N}~Kh@t~B(XbQ<`C*`(Gbi3!5FkIqC?`iC{~#zEd_j2SOs@syK^ti;SYJM9a>}gy zIz_6Mvxs?D_ojzypJHfsh>OBxL<5a+5@gSPhxuGaz<2g0*E+2)EVCeY!NH}Mix^7ric|5p>>v7%*w-kkTO{O#&t z-^7KUV8~u)6z1I0)Uqhem_pa0pNJ5JxfR!K+Ez{(6uap zWn@#IEmMpA8S8DCrG$xYYuXzdDuAa7+5&$Lz_qnu8l9FEjiHTbp#G^wxzV^*pJlg# zy8LI`l5&DZWizP`Zbh;=qP7tebr@%hCI>aOF#cQruWESHzho!ApBqtJ%@&1GcGD@3fn$Gxa=f|0u^nqRCIb%5)E6ChBST(#b8@J3%bX zryXMP1acXFK#ou>CZSL)yIb^M3FS3T$CstfysPENDiz;7;7#FM+z zZTfh@PRo?h=mIqs#2@Ea)(8w_yG&KyhAq@ICR4`D?WT%ZvV^Q82^@eNRmkcyi8(K2 zBVjH|tRh9fo`oNtc?psuNuV~FqUtnmB;ak-XTfzMrYh1W+Tn|rJnIaE7uz-Oz-^^P zCNcHd3?EC*sb9>E=g+^Pk}DY+9n5c#=m_ZL5elRA^Mgnn(pBtl&T?bleb%5i)VFx9 z`B&py6aqXm&UnrkB~TmMm1Dw7ZtTr?LmuZKrV#xO=|D6wHp2H+qX-VzR;VfGeDEb- zfPNOmi5zDTAxK!pfwMGSgBJfK8~$Iw9YA(fDx!p5$bf8eeBsw85DZfpf<+&6I(dzD zY3qbA@-XyA8XR;qyS0X-ZsKkU)_F`Srm<6C^u z8MogDzvEq|ad%eUi44W9yEm;O5m#2%#F1uO8R=SwKVyHe#@PPM3{fn5Kzh+DD8xl?I{7M975F98&%dl|$42 zS1N~*_W!AJDB`1H{qm`+vGG2bB@-a>bu=Bbj>YU}e-Y|5x2}1TOp&UYkd#Lsx5-K; zP2l>Iod0%vj7fsQTk6avvyT^%NeDR%Gn6>602S9wf?mm3_n#;V`_l%8{YxkRRTa!hfi=<~V; zDNDv`&hw@N&G@;jm&>|VQLjeox=q?}*{**n zbMKpHVH#_=Gng;*tCailFO|da5q!OWs2uKuhP2#-aIbhPFt78qnJwd9$oL>GdOuS=eU$0_U>cUpDtrumr!#lr z9vquC^KU+*r#e+|=UL0Es4J!A)!w_aE5dy5<>T92y_-ILsfXs{0>8CRi?$P@KJ+Vhhvy!WQAPbh zN%ruLe9z@+dme^`EmkMvN{yf02r&-X^m9B5?S85z0|_PqYX7Rqt8&$5Xjs90-UVnl zS5nb4;o{ld`f~_CzHxl-v-Wi3`6RaUz`(-T7@KhMScIF5)B3RUkgcQ9&y^3=+H&zY zr&a~D()XiPk#hci<{cO{MWq$t;^;JC^fL>^}yxmxNN#hAR0=*|+z|H#ox$LRbNnLl3S z?Tt`X_aE_jNdh0V%9hf1;kO$J*k!=|{&4yWI5huJpP63YHi(%X^#e+GV*J?F*SV?n zCbAVVR)?-zldycU9hO`hQAf<|OQhuPjFUitm5(h%RYQ%aJs9 zTwmaQCfgn(juhf$>5iom+`G5=6K!fvY|>Sr@e3fQhBp0NVmQ^6R#Trwt{!Cqd^%vDc%SLAJ- zRS{Hae7xR}JZQY!RPnYLYM3=n>#aLt_jt@A+HWFlBg5_K z89v_W@*D}p{cTfZNOJW>TB-1ezTUyiO$`1fa%HSQ=|Cjkdj8i4@|#eNw*Zsj48R@% zU}(iGf+o0as@zqzQlc~xqK~{V%;OEpXVnHDB)rLIO{wGC%AlsHj~CD@Rq#S`P3O7i zd)-EMD%6dGuwD8$&;8uIZ&bs@Hny{Vvh-G z<2FRFiK#IN1mcpY^2z89Of$H1($-^i3(^O9=JHCoSV3m6|EFwQoYBeQ`W?{cODQe_ zI=IdszNuEd@38Q31;hvRke8A%@X#xZ2X+JsV2#PKjH zmST@fFV!2gSRbVi2p&gW1W0TNM+@i)5(M-y%!md`D6lfAbcjB=7OD8H%RUgT%Z~fJ zR39qSDUF`f_9Sz?e!nRF@Jm#P4);&$9zV(<|7vTBUqMb^S`&Qk1L567jTLHVL25B^ zLQoHM_J^a9!wCma$5fKRuAG=S7=gIvqW@m{5$1M^gZOAG>X5Cju>tgXc6i|B>KOBbu%@5_J1T&(8IP)VYKSp4^H9N`c6 zAsDA#qWCjFL^PtcDwY7}-F)+sx|WAQC$83oKqs-6*r8G`m>YQ^ka*~NiOMI~D|RU7 z$SW2EkPWfLpE%=hJal|8Pre=^)79SmekSqW+-JsnhNRsek2V&aaInPw*W6u7BnLr5Dd7eT@ z79gC)zNhZ@k_C+xT;^5EgRHHM)Jl@Dkp@B(K=LBMA*D%ibzh2oS4EPqH8$h_UOj1% zTtgMFS(C>lYehaByJlb&BzReiiJf|L~slAcMsOFbijxI}m1p@JBHK70UzRg@hzDtpwvG zdq=ql^>PWoYpA8HziCqp#YQvt@c2i|RtYqcKe;HV3+n!%Df|8AYLrPZlk__^uPt%H z><2G;9HJMiX8lns_+6YA2ZP(ZUDQ{TowePZxxw(Q4y`^xO||5{23oboI32Y=riRf- zQ{>#955a<&^H&6A@!=TzG47l&gco?jp)ngDq&pKmElo!Yq)XtqIbSnq_rA!F^ZB?z zm_DOI#l>*~!@%`Be59ukIQ`55uqT6n(@&ye)N-HD*lKhZu6*S$q6@Bcq*44Fzc|l_BlTZ074A%C@Rn?P$xA)ta(5W&yW*>uXe2j;6vHtK<;o^lN{h-K7@?*zehT+ zH^{yZ*;BAti&fFIz`T9;cdisSh_(hV0Fb_YUKh@BsnON)HvPr=ZgD!5XVCvMD3V0{ zq5rBoMbkh`7obftdixqD390DW>fg3HKhm)`s1|&-)qm;!)R4@qKfal)YkV%&GPzZ`>dQVH&so<95<)NfC^&O*80n?u0KzYbHaaWJrAU}_Pz~{ zC#u|EHXA-%g@-L@(AHfc%g2EqSmF;~6p@z&ktJZCx5*rd!Ks!QsV9J|bP>_+KO#>0*{9vXM8phpM zg2sP))X3HKHKdjcdoxzODeBOjpT2nx@Klbm1j5$@m2I8#vqQaI@pOuB71qLn*6T(j^wJ%0J+4DWr00TJ2gSqpfk*_SR>1ge4u_-@Ke zCmp-2e;o~Omqv6%NfGOLwz^fjKDljmFIO$U zC;(~TK!4^l4SF;-jN$UN1a##3;$A3nB0VwFu*pNxZ-@gY*dy3)%TyH-ZAMu9O~V0) zc8r!U1;~VQpG#-T%NeWQCGfPw^<1=-GoStpt|w)z@=1y|HJ>OoLgoQF9MH2r)ufe* z+SisX(qp}E!LLBk5Y+uks)K?m=No4krQ1s$p-4fWf9+1kCP7H?h3FZI_BF9i8Pm_Otl%?=Xw4)})}q)Ul*0 z#-K3gaL%*p)0Uxo@%))76fyl}Nz@w2RiOQe1quK#Q99}wOys7A#Vn(a0Ki0lV)+6H zn5dQjM-HM<8;FF`NC9~z3{EqX_GU&aG<(2c(%!)mrz<2#o`IKbqI7iPS_L&E_dHt` zSvgUXNOT}(Kmn1af92$Ra|PI-n94Z~67Ut3)3iAoC3@z#joG~m3PTDg#jE^^i3P}M zgwYpC@lE0<{6;ur=%dPvWv2025=kXB`sC-fjSG;^fOcB{-JxN=2thd zZXV9T`tJaKfYD(1!gCK~4SNPlL3geSJaAxFh&MR(7@9Zu#bglWsd2T;Y&jo5-UAZ> z0g>>Q z6jS7tY5;lWM&rU|f`odsy3!sy^C#7LqU22rV0&bg!xx&D%KquqX-TN|a@?D^QPxh1*!UhR|mRj62?z;qE&c=V$J}i6%WzRa+ zR;OJv`FkBf!{7r-(Fk3kjaI4AptaZ3kZj_NfuuL1Tt?Aj0ZzBVi;q~`vrRy^DXo&a z9~J<#PNqt)a2Om+8K42=V}xg+jp-&u;A%=B37B!&V}l?SuxR|c2l-v45qFU&QwADp zlw4&apm_rgGY(R=naf#&q&j}+95eZnJtq$Hks@=;$J4Ww4)16-<(Ht`^e^|V+L8P& zLrrdqR;=PuuG3~MJCu+u$btX*t7X~i zdD9IEV5u0QrGNmI3UN+L^5p%88@Y=(+FYY~UE&F9@6R{dpplSWbGF7?iMwOOihJPa?Cq7&qm5Xvp)r z9dg}MaLDC^nRJB&2{_m_EkOn%k4f-?T zse*A1sWV~tup0(0_v*1@t~8V0f%~LfkKF0Z*p?*0=&3gb8hwYG#g7<=e~}lld(rke z^8QvTj>(6+2b7BWN*Fb_w(;d_K`ah5hA7VfZ&?vg0^Jb{d2Rsq{Q_OXD!&wc6t?s@gBWG z%oxJhcl>m<`fWbkou_zgob;%M*quvAXn`nEAXksrE~oQc@1kQ;fNoOQJlWZA)!Vqr zn)JTX^L#>edb#WPLDa9xr|*NsBd9|mTV-t;R?oi5u;uJW;owH0kQU)V6(fX{sO#eE z+*$P#yX2L{OXBcKZ1*KLj?i? z=AQRc-IX9V?F93uT4aN?s6uYo6fTjcN{)NC-QJD)Bc^CnUZS^bO@J=H=b6#PFE2Bh;(x)T|6_E3?TsLr&Srhl5lxhpR`CRZ0dxx*pLK@B_q-OTK^3lH>FTb=4)20U{oy z1wh1$PXw!>1Q!ycG-IyeN+p5G3yLmJm?B%`E1B8zN zw)qJFOmv>4iq z^)F>qq!=|yaZ6M};y&si2O!qyuOuKwZg1HJDgZx({uBja0%C%>KO*XrLO4Q`WB{Oo zJ?jj;CI|a0z%A`docxm{ zXo{`iY@B^)VG`LKS+=dlp%2sSKm&p|&E0wObGLXR9Kaep^P@q6N59NUUrk>Xil_w` zzi3vK=soM|VV}6qs7Wv1R=hDOS9@Pp#CQo`_E}1zUz$qp3IF7(Yi}$#V|i)U)BNR% zQL?<+g6DO*g+Y#r`L$=KfEe9f7pC@pL6YFq$ZpD;qW#y{2aPfQnYjs}eN!!6GGf~2 z2{Vf|wV*p8^kB1;B=LMnG^`&%IL=TjcOVH={WP)|_!`Q$(jtr$v^8O?oOciO{<)hf zsw(~0M-%7L_w$^+mQs{CX}Mkb3hpy*{F9bRmWY6Kgr#@-FEnAh7Vj2D4YFfW)>$~@ zqm9p+xu5kb8&4jl9?z@}cfWW#*$934rvKFG^JM0HXlLSFe7{#VC_792PE*E{Vd}}` z?su&Ykuz&i9IoKY8sS4>E&IpY%vEfu#Habh!9$U?BVpEsE|-X3M_aVT{`tY>Nz#aS zdvW(IMbe=I7)u~*tih|PJ#u9fn*9wmyEKOkClW#usV#|}r^ZbzA^K(uj?}q5QIo^P z0ar^GOuFp;*QC;N3`zFTV{aY^Nl>Qt;`ERS5MXqE3EH5aZR!1_o53pQQ@*5r@p=#1 znbq0~Gb@v8ZL)Q$ktD0ae{Bw9V&GjVX5!=mu59!tFR1#$L+(c1V=;Wfn;TjD&PX^5 zH0``g9xy%+?ZanTQPr$kZg z-LVH?)}hwlBtlAj0S?DGw66kVi9XARf~P|(X5#7L!)AHxS1qfA4w;|6b)47Nc@~~o zH#vJYoE>=Xp0AG|rk*c;75UDl-83d&?`5d*WS4Zfzs>L#W6wmYi3;uM@k1rP4|77I zUW%ulc|6vkBsrengfiA7I(%!(z}z5I#cG&8b^l-`2hvY$i(vK3)4RIUu;V|e>1d*@ z!LX7g5K{`%H(9W!^2yT}5ju0(L8mEXxnNIvfOkIP+KwIJovQl#hxX__P@=-NBxsx6 zTYDQ-=K}An@I`qRtyhKNl317d`^!v7hnPpAdeG-hw?vvVK!aRj$j9YrOiaJcThU82 zq~$yM#Rh$TvnjnmG;IKgFPCWRbW=Iw7c6xkdw{=ia+qLUKJsR{fWt;&FZ#vV@u3F`|zp-PB28le*W`~TDIN$UFpG?m=8HQd>C9yhNgn2s4fvm z#57w0x{62K8S>P*oP^$#DF{X zdy{~_O|g|PDS}{@srD2q7LW1~vph)z@R@${%HYOmu@bVJZ9z$e1T_U%?IQ_Ybw3o9 zi8N=uKbC& z_NEvTT0#6jx(medBqgEK)JslZ1$R?kia8jn1*oR>rUhe#ND-u7Xa%he&jRTt?vYtN z*YB1MJM$q*v{Rl=YPs$E+J3s;KPY6g_U5&%=hxB}wuR1~Rjt13U}`oYDTA z(l+OHw*91fehP8Kr8+ zj9;JrPH#ivmf=Hfj<|kOSX!U~GARW!I-EU2Fv#*dYdcHzX}}j%3ff$$7g+d8+_*Az z{>Nh`q#!UxUZx<5Exd{r1)}PF zKK;kG4bdfWJn8Zv33P%_#E;qJ${Mdkn)!5VSSS~DsN0OljRUx&G=eg^@#a=v0UMhU z6C>W`spHKCV{heR9xtB#;muQKq`TX+^Ojo8#<+@Rev47@y8X-4>dxR1<2Rcje?G2u zHJA4n<=vy|`UH8*3rW zabPsK15hs;jbhE0X2J_t!+?lV4 zRR-csjE#Sb9h0`A95&u3i6?fw5YIHd(78=RTFNrKAhU* z)$w`i{wTdw+XpetFE2K?`rCp1lcDVK;rGqA9f`zuc&ZApa9}7XC?qHh$uPZ}>saJZ zFi=pos8CSoz`GtcY|bv8HfGMO9=0~Cn#(bn?3fQY{fJ3Qs%oX;MK5RMlfPRRuZDJJ z7=ENNDCMXy?ds(E{378O#_DxlUeFnFZtE*kp_an!Sdv{R3F3-OKevBj;v5K4=6x?z?)@{w)I$2G{E}6ve!PvxOPysl z&%ZWz*`G0VWD%d;E&5Mip~zRp@DM~ubxwo!)u~t2k)Qs zr7z!)=!T*(U1#HE?VqU;?M8{0VcFPoqsC`hzI9n2W^^UtHm-NAkm_r0+xw~M@BDaJK8prx+mE%R~Z_98CLc7NWi5fXa7s9#*o z%D+@$l~CXX8tdeDVQ!t?eS54_Iu(7h4-zarr=0cz&mfb$&9)@0HfV;IxwZOkA&=RmHLob8Z9zUOR!LoC`^h-mp|Jq<8Q6k7yU^>c`EE85r z;WV0n4>g15G+Yw*B3KNw(_s!tGsh%%1Zl)X7&e&(c_R*cRu`<1kJ29Q8y`wP`85If zgL;RZv3p@(!NwaIl2a$`Zb*4u2!B;RX|=fv?6^h$Z;x)4IgIEAJQ)}8YQU+X44lkt zoY`0*uh|I-b}$@hqBYoN28w0HYC0{g>C#i{0dwSPD-$IV?3G z?mRbQ=jCFXLH2&VB~6|xsQUK|hB=M}T8umjqAdB`aZ^`|n9MsDG{Y=?cJs}O&VS@C zQQ{BVoEy#DQNUNwzpMK)sN^MdmK^=L6rRRcCQ;@xvTG`T{rSXmS~7NVMIKimBjvz+ z8DFFr>6TO@oU__lbWv<#+~~2s_jEB;3KqKU%4RPPpIMMXrnRrVh5zpxy>^8zXNQJ@ zVgPOw^w-jLw|9DPY;XS_vUsE8rj`5H(WLx}M-iv&KFTQ`>Zke3Az?~*PwKcgrSvCn z+11w2$uTHy2Gb2K%#U>FXN7@Y6-ZngMPE3xzG0dfzgT(8qIFbL3X5lsUiS@KXKiT%to0Dj zJH7K|?Y1avtz%%UI0FZIk=$F3D7+?cgevG~{$cR(G}5U&sv!FnNt;h^5dN)Hj7vnF zxw0<(n*v%;e)VBbEZmfoK#mFiY1^Z8Oco)bi>e4>Tx?)mISO4gO?=4$Z{qTgPTn0> zOSa>1mj@G@_JecO|28hy;Z|D}VAOlSz=VGp_b*5KPZK{oRa3kWMj$&{a1HiPDE!j7 ze3%l}N*uhfkGEI>l64imM?^hPvLtQn+LI~3W8N*;xFft@sV%(1zObYnH5>Xx3RU!q z7)NS;156uzRk%s+gQ~nQ)`Vn5yxv1bP(K`_sK$cvMDe|3pGUaLH{@2v)HT`-y&D#Ui2aR*v4b@=VIKR8jo6 zJly=pO=gCO2N3}`xBv?U^#Zs_T?utTy4R7dDk%t@=?e|g9!dvEHcvrINWD(Mt|1E?F{2uWw+;SQLvVp;QV&8 zZT2f7hsEQD_4v1gOpli6g4P_-?=<;Tep3~>ox%;&j*(rs=h8js5_*F2A5OgIYl;&G zRnWsWgla^fRTZFNz_5QGrP4!6vdMrI$AK?Q;OpNX{(yy{piJ#eRGsV{oY{;V93Y1M z=V-Rj3zP%ky05_1SpOaheD{a$`_IuP_D*L1Yk0a|78ND}6jT=7U;oN~&)^Te0!#>; z>MoXMwq}35a{R9edfyr%dKMr<1#HMb;s1LyGcy-ww&!pEbC!RG z6aSvbA9}1D`M-mUe;VMQ`}W^MbE=U4a~J>Tz<=(8e-8|;e*T^Rxn%!2_@C?i@4?$O s&x8LjtNx$!{BtS&J`stWMHzY7Wq8Tb?ic5s|}WZ

{ZCy5EW;uOf-4VF=^EZ8;w!qPvy92Xte8 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 %`Z0Kqrrj0DgvR?8chJSV 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 z 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 z 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