#!/usr/bin/env python3
"""Build a reusable source index for the AI smart nutrition canteen PPTX."""

from __future__ import annotations

import csv
import hashlib
import html
import shutil
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path

from PIL import Image, ImageDraw
from pptx import Presentation


WORK_DIR = Path("work/2026-05-16-ai-smart-nutrition-super-canteen-solution-intake")
TMP_DIR = Path("tmp/2026-05-16-ai-smart-canteen-pptx-intake")
PPTX_PATH = Path(
    "/Users/jack/Library/Containers/com.tencent.WeWorkMac/Data/WeDrive/康比特/"
    "🏆2026数科事业部资料库/3-业务资料/1-数科—健康团餐/"
    "康比特【AI智慧营养超级食堂】解决方案_2604.pptx"
)
ASSET_ID = "WEWORK-LIXIAO-20260515-AI-SUPER-CANTEEN-PPTX"
CANONICAL_ROOT_KEY = "delivery"
CANONICAL_REL_PATH = "⭐️标准产品/001 智慧营养健康餐厅/02 销售支撑材料-产品介绍/PPT资料/康比特【AI智慧营养超级食堂】解决方案_2604.pptx"
CANONICAL_PATH = Path(
    "/Users/jack/Library/Containers/com.tencent.WeWorkMac/Data/WeDrive/康比特/"
    "项目交付管理共享空间/⭐️标准产品/001 智慧营养健康餐厅/"
    "02 销售支撑材料-产品介绍/PPT资料/康比特【AI智慧营养超级食堂】解决方案_2604.pptx"
)
ORIGINAL_ROOT_KEY = "business_2026"
ORIGINAL_REL_PATH = "3-业务资料/1-数科—健康团餐/康比特【AI智慧营养超级食堂】解决方案_2604.pptx"
PDF_PATH = TMP_DIR / "pdf" / "康比特【AI智慧营养超级食堂】解决方案_2604.pdf"
PDF_TEXT_PATH = TMP_DIR / "pdf" / "extracted.txt"
RENDERED_DIR = TMP_DIR / "rendered-slides"
PREVIEW_DIR = WORK_DIR / "assets" / "slide-previews"
CONTACT_DIR = WORK_DIR / "assets" / "contact-sheets"


SECTION_RULES = [
    (1, 1, "封面"),
    (2, 2, "目录"),
    (3, 5, "关于康比特"),
    (6, 10, "政策及行业背景"),
    (11, 18, "AI营养智慧食堂平台与价值主张"),
    (19, 32, "前厅：营养结算服务系统"),
    (33, 33, "章节分隔"),
    (34, 39, "后厨：智慧食安监管系统"),
    (40, 40, "章节分隔"),
    (41, 48, "进销存：智慧采购管理系统"),
    (49, 49, "章节分隔"),
    (50, 55, "全场景看板与数据沉淀"),
    (56, 78, "品质案例"),
    (79, 79, "结尾页"),
]


DOMAIN_RULES = {
    "D01": "场景产品与售前方案",
    "D02": "职工营养健康服务",
    "D03": "食品安全与后厨监管",
    "D04": "硬件设备与现场联动",
    "D05": "软件平台与工程底座",
    "D06": "交付运营与复盘资产",
}


@dataclass
class SlideInfo:
    no: int
    title: str
    section: str
    text: str
    picture_count: int
    table_count: int
    chart_count: int
    preview_path: str
    domain_codes: list[str]
    evidence_level: str
    rewrite_use: str
    gap_hint: str


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as fh:
        for chunk in iter(lambda: fh.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def section_for(no: int) -> str:
    for start, end, name in SECTION_RULES:
        if start <= no <= end:
            return name
    return "未分类"


def text_from_shape(shape) -> list[str]:
    if not getattr(shape, "has_text_frame", False):
        return []
    values = []
    for paragraph in shape.text_frame.paragraphs:
        value = paragraph.text.strip()
        if value:
            values.append(" ".join(value.split()))
    return values


def domains_for(no: int, text: str) -> list[str]:
    hits: list[str] = []
    if no <= 18 or 56 <= no <= 78:
        hits.append("D01")
    if any(k in text for k in ["营养", "健康", "体重", "膳食", "AI运动营养师"]):
        hits.append("D02")
    if any(k in text for k in ["食安", "后厨", "留样", "晨检", "温湿度", "AI视频", "农残"]):
        hits.append("D03")
    if any(k in text for k in ["智能台", "消费机", "闸机", "摄像", "秤", "设备", "留样柜", "检测仪"]):
        hits.append("D04")
    if 19 <= no <= 55 or any(k in text for k in ["系统", "平台", "小程序", "PC端", "手机端", "报表", "看板"]):
        hits.append("D05")
    if 56 <= no <= 78 or any(k in text for k in ["案例", "项目", "基地", "政府", "企业", "学校", "医院"]):
        hits.append("D06")
    return sorted(set(hits))


def evidence_level_for(no: int, text: str) -> str:
    if 57 <= no <= 78:
        return "B - 内部案例材料，可支撑案例线索；对外数据需补合同/验收/客户确认"
    if 7 <= no <= 9:
        return "C - 政策/行业引用，需回链官方文件"
    if any(k in text for k in ["截图", "看板", "设备", "智能台", "系统"]):
        return "B - 内部产品/设备材料，需补源系统截图或设备台账"
    return "D - 方案叙事或战略表达，需标注为待验证"


def rewrite_use_for(no: int, section: str, text: str) -> str:
    if no == 1:
        return "封面命名、品牌主视觉占位"
    if no == 2:
        return "重写目录骨架"
    if section == "关于康比特":
        return "公司背书与资质页"
    if section == "政策及行业背景":
        return "政策背景和行业痛点页"
    if "价值主张" in text or "超级载体" in text:
        return "总价值主张、方案开篇"
    if "前厅" in section:
        return "前厅结算、AI营养、设备和报表章节"
    if "后厨" in section:
        return "食安监管、明厨亮灶、留样晨检和AI巡检章节"
    if "进销存" in section:
        return "采购库存、供应商、成本利润和对账章节"
    if "看板" in section:
        return "驾驶舱、数据资产和管理闭环章节"
    if section == "品质案例":
        return "分行业案例库和标杆项目页"
    return "章节分隔或待重绘页"


def gap_for(no: int, text: str, picture_count: int) -> str:
    gaps: list[str] = []
    if no in {7, 8, 9}:
        gaps.append("补官方政策来源链接与发布日期")
    if 57 <= no <= 78:
        gaps.append("补项目合同/验收/客户可公开授权证据")
    if any(k in text for k in ["下降80%", "下降30%", "识别率高于99", "0 . 3秒", "100000人"]):
        gaps.append("补量化指标的测试或客户确认")
    if any(k in text for k in ["健康数据", "体检报告", "HIS", "体质健康报告"]):
        gaps.append("补健康数据授权、脱敏、非医疗边界和等保口径")
    if any(k in text for k in ["设备", "智能台", "消费机", "闸机", "检测仪", "留样柜"]):
        gaps.append("补设备型号、供应商、协议/SDK、成本售价和联调记录")
    if picture_count > 0:
        gaps.append("补图片/截图来源，必要时替换为现系统高清截图")
    return "；".join(gaps) if gaps else "无关键缺口，重写时仍需核对事实"


def relative(path: Path) -> str:
    return path.relative_to(WORK_DIR).as_posix()


def make_previews(slide_count: int) -> None:
    PREVIEW_DIR.mkdir(parents=True, exist_ok=True)
    CONTACT_DIR.mkdir(parents=True, exist_ok=True)
    for png in sorted(RENDERED_DIR.glob("slide-*.png")):
        no = int(png.stem.split("-")[1])
        out = PREVIEW_DIR / f"slide-{no:02d}.jpg"
        with Image.open(png) as im:
            im = im.convert("RGB")
            target_w = 960
            target_h = round(im.height * target_w / im.width)
            im = im.resize((target_w, target_h))
            im.save(out, "JPEG", quality=82, optimize=True)

    previews = [PREVIEW_DIR / f"slide-{i:02d}.jpg" for i in range(1, slide_count + 1)]
    for start in range(1, slide_count + 1, 20):
        group = previews[start - 1 : start - 1 + 20]
        thumbs = []
        for path in group:
            with Image.open(path) as im:
                im = im.convert("RGB").resize((240, 135))
                thumbs.append((path, im.copy()))
        cols = 4
        rows = (len(thumbs) + cols - 1) // cols
        sheet = Image.new("RGB", (cols * 260, rows * 175), "white")
        draw = ImageDraw.Draw(sheet)
        for idx, (path, im) in enumerate(thumbs):
            x = (idx % cols) * 260
            y = (idx // cols) * 175
            sheet.paste(im, (x, y + 24))
            draw.text((x + 6, y + 5), path.stem.replace("slide-", "P"), fill=(20, 20, 20))
        sheet.save(CONTACT_DIR / f"slides-{start:02d}-{start + len(group) - 1:02d}.jpg", "JPEG", quality=86, optimize=True)


def write_csv(path: Path, rows: list[dict[str, str]], fields: list[str]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", newline="", encoding="utf-8-sig") as fh:
        writer = csv.DictWriter(fh, fieldnames=fields)
        writer.writeheader()
        writer.writerows(rows)


def build_slide_infos() -> list[SlideInfo]:
    prs = Presentation(str(PPTX_PATH))
    make_previews(len(prs.slides))
    infos: list[SlideInfo] = []
    for no, slide in enumerate(prs.slides, 1):
        texts: list[str] = []
        picture_count = table_count = chart_count = 0
        for shape in slide.shapes:
            texts.extend(text_from_shape(shape))
            if shape.shape_type == 13:
                picture_count += 1
            if getattr(shape, "has_table", False):
                table_count += 1
            if getattr(shape, "has_chart", False):
                chart_count += 1
        title = texts[0] if texts else ""
        text = " | ".join(texts)
        section = section_for(no)
        infos.append(
            SlideInfo(
                no=no,
                title=title,
                section=section,
                text=text,
                picture_count=picture_count,
                table_count=table_count,
                chart_count=chart_count,
                preview_path=relative(PREVIEW_DIR / f"slide-{no:02d}.jpg"),
                domain_codes=domains_for(no, text),
                evidence_level=evidence_level_for(no, text),
                rewrite_use=rewrite_use_for(no, section, text),
                gap_hint=gap_for(no, text, picture_count),
            )
        )
    return infos


def build_outputs(infos: list[SlideInfo]) -> None:
    source_rows = [
        {
            "source_id": ASSET_ID,
            "source_type": "enterprise_wechat_wedrive_pptx",
            "title": "康比特【AI智慧营养超级食堂】解决方案_2604.pptx",
            "sender_context": "用户说明：企业微信里李潇昨天发；当前线程日期 2026-05-16，昨天按 2026-05-15 记录",
            "canonical_root_key": CANONICAL_ROOT_KEY,
            "canonical_rel_path": CANONICAL_REL_PATH,
            "original_root_key": ORIGINAL_ROOT_KEY,
            "original_rel_path": ORIGINAL_REL_PATH,
            "abs_path_on_jack_canonical": str(CANONICAL_PATH),
            "abs_path_on_jack_original": str(PPTX_PATH),
            "size_bytes": str(PPTX_PATH.stat().st_size),
            "mtime_local": datetime.fromtimestamp(PPTX_PATH.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S %z"),
            "sha256": sha256(PPTX_PATH),
            "copied_raw_binary": "no",
            "copy_policy": "302MB 原始 PPTX 不复制入仓库；已复制到微盘标准产品目录，zhctprompt 只保留 source asset registry + 哈希 + 派生产物",
            "team_recovery_index": "work_company_knowledge/external-source-assets/source-assets.csv",
            "derived_outputs": "slide-structure.csv; reusable-evidence-slots.csv; product-system-mapping.csv; gap-list.csv; source-index.md; review.html",
        }
    ]
    write_csv(WORK_DIR / "source-index.csv", source_rows, list(source_rows[0].keys()))

    slide_rows = [
        {
            "slide_no": f"{info.no:02d}",
            "section": info.section,
            "title": info.title,
            "domain_codes": "+".join(info.domain_codes),
            "domain_names": "；".join(DOMAIN_RULES[d] for d in info.domain_codes),
            "picture_count": str(info.picture_count),
            "table_count": str(info.table_count),
            "chart_count": str(info.chart_count),
            "evidence_level": info.evidence_level,
            "rewrite_use": info.rewrite_use,
            "gap_hint": info.gap_hint,
            "preview_path": info.preview_path,
            "text_excerpt": info.text[:600],
        }
        for info in infos
    ]
    write_csv(
        WORK_DIR / "slide-structure.csv",
        slide_rows,
        [
            "slide_no",
            "section",
            "title",
            "domain_codes",
            "domain_names",
            "picture_count",
            "table_count",
            "chart_count",
            "evidence_level",
            "rewrite_use",
            "gap_hint",
            "preview_path",
            "text_excerpt",
        ],
    )

    evidence_rows = [
        {
            "slot_id": "EV-01",
            "slides": "07-09",
            "slot_name": "政策依据",
            "source_evidence": "PPT 内政策引用页",
            "reuse_for": "开篇外部必要性、政策风口",
            "evidence_level": "C",
            "next_action": "补国家卫健委、国务院、教育部、住建部等官方链接；只保留与目标行业相关的政策",
        },
        {
            "slot_id": "EV-02",
            "slides": "12-16",
            "slot_name": "AI营养超级食堂价值主张",
            "source_evidence": "PPT 内价值主张与能力框架",
            "reuse_for": "方案总叙事、第一页价值锚点",
            "evidence_level": "D",
            "next_action": "把“超级载体”拆成可验证的运营、营养、食安、数据价值指标",
        },
        {
            "slot_id": "EV-03",
            "slides": "19-32",
            "slot_name": "前厅营养结算服务系统",
            "source_evidence": "PPT 内流程、设备、报表截图位",
            "reuse_for": "前厅章节、设备组合页、软件截图页",
            "evidence_level": "B",
            "next_action": "补现系统高清截图、设备台账、支付/身份/订单接口边界",
        },
        {
            "slot_id": "EV-04",
            "slides": "34-39",
            "slot_name": "后厨智慧食安监管系统",
            "source_evidence": "PPT 内明厨亮灶、留样、晨检、AI巡检、温湿度页",
            "reuse_for": "食安监管章节、后厨闭环架构",
            "evidence_level": "B",
            "next_action": "补 food_safety/qc/store 现有能力边界、AI识别指标、摄像头和留样设备来源",
        },
        {
            "slot_id": "EV-05",
            "slides": "41-48",
            "slot_name": "智慧采购与进销存",
            "source_evidence": "PPT 内采购、验收、库存、供应商、利润和对账页",
            "reuse_for": "进销存章节、供应链成本控制页",
            "evidence_level": "B",
            "next_action": "补采购入库出库盘点真实页面、成本利润计算口径、供应商端截图",
        },
        {
            "slot_id": "EV-06",
            "slides": "50-55",
            "slot_name": "全场景数据看板与数据资产",
            "source_evidence": "PPT 内前厅/后厨/进销存看板和数据沉淀页",
            "reuse_for": "管理驾驶舱、数据闭环、AI工程化输入",
            "evidence_level": "B/D",
            "next_action": "把数据指标映射到现有表、接口、埋点和可演示数据源；区分已实现和规划",
        },
        {
            "slot_id": "EV-07",
            "slides": "57-78",
            "slot_name": "品质案例库",
            "source_evidence": "PPT 内政府、企业、学校、体育、医院案例",
            "reuse_for": "按行业拆案例页、标杆客户证明",
            "evidence_level": "B",
            "next_action": "补可公开客户授权、验收截图、合同/交付记录；敏感客户匿名化",
        },
    ]
    write_csv(WORK_DIR / "reusable-evidence-slots.csv", evidence_rows, list(evidence_rows[0].keys()))

    mapping_rows = [
        {
            "product_system_layer": "智慧食堂产品战略体系",
            "five_module": "场景产品化",
            "domain_code": "D01",
            "domain_name": DOMAIN_RULES["D01"],
            "ppt_slides": "01-18,56-78",
            "reuse_output": "行业场景、客户分层、案例证据、售前方案骨架",
            "alignment": "强：PPT 已有公司背书、政策、价值主张和案例，但需按行业重排",
        },
        {
            "product_system_layer": "智慧食堂产品战略体系",
            "five_module": "场景产品化",
            "domain_code": "D02",
            "domain_name": DOMAIN_RULES["D02"],
            "ppt_slides": "15-23,51,55,57-78",
            "reuse_output": "AI营养服务包、健康画像、餐前建议、餐后报告、营养数据资产",
            "alignment": "强：是差异化主线；缺健康数据合规、算法指标和服务边界",
        },
        {
            "product_system_layer": "康比特食安方案资产体系",
            "five_module": "软件平台化",
            "domain_code": "D03",
            "domain_name": DOMAIN_RULES["D03"],
            "ppt_slides": "34-39,67",
            "reuse_output": "后厨食安监管章节、明厨亮灶、留样晨检、AI巡检",
            "alignment": "中强：能补食安方案资产，但需和 food_safety/qc 能力分清",
        },
        {
            "product_system_layer": "智慧食堂产品战略体系",
            "five_module": "硬件体系化",
            "domain_code": "D04",
            "domain_name": DOMAIN_RULES["D04"],
            "ppt_slides": "25-30,36-39,41,50",
            "reuse_output": "设备包、硬件组合、现场联动和验收清单",
            "alignment": "中：设备展示丰富，但缺型号、供应商、协议、成本和测试记录",
        },
        {
            "product_system_layer": "智慧食堂产品战略体系",
            "five_module": "软件平台化",
            "domain_code": "D05",
            "domain_name": DOMAIN_RULES["D05"],
            "ppt_slides": "19-32,34-48,50-55",
            "reuse_output": "PC/移动/小程序/设备端/API/数据对象的方案目录",
            "alignment": "中：方案功能完整，需与 store/ai_api/ai_store/ai_app 现状绑定",
        },
        {
            "product_system_layer": "产品开发部总控体系",
            "five_module": "交付资产化",
            "domain_code": "D06",
            "domain_name": DOMAIN_RULES["D06"],
            "ppt_slides": "56-78",
            "reuse_output": "行业案例库、交付证明、演示素材和验收证据位",
            "alignment": "强但需治理：案例素材多，必须建立可公开/内部/匿名三级口径",
        },
    ]
    write_csv(WORK_DIR / "product-system-mapping.csv", mapping_rows, list(mapping_rows[0].keys()))

    gap_rows = [
        {
            "gap_id": "GAP-PPT-01",
            "area": "公司微盘来源",
            "issue": "本 PPT 位于 2026 数科事业部资料库，不在现有两个 company-wedrive 默认扫描根内",
            "risk": "后续 agent 可能只搜旧索引而漏掉该类方案素材",
            "next_action": "确认是否把该资料库作为第三个受控源根；确认前本任务以 ad-hoc source index 管理",
        },
        {
            "gap_id": "GAP-PPT-02",
            "area": "政策依据",
            "issue": "政策页只有文案引用，没有官方链接、文件编号和发布日期",
            "risk": "对外方案易被质疑来源或政策适配性",
            "next_action": "重写前补官方原文链接，并筛除与目标客户无关政策",
        },
        {
            "gap_id": "GAP-PPT-03",
            "area": "客户案例",
            "issue": "案例和量化收益较多，但缺可公开授权、验收截图和合同/交付记录链接",
            "risk": "不能直接对外使用敏感客户名或收益数据",
            "next_action": "为每个案例补证据路径、公开级别、可脱敏版本和客户行业标签",
        },
        {
            "gap_id": "GAP-PPT-04",
            "area": "硬件设备",
            "issue": "设备展示丰富，但型号、供应商、协议/SDK、成本、售价和联调记录未结构化",
            "risk": "方案重写时难以落到报价、实施和验收",
            "next_action": "同步更新 hardware-capability-map.csv 或设备 intake 表",
        },
        {
            "gap_id": "GAP-PPT-05",
            "area": "AI能力",
            "issue": "AI菜品识别、AI营养建议、AI巡检等能力缺算法指标、样本边界和失败处理",
            "risk": "AI叙事强但验收指标弱",
            "next_action": "补准确率、召回率、识别类别、训练数据边界、人工复核流程",
        },
        {
            "gap_id": "GAP-PPT-06",
            "area": "健康数据合规",
            "issue": "涉及体检、体质、手环/体脂秤、HIS、慢病指标等健康数据",
            "risk": "存在个人信息、医疗边界、客户授权和等保要求",
            "next_action": "明确非医疗化服务边界、授权采集、脱敏、数据留存和对接责任",
        },
        {
            "gap_id": "GAP-PPT-07",
            "area": "软件现状绑定",
            "issue": "PPT 功能未逐项绑定到 store/ai_api/ai_store/ai_app/qc/food_safety 现有页面、接口和数据表",
            "risk": "容易把规划、演示素材和已交付能力混在一起",
            "next_action": "按 D03/D05 生成现状矩阵：已上线、可演示、需开发、外采/待确认",
        },
        {
            "gap_id": "GAP-PPT-08",
            "area": "页面结构",
            "issue": "17、18、33、40、49、79 页文字层为空或近似分隔页",
            "risk": "重写时可能保留低信息密度页面",
            "next_action": "把分隔页合并为章节扉页，补章节一句话价值和证据预告",
        },
        {
            "gap_id": "GAP-PPT-09",
            "area": "截图来源",
            "issue": "大量页面使用图片/截图，但源系统、拍摄日期、版本号未登记",
            "risk": "后续改版难以替换高清源或验证是否过期",
            "next_action": "建立 screenshot evidence slot：系统来源、页面路径、版本、截图人、可公开级别",
        },
        {
            "gap_id": "GAP-PPT-10",
            "area": "方案版本化",
            "issue": "一个 PPT 同时覆盖政府、企业、学校、社区、医院、竞技体育等客群",
            "risk": "正式售前时主线不聚焦",
            "next_action": "重写时拆成母版 + 行业版本：机关企事业、学校、园区/企业、竞技体育/医疗营养",
        },
    ]
    write_csv(WORK_DIR / "gap-list.csv", gap_rows, list(gap_rows[0].keys()))

    slide_text = ["# PPT 页面文字抽取", "", "说明：该文件由 python-pptx 从 PPTX 文本层抽取，纯图片页可能为空。", ""]
    for info in infos:
        slide_text.extend(
            [
                f"## P{info.no:02d} {info.title or '(无文字标题)'}",
                "",
                f"- 章节：{info.section}",
                f"- 产品体系：{'+'.join(info.domain_codes) or '未映射'}",
                f"- 预览：`{info.preview_path}`",
                "",
                info.text or "(文字层为空，需看页面预览)",
                "",
            ]
        )
    (WORK_DIR / "slide-text.md").write_text("\n".join(slide_text), encoding="utf-8")

    source_md = f"""# 康比特 AI智慧营养超级食堂解决方案 2604 Source Index

## Source

| Field | Value |
| --- | --- |
| source_id | {ASSET_ID} |
| title | 康比特【AI智慧营养超级食堂】解决方案_2604.pptx |
| user context | 企业微信里李潇昨天发；当前线程日期 2026-05-16，按 2026-05-15 记录 |
| canonical root key | `{CANONICAL_ROOT_KEY}` |
| canonical relative path | `{CANONICAL_REL_PATH}` |
| canonical path on Jack's machine | `{CANONICAL_PATH}` |
| original intake root key | `{ORIGINAL_ROOT_KEY}` |
| original intake path on Jack's machine | `{PPTX_PATH}` |
| file size | {PPTX_PATH.stat().st_size} bytes |
| sha256 | `{sha256(PPTX_PATH)}` |
| raw copy policy | 原始 PPTX 302MB，不复制进仓库；保留源路径、哈希、结构化索引和轻量预览 |
| conversion | MarkItDown not available; used python-pptx for text/structure and LibreOffice PDF + pdftoppm for page previews |

## Team Recovery

本文件不再把姜阳本机绝对路径当作唯一恢复入口。本 PPT 已登记为外部受控资料资产：

- registry: `work_company_knowledge/external-source-assets/source-assets.csv`
- root map: `work_company_knowledge/external-source-assets/local-root-map.csv`
- asset id: `{ASSET_ID}`

其他同事恢复方式：

1. 在企业微信微盘中确认自己是否有 `{CANONICAL_ROOT_KEY}` 对应资料根：`项目交付管理共享空间`。
2. 找到相对路径 `{CANONICAL_REL_PATH}`。
3. 下载或同步后计算 SHA256，必须等于 `{sha256(PPTX_PATH)}` 才视为同一版本。
4. 如果暂时拿不到原件，先使用本目录下的 `slide-structure.csv`、`reusable-evidence-slots.csv`、`gap-list.csv` 和 `review.html` 继续做方案重写。

## Outputs

| Output | Use |
| --- | --- |
| `source-index.csv` | 原始来源、哈希、边界 |
| `slide-structure.csv` | 79 页页面结构、标题、能力域、重写用途、缺口提示、预览路径 |
| `reusable-evidence-slots.csv` | 可复用截图/证据位 |
| `product-system-mapping.csv` | 与三条主线、五模块、D01-D06 产品体系的对应关系 |
| `gap-list.csv` | 后续方案重写前必须补的缺口 |
| `slide-text.md` | 全页文本层抽取 |
| `assets/slide-previews/` | 79 页轻量 JPEG 预览 |
| `assets/contact-sheets/` | 分段总览图 |
| `review.html` | 人审页面 |

## Structure Summary

- P01-P02：封面与目录。
- P03-P05：公司背书和康比特资质。
- P06-P10：政策、行业背景和团餐运营挑战。
- P11-P18：AI 营养智慧食堂平台、价值主张和“超级载体”叙事。
- P19-P32：前厅营养结算服务系统，包括餐前建议、餐中指导、餐后报告、结算模式、设备和报表。
- P34-P39：后厨智慧食安监管系统，包括明厨亮灶、留样、晨检、AI视频监管和温湿度监测。
- P41-P48：智慧采购管理系统，包括询价比价、采购退货、验收入库、库存、供应商、成本利润和对账。
- P50-P55：智能终端联动、前厅/后厨/进销存看板和全场景数据沉淀。
- P56-P78：政府、企业、学校、体育、医院等品质案例。

## Reuse Guidance

后续重写方案时，不建议按原 PPT 顺序直接复刻。建议改成：

1. 一页行业版价值主张：AI营养 + 食安合规 + 运营降本 + 数据闭环。
2. 三个核心场景包：前厅营养结算、后厨食安监管、采购进销存。
3. 一张软硬件与数据架构图：设备、系统、数据、AI 服务和看板。
4. 分行业案例页：机关企事业、学校、园区/企业、竞技体育/医疗营养。
5. 缺口页或附录：政策来源、案例授权、设备台账、接口/API、健康数据合规。
"""
    (WORK_DIR / "source-index.md").write_text(source_md, encoding="utf-8")

    summary_md = f"""# Intake Summary

本次已把企业微信来源 `康比特【AI智慧营养超级食堂】解决方案_2604.pptx` 吸收到 zhctprompt 的可复用方案资料层。

## Done

- 定位源文件并记录 SHA256、大小、mtime 和源路径。
- 未复制 302MB 原始 PPTX，只保存结构化抽取和轻量页面预览。
- 解析 79 页结构、316 个媒体对象中的页级图片使用情况。
- 生成页面结构、可复用证据位、产品体系映射和缺口清单。
- 生成 HTML review 页面，便于后续方案重写前人审。

## Key Finding

这份 PPT 的核心结构是“公司背书 -> 政策和团餐痛点 -> AI营养智慧食堂平台 -> 前厅/后厨/进销存三大解决方案 -> 数据看板 -> 案例库”。它和现有产品体系高度匹配，但当前证据等级以 B/D 为主：可作为内部重写输入，不能直接作为对外事实证据。

## Validation

- PPTX slides: {len(infos)}
- PDF render pages: {len(list(PREVIEW_DIR.glob('slide-*.jpg')))}
- Source SHA256: `{sha256(PPTX_PATH)}`
- Generated tables: `source-index.csv`, `slide-structure.csv`, `reusable-evidence-slots.csv`, `product-system-mapping.csv`, `gap-list.csv`

## Human Review Status

- status: pending
- reviewer: Jack / 产品方案负责人
- decision needed: 是否把 `🏆2026数科事业部资料库` 纳入 company-wedrive 第三源根；是否允许使用 PPT 中客户名称和量化收益。
"""
    (WORK_DIR / "summary.md").write_text(summary_md, encoding="utf-8")

    html_rows = "\n".join(
        f"<tr><td>P{info.no:02d}</td><td>{html.escape(info.section)}</td><td>{html.escape(info.title[:80])}</td>"
        f"<td>{html.escape('+'.join(info.domain_codes))}</td><td>{html.escape(info.rewrite_use)}</td>"
        f"<td><a href='{html.escape(info.preview_path)}'>preview</a></td></tr>"
        for info in infos
    )
    gap_html = "\n".join(
        f"<li><b>{html.escape(row['gap_id'])} {html.escape(row['area'])}</b>：{html.escape(row['issue'])}<br><span>{html.escape(row['next_action'])}</span></li>"
        for row in gap_rows
    )
    evidence_html = "\n".join(
        f"<tr><td>{html.escape(row['slot_id'])}</td><td>{html.escape(row['slides'])}</td><td>{html.escape(row['slot_name'])}</td><td>{html.escape(row['reuse_for'])}</td><td>{html.escape(row['next_action'])}</td></tr>"
        for row in evidence_rows
    )
    contact_sheets = "\n".join(
        f"<figure><img src='{html.escape(relative(path))}' alt='{html.escape(path.stem)}'><figcaption>{html.escape(path.stem)}</figcaption></figure>"
        for path in sorted(CONTACT_DIR.glob("*.jpg"))
    )
    review_html = f"""<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>AI智慧营养超级食堂 PPT Intake Review</title>
<style>
body{{margin:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#172033;background:#f6f7f9;line-height:1.55}}
header{{background:#12263a;color:#fff;padding:32px 48px}}
main{{max-width:1180px;margin:0 auto;padding:28px 24px 60px}}
section{{background:#fff;border:1px solid #e3e7ee;border-radius:8px;margin:18px 0;padding:22px}}
h1,h2{{margin:0 0 12px}} h1{{font-size:28px}} h2{{font-size:20px;color:#12263a}}
.meta{{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}}
.box{{background:#f8fafc;border:1px solid #e5eaf1;border-radius:6px;padding:12px}}
table{{width:100%;border-collapse:collapse;font-size:13px}} th,td{{border-bottom:1px solid #edf0f4;padding:8px;text-align:left;vertical-align:top}} th{{background:#f5f7fa}}
.sheets{{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}} figure{{margin:0}} img{{max-width:100%;border:1px solid #d9dee7;border-radius:6px}} figcaption{{font-size:12px;color:#667085;margin-top:6px}}
li{{margin-bottom:10px}} span{{color:#5b6472}}
</style>
</head>
<body>
<header>
<h1>康比特【AI智慧营养超级食堂】解决方案_2604.pptx Intake Review</h1>
<p>用于后续方案重写的 source index、页面结构、证据位和 gap list。</p>
</header>
<main>
<section>
<h2>Source Boundary</h2>
<div class="meta">
<div class="box"><b>Slides</b><br>{len(infos)}</div>
<div class="box"><b>Raw Copy</b><br>不复制 302MB PPTX</div>
<div class="box"><b>Evidence Level</b><br>内部 B/D 为主，政策为 C</div>
</div>
<p>源路径：<code>{html.escape(str(PPTX_PATH))}</code></p>
<p>SHA256：<code>{sha256(PPTX_PATH)}</code></p>
</section>
<section>
<h2>Contact Sheets</h2>
<div class="sheets">{contact_sheets}</div>
</section>
<section>
<h2>Reusable Evidence Slots</h2>
<table><thead><tr><th>ID</th><th>Slides</th><th>Slot</th><th>Reuse</th><th>Next Action</th></tr></thead><tbody>{evidence_html}</tbody></table>
</section>
<section>
<h2>Gap List</h2>
<ul>{gap_html}</ul>
</section>
<section>
<h2>Slide Structure</h2>
<table><thead><tr><th>Slide</th><th>Section</th><th>Title</th><th>Domain</th><th>Rewrite Use</th><th>Preview</th></tr></thead><tbody>{html_rows}</tbody></table>
</section>
</main>
</body>
</html>
"""
    (WORK_DIR / "review.html").write_text(review_html, encoding="utf-8")


def main() -> None:
    if not PPTX_PATH.exists():
        raise SystemExit(f"missing PPTX: {PPTX_PATH}")
    if not RENDERED_DIR.exists():
        raise SystemExit(f"missing rendered slides: {RENDERED_DIR}")
    if WORK_DIR.exists():
        for path in [PREVIEW_DIR, CONTACT_DIR]:
            if path.exists():
                shutil.rmtree(path)
    WORK_DIR.mkdir(parents=True, exist_ok=True)
    infos = build_slide_infos()
    build_outputs(infos)


if __name__ == "__main__":
    main()
