#!/usr/bin/env python3
"""Generate the V2 goal-execution edition of the delivery operating system."""

from __future__ import annotations

import csv
import html
import json
import os
import subprocess
import sys
import tempfile
from pathlib import Path
from zipfile import ZIP_DEFLATED, ZipFile

from docx import Document
from docx.enum.section import WD_SECTION
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Cm, Pt, RGBColor
from lxml import etree


ROOT = Path(__file__).resolve().parent
PROJECT_ROOT = ROOT.parents[1]
V1_ROOT = PROJECT_ROOT / "work" / "2026-08-06-delivery-department-operating-system"
TEMPLATE_ROOT = PROJECT_ROOT / "standards-stack" / "agent-skills" / "skills" / "kangbite-word-template"
TEMPLATE = TEMPLATE_ROOT / "assets" / "kangbite-digital-sports-word-template.docx"
PREPARE = TEMPLATE_ROOT / "scripts" / "prepare_working_copy.py"
STARTER = ROOT / "tmp" / "delivery-operating-system-v2-starter.docx"
FINAL_DOCX = ROOT / "交付实施部目标落地管理体系_V2.0.docx"
METADATA = ROOT / "wecom-v2-workbook-metadata.json"


def field(name, kind, definition, owner, timing, required="是", quality="非空且与事实一致", dashboard="否"):
    return {
        "name": name,
        "type": kind,
        "definition": definition,
        "owner": owner,
        "timing": timing,
        "required": required,
        "quality": quality,
        "dashboard": dashboard,
    }


SHEETS = [
    {
        "title": "01-项目目标总表",
        "purpose": "一项目一行，只回答项目要交什么、谁负责、哪天完成、现在是否可信。",
        "fields": [
            field("项目ID", "FIELD_TYPE_TEXT", "项目唯一编号", "管理者/PMO", "项目纳入管理当天", quality="唯一且不重复"),
            field("项目名称", "FIELD_TYPE_TEXT", "客户可识别的项目名称", "项目负责人", "项目建立时", dashboard="是"),
            field("项目负责人", "FIELD_TYPE_TEXT", "对项目最终结果负责的唯一人员", "部门负责人", "项目分配当天", dashboard="是"),
            field("当前阶段", "FIELD_TYPE_SINGLE_SELECT", "准备/部署/数据接口/设备联调/系统培训/试运行/验收", "项目负责人", "阶段变化时", dashboard="是"),
            field("本月目标结果", "FIELD_TYPE_TEXT", "本月必须交付的可验收结果，不写过程动作", "项目负责人", "每月首个工作日", dashboard="是"),
            field("计划完成日", "FIELD_TYPE_DATE_TIME", "经确认的目标完成日期", "项目负责人", "项目启动或目标确认时", dashboard="是"),
            field("预测完成日", "FIELD_TYPE_DATE_TIME", "基于当前事实预计完成的日期", "项目负责人", "每周一；偏差发生时", dashboard="是"),
            field("本周唯一目标", "FIELD_TYPE_TEXT", "本周最重要且可验收的结果", "项目负责人", "每周一", dashboard="是"),
            field("健康灯", "FIELD_TYPE_SINGLE_SELECT", "绿=按期；黄=有偏差但可追回；红=需要外部决策或无法按期", "项目负责人", "每周一；状态变化时", dashboard="是"),
            field("关键阻塞", "FIELD_TYPE_TEXT", "当前最影响目标兑现的一个事实", "项目负责人", "发生阻塞当天", required="否", dashboard="是"),
            field("下一动作", "FIELD_TYPE_TEXT", "当前最重要的推进动作", "项目负责人", "每周一；动作完成后", dashboard="是"),
            field("需要领导支持", "FIELD_TYPE_TEXT", "明确需要的决策、资源或跨部门协调", "项目负责人", "形成红灯或跨部门阻塞时", required="否", dashboard="是"),
            field("结果证据", "FIELD_TYPE_URL", "计划、截图、验收或交付文件链接", "项目负责人", "关键结果完成时", required="否", quality="标记完成时必须有证据"),
            field("最后更新时间", "FIELD_TYPE_DATE_TIME", "项目事实最后更新时间", "项目负责人/PMO", "每次更新时", dashboard="是"),
        ],
    },
    {
        "title": "02-本周行动清单",
        "purpose": "每个人只维护未来一周最多3项主动任务，确保知道接下来做什么、什么时候结束。",
        "fields": [
            field("任务ID", "FIELD_TYPE_TEXT", "任务唯一编号", "项目负责人/PMO", "任务创建时", quality="唯一且不重复"),
            field("项目ID", "FIELD_TYPE_TEXT", "关联项目", "项目负责人", "任务创建时", dashboard="是"),
            field("行动结果", "FIELD_TYPE_TEXT", "动词开头、完成后可验收的结果", "项目负责人", "每周一或任务新增时", dashboard="是"),
            field("责任人", "FIELD_TYPE_TEXT", "对任务结果负责的唯一人员", "项目负责人", "任务分配时", dashboard="是"),
            field("截止时间", "FIELD_TYPE_DATE_TIME", "任务必须完成的时间", "项目负责人", "任务分配时", dashboard="是"),
            field("完成标准", "FIELD_TYPE_TEXT", "做到什么程度才算完成", "项目负责人", "任务分配时", quality="无完成标准不进入执行"),
            field("状态", "FIELD_TYPE_SINGLE_SELECT", "未开始/进行中/受阻/待确认/已完成", "任务责任人", "接单时；完成或受阻时", dashboard="是"),
            field("证据链接", "FIELD_TYPE_URL", "截图、文档、系统状态或客户确认", "任务责任人", "任务完成时", required="否", quality="无证据不得标记已完成"),
            field("阻塞说明", "FIELD_TYPE_TEXT", "阻塞事实、影响和已采取动作", "任务责任人", "受阻当天", required="否", dashboard="是"),
            field("下一步", "FIELD_TYPE_TEXT", "解除阻塞或继续推进的具体动作", "任务责任人", "受阻或状态变化时", required="否"),
            field("最后更新时间", "FIELD_TYPE_DATE_TIME", "任务最后更新时间", "任务责任人", "每次更新时", dashboard="是"),
        ],
    },
    {
        "title": "03-问题升级清单",
        "purpose": "只记录需要跨人、跨部门或领导决策的问题，避免普通事项挤占会议。",
        "fields": [
            field("问题ID", "FIELD_TYPE_TEXT", "问题唯一编号", "项目负责人/PMO", "问题升级时", quality="唯一且不重复"),
            field("项目ID", "FIELD_TYPE_TEXT", "关联项目", "项目负责人", "问题升级时", dashboard="是"),
            field("问题事实", "FIELD_TYPE_TEXT", "一句话写清已经发生或即将发生的事实", "项目负责人", "发现当天", dashboard="是"),
            field("影响", "FIELD_TYPE_TEXT", "对日期、客户、质量或范围的具体影响", "项目负责人", "发现当天", dashboard="是"),
            field("解决责任人", "FIELD_TYPE_TEXT", "推动问题关闭的唯一人员", "部门负责人/项目负责人", "问题升级当天", dashboard="是"),
            field("建议方案", "FIELD_TYPE_TEXT", "责任人建议采用的解决方案", "项目负责人", "问题升级当天", dashboard="是"),
            field("需要谁决策", "FIELD_TYPE_TEXT", "必须做决定或提供资源的人", "项目负责人", "问题升级当天", dashboard="是"),
            field("最晚决策时间", "FIELD_TYPE_DATE_TIME", "超过后将产生明确影响的时间", "项目负责人", "问题升级当天", dashboard="是"),
            field("状态", "FIELD_TYPE_SINGLE_SELECT", "待处理/处理中/待决策/已关闭", "解决责任人", "状态变化时", dashboard="是"),
            field("决策或关闭证据", "FIELD_TYPE_URL", "决策记录、确认或关闭证据", "解决责任人", "决策或关闭当天", required="否", quality="关闭时必须有证据"),
        ],
    },
    {
        "title": "04-项目周复盘",
        "purpose": "每周只复盘目标是否完成、为什么、下周三件事和需要什么决策。",
        "fields": [
            field("周次", "FIELD_TYPE_TEXT", "自然周，例如2026-W32", "PMO", "每周五"),
            field("项目ID", "FIELD_TYPE_TEXT", "关联项目", "项目负责人", "每周五", dashboard="是"),
            field("本周目标", "FIELD_TYPE_TEXT", "本周承诺的唯一结果", "项目负责人", "每周一确认；周五回读"),
            field("完成情况", "FIELD_TYPE_SINGLE_SELECT", "完成/部分完成/未完成", "项目负责人", "每周五", dashboard="是"),
            field("结果证据", "FIELD_TYPE_URL", "证明目标完成的链接", "项目负责人", "每周五", required="否", quality="完成时必须有证据"),
            field("偏差原因", "FIELD_TYPE_TEXT", "未完成或延期的直接原因", "项目负责人", "每周五", required="否", dashboard="是"),
            field("下周三件事", "FIELD_TYPE_TEXT", "下周最多3项关键结果", "项目负责人", "每周五", dashboard="是"),
            field("需要决策", "FIELD_TYPE_TEXT", "领导下周需要拍板的事项", "项目负责人", "每周五", required="否", dashboard="是"),
            field("管理复核", "FIELD_TYPE_SINGLE_SELECT", "未复核/已复核/退回补充", "部门负责人/PMO", "周会前", dashboard="是"),
        ],
    },
]


METRICS = [
    ["在手项目数", "目前需要管理多少项目", "01-项目目标总表", "项目状态有效的项目数量", "管理者/PMO"],
    ["绿黄红项目数", "哪些项目按期、偏差或需要决策", "01-项目目标总表", "按健康灯分组统计", "项目负责人"],
    ["本周到期任务数", "本周必须兑现多少行动", "02-本周行动清单", "截止时间在本周的任务数量", "项目负责人/PMO"],
    ["逾期任务数", "哪些承诺已经过期", "02-本周行动清单", "截止时间已过且未完成的任务数量", "任务责任人"],
    ["待决策问题数", "领导现在必须拍板什么", "03-问题升级清单", "状态为待决策的问题数量", "部门负责人"],
]


ROLES = [
    ["部门负责人", "确定项目优先级、指定项目负责人、处理红灯和跨部门资源", "周一看目标；红灯当天决策；周五复盘"],
    ["项目负责人", "对项目目标、完成日期、任务分解、健康灯和问题升级负责", "每周一定目标；变化时更新；周五复盘"],
    ["交付实施人员", "完成被分配的行动，受阻立即说明，完成必须留证据", "接单时确认；完成或受阻时更新"],
    ["PMO/管理支持", "检查字段是否完整，汇总五个指标，组织只讨论偏差的周会", "周一检查；周五锁定数据"],
]


RULES = [
    "一个项目只能有一个项目负责人、一个本月目标和一个本周唯一目标。",
    "每个人同时进行的主动任务最多3项；新增任务前先完成、取消或延期旧任务。",
    "任务必须有责任人、截止时间、完成标准；缺一项不进入执行。",
    "状态只在接单、完成、受阻或日期变化时更新，不要求每天写长篇日报。",
    "完成必须有证据；没有截图、文档、系统状态或客户确认，不算完成。",
    "红灯问题当天升级，并附建议方案、决策人和最晚决策时间。",
]


def matrix_rows():
    rows = []
    for sheet in SHEETS:
        for item in sheet["fields"]:
            rows.append([
                sheet["title"], item["name"], item["definition"], item["owner"],
                item["timing"], item["required"], item["quality"], item["dashboard"],
            ])
    return rows


def online_url():
    if METADATA.exists():
        return json.loads(METADATA.read_text(encoding="utf-8")).get("url", "")
    return "企微Agent创建后写入"


def write_csv(name, headers, rows):
    with (ROOT / name).open("w", encoding="utf-8-sig", newline="") as handle:
        writer = csv.writer(handle)
        writer.writerow(headers)
        writer.writerows(rows)


def rich(value):
    return [{"type": "text", "text": str(value)}]


def build_schema():
    schema = {"title": "交付实施部-目标落地简化版-V2.0-20260807", "sheets": []}
    for sheet in SHEETS:
        schema["sheets"].append({
            "title": sheet["title"],
            "purpose": sheet["purpose"],
            "fields": [{"field_title": item["name"], "field_type": item["type"]} for item in sheet["fields"]],
            "records": [],
        })
    (ROOT / "wecom-v2-workbook-schema.json").write_text(
        json.dumps(schema, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
    )


def build_markdown():
    lines = [
        "# 交付实施部目标落地管理体系 V2.0",
        "",
        "- 定位：当前阶段试运行版，优先解决‘交付人员不知道接下来做什么、什么时候结束’。",
        "- V1：完整管理蓝图，继续保留，作为未来能力库，不要求当前团队一次性执行。",
        f"- V1在线工作簿：{json.loads((V1_ROOT / 'wecom-workbook-metadata.json').read_text(encoding='utf-8')).get('url', '')}",
        f"- V2在线工作簿：{online_url()}",
        "- 证据边界：V2是管理试运行方案，效果需通过4个真实项目、4周执行验证。",
        "",
        "> V2目标：让每个项目有结果、有负责人、有截止日期；让每个交付人只看未来一周最多3项行动；让领导只看偏差和需要决策的事项。",
        "",
        "## 1. 为什么从V1简化",
        "",
        "V1回答‘成熟的交付实施部最终应该如何管理’，V2回答‘当前团队下周开始能坚持做什么’。V2暂不强制维护预算人天、容量、定制化比例、产品复用、回款金额和完整七阶段任务库。这些内容继续保留在V1，只有当基础数据连续4周稳定后才逐项启用。",
        "",
        "## 2. V2只管五件事",
        "",
        "1. 要交什么结果。",
        "2. 谁对结果负责。",
        "3. 哪天必须完成。",
        "4. 做到什么才算完成。",
        "5. 完成证据在哪里。",
        "",
        "## 3. 四张表",
        "",
        "| 表 | 用途 | 主要填写人 | 更新时间 |",
        "|---|---|---|---|",
    ]
    for sheet in SHEETS:
        owners = "、".join(dict.fromkeys(item["owner"] for item in sheet["fields"]))
        lines.append(f"| {sheet['title']} | {sheet['purpose']} | {owners} | 按字段触发 |")
    lines += ["", "## 4. 谁负责什么", "", "| 角色 | 只负责什么 | 什么时候动作 |", "|---|---|---|"]
    lines += ["| " + " | ".join(row) + " |" for row in ROLES]
    lines += ["", "## 5. 六条执行规则", ""]
    lines += [f"{idx}. {text}" for idx, text in enumerate(RULES, 1)]
    lines += [
        "", "## 6. 固定节奏", "",
        "- 每周一30分钟：项目负责人确认本周唯一目标，给每人最多3项行动。",
        "- 周中按事件更新：完成、受阻或日期变化才更新；受阻当天升级，不写长日报。",
        "- 每周五30分钟：项目负责人完成周复盘，PMO锁定数据。",
        "- 领导周会20分钟：只看红黄项目、逾期任务和待决策问题，不逐项目念进度。",
        "", "## 7. 领导只看五个数字", "",
        "| 指标 | 回答的问题 | 来源 | 口径 | 责任人 |", "|---|---|---|---|---|",
    ]
    lines += ["| " + " | ".join(row) + " |" for row in METRICS]
    lines += [
        "", "## 8. 四周落地", "",
        "| 周次 | 只做什么 | 通过标准 |", "|---|---|---|",
        "| 第1周 | 录入城市副中心、国康、滨州健康职业学院、玉溪体校4个项目；确认负责人、本月目标和完成日 | 4个项目必填字段完整 |",
        "| 第2周 | 每个项目设置本周唯一目标；每人最多3项行动 | 所有人能从表中回答下一步和截止时间 |",
        "| 第3周 | 启用问题升级清单和20分钟领导周会 | 红灯问题都有方案、决策人和时限 |",
        "| 第4周 | 复盘执行率与维护成本，决定是否启用V1的验收、人天或产品复用模块 | 形成继续、调整或停止清单 |",
        "", "## 9. V2试运行成功标准", "",
        "- 4个试点项目100%有负责人、目标结果和计划完成日。",
        "- 每名交付人员能在1分钟内说清自己的下一项任务和截止时间。",
        "- 已完成任务100%有可回读证据。",
        "- 受阻和逾期事项在发生当天进入清单，不依赖领导追问。",
        "- 周会只讨论偏差和决策，控制在20分钟左右。",
        "", "## 10. V1什么时候启用", "",
        "只有当V2连续4周稳定运行，且团队字段完整率、证据率和周会节奏可维持时，再按真实痛点从V1逐项增加：先验收节点，再人天成本，再产品复用；不一次性全部打开。",
        "", "## 附录：逐字段责任", "",
        "完整字段责任见 `v2-field-responsibility-matrix.csv`。V2共4张表、44个字段，业务表保持空白，待4个真实项目录入。",
    ]
    (ROOT / "delivery-implementation-department-operating-system-v2.md").write_text("\n".join(lines) + "\n", encoding="utf-8")


def esc(value):
    return html.escape(str(value), quote=True)


def build_html():
    sheet_cards = "".join(
        f'<article><span>{idx:02d}</span><h3>{esc(sheet["title"][3:])}</h3><p>{esc(sheet["purpose"])}</p><b>{len(sheet["fields"])}个字段</b></article>'
        for idx, sheet in enumerate(SHEETS, 1)
    )
    metric_cards = "".join(
        f'<div class="metric"><small>{esc(row[0])}</small><strong>待录入</strong><p>{esc(row[1])}</p></div>' for row in METRICS
    )
    role_rows = "".join(f"<tr><td>{esc(r[0])}</td><td>{esc(r[1])}</td><td>{esc(r[2])}</td></tr>" for r in ROLES)
    field_rows = "".join(
        f"<tr><td>{esc(r[0])}</td><td>{esc(r[1])}</td><td>{esc(r[3])}</td><td>{esc(r[4])}</td><td>{esc(r[6])}</td></tr>"
        for r in matrix_rows()
    )
    doc = f"""<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>交付实施部目标落地管理体系 V2.0</title><style>
*{{box-sizing:border-box}}body{{margin:0;background:#f2f6f5;color:#203536;font-family:'Microsoft YaHei','PingFang SC',sans-serif}}.wrap{{width:min(1180px,calc(100% - 32px));margin:24px auto 60px}}header{{padding:56px;border-radius:28px;background:linear-gradient(135deg,#123f42,#176d69 62%,#d57938);color:white;box-shadow:0 18px 60px #174a4930}}.eyebrow{{font-size:13px;letter-spacing:.16em;opacity:.75}}h1{{font-size:42px;line-height:1.2;margin:16px 0}}header p{{font-size:18px;max-width:820px;line-height:1.7}}header a{{display:inline-block;margin-top:14px;padding:12px 20px;background:white;color:#176d69;text-decoration:none;border-radius:10px;font-weight:700}}section{{margin-top:24px;background:white;padding:34px;border-radius:22px;border:1px solid #dce8e6}}h2{{margin:0 0 18px;color:#164f50;font-size:26px}}.lead{{font-size:22px;line-height:1.7;color:#355a5b}}.callout{{padding:24px;border-left:5px solid #d57938;background:#fff6ef;border-radius:12px;font-size:18px;line-height:1.7}}.grid4{{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}}article{{padding:22px;background:#f5f9f8;border-radius:16px;border:1px solid #dce8e6}}article span{{color:#d57938;font-weight:800}}article h3{{margin:10px 0;color:#176d69}}article p{{min-height:76px;line-height:1.6;color:#526a6b}}article b{{font-size:13px}}.metrics{{display:grid;grid-template-columns:repeat(5,1fr);gap:12px}}.metric{{padding:20px;border-radius:14px;background:#143f42;color:white}}.metric small{{display:block;min-height:40px}}.metric strong{{display:block;margin:16px 0 10px;font-size:24px;color:#f4b27d}}.metric p{{font-size:13px;line-height:1.5;opacity:.8}}table{{width:100%;border-collapse:collapse;font-size:14px}}th,td{{padding:13px 12px;border-bottom:1px solid #dce8e6;text-align:left;vertical-align:top;line-height:1.55}}th{{background:#e8f3f1;color:#164f50;position:sticky;top:0}}.tablebox{{overflow:auto;max-height:560px;border:1px solid #dce8e6;border-radius:14px}}.rhythm{{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}}.rhythm div{{padding:20px;background:#f5f9f8;border-radius:14px}}.rhythm b{{color:#176d69}}.compare{{display:grid;grid-template-columns:1fr 1fr;gap:16px}}.compare div{{padding:24px;border-radius:16px}}.v1{{background:#f0f2f3}}.v2{{background:#e8f3f1;border:2px solid #82b6ad}}footer{{text-align:center;color:#738788;padding:28px}}@media(max-width:900px){{.grid4,.metrics,.rhythm{{grid-template-columns:1fr 1fr}}.compare{{grid-template-columns:1fr}}h1{{font-size:32px}}header{{padding:34px}}}}@media(max-width:560px){{.grid4,.metrics,.rhythm{{grid-template-columns:1fr}}}}
</style></head><body><div class="wrap"><header><div class="eyebrow">DELIVERY EXECUTION SYSTEM · V2.0</div><h1>目标落地，不先追求体系完整</h1><p>V1保留为完整能力蓝图；V2只解决当前最急的问题：每个项目要交什么、谁负责、哪天完成，每个交付人接下来做什么。</p><a href="{esc(online_url())}">打开企微V2在线表格</a></header>
<section><h2>V1 与 V2 的关系</h2><div class="compare"><div class="v1"><h3>V1 · 完整蓝图</h3><p>10张表、178个字段，覆盖进度、质量、范围、成本、容量、验收、回款和产品复用。保留，不删除，作为未来能力库。</p></div><div class="v2"><h3>V2 · 当前落地版</h3><p>4张表、44个字段，只管目标、行动、问题和复盘。连续运行4周后，才决定从V1增加哪些模块。</p></div></div></section>
<section><h2>只管五件事</h2><div class="callout">要交什么结果　·　谁对结果负责　·　哪天必须完成　·　做到什么才算完成　·　完成证据在哪里</div></section>
<section><h2>四张表</h2><div class="grid4">{sheet_cards}</div></section>
<section><h2>领导只看五个数字</h2><p class="lead">数字不是为了汇报好看，而是为了决定优先级、资源和是否调整承诺。</p><div class="metrics">{metric_cards}</div></section>
<section><h2>谁填、什么时候填</h2><div class="tablebox"><table><thead><tr><th>角色</th><th>只负责什么</th><th>什么时候动作</th></tr></thead><tbody>{role_rows}</tbody></table></div></section>
<section><h2>固定节奏</h2><div class="rhythm"><div><b>周一30分钟</b><p>每个项目只定一个本周目标；每人最多3项行动。</p></div><div><b>周中按事件更新</b><p>完成、受阻或日期变化才更新，不写长篇日报。</p></div><div><b>周五30分钟</b><p>项目负责人复盘，PMO检查证据并锁定数据。</p></div><div><b>领导周会20分钟</b><p>只讨论红黄、逾期和待决策，不逐项目念进度。</p></div></div></section>
<section><h2>44个字段责任矩阵</h2><p>每个字段都明确填写人、时间和质量门禁；表内不预填虚假项目数据。</p><div class="tablebox"><table><thead><tr><th>表</th><th>字段</th><th>谁填</th><th>什么时候填</th><th>质量规则</th></tr></thead><tbody>{field_rows}</tbody></table></div></section>
<section><h2>四周落地门禁</h2><div class="callout">第1周录入4个项目 → 第2周让每人只看3项行动 → 第3周启用问题升级和20分钟周会 → 第4周决定V1哪些模块值得增加。没有连续4周数据，不扩大字段。</div></section>
<footer>交付实施部目标落地管理体系 V2.0 · 当前阶段试运行版 · 2026-08-07</footer></div></body></html>"""
    (ROOT / "delivery-department-goal-execution-v2.html").write_text(doc, encoding="utf-8")


def shade(cell, fill):
    tc_pr = cell._tc.get_or_add_tcPr()
    shd = tc_pr.find(qn("w:shd"))
    if shd is None:
        shd = OxmlElement("w:shd")
        tc_pr.append(shd)
    shd.set(qn("w:fill"), fill)


def margins(cell, top=90, start=100, bottom=90, end=100):
    tc_pr = cell._tc.get_or_add_tcPr()
    tc_mar = tc_pr.first_child_found_in("w:tcMar")
    if tc_mar is None:
        tc_mar = OxmlElement("w:tcMar")
        tc_pr.append(tc_mar)
    for side, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
        node = tc_mar.find(qn(f"w:{side}"))
        if node is None:
            node = OxmlElement(f"w:{side}")
            tc_mar.append(node)
        node.set(qn("w:w"), str(value))
        node.set(qn("w:type"), "dxa")


def set_font(run, size=10.5, bold=False, color="243B3D"):
    run.font.name = "Microsoft YaHei"
    rfonts = run._element.get_or_add_rPr().get_or_add_rFonts()
    rfonts.set(qn("w:ascii"), "Microsoft YaHei")
    rfonts.set(qn("w:hAnsi"), "Microsoft YaHei")
    rfonts.set(qn("w:eastAsia"), "Microsoft YaHei")
    run.font.size = Pt(size)
    run.bold = bold
    run.font.color.rgb = RGBColor.from_string(color)


def repeat_header(row):
    node = OxmlElement("w:tblHeader")
    node.set(qn("w:val"), "true")
    row._tr.get_or_add_trPr().append(node)


def add_table(doc, headers, rows, widths, font_size=8.5):
    table = doc.add_table(rows=1, cols=len(headers))
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    repeat_header(table.rows[0])
    for idx, name in enumerate(headers):
        cell = table.rows[0].cells[idx]
        shade(cell, "DCEBE8")
        margins(cell)
        cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
        p = cell.paragraphs[0]
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        set_font(p.add_run(name), 9, True, "164F50")
    for values in rows:
        row = table.add_row()
        for idx, value in enumerate(values):
            cell = row.cells[idx]
            margins(cell)
            cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
            p = cell.paragraphs[0]
            p.paragraph_format.space_after = Pt(0)
            p.paragraph_format.line_spacing = 1.05
            set_font(p.add_run(str(value)), font_size)
    for row in table.rows:
        for idx, width in enumerate(widths):
            row.cells[idx].width = Cm(width)
    doc.add_paragraph().paragraph_format.space_after = Pt(2)


def heading(doc, text, level=1):
    p = doc.add_heading(level=level)
    p.paragraph_format.space_before = Pt(10 if level == 1 else 6)
    p.paragraph_format.space_after = Pt(5)
    set_font(p.add_run(text), 16 if level == 1 else 12, True, "176D69" if level == 1 else "234B4B")


def para(doc, text, bold_lead=None):
    p = doc.add_paragraph()
    p.paragraph_format.space_after = Pt(5)
    p.paragraph_format.line_spacing = 1.25
    if bold_lead and text.startswith(bold_lead):
        set_font(p.add_run(bold_lead), 10.5, True, "176D69")
        set_font(p.add_run(text[len(bold_lead):]), 10.5)
    else:
        set_font(p.add_run(text), 10.5)


def bullet(doc, text):
    p = doc.add_paragraph(style="List Paragraph")
    p.paragraph_format.left_indent = Cm(0.5)
    p.paragraph_format.first_line_indent = Cm(-0.3)
    p.paragraph_format.space_after = Pt(3)
    set_font(p.add_run("• "), 10.2, True, "D57938")
    set_font(p.add_run(text), 10.2)


def duplicate_retained_header_for_even_pages(docx_path: Path):
    """Create a real even-page header part for LibreOffice/Word compatibility."""
    with ZipFile(docx_path, "r") as source:
        parts = {name: source.read(name) for name in source.namelist()}

    header_source = "word/header1.xml"
    header_target = "word/header2.xml"
    if header_source not in parts:
        raise RuntimeError("Retained template header1.xml is missing")
    parts[header_target] = parts[header_source]
    rel_source = "word/_rels/header1.xml.rels"
    rel_target = "word/_rels/header2.xml.rels"
    if rel_source in parts:
        parts[rel_target] = parts[rel_source]

    rel_ns = "http://schemas.openxmlformats.org/package/2006/relationships"
    office_rel = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
    word_ns = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
    content_ns = "http://schemas.openxmlformats.org/package/2006/content-types"
    relationship_id = "rIdEvenHeader"

    rel_root = etree.fromstring(parts["word/_rels/document.xml.rels"])
    for node in rel_root.findall(f"{{{rel_ns}}}Relationship"):
        if node.get("Id") == relationship_id:
            rel_root.remove(node)
    etree.SubElement(
        rel_root,
        f"{{{rel_ns}}}Relationship",
        Id=relationship_id,
        Type=f"{office_rel}/header",
        Target="header2.xml",
    )
    parts["word/_rels/document.xml.rels"] = etree.tostring(rel_root, xml_declaration=True, encoding="UTF-8", standalone=True)

    document_root = etree.fromstring(parts["word/document.xml"])
    sect_prs = document_root.findall(f".//{{{word_ns}}}sectPr")
    if not sect_prs:
        raise RuntimeError("Document section properties are missing")
    default_relationship_id = None
    for sect_pr in sect_prs:
        for node in sect_pr.findall(f"{{{word_ns}}}headerReference"):
            if node.get(f"{{{word_ns}}}type") == "default":
                default_relationship_id = node.get(f"{{{office_rel}}}id")
                break
        if default_relationship_id:
            break
    if not default_relationship_id:
        raise RuntimeError("Retained default header relationship is missing")
    for sect_pr in sect_prs:
        default_ref = next(
            (node for node in sect_pr.findall(f"{{{word_ns}}}headerReference") if node.get(f"{{{word_ns}}}type") == "default"),
            None,
        )
        if default_ref is None:
            default_ref = etree.Element(f"{{{word_ns}}}headerReference")
            default_ref.set(f"{{{word_ns}}}type", "default")
            default_ref.set(f"{{{office_rel}}}id", default_relationship_id)
            sect_pr.insert(0, default_ref)
        for node in list(sect_pr.findall(f"{{{word_ns}}}headerReference")):
            if node.get(f"{{{word_ns}}}type") == "even":
                sect_pr.remove(node)
        even_ref = etree.Element(f"{{{word_ns}}}headerReference")
        even_ref.set(f"{{{word_ns}}}type", "even")
        even_ref.set(f"{{{office_rel}}}id", relationship_id)
        sect_pr.insert(sect_pr.index(default_ref) + 1, even_ref)
    parts["word/document.xml"] = etree.tostring(document_root, xml_declaration=True, encoding="UTF-8", standalone=True)

    settings_root = etree.fromstring(parts["word/settings.xml"])
    if settings_root.find(f"{{{word_ns}}}evenAndOddHeaders") is None:
        settings_root.insert(0, etree.Element(f"{{{word_ns}}}evenAndOddHeaders"))
    parts["word/settings.xml"] = etree.tostring(settings_root, xml_declaration=True, encoding="UTF-8", standalone=True)

    content_root = etree.fromstring(parts["[Content_Types].xml"])
    target_part = "/word/header2.xml"
    for node in content_root.findall(f"{{{content_ns}}}Override"):
        if node.get("PartName") == target_part:
            content_root.remove(node)
    etree.SubElement(
        content_root,
        f"{{{content_ns}}}Override",
        PartName=target_part,
        ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",
    )
    parts["[Content_Types].xml"] = etree.tostring(content_root, xml_declaration=True, encoding="UTF-8", standalone=True)

    handle, temp_name = tempfile.mkstemp(prefix="delivery-v2-even-header-", suffix=".docx", dir=str(docx_path.parent))
    os.close(handle)
    try:
        with ZipFile(temp_name, "w", ZIP_DEFLATED) as target:
            for name, payload in parts.items():
                target.writestr(name, payload)
        os.replace(temp_name, docx_path)
    finally:
        if os.path.exists(temp_name):
            os.unlink(temp_name)


def build_docx():
    if not STARTER.exists():
        STARTER.parent.mkdir(parents=True, exist_ok=True)
        subprocess.run([sys.executable, str(PREPARE), str(TEMPLATE), str(STARTER)], check=True)
    doc = Document(STARTER)
    for name in ("Normal", "List Paragraph"):
        style = doc.styles[name]
        style.font.name = "Microsoft YaHei"
        style._element.rPr.rFonts.set(qn("w:eastAsia"), "Microsoft YaHei")
        style.font.size = Pt(10.5)
    section = doc.sections[0]
    section.top_margin = Cm(2.54)
    section.bottom_margin = Cm(2.54)
    section.left_margin = Cm(3.175)
    section.right_margin = Cm(3.175)
    # LibreOffice treats the template's default header as an odd-page header.
    # Reference the same retained header explicitly for even pages so every
    # rendered page keeps the original logo, brand text and rule.
    doc.settings.odd_and_even_pages_header_footer = True
    sect_pr = section._sectPr
    default_header = sect_pr.find(qn("w:headerReference"))
    even_header = next(
        (node for node in sect_pr.findall(qn("w:headerReference")) if node.get(qn("w:type")) == "even"),
        None,
    )
    if default_header is not None and even_header is None:
        even_header = OxmlElement("w:headerReference")
        even_header.set(qn("w:type"), "even")
        even_header.set(qn("r:id"), default_header.get(qn("r:id")))
        sect_pr.insert(1, even_header)

    def start_new_page_section():
        new_section = doc.add_section(WD_SECTION.NEW_PAGE)
        new_section.top_margin = Cm(2.54)
        new_section.bottom_margin = Cm(2.54)
        new_section.left_margin = Cm(3.175)
        new_section.right_margin = Cm(3.175)
        return new_section

    title = doc.paragraphs[0]
    title.paragraph_format.space_before = Pt(28)
    title.paragraph_format.space_after = Pt(8)
    set_font(title.add_run("交付实施部目标落地管理体系"), 24, True, "164F50")
    subtitle = doc.add_paragraph()
    subtitle.paragraph_format.space_after = Pt(18)
    set_font(subtitle.add_run("V2.0 当前阶段试运行版 · 四张表 · 五个数字 · 三个更新节点"), 12, False, "637A7B")
    meta = doc.add_paragraph()
    set_font(meta.add_run("2026-08-07  |  V1保留为完整蓝图  |  V2优先保证目标落地"), 9, False, "6B7E7F")

    callout = doc.add_table(rows=1, cols=1)
    callout.alignment = WD_TABLE_ALIGNMENT.CENTER
    cell = callout.cell(0, 0)
    shade(cell, "E8F3F1")
    margins(cell, 180, 180, 180, 180)
    p = cell.paragraphs[0]
    set_font(p.add_run("V2只要求五件事："), 11, True, "176D69")
    set_font(p.add_run("结果、责任人、截止时间、完成标准、结果证据。"), 11)
    doc.add_paragraph()

    heading(doc, "一、为什么要有V2")
    para(doc, "V1是成熟交付部门的完整管理蓝图，但当前团队如果同时维护进度、质量、范围、成本、容量、验收、回款和复用，会先产生大量填表负担。V2只解决当前最急的问题：交付人员不知道接下来做什么、什么时候结束，管理者不知道承诺是否还能兑现。")
    add_table(doc, ["版本", "定位", "当前动作"], [
        ["V1 完整蓝图", "10张表、178个字段，覆盖交付经营全链路", "完整保留，不要求当前一次性启用"],
        ["V2 落地版", "4张表、44个字段，只管目标、行动、问题和复盘", "用4个项目连续试运行4周"],
    ], [2.7, 6.3, 5.0], 9)
    start_new_page_section()
    para(doc, "V2不是否定V1，而是V1的第一阶段实施切片。只有V2稳定运行后，才从V1逐项增加真实需要的模块。")

    heading(doc, "二、V2的结果")
    for text in [
        "每个项目都有唯一负责人、可验收目标和明确完成日期。",
        "每名交付人员同时只承担最多3项主动任务，能在1分钟内说明下一步和截止时间。",
        "所有完成结果有证据，所有阻塞在发生当天被看见。",
        "领导周会只讨论红黄项目、逾期任务和待决策问题。",
    ]:
        bullet(doc, text)

    heading(doc, "三、四个角色")
    add_table(doc, ["角色", "只负责什么", "什么时候动作"], ROLES, [2.4, 7.1, 4.7], 8.6)

    heading(doc, "四、四张表")
    add_table(doc, ["表", "用途", "字段数"], [[s["title"], s["purpose"], len(s["fields"])] for s in SHEETS], [3.2, 9.5, 1.4], 8.8)
    para(doc, "在线工作簿：" + online_url())

    heading(doc, "五、字段由谁填、什么时候填")
    add_table(doc, ["表", "字段", "谁填", "什么时候填", "质量门禁"],
              [[r[0], r[1], r[3], r[4], r[6]] for r in matrix_rows()], [2.6, 2.3, 2.5, 3.3, 3.7], 7.6)

    start_new_page_section()
    heading(doc, "六、六条执行规则")
    for idx, text in enumerate(RULES, 1):
        para(doc, f"规则{idx}：{text}", bold_lead=f"规则{idx}：")

    heading(doc, "七、固定节奏")
    add_table(doc, ["节点", "要做什么", "完成标准"], [
        ["每周一30分钟", "项目负责人确认本周唯一目标；每人最多3项行动", "所有行动有责任人、截止时间和完成标准"],
        ["周中按事件更新", "完成、受阻或日期变化才更新；受阻当天升级", "状态与事实一致，不写长日报"],
        ["每周五30分钟", "项目负责人复盘，PMO检查证据并锁定数据", "完成有证据，偏差有原因和下一步"],
        ["领导周会20分钟", "只看红黄、逾期和待决策问题", "每项问题有建议方案和决策期限"],
    ], [3.0, 6.2, 5.1], 8.6)

    heading(doc, "八、领导只看五个数字")
    add_table(doc, ["指标", "回答的问题", "来源", "责任人"], [[r[0], r[1], r[2], r[4]] for r in METRICS], [3.2, 5.0, 3.2, 2.6], 8.6)
    para(doc, "领导大屏先不展示成本、回款、定制化和复用率。等V2数据稳定后，再根据实际管理问题从V1逐项增加。")

    heading(doc, "九、四周落地计划")
    add_table(doc, ["周次", "只做什么", "通过标准"], [
        ["第1周", "录入城市副中心、国康、滨州健康职业学院、玉溪体校4个项目", "4个项目有负责人、本月目标和计划完成日"],
        ["第2周", "每个项目设本周唯一目标；每人最多3项行动", "所有人能从表中回答下一步和截止时间"],
        ["第3周", "启用问题升级和20分钟领导周会", "红灯都有方案、决策人和最晚决策时间"],
        ["第4周", "复盘执行率与维护成本，决定是否启用V1模块", "形成继续、调整、停止和新增模块清单"],
    ], [1.8, 7.4, 5.1], 8.6)

    heading(doc, "十、试运行验收")
    for text in [
        "4个项目100%有负责人、目标结果和计划完成日。",
        "每名交付人员能在1分钟内说清下一项任务和截止时间。",
        "已完成任务100%有可回读证据。",
        "受阻和逾期事项在发生当天进入清单，不依赖领导追问。",
        "周会只讨论偏差和决策，控制在20分钟左右。",
    ]:
        bullet(doc, text)
    para(doc, "只有连续4周达到基础门禁，才按真实痛点从V1启用下一模块。优先顺序建议：验收节点 → 人天成本 → 产品复用；不一次性全部启用。")
    doc.save(FINAL_DOCX)
    duplicate_retained_header_for_even_pages(FINAL_DOCX)


def build_summary():
    text = f"""# 交付实施部目标落地管理体系 V2.0 产出说明

## 版本关系

- V1保留为成熟交付实施部的完整管理蓝图，不修改、不删除。
- V2是当前阶段的最小落地切片，只管目标、行动、问题和周复盘。

## 产出

- 正式Word：`交付实施部目标落地管理体系_V2.0.docx`
- Markdown真源：`delivery-implementation-department-operating-system-v2.md`
- 领导审阅HTML：`delivery-department-goal-execution-v2.html`
- 字段责任矩阵：`v2-field-responsibility-matrix.csv`
- 领导指标：`v2-leadership-metrics.csv`
- 企微结构：`wecom-v2-workbook-schema.json`
- 企微在线工作簿：{online_url()}

## 验收边界

- 4张业务表、44个字段，在线表不预填虚假项目数据。
- 试点对象为城市副中心、国康、滨州健康职业学院、玉溪体校；本次只搭结构，不编造项目状态。
- 连续4周验证后，再决定从V1启用哪些模块。
"""
    (ROOT / "summary.md").write_text(text, encoding="utf-8")


def main():
    write_csv("v2-field-responsibility-matrix.csv", ["表", "字段", "定义", "填写责任人", "填写时间", "必填", "质量规则", "领导大屏"], matrix_rows())
    write_csv("v2-leadership-metrics.csv", ["指标", "领导问题", "来源", "口径", "责任人"], METRICS)
    write_csv("v2-workbook-sheet-map.csv", ["表", "用途", "字段数"], [[s["title"], s["purpose"], len(s["fields"])] for s in SHEETS])
    build_schema()
    build_markdown()
    build_html()
    build_docx()
    build_summary()


if __name__ == "__main__":
    main()
