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

import base64
import html
import shutil
from io import BytesIO
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont
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, WD_BREAK
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Cm, Inches, Pt, RGBColor


REPORT_DIR = Path(
    "/Users/jack/同步空间/cpt/05_经营管理与会议/005_日常管理/"
    "周例会（事业部层面）/2026年"
)
BASE_DOCX = REPORT_DIR / "数字技术中心&交付实施部7 月月报.docx"
REPORT_STEM = "数字技术中心&交付实施部8月月报（截至8月22日·对外版）"
OUTPUT_DOCX = REPORT_DIR / f"{REPORT_STEM}.docx"
OUTPUT_HTML = REPORT_DIR / f"{REPORT_STEM}-预览.html"
ASSET_DIR = REPORT_DIR / f"{REPORT_STEM}-assets"

SOURCE_IMAGE_DIR = REPORT_DIR / "数字技术中心&交付实施部8月月报（截至8月21日）-assets"
FONT_PATH = Path("/Users/jack/Library/Fonts/msyh.ttc")

NAVY = "12344D"
TEAL = "008C82"
CYAN = "2C7BE5"
ORANGE = "F28C45"
PALE_TEAL = "E8F5F2"
PALE_BLUE = "EEF4FA"
PALE_ORANGE = "FFF1E7"
MID_GREY = "5E6B78"
LIGHT_GREY = "E3E8ED"
WHITE = "FFFFFF"
BLACK = "17212B"


def font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont:
    return ImageFont.truetype(str(FONT_PATH), size=size, index=1 if bold else 0)


def rounded_rect(draw: ImageDraw.ImageDraw, xy, radius, fill, outline=None, width=1):
    draw.rounded_rectangle(xy, radius=radius, fill=fill, outline=outline, width=width)


def draw_centered(draw, box, text, fnt, fill):
    left, top, right, bottom = box
    bbox = draw.textbbox((0, 0), text, font=fnt)
    x = left + (right - left - (bbox[2] - bbox[0])) / 2
    y = top + (bottom - top - (bbox[3] - bbox[1])) / 2 - 4
    draw.text((x, y), text, font=fnt, fill=fill)


def create_collaboration_overview(path: Path):
    canvas = Image.new("RGB", (2400, 1220), "#F8FAFC")
    draw = ImageDraw.Draw(canvas)
    draw.text((110, 70), "项目归类：从客户项目回到两套产品", font=font(62, True), fill="#12344D")
    draw.text((110, 155), "依据项目总表、项目标签表和8月新增项目整理", font=font(30), fill="#5E6B78")

    cards = [
        (
            "政企类",
            "营养结算｜食安进销存｜大型组织接口",
            "国信、城市副中心、金斯瑞、三全、四方达、国康医院、中央网信办等",
            "#E8F5F2",
            "#008C82",
        ),
        (
            "学校类",
            "阳光消费｜校园食安｜运动营养实训",
            "滨州、首通智城、人大附中、保康三中、中央美院、玉溪、湖南体职院等",
            "#EEF4FA",
            "#2C7BE5",
        ),
    ]
    for i, (title, products, projects, bg, accent) in enumerate(cards):
        x = 110 + i * 1110
        rounded_rect(draw, (x, 275, x + 1040, 890), 34, bg, "#D8E0E7", 2)
        draw.rectangle((x, 275, x + 18, 890), fill=accent)
        draw.text((x + 70, 340), title, font=font(58, True), fill=accent)
        draw.text((x + 70, 470), products, font=font(32, True), fill="#17212B")
        draw.multiline_text((x + 70, 590), projects, font=font(28), fill="#5E6B78", spacing=16)
        draw.text((x + 70, 790), "项目用来验证产品，不再按单个客户重复造一套。", font=font(27), fill=accent)

    rounded_rect(draw, (110, 965, 2290, 1135), 24, "#12344D")
    draw.text((165, 1010), "8月判断", font=font(34, True), fill="#5FE0D3")
    draw.text((390, 1010), "项目已经覆盖多种客户场景，9月要把共性收口为政企版和学校版。", font=font(34, True), fill="white")
    canvas.save(path, quality=95)


def create_plan_status(path: Path):
    canvas = Image.new("RGB", (2400, 1120), "#FFFFFF")
    draw = ImageDraw.Draw(canvas)
    draw.text((110, 70), "北京三区：硬件共性已经清楚", font=font(62, True), fill="#12344D")
    draw.text((110, 155), "来源：丰台、朝阳、昌平公开项目清单与应标差距对照", font=font(30), fill="#5E6B78")
    items = [
        ("191", "所/校区", "三区公开项目清单覆盖范围", "#E8F5F2", "#008C82"),
        ("25/26", "类硬件共同出现", "硬件需求高度重复", "#EEF4FA", "#2C7BE5"),
        ("96.2%", "硬件共性", "具备标准组合基础", "#FFF1E7", "#F28C45"),
        ("13/15", "项具备应标基础", "蒸烤箱和推车仍需锁定", "#F1ECFB", "#7C4DCA"),
    ]
    left, gap, card_w = 110, 38, 515
    for i, (num, label, note, bg, accent) in enumerate(items):
        x = left + i * (card_w + gap)
        rounded_rect(draw, (x, 290, x + card_w, 805), 30, bg, "#D8E0E7", 2)
        draw.rectangle((x, 290, x + 14, 805), fill=accent)
        draw.text((x + 58, 355), num, font=font(82, True), fill=accent)
        draw.text((x + 58, 505), label, font=font(32, True), fill="#17212B")
        draw.multiline_text((x + 58, 620), note, font=font(26), fill="#5E6B78", spacing=10)
    rounded_rect(draw, (110, 885, 2290, 1040), 24, "#12344D")
    draw.text((165, 925), "结论", font=font(34, True), fill="#5FE0D3")
    draw.text((330, 925), "校园餐硬件可以按五类组合封装；清单覆盖不等于公司中标或市场份额。", font=font(32, True), fill="white")
    canvas.save(path, quality=95)


def create_september_roadmap(path: Path):
    canvas = Image.new("RGB", (2400, 1120), "#F8FAFC")
    draw = ImageDraw.Draw(canvas)
    draw.text((110, 70), "9月怎么干：先成型，再验证，最后封装", font=font(62, True), fill="#12344D")
    draw.text((110, 155), "项目不是主线，产品成型和规模复制才是主线", font=font(30), fill="#5E6B78")
    weeks = [
        ("01", "两套产品成型", "政企版、学校版\n统一软件、硬件和边界", "#E8F5F2", "#008C82"),
        ("02", "项目反校", "四条产品线\n每条至少两个项目验证", "#EEF4FA", "#2C7BE5"),
        ("03", "硬件应标封装", "25类共性硬件\n补齐13项材料和2项缺口", "#FFF1E7", "#F28C45"),
        ("04", "安全合规验证", "部署、自测、扫描\n整改、复测、现场节点", "#F1ECFB", "#7C4DCA"),
    ]
    x0, gap, card_w = 110, 40, 515
    for i, (week, title, detail, bg, accent) in enumerate(weeks):
        x = x0 + i * (card_w + gap)
        rounded_rect(draw, (x, 305, x + card_w, 895), 28, bg, "#D8E0E7", 2)
        draw.ellipse((x + 45, 355, x + 145, 455), fill=accent)
        draw_centered(draw, (x + 45, 355, x + 145, 455), week, font(34, True), "white")
        draw.text((x + 50, 510), title, font=font(42, True), fill="#17212B")
        draw.multiline_text((x + 50, 625), detail, font=font(30), fill="#5E6B78", spacing=18)
    rounded_rect(draw, (110, 950, 2290, 1050), 20, "#12344D")
    draw_centered(draw, (110, 950, 2290, 1050), "月底看结果：2套产品包｜4条项目样板｜1套硬件应标包｜1条等保验证路径", font(31, True), "white")
    canvas.save(path, quality=95)


def set_cell_shading(cell, fill: str):
    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_border(cell, color="D9E1E8", size="4"):
    tc_pr = cell._tc.get_or_add_tcPr()
    borders = tc_pr.first_child_found_in("w:tcBorders")
    if borders is None:
        borders = OxmlElement("w:tcBorders")
        tc_pr.append(borders)
    for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
        tag = f"w:{edge}"
        el = borders.find(qn(tag))
        if el is None:
            el = OxmlElement(tag)
            borders.append(el)
        el.set(qn("w:val"), "single")
        el.set(qn("w:sz"), size)
        el.set(qn("w:color"), color)


def set_run_font(run, size=10.5, bold=False, color=BLACK, name="Heiti SC"):
    run.font.name = name
    run._element.rPr.rFonts.set(qn("w:eastAsia"), name)
    run.font.size = Pt(size)
    run.font.bold = bold
    run.font.color.rgb = RGBColor.from_string(color)


def style_paragraph(paragraph, before=0, after=5, line=1.18, keep=False):
    paragraph.paragraph_format.space_before = Pt(before)
    paragraph.paragraph_format.space_after = Pt(after)
    paragraph.paragraph_format.line_spacing = line
    paragraph.paragraph_format.keep_with_next = keep


def add_text(doc, text, size=10.5, bold=False, color=BLACK, align=None, after=5, before=0):
    p = doc.add_paragraph()
    if align is not None:
        p.alignment = align
    style_paragraph(p, before=before, after=after)
    r = p.add_run(text)
    set_run_font(r, size=size, bold=bold, color=color)
    return p


def add_title(doc, title, subtitle):
    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    style_paragraph(p, before=18, after=12, line=1)
    r = p.add_run(title)
    set_run_font(r, size=27, bold=True, color=NAVY)
    p2 = doc.add_paragraph()
    p2.alignment = WD_ALIGN_PARAGRAPH.CENTER
    style_paragraph(p2, after=16, line=1)
    r2 = p2.add_run(subtitle)
    set_run_font(r2, size=12, bold=True, color=TEAL)


def add_part(doc, label, title, color=TEAL):
    p = doc.add_paragraph()
    style_paragraph(p, before=4, after=10, line=1, keep=True)
    r1 = p.add_run(label)
    set_run_font(r1, size=11, bold=True, color=color)
    r2 = p.add_run("  " + title)
    set_run_font(r2, size=17, bold=True, color=NAVY)


def add_heading(doc, title, level=1):
    p = doc.add_paragraph()
    style_paragraph(p, before=6 if level == 1 else 4, after=6, keep=True)
    r = p.add_run(title)
    set_run_font(r, size=16 if level == 1 else 12.5, bold=True, color=NAVY if level == 1 else TEAL)
    return p


def add_callout(doc, title, body, fill=PALE_TEAL, accent=TEAL):
    table = doc.add_table(rows=1, cols=1)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    cell = table.cell(0, 0)
    cell.width = Cm(18.2)
    set_cell_shading(cell, fill)
    set_cell_border(cell, color=fill, size="0")
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
    p = cell.paragraphs[0]
    style_paragraph(p, before=3, after=3, line=1.18)
    r = p.add_run(title + "｜")
    set_run_font(r, size=10.5, bold=True, color=accent)
    r2 = p.add_run(body)
    set_run_font(r2, size=10.5, bold=False, color=BLACK)
    doc.add_paragraph().paragraph_format.space_after = Pt(1)


def add_image(doc, path: Path, width_in: float, caption: str | None = None):
    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    style_paragraph(p, after=2, line=1)
    p.add_run().add_picture(str(path), width=Inches(width_in))
    if caption:
        cp = doc.add_paragraph()
        cp.alignment = WD_ALIGN_PARAGRAPH.CENTER
        style_paragraph(cp, after=5, line=1)
        r = cp.add_run(caption)
        set_run_font(r, size=8.5, color=MID_GREY)


def add_table(doc, headers, rows, widths=None, font_size=8.7, header_fill=NAVY):
    table = doc.add_table(rows=1, cols=len(headers))
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    if widths:
        for i, w in enumerate(widths):
            table.columns[i].width = Cm(w)
    for i, h in enumerate(headers):
        cell = table.rows[0].cells[i]
        if widths:
            cell.width = Cm(widths[i])
        set_cell_shading(cell, header_fill)
        set_cell_border(cell)
        cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
        p = cell.paragraphs[0]
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        style_paragraph(p, before=2, after=2, line=1.05)
        r = p.add_run(h)
        set_run_font(r, size=font_size, bold=True, color=WHITE)
    for ri, row in enumerate(rows):
        cells = table.add_row().cells
        for i, value in enumerate(row):
            cell = cells[i]
            if widths:
                cell.width = Cm(widths[i])
            set_cell_shading(cell, WHITE if ri % 2 == 0 else "F6F8FA")
            set_cell_border(cell)
            cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
            p = cell.paragraphs[0]
            style_paragraph(p, before=1.5, after=1.5, line=1.05)
            r = p.add_run(str(value))
            set_run_font(r, size=font_size, color=BLACK)
    doc.add_paragraph().paragraph_format.space_after = Pt(1)
    return table


def add_page_break(doc):
    p = doc.add_paragraph()
    p.add_run().add_break(WD_BREAK.PAGE)


def clear_body(doc: Document):
    body = doc._element.body
    for child in list(body):
        if child.tag != qn("w:sectPr"):
            body.remove(child)


def configure_document(doc: Document):
    section = doc.sections[0]
    section.top_margin = Cm(1.65)
    section.bottom_margin = Cm(1.6)
    section.left_margin = Cm(1.65)
    section.right_margin = Cm(1.65)
    section.header_distance = Cm(0.6)
    section.footer_distance = Cm(0.6)

    styles = doc.styles
    normal = styles["Normal"]
    normal.font.name = "Heiti SC"
    normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Heiti SC")
    normal.font.size = Pt(10.5)

    for s in doc.sections:
        hp = s.header.paragraphs[0] if s.header.paragraphs else s.header.add_paragraph()
        hp.text = "康比特数字体育科技事业部｜2026年8月月报"
        hp.alignment = WD_ALIGN_PARAGRAPH.LEFT
        style_paragraph(hp, after=0, line=1)
        for run in hp.runs:
            set_run_font(run, size=8.5, color=NAVY)
        for fp in s.footer.paragraphs:
            for run in fp.runs:
                set_run_font(run, size=8, color=MID_GREY)

    doc.core_properties.title = "康比特数字业务2026年8月经营、产品与交付月报"
    doc.core_properties.author = "数字技术中心&交付实施部"
    doc.core_properties.subject = "截至2026年8月21日的月中收口版"


def build_docx(assets: dict[str, Path]):
    doc = Document(str(BASE_DOCX))
    clear_body(doc)
    configure_document(doc)

    # Page 1
    add_title(doc, "数字技术中心&交付实施部8月月报", "截至 2026 年 8 月 21 日｜月中收口版")
    add_callout(
        doc,
        "本月结论",
        "团队已经从“多项目并行”转向“产品包—项目门禁—真实数据回归—AI协作—证据沉淀”一体化推进；但17项8月任务仍未完成全量封版，9月必须把验收与复用作为主线。",
        fill=PALE_ORANGE,
        accent=ORANGE,
    )
    add_image(doc, assets["collaboration"], 6.45, "图 1｜8月协作空间快照。提交量和任务量只证明协作活动，不等于项目验收。")
    add_table(
        doc,
        ["本月形成", "当前偏差", "9月主线"],
        [["5+2产品能力、政企/校园产品包、项目门禁与AI协作底座", "8项进行中、4项延期、5项待补证；0项完整封版", "先关门、再封版、再跨项目复用"]],
        widths=[6.0, 6.0, 6.0],
        font_size=9.2,
    )

    # Page 2
    add_page_break(doc)
    add_part(doc, "PART 01", "8月份：我们完成了哪些工作")
    add_heading(doc, "1. 产品规模：运营数据继续积累，但经营口径必须分层")
    add_image(doc, assets["dashboard"], 6.55, "图 2｜运营指标概览（截至8月16日）。累计流水不等于公司收入、验收或回款。")
    add_table(
        doc,
        ["可查询项目", "总用户", "累计订单", "累计交易金额", "累计活跃用户"],
        [["13个", "28,476人", "1,828,358单", "3,118.14万元", "5,337人"]],
        widths=[3.2, 3.2, 3.7, 4.1, 3.8],
        font_size=10.2,
    )
    add_callout(
        doc,
        "经营判断",
        "项目覆盖和真实订单说明产品具备多场景运行基础；下一步不能只看覆盖面，要看项目从上线到验收的周期、标准模块复用次数、持续运营和后续增订。",
    )

    # Page 3
    add_page_break(doc)
    add_part(doc, "PART 01 / PRODUCT", "产品策略与标准产品")
    add_heading(doc, "2. 产品策略：从功能清单转向“5+2能力 + 场景标准包”")
    add_image(doc, assets["five_plus_two"], 6.45, "图 3｜AI营养智慧食堂5+2能力发布页。该图说明产品表达，不代表所有能力已在所有项目验收。")
    add_table(
        doc,
        ["主线", "8月已形成", "当前边界"],
        [
            ["5+2产品能力", "五大基础能力与两大差异能力形成统一故事线，并进入周报、路演和双版本PPT样稿。", "待28个项目案例逐一勾稽，不能把概念图冒充现场。"],
            ["政企营养模块", "完成原型初评，进入竞品分析与内容调整。", "需求、Demo、用例、手册、销售口径五件套尚未收口。"],
            ["校园数字治理", "多校组织、膳食经费、支付与阳光消费能力形成场景化产品包。", "需要补跨学校运行、账单对账、客户确认和复用证据。"],
            ["产品蓝图与路线图", "形成产品蓝图、版本规划、8—9月周节奏和项目反校逻辑。", "V1.0型号、报价边界和交付边界仍待负责人确认。"],
        ],
        widths=[3.6, 8.0, 6.4],
        font_size=8.9,
    )

    # Page 4
    add_page_break(doc)
    add_part(doc, "PART 01 / STANDARDIZATION", "标准产品与销售交付协同")
    add_heading(doc, "3. 产品资产：产品、商务、销售开始在同一工作台协同")
    table = doc.add_table(rows=1, cols=2)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    left, right = table.rows[0].cells
    left.width = Cm(8.0)
    right.width = Cm(10.0)
    set_cell_border(left, color=WHITE, size="0")
    set_cell_border(right, color=WHITE, size="0")
    p = left.paragraphs[0]
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    p.add_run().add_picture(str(assets["campus_product"]), width=Inches(2.95))
    rp = right.paragraphs[0]
    style_paragraph(rp, after=5)
    r = rp.add_run("Odoo产品协同试点")
    set_run_font(r, size=13, bold=True, color=NAVY)
    bullets = [
        "导入64条智慧食堂产品主数据，形成一级/二级分类。",
        "产品中心、销售产品入口、报价智能配单与复制工作台完成本地验收。",
        "六角色协同培训包完成桌面端、移动端浏览器验收。",
        "产品/商务可看成本与厂商，销售不看成本和具体厂商的权限规则通过本地回归。",
        "固定外网域名和真实业务试点仍未闭环，不能写成正式生产系统。",
    ]
    for text in bullets:
        p = right.add_paragraph()
        style_paragraph(p, after=5, line=1.18)
        r = p.add_run("• " + text)
        set_run_font(r, size=9.6, color=BLACK)
    add_callout(
        doc,
        "产品化价值",
        "团队正在把产品清单、场景套餐、报价、权限和交付边界放入同一协作链路。8月证明了本地可用，9月要用真实报价和真实项目交接验证。",
        fill=PALE_BLUE,
        accent=CYAN,
    )

    # Page 5
    add_page_break(doc)
    add_part(doc, "PART 01 / PROJECTS", "重点项目验证")
    add_heading(doc, "4. 项目安排：重点项目既要交付，也要留下标准资产")
    add_image(doc, assets["project_cockpit"], 6.45, "图 4｜交付实施部项目管理驾驶舱。当前总表有62条记录，其中18条为2026年项目。")
    add_table(
        doc,
        ["项目", "8月可核验进展", "沉淀方向", "仍未闭环"],
        [
            ["国信", "消费机真实HTTP与H5支付/退款链路通过；补贴、订单与异步导出持续开发。", "国企消费策略、补贴与资金记录标准模块", "H5开发库字段缺口、部分代码待提交、现场验收"],
            ["滨州", "一卡通48条代码关系回读；离线待支付状态机与微信V3充值可靠性形成专项回归。", "一卡通、访客混合、双钱包与离线补偿模板", "真机小额交易、Android现场和最终对账"],
            ["三全", "EMQX Q142/Q143正式通过；Redis本机双版本角色通过。", "高可用部署与故障切换模板", "全角色门禁、支付宝与完整回退"],
            ["城市副中心", "三块大屏点亮，460设备稳定性完成本轮修复。", "政务云、网络、设备稳定性检查模板", "全流程回归、压测和连续运行"],
        ],
        widths=[2.4, 6.8, 5.0, 3.8],
        font_size=8.25,
    )

    # Page 6
    add_page_break(doc)
    add_part(doc, "PART 01 / AI & ASSETS", "AI协作与交付资产")
    add_heading(doc, "5. AI从个人工具走向团队机制，但量化收益仍需补证")
    add_table(
        doc,
        ["能力", "8月可核验成果", "边界 / 下一步"],
        [
            ["CSCT PRO日复盘", "具体标题、真实活动时间、2—3句短摘要和SMART/PDCA/PMP门禁进入每日自动化。", "需用后续真实运行检查采用质量，不能用自动生成代替项目证据。"],
            ["AI Native审计", "三页展示各聚合102个证据日，47个外链均为HTTPS，OSS与本地字节一致。", "0/6成员席位实名接入；活动量不等于全员采用。"],
            ["企业微信AI", "CLI 1.1.0、14服务、95方法、1,510项源码测试通过；身份/在线表格/智能表格只读通过。", "其余模块权限与MCP宿主直连待续期验证。"],
            ["Skill-Up评测", "形成7个用例，保留基线失败，代表性充值用例最终回归通过。", "其余6个用例只完成配置验证，不得写成全绿。"],
            ["本地模型", "Qwen3.8多模态文本/视觉API和流式聊天通过；Ollama DeepSeek用法完成归档。", "尚未形成项目级质量、成本和隐私基线。"],
        ],
        widths=[3.2, 8.4, 6.4],
        font_size=8.5,
    )
    two = doc.add_table(rows=1, cols=2)
    two.alignment = WD_TABLE_ALIGNMENT.CENTER
    for i, key in enumerate(["hardware", "mlps"]):
        cell = two.rows[0].cells[i]
        set_cell_border(cell, color=WHITE, size="0")
        p = cell.paragraphs[0]
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        p.add_run().add_picture(str(assets[key]), width=Inches(3.05))
    add_text(doc, "图 5｜硬件研发计划与三级等保进度。计划、备案提交和材料包不等于实机验收或认证通过。", size=8.3, color=MID_GREY, align=WD_ALIGN_PARAGRAPH.CENTER, after=1)

    # Page 7
    add_page_break(doc)
    add_part(doc, "PART 02", "偏差与问题分析", color=ORANGE)
    add_text(doc, "8月最大偏差不是团队没有做事，而是“完成”仍停留在建设和验证，验收物、客户确认和跨项目复用没有同步收口。", size=12.3, bold=True, color=NAVY, after=10)
    add_image(doc, assets["plan_status"], 6.45, "图 6｜17项8月任务状态（8月17日统一口径）。")
    add_table(
        doc,
        ["状态", "数量", "管理判断"],
        [
            ["进行中", "8项", "已有建设或局部验证，但验收物不齐。"],
            ["偏离/延期", "4项", "原时间窗已过，必须重排门禁和责任。"],
            ["待补证", "5项", "缺全量台账、真实问题集、独立项目计划或关闭审计。"],
        ],
        widths=[3.4, 2.5, 12.1],
        font_size=9.4,
        header_fill=ORANGE,
    )

    # Page 8
    add_page_break(doc)
    add_part(doc, "PART 02 / ROOT CAUSE", "三个需要在9月纠正的系统问题", color=ORANGE)
    add_heading(doc, "问题一：任务数量很大，但完成定义没有前置")
    add_text(doc, "现象：8月产生大量提交、任务和材料，17项计划却没有一项达到全量封版。影响：团队很忙，管理层仍无法判断哪些能力可卖、可交付、可复用。根因：需求、Demo、测试、手册、销售口径、客户确认等验收物没有在任务启动时一起冻结。", size=9.7)
    add_callout(doc, "9月动作", "所有任务先写完成定义，再进入执行；没有验收物、责任人和日期的事项不得标记完成。验证指标：17项遗留任务100%形成关闭或升级记录。", fill=PALE_ORANGE, accent=ORANGE)

    add_heading(doc, "问题二：项目入口已集中，但全量需求与变更仍未统一")
    add_text(doc, "现象：交付驾驶舱已登记62条项目记录，但7月提出的22项目统一需求台账、25项无状态记录清零和变更确认仍无全量审计。影响：项目表能看见，不代表需求范围、合同边界和验收状态受控。根因：项目看板先解决了信息展示，尚未把唯一需求窗口、电子台账和变更流程变成强制门禁。", size=9.7)
    add_callout(doc, "9月动作", "先在三全、滨州、城市副中心做三项目试点，再扫描全量项目；所有新增需求必须写提出人、原因、优先级、合同范围、日期和验收标准。", fill=PALE_BLUE, accent=CYAN)

    add_heading(doc, "问题三：AI和标准资产有了，但节省了多少还说不清")
    add_text(doc, "现象：部署/运维Agent、Skill-Up、企业微信CLI、Odoo协同和多类Skill已经形成，真实项目的周期、一次通过率、人工修改量和复用次数仍缺基线。影响：不能证明AI对交付成本和质量的真实贡献。根因：能力建设与经营指标分开推进，没有在试点开始前记录Before。", size=9.7)
    add_callout(doc, "9月动作", "每个AI试点固定记录前后耗时、一次通过率、人工修改量和复用次数；生产、支付、权限和安全动作继续保留人工门禁。", fill=PALE_TEAL, accent=TEAL)

    # Page 9
    add_page_break(doc)
    add_part(doc, "PART 03", "9月份：产品、项目与研发整体规划")
    add_image(doc, assets["september"], 6.45, "图 7｜9月四周路线：收口—封版—复用—发布。")
    add_table(
        doc,
        ["周次", "主目标", "可回读验收物", "责任角色"],
        [
            ["W1", "收口8月17项任务和7月遗留", "关闭/升级清单、Owner、日期、验收物、风险状态", "产品负责人 / 项目经理"],
            ["W2", "政企/校园标准产品包封版", "产品清单、模块/报价/交付边界、营养五件套", "产品 / 销售 / 交付"],
            ["W3", "四个样板资产与第二项目复用", "流程、清单、手册、复用计时与人工修改记录", "项目组 / 交付组 / 研发"],
            ["W4", "学校版营养里程碑与内部发布", "年级营养分析、菜单达标检查、调整建议、发布评审", "产品 / 研发 / 测试"],
        ],
        widths=[2.0, 5.2, 7.3, 3.5],
        font_size=8.8,
    )

    # Page 10
    add_page_break(doc)
    add_part(doc, "PART 03 / MILESTONES", "9月关键里程碑与通过标准")
    add_heading(doc, "1. 产品：完成“可卖、可交接、可验收”的两套标准包")
    add_table(
        doc,
        ["里程碑", "完成定义", "通过标准"],
        [
            ["政企版标准包", "营养、结算、食安、进销存与硬件组合；模块、报价、交付边界清楚。", "至少2个项目反校，销售和交付共同签字确认。"],
            ["校园版标准包", "多校组织、膳食经费、支付、阳光消费与营养管理形成版本边界。", "首通/滨州/央美至少2个项目回读真实流程和差异。"],
            ["学校版营养模块", "年级营养分析、菜单达标检查和调整建议具备需求、Demo、用例、手册、话术。", "产品评审、研发测试和销售讲解三方通过。"],
        ],
        widths=[4.0, 8.0, 6.0],
        font_size=8.8,
    )
    add_heading(doc, "2. 项目与AI：四个样板封版，两个项目复用")
    add_table(
        doc,
        ["样板", "标准资产", "9月验收"],
        [
            ["三全", "高可用部署、角色门禁、故障切换与回退", "完整链路通过并形成第二项目演练"],
            ["滨州", "一卡通、离线消费、双钱包、全流程测试与远程交接", "真机小额、对账和连续运行证据齐全"],
            ["城市副中心", "政务云、网络、460设备、三屏、压测与异常恢复", "回归/压测报告和问题手册封版"],
            ["等保/纯内网", "材料清单、环境、自测、扫描、整改、复测与现场检查", "外部结果单列，不用备案提交替代认证"],
        ],
        widths=[3.2, 9.0, 5.8],
        font_size=8.6,
    )
    add_callout(
        doc,
        "一句话战略总结",
        "8月把团队协作和产品资产搭起来，9月必须用真实项目完成封版与复用，让项目交付、产品标准、AI效率和经营价值进入同一条证据链。",
        fill=PALE_ORANGE,
        accent=ORANGE,
    )

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


def build_docx_external(assets: dict[str, Path]):
    doc = Document(str(BASE_DOCX))
    clear_body(doc)
    configure_document(doc)
    doc.core_properties.title = "康比特数字业务2026年8月产品与项目月报（对外版）"
    doc.core_properties.subject = "截至2026年8月22日"

    # Page 1
    add_title(doc, "数字技术中心&交付实施部8月月报", "截至 2026 年 8 月 22 日｜对外版")
    add_callout(
        doc,
        "本月结论",
        "8月围绕产品升级、项目交付、硬件标准化和安全合规四条主线展开。产品开始按政企版和学校版收口；项目覆盖多种客户场景；校园餐硬件共性已经清楚；三级等保进入材料提交后的实质准备阶段。",
        fill=PALE_ORANGE,
        accent=ORANGE,
    )
    add_image(doc, assets["collaboration"], 6.45, "图 1｜项目按政企类和学校类归纳，项目用于验证产品。")
    add_table(
        doc,
        ["8月形成", "当前问题", "9月重点"],
        [["两类产品方向、五类硬件组合和安全合规资料", "部分成果仍处于开发、联调和资料准备阶段", "产品成型、项目反校、硬件应标、安全验证"]],
        widths=[6.0, 6.0, 6.0],
        font_size=9.3,
    )

    # Page 2
    add_page_break(doc)
    add_part(doc, "PART 01", "8月份完成的工作")
    add_heading(doc, "1. 运营基础：项目已经进入多场景运行")
    add_image(doc, assets["dashboard"], 6.55, "图 2｜运营指标概览（截至8月16日）。累计交易额不等于公司收入或回款。")
    add_table(
        doc,
        ["可查询项目", "总用户", "累计订单", "累计交易额", "累计活跃用户"],
        [["13个", "28,476人", "1,828,358单", "3,118.14万元", "5,337人"]],
        widths=[3.2, 3.2, 3.7, 4.1, 3.8],
        font_size=10.0,
    )
    add_callout(
        doc,
        "业务判断",
        "项目已经覆盖政府机关、国企集团、学校、医院、金融机构和体育院校。下一阶段不再以项目数量作为主要结果，而要看标准产品能否复用、项目能否验收、客户能否持续使用。",
    )

    # Page 3
    add_page_break(doc)
    add_part(doc, "PART 01 / GOVERNMENT & ENTERPRISE", "政企类：开发了什么，应用到哪里")
    add_heading(doc, "2. 政企产品从结算系统扩展为食堂整体管理")
    add_table(
        doc,
        ["产品方向", "主要内容", "应用项目"],
        [
            ["政企智慧营养餐厅", "人员、账户、补贴、消费、退款、对账、称重、AI识别和营养展示。", "国信、城市副中心、金斯瑞、江西206、四方达、重庆环卫、张家港农商行、新吴区政府、中债、首都机场、赛迪等"],
            ["政企食安与进销存", "供应商、采购、验收、库存、晨检、留样、温湿度、视频巡查和问题整改。", "国信、三全、中央网信办、海开、安徽武警等"],
            ["大型组织接口与部署", "本地、公有云、政务云部署；SAP、银行、财务、HIS、一卡通、考勤和利旧设备接口。", "三全、国康医院、城市副中心、国信、大兴、华油、中煤等"],
            ["运动营养与数字体育", "体成分、运动设备、训练数据和营养建议；场馆与设备项目按数字体育交付管理。", "上海体育训练基地、北京市体育科学研究所、山西体科所、福建漳州营养系统等"],
        ],
        widths=[4.1, 7.2, 6.7],
        font_size=8.65,
    )
    add_callout(
        doc,
        "政企类结论",
        "政企客户需要的不是单一收银工具，而是结算、营养、食安、进销存和组织接口能够一起工作。高频功能应做成标准配置，SAP、银行、HIS和考勤等外部接口单独评估。",
        fill=PALE_BLUE,
        accent=CYAN,
    )

    # Page 4
    add_page_break(doc)
    add_part(doc, "PART 01 / SCHOOL", "学校类：开发了什么，验证了什么")
    add_heading(doc, "3. 学校版已经形成两条产品线")
    add_table(
        doc,
        ["产品方向", "主要内容", "应用项目"],
        [
            ["校园营养结算与阳光消费", "多学校、多食堂、学生账户、膳食经费、月度账单、缴费、称重结算、餐盘绑定和营养展示。", "滨州、首通智城、人大附中、中央美院、大兴三校"],
            ["校园食安与进销存", "采购验收、库存、晨检、留样、环境监测、AI明厨亮灶、公示大屏和监管接口。", "滨州、保康三中、大兴三校、广西体育运动学校"],
            ["运动营养与院校实训", "体脂、体成分、运动设备和营养系统接入教学与训练，支持体测展示和实训。", "玉溪、安徽体职院、重庆医专、湖南体职院、江苏食品药品学院、成都广汉飞行学院等"],
        ],
        widths=[4.2, 8.2, 5.6],
        font_size=8.7,
    )
    add_heading(doc, "项目验证的功能", level=2)
    add_table(
        doc,
        ["验证方向", "项目与阶段结果"],
        [
            ["多校组织与资金", "首通智城验证多学校独立运营、膳食经费、月度账单和缴费入口。"],
            ["一卡通与称重结算", "滨州验证一卡通、访客混合、离线消费、双钱包、餐盘绑定和称重餐线。"],
            ["电子菜牌与信息发布", "人大附中验证旧设备兼容、菜品发布、分页轮播和语音叫号；中央美院验证校园一卡通与称重结算上线条件。"],
            ["食安与后厨", "保康三中验证AI明厨亮灶、晨检、留样、收货秤、采购库存和公示大屏的组合方式。"],
            ["运动营养", "玉溪、湖南体职院、重庆医专等验证体成分、运动设备和营养系统的数据接入。"],
        ],
        widths=[5.0, 13.0],
        font_size=8.6,
    )
    add_text(doc, "以上为项目阶段性验证；没有客户验收或连续运行记录的功能，不写成已经规模化复制。", size=8.8, color=MID_GREY, after=1)

    # Page 5
    add_page_break(doc)
    add_part(doc, "PART 01 / PRODUCT", "产品策略：从项目功能转为客户可以买的产品")
    add_heading(doc, "4. 8月产品工作沿三条主线收口")
    add_image(doc, assets["five_plus_two"], 6.45, "图 3｜AI营养智慧食堂5+2产品表达。产品表达不替代项目验收。")
    add_table(
        doc,
        ["产品主线", "8月结果", "客户价值"],
        [
            ["智慧营养进入高频用餐", "营养标签、用餐反馈、称重和AI识别等能力进入前厅设备、移动端和管理端。", "客户既能改善就餐效率，也能把真实用餐数据用于营养管理。"],
            ["标准团餐产品成型", "营养结算、智慧食安、智慧进销存和硬件开始按政企版、学校版组合。", "销售能讲清范围，交付能按清单实施，客户能按标准验收。"],
            ["项目经验变成资料", "项目中的接口、设备、部署、测试和操作经验开始整理为方案、配置表、检查表和操作资料。", "下一个同类项目减少重复沟通和重复设计。"],
        ],
        widths=[4.3, 7.2, 6.5],
        font_size=8.8,
    )

    # Page 6
    add_page_break(doc)
    add_part(doc, "PART 01 / HARDWARE", "硬件产品与校园市场")
    add_heading(doc, "5. 北京三区采购清单显示，硬件需求可以标准化")
    add_image(doc, assets["plan_status"], 6.45, "图 4｜北京三区公开项目清单与应标差距判断。")
    add_table(
        doc,
        ["硬件组合", "包含内容", "产品判断"],
        [
            ["现场作业与食安终端", "管理一体机、智能验货机、库管手持机、智能留样仪", "业务流程一致，可按学校规模分档。"],
            ["食安物联监测与控制", "温湿度、中心温度计、广温监测、智能开关、网关", "统一设备编码、通信协议、告警和台账。"],
            ["视频监控与AI预警", "摄像机、硬盘、PoE交换机、录像机、算法终端", "统一兼容矩阵和AI事件接口。"],
            ["网络与安装配套", "机柜、网线、电源线、线管、插排、配线架等", "统一材料规格、施工工艺和验收标准。"],
            ["智能烹饪", "20层万能蒸烤箱及配套推车", "品类共性明确，最终型号、授权和服务边界仍需确认。"],
        ],
        widths=[4.2, 7.4, 6.4],
        font_size=8.55,
    )
    add_text(doc, "公开清单涉及191所/校区，只表示采购需求覆盖范围，不表示康比特中标规模或市场份额。", size=8.7, color=MID_GREY, after=1)

    # Page 7
    add_page_break(doc)
    add_part(doc, "PART 01 / SECURITY", "三级等保与问题复盘")
    add_heading(doc, "6. 安全合规从材料准备进入验证阶段")
    add_image(doc, assets["mlps"], 6.35, "图 5｜三级等保备案进度。最终结果以主管机关和测评机构为准。")
    add_table(
        doc,
        ["已完成", "正在推进", "最终判断"],
        [["备案材料提交；二级/三级材料清单、采集模板和缺项检查工具形成", "环境部署、自测、首轮扫描、整改和复测准备", "取得正式回读前，不写成已通过三级等保"]],
        widths=[6.0, 6.0, 6.0],
        font_size=9.0,
    )
    add_heading(doc, "本月需要解决的两个问题", level=2)
    add_callout(doc, "问题一", "产品和项目验收速度仍需加快。部分成果已经完成开发或联调，但连续运行、客户确认和验收资料没有同步收口。", fill=PALE_ORANGE, accent=ORANGE)
    add_callout(doc, "问题二", "硬件和安全节点受外部条件影响。厂家参数、检测报告、授权以及等保扫描和现场检查时间需要前置锁定。", fill=PALE_BLUE, accent=CYAN)

    # Page 8
    add_page_break(doc)
    add_part(doc, "PART 02", "9月份怎么干")
    add_heading(doc, "9月不再铺任务，先把两套产品做成")
    add_image(doc, assets["september"], 6.45, "图 6｜9月工作思路：产品成型—项目反校—硬件封装—安全验证。")
    add_text(doc, "第一步，先把政企版和学校版的范围定下来。没有统一产品清单，项目越多，重复设计越多。", size=10.2, bold=True, color=NAVY)
    add_text(doc, "第二步，用项目反校产品。每条产品线至少选两个项目，核对功能、配置、接口、硬件和验收方式。", size=10.2, bold=True, color=NAVY)
    add_text(doc, "第三步，把硬件和安全材料补齐。投标和交付需要正式规格、检测材料、部署条件和验收依据，不能等到最后再找。", size=10.2, bold=True, color=NAVY)
    add_callout(doc, "9月判断标准", "月底必须拿到2套标准产品包、4条产品线样板、1套校园餐硬件应标包和1条完整的等保验证路径。", fill=PALE_TEAL, accent=TEAL)

    # Page 9
    add_page_break(doc)
    add_part(doc, "PART 02 / PRODUCT & PROJECT", "9月主线一、二：产品成型，项目反校")
    add_heading(doc, "1. 两套标准产品包")
    add_table(
        doc,
        ["产品包", "封装内容", "月底结果"],
        [
            ["政企版", "营养结算、食安、进销存、组织接口、部署方式、硬件组合、实施资料和验收标准。", "产品、销售、交付使用同一份清单；外部接口单独报价和排期。"],
            ["学校版", "阳光消费、校园食安、营养管理、家校公示、主管部门监管、院校实训和硬件组合。", "普通学校与体育院校的产品边界分开；形成学校版演示和验收材料。"],
        ],
        widths=[3.4, 8.4, 6.2],
        font_size=8.9,
    )
    add_heading(doc, "2. 四条产品线样板")
    add_table(
        doc,
        ["产品线", "项目反校范围", "需要形成的资料"],
        [
            ["政企智慧营养餐厅", "国信、城市副中心、金斯瑞、四方达、江西206等至少选2个", "标准功能、配置项、设备组合和验收清单"],
            ["政企食安与进销存", "国信、三全、中央网信办、海开等至少选2个", "采购验收、库存、食安和组织接口清单"],
            ["校园营养结算与阳光消费", "滨州、首通智城、人大附中、中央美院、大兴三校等至少选2个", "多校组织、经费、缴费、结算和信息发布清单"],
            ["校园食安与运动营养", "保康三中、玉溪、湖南体职院、重庆医专等至少选2个", "食安设备、营养分析、体成分和实训清单"],
        ],
        widths=[4.2, 8.2, 5.6],
        font_size=8.45,
    )

    # Page 10
    add_page_break(doc)
    add_part(doc, "PART 02 / HARDWARE & SECURITY", "9月主线三、四：硬件封装，安全验证")
    add_heading(doc, "3. 完成校园餐硬件应标包")
    add_table(
        doc,
        ["工作", "做法", "结果"],
        [
            ["五类硬件组合", "按现场终端、物联监测、视频AI、网络配套、智能烹饪整理配置表。", "销售和投标可以按学校数量、点位和场景快速配置。"],
            ["13项应标材料", "补正式规格、检测报告、厂家授权、接口说明、兼容清单和样机记录。", "已有产品和候选方案能够直接组织材料。"],
            ["蒸烤箱与推车", "锁定厂家、主机、推车、BOM、报价、授权、接口和服务边界。", "关闭当前两项硬件缺口。"],
        ],
        widths=[3.7, 8.5, 5.8],
        font_size=8.8,
    )
    add_heading(doc, "4. 完成三级等保验证路径")
    add_table(
        doc,
        ["阶段", "9月动作", "通过标准"],
        [
            ["部署与自测", "完成环境部署、基础配置和自测记录。", "环境可复现，问题有记录。"],
            ["扫描与整改", "完成首轮漏洞扫描、整改和复测。", "每个问题有处理结果和复测结论。"],
            ["现场与复用", "明确现场检查和备案节点，并将材料模板用于下一个项目。", "外部结果单列；资料能在第二个项目直接使用。"],
        ],
        widths=[3.4, 8.0, 6.6],
        font_size=8.8,
    )
    add_callout(
        doc,
        "9月总结",
        "9月的重点不是再增加多少功能，而是把政企版和学校版做成，把项目经验收回来，把硬件和安全材料补齐。做到这三点，销售能卖、项目能干、客户能验收。",
        fill=PALE_ORANGE,
        accent=ORANGE,
    )

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


def image_data_uri(path: Path) -> str:
    mime = "image/png" if path.suffix.lower() == ".png" else "image/jpeg"
    return f"data:{mime};base64,{base64.b64encode(path.read_bytes()).decode('ascii')}"


def build_html(assets: dict[str, Path]):
    css = """
    :root{--navy:#12344d;--teal:#008c82;--blue:#2c7be5;--orange:#f28c45;--ink:#17212b;--muted:#5e6b78;--line:#dfe6ec;--paper:#f6f8fa}
    *{box-sizing:border-box}body{margin:0;background:var(--paper);font-family:"Microsoft YaHei","PingFang SC",sans-serif;color:var(--ink)}
    .page{max-width:1120px;margin:28px auto;background:#fff;padding:54px 64px;box-shadow:0 18px 60px rgba(18,52,77,.08)}
    h1{font-size:42px;color:var(--navy);margin:0 0 8px}h2{font-size:28px;color:var(--navy);margin:34px 0 14px}h3{font-size:20px;color:var(--teal);margin:26px 0 10px}
    .sub{color:var(--teal);font-weight:700}.lead{font-size:20px;line-height:1.75;background:#fff1e7;border-left:6px solid var(--orange);padding:22px 24px;margin:26px 0}
    .part{font-weight:800;color:var(--teal);letter-spacing:.08em;margin-top:42px}.part.orange{color:var(--orange)}
    img{width:100%;border:1px solid var(--line);border-radius:8px;margin:12px 0}.caption{font-size:13px;color:var(--muted);text-align:center;margin-top:0}
    table{width:100%;border-collapse:collapse;margin:16px 0 24px;font-size:14px;line-height:1.55}th{background:var(--navy);color:#fff;text-align:left;padding:11px}td{border:1px solid var(--line);padding:11px;vertical-align:top}tr:nth-child(even) td{background:#f8fafc}
    .callout{padding:18px 22px;background:#e8f5f2;border-left:5px solid var(--teal);line-height:1.7;margin:18px 0}.note{font-size:13px;color:var(--muted)}
    .footer{margin-top:38px;padding-top:16px;border-top:1px solid var(--line);color:var(--muted);font-size:12px}
    @media(max-width:760px){.page{margin:0;padding:28px 20px;box-shadow:none}h1{font-size:30px}table{font-size:12px;display:block;overflow-x:auto}}
    @media print{@page{size:A4;margin:12mm}body{background:#fff}.page{max-width:none;margin:0;padding:0;box-shadow:none}h2,h3,.part{break-after:avoid;page-break-after:avoid}img,table,.callout,.lead{break-inside:avoid;page-break-inside:avoid}}
    """
    body = f"""
    <div class="page">
      <h1>数字技术中心&交付实施部8月月报</h1><div class="sub">截至 2026 年 8 月 21 日｜月中收口版</div>
      <div class="lead"><b>本月结论：</b>团队已经从“多项目并行”转向“产品包—项目门禁—真实数据回归—AI协作—证据沉淀”一体化推进；但17项8月任务仍未完成全量封版，9月必须把验收与复用作为主线。</div>
      <img src="{image_data_uri(assets['collaboration'])}"><p class="caption">8月协作空间快照。活动量不等于客户验收。</p>
      <div class="part">PART 01｜8月份完成的工作</div>
      <h2>1. 产品规模：运营数据继续积累，但经营口径必须分层</h2>
      <img src="{image_data_uri(assets['dashboard'])}"><p class="caption">运营指标概览（截至8月16日）。累计流水不等于公司收入、验收或回款。</p>
      <table><tr><th>可查询项目</th><th>总用户</th><th>累计订单</th><th>累计交易金额</th><th>累计活跃用户</th></tr><tr><td>13个</td><td>28,476人</td><td>1,828,358单</td><td>3,118.14万元</td><td>5,337人</td></tr></table>
      <h2>2. 产品策略：从功能清单转向“5+2能力 + 场景标准包”</h2>
      <img src="{image_data_uri(assets['five_plus_two'])}"><p class="caption">AI营养智慧食堂5+2能力发布页；不代表所有能力已在所有项目验收。</p>
      <table><tr><th>主线</th><th>8月已形成</th><th>当前边界</th></tr>
      <tr><td>5+2产品能力</td><td>统一故事线进入周报、路演和双版本PPT样稿。</td><td>28个项目案例仍需逐一勾稽。</td></tr>
      <tr><td>政企营养模块</td><td>完成原型初评并进入竞品分析。</td><td>五件套尚未收口。</td></tr>
      <tr><td>校园数字治理</td><td>多校组织、膳食经费、支付与阳光消费形成场景包。</td><td>需补跨学校运行、对账和客户确认。</td></tr></table>
      <h2>3. 标准产品与销售交付协同</h2>
      <img src="{image_data_uri(assets['campus_product'])}" style="max-width:520px;display:block;margin:16px auto"><p class="caption">校园数字治理一体化产品页。</p>
      <div class="callout"><b>Odoo产品协同试点：</b>导入64条产品主数据，完成多级分类、报价智能配单、六角色培训与成本/厂商隐私规则的本地验收。固定域名和真实业务试点仍未闭环。</div>
      <h2>4. 重点项目验证</h2>
      <img src="{image_data_uri(assets['project_cockpit'])}"><p class="caption">交付项目驾驶舱：62条项目记录，其中18条为2026年项目。</p>
      <table><tr><th>项目</th><th>8月可核验进展</th><th>仍未闭环</th></tr>
      <tr><td>国信</td><td>消费机真实HTTP与H5支付/退款链路通过；补贴、订单与异步导出持续开发。</td><td>字段缺口、代码提交和现场验收。</td></tr>
      <tr><td>滨州</td><td>一卡通48条代码关系回读；离线待支付状态机与微信V3充值专项回归。</td><td>真机小额、Android现场和最终对账。</td></tr>
      <tr><td>三全</td><td>EMQX Q142/Q143与Redis角色通过。</td><td>全角色门禁、支付宝和完整回退。</td></tr>
      <tr><td>城市副中心</td><td>三块大屏点亮，460设备完成本轮稳定性修复。</td><td>全流程回归、压测和连续运行。</td></tr></table>
      <h2>5. AI协作与交付资产</h2>
      <table><tr><th>能力</th><th>8月可核验成果</th><th>边界</th></tr>
      <tr><td>CSCT PRO日复盘</td><td>活动事实与SMART/PDCA/PMP门禁进入每日自动化。</td><td>自动生成不能替代项目证据。</td></tr>
      <tr><td>AI Native审计</td><td>三页各聚合102个证据日，47个HTTPS外链和OSS字节回读通过。</td><td>成员实名接入仍为0/6。</td></tr>
      <tr><td>企业微信AI</td><td>CLI 1.1.0、14服务、95方法、1,510项源码测试通过。</td><td>其余模块权限和MCP直连待续期。</td></tr>
      <tr><td>Skill-Up</td><td>7个用例建立，代表性充值用例最终回归通过。</td><td>其余6个未执行模型回归。</td></tr></table>
      <div class="part orange">PART 02｜偏差与问题分析</div>
      <img src="{image_data_uri(assets['plan_status'])}"><p class="caption">17项8月任务状态（8月17日统一口径）。</p>
      <h3>问题一：任务数量很大，但完成定义没有前置</h3><p>8月产生大量提交、任务和材料，17项计划却没有一项达到全量封版。9月所有任务先写完成定义，再进入执行；没有验收物、责任人和日期的事项不得标记完成。</p>
      <h3>问题二：项目入口已集中，但全量需求与变更仍未统一</h3><p>驾驶舱已登记62条项目记录，但22项目统一需求台账、25项无状态记录清零和变更确认仍无全量审计。9月先完成三项目试点，再扫描全量项目。</p>
      <h3>问题三：AI和标准资产有了，但节省了多少还说不清</h3><p>部署/运维Agent、Skill-Up、企业微信CLI、Odoo协同和多类Skill已经形成，真实项目的周期、一次通过率、人工修改量和复用次数仍缺基线。9月每个AI试点固定记录Before/After。</p>
      <div class="part">PART 03｜9月份整体规划</div>
      <img src="{image_data_uri(assets['september'])}"><p class="caption">9月四周路线：收口—封版—复用—发布。</p>
      <table><tr><th>周次</th><th>主目标</th><th>验收物</th></tr>
      <tr><td>W1</td><td>收口8月17项任务和7月遗留</td><td>关闭/升级清单、Owner、日期、验收物</td></tr>
      <tr><td>W2</td><td>政企/校园标准产品包封版</td><td>产品清单、三类边界、营养五件套</td></tr>
      <tr><td>W3</td><td>四个样板资产与第二项目复用</td><td>流程、清单、手册、复用计时</td></tr>
      <tr><td>W4</td><td>学校版营养里程碑与内部发布</td><td>年级分析、达标检查、调整建议、发布评审</td></tr></table>
      <div class="lead"><b>一句话战略总结：</b>8月把团队协作和产品资产搭起来，9月必须用真实项目完成封版与复用，让项目交付、产品标准、AI效率和经营价值进入同一条证据链。</div>
      <div class="footer">数据口径：截至2026-08-21；8月任务状态采用2026-08-17统一对标。内部管理材料。</div>
    </div>
    """
    OUTPUT_HTML.write_text(f"<!doctype html><html lang='zh-CN'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>{html.escape(REPORT_STEM)}</title><style>{css}</style></head><body>{body}</body></html>", encoding="utf-8")


def build_html_external(assets: dict[str, Path]):
    css = """
    :root{--navy:#12344d;--teal:#008c82;--blue:#2c7be5;--orange:#f28c45;--ink:#17212b;--muted:#5e6b78;--line:#dfe6ec;--paper:#f6f8fa}
    *{box-sizing:border-box}body{margin:0;background:var(--paper);font-family:"Microsoft YaHei","PingFang SC",sans-serif;color:var(--ink)}
    .page{max-width:1120px;margin:28px auto;background:#fff;padding:54px 64px;box-shadow:0 18px 60px rgba(18,52,77,.08)}
    h1{font-size:42px;color:var(--navy);margin:0 0 8px}h2{font-size:28px;color:var(--navy);margin:34px 0 14px}h3{font-size:20px;color:var(--teal);margin:26px 0 10px}
    p{line-height:1.75}.sub{color:var(--teal);font-weight:700}.lead{font-size:20px;line-height:1.75;background:#fff1e7;border-left:6px solid var(--orange);padding:22px 24px;margin:26px 0}
    .part{font-weight:800;color:var(--teal);letter-spacing:.08em;margin-top:42px}.part.orange{color:var(--orange)}
    img{width:100%;border:1px solid var(--line);border-radius:8px;margin:12px 0}.caption{font-size:13px;color:var(--muted);text-align:center;margin-top:0}
    table{width:100%;border-collapse:collapse;margin:16px 0 24px;font-size:14px;line-height:1.55}th{background:var(--navy);color:#fff;text-align:left;padding:11px}td{border:1px solid var(--line);padding:11px;vertical-align:top}tr:nth-child(even) td{background:#f8fafc}
    .callout{padding:18px 22px;background:#e8f5f2;border-left:5px solid var(--teal);line-height:1.7;margin:18px 0}.warn{background:#fff1e7;border-left-color:var(--orange)}
    .footer{margin-top:38px;padding-top:16px;border-top:1px solid var(--line);color:var(--muted);font-size:12px}
    @media(max-width:760px){.page{margin:0;padding:28px 20px;box-shadow:none}h1{font-size:30px}table{font-size:12px;display:block;overflow-x:auto}}
    @media print{@page{size:A4;margin:12mm}body{background:#fff}.page{max-width:none;margin:0;padding:0;box-shadow:none}h2,h3,.part{break-after:avoid;page-break-after:avoid}img,table,.callout,.lead{break-inside:avoid;page-break-inside:avoid}}
    """
    body = f"""
    <div class="page">
      <h1>数字技术中心&交付实施部8月月报</h1><div class="sub">截至 2026 年 8 月 22 日｜对外版</div>
      <div class="lead"><b>本月结论：</b>8月围绕产品升级、项目交付、硬件标准化和安全合规四条主线展开。产品开始按政企版和学校版收口；项目覆盖多种客户场景；校园餐硬件共性已经清楚；三级等保进入材料提交后的实质准备阶段。</div>
      <img src="{image_data_uri(assets['collaboration'])}"><p class="caption">项目按政企类和学校类归纳，项目用于验证产品。</p>

      <div class="part">PART 01｜8月份完成的工作</div>
      <h2>1. 运营基础：项目已经进入多场景运行</h2>
      <img src="{image_data_uri(assets['dashboard'])}"><p class="caption">运营指标概览（截至8月16日）。累计交易额不等于公司收入或回款。</p>
      <table><tr><th>可查询项目</th><th>总用户</th><th>累计订单</th><th>累计交易额</th><th>累计活跃用户</th></tr><tr><td>13个</td><td>28,476人</td><td>1,828,358单</td><td>3,118.14万元</td><td>5,337人</td></tr></table>

      <h2>2. 政企类：从结算系统扩展为食堂整体管理</h2>
      <table><tr><th>产品方向</th><th>主要内容</th><th>应用项目</th></tr>
      <tr><td>政企智慧营养餐厅</td><td>账户、补贴、消费、退款、对账、称重、AI识别和营养展示。</td><td>国信、城市副中心、金斯瑞、江西206、四方达、重庆环卫、张家港农商行、新吴区政府、中债、首都机场、赛迪等</td></tr>
      <tr><td>政企食安与进销存</td><td>供应商、采购、验收、库存、晨检、留样、环境监测、视频巡查和问题整改。</td><td>国信、三全、中央网信办、海开、安徽武警等</td></tr>
      <tr><td>大型组织接口与部署</td><td>本地、公有云、政务云；SAP、银行、财务、HIS、一卡通、考勤和利旧设备接口。</td><td>三全、国康医院、城市副中心、国信、大兴、华油、中煤等</td></tr></table>
      <div class="callout"><b>政企类结论：</b>政企客户需要的不是单一收银工具，而是结算、营养、食安、进销存和组织接口能够一起工作。高频功能做成标准配置，外部接口单独评估。</div>

      <h2>3. 学校类：形成校园餐和运动营养两条产品线</h2>
      <table><tr><th>产品方向</th><th>主要内容</th><th>应用项目</th></tr>
      <tr><td>校园营养结算与阳光消费</td><td>多学校、多食堂、学生账户、膳食经费、月度账单、缴费、称重结算、餐盘绑定和营养展示。</td><td>滨州、首通智城、人大附中、中央美院、大兴三校</td></tr>
      <tr><td>校园食安与进销存</td><td>采购验收、库存、晨检、留样、环境监测、AI明厨亮灶、公示大屏和监管接口。</td><td>滨州、保康三中、大兴三校、广西体育运动学校</td></tr>
      <tr><td>运动营养与院校实训</td><td>体脂、体成分、运动设备和营养系统接入教学与训练。</td><td>玉溪、安徽体职院、重庆医专、湖南体职院、江苏食品药品学院、成都广汉飞行学院等</td></tr></table>
      <table><tr><th>验证方向</th><th>项目结果</th></tr>
      <tr><td>多校组织与资金</td><td>首通智城验证多学校独立运营、膳食经费、月度账单和缴费入口。</td></tr>
      <tr><td>一卡通与称重结算</td><td>滨州验证一卡通、访客混合、离线消费、双钱包、餐盘绑定和称重餐线。</td></tr>
      <tr><td>信息发布</td><td>人大附中验证旧电子菜牌兼容、菜品发布、分页轮播和语音叫号。</td></tr>
      <tr><td>食安与后厨</td><td>保康三中验证AI明厨亮灶、晨检、留样、收货秤、采购库存和公示大屏的组合。</td></tr>
      <tr><td>运动营养</td><td>玉溪、湖南体职院、重庆医专等验证体成分、运动设备和营养系统的数据接入。</td></tr></table>

      <h2>4. 产品策略：从项目功能转为客户可以买的产品</h2>
      <img src="{image_data_uri(assets['five_plus_two'])}"><p class="caption">AI营养智慧食堂5+2产品表达。产品表达不替代项目验收。</p>
      <table><tr><th>产品主线</th><th>8月结果</th><th>客户价值</th></tr>
      <tr><td>智慧营养进入高频用餐</td><td>营养标签、用餐反馈、称重和AI识别进入前厅设备、移动端和管理端。</td><td>同时改善用餐效率和营养管理。</td></tr>
      <tr><td>标准团餐产品成型</td><td>营养结算、食安、进销存和硬件开始按政企版、学校版组合。</td><td>销售能讲清范围，交付能按清单实施。</td></tr>
      <tr><td>项目经验变成资料</td><td>接口、设备、部署、测试和操作经验整理为方案、配置表和检查表。</td><td>下一个同类项目减少重复设计。</td></tr></table>

      <h2>5. 硬件产品：北京三区需求高度重复</h2>
      <img src="{image_data_uri(assets['plan_status'])}"><p class="caption">北京三区公开项目清单与应标差距判断。</p>
      <table><tr><th>硬件组合</th><th>主要内容</th></tr>
      <tr><td>现场作业与食安终端</td><td>管理一体机、智能验货机、库管手持机、智能留样仪。</td></tr>
      <tr><td>食安物联监测与控制</td><td>温湿度、中心温度计、广温监测、智能开关和网关。</td></tr>
      <tr><td>视频监控与AI预警</td><td>摄像机、监控存储、交换设备和算法终端。</td></tr>
      <tr><td>网络与安装配套</td><td>机柜、网线、电源线、线管、插排、配线架等。</td></tr>
      <tr><td>智能烹饪</td><td>20层万能蒸烤箱及配套推车；最终型号、授权和服务边界仍需确认。</td></tr></table>
      <p class="caption">191所/校区表示公开项目清单覆盖范围，不表示康比特中标规模或市场份额。</p>

      <h2>6. 三级等保：从材料准备进入验证阶段</h2>
      <img src="{image_data_uri(assets['mlps'])}"><p class="caption">三级等保备案进度。最终结果以主管机关和测评机构为准。</p>
      <table><tr><th>已完成</th><th>正在推进</th><th>最终判断</th></tr><tr><td>备案材料提交；材料清单、采集模板和缺项检查工具形成</td><td>环境部署、自测、扫描、整改和复测准备</td><td>取得正式回读前，不写成已通过三级等保</td></tr></table>

      <div class="part orange">PART 02｜偏差与问题</div>
      <div class="callout warn"><b>问题一：</b>产品和项目验收速度仍需加快。部分成果已经完成开发或联调，但连续运行、客户确认和验收资料没有同步收口。</div>
      <div class="callout"><b>问题二：</b>硬件和安全节点受外部条件影响。厂家参数、检测报告、授权以及等保扫描和现场检查时间需要前置锁定。</div>

      <div class="part">PART 03｜9月份怎么干</div>
      <h2>9月不再铺任务，先把两套产品做成</h2>
      <img src="{image_data_uri(assets['september'])}"><p class="caption">9月工作思路：产品成型—项目反校—硬件封装—安全验证。</p>
      <p><b>第一步，先把政企版和学校版的范围定下来。</b>没有统一产品清单，项目越多，重复设计越多。</p>
      <p><b>第二步，用项目反校产品。</b>每条产品线至少选两个项目，核对功能、配置、接口、硬件和验收方式。</p>
      <p><b>第三步，把硬件和安全材料补齐。</b>投标和交付需要正式规格、检测材料、部署条件和验收依据，不能等到最后再找。</p>

      <h2>1. 两套标准产品包</h2>
      <table><tr><th>产品包</th><th>封装内容</th><th>月底结果</th></tr>
      <tr><td>政企版</td><td>营养结算、食安、进销存、组织接口、部署方式、硬件组合、实施资料和验收标准。</td><td>产品、销售、交付使用同一份清单；外部接口单独报价和排期。</td></tr>
      <tr><td>学校版</td><td>阳光消费、校园食安、营养管理、家校公示、主管部门监管、院校实训和硬件组合。</td><td>普通学校与体育院校的产品边界分开；形成学校版演示和验收材料。</td></tr></table>

      <h2>2. 四条产品线样板</h2>
      <table><tr><th>产品线</th><th>项目反校范围</th><th>形成资料</th></tr>
      <tr><td>政企智慧营养餐厅</td><td>国信、城市副中心、金斯瑞、四方达、江西206等至少选2个</td><td>标准功能、配置项、设备组合和验收清单</td></tr>
      <tr><td>政企食安与进销存</td><td>国信、三全、中央网信办、海开等至少选2个</td><td>采购验收、库存、食安和组织接口清单</td></tr>
      <tr><td>校园营养结算与阳光消费</td><td>滨州、首通智城、人大附中、中央美院、大兴三校等至少选2个</td><td>多校组织、经费、缴费、结算和信息发布清单</td></tr>
      <tr><td>校园食安与运动营养</td><td>保康三中、玉溪、湖南体职院、重庆医专等至少选2个</td><td>食安设备、营养分析、体成分和实训清单</td></tr></table>

      <h2>3. 硬件与安全收口</h2>
      <table><tr><th>工作</th><th>9月动作</th><th>月底结果</th></tr>
      <tr><td>五类硬件组合</td><td>形成配置表、兼容清单和验收标准。</td><td>校园餐硬件可以按学校数量和场景快速配置。</td></tr>
      <tr><td>13项应标材料</td><td>补规格、检测报告、厂家授权、接口说明和样机记录。</td><td>已有产品和候选方案能够直接组织材料。</td></tr>
      <tr><td>蒸烤箱与推车</td><td>锁定厂家、型号、BOM、报价、授权和服务边界。</td><td>关闭两项硬件缺口。</td></tr>
      <tr><td>三级等保</td><td>完成部署、自测、扫描、整改、复测和现场节点确认。</td><td>形成一条可以在下一个项目复用的安全验证路径。</td></tr></table>

      <div class="lead"><b>9月总结：</b>9月的重点不是再增加多少功能，而是把政企版和学校版做成，把项目经验收回来，把硬件和安全材料补齐。做到这三点，销售能卖、项目能干、客户能验收。</div>
      <div class="footer">数据口径：截至2026-08-22。项目分类依据截至8月15日的项目总表与标签快照，后续以最新项目台账复核。内部管理及业务交流材料。</div>
    </div>
    """
    OUTPUT_HTML.write_text(f"<!doctype html><html lang='zh-CN'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>{html.escape(REPORT_STEM)}</title><style>{css}</style></head><body>{body}</body></html>", encoding="utf-8")


def main():
    ASSET_DIR.mkdir(parents=True, exist_ok=True)
    assets = {
        "collaboration": ASSET_DIR / "project-classification-overview.png",
        "plan_status": ASSET_DIR / "beijing-hardware-market-overview.png",
        "september": ASSET_DIR / "september-strategy-map.png",
        "dashboard": ASSET_DIR / "project-operation-dashboard.png",
        "project_cockpit": ASSET_DIR / "delivery-project-cockpit.png",
        "hardware": ASSET_DIR / "hardware-plan.png",
        "mlps": ASSET_DIR / "mlps-progress.png",
        "five_plus_two": ASSET_DIR / "five-plus-two-cover.png",
        "campus_product": ASSET_DIR / "campus-product-page.png",
    }
    create_collaboration_overview(assets["collaboration"])
    create_plan_status(assets["plan_status"])
    create_september_roadmap(assets["september"])
    source_map = {
        "dashboard": "project-operation-dashboard.png",
        "project_cockpit": "delivery-project-cockpit.png",
        "hardware": "hardware-plan.png",
        "mlps": "mlps-progress.png",
        "five_plus_two": "five-plus-two-cover.png",
        "campus_product": "campus-product-page.png",
    }
    for key, name in source_map.items():
        shutil.copy2(SOURCE_IMAGE_DIR / name, assets[key])
    build_docx_external(assets)
    build_html_external(assets)
    print(OUTPUT_DOCX)
    print(OUTPUT_HTML)


if __name__ == "__main__":
    main()
