---
name: wx-quote-assistant
description: |
  万能报价助手。帮助用户完成报价全流程：业务初始化、需求收集与大纲生成、价格计算与初稿生成、质检与交付。
  触发词：报价、做报价、出报价单、报价方案、报价流程、客户报价、项目报价、报价助手、新报价、开始报价。
  也支持：更新配置、更新产品表、更新定价规则——用于修改已有业务配置。
agent_created: true
---

# 万能报价助手 (wx-quote-assistant)

通用智能报价工作流 Skill。支持跨平台（Windows/macOS/Linux）。所有文件操作使用 `os.path.expanduser` 和 `os.path.join` 处理路径。

## 目录隔离

| 目录 | 用途 | 读写 |
|------|------|------|
| Skill 目录 (`~/.workbuddy/skills/wx-quote-assistant/`) | 脚本、参考、模板资产 | 只读 |
| 数据目录 (`~/.wx-quote-assistant/`) | 业务配置（config、规则、产品表、模板） | 读写 |
| 报价目录 (`{workspace}/报价单/{session_id}/`) | 交付物 + 会话数据（`.session/` 隐藏子目录） | 写入 |

**报价目录结构：**
```
{workspace}/报价单/{session_id}/
├── 报价方案.html          # 客户端报价书
├── 报价单.xlsx            # Excel 报价单
├── 报价单质检报告.html     # 内部质检报告
└── .session/              # 隐藏目录，会话数据
    ├── state.json
    ├── outline.md
    ├── draft.md
    ├── qa_results.json
    └── full_data.json
```

`session_id` 格式为 `{YYYY-MM-DD-HHmmss}`（如 `2026-06-16-151407`），交付物与会话数据存放在同一报价目录下。session_id 即输出目录名，无需额外序号。

---

## 启动与路由

每次 Skill 被触发时，执行以下路由逻辑：

1. 读取 `os.path.expanduser("~/.wx-quote-assistant/active_business")`。
2. 若文件不存在或内容为空 → 进入**阶段0（业务初始化）**。
3. 若存在有效业务名称，且用户未说"新建业务/初始化新业务/切换业务" → 进入**阶段1（开始报价）**。
4. 用户说"开始报价"或"新报价"时，也进入**阶段1**。
5. 用户说"更新配置/更新产品表/更新定价规则"时，重新进入阶段0的对应子步骤。

## 依赖检查

Skill 启动时，仅检查核心依赖 openpyxl（Excel 读写，阶段2产品表检查 + 阶段4生成报价单.xlsx）：

```python
try:
    import openpyxl
except ImportError:
    # 提示用户安装
```

若缺失，输出：
> 缺失核心依赖 openpyxl（用于 Excel 报价单生成），请执行 `pip install openpyxl`，或回复"自动安装"。

用户同意后执行：
```python
import subprocess, sys
subprocess.run([sys.executable, "-m", "pip", "install", "openpyxl"], shell=False)
```

**可选依赖**：markdown（MD→HTML 转换，阶段4；当前代码用硬编码 HTML 模板，暂未调用 markdown 库）、python-docx（Word 模板解析）、weasyprint（PDF 生成）。在对应步骤按需检查。

---

## 阶段0：业务初始化

按顺序执行每个子步骤，每步等待用户确认后进入下一步。

**开场白：**

> 您好！为了更精准地帮您输出报价方案，我需要先做一次初始化学习。简单几步，了解您的报价单结构、产品体系、报价策略及其他注意事项。请按照指引操作，完成后就可以快速生成报价了。

### 0.1 业务名称

用自然语言问：

> 您想给这个报价业务取个名字吗？比如公司名或品牌名，方便我区分不同业务的配置。
> 
> 后续您还可以随时新增或切换其他业务。

创建以下目录结构：

```
~/.wx-quote-assistant/{业务名称}/
├── config.json
├── templates/
├── product_tables/
├── rules/
└── logs/
```

```python
import os
base = os.path.expanduser("~/.wx-quote-assistant")
business_dir = os.path.join(base, business_name)
for sub in ["templates", "product_tables", "rules", "logs"]:
    os.makedirs(os.path.join(business_dir, sub), exist_ok=True)
```

### 0.2 报价场景

用自然语言问：

> 你们主要的业务场景有哪些？比如空压站节能改造、年度维保、设备采购等。简单列几个就行，方便我理解你的业务范围。如果暂时不清楚，可以先跳过。

### 0.3 报价方信息

用自然语言问：

> 请告诉我报价方的信息：公司名称、报价人、联系电话、邮箱。这些信息会出现在报价单上，也可以把你自己常用的信息直接告诉我。

记录到 config.json。

### 0.4 上传报价模板（可选）

用自然语言问：

> 有没有现成的报价模板？Word、Excel、Markdown 格式都可以。有的话发给我，我会读懂它并记住格式，以后自动套用。没有的话也不用担心，我有一套标准模板直接帮你做。

后续按原流程处理模板解析和变量确认。

### 0.5 产品组合规则

用自然语言问：

> 报价时有没有固定的产品搭配规则？比如某个场景下必须包含哪些产品、每个产品用量怎么算。
> 
> 举个例子：「空压站中配 = 节能控制柜每台空压机1台 + 压力传感器每台1只 + 网关1台 + 安装调试2工日」

保存到 `rules/product_combo.md`。

### 0.6 产品表配置

用自然语言问：

> 有产品价格表吗？Excel 格式就可以。包含产品名称、型号、单位、参考价格这些信息。发给我后，后续报价就能直接从里面取值了。
> 
> 现在来不及也没关系，报价的时候再补充也可以。

保存到 `product_tables/`，自动提取单位写入 config 的 `units`。

### 0.7 定价规则

用自然语言问：

> 你们平时报价有什么折扣或定价策略吗？比如满多少优惠多少、不同客户等级不同折扣、运费怎么算、税率多少之类。
> 
> 举个例子：「设备类项目总价超20万给5%折扣，超50万另议。维保首年可打9折。税率13%。」

保存到 `rules/pricing_rules.md`。

### 0.8 质检规则

用自然语言说明：

> 为了确保报价不出错，我会在生成报价后自动做一轮质检。默认有三个角色参与检查：
> - **销售负责人**：检查需求匹配、折扣是否合理、有没有遗漏客户要求
> - **交付负责人**：检查交期能不能做到、安装工日够不够、有没有需要外协的
> - **财务负责人**：检查价格计算对不对、付款条件是否合规、有没有回款风险
> 
> 需要增减角色或调整检查项吗？直接告诉我就可以。

合并用户修改保存到 `rules/quality_check.md`，默认清单作为兜底。

### 0.9 生成 config.json

```json
{
  "business_name": "{业务名称}",
  "company_name": "",
  "quoter_name": "",
  "quoter_phone": "",
  "quoter_email": "",
  "units": [],
  "scenarios": [],
  "template": "",
  "template_format": "md",
  "product_table": "",
  "rules": {
    "product_combo": "rules/product_combo.md",
    "pricing": "rules/pricing_rules.md",
    "quality_check": "rules/quality_check.md"
  },
  "defaults": {
    "tax_rate": 0.13,
    "validity_days": 30,
    "payment_terms": "合同签订后付30%，验收后付70%",
    "warranty": "按行业标准执行",
    "service_scope": "含产品交付及标准安装"
  }
}
```

### 0.10 写入 active_business

```python
with open(os.path.expanduser("~/.wx-quote-assistant/active_business"), "w") as f:
    f.write(business_name)
```

输出初始化完成的提示：

> 🎉 搞定！您的报价业务「{业务名称}」已初始化完成，现在可以开始报价了，对我说「开始报价」就行。

## 阶段1：需求收集与大纲生成

目标：生成不含价格的报价大纲，应用产品组合规则。

### 1.1 初始化会话

```bash
python3 scripts/state_manager.py init --business "{业务名称}" --workspace "{当前工作空间}"
```

从输出获取 `session_id`。该 ID 同时作为报价目录名，所有中间产物和最终交付物存入同一目录：

- 交付物：`{workspace}/报价单/{session_id}/`
- 会话数据：`{workspace}/报价单/{session_id}/.session/`（即 `session_dir`）

```python
session_dir = os.path.join(workspace, "报价单", session_id, ".session")
os.makedirs(session_dir, exist_ok=True)
```

### 1.2 自动匹配业务

加载 config.json，检查业务信息与当前上下文是否匹配：

- **匹配**：用户需求描述中提到了业务场景（如"空压站节能改造"），且 config 中有对应场景 → **自动确认，直接进入 1.3，不弹出询问**。
- **不明确但可能匹配**：用户只说"报价"或"开始报价" → 同样自动确认，直接进入 1.3。
- **明显不匹配**：用户描述的业务类型与 config 中的场景完全无关 → 弹出 AskUserQuestion 询问确认或切换业务：

```json
{
  "questions": [{
    "question": "当前业务为「{业务名称}」，与您描述的场景似乎不匹配。是否切换业务？",
    "header": "业务确认",
    "options": [
      {"label": "确认使用此配置", "description": "继续使用当前业务配置报价"},
      {"label": "切换其他业务", "description": "切换到已配置的其他业务"},
      {"label": "调整配置项", "description": "在当前业务中临时调整信息"}
    ]
  }]
}
```

这样绝大多数情况下用户可以跳过业务确认步骤，直接开始描述需求。

### 1.3 收集客户需求

这是报价流程**第一个与用户交互的步骤**。引导用户用自然语言描述：

> 「请告诉我这次的客户信息以及报价需求，比如客户是谁、需要什么产品或服务、数量多少、有没有时间要求等等。」

示例：「江北精密制造，3台75kW空压机中高配节能改造，2周内供货，预算45万左右。」

产品价格表在此阶段**不提前收集**，等进入阶段2需要定价时再按预设逻辑询问。

### 1.4 提取关键信息

从用户描述中提取：
- 客户名称
- 产品名（或场景+档次）
- 数量、单位
- 交期
- 预算（可选）
- 特殊要求（可选）

缺失必要字段（产品/数量）时追问。客户名称如未提供也在此时询问。

### 1.5 生成并展示报价大纲

**用户需求收集完毕后，不做产品组合单独确认，直接应用产品组合规则并输出完整报价大纲。**

内部处理流程：
- 如果用户直接给出具体产品型号和数量（如 N500 20桶），直接使用。
- 否则，读取 `product_combo.md`，结合 1.4 提取的场景和档次，自动匹配产品明细（型号、数量比例等）。

**立即生成完整的报价大纲 Markdown 内容，直接输出到聊天界面供用户查看。** 这是阶段1的最终交付节点，用户收到大纲后只需选择"确认"或"修改"。

报价大纲包含以下完整章节：
- 报价方信息（从 config 读取，允许用户临时修改）
- 客户名称（手动输入）
- 项目概况（场景、规模、目标、方案定位）
- 产品方案明细表（产品、规格、数量、单位，**无单价**）
- 商务条款：交期、付款方式、质保
- 服务范围
- 有效期（默认30天）
- 备注（预算目标等）

**输出规则（强制）：**
1. 提取需求 + 匹配产品组合后，**直接将完整大纲以 Markdown 文本形式输出到聊天界面**。
2. 同时保存到会话目录：`outline_path = os.path.join(session_dir, "outline.md")`
3. 大纲输出完毕后再调用 `AskUserQuestion` 询问确认，**严禁在大纲未完整展示前就弹出确认窗口**。

### 1.6 用户确认

在大纲内容**已完整展示在聊天界面后**，调用 `AskUserQuestion` 弹出选项窗口：

```json
{
  "questions": [{
    "question": "请确认报价大纲内容。确认后进入定价阶段，或提出修改意见。",
    "header": "大纲确认",
    "options": [
      {"label": "确认大纲，进入定价", "description": "大纲无误，进入阶段2价格计算"},
      {"label": "需要修改大纲", "description": "调整产品、数量、交期、条款等内容"}
    ]
  }]
}
```

确认后签核 checkpoint：
```bash
python3 scripts/state_manager.py sign-off --workspace "{workspace}" --business "{name}" --session-id "{id}" --checkpoint outline_confirmed
```

**必须等待用户选择"确认大纲"后才能进入阶段2。**

---

## 阶段2：价格计算与初稿生成

目标：获取单价，应用定价规则，计算总价，生成完整报价书。

### 2.1 确认产品价格表

**此时才首次询问产品价格表**。检查 config.json 中 `product_table` 是否有值且文件存在：

- **有且文件存在**：调用 AskUserQuestion：
```json
{
  "questions": [{
    "question": "检测到已有产品表：{文件名}（{修改时间}）。请选择：",
    "header": "产品表确认",
    "options": [
      {"label": "使用当前产品表", "description": "继续使用已有价格表"},
      {"label": "上传最新产品表", "description": "替换为最新的 Excel 价格表"}
    ]
  }]
}
```

- **无或文件不存在**：提示用户上传：
  > 「还没有产品价格表，请上传最新的产品 Excel 表格（包含产品名称、型号、单位、参考价格）。」

用户上传后，保存到 `product_tables/`，更新 config.json 的 `product_table` 字段，并从表中提取单位列表写入 `units`。目的是确保每次报价都使用最新价格，避免失真。

### 2.2 单价获取

对产品方案中的每个产品，询问用户本次报价的单价。

- 可参考产品表中的"参考价"列，但不强制。
- 用户可批量输入（如"全部按参考价"）或逐条输入。
- 单价缺失时暂停询问，不得使用默认值猜测。

### 2.3 用量换算

若用户以面积提需求且产品非面积单位（如涂料按桶卖），询问换算系数（如每桶涂布面积），计算实际所需桶数。

### 2.4 定价规则应用

1. 将产品明细（含数量、单价）写入 JSON：
   ```python
   products_json = os.path.join(session_dir, "products_input.json")
   ```

2. 调用 price_calculator.py 生成计算上下文：
   ```bash
   python3 scripts/price_calculator.py calculate \
     --products {products_json} \
     --rules {pricing_rules_path} \
     --tax-rate {tax_rate} \
     --output {session_dir}/pricing_context.json
   ```

3. 读取 `pricing_context.json`，将其中的 `expected_output.instructions` 和产品数据提交给 LLM，获取结构化的价格结果。

4. 保存 LLM 结果：
   ```bash
   python3 scripts/price_calculator.py save-result \
     --result {llm_result_json} \
     --output {session_dir}/priced_result.json
   ```

5. 不追加 AI 标记。

### 2.5 生成报价初稿

加载模板（`assets/default_template.md` 或用户自定义模板）。使用 `template_parser.py` 替换变量：

```bash
python3 scripts/template_parser.py parse \
  --template {template_path} \
  --data {session_dir}/full_data.json \
  --output {session_dir}/draft.md
```

或直接用 Python 代码渲染 Markdown 草稿。

### 2.6 用户确认

展示初稿后，**调用 `AskUserQuestion` 弹出选项窗口**：

```json
{
  "questions": [{
    "question": "请确认报价初稿。确认后进入质检阶段，或提出修改。",
    "header": "初稿确认",
    "options": [
      {"label": "确认初稿，进入质检", "description": "价格和条款无误，进入阶段3质检"},
      {"label": "需要修改价格或条款", "description": "调整单价、折扣、交期、付款条件等"}
    ]
  }]
}
```

若修改涉及价格（如调整数量），重新进入2.4。

---

## 阶段3：质检与自动交付

质检完成后直接进入最终交付，无需用户在此阶段做确认。

### 3.1 加载质检清单

1. 加载默认清单 `references/checklists_default.yaml`。
2. 加载用户自定义 `rules/quality_check.md`（如存在）。
3. 合并两个清单（用户规则优先覆盖同名角色/检查项）。

### 3.2 执行质检

```bash
python3 scripts/quality_checker.py check \
  --checklist {checklist_path} \
  --draft {session_dir}/draft.md \
  --priced {session_dir}/priced_result.json \
  --output {session_dir}/qa_results.json
```

脚本自动处理 `type: auto` 的检查项。对于 `type: llm_assisted` 的检查项，读取 `qa_results.json` 中标记为 `pending_llm` 的项，逐项提交 LLM 判断。不追加 AI 标记。

### 3.3 生成质检报告

调用 `quality_checker.py` 的 format_report 或手动构建 Markdown 报告。三种结果：
- ✅ 符合
- ⚠️ 建议（附说明）
- ❓ 待确认（附说明）

### 3.4 标注初稿

读取 `draft.md`，对所有缺失信息、假设值、未确认项，在对应位置添加标记：
```
**【需人工确认】**
```

### 3.5 直接进入最终交付

质检完成后，**不等待用户确认，直接进入最终交付环节**。质检报告随最终交付物一并输出。

---

## 最终交付

### 1. 去标注

从 `draft.md` 中移除所有 `**【需人工确认】**` 标记，生成干净版本。

**客户端生成物不得出现 AI 辅助标注**：构建 `full_data.json` 时，`footer_text` 字段应为公司/项目信息（如 "华宸智服 · 工业节能改造"），不得使用 "AI 辅助生成，请人工复核" 等 AI 标注文字。该标注仅保留在内部质检报告（QA report）中。

### 2. 构建完整数据

将所有报价信息组装为一个 JSON 文件 `{session_dir}/full_data.json`。字段结构参照 `references/full_data_schema.md`，核心字段：

```python
# 报价目录即输出目录
output_dir = os.path.join(workspace, "报价单", session_id)
session_dir = os.path.join(output_dir, ".session")
os.makedirs(output_dir, exist_ok=True)
```

```python
full_data = {
    # 报价方/客户信息
    "company_name": "...", "quoter_name": "...", "quoter_phone": "...", "quoter_email": "...",
    "client_name": "...", "quote_no": "...", "quote_date": "...", "valid_until": "...",
    # 产品
    "products": [{"seq": ..., "category": "...", "name": "...", "model": "...",
                   "qty": ..., "unit": "...", "unit_price": ..., "discount_desc": "...", "subtotal": ...}],
    # 金额
    "discount_desc": "...", "discount_amount": ..., "discounted_total": ...,
    "tax_amount": ..., "shipping_fee": ..., "grand_total": ..., "grand_total_cn": "...",
    # 条款
    "delivery_date": "...", "payment_terms": "...", "warranty": "...",
    "service_scope": "...", "delivery_boundary": "...",
    # 模板内容
    "header_title": "...", "header_subtitle": "...",
    "requirement_summary": "...", "solution_overview": "...",
    "alerts": [["文本", "warn|info"], ...],
    "project_overview": [[k1,v1,k2,v2], ...],
    "tech_overview": [[k,v], ...],
    "notes": [["文本", "hex_color", "warn|info"], ...],
    "category_order": ["设备", "硬件", "软件", "服务", "备件"],
    "category_names": {"设备": "...", ...},
    "footer_text": "...",
}
```

### 3. 生成 HTML + Excel + 质检报告

```bash
python3 scripts/generators.py all \
  --data {session_dir}/full_data.json \
  --out-dir {output_dir} \
  --name-prefix "报价方案"
```

- HTML：`assets/default_html.html` 模板（客户端，**不含质检内容**）
- Excel：`scripts/excel_builder.py` 生成（客户端，**不含质检内容**）
- 质检报告：`assets/default_qa_report.html` 模板（内部审批，含质检清单+预算分析+风险提醒+审批清单）

质检数据通过 full_data.json 的以下字段传入：
```python
"qa_checks": [{"role":"销售总监","question":"...","result":"pass|warn|pending","note":"..."}, ...],
"qa_risks": [["风险描述文本", "danger|warn|info"], ...],
"qa_approvals": [{"title":"...","approver":"周强","status":"pending|done"}, ...],
"qa_budget": 450000,  # 客户预算金额
```

### 4. PDF 生成（可选）

### 5. 签核完成

### 6. 输出文件路径

生成完毕后，**必须将交付物以可点击链接形式呈现给用户**：

```python
# 使用 present_files 工具将文件链接到对话框
```

传入所有生成的文件路径（HTML、Excel、质检报告），其中 HTML 文件会被自动预览打开。

向用户展示最终交付物：
- `{output_dir}/报价方案.html` — 客户端报价书
- `{output_dir}/报价单.xlsx` — 客户端 Excel
- `{output_dir}/报价单质检报告.html` — 内部质检报告
- `{output_dir}/报价方案.pdf`（如生成成功）

展示完毕后，用一句提示收尾。 根据AI协助生成的内容，请仔细检查后再发给客户！

---

## 通用约束

- **禁止私自修改**：执行报价任务过程中，禁止私自修改本 Skill 的任何文件（SKILL.md、scripts/、assets/、references/），也禁止私自创建新的 Skill。如需修改 Skill，必须由用户明确发起。
- **编码**：所有文本文件读写必须指定 `encoding="utf-8"`。
- **外部命令**：使用 `subprocess.run` 并设置 `shell=False`。
- **阶段确认**：阶段1和阶段2必须等待用户明确确认才能进入下一阶段。阶段3（质检）无需确认，直接进入最终交付。阶段0（初始化）按子步骤逐项文本确认。
- **展现交付结果**：生成物展示完毕，提醒用户：如果你有一些需求不符合，可随时告诉我，我帮你重新调整。 根据AI协助生成的内容，请仔细检查后再发给客户！
- **缺失信息**：缺失必要信息（产品表、单价、换算系数）时必须暂停并询问，不允许使用默认值猜测。
- **配置更新**：用户说"更新配置"时，重新进入阶段0的对应子步骤（更新产品表、定价规则等）。
- **跨平台**：所有路径使用 `os.path.expanduser("~")` 和 `os.path.join`。

## 用户确认交互规范

**关键确认节点必须使用 `AskUserQuestion` 工具弹出选项窗口**，让用户点击选择，而非等待文本输入。适用节点及选项模板：

| 节点 | 触发时机 | 选项 |
|------|------|------|
| 1.2 业务确认 | 展示业务配置后（仅不匹配时弹出） | "确认使用此配置"/"切换其他业务"/"调整配置项" |
| 2.1 产品表确认 | 进入定价时，检查产品表后 | "使用当前产品表"/"上传最新产品表" |
| 1.6 大纲确认 | **大纲完整展示到聊天界面后** | "确认大纲，进入定价"/"需要修改大纲" |
| 2.6 初稿确认 | 展示报价初稿后 | "确认初稿，进入质检"/"需要修改价格或条款" |

> **质检阶段（阶段3）不再设用户确认节点**，质检完成后直接生成最终交付物。

**大纲展示规则（强制）：**
- 1.6 生成大纲后，**必须先完整输出大纲到聊天界面**，再调用 `AskUserQuestion` 弹出确认窗口。
- 严禁在大纲尚未完整展示时就直接弹出 `AskUserQuestion`，用户必须能看到所有内容后再做选择。

**文本输入类节点（无需 AskUserQuestion）**：需求描述(1.3)、单价输入(2.2)、用量换算(2.3)等需要用户自由文本输入的节点，仍用自然语言引导。

## 脚本调用参考

| 脚本 | 命令 | 用途 |
|------|------|------|
| state_manager.py | `init --business X --workspace W` | 创建新报价会话（会话存入 `W/报价单/{session_id}/.session/`） |
| state_manager.py | `get-state --workspace W --business X --session-id Y` | 读取会话状态 |
| state_manager.py | `set-stage --workspace W --business X --session-id Y --stage N` | 更新当前阶段 |
| state_manager.py | `sign-off --workspace W --business X --session-id Y --checkpoint Z` | 签核检查点 |
| template_parser.py | `list-vars --template T` | 列出模板变量 |
| template_parser.py | `parse --template T --data D --output O` | 解析渲染模板 |
| price_calculator.py | `calculate --products P --rules R --output O` | 生成定价上下文 |
| price_calculator.py | `save-result --result R --output O` | 保存 LLM 定价结果 |
| quality_checker.py | `check --checklist C --draft D --priced P --output O` | 执行质检 |
| excel_builder.py | `build --data J --output O` | 生成标准 Excel 报价单 |
| generators.py | `all --data J --out-dir O` | 生成全部格式（HTML+Excel+QA报告+PDF） |
| generators.py | `html --data J --output O` | 仅生成 HTML（客户端） |
| generators.py | `excel --data J --output O` | 仅生成 Excel（客户端） |
| generators.py | `qa --data J --output O` | 仅生成质检报告（内部） |
| generators.py | `pdf --html H --output O` | 仅生成 PDF |
