#!/usr/bin/env python3
from __future__ import annotations

import argparse
from pathlib import Path

from docx import Document
from docx.enum.section import WD_SECTION
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_ROW_HEIGHT_RULE, WD_TABLE_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK, WD_LINE_SPACING
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Cm, Inches, Pt, RGBColor


NAVY = "17395C"
BLUE = "2F5D80"
ORANGE = "EE7623"
TEAL = "2B7A78"
LIGHT_BLUE = "EAF1F7"
LIGHT_ORANGE = "FFF1E7"
LIGHT_TEAL = "E8F4F2"
LIGHT_GRAY = "F4F6F8"
MID_GRAY = "6B7280"
DARK = "263238"
WHITE = "FFFFFF"
RISK = "A33A2B"

BODY_FONT = "Arial Unicode MS"
EAST_ASIA_FONT = "Arial Unicode MS"


def set_run_font(run, name=BODY_FONT, size=10.5, bold=False, color=DARK, italic=False):
    run.font.name = name
    run.font.size = Pt(size)
    run.font.bold = bold
    run.font.italic = italic
    run.font.color.rgb = RGBColor.from_string(color)
    rpr = run._element.get_or_add_rPr()
    rfonts = rpr.rFonts
    if rfonts is None:
        rfonts = OxmlElement("w:rFonts")
        rpr.insert(0, rfonts)
    rfonts.set(qn("w:ascii"), name)
    rfonts.set(qn("w:hAnsi"), name)
    rfonts.set(qn("w:eastAsia"), EAST_ASIA_FONT)
    return run


def set_cell_shading(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 set_cell_margins(cell, top=90, start=110, bottom=90, end=110):
    tc = cell._tc
    tc_pr = 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 m, v in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
        node = tc_mar.find(qn(f"w:{m}"))
        if node is None:
            node = OxmlElement(f"w:{m}")
            tc_mar.append(node)
        node.set(qn("w:w"), str(v))
        node.set(qn("w:type"), "dxa")


def set_table_borders(table, color="D9E1E8", size="6"):
    tbl_pr = table._tbl.tblPr
    borders = tbl_pr.find(qn("w:tblBorders"))
    if borders is None:
        borders = OxmlElement("w:tblBorders")
        tbl_pr.append(borders)
    for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
        tag = borders.find(qn(f"w:{edge}"))
        if tag is None:
            tag = OxmlElement(f"w:{edge}")
            borders.append(tag)
        tag.set(qn("w:val"), "single")
        tag.set(qn("w:sz"), size)
        tag.set(qn("w:space"), "0")
        tag.set(qn("w:color"), color)


def set_table_width(table, widths_cm):
    table.autofit = False
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    dxa_widths = [int(width / 2.54 * 1440) for width in widths_cm]
    tbl = table._tbl
    tbl_pr = tbl.tblPr
    layout = tbl_pr.find(qn("w:tblLayout"))
    if layout is None:
        layout = OxmlElement("w:tblLayout")
        tbl_pr.append(layout)
    layout.set(qn("w:type"), "fixed")
    tbl_ind = tbl_pr.find(qn("w:tblInd"))
    if tbl_ind is None:
        tbl_ind = OxmlElement("w:tblInd")
        tbl_pr.append(tbl_ind)
    tbl_ind.set(qn("w:w"), "0")
    tbl_ind.set(qn("w:type"), "dxa")
    grid = tbl.tblGrid
    for child in list(grid):
        grid.remove(child)
    for width in dxa_widths:
        grid_col = OxmlElement("w:gridCol")
        grid_col.set(qn("w:w"), str(width))
        grid.append(grid_col)
    for row in table.rows:
        for i, width in enumerate(widths_cm):
            row.cells[i].width = Cm(width)
            tc_pr = row.cells[i]._tc.get_or_add_tcPr()
            tc_w = tc_pr.find(qn("w:tcW"))
            if tc_w is None:
                tc_w = OxmlElement("w:tcW")
                tc_pr.append(tc_w)
            tc_w.set(qn("w:w"), str(dxa_widths[i]))
            tc_w.set(qn("w:type"), "dxa")
    tbl_w = tbl_pr.find(qn("w:tblW"))
    if tbl_w is None:
        tbl_w = OxmlElement("w:tblW")
        tbl_pr.append(tbl_w)
    tbl_w.set(qn("w:w"), str(sum(dxa_widths)))
    tbl_w.set(qn("w:type"), "dxa")


def set_repeat_table_header(row):
    tr_pr = row._tr.get_or_add_trPr()
    tbl_header = OxmlElement("w:tblHeader")
    tbl_header.set(qn("w:val"), "true")
    tr_pr.append(tbl_header)


def set_keep(paragraph, keep_with_next=False, keep_lines=True):
    ppr = paragraph._p.get_or_add_pPr()
    if keep_with_next:
        ppr.append(OxmlElement("w:keepNext"))
    if keep_lines:
        ppr.append(OxmlElement("w:keepLines"))


def ensure_bullet_numbering(doc):
    cached = getattr(doc, "_weekly_report_bullet_num_id", None)
    if cached is not None:
        return cached
    numbering = doc.part.numbering_part.element
    abstract_ids = [int(node.get(qn("w:abstractNumId"))) for node in numbering.findall(qn("w:abstractNum"))]
    num_ids = [int(node.get(qn("w:numId"))) for node in numbering.findall(qn("w:num"))]
    abstract_id = max(abstract_ids, default=0) + 1
    num_id = max(num_ids, default=0) + 1

    abstract = OxmlElement("w:abstractNum")
    abstract.set(qn("w:abstractNumId"), str(abstract_id))
    multi = OxmlElement("w:multiLevelType")
    multi.set(qn("w:val"), "singleLevel")
    abstract.append(multi)
    lvl = OxmlElement("w:lvl")
    lvl.set(qn("w:ilvl"), "0")
    start = OxmlElement("w:start")
    start.set(qn("w:val"), "1")
    lvl.append(start)
    num_fmt = OxmlElement("w:numFmt")
    num_fmt.set(qn("w:val"), "bullet")
    lvl.append(num_fmt)
    lvl_text = OxmlElement("w:lvlText")
    lvl_text.set(qn("w:val"), "•")
    lvl.append(lvl_text)
    lvl_jc = OxmlElement("w:lvlJc")
    lvl_jc.set(qn("w:val"), "left")
    lvl.append(lvl_jc)
    ppr = OxmlElement("w:pPr")
    tabs = OxmlElement("w:tabs")
    tab = OxmlElement("w:tab")
    tab.set(qn("w:val"), "num")
    tab.set(qn("w:pos"), "540")
    tabs.append(tab)
    ppr.append(tabs)
    ind = OxmlElement("w:ind")
    ind.set(qn("w:left"), "540")
    ind.set(qn("w:hanging"), "270")
    ppr.append(ind)
    lvl.append(ppr)
    rpr = OxmlElement("w:rPr")
    rfonts = OxmlElement("w:rFonts")
    rfonts.set(qn("w:ascii"), BODY_FONT)
    rfonts.set(qn("w:hAnsi"), BODY_FONT)
    rfonts.set(qn("w:eastAsia"), EAST_ASIA_FONT)
    rpr.append(rfonts)
    lvl.append(rpr)
    abstract.append(lvl)
    numbering.append(abstract)

    num = OxmlElement("w:num")
    num.set(qn("w:numId"), str(num_id))
    abstract_ref = OxmlElement("w:abstractNumId")
    abstract_ref.set(qn("w:val"), str(abstract_id))
    num.append(abstract_ref)
    numbering.append(num)
    doc._weekly_report_bullet_num_id = num_id
    return num_id


def configure_styles(doc):
    normal = doc.styles["Normal"]
    normal.font.name = BODY_FONT
    normal.font.size = Pt(10.5)
    normal._element.rPr.rFonts.set(qn("w:eastAsia"), EAST_ASIA_FONT)
    normal.paragraph_format.space_after = Pt(5)
    normal.paragraph_format.line_spacing = 1.28
    normal.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY

    for style_name, size, color, before, after in (
        ("Heading 1", 15.5, NAVY, 12, 6),
        ("Heading 2", 12.5, BLUE, 8, 4),
        ("Heading 3", 11.0, TEAL, 6, 3),
    ):
        style = doc.styles[style_name]
        style.font.name = BODY_FONT
        style.font.size = Pt(size)
        style.font.bold = True
        style.font.color.rgb = RGBColor.from_string(color)
        style._element.rPr.rFonts.set(qn("w:eastAsia"), EAST_ASIA_FONT)
        style.paragraph_format.space_before = Pt(before)
        style.paragraph_format.space_after = Pt(after)
        style.paragraph_format.keep_with_next = True
        style.paragraph_format.keep_together = True


def add_rich_paragraph(doc, parts, *, align=None, after=5, before=0, line=1.28, indent=True):
    p = doc.add_paragraph()
    if align is not None:
        p.alignment = align
    p.paragraph_format.space_before = Pt(before)
    p.paragraph_format.space_after = Pt(after)
    p.paragraph_format.line_spacing = line
    if indent:
        p.paragraph_format.first_line_indent = Cm(0.74)
    for text, bold, color in parts:
        set_run_font(p.add_run(text), bold=bold, color=color)
    return p


def add_bullet(doc, text, level=0, color=DARK):
    p = doc.add_paragraph()
    num_id = ensure_bullet_numbering(doc)
    ppr = p._p.get_or_add_pPr()
    num_pr = OxmlElement("w:numPr")
    ilvl = OxmlElement("w:ilvl")
    ilvl.set(qn("w:val"), "0")
    num_id_node = OxmlElement("w:numId")
    num_id_node.set(qn("w:val"), str(num_id))
    num_pr.append(ilvl)
    num_pr.append(num_id_node)
    ppr.append(num_pr)
    p.paragraph_format.space_after = Pt(3.5)
    p.paragraph_format.line_spacing = 1.2
    set_run_font(p.add_run(text), color=color)
    return p


def add_number(doc, label, text):
    p = doc.add_paragraph()
    p.paragraph_format.left_indent = Cm(0.7)
    p.paragraph_format.first_line_indent = Cm(-0.7)
    p.paragraph_format.space_after = Pt(4)
    p.paragraph_format.line_spacing = 1.22
    set_run_font(p.add_run(f"{label}  "), bold=True, color=ORANGE)
    set_run_font(p.add_run(text))
    return p


def add_callout(doc, title, body, fill=LIGHT_BLUE, title_color=NAVY):
    table = doc.add_table(rows=1, cols=1)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    set_table_width(table, [14.45])
    set_table_borders(table, color=fill, size="2")
    cell = table.cell(0, 0)
    set_cell_shading(cell, fill)
    set_cell_margins(cell, top=150, start=190, bottom=150, end=190)
    p = cell.paragraphs[0]
    p.paragraph_format.space_after = Pt(4)
    set_run_font(p.add_run(title), size=11.5, bold=True, color=title_color)
    p2 = cell.add_paragraph()
    p2.paragraph_format.space_after = Pt(0)
    p2.paragraph_format.line_spacing = 1.25
    set_run_font(p2.add_run(body), size=10.2)
    doc.add_paragraph().paragraph_format.space_after = Pt(1)
    return table


def add_data_table(doc, headers, rows, widths_cm, header_fill=NAVY, font_size=8.7):
    table = doc.add_table(rows=1, cols=len(headers))
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    set_table_width(table, widths_cm)
    set_table_borders(table)
    header = table.rows[0]
    set_repeat_table_header(header)
    for i, text in enumerate(headers):
        cell = header.cells[i]
        set_cell_shading(cell, header_fill)
        set_cell_margins(cell, top=100, start=90, bottom=100, end=90)
        cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
        p = cell.paragraphs[0]
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        p.paragraph_format.space_after = Pt(0)
        set_run_font(p.add_run(text), size=font_size, bold=True, color=WHITE)
    for r_idx, row_values in enumerate(rows):
        row = table.add_row()
        row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST
        for i, value in enumerate(row_values):
            cell = row.cells[i]
            set_cell_margins(cell, top=90, start=90, bottom=90, end=90)
            cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
            if r_idx % 2 == 1:
                set_cell_shading(cell, "FAFBFC")
            p = cell.paragraphs[0]
            p.alignment = WD_ALIGN_PARAGRAPH.CENTER if i == 0 else WD_ALIGN_PARAGRAPH.LEFT
            p.paragraph_format.space_after = Pt(0)
            p.paragraph_format.line_spacing = 1.1
            set_run_font(p.add_run(str(value)), size=font_size, bold=(i == 0), color=(NAVY if i == 0 else DARK))
    doc.add_paragraph().paragraph_format.space_after = Pt(1)
    return table


def add_priority_card(doc, priority, title, body, owner):
    fill = LIGHT_ORANGE if priority == "P0" else LIGHT_BLUE
    accent = ORANGE if priority == "P0" else BLUE
    table = doc.add_table(rows=1, cols=2)
    set_table_width(table, [1.15, 13.30])
    set_table_borders(table, color="E1E7ED", size="4")
    left, right = table.rows[0].cells
    set_cell_shading(left, accent)
    set_cell_shading(right, fill)
    for c in (left, right):
        set_cell_margins(c, top=95, start=115, bottom=95, end=115)
        c.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    p = left.paragraphs[0]
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    p.paragraph_format.space_after = Pt(0)
    set_run_font(p.add_run(priority), size=10, bold=True, color=WHITE)
    p = right.paragraphs[0]
    p.paragraph_format.space_after = Pt(2)
    set_run_font(p.add_run(title + "  "), size=10.2, bold=True, color=NAVY)
    set_run_font(p.add_run(body), size=9.3)
    p2 = right.add_paragraph()
    p2.paragraph_format.space_after = Pt(0)
    set_run_font(p2.add_run("责任牵头："), size=8.8, bold=True, color=MID_GRAY)
    set_run_font(p2.add_run(owner), size=8.8, color=MID_GRAY)
    spacer = doc.add_paragraph()
    spacer.paragraph_format.space_after = Pt(1)
    return table


def add_page_heading(doc, text, level=1):
    p = doc.add_heading(text, level=level)
    p.paragraph_format.page_break_before = True
    return p


def build(template, output):
    doc = Document(template)
    configure_styles(doc)
    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)
    section.header_distance = Cm(1.2)
    section.footer_distance = Cm(1.0)

    props = doc.core_properties
    props.title = "2026年8月10日数科与团餐业务周报"
    props.subject = "华亭11层数科周例会：两阶段交付、产品标准化与8月经营推进"
    props.author = "康比特数字技术中心"
    props.keywords = "数科,团餐,周报,两阶段交付,产品标准化,一键安装包"

    title = doc.add_paragraph()
    title.alignment = WD_ALIGN_PARAGRAPH.CENTER
    title.paragraph_format.space_before = Pt(20)
    title.paragraph_format.space_after = Pt(7)
    set_run_font(title.add_run("2026 年 8 月 10 日\n数科与团餐业务周报"), size=22, bold=True, color=NAVY)
    set_keep(title, keep_with_next=True)

    subtitle = doc.add_paragraph()
    subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
    subtitle.paragraph_format.space_after = Pt(16)
    set_run_font(subtitle.add_run("华亭 11 层数科周例会｜两阶段交付 · 产品标准化 · 经营闭环"), size=10.5, color=MID_GRAY)

    add_callout(
        doc,
        "本周作战指令",
        "所有项目统一执行“15 天标准产品交付 + 1 周驻场跟餐 + 合同范围内远程定制交付”。8 月产研只抓两件事：历史项目产品化、一键安装包。销售在合同前锁定部署条件和定制边界，交付拿到即可执行的项目输入。",
        fill=LIGHT_ORANGE,
        title_color=ORANGE,
    )

    doc.add_heading("一、管理层结论", level=1)
    conclusions = [
        ("01", "交付机制必须切换", "从“项目与需求混在一起推进”切换为“先标准、后定制”；标准产品未交付清楚，不进入定制阶段。"),
        ("02", "8 月产研必须收敛", "历史 28 个项目要转成标准产品与案例资产；一键安装包要让交付摆脱对研发现场部署的依赖。"),
        ("03", "增长必须回到现金与复制", "8 月计划 430 万元、当前预估 486 万元；增订验证服务价值，但 1,034 万元应收和交付压力要求签约、验收、回款同链管理。"),
    ]
    for no, heading, text in conclusions:
        table = doc.add_table(rows=1, cols=2)
        set_table_width(table, [1.20, 13.25])
        set_table_borders(table, color="E5EAF0", size="4")
        c0, c1 = table.rows[0].cells
        set_cell_shading(c0, NAVY)
        set_cell_shading(c1, LIGHT_BLUE)
        for c in (c0, c1):
            set_cell_margins(c, top=110, start=130, bottom=110, end=130)
            c.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
        p0 = c0.paragraphs[0]
        p0.alignment = WD_ALIGN_PARAGRAPH.CENTER
        p0.paragraph_format.space_after = Pt(0)
        set_run_font(p0.add_run(no), size=13, bold=True, color=WHITE)
        p1 = c1.paragraphs[0]
        p1.paragraph_format.space_after = Pt(2)
        set_run_font(p1.add_run(heading), size=10.8, bold=True, color=NAVY)
        p2 = c1.add_paragraph()
        p2.paragraph_format.space_after = Pt(0)
        p2.paragraph_format.line_spacing = 1.2
        set_run_font(p2.add_run(text), size=9.5)
        doc.add_paragraph().paragraph_format.space_after = Pt(0)

    add_page_heading(doc, "二、统一执行“两阶段交付”", level=1)
    add_callout(doc, "启动门禁", "销售、售前、产品和交付在合同前锁定服务器、网络、小程序、支付、第三方接口等部署条件，区分标准产品与定制内容，并写入合同附件或项目交接材料。计时从部署条件齐全、项目输入完整开始。", fill=LIGHT_TEAL, title_color=TEAL)

    stages = [
        ["阶段 0", "项目启动与需求确认", "甲方指定一名项目经理作为唯一窗口；需求统一进入电子台账；标准/定制边界、部署条件、验收人全部确认。"],
        ["阶段 1", "15 天标准产品交付", "使用标准材料和一键安装包完成安装、配置、测试和客户确认；随后安排 1 周驻场跟餐，验证高峰期流程、设备稳定性和异常处理。"],
        ["阶段 2", "合同范围内定制交付", "标准产品验收后，再远程交付合同范围内的定制功能并培训；每项定制明确负责人、时间和验收标准。"],
        ["全程", "变更控制", "新增需求形成变更记录，写清是否在合同范围内，以及对工期、费用和验收的影响；未登记的口头需求不直接安排开发。"],
    ]
    add_data_table(doc, ["阶段", "交付目标", "执行要求"], stages, [2.0, 3.9, 8.55], header_fill=TEAL, font_size=9.1)

    doc.add_heading("售前到交付的一份可执行输入", level=2)
    for item in [
        "部署环境：服务器规格、操作系统、网络、域名/证书、内外网边界。",
        "业务主体：小程序、支付、结算、发票和第三方接口主体。",
        "产品范围：标准功能、软硬件清单、配置项和明确排除项。",
        "定制范围：需求说明、负责人、完成时间、验收标准和费用边界。",
        "客户配合：甲方项目经理、测试人员、确认人、培训对象和现场条件。",
    ]:
        add_bullet(doc, item)

    doc.add_heading("AI 的定位", level=2)
    add_rich_paragraph(doc, [("AI 负责归集，负责人负责确认。", True, NAVY), (" AI 协助把会议、群聊和现场反馈整理成需求台账、变更记录和周度差异清单；范围、工期、费用和验收结论仍由项目负责人和甲方需求窗口确认。", False, DARK)], indent=False)

    add_page_heading(doc, "三、8 月产研只抓两件事", level=1)
    add_callout(doc, "第一件：历史项目产品化", "以 28 个在运/交付项目为样本，按客户类型、使用人群、软硬件组合、用餐流程、项目价值和验收边界，收敛为可报价、可部署、可验收的标准产品；与推广中心形成“一项目一张图”的销售案例。", fill=LIGHT_BLUE, title_color=NAVY)
    product_rows = [
        ["前厅标准产品候选", "410 称重秤、电子菜盘、防逃单、刷脸/刷卡与高峰期用餐流程", "标准模块、配置清单、验收脚本、场景案例"],
        ["校园数字治理候选", "膳食营养、食材采购、膳食经费、阳光消费、食品安全五系统", "学校/区域部署包、角色流程、家长端与对账边界"],
        ["健康管理闭环假设", "初始测评—营养干预—持续用餐—阶段复测—健康变化", "以体成分、体健之星等现有能力做试点验证；不得先写成经营成果"],
        ["项目案例资产", "场景、目标人群、业务流程、软硬件组合、项目价值、证据边界", "首批高价值样板的 3D 图/项目卡和可复用销售话术"],
    ]
    add_data_table(doc, ["方向", "收敛内容", "8 月应形成"], product_rows, [3.2, 6.1, 5.15], header_fill=NAVY, font_size=8.9)

    add_callout(doc, "第二件：一键安装包", "把环境搭建、组件安装、初始化配置和基础测试封装为交付可执行工具。安装包不是一个孤立按钮，必须同时交付版本清单、部署条件、验收脚本、异常处理和回滚说明。", fill=LIGHT_ORANGE, title_color=ORANGE)
    for label, text in [
        ("范围", "服务与组件清单、支持环境、标准配置、默认数据和不支持项。"),
        ("质量", "安装前检查、安装过程日志、安装后冒烟测试、失败提示和可恢复路径。"),
        ("交付", "交付人员操作说明、客户确认单、版本升级规则和问题升级通道。"),
        ("验收", "在真实服务器上完成可重复安装，交付人员独立执行并通过标准产品验收脚本。"),
    ]:
        add_number(doc, label, text)

    add_page_heading(doc, "四、经营与项目运行概览", level=1)
    operating_rows = [
        ["数科业务", "上周目标 0.5 万，实际 1.55 万；本周目标 4.02 万；月度预估 134.65 万", "项目节奏总体可控，仍需盯签约、验收与交付节点"],
        ["团餐业务", "8 月目标 243 万；中央美院 23.6 万项目基本完成签约；本周签约目标 38 万", "渠道商机和老客户增订成为增长信号"],
        ["事业部整体", "8 月计划 430 万，当前预估 486 万；未发货 577 万", "预测、发货、验收、确收和回款必须分口径管理"],
        ["现金回收", "应收账款 1,034 万；需补 2018 年前老账最新催收证据", "逐笔明确责任人、下一节点和预计回款时间"],
        ["项目运行", "28 个项目；2 万+用户；日使用约 5,000 人；项目流水 3,000 万+", "可支撑产品化与案例化；流水不等于公司收入或回款"],
        ["推广获客", "上周获客 7 条；筛选 159 条高匹配渠道；爱采购上架约 34 款产品", "统一分级、分配、跟进、转化和关闭原因"],
    ]
    add_data_table(doc, ["观察项", "来源事实", "管理判断"], operating_rows, [2.3, 7.35, 4.8], header_fill=NAVY, font_size=8.65)
    add_callout(doc, "经营口径提醒", "486 万元是月度预估，577 万元是未发货金额，3,000 万+是项目流水；三者均不能替代验收、确收与回款。", fill=LIGHT_ORANGE, title_color=RISK)

    doc.add_heading("服务带动增订", level=2)
    add_rich_paragraph(doc, [("增订不是偶然的小单，而是产品与服务认可的结果。", True, NAVY), (" 每个增订项目都要回溯：客户先购买了什么、认可了什么、追加了什么、服务做对了什么、是否能复制到集团其他点位，并建立“标准产品交付—稳定服务—增订/转介绍—持续服务收入”的闭环。", False, DARK)], indent=False)

    add_page_heading(doc, "五、重点项目与本周动作", level=1)
    cards = [
        ("P0", "什刹海体校", "运维服务合同已签、首付款已到；完成运维启动计划及 8 月 12 日进口器材开标准备。", "数科业务中心、交付实施部"),
        ("P0", "北京体育大学设备更新", "合同已过会、现场已勘测；完成双方盖章、设备入场计划和现场责任清单，并与林颖对齐节点。", "项目负责人、商务、交付"),
        ("P0", "国防科技大学", "8 月 10 日后进场处理漏水装修并启动验收回款；张帅完成流程/设备培训，研究院李阳补服务方案。", "张帅、交付实施部、研究院李阳"),
        ("P0", "保康中学", "锁定现场交付范围、部署条件、验收人和完成时间。", "交付实施部；具体负责人待明确"),
        ("P0", "哈尔滨膳食营养项目", "锁定合同推进节点、首期标准产品范围和“营养 + 健康 + 体质监测”产品方向。", "海宇、产品、交付"),
        ("P1", "城市服务中心", "完成 56 台设备的移交/试餐记录，并转成同类机关项目可复制案例。", "交付实施部、推广中心"),
        ("P1", "芜湖武警项目", "完成现场调研，锁定部署条件、产品组合、价格与售后边界。", "团餐业务中心、产品/售前"),
        ("P1", "28 个项目案例化", "统一“一项目一张图”模板，先完成首批高价值样板。", "产品、交付、推广中心"),
        ("P1", "展会与推广线索", "完成线索分配、联系记录更新和高优先级名单输出。", "数科/团餐销售、推广中心"),
    ]
    for card in cards:
        add_priority_card(doc, *card)

    doc.add_heading("六、产品、销售与组织能力建设", level=1)
    capabilities = [
        ("1", "标准化销售表达", "形成“康比特公司与规模 → 数科和团餐业务 → 客户场景 → 对应产品与案例”的标准开场；覆盖政企、学校、部队、康养等场景。"),
        ("2", "经验资产化", "每周复盘签约与丢单项目，沉淀客户画像、角色链、产品组合、报价策略和成败原因；形成 3—4 份报价清单和 2—3 套场景工具包。"),
        ("3", "每周讲产品、讲项目", "轮流展示重点项目、失败原因和招投标数据，用于团队共享与新员工快速上手。"),
        ("4", "康比特差异化闭环", "探索连接体成分测试、体健之星和智慧餐厅用餐数据，验证“测评—营养干预—复测”的客户价值与付费意愿。"),
    ]
    for no, title_text, body in capabilities:
        add_number(doc, no, f"{title_text}：{body}")

    doc.add_heading("主要风险与管理要求", level=1)
    risks = [
        ["交付周期失控", "部署条件不清，标准与定制混在一起", "以项目启动门禁作为 15 天计时起点"],
        ["范围持续增加", "口头需求直接进入研发，工期/成本/验收失真", "唯一窗口 + 电子台账 + 变更确认"],
        ["研发被部署占用", "安装工具缺少版本、测试和回滚能力", "按“工具 + 文档 + 验收 + 异常处理”完整交付"],
        ["预测未转成现金", "预估、未发货与项目流水不能替代回款", "建立签约—发货—验收—确收—回款台账"],
        ["团队理解不一致", "场景模拟暴露差异化、话术与痛点挖掘不足", "标准话术、报价清单、项目复盘、轮流讲项目"],
    ]
    add_data_table(doc, ["风险", "表现", "控制动作"], risks, [3.1, 5.5, 5.85], header_fill=RISK, font_size=8.9)

    doc.add_heading("七、本周必须完成的管理闭环", level=1)
    actions = [
        "发布“两阶段交付”项目模板：启动门禁、15 天标准产品交付、1 周驻场跟餐、定制阶段和变更流程。",
        "发布部署前置条件表 V1，并纳入合同附件或项目交接材料。",
        "明确一键安装包 V1 的范围、责任人、版本节点和验收标准。",
        "完成 28 个项目的产品化分类规则，并选定首批案例化样板。",
        "对 P0 项目逐项锁定负责人、完成时间和验收证据。",
        "商务中心输出签约—发货—验收—确收—回款台账，并更新 2018 年前应收催收证据。",
        "启动每周讲产品、讲项目和招投标数据展示机制；AI 负责归集记录，负责人负责确认结论。",
    ]
    for idx, action in enumerate(actions, 1):
        add_number(doc, str(idx), action)

    doc.add_heading("数据边界与待确认项", level=1)
    for item in [
        "事业部 8 月计划 430 万元、当前预估 486 万元；预估不等于实际完成。",
        "28 个项目的 2 万+用户、日使用约 5,000 人和 3,000 万+项目流水来自会议汇报；项目流水不等于合同收入、验收或回款。",
        "智能建设中心上周达成金额存在“2 万元”和“1.59 万元”两种口径，未进入管理结论，待商务中心统一。",
        "本周入职人数存在“5 人”和“2 人”两种口径，正文只保留“进入集中入职期”，具体人数待人力确认。",
        "哈尔滨客户名称存在语音转写差异，正文采用业务描述性名称；正式合同名称由业务中心回填。",
    ]:
        add_bullet(doc, item, color=MID_GRAY)

    closing = doc.add_paragraph()
    closing.alignment = WD_ALIGN_PARAGRAPH.CENTER
    closing.paragraph_format.space_before = Pt(12)
    closing.paragraph_format.space_after = Pt(0)
    set_run_font(closing.add_run("把项目输入锁清楚，把标准产品交付快，把定制边界守住，把服务价值转成增订与回款。"), size=11.5, bold=True, color=ORANGE)

    output.parent.mkdir(parents=True, exist_ok=True)
    doc.save(output)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("template", type=Path)
    parser.add_argument("output", type=Path)
    args = parser.parse_args()
    build(args.template, args.output)


if __name__ == "__main__":
    main()
