"""统一反馈中心的原因池与候选生成。""" from __future__ import annotations from collections import OrderedDict from typing import Any _REASON_DEFINITIONS: list[dict[str, Any]] = [ { "code": "owner_mismatch", "label": "负责人不匹配", "description": "AI 推荐、人工指派或协作责任边界需要复核。", "source_types": {"task_breakdown", "task_override"}, "field_names": {"assignee_id"}, "confidence": 0.96, }, { "code": "capability_mismatch", "label": "能力匹配偏差", "description": "人员能力、技能覆盖或职责分配与任务要求不一致。", "source_types": {"task_breakdown", "task_override"}, "field_names": {"assignee_id", "required_skill_tag_ids", "required_skill_names"}, "confidence": 0.91, }, { "code": "skill_gap", "label": "技能要求不准", "description": "任务技能标签、能力要求或技能覆盖范围需要修正。", "source_types": {"task_breakdown", "task_override"}, "field_names": {"required_skill_tag_ids", "required_skill_names"}, "confidence": 0.94, }, { "code": "priority_misalignment", "label": "优先级错配", "description": "优先级建议与业务紧急度或协作节奏不匹配。", "source_types": {"task_breakdown", "task_override"}, "field_names": {"priority"}, "confidence": 0.89, }, { "code": "dependency_wrong", "label": "依赖关系错误", "description": "任务之间的依赖、先后顺序或阻塞链路判断有误。", "source_types": {"task_breakdown", "task_override"}, "field_names": {"depends_on_task_keys"}, "confidence": 0.92, }, { "code": "risk_underestimated", "label": "风险低估", "description": "任务风险等级、风险说明或执行风险识别偏低。", "source_types": {"task_breakdown", "task_override", "signal_feedback"}, "field_names": {"risk_level", "risk_notes", "strategic_classification"}, "confidence": 0.9, }, { "code": "effort_estimation_bias", "label": "工时预估偏差", "description": "任务预估工时与人工判断差距较大。", "source_types": {"task_breakdown", "task_override"}, "field_names": {"estimated_hours"}, "confidence": 0.95, }, { "code": "scope_overbroad", "label": "范围过大", "description": "任务标题、描述或业务范围切分不够清晰。", "source_types": {"task_breakdown", "task_override"}, "field_names": {"title", "description"}, "confidence": 0.82, }, { "code": "incomplete_output", "label": "输出信息不完整", "description": "AI 输出缺少关键字段、上下文或业务约束。", "source_types": {"task_breakdown", "task_override", "qa_feedback"}, "field_names": {"task_object", "qa_feedback"}, "confidence": 0.78, }, { "code": "answer_incorrect", "label": "答案不正确", "description": "QA 回复存在事实错误、遗漏或推断偏差。", "source_types": {"qa_feedback"}, "field_names": {"qa_feedback"}, "confidence": 0.93, }, { "code": "source_issue", "label": "来源问题", "description": "QA 的来源引用、检索材料或证据组织存在问题。", "source_types": {"qa_feedback"}, "field_names": {"qa_feedback"}, "confidence": 0.86, }, { "code": "wrong_fast_path", "label": "误走快速路径", "description": "问题本应进入更重的检索路径,却被过早走了 fast path。", "source_types": {"qa_feedback"}, "field_names": {"qa_feedback"}, "confidence": 0.9, }, { "code": "should_have_gone_deep", "label": "应该进入深检索", "description": "当前问题需要更深层的证据检索与合成,但 runtime 未升级。", "source_types": {"qa_feedback"}, "field_names": {"qa_feedback"}, "confidence": 0.91, }, { "code": "wrong_source_priority", "label": "来源优先级错误", "description": "检索时对事实源的优先级排序不合理,导致引用偏差。", "source_types": {"qa_feedback"}, "field_names": {"qa_feedback"}, "confidence": 0.88, }, { "code": "insufficient_evidence", "label": "证据不足", "description": "回答缺少足够内部证据支撑,应澄清或拒答。", "source_types": {"qa_feedback"}, "field_names": {"qa_feedback"}, "confidence": 0.94, }, { "code": "good_retrieval_bad_synthesis", "label": "检索好但合成差", "description": "证据命中基本正确,但最终答案组织、归纳或结论表达有偏差。", "source_types": {"qa_feedback"}, "field_names": {"qa_feedback"}, "confidence": 0.83, }, { "code": "over_deep_latency", "label": "过度深检索延迟", "description": "问题本可快速回答,但进入过深链路导致延迟偏高。", "source_types": {"qa_feedback"}, "field_names": {"qa_feedback"}, "confidence": 0.84, }, { "code": "permission_scope_miss", "label": "权限范围漏检", "description": "检索没有正确命中用户应有权限范围内的事实源。", "source_types": {"qa_feedback"}, "field_names": {"qa_feedback"}, "confidence": 0.87, }, { "code": "strategic_judgement_mismatch", "label": "战略判断偏差", "description": "AI 对信号是否战略相关的判断与人工判断不一致。", "source_types": {"signal_feedback"}, "field_names": {"strategic_classification"}, "confidence": 0.97, }, { "code": "signal_noise", "label": "噪声信号", "description": "信号本身噪声过高或不值得进入后续流程。", "source_types": {"signal_feedback"}, "field_names": {"strategic_classification"}, "confidence": 0.8, }, { "code": "manual_override", "label": "人工覆盖", "description": "字段由人工介入修改后形成反馈样本。", "source_types": {"task_breakdown", "task_override", "qa_feedback", "signal_feedback"}, "field_names": {"*", "task_object", "qa_feedback", "strategic_classification"}, "confidence": 0.75, }, { "code": "manual_review_needed", "label": "需要人工复核", "description": "当前样本仍需要人工确认,不足以自动通过。", "source_types": {"task_breakdown", "task_override", "qa_feedback", "signal_feedback"}, "field_names": {"*"}, "confidence": 0.7, }, ] def _candidate_payload(definition: dict[str, Any], *, source_type: str, field_name: str) -> dict[str, Any]: return { "code": definition["code"], "label": definition["label"], "description": definition["description"], "source_type": source_type, "field_name": field_name, "confidence": float(definition.get("confidence") or 0.0), } def list_reason_catalog( *, source_type: str | None = None, field_name: str | None = None, ) -> list[dict[str, Any]]: items: list[dict[str, Any]] = [] for definition in _REASON_DEFINITIONS: if source_type and source_type not in definition["source_types"]: continue if field_name and "*" not in definition["field_names"] and field_name not in definition["field_names"]: continue items.append( { "code": definition["code"], "label": definition["label"], "description": definition["description"], "source_types": sorted(definition["source_types"]), "field_names": sorted(definition["field_names"]), "confidence": float(definition.get("confidence") or 0.0), } ) return items def _append_unique(candidate_map: OrderedDict[str, dict[str, Any]], candidate: dict[str, Any]) -> None: code = str(candidate.get("code") or "").strip() if not code or code in candidate_map: return candidate_map[code] = candidate def _normalize_text(value: Any) -> str: return str(value or "").strip().lower() def _is_truthy_change(before_value: Any, after_value: Any) -> bool: if before_value is None and after_value is None: return False return before_value != after_value def _estimated_hours_direction(before_value: Any, after_value: Any) -> str: try: before = float(before_value or 0) after = float(after_value or 0) except (TypeError, ValueError): return "unknown" if after > before: return "increase" if after < before: return "decrease" return "flat" def build_reason_candidates( *, source_type: str, field_name: str, before_value: Any = None, after_value: Any = None, ) -> list[dict[str, Any]]: """基于字段差异构建稳定、可解释的候选原因。""" source_key = _normalize_text(source_type) field_key = _normalize_text(field_name) candidates: OrderedDict[str, dict[str, Any]] = OrderedDict() def add(code: str, label: str, confidence: float, description: str) -> None: _append_unique( candidates, { "code": code, "label": label, "description": description, "source_type": source_key, "field_name": field_key, "confidence": float(confidence), }, ) if source_key == "task_override" or source_key == "task_breakdown": if field_key in {"assignee_id", "owner_id"}: add( "owner_mismatch", "负责人不匹配", 0.96 if _is_truthy_change(before_value, after_value) else 0.88, "负责人或责任归属与任务实际执行人不一致。", ) add( "capability_mismatch", "能力匹配偏差", 0.91, "人员能力、技能覆盖或职责分配与任务要求不一致。", ) if field_key in {"required_skill_tag_ids", "required_skill_names"}: add( "skill_gap", "技能要求不准", 0.94 if _is_truthy_change(before_value, after_value) else 0.82, "任务所需技能标签需要复核。", ) add( "capability_mismatch", "能力匹配偏差", 0.9, "人员能力、技能覆盖或职责分配与任务要求不一致。", ) if field_key in {"depends_on_task_keys", "dependency", "dependencies"}: add( "dependency_wrong", "依赖关系错误", 0.92, "任务依赖、前置关系或阻塞关系需要修正。", ) if field_key == "priority": add( "priority_misalignment", "优先级错配", 0.9, "优先级建议与业务紧急度或协作节奏不匹配。", ) if field_key == "estimated_hours": direction = _estimated_hours_direction(before_value, after_value) confidence = 0.95 if direction in {"increase", "decrease"} else 0.8 add( "effort_estimation_bias", "工时预估偏差", confidence, "AI 工时预估与人工判断存在明显差异。", ) if direction == "increase": add( "scope_overbroad", "范围过大", 0.78, "任务范围可能需要进一步拆分。", ) if field_key in {"risk_level", "risk_notes"}: add( "risk_underestimated", "风险低估", 0.9, "任务风险说明可能低估了实际执行复杂度。", ) if field_key in {"title", "description"}: add( "scope_overbroad", "范围过大", 0.84, "任务标题或描述可能覆盖了过多内容。", ) if field_key == "task_object": add( "incomplete_output", "输出信息不完整", 0.76, "模型输出仍可能缺失关键细节,需要人工补全。", ) add("manual_override", "人工覆盖", 0.78, "AI 拆解结果经过人工修改后形成学习样本。") elif source_key == "qa_feedback": rating = _normalize_text((after_value or {}).get("rating") if isinstance(after_value, dict) else None) error_types = [] if isinstance(after_value, dict): error_types = [str(item).strip().lower() for item in after_value.get("error_types") or [] if str(item).strip()] if rating == "negative": if any(token in {"hallucination", "幻觉", "错误", "wrong"} for token in error_types): add( "answer_incorrect", "答案不正确", 0.95, "反馈显示回答内容存在事实错误或幻觉。", ) add( "source_issue", "来源问题", 0.84, "回答所依赖的来源或证据组织需要复核。", ) add( "incomplete_output", "输出信息不完整", 0.78, "回答缺少关键信息或上下文不够完整。", ) grounding_reason_map = { "wrong_fast_path": ("wrong_fast_path", "误走快速路径", 0.91, "问题本应进入更重的检索路径,却被过早走了 fast path。"), "fast_path": ("wrong_fast_path", "误走快速路径", 0.89, "问题本应进入更重的检索路径,却被过早走了 fast path。"), "should_have_gone_deep": ("should_have_gone_deep", "应该进入深检索", 0.92, "当前问题需要更深层的证据检索与合成,但 runtime 未升级。"), "deep": ("should_have_gone_deep", "应该进入深检索", 0.88, "当前问题需要更深层的证据检索与合成,但 runtime 未升级。"), "wrong_source_priority": ("wrong_source_priority", "来源优先级错误", 0.88, "检索时对事实源的优先级排序不合理。"), "insufficient_evidence": ("insufficient_evidence", "证据不足", 0.94, "回答缺少足够内部证据支撑,应澄清或拒答。"), "good_retrieval_bad_synthesis": ("good_retrieval_bad_synthesis", "检索好但合成差", 0.83, "证据命中基本正确,但最终答案组织或结论表达有偏差。"), "over_deep_latency": ("over_deep_latency", "过度深检索延迟", 0.84, "问题本可快速回答,但进入过深链路导致延迟偏高。"), "permission_scope_miss": ("permission_scope_miss", "权限范围漏检", 0.87, "检索没有正确命中用户应有权限范围内的事实源。"), } for token in error_types: mapping = grounding_reason_map.get(token) if mapping is None: continue add(mapping[0], mapping[1], mapping[2], mapping[3]) else: add("manual_review_needed", "需要人工复核", 0.72, "QA 反馈建议保留人工复核以确认样本质量。") elif source_key == "signal_feedback": predicted = None actual = None if isinstance(before_value, dict): predicted = before_value.get("ai_predicted_strategic") if isinstance(after_value, dict): actual = after_value.get("was_strategic") if predicted is not None and actual is not None and bool(predicted) != bool(actual): add( "strategic_judgement_mismatch", "战略性判断偏差", 0.97, "AI 对信号是否需要进入战略流程的判断与人工结果不一致。", ) add( "signal_noise", "噪声信号", 0.82, "当前信号可能缺少足够强的业务价值信号。", ) else: add("manual_review_needed", "需要人工复核", 0.7, "当前样本需要进一步人工确认。") if not candidates: add( "manual_review_needed", "需要人工复核", 0.7, "当前样本需要进一步人工确认。", ) return list(candidates.values()) __all__ = ["build_reason_candidates", "list_reason_catalog"]