{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cell-00",
   "metadata": {},
   "source": [
    "# 第12章 学生Notebook：客户咨询分流与待回复工作台\n",
    "\n",
    "> 把AI能力组织成可交付流程\n",
    "\n",
    "本Notebook完成以下任务：\n",
    "1. 加载咨询数据和服务指南\n",
    "2. 完成可行性四维评分\n",
    "3. 填写六段设计书\n",
    "4. 实现主题/紧急度分类路由\n",
    "5. 从服务指南检索证据\n",
    "6. 生成回复草稿并标记人工复核\n",
    "7. 处理边界案例和失败兜底\n",
    "8. 生成HTML工作台\n",
    "9. B档：修改一个分类规则或AI介入层级\n",
    "\n",
    "**准备**：确认 `data/inquiries.json` 和 `data/service_guide.json` 存在。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-01",
   "metadata": {},
   "source": [
    "## 1. 加载数据\n",
    "\n",
    "读取客户咨询和服务指南。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-02",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "import re\n",
    "import html\n",
    "from pathlib import Path\n",
    "from datetime import datetime\n",
    "\n",
    "# 加载数据\n",
    "data_dir = Path('../data')\n",
    "\n",
    "with open(data_dir / 'inquiries.json', 'r', encoding='utf-8') as f:\n",
    "    inquiries_data = json.load(f)\n",
    "inquiries = inquiries_data['inquiries']\n",
    "\n",
    "with open(data_dir / 'service_guide.json', 'r', encoding='utf-8') as f:\n",
    "    guide = json.load(f)\n",
    "\n",
    "print(f'咨询数量: {len(inquiries)}')\n",
    "print(f'服务分类数量: {len(guide[\"categories\"])}')\n",
    "print()\n",
    "print('前3条咨询:')\n",
    "for inq in inquiries[:3]:\n",
    "    print(f'  {inq[\"id\"]} [{inq[\"urgency\"]}] {inq[\"customer\"]}: {inq[\"text\"][:40]}...')\n",
    "print()\n",
    "print('服务指南分类:')\n",
    "for cat in guide['categories']:\n",
    "    print(f'  {cat[\"id\"]}: {cat[\"name\"]} (AI级别: {cat[\"ai_level\"]})')\n",
    "print()\n",
    "print('升级规则:')\n",
    "for level, topics in guide['escalation_rules'].items():\n",
    "    print(f'  {level}: {topics}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-03",
   "metadata": {},
   "source": [
    "## 2. 可行性四维评分\n",
    "\n",
    "在开始设计工作流之前，先评估这个任务的可行性。对每个维度打1-5分。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-04",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 可行性四维评分\n",
    "# 请根据你的理解填写评分（1-5分）和理由\n",
    "\n",
    "feasibility = {\n",
    "    '数据充分性': {\n",
    "        'score': 4,  # 修改为你的评分\n",
    "        'reason': '服务指南覆盖常见场景，但无法覆盖所有情况'\n",
    "    },\n",
    "    '技术可行性': {\n",
    "        'score': 5,  # 修改为你的评分\n",
    "        'reason': ''  # 填写你的理由\n",
    "    },\n",
    "    '风险可控性': {\n",
    "        'score': 3,  # 修改为你的评分\n",
    "        'reason': ''  # 填写你的理由\n",
    "    },\n",
    "    '可交接性': {\n",
    "        'score': 4,  # 修改为你的评分\n",
    "        'reason': ''  # 填写你的理由\n",
    "    }\n",
    "}\n",
    "\n",
    "print('可行性四维评分:')\n",
    "total = 0\n",
    "for dim, info in feasibility.items():\n",
    "    print(f'  {dim}: {info[\"score\"]}/5')\n",
    "    print(f'    理由: {info[\"reason\"]}')\n",
    "    total += info['score']\n",
    "print(f'  总分: {total}/20')\n",
    "print()\n",
    "if total >= 14:\n",
    "    print('评估结论: 可行性较高，可以推进')\n",
    "elif total >= 10:\n",
    "    print('评估结论: 有一定风险，需要重点关注薄弱环节')\n",
    "else:\n",
    "    print('评估结论: 风险较高，建议重新评估方案')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-05",
   "metadata": {},
   "source": [
    "## 3. 六段设计书\n",
    "\n",
    "填写完整的六段设计书。这是工作流工程化的核心文档。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-06",
   "metadata": {},
   "outputs": [],
   "source": [
    "design_doc = {\n",
    "    '第1段_任务声明': {\n",
    "        '做什么': '把客户咨询分流到正确的处理路径，生成回复草稿',\n",
    "        '不做什么': '不真实发送、不做最终决策、不处理服务指南之外的问题',\n",
    "        '输入': '客户咨询文本',\n",
    "        '输出': '分类结果 + 证据 + 回复草稿 + 责任人'\n",
    "    },\n",
    "    '第2段_可行性评估': {\n",
    "        '参考': '见上方四维评分'\n",
    "    },\n",
    "    '第3段_四段式工作流': {\n",
    "        '输入': '客户咨询文本',\n",
    "        'AI动作': '主题分类 + 紧急度判断 + 证据检索 + 草稿生成',\n",
    "        '人工复核': '边界案例和失败案例由人工确认',\n",
    "        '输出': '待回复清单（含状态、证据、责任人）'\n",
    "    },\n",
    "    '第4段_责任链': {\n",
    "        '数据读取': '系统负责',\n",
    "        '隐私脱敏': '系统自动 + 人工确认',\n",
    "        '分类': 'AI提供候选，人工可覆盖',\n",
    "        '证据检索': 'AI负责',\n",
    "        '草稿生成': 'AI负责',\n",
    "        '复核': '人工负责（边界/失败案例）',\n",
    "        '最终发送': '人工负责（AI不发送）'\n",
    "    },\n",
    "    '第5段_失效模式': [\n",
    "        {'场景': '分类错误', '影响': '咨询进错队列', '对策': '人工复核覆盖'},\n",
    "        {'场景': '检索无结果', '影响': '无法生成草稿', '对策': '进入人工队列'},\n",
    "        {'场景': '模型不可用', '影响': '无法生成草稿', '对策': '使用模板兜底'},\n",
    "        {'场景': '客户发送敏感信息', '影响': '隐私泄露', '对策': '自动脱敏'},\n",
    "        {'场景': '客户情绪激动', '影响': '回复不当激化矛盾', '对策': '强制人工队列'}\n",
    "    ],\n",
    "    '第6段_对外可解释性': {\n",
    "        '模板': '您的咨询被分类为{类别}，根据规则{规则编号}，回复由AI生成并经{责任人}复核。如不满意可{升级路径}。'\n",
    "    }\n",
    "}\n",
    "\n",
    "print('六段设计书:')\n",
    "for section, content in design_doc.items():\n",
    "    print(f'\\n{section}:')\n",
    "    if isinstance(content, dict):\n",
    "        for k, v in content.items():\n",
    "            print(f'  {k}: {v}')\n",
    "    elif isinstance(content, list):\n",
    "        for item in content:\n",
    "            print(f'  - {item[\"场景\"]}: {item[\"对策\"]}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-07",
   "metadata": {},
   "source": [
    "## 4. 路由：主题/紧急度分类\n",
    "\n",
    "根据服务指南的关键词，对每条咨询进行主题分类和紧急度判断。\n",
    "\n",
    "**运行**：观察每条咨询的分类结果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-08",
   "metadata": {},
   "outputs": [],
   "source": [
    "def classify_topic(text, categories):\n",
    "    \"\"\"根据关键词匹配服务指南分类\"\"\"\n",
    "    scores = {}\n",
    "    for cat in categories:\n",
    "        score = 0\n",
    "        for kw in cat.get('keywords', []):\n",
    "            if kw in text:\n",
    "                score += 1\n",
    "        if score > 0:\n",
    "            scores[cat['id']] = score\n",
    "    \n",
    "    if not scores:\n",
    "        return 'CAT_UNKNOWN', 0\n",
    "    \n",
    "    best = max(scores, key=scores.get)\n",
    "    return best, scores[best]\n",
    "\n",
    "def classify_urgency(text, declared_urgency):\n",
    "    \"\"\"判断紧急度：结合声明紧急度和文本情绪词\"\"\"\n",
    "    urgent_words = ['投诉', '欺诈', '赶紧', '马上', '立刻', '不满', '态度差',\n",
    "                    '举报', '消费者协会', '差评']\n",
    "    \n",
    "    has_urgent_word = any(w in text for w in urgent_words)\n",
    "    \n",
    "    if declared_urgency == '紧急' or has_urgent_word:\n",
    "        return '紧急'\n",
    "    return '普通'\n",
    "\n",
    "# 对所有咨询进行分类\n",
    "classification_results = []\n",
    "for inq in inquiries:\n",
    "    topic_id, topic_score = classify_topic(inq['text'], guide['categories'])\n",
    "    urgency = classify_urgency(inq['text'], inq['urgency'])\n",
    "    \n",
    "    # 找到对应的分类信息\n",
    "    cat_info = next((c for c in guide['categories'] if c['id'] == topic_id), None)\n",
    "    \n",
    "    result = {\n",
    "        'inquiry_id': inq['id'],\n",
    "        'customer': inq['customer'],\n",
    "        'text': inq['text'],\n",
    "        'topic_id': topic_id,\n",
    "        'topic_name': cat_info['name'] if cat_info else '未知',\n",
    "        'topic_score': topic_score,\n",
    "        'urgency': urgency,\n",
    "        'ai_level': cat_info['ai_level'] if cat_info else 'human_only',\n",
    "        'original_category': inq['category']\n",
    "    }\n",
    "    classification_results.append(result)\n",
    "\n",
    "print(f'{\"咨询ID\":<8} {\"主题\":<12} {\"紧急度\":<6} {\"AI级别\":<18} {\"匹配度\"}')\n",
    "print('-' * 65)\n",
    "for r in classification_results:\n",
    "    print(f'{r[\"inquiry_id\"]:<8} {r[\"topic_name\"]:<12} {r[\"urgency\"]:<6} {r[\"ai_level\"]:<18} {r[\"topic_score\"]}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-09",
   "metadata": {},
   "source": [
    "**思考**：\n",
    "\n",
    "1. 哪些咨询的分类匹配度为0？它们应该进哪个队列？\n",
    "2. INQ05（信息不足）被分到了哪个类别？为什么？\n",
    "3. INQ08（含敏感信息）的分类正确吗？还需要做什么额外处理？\n",
    "\n",
    "在下方写下你的观察。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-10",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 我的观察：\n",
    "# \n",
    "# 匹配度为0的咨询：\n",
    "# \n",
    "# INQ05 被分到了：\n",
    "# \n",
    "# INQ08 还需要："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-11",
   "metadata": {},
   "source": [
    "## 5. 证据检索：从服务指南找依据\n",
    "\n",
    "对每条咨询，从服务指南中匹配最相关的规则作为回复依据。\n",
    "\n",
    "**运行**：观察每条咨询匹配到的规则。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-12",
   "metadata": {},
   "outputs": [],
   "source": [
    "def retrieve_evidence(text, topic_id, categories):\n",
    "    \"\"\"从服务指南检索匹配的规则\"\"\"\n",
    "    cat_info = next((c for c in categories if c['id'] == topic_id), None)\n",
    "    if not cat_info:\n",
    "        return None, '无匹配分类'\n",
    "    \n",
    "    rules = cat_info.get('rules', [])\n",
    "    if not rules:\n",
    "        return None, '分类下无规则'\n",
    "    \n",
    "    # 简单策略：返回第一条规则作为候选\n",
    "    # 实际系统可以用更复杂的匹配\n",
    "    # 对于退换货，检查是否包含质量/发错关键词\n",
    "    if topic_id == 'CAT_RETURN':\n",
    "        if any(w in text for w in ['发错', '质量', '不对', '破损']):\n",
    "            return next((r for r in rules if r['rule_id'] == 'RET002'), rules[0]), '匹配商家责任规则'\n",
    "    \n",
    "    # 对于物流，检查是否有订单号\n",
    "    if topic_id == 'CAT_LOGISTICS':\n",
    "        has_order = bool(re.search(r'\\d{10,}', text))\n",
    "        if has_order:\n",
    "            return rules[0], '有订单号，匹配物流查询规则'\n",
    "    \n",
    "    # 默认返回第一条规则\n",
    "    return rules[0], f'匹配{cat_info[\"name\"]}默认规则'\n",
    "\n",
    "# 为每条咨询检索证据\n",
    "for r in classification_results:\n",
    "    rule, match_reason = retrieve_evidence(r['text'], r['topic_id'], guide['categories'])\n",
    "    r['evidence_rule'] = rule\n",
    "    r['match_reason'] = match_reason\n",
    "\n",
    "print(f'{\"咨询ID\":<8} {\"匹配规则\":<10} {\"需要人工\":<8} {\"匹配原因\"}')\n",
    "print('-' * 70)\n",
    "for r in classification_results:\n",
    "    rule = r['evidence_rule']\n",
    "    rule_id = rule['rule_id'] if rule else '无'\n",
    "    need_human = '是' if (rule and rule.get('need_human')) else '否'\n",
    "    print(f'{r[\"inquiry_id\"]:<8} {rule_id:<10} {need_human:<8} {r[\"match_reason\"]}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-13",
   "metadata": {},
   "source": [
    "## 6. 隐私脱敏\n",
    "\n",
    "检查咨询文本中是否包含敏感信息，如有则脱敏处理。\n",
    "\n",
    "**运行**：观察哪些咨询触发了脱敏。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-14",
   "metadata": {},
   "outputs": [],
   "source": [
    "def check_and_mask_privacy(text, privacy_rules):\n",
    "    \"\"\"检查并脱敏敏感信息\"\"\"\n",
    "    masked_text = text\n",
    "    found_sensitive = []\n",
    "    \n",
    "    patterns = privacy_rules.get('sensitive_patterns', [])\n",
    "    \n",
    "    # 手机号\n",
    "    phone_pattern = r'1[3-9]\\d{9}'\n",
    "    if re.search(phone_pattern, masked_text):\n",
    "        masked_text = re.sub(phone_pattern, '[已脱敏:手机号]', masked_text)\n",
    "        found_sensitive.append('手机号')\n",
    "    \n",
    "    # 身份证号\n",
    "    id_pattern = r'\\d{17}[\\dXx]'\n",
    "    if re.search(id_pattern, masked_text):\n",
    "        masked_text = re.sub(id_pattern, '[已脱敏:身份证号]', masked_text)\n",
    "        found_sensitive.append('身份证号')\n",
    "    \n",
    "    # 银行卡号（16-19位数字）\n",
    "    bank_pattern = r'\\d{16,19}'\n",
    "    if re.search(bank_pattern, masked_text) and '已脱敏' not in masked_text:\n",
    "        masked_text = re.sub(bank_pattern, '[已脱敏:银行卡号]', masked_text)\n",
    "        found_sensitive.append('银行卡号')\n",
    "    \n",
    "    return masked_text, found_sensitive\n",
    "\n",
    "# 对所有咨询进行脱敏检查\n",
    "for r in classification_results:\n",
    "    masked, found = check_and_mask_privacy(r['text'], guide['privacy_rules'])\n",
    "    r['masked_text'] = masked\n",
    "    r['sensitive_found'] = found\n",
    "\n",
    "print('隐私脱敏结果:')\n",
    "for r in classification_results:\n",
    "    if r['sensitive_found']:\n",
    "        print(f'  {r[\"inquiry_id\"]}: 发现 {\", \".join(r[\"sensitive_found\"])}')\n",
    "        print(f'    原文: {r[\"text\"][:50]}...')\n",
    "        print(f'    脱敏: {r[\"masked_text\"][:50]}...')\n",
    "        print()\n",
    "\n",
    "if not any(r['sensitive_found'] for r in classification_results):\n",
    "    print('  未发现敏感信息')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-15",
   "metadata": {},
   "source": [
    "## 7. 草稿生成与人工复核队列\n",
    "\n",
    "根据AI介入级别和规则模板生成回复草稿。边界案例和失败案例强制进入人工队列。\n",
    "\n",
    "**运行**：观察每条咨询的处理路径。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-16",
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_draft(r, guide):\n",
    "    \"\"\"根据分类和规则生成回复草稿\"\"\"\n",
    "    result = dict(r)  # copy\n",
    "    \n",
    "    # 判断是否需要强制人工\n",
    "    force_human = False\n",
    "    force_reason = ''\n",
    "    \n",
    "    # 边界案例：信息不足\n",
    "    if r['topic_score'] == 0:\n",
    "        force_human = True\n",
    "        force_reason = '分类匹配度为0，信息可能不足'\n",
    "    \n",
    "    # 边界案例：含敏感信息\n",
    "    if r['sensitive_found']:\n",
    "        force_human = True\n",
    "        force_reason = f'含敏感信息({\", \".join(r[\"sensitive_found\"])})，需人工确认脱敏'\n",
    "    \n",
    "    # 边界案例：原始分类为boundary或failure\n",
    "    if r['original_category'] == 'boundary':\n",
    "        # 检查是否属于升级规则中的立即人工类别\n",
    "        if r['topic_name'] in guide['escalation_rules'].get('immediate_human', []):\n",
    "            force_human = True\n",
    "            force_reason = f'{r[\"topic_name\"]}属于立即人工处理类别'\n",
    "    \n",
    "    if r['original_category'] == 'failure':\n",
    "        force_human = True\n",
    "        force_reason = '技术故障类咨询，AI系统可能不可用，需人工兜底'\n",
    "    \n",
    "    # 规则要求人工\n",
    "    rule = r.get('evidence_rule')\n",
    "    if rule and rule.get('need_human'):\n",
    "        force_human = True\n",
    "        force_reason = force_reason or rule.get('human_reason', '规则要求人工处理')\n",
    "    \n",
    "    # 生成草稿\n",
    "    if force_human:\n",
    "        result['queue'] = '人工队列'\n",
    "        result['force_reason'] = force_reason\n",
    "        if rule:\n",
    "            result['draft'] = rule.get('response_template', '需要人工处理')\n",
    "        else:\n",
    "            result['draft'] = '感谢您的留言。为了更快帮您解决问题，请补充以下信息：1）订单号；2）具体问题描述。'\n",
    "        result['status'] = '待人工处理'\n",
    "        result['responsible'] = '人工客服'\n",
    "    else:\n",
    "        result['queue'] = 'AI草拟'\n",
    "        result['force_reason'] = ''\n",
    "        if rule:\n",
    "            result['draft'] = rule.get('response_template', '正在为您处理')\n",
    "        else:\n",
    "            result['draft'] = '正在为您查询，请稍候'\n",
    "        \n",
    "        if r['ai_level'] == 'auto_draft':\n",
    "            result['status'] = '待发送'\n",
    "            result['responsible'] = 'AI（已记录日志）'\n",
    "        elif r['ai_level'] == 'draft_with_review':\n",
    "            result['status'] = '待人工复核'\n",
    "            result['responsible'] = '人工复核'\n",
    "        else:\n",
    "            result['status'] = '待人工处理'\n",
    "            result['responsible'] = '人工客服'\n",
    "            result['queue'] = '人工队列'\n",
    "    \n",
    "    return result\n",
    "\n",
    "# 生成所有草稿\n",
    "draft_results = [generate_draft(r, guide) for r in classification_results]\n",
    "\n",
    "print(f'{\"咨询ID\":<8} {\"队列\":<10} {\"状态\":<12} {\"责任人\":<14} {\"强制原因\"}')\n",
    "print('-' * 80)\n",
    "for r in draft_results:\n",
    "    print(f'{r[\"inquiry_id\"]:<8} {r[\"queue\"]:<10} {r[\"status\"]:<12} {r[\"responsible\"]:<14} {r[\"force_reason\"][:30]}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-17",
   "metadata": {},
   "source": [
    "**思考**：\n",
    "\n",
    "1. 有多少条咨询进入了人工队列？占比多少？\n",
    "2. INQ03（退款争议）和INQ06（投诉）为什么必须人工处理？\n",
    "3. 如果模型不可用，哪些咨询还能处理？哪些不能？\n",
    "\n",
    "在下方写下你的分析。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-18",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 我的分析：\n",
    "# \n",
    "# 进入人工队列的咨询数量和占比：\n",
    "# \n",
    "# INQ03和INQ06必须人工的原因：\n",
    "# \n",
    "# 模型不可用时的影响："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-19",
   "metadata": {},
   "source": [
    "## 8. HTML工作台\n",
    "\n",
    "生成一个HTML工作台，展示所有咨询的处理状态、证据和责任人。\n",
    "\n",
    "**运行**：生成HTML文件并在浏览器中查看。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-20",
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_html_workbench(draft_results, output_path):\n",
    "    \"\"\"生成HTML工作台\"\"\"\n",
    "    rows = ''\n",
    "    for r in draft_results:\n",
    "        rule_id = r['evidence_rule']['rule_id'] if r.get('evidence_rule') else '无'\n",
    "        \n",
    "        # 状态颜色\n",
    "        status_colors = {\n",
    "            '待发送': '#4CAF50',\n",
    "            '待人工复核': '#FF9800',\n",
    "            '待人工处理': '#f44336'\n",
    "        }\n",
    "        color = status_colors.get(r['status'], '#999')\n",
    "        \n",
    "        # 脱敏标记\n",
    "        sensitive_mark = ' ⚠️含敏感信息' if r['sensitive_found'] else ''\n",
    "        \n",
    "        rows += f'''\n",
    "        <tr>\n",
    "            <td>{r['inquiry_id']}</td>\n",
    "            <td>{html.escape(r['customer'])}</td>\n",
    "            <td>{html.escape(r['text'][:50])}...</td>\n",
    "            <td>{r['topic_name']}</td>\n",
    "            <td>{r['urgency']}</td>\n",
    "            <td>{rule_id}</td>\n",
    "            <td style=\"color:{color};font-weight:bold\">{r['status']}{sensitive_mark}</td>\n",
    "            <td>{r['responsible']}</td>\n",
    "            <td>{html.escape(r.get('force_reason', '')[:30])}</td>\n",
    "        </tr>'''\n",
    "    \n",
    "    html_content = f'''<!DOCTYPE html>\n",
    "<html lang=\"zh-CN\">\n",
    "<head>\n",
    "    <meta charset=\"UTF-8\">\n",
    "    <title>客户咨询分流工作台 - 第12章</title>\n",
    "    <style>\n",
    "        body {{ font-family: \"Microsoft YaHei\", sans-serif; margin: 20px; background: #f5f5f5; }}\n",
    "        h1 {{ color: #333; }}\n",
    "        .summary {{ background: #fff; padding: 15px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}\n",
    "        table {{ width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}\n",
    "        th {{ background: #1976D2; color: white; padding: 12px 8px; text-align: left; font-size: 14px; }}\n",
    "        td {{ padding: 10px 8px; border-bottom: 1px solid #eee; font-size: 13px; }}\n",
    "        tr:hover {{ background: #f0f7ff; }}\n",
    "        .footer {{ margin-top: 20px; color: #666; font-size: 12px; }}\n",
    "        .warning {{ color: #f44336; }}\n",
    "    </style>\n",
    "</head>\n",
    "<body>\n",
    "    <h1>📋 客户咨询分流工作台</h1>\n",
    "    <div class=\"summary\">\n",
    "        <p><strong>店铺：</strong>{guide['store_name']}</p>\n",
    "        <p><strong>咨询总数：</strong>{len(draft_results)}</p>\n",
    "        <p><strong>待发送：</strong>{sum(1 for r in draft_results if r['status'] == '待发送')}</p>\n",
    "        <p><strong>待人工复核：</strong>{sum(1 for r in draft_results if r['status'] == '待人工复核')}</p>\n",
    "        <p><strong>待人工处理：</strong>{sum(1 for r in draft_results if r['status'] == '待人工处理')}</p>\n",
    "        <p><strong>生成时间：</strong>{datetime.now().strftime('%Y-%m-%d %H:%M')}</p>\n",
    "        <p class=\"warning\"><strong>⚠️ 注意：本工作台仅展示草稿，绝不真实发送任何回复。</strong></p>\n",
    "    </div>\n",
    "    <table>\n",
    "        <tr>\n",
    "            <th>编号</th>\n",
    "            <th>客户</th>\n",
    "            <th>咨询内容</th>\n",
    "            <th>主题</th>\n",
    "            <th>紧急度</th>\n",
    "            <th>规则</th>\n",
    "            <th>状态</th>\n",
    "            <th>责任人</th>\n",
    "            <th>备注</th>\n",
    "        </tr>\n",
    "        {rows}\n",
    "    </table>\n",
    "    <div class=\"footer\">\n",
    "        <p>第12章教学资源 | 所有数据为虚构 | AI不承担最终决策责任</p>\n",
    "    </div>\n",
    "</body>\n",
    "</html>'''\n",
    "    \n",
    "    Path(output_path).parent.mkdir(parents=True, exist_ok=True)\n",
    "    with open(output_path, 'w', encoding='utf-8') as f:\n",
    "        f.write(html_content)\n",
    "    return output_path\n",
    "\n",
    "output_path = generate_html_workbench(draft_results, '../outputs/ch12_workbench.html')\n",
    "print(f'HTML工作台已生成: {output_path}')\n",
    "print()\n",
    "print('请在浏览器中打开查看。')\n",
    "print()\n",
    "\n",
    "# 统计\n",
    "print('工作台统计:')\n",
    "print(f'  待发送: {sum(1 for r in draft_results if r[\"status\"] == \"待发送\")} 条')\n",
    "print(f'  待人工复核: {sum(1 for r in draft_results if r[\"status\"] == \"待人工复核\")} 条')\n",
    "print(f'  待人工处理: {sum(1 for r in draft_results if r[\"status\"] == \"待人工处理\")} 条')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-21",
   "metadata": {},
   "source": [
    "## 9. 审计日志\n",
    "\n",
    "生成每条咨询的处理日志，用于追溯和交接。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-22",
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_audit_log(draft_results):\n",
    "    \"\"\"生成审计日志\"\"\"\n",
    "    logs = []\n",
    "    for r in draft_results:\n",
    "        log_entry = {\n",
    "            'timestamp': datetime.now().isoformat(),\n",
    "            'inquiry_id': r['inquiry_id'],\n",
    "            'customer': r['customer'],\n",
    "            'topic': r['topic_name'],\n",
    "            'urgency': r['urgency'],\n",
    "            'classification_score': r['topic_score'],\n",
    "            'sensitive_info': r['sensitive_found'],\n",
    "            'evidence_rule': r['evidence_rule']['rule_id'] if r.get('evidence_rule') else None,\n",
    "            'ai_level': r['ai_level'],\n",
    "            'queue': r['queue'],\n",
    "            'status': r['status'],\n",
    "            'responsible': r['responsible'],\n",
    "            'force_reason': r.get('force_reason', ''),\n",
    "            'draft_generated': bool(r.get('draft'))\n",
    "        }\n",
    "        logs.append(log_entry)\n",
    "    return logs\n",
    "\n",
    "audit_logs = generate_audit_log(draft_results)\n",
    "\n",
    "print('审计日志（前3条）:')\n",
    "for log in audit_logs[:3]:\n",
    "    print(f'  [{log[\"timestamp\"][:19]}] {log[\"inquiry_id\"]} → {log[\"topic\"]} → {log[\"status\"]} ({log[\"responsible\"]})')\n",
    "    if log['sensitive_info']:\n",
    "        print(f'    ⚠️ 敏感信息: {log[\"sensitive_info\"]}')\n",
    "    if log['force_reason']:\n",
    "        print(f'    强制原因: {log[\"force_reason\"]}')\n",
    "print(f'  ... 共 {len(audit_logs)} 条日志')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23",
   "metadata": {},
   "source": [
    "## 10. B档修改：修改一个分类规则或AI介入层级\n",
    "\n",
    "从以下选项中选择**一项**修改，修改前先写预测。\n",
    "\n",
    "**选项A**：修改一个分类类别\n",
    "- 例如：把\"退换货\"的关键词增加或减少\n",
    "- 预测：哪些咨询的分类会改变？\n",
    "\n",
    "**选项B**：修改一个AI介入层级\n",
    "- 例如：把\"退换货\"从 `draft_with_review` 改为 `human_only`\n",
    "- 预测：哪些咨询的路径会改变？误分成本有什么影响？\n",
    "\n",
    "**选项C**：修改边界规则\n",
    "- 例如：把投诉的升级规则从\"立即人工\"改为\"AI草拟+人工复核\"\n",
    "- 预测：风险和效率如何变化？"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-24",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 我选择：选项___\n",
    "# \n",
    "# 修改前预测：\n",
    "# \n",
    "# 修改内容：\n",
    "\n",
    "# === 示例：选项B - 修改AI介入层级 ===\n",
    "# 取消下面的注释并修改\n",
    "\n",
    "# # 修改前：记录原始状态\n",
    "# original_levels = {cat['id']: cat['ai_level'] for cat in guide['categories']}\n",
    "# print('修改前AI介入级别:')\n",
    "# for cid, level in original_levels.items():\n",
    "#     print(f'  {cid}: {level}')\n",
    "# \n",
    "# # 修改：把退换货改为human_only\n",
    "# for cat in guide['categories']:\n",
    "#     if cat['id'] == 'CAT_RETURN':\n",
    "#         cat['ai_level'] = 'human_only'  # 原来是 draft_with_review\n",
    "# \n",
    "# # 重新运行分类和草稿生成\n",
    "# # ... (重新调用上面的函数)\n",
    "# \n",
    "# # 对比修改前后的差异\n",
    "# print('\\n修改后受影响咨询:')\n",
    "# # 找出路径改变的咨询"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-25",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 修改后的影响分析：\n",
    "# \n",
    "# 哪些咨询路径改变了：\n",
    "# \n",
    "# 误分成本影响：\n",
    "# \n",
    "# 责任链变化："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-26",
   "metadata": {},
   "source": [
    "## 11. 人工改写草稿\n",
    "\n",
    "选择至少一条AI生成的回复草稿，进行人工改写，并标记修改依据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-27",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 选择一条草稿进行人工改写\n",
    "# 请选择一条你认为需要改进的回复\n",
    "\n",
    "print('可改写的草稿:')\n",
    "for i, r in enumerate(draft_results):\n",
    "    print(f'  {i+1}. {r[\"inquiry_id\"]} ({r[\"topic_name\"]}):')\n",
    "    print(f'     客户: {r[\"text\"][:40]}...')\n",
    "    print(f'     草稿: {r[\"draft\"][:60]}...')\n",
    "    print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-28",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 我选择改写第___条草稿\n",
    "# \n",
    "# 原始草稿：\n",
    "# \n",
    "# 改写后：\n",
    "# \n",
    "# 修改依据：\n",
    "# （例如：原草稿语气过于机械/缺少共情/信息不完整/不符合实际情况）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-29",
   "metadata": {},
   "source": [
    "## 12. 验收\n",
    "\n",
    "运行下面的检查，确认你完成了所有必做项。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-30",
   "metadata": {},
   "outputs": [],
   "source": [
    "checks = {\n",
    "    '加载了咨询数据和服务指南': True,\n",
    "    '完成了可行性四维评分': False,  # 检查第2节是否填写了理由\n",
    "    '填写了六段设计书': True,  # 已预填，学生可补充\n",
    "    '运行了主题/紧急度分类': True,\n",
    "    '运行了证据检索': True,\n",
    "    '运行了隐私脱敏': True,\n",
    "    '生成了回复草稿': True,\n",
    "    '识别了边界案例': True,\n",
    "    '生成了HTML工作台': True,\n",
    "    '完成了B档修改': False,  # 检查第10节\n",
    "    '人工改写了至少一条草稿': False,  # 检查第11节\n",
    "}\n",
    "\n",
    "print('验收清单:')\n",
    "for check, status in checks.items():\n",
    "    print(f'  [{\"✓\" if status else \" \"}] {check}')\n",
    "\n",
    "print()\n",
    "print('请手动把 False 改为 True，确认你完成了对应项目。')\n",
    "print()\n",
    "print('关键概念检查:')\n",
    "print('  1. 四段式工作流的四个阶段是 ___')\n",
    "print('  2. AI介入级别有三种，分别是 ___')\n",
    "print('  3. 责任链的作用是 ___')\n",
    "print('  4. 失败兜底的含义是 ___')\n",
    "print('  5. 为什么AI不能真实发送回复？___')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.10.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
