Skill: gh488/wood-quotation-assistant (version 1.0.1)

--- name: wood-quotation-assistant description: 木工厂报价助手。自动匹配阶梯价格、计算损耗率、生成报价明细表（工艺成本/损耗/总成本/报价金额）。支持图纸尺寸提取、多格式导出。触发关键词：算报价、图纸报价、工艺核算、生成报价表、木作报价、板材成本核算。 --- # 木工厂报价助手 ## 概述 木作/家具行业专属报价工具，支持： - 阶梯价格自动匹配 - 损耗率精确计算 - 批量折扣智能生效 - 多格式图纸解析 - 报价明细双输出（对话表格 + Excel） **适用于**：木工厂、家具厂、展柜制作、定制木作商家 ## 核算流程 ``` 图纸输入 → 图纸解析 → 工艺拆解 → 成本核算 ↓ 报价输出 ← 汇总计算 ← 损耗计算 ← 匹配采购 ``` ## 使用方式 ### 1. 图纸输入 支持格式： - **CAD/PDF 图纸**：优先适配，可直接解析尺寸、工艺要求 - **图片 + 文字描述**：兼容客户拍照/手绘图纸场景（JPG/PNG） ### 2. 报价生成 1. 解析图纸，提取尺寸、工艺要求 2. 按核算模板拆解工艺分项 3. 匹配材料单价、损耗率 4. 计算各项成本、损耗、总成本 5. 生成报价金额 ### 3. 输出格式 - **主输出**：对话中结构化表格展示 - **附加输出**：一键导出 Excel 文件（.xlsx） ## 数据配置 ### 材料价格（阶梯价格） 修改 `scripts/calculate_cost.py` 中的 `MATERIAL_PRICES`： ```python MATERIAL_PRICES = { "桦木板材(18mm)": {"1-49张": 220, "50-99张": 205, "100张以上": 190}, "E1级颗粒板(18mm)": {"1-99张": 85, "100-199张": 80, "200张以上": 75}, "水性清漆(5L)": {"1-19桶": 180, "20-49桶": 170, "50桶以上": 160}, "冷轧钢铰链": {"1-499个": 8.5, "500-999个": 8, "1000个以上": 7.5} } ``` ### 损耗率 修改 `scripts/calculate_cost.py` 中的 `LOSS_RATES`： ```python LOSS_RATES = { "桦木板材": 0.06, "E1级颗粒板": 0.03, "水性清漆": 0.01, "冷轧钢铰链": 0.01 } ``` ### 工艺单价 修改 `scripts/calculate_cost.py` 中的 `PROCESS_PRICES`： ```python PROCESS_PRICES = { "切割": 5, "封边": 3, "喷漆": 25, "烤漆": 40, "贴皮": 15, "CNC": 30, "雕刻": 35, "打磨": 8, "组装": 20, } ``` ### 利润率 修改 `scripts/calculate_cost.py` 中的 `PROFIT_MARGIN`： ```python PROFIT_MARGIN = 1.30 # 1.30 = 30% 利润率 ``` ### 模式切换 修改 `scripts/calculate_cost.py` 中的 `RUN_MODE`： ```python RUN_MODE = "INTERNAL" # 内部版：使用真实采购数据 RUN_MODE = "EXTERNAL" # 公开版：隐藏核心数据 ``` ## 触发关键词 - 算报价 - 图纸报价 - 工艺核算 - 生成报价表 - 木作报价 - 板材成本核算 ## 触发示例 - "算一下这个图纸的报价" - "帮我核算这个木作的成本" - "生成板材成本报价表" - "这个柜子的工艺报价是多少" ## 技能资源 ### scripts/ - `parse_drawing.py` — 图纸解析脚本（CAD/PDF/图片） - `calculate_cost.py` — 成本核算脚本 - `export_quotation.py` — Excel 导出脚本 ### references/ - `quotation_logic.md` — 核算逻辑说明 - `material_codes.md` — 材料编码对照表 ### data/ - `purchase_cost_data.md` — 采购数据配置参考 - `wood_quotation_template.md` — 核算模板说明 ## 注意事项 1. 首次使用请根据实际情况配置材料单价、损耗率、利润率 2. 批量采购建议调整阶梯价格档位以匹配真实供应商政策 3. Excel 导出格式可自定义调整 `export_quotation.py` 4. 图纸解析依赖尺寸标注，建议配合文字描述提高准确率

path: 使用指引.txt
content: ================================================================================
                    木工厂报价助手 - 使用指引
================================================================================

【技能名称】
wood-quotation-assistant（木工厂报价助手）

--------------------------------------------------------------------------------
【使用入口】
--------------------------------------------------------------------------------
1. 打开 QClaw
2. 进入「技能」模块
3. 找到 wood-quotation-assistant
4. 点击「启用」

--------------------------------------------------------------------------------
【核心触发词】
--------------------------------------------------------------------------------
• 算报价
• 图纸报价
• 工艺核算
• 生成报价表
• 木作报价
• 板材成本核算

--------------------------------------------------------------------------------
【使用步骤】
--------------------------------------------------------------------------------
1. 上传图纸文件（支持 CAD/PDF/JPG/PNG）
2. 输入触发词，如："算一下这个图纸的报价"
3. 可选：补充尺寸/工艺文字描述
4. 技能自动输出报价明细（对话表格 + Excel 导出）

--------------------------------------------------------------------------------
【输出内容】
--------------------------------------------------------------------------------
• 材料成本明细（阶梯价格自动匹配）
• 工艺成本明细
• 损耗核算
• 报价汇总（含利润率）
• Excel 报价单（可下载存档）

--------------------------------------------------------------------------------
【数据配置】
--------------------------------------------------------------------------------
首次使用请根据实际情况修改 scripts/calculate_cost.py：

• MATERIAL_PRICES — 材料价格（阶梯价格）
• LOSS_RATES — 损耗率
• PROCESS_PRICES — 工艺单价
• PROFIT_MARGIN — 利润率（默认 30%）

--------------------------------------------------------------------------------
【模式切换】
--------------------------------------------------------------------------------
• RUN_MODE = "INTERNAL" — 内部版，使用真实采购数据
• RUN_MODE = "EXTERNAL" — 公开版，使用示例配置

================================================================================


path: scripts/calculate_cost.py
content: """
calculate_cost.py - 成本核算脚本
复刻 wood_quotation_template.xlsx 的完整计算逻辑
"""

import json
from pathlib import Path


# ============================================================
# 模式切换：INTERNAL（内部版）/ EXTERNAL（公开版）
# ============================================================
RUN_MODE = "EXTERNAL"  # 切换为 "INTERNAL" 可启用真实采购数据


# ============================================================
# 采购配置（根据实际情况修改）
# ============================================================
if RUN_MODE == "INTERNAL":
    MATERIAL_PRICES = {
        "桦木板材(18mm)": {"1-49张": 220, "50-99张": 205, "100张以上": 190},
        "E1级颗粒板(18mm)": {"1-99张": 85, "100-199张": 80, "200张以上": 75},
        "水性清漆(5L)": {"1-19桶": 180, "20-49桶": 170, "50桶以上": 160},
        "冷轧钢铰链": {"1-499个": 8.5, "500-999个": 8, "1000个以上": 7.5}
    }

    LOSS_RATES = {
        "桦木板材": 0.06,
        "E1级颗粒板": 0.03,
        "水性清漆": 0.01,
        "冷轧钢铰链": 0.01
    }

else:
    # 公开版：示例配置，请根据实际情况修改
    MATERIAL_PRICES = {
        "板材(18mm)": {"1-49张": 200, "50-99张": 180, "100张以上": 160},
        "颗粒板(18mm)": {"1-99张": 80, "100-199张": 75, "200张以上": 70},
        "清漆(5L)": {"1-19桶": 180, "20-49桶": 160, "50桶以上": 150},
        "铰链": {"1-499个": 8, "500-999个": 7, "1000个以上": 6}
    }
    LOSS_RATES = {
        "板材": 0.05,
        "颗粒板": 0.03,
        "清漆": 0.01,
        "铰链": 0.01
    }

# 工艺单价（元/项）
PROCESS_PRICES = {
    "切割": 5,
    "封边": 3,
    "喷漆": 25,
    "烤漆": 40,
    "贴皮": 15,
    "CNC": 30,
    "雕刻": 35,
    "打磨": 8,
    "组装": 20,
}

# 报价利润率（可调整）
PROFIT_MARGIN = 1.30  # 1.30 = 30% 利润率


def calc_board_qty(width_mm, depth_mm, height_mm, board_type="多层板") -> dict:
    """
    计算板材用量（基于展开面积估算）
    返回：板材张数 + 利用率
    """
    # 每张标准板可用面积（mm²）
    BOARD_AREA = 1220 * 2440  # 约 2.98 m²

    # 估算展开系数（根据柜体类型调整）
    展开系数 = 4.5  # 柜体类平均展开系数
    展开面积_mm2 = (width_mm * depth_mm + depth_mm * height_mm + width_mm * height_mm) * 2 * 展开系数
    展开面积_m2 = 展开面积_mm2 / 1_000_000

    # 单张板材面积（m²）
    board_area_m2 = (1220 * 2440) / 1_000_000  # ≈ 2.98 m²

    # 理论张数
    raw_qty = 展开面积_m2 / board_area_m2
    # 含损耗张数（取整+损耗余量）
    qty_with_loss = round(raw_qty * 1.08, 1)

    return {
        "展开面积_m2": round(展开面积_m2, 3),
        "理论张数": round(raw_qty, 2),
        "含损耗张数": qty_with_loss,
        "利用率": f"{round(raw_qty / qty_with_loss * 100, 1)}%"
    }


def calc_process_cost(processes: list) -> dict:
    """计算工艺成本（分项）"""
    items = []
    total = 0
    for p in processes:
        if p in PROCESS_PRICES:
            price = PROCESS_PRICES[p]
            total += price
            items.append({"工艺": p, "单价(元)": price, "备注": "含损耗"})
    return {"分项": items, "工艺成本合计": total}


def calc_material_cost(materials: list, qty: float) -> dict:
    """计算材料成本（支持阶梯价格精确匹配）"""
    items = []
    total = 0
    for m in materials:
        if m in MATERIAL_PRICES:
            price_tiers = MATERIAL_PRICES[m]
            # 根据用量精确匹配阶梯价格
            price = None
            matched_tier = None
            
            # 解析阶梯价格区间
            for tier, p in price_tiers.items():
                # 匹配格式：如 "1-49张"、"100张以上"、"1-99张"
                if "以上" in tier or "+" in tier:
                    # 无上限档位
                    min_qty = int(tier.replace("张以上", "").replace("个以上", "").replace("桶以上", "").replace("+", "").replace("以上", ""))
                    if qty >= min_qty:
                        price = p
                        matched_tier = tier
                elif "-" in tier:
                    # 区间档位
                    parts = tier.replace("张", "").replace("个", "").replace("桶", "").split("-")
                    min_qty = int(parts[0])
                    max_qty = int(parts[1])
                    if min_qty <= qty <= max_qty:
                        price = p
                        matched_tier = tier
            
            # 默认取第一档
            if price is None:
                first_tier = list(price_tiers.keys())[0]
                price = price_tiers[first_tier]
                matched_tier = first_tier
            
            cost = price * qty
            total += cost
            items.append({
                "材料": m,
                "价格档位": matched_tier,
                "单价": price,
                "用量": qty,
                "金额(元)": round(cost, 2)
            })
    return {"分项": items, "材料成本合计": round(total, 2)}


def calc_loss(materials: list, material_cost: float, process_cost: float) -> dict:
    """计算综合损耗（材料损耗）"""
    # 材料损耗率
    combined_rate = sum(LOSS_RATES.get(m.split("(")[0], 0) for m in materials)
    # 含材料损耗
    total_base = material_cost + process_cost
    loss = round(total_base * combined_rate, 2)
    return {
        "综合损耗率": f"{round(combined_rate * 100, 1)}%",
        "损耗金额(元)": loss
    }


def generate_quotation(parsing_result: dict) -> dict:
    """
    主入口：根据图纸解析结果生成完整报价
    Args:
        parsing_result: parse_drawing.py 的输出
    Returns:
        dict: 完整报价明细
    """
    dimensions = parsing_result.get("dimensions", {})
    materials = parsing_result.get("materials", [])
    processes = parsing_result.get("processes", [])

    if not dimensions:
        return {
            "status": "error",
            "message": "未能从图纸中提取尺寸数据，请补充尺寸信息（格式：W*D*H，如1200*600*800）"
        }

    # 取第一个识别的尺寸
    dim_key = list(dimensions.keys())[0]
    dim = dimensions[dim_key]
    w, d, h = dim["width"], dim["depth"], dim["height"]

    # 1. 板材用量
    board_calc = calc_board_qty(w, d, h)
    qty = board_calc["含损耗张数"]

    # 2. 材料成本
    default_materials = ["桦木板材(18mm)"]
    mat_cost_result = calc_material_cost(materials if materials else default_materials, qty)
    material_cost = mat_cost_result["材料成本合计"]

    # 3. 工艺成本
    proc_cost_result = calc_process_cost(processes if processes else ["切割", "封边", "打磨", "组装"])

    # 4. 损耗
    loss_result = calc_loss(materials if materials else default_materials, material_cost, proc_cost_result["工艺成本合计"])

    # 5. 总成本
    工艺成本 = proc_cost_result["工艺成本合计"]
    损耗金额 = loss_result["损耗金额(元)"]
    总成本 = round(material_cost + 工艺成本 + 损耗金额, 2)

    # 6. 报价金额
    报价金额 = round(总成本 * PROFIT_MARGIN, 2)

    # 汇总
    return {
        "status": "success",
        "图纸尺寸": dim_key,
        "宽_w": w,
        "深_d": d,
        "高_h": h,
        "材料明细": mat_cost_result["分项"],
        "工艺明细": proc_cost_result["分项"],
        "板材用量": board_calc,
        "损耗": loss_result,
        "工艺成本": 工艺成本,
        "材料成本": material_cost,
        "总成本": 总成本,
        "报价金额": 报价金额,
        "利润率": f"{(PROFIT_MARGIN - 1) * 100:.0f}%"
    }


def format_quotation_table(quotation: dict) -> str:
    """生成 Markdown 报价表格（对话展示用）"""
    if quotation.get("status") == "error":
        return f"❌ 报价失败：{quotation.get('message')}"

    lines = [
        "## 📋 木作报价明细",
        "",
        f"**图纸尺寸**：{quotation.get('图纸尺寸')}（W{quotation['宽_w']} × D{quotation['深_d']} × H{quotation['高_h']}mm）",
        "",
        "### 一、材料成本",
        "| 材料 | 价格档位 | 单价 | 用量 | 金额 |",
        "|------|----------|------|------|------|",
    ]

    for m in quotation.get("材料明细", []):
        lines.append(f"| {m['材料']} | {m['价格档位']} | {m['单价']}元 | {m['用量']} | {m['金额(元)']}元 |")
    lines.append(f"| **材料合计** | | | | **{quotation['材料成本']}元** |")
    lines.append("")

    lines.append("### 二、工艺成本")
    lines.append("| 工艺 | 单价 |")
    lines.append("|------|------|")
    for p in quotation.get("工艺明细", []):
        lines.append(f"| {p['工艺']} | {p['单价(元)']}元 |")
    lines.append(f"| **工艺合计** | **{quotation['工艺成本']}元** |")
    lines.append("")

    lines.append("### 三、损耗核算")
    lines.append(f"- 综合损耗率：{quotation['损耗']['综合损耗率']}")
    lines.append(f"- 损耗金额：{quotation['损耗']['损耗金额(元)']}元")
    lines.append("")

    lines.append("### 四、报价汇总")
    lines.append(f"- **板材用量**：{quotation['板材用量']['含损耗张数']}张（利用率 {quotation['板材用量']['利用率']}）")
    lines.append(f"- **总成本**：{quotation['总成本']}元")
    lines.append(f"- **报价金额**：💰 **{quotation['报价金额']}元**")
    lines.append(f"- **利润率**：{quotation['利润率']}")
    lines.append("")

    lines.append("*本报价仅供内部参考，不对外泄露采购数据。*")

    return "\n".join(lines)


if __name__ == "__main__":
    import sys
    if len(sys.argv) > 1:
        # 接收 parse_drawing.py 的输出
        parsing_result = json.loads(sys.argv[1])
    else:
        # 默认测试数据
        parsing_result = {
            "dimensions": {"W1200_D600_H800": {"width": 1200, "depth": 600, "height": 800}},
            "materials": ["桦木板材(18mm)"],
            "processes": ["切割", "封边", "喷漆", "打磨", "组装"]
        }

    quotation = generate_quotation(parsing_result)
    print(format_quotation_table(quotation))


path: scripts/export_quotation.py
content: """
export_quotation.py - 报价导出脚本
生成与 wood_quotation_template.xlsx 格式一致的报价 Excel 文件
"""

import json
import sys
from pathlib import Path

try:
    import openpyxl
    from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
    from openpyxl.utils import get_column_letter
except ImportError:
    print("❌ openpyxl 未安装，执行: pip install openpyxl")
    sys.exit(1)


def create_quotation_workbook(quotation: dict, output_path: str):
    """
    创建报价 Excel 工作簿
    对齐 wood_quotation_template.xlsx 的格式和列结构
    """
    wb = openpyxl.Workbook()

    # ---------- Sheet1: 报价明细 ----------
    ws = wb.active
    ws.title = "报价明细"

    # 样式定义
    header_font = Font(bold=True, size=12, color="FFFFFF")
    header_fill = PatternFill("solid", fgColor="2E75B6")  # 蓝色表头
    subheader_fill = PatternFill("solid", fgColor="4472C4")
    money_font = Font(bold=True, size=14, color="C00000")  # 红色金额
    thin_border = Border(
        left=Side(style="thin"),
        right=Side(style="thin"),
        top=Side(style="thin"),
        bottom=Side(style="thin")
    )

    def style_header(cell, text):
        cell.value = text
        cell.font = header_font
        cell.fill = header_fill
        cell.alignment = Alignment(horizontal="center", vertical="center")
        cell.border = thin_border

    def style_cell_simple(row, col, value, bold=False, align="left"):
        cell = ws.cell(row=row, column=col)
        cell.value = value
        cell.font = Font(bold=bold)
        cell.alignment = Alignment(horizontal=align, vertical="center")
        cell.border = thin_border
        return cell

    # 第1行：标题
    ws.merge_cells("A1:F1")
    title_cell = ws["A1"]
    title_cell.value = "黄杰木工厂 · 定制木作报价单"
    title_cell.font = Font(bold=True, size=16, color="1F4E79")
    title_cell.alignment = Alignment(horizontal="center", vertical="center")
    title_cell.fill = PatternFill("solid", fgColor="D9E2F3")
    ws.row_dimensions[1].height = 36

    # 第2行：图纸尺寸
    ws.merge_cells("A2:F2")
    dim_cell = ws["A2"]
    dim_cell.value = f"图纸尺寸：{quotation.get('图纸尺寸', 'N/A')}（宽{quotation['宽_w']} × 深{quotation['深_d']} × 高{quotation['高_h']}mm）"
    dim_cell.font = Font(size=11)
    dim_cell.alignment = Alignment(horizontal="center")
    dim_cell.fill = PatternFill("solid", fgColor="EAF2FF")
    ws.row_dimensions[2].height = 24

    # 第3行：空行
    ws.row_dimensions[3].height = 6

    # ---------- 材料成本 ----------
    row = 4
    ws.merge_cells(f"A{row}:F{row}")
    style_header(ws.cell(row=row, column=1), "一、材料成本")
    row += 1

    # 材料表头
    headers = ["材料名称", "价格档位", "单价(元)", "用量", "金额(元)", "备注"]
    for col, h in enumerate(headers, 1):
        style_header(ws.cell(row=row, column=col), h)
    row += 1

    for m in quotation.get("材料明细", []):
        style_cell_simple(row, 1, m["材料"])
        style_cell_simple(row, 2, m.get("规格", m.get("价格档位", "")))
        style_cell_simple(row, 3, m["单价"], align="right")
        style_cell_simple(row, 4, m["用量"], align="right")
        style_cell_simple(row, 5, m["金额(元)"], align="right")
        style_cell_simple(row, 6, "")
        row += 1

    # 材料合计
    ws.merge_cells(f"A{row}:D{row}")
    style_cell_simple(row, 1, "材料成本合计", bold=True)
    ws.cell(row=row, column=5).value = quotation.get("材料成本", 0)
    ws.cell(row=row, column=5).font = Font(bold=True)
    ws.cell(row=row, column=5).alignment = Alignment(horizontal="right")
    ws.cell(row=row, column=5).border = thin_border
    ws.cell(row=row, column=6).border = thin_border
    row += 2

    # ---------- 工艺成本 ----------
    ws.merge_cells(f"A{row}:F{row}")
    style_header(ws.cell(row=row, column=1), "二、工艺成本")
    row += 1

    headers2 = ["工艺名称", "单价(元)", "备注", "", "", ""]
    for col, h in enumerate(headers2[:3], 1):
        style_header(ws.cell(row=row, column=col), h)
    row += 1

    for p in quotation.get("工艺明细", []):
        style_cell_simple(row, 1, p["工艺"])
        style_cell_simple(row, 2, p["单价(元)"], align="right")
        style_cell_simple(row, 3, p.get("备注", "含损耗"))
        row += 1

    ws.merge_cells(f"A{row}:B{row}")
    style_cell_simple(row, 1, "工艺成本合计", bold=True)
    ws.cell(row=row, column=3).value = quotation.get("工艺成本", 0)
    ws.cell(row=row, column=3).font = Font(bold=True)
    ws.cell(row=row, column=3).alignment = Alignment(horizontal="right")
    ws.cell(row=row, column=3).border = thin_border
    row += 2

    # ---------- 损耗 ----------
    ws.merge_cells(f"A{row}:F{row}")
    style_header(ws.cell(row=row, column=1), "三、损耗核算")
    row += 1

    style_cell_simple(row, 1, "综合损耗率")
    ws.cell(row=row, column=2).value = quotation["损耗"]["综合损耗率"]
    ws.cell(row=row, column=2).alignment = Alignment(horizontal="right")
    ws.cell(row=row, column=2).border = thin_border
    row += 1

    style_cell_simple(row, 1, "损耗金额")
    ws.cell(row=row, column=2).value = quotation["损耗"]["损耗金额(元)"]
    ws.cell(row=row, column=2).alignment = Alignment(horizontal="right")
    ws.cell(row=row, column=2).border = thin_border
    row += 2

    # ---------- 报价汇总 ----------
    ws.merge_cells(f"A{row}:F{row}")
    style_header(ws.cell(row=row, column=1), "四、报价汇总")
    row += 1

    summary = [
        ("板材用量", f"{quotation['板材用量']['含损耗张数']}张（利用率 {quotation['板材用量']['利用率']}）"),
        ("总成本", f"{quotation['总成本']}元"),
        ("利润率", quotation["利润率"]),
    ]
    for label, val in summary:
        style_cell_simple(row, 1, label)
        ws.merge_cells(f"B{row}:C{row}")
        ws.cell(row=row, column=2).value = val
        ws.cell(row=row, column=2).alignment = Alignment(horizontal="right")
        ws.cell(row=row, column=2).border = thin_border
        row += 1

    # 报价金额（重点高亮）
    row += 1
    ws.merge_cells(f"A{row}:C{row}")
    price_cell = ws.cell(row=row, column=1, value="💰 报价金额（含利润）")
    price_cell.font = Font(bold=True, size=14, color="FFFFFF")
    price_cell.fill = PatternFill("solid", fgColor="C00000")
    price_cell.alignment = Alignment(horizontal="right", vertical="center")
    price_cell.border = thin_border

    ws.merge_cells(f"D{row}:F{row}")
    ws.cell(row=row, column=4).value = f"{quotation['报价金额']}元"
    ws.cell(row=row, column=4).font = Font(bold=True, size=16, color="C00000")
    ws.cell(row=row, column=4).alignment = Alignment(horizontal="center", vertical="center")
    ws.cell(row=row, column=4).fill = PatternFill("solid", fgColor="FCE4D6")
    ws.cell(row=row, column=4).border = thin_border
    ws.row_dimensions[row].height = 32
    row += 2

    # 底部说明
    ws.merge_cells(f"A{row}:F{row}")
    ws.cell(row=row, column=1).value = "⚠️ 本报价单仅供内部参考，采购数据不对外泄露。如有疑问请联系：黄杰（0592-7153916）"
    ws.cell(row=row, column=1).font = Font(size=9, color="808080", italic=True)
    ws.cell(row=row, column=1).alignment = Alignment(horizontal="center")

    # 列宽
    col_widths = [20, 16, 14, 10, 14, 16]
    for i, w in enumerate(col_widths, 1):
        ws.column_dimensions[get_column_letter(i)].width = w

    # ---------- Sheet2: 数据源 ----------
    ws2 = wb.create_sheet("数据源")
    ws2["A1"] = "来源"
    ws2["B1"] = "内容"
    ws2["A2"] = "核算模板"
    ws2["B2"] = "./data/wood_quotation_template.xlsx"
    ws2["A3"] = "采购数据"
    ws2["B3"] = "./data/purchase_cost_data.xlsx"

    wb.save(output_path)
    return output_path


if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("用法: python export_quotation.py <报价JSON> <输出路径.xlsx>")
        print("示例: python export_quotation.py '{\"status\":\"success\",...}' ./output.xlsx")
        sys.exit(1)

    quotation = json.loads(sys.argv[1])
    output_path = sys.argv[2]

    if quotation.get("status") == "error":
        print(f"❌ 导出失败：{quotation.get('message')}")
        sys.exit(1)

    path = create_quotation_workbook(quotation, output_path)
    print(f"✅ 报价单已导出：{path}")


path: scripts/parse_drawing.py
content: """
parse_drawing.py - 图纸解析脚本
支持：CAD (DWG) / PDF / 图片 (JPG/PNG)
提取：尺寸数据、工艺要求、材料说明
"""

import os
import sys
import json
from pathlib import Path


def parse_dwg(file_path: str) -> dict:
    """解析 DWG CAD 图纸"""
    # TODO: 接入 LibreCAD / ODA File Converter 或 ezdxf 库
    # 现阶段返回占位结构，等待接入真实解析引擎
    return {
        "format": "dwg",
        "file": file_path,
        "status": "pending",
        "dimensions": {},
        "materials": [],
        "processes": [],
        "note": "请补充 CAD 图纸解析实现"
    }


def parse_pdf(file_path: str) -> dict:
    """解析 PDF 图纸（文字+尺寸提取）"""
    try:
        import pdfplumber
    except ImportError:
        return {
            "format": "pdf",
            "file": file_path,
            "status": "error",
            "error": "pdfplumber 未安装，执行: pip install pdfplumber"
        }

    result = {
        "format": "pdf",
        "file": file_path,
        "status": "success",
        "dimensions": {},
        "materials": [],
        "processes": [],
        "raw_text": ""
    }

    with pdfplumber.open(file_path) as pdf:
        for page in pdf.pages:
            text = page.extract_text() or ""
            result["raw_text"] += text + "\n"
            # 尺寸正则：识别 "W1200*D600*H800" / "1200×600×800" 等格式
            import re
            dim_pattern = re.findall(r'(\d+[\*×x]\d+[\*×x]\d+)', text)
            for d in dim_pattern:
                parts = re.split(r'[\*×x]', d)
                if len(parts) == 3:
                    w, d_h, h = parts
                    result["dimensions"][f"W{w}_D{d_h}_H{h}"] = {
                        "width": int(w), "depth": int(d_h), "height": int(h)
                    }
            # 工艺关键词
            keywords = ["喷漆", "烤漆", "贴皮", "封边", "CNC", "雕刻", "打磨", "组装"]
            for kw in keywords:
                if kw in text:
                    if kw not in result["processes"]:
                        result["processes"].append(kw)
            # 材料关键词
            mat_keywords = ["密度板", "颗粒板", "多层板", "实木", "欧松板", "刨花板"]
            for m in mat_keywords:
                if m in text:
                    if m not in result["materials"]:
                        result["materials"].append(m)

    return result


def parse_image(file_path: str, description: str = "") -> dict:
    """解析图片图纸 + 文字描述"""
    # TODO: 接入 OCR（pytesseract）或视觉模型识别尺寸/工艺
    # 现阶段依赖 description 字段作为主要输入
    result = {
        "format": "image",
        "file": file_path,
        "status": "partial",
        "dimensions": {},
        "materials": [],
        "processes": [],
        "raw_description": description
    }

    import re
    # 从 description 中解析尺寸
    dim_pattern = re.findall(r'(\d+[\*×x]\d+[\*×x]\d+)', description)
    for d in dim_pattern:
        parts = re.split(r'[\*×x]', d)
        if len(parts) == 3:
            w, d_h, h = parts
            result["dimensions"][f"W{w}_D{d_h}_H{h}"] = {
                "width": int(w), "depth": int(d_h), "height": int(h)
            }

    keywords = ["喷漆", "烤漆", "贴皮", "封边", "CNC", "雕刻", "打磨", "组装"]
    for kw in keywords:
        if kw in description and kw not in result["processes"]:
            result["processes"].append(kw)

    mat_keywords = ["密度板", "颗粒板", "多层板", "实木", "欧松板", "刨花板"]
    for m in mat_keywords:
        if m in description and m not in result["materials"]:
            result["materials"].append(m)

    return result


def parse_drawing(file_path: str, description: str = "") -> dict:
    """
    主入口：根据文件扩展名自动路由解析器
    Args:
        file_path: 图纸文件路径
        description: 用户提供的文字描述（图片场景补充用）
    Returns:
        dict: 解析结果，含 dimensions / materials / processes
    """
    ext = Path(file_path).suffix.lower()
    if ext in [".dwg", ".dxf"]:
        return parse_dwg(file_path)
    elif ext == ".pdf":
        return parse_pdf(file_path)
    elif ext in [".jpg", ".jpeg", ".png", ".bmp"]:
        return parse_image(file_path, description)
    else:
        return {
            "format": "unknown",
            "file": file_path,
            "status": "error",
            "error": f"不支持的图纸格式: {ext}，支持: DWG/DXF/PDF/JPG/PNG"
        }


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python parse_drawing.py <图纸路径> [文字描述]")
        sys.exit(1)
    file_path = sys.argv[1]
    description = sys.argv[2] if len(sys.argv) > 2 else ""
    result = parse_drawing(file_path, description)
    print(json.dumps(result, ensure_ascii=False, indent=2))


path: references/material_codes.md
content: # 材料编码对照表

## 一、板材编码

| 编码 | 名称 | 规格 | 厚度 | 单位 |
|-----|------|-----|------|------|
| MB001 | 中密度纤维板 | 1220×2440 | 18mm | 张 |
| MB002 | 刨花板 | 1220×2440 | 18mm | 张 |
| MB003 | 多层实木板 | 1220×2440 | 18mm | 张 |
| MB004 | 实木直拼板 | 定制 | 20mm | m³ |
| MB005 | 定向刨花板 | 1220×2440 | 18mm | 张 |

## 二、封边带编码

| 编码 | 名称 | 规格 | 单位 |
|-----|------|-----|------|
| FB001 | PVC封边带 | 22mm×1mm | 卷(100m) |
| FB002 | ABS封边带 | 22mm×1mm | 卷(100m) |
| FB003 | 木皮封边带 | 22mm×0.5mm | 卷(50m) |

## 三、五金编码

| 编码 | 名称 | 规格 | 单位 |
|-----|------|-----|------|
| HJ001 | 铰链-普通 | 110°全盖 | 个 |
| HJ002 | 铰链-阻尼 | 110°全盖 | 个 |
| HJ003 | 滑轨-三节 | 450mm | 副 |
| HJ004 | 滑轨-骑马抽 | 450mm | 副 |
| HJ005 | 拉手-铝合金 | 128mm | 个 |

## 四、油漆编码

| 编码 | 名称 | 单位 |
|-----|------|------|
| PT001 | PU底漆 | 组(5kg) |
| PT002 | PU面漆 | 组(5kg) |
| PT003 | PE底漆 | 组(5kg) |
| PT004 | NC硝基漆 | 组(5kg) |
| PT005 | 水性漆 | 组(5kg) |

## 五、使用说明

1. **编码查询**：脚本中通过编码快速匹配材料单价
2. **价格更新**：修改 `calculate_cost.py` 中的配置
3. **新增材料**：按编码规则扩展，保持格式一致


path: references/quotation_logic.md
content: # 木工厂报价核算逻辑说明

## 一、核算模板

**文件路径**：`./data/wood_quotation_template.xlsx`
**说明**：本技能复刻该 Excel 模板的核算流程，包括所有公式、损耗率、工艺分项逻辑。

---

## 二、工艺分项体系

### 2.1 标准工艺清单

| 工艺编号 | 工艺名称 | 计价单位 | 单价（元） | 损耗率 | 说明 |
|---------|---------|---------|-----------|--------|------|
| P01 | 切割 | 项 | 5 | 3% | 含精裁、开料 |
| P02 | 封边 | 项 | 3/米 | 2% | PVC/ABS封边带 |
| P03 | 喷漆 | 项 | 25 | 8% | 底漆+面漆 |
| P04 | 烤漆 | 项 | 40 | 10% | 钢琴烤漆工艺 |
| P05 | 贴皮 | 项 | 15 | 5% | 木皮/科技木皮 |
| P06 | CNC | 项 | 30 | 4% | 数控加工/镂铣 |
| P07 | 雕刻 | 项 | 35 | 6% | 激光/数控雕刻 |
| P08 | 打磨 | 项 | 8 | 3% | 粗磨+细磨 |
| P09 | 组装 | 项 | 20 | 2% | 五金组装+调整 |

### 2.2 常见工艺组合

**标准柜体**（默认组合）：
切割 + 封边 + 打磨 + 组装

**油漆柜体**：
切割 + 封边 + 打磨 + 喷漆 + 组装

**高端烤漆柜**：
切割 + 封边 + CNC + 打磨 + 烤漆 + 组装

**贴皮柜体**：
切割 + 封边 + 贴皮 + 组装

---

## 三、板材核算逻辑

### 3.1 标准板材规格

| 板材类型 | 规格（mm） | 厚度 | 适用场景 |
|---------|-----------|------|---------|
| 密度板 | 1220×2440 | 18mm | 吸塑/烤漆基材 |
| 颗粒板 | 1220×2440 | 18mm | 性价比柜体 |
| 多层板 | 1220×2440 | 18mm | 中高端柜体 |
| 实木板 | 定制 | 20mm | 高端定制 |
| 欧松板 | 1220×2440 | 18mm | 载奢风格 |

### 3.2 板材用量计算公式

```
展开面积（m²）= (宽×深 + 深×高 + 宽×高) × 2 × 展开系数
理论张数 = 展开面积 ÷ 单张板材面积（2.98m²）
含损耗张数 = 理论张数 × 1.08（8%加工损耗）
```

**展开系数参考**：
- 衡柜/书柜：4.5
- 橱柜：4.8
- 酒柜/展示柜：5.2
- 异形柜体：5.5

---

## 四、损耗计算

### 4.1 综合损耗率

```python
综合损耗率 = Σ(各工艺损耗率)
```

### 4.2 损耗金额

```python
损耗金额 = (材料成本 + 工艺成本) × 综合损耗率
```

---

## 五、成本汇总

### 5.1 计算顺序

```
1. 板材用量 → 2. 材料成本 → 3. 工艺成本 → 4. 损耗金额 → 5. 总成本
```

### 5.2 总成本公式

```python
总成本 = 材料成本 + 工艺成本 + 损耗金额
```

### 5.3 报价金额公式

```python
报价金额 = 总成本 × (1 + 利润率)
默认利润率 = 30%
```

---

## 六、输出格式

### 6.1 对话表格格式

Markdown 结构化表格，展示：材料明细、工艺明细、损耗核算、报价汇总。

### 6.2 Excel 导出格式

与 `wood_quotation_template.xlsx` 结构一致，含：
- Sheet1：报价明细（含完整分项）
- Sheet2：数据源记录
- 报价金额单元格红色高亮


path: data/purchase_cost_data.md
content: # 采购数据配置说明

## 配置位置

修改 `scripts/calculate_cost.py` 中的以下变量：

### MATERIAL_PRICES（材料价格）

```python
MATERIAL_PRICES = {
    "板材(18mm)": {"1-49张": 200, "50-99张": 180, "100张以上": 160},
    "颗粒板(18mm)": {"1-99张": 80, "100-199张": 75, "200张以上": 70},
}
```

### LOSS_RATES（损耗率）

```python
LOSS_RATES = {
    "板材": 0.05,
    "颗粒板": 0.03,
}
```

### PROCESS_PRICES（工艺单价）

```python
PROCESS_PRICES = {
    "切割": 5,
    "封边": 3,
    "喷漆": 25,
}
```

### PROFIT_MARGIN（利润率）

```python
PROFIT_MARGIN = 1.30  # 30% 利润率
```

## 阶梯价格说明

格式：`{"区间": 单价}`

示例：
- `"1-49张": 200` — 1到49张，单价200元
- `"50-99张": 180` — 50到99张，单价180元
- `"100张以上": 160` — 100张及以上，单价160元

## 更新步骤

1. 打开 `scripts/calculate_cost.py`
2. 修改对应变量
3. 保存文件
4. 重启 QClaw


path: data/wood_quotation_template.md
content: # 核算模板说明

## 模板结构

### Sheet1: 报价明细

| 列号 | 列名 | 说明 |
|-----|------|------|
| A | 项目名称 | 材料/工艺名称 |
| B | 规格/型号 | 尺寸规格 |
| C | 单价 | 元/单位 |
| D | 数量 | 用量 |
| E | 金额 | 单价×数量 |
| F | 备注 | 损耗说明等 |

### Sheet2: 成本汇总

- 材料成本合计
- 工艺成本合计
- 损耗金额
- 总成本
- 报价金额（含利润率）

## 计算逻辑

1. 板材用量 = 展开面积 ÷ 单张面积 × (1 + 损耗率)
2. 材料成本 = Σ(单价 × 数量)
3. 工艺成本 = Σ(工艺单价)
4. 损耗金额 = (材料成本 + 工艺成本) × 损耗率
5. 总成本 = 材料成本 + 工艺成本 + 损耗金额
6. 报价金额 = 总成本 × (1 + 利润率)

## 利润率配置

- 默认利润率：30%
- 可调整范围：20%-50%
- 修改位置：`scripts/calculate_cost.py` 中的 `PROFIT_MARGIN`


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)