#!/usr/bin/env python3
"""Compile the latest smart-canteen source batch into one Markdown knowledge base."""

from __future__ import annotations

import argparse
import csv
import re
from collections import Counter, OrderedDict
from pathlib import Path

from openpyxl import load_workbook


EXCLUDED_CATALOG_ITEMS = {"实施运杂费", "售后服务维保费用"}
SENSITIVE_TERMS = re.compile(r"(?i)(密码|口令|password|token|secret)")


def text(value: object) -> str:
    if value is None:
        return ""
    value = re.sub(r"\s+", " ", str(value)).strip()
    if SENSITIVE_TERMS.search(value):
        return "涉及账号或口令的内容已脱敏"
    return value.replace("|", "｜")


def compact(value: str, limit: int = 220) -> str:
    value = text(value)
    if len(value) <= limit:
        return value
    shortened = value[:limit].rsplit("，", 1)[0].rsplit("；", 1)[0]
    return (shortened or value[:limit]).rstrip("，；。 ") + "。"


def read_catalog(path: Path) -> OrderedDict[str, list[dict[str, str]]]:
    workbook = load_workbook(path, read_only=True, data_only=True)
    result: OrderedDict[str, list[dict[str, str]]] = OrderedDict()
    for sheet in workbook:
        rows = sheet.iter_rows(min_col=1, max_col=12, values_only=True)
        header = [text(value) for value in next(rows)]
        header = [text(value) for value in next(rows)]
        positions = {name: header.index(name) for name in ("设备名称", "型号", "功能介绍", "使用场景")}
        products = []
        seen = set()
        for row in rows:
            values = [text(value) for value in row]
            name = values[positions["设备名称"]]
            model = values[positions["型号"]]
            if not name or name in EXCLUDED_CATALOG_ITEMS or name == "设备名称":
                continue
            key = (name, model)
            if key in seen:
                continue
            seen.add(key)
            products.append(
                {
                    "name": name,
                    "model": model or "按项目确认",
                    "function": compact(values[positions["功能介绍"]], 190) or "见当前产品配置与项目清单。",
                    "scene": compact(values[positions["使用场景"]], 170) or "按项目场景和配置确认。",
                }
            )
        result[sheet.title] = products
    return result


def read_functions(path: Path) -> OrderedDict[str, list[dict[str, str]]]:
    workbook = load_workbook(path, read_only=True, data_only=True)
    result: OrderedDict[str, list[dict[str, str]]] = OrderedDict()
    for sheet in workbook:
        rows = list(sheet.iter_rows(values_only=True))
        header = [text(value) for value in rows[2]]
        positions = {name: header.index(name) for name in ("终端", "一级模块", "二级功能", "详细功能说明", "适用场景 / 核心价值")}
        role_columns = list(range(positions["详细功能说明"] + 1, positions["适用场景 / 核心价值"]))
        terminal = ""
        module = ""
        functions = []
        for row in rows[3:]:
            values = [text(value) for value in row]
            if positions["终端"] < len(values) and values[positions["终端"]]:
                terminal = values[positions["终端"]]
            if positions["一级模块"] < len(values) and values[positions["一级模块"]]:
                module = values[positions["一级模块"]]
            feature = values[positions["二级功能"]] if positions["二级功能"] < len(values) else ""
            description = values[positions["详细功能说明"]] if positions["详细功能说明"] < len(values) else ""
            if not feature or not description:
                continue
            core_roles = []
            related_roles = []
            for column in role_columns:
                marker = values[column] if column < len(values) else ""
                label = header[column] if column < len(header) else ""
                if marker == "✅":
                    core_roles.append(label)
                elif marker == "🌟":
                    related_roles.append(label)
            functions.append(
                {
                    "terminal": terminal,
                    "module": module,
                    "feature": feature,
                    "description": compact(description, 240),
                    "core_roles": "、".join(core_roles) or "按权限配置",
                    "related_roles": "、".join(related_roles),
                    "value": compact(values[positions["适用场景 / 核心价值"]], 140),
                }
            )
        result[sheet.title] = functions
    return result


def read_sources(path: Path) -> list[dict[str, str]]:
    with path.open(encoding="utf-8-sig", newline="") as handle:
        return list(csv.DictReader(handle))


def md_table(headers: list[str], rows: list[list[str]]) -> list[str]:
    result = ["| " + " | ".join(headers) + " |", "| " + " | ".join("---" for _ in headers) + " |"]
    result.extend("| " + " | ".join(text(cell) for cell in row) + " |" for row in rows)
    return result


def build(catalog_path: Path, function_path: Path, source_index: Path, output: Path) -> None:
    catalog = read_catalog(catalog_path)
    functions = read_functions(function_path)
    sources = read_sources(source_index)
    product_count = sum(len(items) for items in catalog.values())
    function_count = sum(len(items) for items in functions.values())

    lines = [
        "---",
        "title: 康比特智慧食堂产品知识库",
        "status: current",
        "updated_at: 2026-09-16",
        "owner: 产品团队",
        "source_batch: 2026-09-product-introduction-materials",
        "image_policy: no-image-dependency",
        "---",
        "",
        "# 康比特智慧食堂产品知识库",
        "",
        "> 本文是智慧食堂产品资料的当前 Markdown 真源。原 PPT、Excel、Word 和图片继续保留为证据与版式来源，但不再承担日常知识检索。产品名称、型号和场景以 2026 年 9 月产品清单为优先依据；软件能力以 2026 年 9 月功能矩阵为优先依据。报价、数量、投标参数、认证和项目承诺不在本文冻结。",
        "",
        "## 1. 使用范围与更新规则",
        "",
        "- 面向产品、售前、销售、交付、研发和后续 Agent，统一回答“卖什么、适合谁、怎么组合、边界在哪里”。",
        "- 当前覆盖营养结算、智慧食安、智慧采购，以及政企、学校、医院和康养场景的组合逻辑。",
        "- 产品清单中的图片已转为结构化文字条目；本文没有图片依赖，也不引用桌面或个人缓存图片路径。",
        "- 新资料先进入批次来源索引，再编译进本文。不要再新建平行的 current 产品总览。",
        "- 精确配置、价格、数量、实施范围和验收口径以审批后的项目配置、报价、合同及验收文件为准。",
        "",
        "### 资料优先级",
        "",
        "1. `产品清单及场景介绍202609.xlsx`：产品名称、型号、产品功能、使用场景。",
        "2. `软件功能描述202609.xlsx`：三套系统的端、模块、功能、角色和价值。",
        "3. 20260914 方案与知识问答库：产品叙事、组合逻辑、售前边界。",
        "4. 20260909—20260915 操作手册：真实操作路径与角色动作。",
        "5. 其他 PPT：场景补充和表达参考，不覆盖前四级主真源。",
        "",
        "### 已识别的版本冲突",
        "",
        "2026 年 9 月产品清单把营养结算 PC/手机端型号写为 `CPT-NP- V2.0（PC端）`、`CPT-NM-V2.0（手机端）`，早期资料使用 `CPT-Nutr-GMPLTF V2.0`、`CPT-Nutr-GMMOB V2.0`。本文按最新清单原文记录新型号，同时保留旧型号供版本追溯；在产品负责人确认命名切换前，不把二者直接认定为同一产品的正式别名，也不用于正式对外、报价或投标。",
        "",
        "## 2. 产品定位",
        "",
        "康比特智慧食堂以统一的数据和业务平台连接就餐交易、营养健康、食品安全、采购库存和经营管理。目标不是单独替换收银设备，而是形成从餐前计划、餐中服务到餐后分析，再到后厨采购与食安治理的持续运营体系。",
        "",
        "推荐表达：在快速结算基础上，把营养干预、食品安全和经营管理连接起来，让用餐者获得清晰反馈，让食堂运营过程可追溯，让管理者能够用数据决策。",
        "",
        "## 3. 总体架构",
        "",
        "- 一个平台：统一组织、人员、账户、权限、菜品、食材、订单、风险事件、库存和经营数据。",
        "- 三套系统：营养结算服务系统、智慧食安监管系统、智慧采购系统。",
        "- 多端协同：PC 管理端、手机端/小程序、公众 H5、大屏端、供应商端和智能设备端。",
        "- N 类硬件：按餐线、档口、监管点位、仓库、验收点和健康服务点组合。",
        "- 两种部署：公有云适合快速上线与跨地点访问；本地或内网部署适合数据本地化、涉密或统一运维场景。最终架构须结合网络、安全、并发、接口和运维条件确认。",
        "",
        "## 4. 三套系统",
        "",
        "### 4.1 营养结算服务系统",
        "",
        "覆盖人员、账户、补贴、消费规则、菜品与营养、订单、营收对账、订餐、称重结算、AI 识别结算、健康档案、个人反馈和群体分析。",
        "",
        "- 餐前：维护人员与膳食目标，配置菜品、食谱、价格、补贴和消费规则；通过订餐或历史数据辅助备餐。",
        "- 餐中：支持称重自助、小碗菜 AI 识别、风味档口、刷脸、刷卡、扫码、托盘绑定和多种收银终端。",
        "- 餐后：形成个人单餐分析、阶段报告、部门或群体分析、菜品热度、营养结构、营收和对账数据。",
        "",
        "### 4.2 智慧食安监管系统",
        "",
        "覆盖明厨亮灶、视频巡查、AI 行为识别、晨检、留样、农残与快速检测、消毒、废弃物、设备检查、证照健康证、风险预警、整改复核和监管驾驶舱。",
        "",
        "- 学校/食堂执行端负责日常记录、现场处置和证据上传。",
        "- 管理端负责待办、预警、台账、设备与人员资质。",
        "- 监管端支持跨学校或跨食堂查看、督办、复核和风险统计。",
        "- 公众 H5 与大屏可展示授权范围内的阳光厨房、公示与意见反馈。",
        "",
        "### 4.3 智慧采购系统",
        "",
        "覆盖多组织、供应商、商品、定价、询比价、采购、申购、验收、入库、出库、退货、调拨、盘点、批次追溯、票证、统计报表和供应商协同。",
        "",
        "- 学校版聚焦基础采购、验收、库存和溯源。",
        "- 政府版增强询比价、审批、日志、预警和财务对账。",
        "- 集团版增强多组织、计划、成本、运营报表和巡检整改。",
        "- AI 智能收货秤支持有单/无单验收、扣皮扣罚、拍照取证、入库/越库/调拨、失败重传和现场盘点。",
        "",
        "## 5. 典型客户与场景",
        "",
        "### 5.1 政府与企业",
        "",
        "关注高峰效率、员工健康、补贴与多账户、食品安全、采购成本和跨食堂经营透明。推荐从统一人员账户、营养结算、食安闭环和采购库存四个断点切入，再按单食堂或高频餐线试点。",
        "",
        "### 5.2 学校与教育主管部门",
        "",
        "围绕区域监管、学校主体责任、食堂运营、学生营养和家校共治，形成食品安全、采购经费、营养健康和透明公示四条治理主线。区域级能力与单校功能应分开确认。",
        "",
        "### 5.3 医院",
        "",
        "可组合员工就餐、患者/家属便民订餐、营养菜品展示、取餐服务和多类人群膳食支持。医院业务涉及诊疗、病种膳食和医疗数据时，必须单独确认系统边界与合规要求，不把普通团餐能力描述为医疗能力。",
        "",
        "### 5.4 康养与养老",
        "",
        "可围绕老人营养档案、健康目标、食谱推荐、营养价签、用餐反馈和机构运营形成方案。与医疗诊断、治疗建议、健康设备数据接入有关的能力需逐项核实接口、授权和专业责任。",
        "",
        "## 6. 产品目录",
        "",
        f"当前清单共整理 {product_count} 个去重后的软件、硬件、基础设施或服务条目。下表不包含价格、数量和投标参数。",
        "",
    ]

    for sheet_name, products in catalog.items():
        lines.extend([f"### 6.{list(catalog).index(sheet_name) + 1} {sheet_name}", ""])
        lines.extend(md_table(["产品/设备", "最新清单型号", "功能", "适用场景"], [[item["name"], item["model"], item["function"], item["scene"]] for item in products]))
        lines.append("")

    lines.extend(
        [
            "## 7. 软件功能矩阵",
            "",
            f"当前功能矩阵共整理 {function_count} 条功能。角色表示该功能的主要责任人或使用者，不代表最终权限；项目权限以正式配置为准。",
            "",
        ]
    )
    for sheet_name, items in functions.items():
        module_counts = Counter(item["module"] for item in items)
        lines.extend([f"### 7.{list(functions).index(sheet_name) + 1} {sheet_name}", ""])
        lines.append("模块分布：" + "、".join(f"{name}（{count}）" for name, count in module_counts.items()) + "。")
        lines.append("")
        lines.extend(
            md_table(
                ["终端", "一级模块", "功能", "详细说明", "核心角色", "适用场景/价值"],
                [[item["terminal"], item["module"], item["feature"], item["description"], item["core_roles"], item["value"]] for item in items],
            )
        )
        lines.append("")

    lines.extend(
        [
            "## 8. 标准组合方法",
            "",
            "1. 明确客户类型、决策角色和项目阶段。",
            "2. 盘点人数、食堂、餐线、档口、仓库、监管点位、网络和现有系统。",
            "3. 先选标准软件版本，再按吞吐量、点位和动线匹配硬件。",
            "4. 单列第三方接口、数据迁移、非标开发、驻场、弱电、网络改造和额外培训。",
            "5. 用演示或试点验证关键链路，记录输入、预期、实际结果和问题。",
            "6. 把确认后的名称、型号、数量、服务周期和责任边界同步到配置、报价、合同、交付和验收文件。",
            "",
            "## 9. 标准、选配、接口与非标边界",
            "",
            "| 类型 | 判断方式 | 对外表达 |",
            "| --- | --- | --- |",
            "| 标准功能 | 当前正式版本已具备并可在标准环境验证 | 按版本与合同范围交付 |",
            "| 标准选配 | 已有模块或硬件，可按站点、餐线、点位或组织数量配置 | 明确型号、数量和使用条件 |",
            "| 第三方接口 | 需要外部系统、设备或数据配合 | 取得接口文档、测试环境和联调人后评估 |",
            "| 非标开发 | 当前产品不具备，需要新增研发 | 进入需求评估、估时、报价、排期和验收流程 |",
            "",
            "## 10. 关键业务规则",
            "",
            "- 采购下单不等于入库；到货验收并形成入库记录后，库存才正式增加。",
            "- 营养反馈依赖人员、菜品、食材、份量和营养数据质量，不把数据展示直接承诺为健康改善效果。",
            "- 食安 AI 识别需要摄像头角度、光线、网络、算法范围和处置流程共同满足，不把识别告警等同于最终责任判定。",
            "- 结算方式、身份方式、补贴、优惠和账户顺序需按项目规则配置并用测试账号验证。",
            "- 设备数量必须按高峰人流、菜位、档口、仓库和监管点位测算，不复制其他项目数量。",
            "- 公有云、本地、内网或政务云部署需分别确认网络、安全、端口、外部依赖、备份和运维责任。",
            "",
            "## 11. 对外表达与证据要求",
            "",
            "- 不使用“唯一、绝对领先、全部实现”等绝对化表述。",
            "- 不把规划功能、讨论稿或竞品参数写成已上线能力。",
            "- 产品型号、尺寸、性能、证书和接口必须绑定当前型号及适用条件。",
            "- 客户名称、Logo、现场照片、项目数据和成效指标分别确认授权。",
            "- 20 余年运动营养积累、服务国家队、菜品/食材/营养指标数量、案例数量等品牌口径，对外使用前由市场或品牌负责人确认。",
            "- 价格、折扣、渠道底价、税费、有效期和服务周期只进入受控商务文件。",
            "",
            "## 12. 常见问答",
            "",
            "### 这套方案是不是一个收银系统？",
            "",
            "不是。收银和结算只是入口之一，整体还包括营养健康、食安监管、采购库存和经营分析。项目可以从单一场景起步，但需要保留统一数据与后续扩展边界。",
            "",
            "### 三套系统是否必须一起购买？",
            "",
            "不必须。应从客户目标和现状出发组合。单系统可独立建设；需要跨系统联动时，再确认主数据、接口、组织权限和实施范围。",
            "",
            "### 能否承诺节约多少成本或提升多少效率？",
            "",
            "不能使用无基线的通用承诺。应先确认当前人力、浪费、库存、排队、食安闭环等指标，再定义试点周期、取数方法和目标。",
            "",
            "### 是否支持客户已有卡、支付、HR、OA 或财务系统？",
            "",
            "可能需要接口集成。必须先取得双方系统信息、数据方向、字段、鉴权、网络、测试环境和联系人，再评估是否属于标准接口、适配还是非标开发。",
            "",
            "### 图片和 PPT 还能不能用？",
            "",
            "可以作为演示和设计素材使用，但产品事实先查本文。图片、PPT 和 Excel 原件用于追溯、版式与视觉制作，不再作为产品知识的唯一入口。",
            "",
            "## 13. 来源覆盖",
            "",
            f"本页由 {len(sources)} 个来源文件编译。24 个非敏感原件位于企业微信微盘；1 个含演示账号密码的工作簿只保留受限登记，未纳入正文。完整 SHA256 和微盘相对路径见 `work/2026-09-16-product-introduction-materials/source-index.csv`。",
            "",
        ]
    )
    source_rows = []
    for source in sources:
        source_rows.append(
            [
                source["title"],
                source["material_type"],
                source["raw_upload_status"],
                source["extract_status"],
                source["sha256"][:12],
            ]
        )
    lines.extend(md_table(["来源", "资料类型", "原件状态", "编译状态", "SHA256 前缀"], source_rows))
    lines.extend(
        [
            "",
            "## 14. 维护检查",
            "",
            "更新本文时必须完成：",
            "",
            "- 比对新旧产品清单和软件功能矩阵，显式记录型号与功能冲突。",
            "- 检查是否出现第二份 `status: current` 的产品总览。",
            "- 检查文档不含图片依赖、价格、账号密码、客户隐私和未经确认的承诺。",
            "- 更新来源索引、更新时间和覆盖清单。",
            "- 运行 Markdown 链接检查、敏感词扫描和仓库提交前检查。",
            "",
        ]
    )
    output.parent.mkdir(parents=True, exist_ok=True)
    output.write_text("\n".join(lines), encoding="utf-8")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--catalog", required=True, type=Path)
    parser.add_argument("--functions", required=True, type=Path)
    parser.add_argument("--source-index", required=True, type=Path)
    parser.add_argument("--output", required=True, type=Path)
    args = parser.parse_args()
    build(args.catalog, args.functions, args.source_index, args.output)


if __name__ == "__main__":
    main()
