from __future__ import annotations

import csv
import shutil
from collections import Counter, defaultdict
from copy import copy
from datetime import datetime
from pathlib import Path

from openpyxl import load_workbook
from openpyxl.chart import BarChart, PieChart, Reference
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side


ROOT = Path(__file__).resolve().parents[1]
OUTPUTS = ROOT / "outputs"
TEMPLATE = Path("/Users/jack/同步空间/cpt/05_经营管理与会议/005_日常管理/周例会（事业部层面）/2026年/周报模板.xlsx")
CLEAR = Path("/Users/jack/Downloads/数字技术中心产品开发部周报_清晰版_20260601-20260607.xlsx")
RICH = Path("/Users/jack/code/010-cpt/008-zhct/zhctprompt/work/weekly-reports/2026-W23/rich-excel-weekly-report/outputs/数字技术中心产品开发部周报_丰富版_20260601-20260607.xlsx")
PROJECT_ROOT = Path("/Users/jack/code/010-cpt/008-zhct/zhctprompt")
YUNXIAO_LIVE = ROOT / "yunxiao-live-20260606"
YUNXIAO_FALLBACK = PROJECT_ROOT / "work/2026-06-01-yunxiao-wedrive-traceability"

OUT = OUTPUTS / "数字技术中心产品开发部周报_模板版_丰富版_20260601-20260607.xlsx"
DOWNLOAD_COPY = Path("/Users/jack/Downloads/数字技术中心产品开发部周报_模板版_丰富版_20260601-20260607.xlsx")
SOURCE_INDEX = ROOT / "source-index.csv"
SUMMARY = ROOT / "summary.md"


TITLE = "数字技术中心周工作汇报（20260601-20260607）"


PROJECT_ROWS = [
    {
        "type": "团餐【12】",
        "stage": "交付（7）",
        "name": "城市副中心项目",
        "time": "6/10启动备案；6/15服务器/网络确认；7/10交付；7/15试用餐",
        "amount": "待确认",
        "owner": "待确认",
        "contract": "待确认",
        "procurement": "服务器/政务云待客户确认",
        "delivery": "服务器方案、域名HTTPS、远程运维、小程序备案推进",
        "launch": "7/15试用餐前完成环境与接口条件",
        "acceptance": "试用后触发",
        "cash": "待商务确认",
        "progress": "已形成销售需客户确认事项：政务云/本地部署、443/HTTPS、VPN/堡垒机、小程序主体备案。",
        "next": "6/15 前锁定服务器部署方式、网络入口、远程运维方式；6/25 前完成小程序备案。",
        "risk": "服务器和网络条件未确认会直接影响 60 台称、H5/小程序和现场上线稳定性。",
        "note": "可沉淀为政企/本地化设备接入部署检查清单。",
        "link": "work/2026-06-04-city-subcenter-tech-solution/sales-actions.md",
    },
    {
        "type": "团餐【12】",
        "stage": "交付（7）",
        "name": "江苏国信/国庆项目（名称待确认）",
        "time": "6/25试用；7/1正式上线",
        "amount": "56.14w（来源项目表，待商务确认）",
        "owner": "海琴/海军/倩倩/待确认",
        "contract": "方案/范围确认中",
        "procurement": "待启动/待核",
        "delivery": "H5/PC 原型与规则开发准备",
        "launch": "6/25试用；7/1上线计划",
        "acceptance": "上线后触发",
        "cash": "上线后触发",
        "progress": "推进报餐、订餐、接待权限、错分就餐、核销扣款、人脸批量导入等业务规则。",
        "next": "输出模块状态表、客户确认清单、上线风险清单；锁定标准化/非标边界。",
        "risk": "项目名称、范围和定制边界未统一，容易影响试用范围和上线风险。",
        "note": "国企/机关接待餐与预约就餐标准样板。",
        "link": "work/weekly-reports/2026-W23/product-standardization-ai-weekly/",
    },
    {
        "type": "团餐【12】",
        "stage": "交付（7）",
        "name": "食安教委端 PC 版本",
        "time": "6月第2周开发衔接；6月底形成上线计划",
        "amount": "待确认",
        "owner": "待确认",
        "contract": "不涉及",
        "procurement": "待确认",
        "delivery": "八板块 PRD 拆解、效果图、云效任务准备",
        "launch": "待排期",
        "acceptance": "未触发",
        "cash": "不涉及",
        "progress": "PC 版方向基本确认，需把现有 PRD 拆成八板块并补齐云效任务。",
        "next": "输出八板块任务表、效果图清单、开发估时和验收标准。",
        "risk": "缺少开发估时和真实教委/学校验证材料，会影响 6 月产品落地节奏。",
        "note": "学校/教委监管标准产品包。",
        "link": "work/weekly-reports/2026-W23/product-standardization-ai-weekly/product-standardization-ai-weekly-report.md",
    },
    {
        "type": "团餐【12】",
        "stage": "交付（7）",
        "name": "德沃晨检/后厨硬件能力",
        "time": "下周完成门禁联动效果确认",
        "amount": "待确认",
        "owner": "待确认",
        "contract": "不涉及",
        "procurement": "设备到位；接口文档已提供",
        "delivery": "联调评估",
        "launch": "未触发",
        "acceptance": "未触发",
        "cash": "不涉及",
        "progress": "晨检设备、后厨硬件、接口文档已进入评估，需验证与门禁/食安台账联动效果。",
        "next": "输出设备清单、接口字段、联动验证视频、标准版/教委版差异表。",
        "risk": "联动视频和验收证据不足，暂不能直接写入标准产品承诺。",
        "note": "后厨食安硬件证据链候选能力。",
        "link": "work/2026-05-29-hardware-capability-strategy-from-wedrive/",
    },
    {
        "type": "团餐【12】",
        "stage": "交付（7）",
        "name": "江西206项目",
        "time": "本周/下周继续从机迁移和上线问题闭环",
        "amount": "54.14w（来源项目表，待商务确认）",
        "owner": "待确认",
        "contract": "已签/待核",
        "procurement": "已完成/待核",
        "delivery": "从机迁移、设备配对、压力测试、数据权限检查",
        "launch": "已上线/运行中",
        "acceptance": "待确认",
        "cash": "上线后触发",
        "progress": "上线后问题进入收口节奏，迁移、配对、网络、权限需逐项验证。",
        "next": "输出迁移验证记录、问题清单、回归结论，沉淀部署迁移 SOP。",
        "risk": "多环境和设备链路复杂，若无回归记录，后续迁移复用价值不足。",
        "note": "信创/政务环境复杂交付样本。",
        "link": "tasks/QUEUE.md; work/2026-05-29-product-standardization-ai-native-weekly-material/",
    },
    {
        "type": "团餐【12】",
        "stage": "交付（7）",
        "name": "莱迪森（南京建宁中学）部署",
        "time": "数据库名确认后导入 SQL 并验表",
        "amount": "待确认",
        "owner": "待确认",
        "contract": "待确认",
        "procurement": "已准备",
        "delivery": "LNMP/Redis/PHP/Supervisor 已完成到基础环境；SQL 文件已到 VM 目标目录",
        "launch": "未触发",
        "acceptance": "未触发",
        "cash": "待商务确认",
        "progress": "SQL 文件已部署到 `/workspace/wwwroot/zhct_lds/project/db/init/lds/`，Supervisor 本体 active/running。",
        "next": "确认数据库名后导入初始化 SQL、验表数量，再部署项目、Nginx vhost、cron 和业务 Supervisor。",
        "risk": "数据库名、生产配置、客户 VM 可操作窗口和 SSH 可达路径仍待确认；敏感配置不能入库。",
        "note": "本地部署恢复包和客户 VM 部署 SOP。",
        "link": "work_laidisen/deployment-progress.md",
    },
    {
        "type": "团餐【12】",
        "stage": "交付（7）",
        "name": "金斯瑞项目",
        "time": "持续跟进南京餐厅部署、食安、夜宵和设备支持",
        "amount": "44.94w（来源项目表，待商务确认）",
        "owner": "待确认",
        "contract": "已签/待核",
        "procurement": "已完成/待核",
        "delivery": "食安部署、夜宵逻辑、AI识别/取餐柜等项目支持",
        "launch": "运行/待上线",
        "acceptance": "待确认",
        "cash": "上线后触发",
        "progress": "同客户多园区项目复用度较高，当前重点是南京餐厅、夜宵逻辑、AI识别和取餐柜问题收口。",
        "next": "梳理场景、规则、页面、硬件连接清单，判断哪些进入标准产品，哪些保留配置化。",
        "risk": "需求分散，若不归类会变成长期救火。",
        "note": "企业团餐+食安+取餐柜/叫号/硬件证据链样板。",
        "link": "work/2026-05-29-product-standardization-ai-native-weekly-material/project-standardization-map.csv",
    },
    {
        "type": "团餐【12】",
        "stage": "售后/运维（3）",
        "name": "山西焦煤余额显示问题",
        "time": "下周提交问题单和修复计划",
        "amount": "待确认",
        "owner": "待确认",
        "contract": "已签/待核",
        "procurement": "不涉及",
        "delivery": "问题定位",
        "launch": "待修复上线",
        "acceptance": "回归后触发",
        "cash": "不涉及",
        "progress": "消费机端余额/现金口径异常，需要确认现金、补贴、余额显示逻辑，不能直接快改。",
        "next": "提交问题单，确认账务显示口径，修复后补回归截图和设备端验证记录。",
        "risk": "账务口径错误会影响客户信任和设备端财务展示。",
        "note": "可沉淀余额显示回归用例和问题库。",
        "link": "周报0605.xlsx; work/weekly-reports/2026-W23/product-standardization-ai-weekly/",
    },
    {
        "type": "团餐【12】",
        "stage": "售后/运维（3）",
        "name": "AI 人脸识别摄像头调起异常",
        "time": "下周形成修复方案和验证记录",
        "amount": "待确认",
        "owner": "待确认",
        "contract": "待确认",
        "procurement": "不涉及",
        "delivery": "定位中",
        "launch": "待修复",
        "acceptance": "回归后触发",
        "cash": "不涉及",
        "progress": "摄像头调起异常影响河北等人脸识别项目落地，涉及浏览器权限、SDK、设备差异。",
        "next": "输出排障 SOP、影响项目清单、修复方案和验证记录。",
        "risk": "若只处理单点问题，后续同类项目仍会重复排障。",
        "note": "设备调用排障 SOP 候选。",
        "link": "周报0605.xlsx; work/weekly-reports/2026-W23/product-standardization-ai-weekly/",
    },
    {
        "type": "团餐【12】",
        "stage": "售后/运维（3）",
        "name": "首都机场/赛迪营养结算 1.0",
        "time": "运营优化中",
        "amount": "19.29w（来源项目表，待商务确认）",
        "owner": "待确认",
        "contract": "已签/待核",
        "procurement": "已完成/待核",
        "delivery": "运营优化、消费策略和商城对接边界复盘",
        "launch": "已上线/运营中",
        "acceptance": "资料补齐中",
        "cash": "待商务确认",
        "progress": "机场/赛迪可作为营养结算 1.0 项目验证，标准化和定制化边界已在前期台账中初步拆分。",
        "next": "沉淀人员、账户、充值、收银、对账、商城对接和消费策略边界。",
        "risk": "客户案例外发需授权；金额、验收和回款需商务/财务复核。",
        "note": "营养结算 1.0 标准场景。",
        "link": "work/2026-05-29-product-standardization-ai-native-weekly-material/project-standardization-map.csv",
    },
    {
        "type": "团餐【12】",
        "stage": "需求/演示（2）",
        "name": "机场餐卡需求分析 SOP",
        "time": "HTML 外链同步后产品评审",
        "amount": "不涉及",
        "owner": "Jack/Codex/待产品确认",
        "contract": "不涉及",
        "procurement": "不涉及",
        "delivery": "需求分析、Before/After、方案判断已完成",
        "launch": "不涉及",
        "acceptance": "产品评审后确认",
        "cash": "不涉及",
        "progress": "完成 10 条需求分析和 20 张 Before/After 页面级对比，沉淀系统化需求分析 skill。",
        "next": "发布并核验 HTML，拆分第一阶段快上项和第二阶段账务模型项。",
        "risk": "资金流水、余额清零、退款账期、资金来源归属需财务/研发确认，不能只按 UI 快改承诺。",
        "note": "可复制的客户 Excel 需求 -> 系统证据 -> 方案判断样板。",
        "link": "work/weekly-reports/outputs/2026-W23/team-weekly-summary.md",
    },
    {
        "type": "团餐【12】",
        "stage": "需求/演示（2）",
        "name": "智慧餐厅功能演示与 AI 协作会议沉淀",
        "time": "下周转标准演示包和销售 Q&A",
        "amount": "不涉及",
        "owner": "待确认",
        "contract": "不涉及",
        "procurement": "不涉及",
        "delivery": "会议纪要、演示准备、录屏沉淀、AI 自动化测试规则写回治理",
        "launch": "不涉及",
        "acceptance": "人审后复用",
        "cash": "不涉及",
        "progress": "图文会议纪要与功能演示资产已沉淀，后续可转成标准销售/交付包。",
        "next": "输出标准演示包、录屏脚本、销售 Q&A，并归档到产品资料库。",
        "risk": "演示资产若不归档，会继续依赖临时讲解和个人经验。",
        "note": "AI 协作与演示资产化样板。",
        "link": "modules/product/products/smart-canteen/delivery-assets/2026-06-04-function-demo-ai-collaboration-meeting/",
    },
    {
        "type": "数科【3】",
        "stage": "运维/售前（3）",
        "name": "广东反兴奋剂中心项目",
        "time": "6月持续服务",
        "amount": "待确认",
        "owner": "王朔/待确认",
        "contract": "已签/待核",
        "procurement": "不涉及",
        "delivery": "运维/项目支持",
        "launch": "运行中",
        "acceptance": "持续服务",
        "cash": "按节点",
        "progress": "作为数科项目持续运营事项进入统一项目 SOP 看板。",
        "next": "按售前/交付/售后三阶段补齐项目状态、交付物和风险。",
        "risk": "与其他数科项目混写时，领导难以判断资源请求和阶段成果。",
        "note": "数科项目 SOP 归档。",
        "link": "work/weekly-reports/2026-W23/rich-excel-weekly-report/",
    },
    {
        "type": "数科【3】",
        "stage": "运维/售前（3）",
        "name": "上海训练基地项目",
        "time": "6月持续联调/运维",
        "amount": "待确认",
        "owner": "王朔/待确认",
        "contract": "已签/待核",
        "procurement": "不涉及",
        "delivery": "联调/运维",
        "launch": "运行中",
        "acceptance": "持续服务",
        "cash": "按节点",
        "progress": "训练基地联调与运维事项纳入数科项目清单。",
        "next": "补齐阶段、当前卡点、下周产物和需协调事项。",
        "risk": "只写运营中无法体现风险、资源和复用价值。",
        "note": "数科项目 SOP 归档。",
        "link": "work/weekly-reports/2026-W23/rich-excel-weekly-report/",
    },
    {
        "type": "数科【3】",
        "stage": "运维/售前（3）",
        "name": "中央网信办/网信办相关项目",
        "time": "6月继续后厨管家/政务云相关事项",
        "amount": "66.32w（来源项目表，待商务确认）",
        "owner": "王朔/待确认",
        "contract": "已签/待核",
        "procurement": "不涉及",
        "delivery": "使用陪跑/后厨管家/食安相关支持",
        "launch": "已上线/运行中",
        "acceptance": "待触发",
        "cash": "按节点",
        "progress": "食安监管、采购比价、硬件证据链成熟度较高，定制集中在点位、布线、培训和实施。",
        "next": "拆出硬件白名单、点位模板和实施验收清单，谨慎处理客户名称外发授权。",
        "risk": "敏感客户案例默认不外发客户名，金额/验收/回款仍需复核。",
        "note": "食安监管标准包和硬件证据链样本。",
        "link": "standards-stack/product-strategy/smart-canteen/CUSTOMER_CASE_PUBLICATION_MATRIX.csv",
    },
    {
        "type": "产品/AI协作【4】",
        "stage": "内部能力建设（4）",
        "name": "小票模板所见即所得需求",
        "time": "待产品确认默认张数和开发排期",
        "amount": "不涉及",
        "owner": "郭东伟/待确认",
        "contract": "不涉及",
        "procurement": "不涉及",
        "delivery": "云效产品类需求已创建",
        "launch": "待排期",
        "acceptance": "需求验收后触发",
        "cash": "不涉及",
        "progress": "已创建云效产品类需求并生成外链图片说明，关联 VWCG-1019/VWCG-999。",
        "next": "确认默认张数、交互边界、开发排期和验收截图。",
        "risk": "如果只停留在需求创建，无法形成产品标准能力和验收闭环。",
        "note": "后台配置体验和产品标准化改进。",
        "link": "work_store/20260604-receipt-template-wysiwyg-preview/summary.md",
    },
    {
        "type": "产品/AI协作【4】",
        "stage": "内部能力建设（4）",
        "name": "云效项目分类治理与研发 Agent 试跑",
        "time": "统计周期：2026/6/1-2026/6/6；下周选择低/中风险真实研发任务试跑",
        "amount": "不涉及",
        "owner": "Jack/待确认",
        "contract": "不涉及",
        "procurement": "不涉及",
        "delivery": "团队云效 CLI 只读回读本周创建/变更工作项 42 条，其中需求 37 条、开发任务 5 条；本周新建 19 条，覆盖智慧营养健康餐厅、食安系统、食安监管校园版",
        "launch": "内部试点",
        "acceptance": "真实任务跑通后验收",
        "cash": "不涉及",
        "progress": "本周 42 条云效工作项发生变更：VWCG 智慧营养健康餐厅 40 条、食安系统 1 条、食安监管校园版 1 条；状态为已完成 16、待处理 11、待测试 6、已取消 4、已确认/开发中/测试完成/发布完成/开发完成各 1。",
        "next": "围绕本周真实工作项选 1 个低/中风险样板，例如小票模板、点餐限制、消费策略或 AI 聊天 500 问题，跑通任务契约、代码修改、验证、证据包、任务索引和人审闭环。",
        "risk": "本周云效数据能说明团队工作流正在进入 Agent 化，但还不能写成所有研发任务已由 Agent 端到端闭环完成。",
        "note": "把“全员推 AI Agent”落到本周真实任务：新建/变更工作项、状态推进、可选样板任务和下周闭环试跑。",
        "link": "work/weekly-reports/2026-W23/template-weekly-report-enhancement/yunxiao-live-20260606/summary.md; work/2026-06-02-yunxiao-development-agent-stage-results/summary.md",
    },
    {
        "type": "产品/AI协作【4】",
        "stage": "内部能力建设（4）",
        "name": "企业微盘知识底座",
        "time": "按项目机会定向抽取方案/证书/截图/验收材料",
        "amount": "不涉及",
        "owner": "Jack/Codex",
        "contract": "不涉及",
        "procurement": "不涉及",
        "delivery": "58039 个文件、6640 个文档索引已形成",
        "launch": "内部可用",
        "acceptance": "检索命中和材料回读验证",
        "cash": "不涉及",
        "progress": "企业微盘知识库已形成产品/交付/开发检索入口，本周可复用为周报证据底座。",
        "next": "围绕投标四类缺口抽取支付/一卡通、AI食安、明厨亮灶、订餐打餐参数包。",
        "risk": "索引不是原文授权，外发前仍需回读原件和权限确认。",
        "note": "AI 协作资料底座。",
        "link": "work_company_knowledge/reports/summary.md",
    },
    {
        "type": "产品/AI协作【4】",
        "stage": "内部能力建设（4）",
        "name": "专利申请挖掘材料补正",
        "time": "6月持续补证；代理人检索待确认",
        "amount": "不涉及",
        "owner": "待确认",
        "contract": "不涉及",
        "procurement": "不涉及",
        "delivery": "A06/A11/A14/A01/A08 优先级决策报告",
        "launch": "不涉及",
        "acceptance": "补证包/代理人确认",
        "cash": "不涉及",
        "progress": "按技术硬度、证据成熟度、战略价值等维度形成优先级：A06=82、A11=80、A14=74、A01=61、A08=64。",
        "next": "补系统截图、日志、流程图、场景视频、技术差异说明和代理人沟通清单。",
        "risk": "未授权前不得写成已授权专利；控标使用前需证据真件。",
        "note": "产品差异化/IP 资产。",
        "link": "work/2026-06-03-patent-application-mining-package/patent-application-decision-report.html",
    },
]


PRODUCT_ROWS = [
    ("1", "产品差异化", "AI智慧营养", "围绕“AI营养，让你吃得对”重构智慧食堂价值链：健康画像、餐前推荐、餐中反馈、餐后报告和团体健康管理。", "AI运动营养师、智慧健康餐厅、企业/学校/运动队", "6月持续", "待确认", "demo 脚本、报告样张、FAQ、非医疗边界说明", "响应白总价值营销主线；不得写成医疗诊断。"),
    ("2", "产品差异化", "数字技术能力", "将体脂秤、称重设备、人脸识别、消费机、取餐柜、晨检仪等设备数据串成健康团餐数据链路。", "城市副中心、国信、金斯瑞、德沃硬件", "6月持续", "待确认", "设备字段清单、授权边界、接入优先级", "先写可对接/待验证，不写已稳定上线。"),
    ("3", "产品差异化", "三大系统控标能力", "把 AI 营养中枢映射到前厅营养结算、食安监管、进销存三大载体，形成投标/售前可讲的控标项。", "营养结算、食安监管、进销存", "6月持续", "待确认", "控标项、证书/软著证据夹、销售一页纸", "专利、软著、国产化证书需回读真件。"),
    ("4", "产品差异化", "专利/软著/IP资产", "推进 A01/A06/A08/A11/A14 五件专利补证，把技术方向转成差异化证据和投标材料。", "智慧营养、AI视觉结算、食安监管", "6月持续", "待确认", "补证包、代理人沟通清单、资质证据夹", "未授权前不得写成已授权专利。"),
    ("5", "产品标准化", "PC端产品 PRD", "细化教育端/PC端 PRD，提交营销并与开发对齐估时，目标 6 月内完成上线拆解。", "教委端、学校端、企业端", "6/30前", "待确认", "PRD、云效任务、开发估时表、上线计划", "需按周拆分 6-8 月路线图。"),
    ("6", "产品标准化", "接待权限标准定义", "调研用友竞品接待权限逻辑，定义接待负责人、接待部门、外部人员、费用归属和订餐边界。", "江苏国信/企业接待餐", "6月第2周", "倩倩/待确认", "《接待权限标准定义文档》", "非标项目能力，具备企业/国企标准包复用价值。"),
    ("7", "产品标准化", "人脸批量导入与刷脸消费", "跟进 PC 端人脸批量导入、照片字段、权限校验和异常处理，沉淀为人员管理标准能力。", "国信、河北等人脸识别项目", "6月第2-3周", "待确认", "功能说明、字段规则、异常处理、排障 SOP", "需明确照片、权限、数据安全边界。"),
    ("8", "产品标准化", "食安教委监管标准包", "教委端 PC 版本按八板块拆解，补齐云效任务、效果图、验收标准并与前端开发衔接。", "学校/教委/食安监管", "6月第2周", "待确认", "八板块拆解表、效果图、开发任务", "需补真实学校/教委验证材料。"),
    ("9", "产品标准化", "后厨食安硬件证据链", "晨检仪、门禁、后厨硬件、摄像头/食安设备形成上岗准入、异常留痕和监管证据链。", "德沃晨检、教委端、金斯瑞", "下周", "待确认", "设备白名单、接口字段、联动视频", "供应商验证不足前不写成正式承诺。"),
    ("10", "产品标准化", "部署/迁移 SOP", "把 206、莱迪森、城市副中心等复杂环境拆成环境检查、数据库导入、服务验证、回归记录和上线门禁。", "206、莱迪森、城市副中心", "6月持续", "待确认", "部署检查清单、迁移验证记录", "敏感配置、客户密码、生产地址不入库。"),
    ("11", "产品标准化", "余额显示与设备回归用例", "围绕消费机余额/现金/补贴口径建立问题单、修复记录和回归截图模板。", "山西焦煤及消费机项目", "下周", "待确认", "余额显示回归用例、问题库", "账务口径需财务/研发确认。"),
    ("12", "产品标准化", "一次业务流程资产化", "以新增人员、发卡、挂失、冻结、退卡、补卡为样例，验证一次业务同步产出测试、视频、手册和 FAQ。", "产品/测试/交付/培训", "下周打样", "Jack/待确认", "SOP、测试用例、Playwright脚本、HTML报告、操作视频、用户手册", "用于证明 AI 不是只写材料，而是沉淀交付资产。"),
    ("13", "产品标准化", "产品人本周工作总览", "产品同事本周围绕 42 条云效工作项做需求建模、标准功能归类、测试验收口径补齐和项目场景复盘，把零散需求归到项目来源和标准能力。", "智慧营养健康餐厅、国信、机场、金斯瑞、江西206、莱迪森、食安教委、德沃晨检", "6/1-6/6", "产品团队/Jack/Codex", "本周沉淀项目来源与标准功能映射：3 个云效项目、10 类工作主题、若干标准能力候选；详见《项目来源与标准化沉淀》sheet。", "产品工作要表达为“需求如何变成标准能力”，不是只写创建了多少云效任务。"),
    ("14", "产品标准化", "标准功能候选池", "从本周任务中抽取小票模板、消费策略、订单状态、账户退款、人员权限、硬件联调、AI接口异常等标准能力候选。", "智慧营养健康餐厅标准产品、食安系统、校园监管端", "下周复盘", "产品团队", "输出标准功能候选清单、验收口径、需补证据和下周优先级", "待处理/待测试项不能写成已完成标准能力。"),
    ("13", "AI赋能", "本周云效 AI 任务推进", "用团队云效 CLI 只读回读 2026/6/1-2026/6/6 本周发生创建或变更的工作项，展示大家本周实际推进的研发/产品事项。", "全员 AI Agent 推进、研发任务调度、项目追溯", "6/1-6/6", "Jack/Codex", "本周工作项 42 条：需求 37、开发任务 5；新建 19、变更 42；其中智慧营养健康餐厅 40 条、食安系统 1 条、食安监管校园版 1 条。", "全量 64 项目/5754 工作项只作为背景，本周周报正文只写本周发生动作的 42 条。"),
    ("14", "AI赋能", "研发 Agent 真实任务试跑", "把云效任务转成任务契约，驱动 Agent 完成代码改动、验证、证据包、任务索引和人审，先选低/中风险真实任务。", "VWCG/智慧营养健康餐厅、低风险 bug 或小需求", "下周试跑", "Jack/研发负责人", "1 个真实任务闭环样板：任务契约、业务仓库 diff、验证日志、summary、人审页、task-index", "高风险发布、支付、权限、生产数据仍需人工门禁；不能把阶段成果写成已完全闭环。"),
    ("15", "AI赋能", "Playwright UI 自动化测试", "从本地跑通推进到协作项目可复用，补中文报告、失败分析、截图/视频、定时跑测。", "开发、测试、交付", "下周", "待确认", "测试用例、HTML报告、视频、字幕、失败分析", "需要选真实低风险项目接入。"),
    ("16", "AI赋能", "AI 用户手册/操作视频", "把业务流程、测试截图、旁白脚本和执行结果转成 Word/PDF 手册、操作视频和培训素材。", "交付培训、客户使用、售后 FAQ", "下周选样例", "待确认", "手册草稿、视频脚本、截图证据", "避免一次性交付材料散落。"),
    ("17", "其他", "市场调研与竞品能力评估", "结合招投标、竞品、用友接待权限、多模态插件等调研，形成可进入产品和销售话术的结论。", "产品/销售/市场中心", "每周持续", "市场/产品", "市场调研表、竞品对比、场景化材料", "C 级参考不能当作客户或财务事实。"),
    ("18", "其他", "企业微盘知识底座", "围绕项目机会定向抽取方案、证书、截图、验收材料，让产品、交付、开发可追溯使用。", "企业微盘、项目交付资料、数字技术知识库", "每周持续", "Jack/Codex", "58039 文件、6640 文档索引；定向材料包", "索引不是原文授权，外发前需回读原件。"),
    ("19", "其他", "产品资料/标准演示包", "把智慧餐厅功能演示、讲解视频、场景图、销售 Q&A 统一沉淀为标准演示包。", "智慧餐厅演示、销售培训、交付培训", "下周", "待确认", "演示包、录屏脚本、销售 Q&A", "减少临时讲解和个人经验依赖。"),
]


def get_yunxiao_dir() -> Path:
    if (YUNXIAO_LIVE / "yunxiao-workitems.csv").exists():
        return YUNXIAO_LIVE
    return YUNXIAO_FALLBACK


def classify_yunxiao_project(name: str) -> str:
    rules = [
        ("智慧餐厅/智慧营养", ("智慧餐厅", "智慧营养", "餐厅", "团餐")),
        ("食安/监管", ("食安", "食品", "监管")),
        ("科训/训练/体能", ("科训", "训练", "体能", "运动员", "体健", "数据中心")),
        ("反兴奋剂", ("兴奋剂",)),
        ("教育/实训/学校", ("职业教育", "实训", "小学", "学校", "校园")),
        ("进销存/硬件/运维", ("进销存", "硬件", "运维")),
        ("产品/知识库/专题", ("产品", "知识库", "主题", "反馈", "小程序", "电商", "VR")),
    ]
    for category, keywords in rules:
        if any(keyword in name for keyword in keywords):
            return category
    return "其他"


def classify_weekly_work(row: dict) -> str:
    text = f"{row.get('project_name', '')} {row.get('subject', '')}"
    rules = [
        ("国信/小程序/H5", ("国信", "银行 App", "小程序订餐功能迁移")),
        ("机场项目", ("机场",)),
        ("点餐/订餐/消费策略", ("点餐", "订餐", "消费策略", "餐次", "订单状态", "实付金额")),
        ("小票/打印/二维码", ("小票", "二维码", "芯烨", "XP-C58H")),
        ("账户/退款/补贴/报表", ("账户", "退款", "补贴", "报表", "部门查账", "优惠金额", "服务金额")),
        ("人员/权限/数据隔离", ("人员", "离职", "批量导入", "档口权限", "数据隔离", "企业编号")),
        ("项目部署/硬件联调", ("莱蒂森", "金斯瑞", "wattbike", "polar", "德沃", "晨检仪", "设备对接")),
        ("AI问题修复", ("AI 聊天", "content_file_url")),
        ("菜品/营养/智能排菜", ("菜品", "营养", "智能排菜", "口味")),
        ("食安/教委监管", ("食安", "教委", "监管端")),
    ]
    for category, keywords in rules:
        if any(keyword in text for keyword in keywords):
            return category
    return "其他"


def infer_source_project(row: dict) -> str:
    text = f"{row.get('project_name', '')} {row.get('subject', '')}"
    rules = [
        ("国信项目", ("国信",)),
        ("机场项目", ("机场",)),
        ("金斯瑞项目", ("金斯瑞",)),
        ("江西206项目", ("江西206",)),
        ("莱迪森项目", ("莱蒂森", "莱迪森")),
        ("德沃晨检/食安硬件", ("德沃", "晨检仪")),
        ("运动设备联调", ("wattbike", "polar")),
        ("食安教委端/校园版", ("教委", "食安监管系统-校园版", "监管端")),
        ("智慧营养健康餐厅标准产品", ("智慧营养健康餐厅", "标准产品")),
    ]
    for project, keywords in rules:
        if any(keyword in text for keyword in keywords):
            return project
    if "食安系统" in text:
        return "食安系统"
    return row.get("project_name", "未分类项目")


def infer_standard_feature(row: dict) -> str:
    theme = classify_weekly_work(row)
    mapping = {
        "国信/小程序/H5": "客户容器适配与小程序/H5订餐迁移标准",
        "机场项目": "登录、忘记密码与客户短信平台适配标准",
        "点餐/订餐/消费策略": "订餐时间、餐次、消费策略与订单状态规则引擎",
        "小票/打印/二维码": "小票模板所见即所得与打印机配置中心",
        "账户/退款/补贴/报表": "账户退款、补贴交易、消费订单快照与对账报表能力",
        "人员/权限/数据隔离": "人员主数据、物理卡号、档口权限与数据隔离能力",
        "项目部署/硬件联调": "项目部署、硬件联调、支付失败排障与设备接入SOP",
        "AI问题修复": "AI接口异常兼容与聊天历史回归用例",
        "菜品/营养/智能排菜": "菜品价格、营养口径、口味字段与智能排菜基础规则",
        "食安/教委监管": "教委监管端资料清单与食安设备接入标准",
    }
    return mapping.get(theme, "待产品复盘归类")


def infer_product_action(row: dict) -> str:
    status = row.get("status", "")
    category = row.get("category", "")
    if category == "Task":
        return "拆解开发任务、跟踪开发状态、准备验收证据"
    if status in {"已完成", "测试完成", "发布完成"}:
        return "完成需求验收/状态收口，并判断是否进入标准能力"
    if status in {"待测试", "测试中", "集成测试中"}:
        return "推进测试验收，补测试口径和回归场景"
    if status in {"待处理", "已确认"}:
        return "完成需求建模/待排期确认，补业务规则和验收标准"
    if status in {"已取消"}:
        return "清理重复或不再执行需求，避免需求池噪音"
    return "推进需求状态更新和产品复盘"


def infer_next_deposit(row: dict) -> str:
    status = row.get("status", "")
    if status in {"已完成", "测试完成", "发布完成"}:
        return "补充截图/测试用例/操作说明，进入标准功能清单"
    if status in {"待测试", "测试中", "集成测试中"}:
        return "补齐测试用例、回归记录和验收截图"
    if status in {"待处理", "已确认"}:
        return "补业务规则、边界条件、负责人和排期"
    if status == "已取消":
        return "记录取消原因，避免同类需求重复创建"
    return "下周复盘是否纳入标准化资产"


def parse_date(value: str) -> datetime:
    return datetime.strptime(value[:10], "%Y-%m-%d")


def load_yunxiao_stats() -> dict:
    source_dir = get_yunxiao_dir()
    rows = list(csv.DictReader((source_dir / "yunxiao-workitems.csv").open(encoding="utf-8")))
    projects = list(csv.DictReader((source_dir / "yunxiao-projects.csv").open(encoding="utf-8")))
    map_rows = list(csv.DictReader((source_dir / "yunxiao-wedrive-project-map.csv").open(encoding="utf-8")))

    week_start = datetime(2026, 6, 1)
    week_end = datetime(2026, 6, 7)
    weekly_rows = [row for row in rows if week_start <= parse_date(row["gmt_modified"]) < week_end]
    created_this_week = [row for row in rows if week_start <= parse_date(row["gmt_create"]) < week_end]
    weekly_created_ids = {row["serial_number"] for row in created_this_week}
    reqs = [row for row in weekly_rows if row["category"] == "Req"]
    tasks = [row for row in weekly_rows if row["category"] == "Task"]
    status_group_rules = [
        ("已完成/关闭/取消", {"已完成", "发布完成", "测试完成", "已取消", "已关闭", "关闭"}),
        ("待处理/已确认", {"待处理", "已确认"}),
        ("开发中/开发完成/处理中", {"开发中", "开发完成", "处理中"}),
        ("待测试/测试中", {"待测试", "测试中", "集成测试中"}),
        ("发布/灰度/预发中", {"发布中", "预发中", "灰度中"}),
        ("评审/再次打开", {"评审中", "再次打开"}),
    ]
    task_status_groups = []
    for label, statuses in status_group_rules:
        task_status_groups.append((label, sum(1 for row in weekly_rows if row["status"] in statuses)))

    active_statuses = set().union(*[statuses for _, statuses in status_group_rules[1:]])
    class_counts: dict[str, dict] = defaultdict(
        lambda: {"projects": set(), "items": 0, "reqs": 0, "tasks": 0, "active_tasks": 0}
    )
    theme_counts: dict[str, dict] = defaultdict(lambda: {"items": 0, "reqs": 0, "tasks": 0, "new": 0, "done": 0})
    source_counts: dict[str, dict] = defaultdict(
        lambda: {"items": 0, "reqs": 0, "tasks": 0, "new": 0, "done": 0, "features": Counter()}
    )
    by_project: dict[tuple[str, str], dict] = defaultdict(
        lambda: {"items": 0, "reqs": 0, "tasks": 0, "statuses": Counter(), "match": ""}
    )
    for row in weekly_rows:
        project_key = (row["custom_code"], row["project_name"])
        project = by_project[project_key]
        project["items"] += 1
        project["reqs"] += int(row["category"] == "Req")
        project["tasks"] += int(row["category"] == "Task")
        project["statuses"][row["status"]] += 1
        project["match"] = row.get("wedrive_match_status", "")

        category = classify_yunxiao_project(row["project_name"])
        bucket = class_counts[category]
        bucket["projects"].add(row["custom_code"])
        bucket["items"] += 1
        bucket["reqs"] += int(row["category"] == "Req")
        bucket["tasks"] += int(row["category"] == "Task")
        bucket["active_tasks"] += int(row["category"] == "Task" and row["status"] in active_statuses)

        theme = classify_weekly_work(row)
        theme_bucket = theme_counts[theme]
        theme_bucket["items"] += 1
        theme_bucket["reqs"] += int(row["category"] == "Req")
        theme_bucket["tasks"] += int(row["category"] == "Task")
        theme_bucket["new"] += int(row["serial_number"] in weekly_created_ids)
        theme_bucket["done"] += int(row["status"] in {"已完成", "发布完成", "测试完成"})

        source = infer_source_project(row)
        source_bucket = source_counts[source]
        source_bucket["items"] += 1
        source_bucket["reqs"] += int(row["category"] == "Req")
        source_bucket["tasks"] += int(row["category"] == "Task")
        source_bucket["new"] += int(row["serial_number"] in weekly_created_ids)
        source_bucket["done"] += int(row["status"] in {"已完成", "发布完成", "测试完成"})
        source_bucket["features"][infer_standard_feature(row)] += 1

    all_reqs = [row for row in rows if row["category"] == "Req"]
    all_tasks = [row for row in rows if row["category"] == "Task"]

    return {
        "source_dir": source_dir,
        "background_project_count": len(projects),
        "background_workitem_count": len(rows),
        "background_requirement_count": len(all_reqs),
        "background_task_count": len(all_tasks),
        "project_count": len({row["custom_code"] for row in weekly_rows}),
        "workitem_count": len(weekly_rows),
        "requirement_count": len(reqs),
        "task_count": len(tasks),
        "workitem_types": [("需求", len(reqs)), ("开发任务", len(tasks))],
        "task_status_groups": task_status_groups,
        "match_stats": Counter(row["match_status"] for row in map_rows),
        "task_match_stats": Counter(row.get("wedrive_match_status", "") for row in weekly_rows),
        "classifications": [
            (name, len(data["projects"]), data["items"], data["reqs"], data["tasks"], data["active_tasks"])
            for name, data in sorted(class_counts.items(), key=lambda item: item[1]["tasks"], reverse=True)
        ],
        "themes": [
            (name, data["items"], data["reqs"], data["tasks"], data["new"], data["done"])
            for name, data in sorted(theme_counts.items(), key=lambda item: item[1]["items"], reverse=True)
        ],
        "source_summary": [
            (
                name,
                data["items"],
                data["reqs"],
                data["tasks"],
                data["new"],
                data["done"],
                "；".join(feature for feature, _ in data["features"].most_common(3)),
            )
            for name, data in sorted(source_counts.items(), key=lambda item: item[1]["items"], reverse=True)
        ],
        "top_projects": [
            (code, name, data["items"], data["reqs"], data["tasks"], data["match"], "; ".join(f"{k}:{v}" for k, v in data["statuses"].most_common(5)))
            for (code, name), data in sorted(by_project.items(), key=lambda item: item[1]["items"], reverse=True)[:15]
        ],
        "created_this_week": created_this_week,
        "modified_this_week": weekly_rows,
        "weekly_created_ids": weekly_created_ids,
        "week_label": "2026-06-01 至 2026-06-06",
        "generated_at": "2026-06-06 20:42:49 +0800" if source_dir == YUNXIAO_LIVE else "2026-06-01 13:46:37 +0800",
    }


def ensure_dirs() -> None:
    OUTPUTS.mkdir(parents=True, exist_ok=True)


def style_like(ws, src_row: int, dst_row: int, max_col: int) -> None:
    ws.row_dimensions[dst_row].height = ws.row_dimensions[src_row].height
    for col in range(1, max_col + 1):
        src = ws.cell(src_row, col)
        dst = ws.cell(dst_row, col)
        if src.has_style:
            dst._style = copy(src._style)
        if src.number_format:
            dst.number_format = src.number_format
        if src.alignment:
            dst.alignment = copy(src.alignment)
        if src.font:
            dst.font = copy(src.font)
        if src.fill:
            dst.fill = copy(src.fill)
        if src.border:
            dst.border = copy(src.border)


def unmerge_all(ws) -> None:
    for merge in list(ws.merged_cells.ranges):
        ws.unmerge_cells(str(merge))


def setup_common(ws, max_col: int) -> None:
    thin = Side(style="thin", color="D9E2F3")
    for row in ws.iter_rows():
        for cell in row:
            cell.alignment = Alignment(wrap_text=True, vertical="center")
            cell.border = Border(left=thin, right=thin, top=thin, bottom=thin)

    for col in range(1, max_col + 1):
        ws.cell(5 if max_col == 18 else 4, col).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)


def rebuild_project_sheet(ws) -> None:
    unmerge_all(ws)
    ws.delete_rows(6, ws.max_row - 5)
    needed_rows = 5 + len(PROJECT_ROWS)
    if ws.max_row < needed_rows:
        ws.insert_rows(6, needed_rows - ws.max_row)

    for row in range(6, needed_rows + 1):
        style_like(ws, 6 if row == 6 else 7, row, 18)

    ws.merge_cells("A1:R1")
    ws.merge_cells("A2:D3")
    ws.merge_cells("A4:R4")
    ws["A1"] = TITLE
    ws["A2"] = "2026年6月目标（底线147万/冲刺200万，待财务确认）"
    ws["E2"] = "数科"
    ws["F2"] = "智能健身"
    ws["G2"] = "团餐"
    ws["E3"] = "待确认"
    ws["F3"] = "待确认"
    ws["G3"] = "待确认"
    ws["A4"] = "一.项目    围绕6月战役项目抓结果，统一按“合同-采购-交付-上线-验收-回款”六节点推进"

    headers = ["序号", "项目类型", "项目阶段", "项目名称", "关键时间", "项目金额", "负责人", "合同", "采购", "交付", "上线", "验收", "回款", "上周进展", "本周计划/后续动作", "问题与偏差", "备注", "链接"]
    for col, header in enumerate(headers, start=1):
        ws.cell(5, col).value = header

    for i, row in enumerate(PROJECT_ROWS, start=6):
        values = [
            i - 5,
            row["type"],
            row["stage"],
            row["name"],
            row["time"],
            row["amount"],
            row["owner"],
            row["contract"],
            row["procurement"],
            row["delivery"],
            row["launch"],
            row["acceptance"],
            row["cash"],
            row["progress"],
            row["next"],
            row["risk"],
            row["note"],
            row["link"],
        ]
        for col, value in enumerate(values, start=1):
            ws.cell(i, col).value = value

    # Merge the same high-level project type and project stage, matching the template's grouped style.
    for col in (2, 3):
        start = 6
        current = ws.cell(start, col).value
        for row in range(7, needed_rows + 2):
            value = ws.cell(row, col).value if row <= needed_rows else None
            if value != current:
                if row - start > 1:
                    ws.merge_cells(start_row=start, start_column=col, end_row=row - 1, end_column=col)
                    ws.cell(start, col).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
                start = row
                current = value

    setup_common(ws, 18)
    ws.freeze_panes = "A6"
    ws.auto_filter.ref = f"A5:R{needed_rows}"
    ws.print_area = f"A1:R{needed_rows}"
    ws.page_setup.fitToWidth = 1
    ws.page_setup.fitToHeight = 0
    for row in range(6, needed_rows + 1):
        ws.row_dimensions[row].height = 82
    for col, width in {
        "A": 5, "B": 10, "C": 13, "D": 30, "E": 18, "F": 14, "G": 15,
        "H": 12, "I": 14, "J": 20, "K": 15, "L": 15, "M": 14,
        "N": 36, "O": 36, "P": 34, "Q": 28, "R": 32,
    }.items():
        ws.column_dimensions[col].width = width


def rebuild_product_sheet(ws) -> None:
    unmerge_all(ws)
    ws.delete_rows(5, ws.max_row - 4)
    needed_rows = 4 + len(PRODUCT_ROWS)
    if ws.max_row < needed_rows:
        ws.insert_rows(5, needed_rows - ws.max_row)

    for row in range(5, needed_rows + 1):
        style_like(ws, 5 if row == 5 else 6, row, 9)

    ws.merge_cells("A1:I1")
    ws.merge_cells("A2:I2")
    ws["A1"] = TITLE
    ws["A2"] = "二.产品工作 | 按“产品差异化、产品标准化、AI赋能、其他”归纳本周重点工作，并明确时间节点、责任人和对应公司3+2战略"

    headers = ["序号", "一级分类", "二级分类", "工作内容", "对应项目/场景", "时间节点", "负责人", "本周输出/目标", "备注"]
    for col, header in enumerate(headers, start=1):
        ws.cell(4, col).value = header

    for row_idx, row in enumerate(PRODUCT_ROWS, start=5):
        ws.cell(row_idx, 1).value = row_idx - 4
        for col_idx, value in enumerate(row[1:], start=2):
            ws.cell(row_idx, col_idx).value = value

    # Merge first-level categories like the template.
    start = 5
    current = ws.cell(start, 2).value
    for row in range(6, needed_rows + 2):
        value = ws.cell(row, 2).value if row <= needed_rows else None
        if value != current:
            if row - start > 1:
                ws.merge_cells(start_row=start, start_column=2, end_row=row - 1, end_column=2)
                ws.cell(start, 2).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
            start = row
            current = value

    setup_common(ws, 9)
    ws.freeze_panes = "A5"
    ws.auto_filter.ref = f"A4:I{needed_rows}"
    ws.print_area = f"A1:I{needed_rows}"
    ws.page_setup.fitToWidth = 1
    ws.page_setup.fitToHeight = 0
    for row in range(5, needed_rows + 1):
        ws.row_dimensions[row].height = 74
    for col, width in {
        "A": 5, "B": 12, "C": 20, "D": 46, "E": 28,
        "F": 18, "G": 14, "H": 34, "I": 34,
    }.items():
        ws.column_dimensions[col].width = width


def add_yunxiao_ai_sheet(wb) -> None:
    if "云效AI任务统计" in wb.sheetnames:
        del wb["云效AI任务统计"]
    ws = wb.create_sheet("云效AI任务统计")
    stats = load_yunxiao_stats()

    header_fill = PatternFill("solid", fgColor="1F4E79")
    header_font = Font(color="FFFFFF", bold=True)
    title_font = Font(size=16, bold=True, color="1F4E79")
    thin = Side(style="thin", color="D9E2F3")

    ws.merge_cells("A1:I1")
    ws["A1"] = "云效 AI Agent 本周工作统计（2026-06-01 至 2026-06-06）"
    ws["A1"].font = title_font
    ws["A2"] = "数据来源"
    ws["B2"] = str(stats["source_dir"].relative_to(PROJECT_ROOT))
    ws["D2"] = "生成时间"
    ws["E2"] = stats["generated_at"]
    ws["G2"] = "边界"
    ws["H2"] = "只读统计本周创建/变更，不代表自动写回或自动上线"

    metric_rows = [
        ("统计周期", stats["week_label"]),
        ("本周涉及项目数", stats["project_count"]),
        ("本周创建/变更工作项", stats["workitem_count"]),
        ("本周需求数", stats["requirement_count"]),
        ("本周开发任务数", stats["task_count"]),
        ("本周新建工作项", len(stats["created_this_week"])),
        ("本周延续变更工作项", stats["workitem_count"] - len(stats["created_this_week"])),
        ("背景：全量云效项目", stats["background_project_count"]),
        ("背景：全量工作项", stats["background_workitem_count"]),
    ]
    ws.append([])
    ws.append(["一、本周工作规模", "数量/说明"])
    for cell in ws[4]:
        cell.fill = header_fill
        cell.font = header_font
    for row in metric_rows:
        ws.append(list(row))

    type_start = 4 + len(metric_rows) + 3
    ws.cell(type_start, 1).value = "二、工作项类型"
    ws.cell(type_start, 2).value = "数量"
    for cell in ws[type_start]:
        cell.fill = header_fill
        cell.font = header_font
    for idx, row in enumerate(stats["workitem_types"], start=type_start + 1):
        ws.cell(idx, 1).value = row[0]
        ws.cell(idx, 2).value = row[1]

    status_start = type_start
    ws.cell(status_start, 4).value = "三、本周工作状态分布"
    ws.cell(status_start, 5).value = "工作项数"
    for cell in (ws.cell(status_start, 4), ws.cell(status_start, 5)):
        cell.fill = header_fill
        cell.font = header_font
    for idx, row in enumerate(stats["task_status_groups"], start=status_start + 1):
        ws.cell(idx, 4).value = row[0]
        ws.cell(idx, 5).value = row[1]

    theme_start = status_start + len(stats["task_status_groups"]) + 3
    theme_headers = ["四、本周工作主题", "工作项", "需求", "开发任务", "本周新建", "已完成/测试完成/发布完成"]
    for col, value in enumerate(theme_headers, start=1):
        ws.cell(theme_start, col).value = value
        ws.cell(theme_start, col).fill = header_fill
        ws.cell(theme_start, col).font = header_font
    for idx, row in enumerate(stats["themes"], start=theme_start + 1):
        for col, value in enumerate(row, start=1):
            ws.cell(idx, col).value = value

    top_start = theme_start + len(stats["themes"]) + 3
    top_headers = ["五、本周涉及项目", "项目名称", "工作项", "需求", "开发任务", "微盘匹配", "Top 状态"]
    for col, value in enumerate(top_headers, start=1):
        ws.cell(top_start, col).value = value
        ws.cell(top_start, col).fill = header_fill
        ws.cell(top_start, col).font = header_font
    for idx, row in enumerate(stats["top_projects"], start=top_start + 1):
        for col, value in enumerate(row, start=1):
            ws.cell(idx, col).value = value

    week_start = top_start + len(stats["top_projects"]) + 3
    week_headers = ["六、动作", "编号", "类型", "项目", "主题", "标题", "状态", "创建时间", "更新时间"]
    for col, value in enumerate(week_headers, start=1):
        ws.cell(week_start, col).value = value
        ws.cell(week_start, col).fill = header_fill
        ws.cell(week_start, col).font = header_font
    week_rows = []
    for row in sorted(stats["modified_this_week"], key=lambda item: item["gmt_modified"], reverse=True):
        action = "新建" if row["serial_number"] in stats["weekly_created_ids"] else "变更"
        week_rows.append(
            (
                action,
                row["serial_number"],
                "需求" if row["category"] == "Req" else "开发任务",
                row["project_name"],
                classify_weekly_work(row),
                row["subject"],
                row["status"],
                row["gmt_create"],
                row["gmt_modified"],
            )
        )
    for idx, row in enumerate(week_rows[:60], start=week_start + 1):
        for col, value in enumerate(row, start=1):
            ws.cell(idx, col).value = value

    pie = PieChart()
    pie.title = "本周工作项类型"
    pie.add_data(Reference(ws, min_col=2, min_row=type_start, max_row=type_start + len(stats["workitem_types"])), titles_from_data=True)
    pie.set_categories(Reference(ws, min_col=1, min_row=type_start + 1, max_row=type_start + len(stats["workitem_types"])))
    ws.add_chart(pie, "H4")

    status_chart = BarChart()
    status_chart.type = "bar"
    status_chart.title = "本周工作状态分布"
    status_chart.y_axis.title = "状态"
    status_chart.x_axis.title = "工作项数"
    status_chart.add_data(Reference(ws, min_col=5, min_row=status_start, max_row=status_start + len(stats["task_status_groups"])), titles_from_data=True)
    status_chart.set_categories(Reference(ws, min_col=4, min_row=status_start + 1, max_row=status_start + len(stats["task_status_groups"])))
    status_chart.height = 7
    status_chart.width = 13
    ws.add_chart(status_chart, "H19")

    theme_chart = BarChart()
    theme_chart.title = "本周工作主题分布"
    theme_chart.y_axis.title = "工作项数"
    theme_chart.add_data(Reference(ws, min_col=2, min_row=theme_start, max_row=theme_start + len(stats["themes"])), titles_from_data=True)
    theme_chart.set_categories(Reference(ws, min_col=1, min_row=theme_start + 1, max_row=theme_start + len(stats["themes"])))
    theme_chart.height = 7
    theme_chart.width = 13
    ws.add_chart(theme_chart, "H35")

    for row in ws.iter_rows():
        for cell in row:
            cell.alignment = Alignment(wrap_text=True, vertical="top")
            cell.border = Border(left=thin, right=thin, top=thin, bottom=thin)
    for row_idx in (4, type_start, status_start, theme_start, top_start, week_start):
        for cell in ws[row_idx]:
            if cell.value:
                cell.fill = header_fill
                cell.font = header_font
                cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
    for row in range(1, ws.max_row + 1):
        ws.row_dimensions[row].height = 26
    for row in (1, 2):
        ws.row_dimensions[row].height = 30
    for col, width in {
        "A": 18, "B": 22, "C": 18, "D": 34, "E": 14,
        "F": 18, "G": 34, "H": 18, "I": 18,
    }.items():
        ws.column_dimensions[col].width = width
    ws.freeze_panes = "A5"
    ws.sheet_view.zoomScale = 90


def add_project_standardization_sheet(wb) -> None:
    if "项目来源与标准化沉淀" in wb.sheetnames:
        del wb["项目来源与标准化沉淀"]
    ws = wb.create_sheet("项目来源与标准化沉淀")
    stats = load_yunxiao_stats()

    header_fill = PatternFill("solid", fgColor="1F4E79")
    header_font = Font(color="FFFFFF", bold=True)
    title_font = Font(size=16, bold=True, color="1F4E79")
    thin = Side(style="thin", color="D9E2F3")

    ws.merge_cells("A1:J1")
    ws["A1"] = "本周云效任务来源与标准化沉淀思路"
    ws["A1"].font = title_font
    ws["A2"] = "表达口径"
    ws["B2"] = "从项目来源看需求，从标准功能看沉淀，从产品动作看本周贡献"
    ws["E2"] = "统计周期"
    ws["F2"] = stats["week_label"]
    ws["H2"] = "本周工作项"
    ws["I2"] = stats["workitem_count"]

    source_start = 4
    source_headers = ["一、项目/场景来源", "工作项", "需求", "开发任务", "本周新建", "已完成/测试完成/发布完成", "可沉淀标准功能"]
    for col, value in enumerate(source_headers, start=1):
        ws.cell(source_start, col).value = value
        ws.cell(source_start, col).fill = header_fill
        ws.cell(source_start, col).font = header_font
    for row_idx, row in enumerate(stats["source_summary"], start=source_start + 1):
        for col_idx, value in enumerate(row, start=1):
            ws.cell(row_idx, col_idx).value = value

    product_start = source_start + len(stats["source_summary"]) + 3
    product_rows = [
        ("需求建模与任务整理", f"梳理本周 {stats['workitem_count']} 条创建/变更工作项，其中需求 {stats['requirement_count']} 条、开发任务 {stats['task_count']} 条。", "把云效从任务池变成产品周报证据。", "保留来源、状态、主题和标准化归类。"),
        ("项目来源归类", "将工作项归到国信、机场、金斯瑞、江西206、莱迪森、食安教委、德沃晨检和标准产品等来源。", "让领导看到需求不是散点，而是来自明确项目/场景。", "下周补齐每个项目的SOP阶段和负责人。"),
        ("标准功能抽取", "从工作项中抽取小票模板、消费策略、账户退款、人员权限、硬件联调、AI异常修复等标准能力候选。", "把一次性项目需求转成可复用产品能力。", "对待处理/待测试项补验收标准和截图证据。"),
        ("测试与验收推进", "本周状态包含已完成、待测试、开发完成、测试完成、发布完成和待处理，能看出产品验收链路。", "产品人不只提需求，还要推动状态收口和验收口径。", "优先把待测试项转成测试用例和回归记录。"),
        ("AI Agent试跑选题", "从小票模板、点餐规则、消费策略、AI聊天500问题中挑低/中风险任务做Agent闭环。", "让AI Agent基于真实云效任务干事，而不是只写总结。", "形成任务契约、代码验证、证据包和人审页。"),
    ]
    product_headers = ["二、产品人本周工作", "本周动作", "价值表达", "下周沉淀"]
    for col, value in enumerate(product_headers, start=1):
        ws.cell(product_start, col).value = value
        ws.cell(product_start, col).fill = header_fill
        ws.cell(product_start, col).font = header_font
    for row_idx, row in enumerate(product_rows, start=product_start + 1):
        for col_idx, value in enumerate(row, start=1):
            ws.cell(row_idx, col_idx).value = value

    detail_start = product_start + len(product_rows) + 3
    detail_headers = ["三、动作", "编号", "项目/场景来源", "云效项目", "类型", "工作主题", "标准化功能沉淀", "产品人动作", "状态", "下周沉淀"]
    for col, value in enumerate(detail_headers, start=1):
        ws.cell(detail_start, col).value = value
        ws.cell(detail_start, col).fill = header_fill
        ws.cell(detail_start, col).font = header_font
    for row_idx, row in enumerate(
        sorted(stats["modified_this_week"], key=lambda item: (infer_source_project(item), classify_weekly_work(item), item["serial_number"])),
        start=detail_start + 1,
    ):
        action = "新建" if row["serial_number"] in stats["weekly_created_ids"] else "变更"
        values = [
            action,
            row["serial_number"],
            infer_source_project(row),
            row["project_name"],
            "需求" if row["category"] == "Req" else "开发任务",
            classify_weekly_work(row),
            infer_standard_feature(row),
            infer_product_action(row),
            row["status"],
            infer_next_deposit(row),
        ]
        for col_idx, value in enumerate(values, start=1):
            ws.cell(row_idx, col_idx).value = value

    chart = BarChart()
    chart.title = "本周项目/场景来源分布"
    chart.y_axis.title = "工作项数"
    chart.add_data(Reference(ws, min_col=2, min_row=source_start, max_row=source_start + len(stats["source_summary"])), titles_from_data=True)
    chart.set_categories(Reference(ws, min_col=1, min_row=source_start + 1, max_row=source_start + len(stats["source_summary"])))
    chart.height = 7
    chart.width = 13
    ws.add_chart(chart, "H4")

    for row in ws.iter_rows():
        for cell in row:
            cell.alignment = Alignment(wrap_text=True, vertical="top")
            cell.border = Border(left=thin, right=thin, top=thin, bottom=thin)
    for row_idx in (source_start, product_start, detail_start):
        for cell in ws[row_idx]:
            if cell.value:
                cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
    for row in range(1, ws.max_row + 1):
        ws.row_dimensions[row].height = 34
    ws.row_dimensions[1].height = 32
    for col, width in {
        "A": 18, "B": 12, "C": 14, "D": 18, "E": 14,
        "F": 20, "G": 34, "H": 34, "I": 14, "J": 30,
    }.items():
        ws.column_dimensions[col].width = width
    ws.freeze_panes = "A5"
    ws.sheet_view.zoomScale = 90


def add_source_sheet(wb) -> None:
    if "来源索引" in wb.sheetnames:
        del wb["来源索引"]
    ws = wb.create_sheet("来源索引")
    rows = [
        ["source_id", "source_name", "path", "used_for", "evidence_level"],
        ["S01", "用户清晰版周报", str(CLEAR), "原有结构和已提炼内容", "B/D"],
        ["S02", "事业部周报模板", str(TEMPLATE), "项目/产品 sheet 样式、字段和分组模板", "B"],
        ["S03", "丰富版周报 Excel", str(RICH), "已整理的项目、产品、规划、AI、风险内容", "B/D"],
        ["S04", "W23团队周汇总", "work/weekly-reports/outputs/2026-W23/team-weekly-summary.md", "机场餐卡、知识库、莱迪森、云效 Agent、专利等补充", "B/D"],
        ["S05", "专项图文周报", "work/weekly-reports/2026-W23/product-standardization-ai-weekly/product-standardization-ai-weekly-report.md", "五层分解、SOP、产品规划、AI资产和Before/After口径", "B/D"],
        ["S06", "城市副中心技术销售推进事项", "work/2026-06-04-city-subcenter-tech-solution/sales-actions.md", "城市副中心关键时间和客户确认事项", "B"],
        ["S07", "莱迪森部署进度", "work_laidisen/deployment-progress.md", "莱迪森部署状态、下一步和敏感边界", "B"],
        ["S08", "产品标准化/AI Native素材", "work/2026-05-29-product-standardization-ai-native-weekly-material/", "国信、金斯瑞、江西206、网信办、机场等标准化/金额参考", "B/D"],
        ["S09", "HEARTBEAT/QUEUE", "HEARTBEAT.md; tasks/QUEUE.md", "当前项目状态与恢复入口", "B"],
        ["S10", "团队云效 CLI 实时只读快照", "work/weekly-reports/2026-W23/template-weekly-report-enhancement/yunxiao-live-20260606/", "AI赋能：从实时快照中筛选2026/6/1-6/6本周创建或变更的42条工作项", "A/B"],
        ["S11", "云效 × 研发 Agent 阶段成果", "work/2026-06-02-yunxiao-development-agent-stage-results/summary.md", "研发 Agent 控制链路、harness、人审和下一阶段试跑边界", "A/B/D"],
        ["S12", "项目来源与标准化沉淀映射", "本工作簿《项目来源与标准化沉淀》sheet", "将42条本周云效工作项映射到项目来源、标准功能候选和产品人动作", "D"],
    ]
    for row in rows:
        ws.append(row)
    header_fill = PatternFill("solid", fgColor="1F4E79")
    header_font = Font(color="FFFFFF", bold=True)
    for cell in ws[1]:
        cell.fill = header_fill
        cell.font = header_font
    for row in ws.iter_rows():
        for cell in row:
            cell.alignment = Alignment(wrap_text=True, vertical="top")
            cell.border = Border(left=Side(style="thin", color="D9E2F3"), right=Side(style="thin", color="D9E2F3"), top=Side(style="thin", color="D9E2F3"), bottom=Side(style="thin", color="D9E2F3"))
    for col, width in {"A": 10, "B": 24, "C": 70, "D": 42, "E": 14}.items():
        ws.column_dimensions[col].width = width
    ws.freeze_panes = "A2"


def write_source_index() -> None:
    rows = [
        ["source_id", "source_name", "path", "used_for", "evidence_level"],
        ["S01", "用户清晰版周报", str(CLEAR), "原有结构和已提炼内容", "B/D"],
        ["S02", "事业部周报模板", str(TEMPLATE), "项目/产品 sheet 样式、字段和分组模板", "B"],
        ["S03", "丰富版周报 Excel", str(RICH), "已整理的项目、产品、规划、AI、风险内容", "B/D"],
        ["S04", "W23团队周汇总", "work/weekly-reports/outputs/2026-W23/team-weekly-summary.md", "W23补充内容", "B/D"],
        ["S05", "专项图文周报", "work/weekly-reports/2026-W23/product-standardization-ai-weekly/product-standardization-ai-weekly-report.md", "新周报底层逻辑", "B/D"],
        ["S06", "城市副中心", "work/2026-06-04-city-subcenter-tech-solution/sales-actions.md", "关键时间与客户确认事项", "B"],
        ["S07", "莱迪森", "work_laidisen/deployment-progress.md", "部署状态和下一步", "B"],
        ["S08", "团队云效 CLI 实时只读快照", "work/weekly-reports/2026-W23/template-weekly-report-enhancement/yunxiao-live-20260606/", "AI赋能本周任务统计：2026/6/1-6/6创建或变更工作项", "A/B"],
        ["S09", "云效 × 研发 Agent 阶段成果", "work/2026-06-02-yunxiao-development-agent-stage-results/summary.md", "研发 Agent 阶段成果与边界", "A/B/D"],
        ["S10", "项目来源与标准化沉淀映射", "本工作簿《项目来源与标准化沉淀》sheet", "项目来源、标准功能候选和产品人动作", "D"],
    ]
    with SOURCE_INDEX.open("w", encoding="utf-8", newline="") as f:
        csv.writer(f).writerows(rows)


def write_summary() -> None:
    SUMMARY.write_text(
        "\n".join(
            [
                "# 数字技术中心产品开发部周报模板版丰富版",
                "",
                "## Goal",
                "基于用户给出的清晰版周报和事业部周报模板，继续从 zhctprompt 项目内补充内容，生成更丰满且严格贴合模板的 Excel 周报。",
                "",
                "## Output",
                f"- 项目内输出：`{OUT.relative_to(PROJECT_ROOT)}`",
                f"- Downloads 副本：`{DOWNLOAD_COPY}`",
                "",
                "## Template Rule",
                "- 以 `周报模板.xlsx` 为母版。",
                "- 保留 `项目` / `产品` 两个主 sheet。",
                "- `项目` sheet 严格使用模板字段：项目类型、项目阶段、项目名称、关键时间、项目金额、负责人、合同、采购、交付、上线、验收、回款、上周进展、本周计划/后续动作、问题与偏差、备注、链接。",
                "- 项目按团餐、数科、产品/AI协作分组，并保留模板式合并分组。",
                "",
                "## Content Enrichment",
                f"- 项目表：{len(PROJECT_ROWS)} 条。",
                f"- 产品表：{len(PRODUCT_ROWS)} 条。",
                "- 新增 `云效AI任务统计` sheet：基于团队云效 CLI 只读快照，筛选 2026/6/1-6/6 本周创建或变更的 42 条工作项，并生成类型、状态、工作主题和明细图表。",
                "- 新增 `项目来源与标准化沉淀` sheet：把本周任务映射到项目/场景来源、标准化功能候选、产品人动作和下周沉淀方式。",
                "- 补充来源包括 W23 团队周汇总、专项图文周报、丰富版 Excel、城市副中心技术推进事项、莱迪森部署进度、产品标准化/AI Native 素材和 HEARTBEAT/QUEUE。",
                "",
                "## Evidence Boundary",
                "- 金额只引用项目内已有来源，并标注待商务/财务确认。",
                "- 合同、验收、回款、客户反馈未有证据时写待确认或触发条件。",
                "- AI 提效、标准化价值和复用价值按 B/D 级解释处理，不写成已财务验证结果。",
                "",
            ]
        ),
        encoding="utf-8",
    )


def build() -> None:
    ensure_dirs()
    wb = load_workbook(TEMPLATE)
    # Keep only the two template sheets, then add source index.
    for sheet in list(wb.sheetnames):
        if sheet not in {"项目", "产品"}:
            del wb[sheet]
    rebuild_project_sheet(wb["项目"])
    rebuild_product_sheet(wb["产品"])
    add_yunxiao_ai_sheet(wb)
    add_project_standardization_sheet(wb)
    add_source_sheet(wb)
    wb.save(OUT)
    shutil.copy2(OUT, DOWNLOAD_COPY)
    write_source_index()
    write_summary()
    print(OUT)
    print(DOWNLOAD_COPY)


if __name__ == "__main__":
    build()
