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_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 Inches, Pt, RGBColor


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "报告" / "精度保持性评估完整运行逻辑与案例说明.docx"
WHOLE_FIG = ROOT / "报告" / "图表" / "整机精度保持性综合评价流程图.png"
PART_FIG = ROOT / "报告" / "图表" / "部件精度保持性评估流程图.png"

NAVY = "17375E"
BLUE = "2E74B5"
CYAN = "DDEBF7"
PALE_BLUE = "EEF5FC"
GREEN = "E2F0D9"
ORANGE = "FCE4D6"
RED = "F4CCCC"
GRAY = "F2F4F7"
MID_GRAY = "66758A"
WHITE = "FFFFFF"
BLACK = "1F2937"
FONT = "PingFang SC"


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_border(cell, color="D7E0EA", size="6"):
    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}"
        element = borders.find(qn(tag))
        if element is None:
            element = OxmlElement(tag)
            borders.append(element)
        element.set(qn("w:val"), "single")
        element.set(qn("w:sz"), size)
        element.set(qn("w:color"), color)


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 margin, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
        node = tc_mar.find(qn(f"w:{margin}"))
        if node is None:
            node = OxmlElement(f"w:{margin}")
            tc_mar.append(node)
        node.set(qn("w:w"), str(value))
        node.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 keep_with_next(paragraph):
    paragraph.paragraph_format.keep_with_next = True


def set_run(run, size=10.5, bold=False, color=BLACK, italic=False):
    run.font.name = FONT
    run._element.get_or_add_rPr().rFonts.set(qn("w:eastAsia"), FONT)
    run.font.size = Pt(size)
    run.font.bold = bold
    run.font.italic = italic
    run.font.color.rgb = RGBColor.from_string(color)


def add_text(doc, text, size=10.5, bold=False, color=BLACK, align=None, after=6, before=0):
    p = doc.add_paragraph()
    p.paragraph_format.space_before = Pt(before)
    p.paragraph_format.space_after = Pt(after)
    p.paragraph_format.line_spacing = 1.25
    if align is not None:
        p.alignment = align
    set_run(p.add_run(text), size=size, bold=bold, color=color)
    return p


def add_bullet(doc, text):
    p = doc.add_paragraph(style="List Bullet")
    p.paragraph_format.space_after = Pt(4)
    p.paragraph_format.line_spacing = 1.2
    set_run(p.add_run(text), size=10.3)
    return p


def add_number(doc, text):
    p = doc.add_paragraph(style="List Number")
    p.paragraph_format.space_after = Pt(4)
    p.paragraph_format.line_spacing = 1.2
    set_run(p.add_run(text), size=10.3)
    return p


def add_heading(doc, text, level=1):
    p = doc.add_paragraph(style=f"Heading {level}")
    keep_with_next(p)
    p.paragraph_format.space_before = Pt({1: 16, 2: 12, 3: 8}[level])
    p.paragraph_format.space_after = Pt({1: 8, 2: 6, 3: 4}[level])
    run = p.add_run(text)
    set_run(run, size={1: 16, 2: 13, 3: 11.5}[level], bold=True, color=BLUE if level < 3 else NAVY)
    return p


def add_callout(doc, title, body, fill=PALE_BLUE, title_color=NAVY):
    table = doc.add_table(rows=1, cols=1)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    cell = table.cell(0, 0)
    cell.width = Inches(6.35)
    set_cell_shading(cell, fill)
    set_cell_border(cell, color=fill)
    set_cell_margins(cell, top=150, start=180, bottom=150, end=180)
    p = cell.paragraphs[0]
    p.paragraph_format.space_after = Pt(4)
    set_run(p.add_run(title), size=11, 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(p2.add_run(body), size=10.3, color=BLACK)
    doc.add_paragraph().paragraph_format.space_after = Pt(1)


def add_table(doc, headers, rows, widths=None, header_fill=NAVY, font_size=9.2):
    table = doc.add_table(rows=1, cols=len(headers))
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    header = table.rows[0]
    set_repeat_table_header(header)
    for i, text in enumerate(headers):
        cell = header.cells[i]
        if widths:
            cell.width = Inches(widths[i])
        set_cell_shading(cell, header_fill)
        set_cell_border(cell)
        set_cell_margins(cell)
        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(p.add_run(str(text)), size=font_size, bold=True, color=WHITE)
    for row_idx, values in enumerate(rows):
        cells = table.add_row().cells
        for i, value in enumerate(values):
            cell = cells[i]
            if widths:
                cell.width = Inches(widths[i])
            set_cell_shading(cell, WHITE if row_idx % 2 == 0 else "F8FAFC")
            set_cell_border(cell)
            set_cell_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.12
            set_run(p.add_run(str(value)), size=font_size)
    doc.add_paragraph().paragraph_format.space_after = Pt(2)
    return table


def add_figure(doc, image_path, caption):
    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    p.paragraph_format.keep_with_next = True
    p.add_run().add_picture(str(image_path), width=Inches(6.5))
    cp = doc.add_paragraph()
    cp.alignment = WD_ALIGN_PARAGRAPH.CENTER
    cp.paragraph_format.space_after = Pt(8)
    set_run(cp.add_run(caption), size=9.5, color=MID_GRAY, italic=True)


def add_page_break(doc):
    doc.add_page_break()


def configure_document():
    doc = Document()
    section = doc.sections[0]
    section.page_width = Inches(8.5)
    section.page_height = Inches(11)
    section.top_margin = Inches(0.78)
    section.bottom_margin = Inches(0.72)
    section.left_margin = Inches(0.85)
    section.right_margin = Inches(0.85)
    section.header_distance = Inches(0.35)
    section.footer_distance = Inches(0.35)

    styles = doc.styles
    normal = styles["Normal"]
    normal.font.name = FONT
    normal._element.rPr.rFonts.set(qn("w:eastAsia"), FONT)
    normal.font.size = Pt(10.5)
    normal.paragraph_format.space_after = Pt(6)
    normal.paragraph_format.line_spacing = 1.25

    for style_name in ("List Bullet", "List Number"):
        style = styles[style_name]
        style.font.name = FONT
        style._element.rPr.rFonts.set(qn("w:eastAsia"), FONT)
        style.font.size = Pt(10.3)

    header = section.header
    hp = header.paragraphs[0]
    hp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
    set_run(hp.add_run("数控机床精度保持性综合评价系统 | 运行逻辑说明"), size=8.5, color=MID_GRAY)

    footer = section.footer
    fp = footer.paragraphs[0]
    fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
    set_run(fp.add_run("依据《说明书》及《车削类机床精度保持性验证评价规范20260320》"), size=8.2, color=MID_GRAY)
    return doc


def build():
    doc = configure_document()

    add_text(doc, "ACCURACY RETENTION EVALUATION", size=10, bold=True, color=BLUE, align=WD_ALIGN_PARAGRAPH.CENTER, after=16, before=55)
    add_text(doc, "精度保持性评估完整运行逻辑与案例说明", size=23, bold=True, color=NAVY, align=WD_ALIGN_PARAGRAPH.CENTER, after=8)
    add_text(doc, "整机综合评价 + 功能部件评价", size=14, color=BLUE, align=WD_ALIGN_PARAGRAPH.CENTER, after=24)
    add_callout(
        doc,
        "文档用途",
        "用于软件演示、项目汇报和用户培训。重点回答：系统具体录入什么数据；参数进入哪一个公式；公式输出什么中间量；中间量如何归一化、赋权和逐层汇总；最终页面展示什么结果。",
        fill=CYAN,
    )
    add_text(doc, "版本日期：2026年6月15日", size=10, color=MID_GRAY, align=WD_ALIGN_PARAGRAPH.CENTER, after=6, before=18)
    add_text(doc, "适用软件：数控机床中试验证阶段精度保持性综合评价系统", size=10, color=MID_GRAY, align=WD_ALIGN_PARAGRAPH.CENTER, after=24)
    add_text(doc, "参考文件", size=11, bold=True, color=NAVY, after=6)
    add_bullet(doc, "《一种数控机床整机精度保持性多层次、多维度综合评价方法》（项目文件：说明书.docx）")
    add_bullet(doc, "《车削类机床精度保持性验证评价规范20260320》")
    add_bullet(doc, "软件当前数据库配置、测试记录、制品样本和后端评价算法")

    add_page_break(doc)
    add_heading(doc, "1. 一句话先讲清楚", 1)
    add_callout(
        doc,
        "整机评估",
        "把几何精度、位置精度的长期退化序列，以及加工制品的批次样本，分别计算成五类评价量，再统一转成百分制，通过动态组合权重形成单项得分，最后按“指标层→准则层→目标层”逐层汇总为整机综合得分。",
        fill=GREEN,
    )
    add_callout(
        doc,
        "部件评估",
        "每个加载循环后记录部件精度，先判断是否有任一指标超过失效阈值：已失效则回答“能保持多久”；未失效则回答“保持得有多好”。",
        fill=ORANGE,
    )

    add_heading(doc, "1.1 权重到底是输入还是输出？", 2)
    add_table(
        doc,
        ["权重名称", "性质", "来源", "进入哪一步", "是否在结果中输出"],
        [
            ["评价维度主观权重 u_j", "输入", "规范/AHP专家判断的已验证结果；软件预置几何/位置为0.25、0.50、0.25，加工为0.50、0.50", "与客观权重融合", "是，指标明细中返回"],
            ["评价维度客观权重 v_j", "计算输出", "由本次各维度百分制得分占比动态计算", "与主观权重融合", "是"],
            ["评价维度组合权重 W_j", "计算输出", "W_j=√(u_jv_j)/Σ√(u_jv_j)", "计算单个精度指标综合分", "是"],
            ["指标层权重 W_Ci", "配置输入", "设备台账配置；缺失时按机型和规范推荐表匹配", "指标分→准则层分", "是，归一化后输出"],
            ["准则层权重 W_Bk", "配置输入", "设备台账配置；默认几何0.25、位置0.25、加工0.50", "准则层分→整机总分", "是"],
        ],
        widths=[1.1, 0.75, 2.15, 1.25, 1.1],
        font_size=8.5,
    )
    add_callout(
        doc,
        "演示时建议这样说",
        "不是所有权重都由用户手工填写。管理员配置的是“各精度指标在同类中的重要程度”和“三大精度集的重要程度”；系统根据本次数据表现自动计算客观权重和最终组合权重。",
        fill=CYAN,
    )

    add_heading(doc, "1.2 软件中的三类参数", 2)
    add_table(
        doc,
        ["类型", "典型内容", "维护位置", "作用"],
        [
            ["管理员配置", "指标绑定、δ0、δth、USL/LSL、指标层权重、准则层权重、单循环小时", "设备台账；加工公差还来自制品指标配置", "定义评价对象、边界和汇总关系"],
            ["操作者录入", "周期检测值、检测时间、制品批次、样本值、循环试验记录", "数据采集/制品数据录入", "形成算法的动态实测输入"],
            ["系统固定", "ARDR/ARD/AFD/ACD/AD公式、正逆向归一化、客观赋权、拉格朗日融合、逐层汇总", "后端算法", "保证相同数据和配置得到相同结果"],
        ],
        widths=[1.0, 2.4, 1.55, 1.55],
        font_size=9,
    )

    add_page_break(doc)
    add_heading(doc, "2. 整机精度保持性综合评价", 1)
    add_text(doc, "整机评价采用目标层—准则层—指标层三层体系。目标层是整机综合得分；准则层是几何精度、位置精度和加工精度；指标层是工作台平面度、定位精度、外圆圆度等具体指标。")
    add_figure(doc, WHOLE_FIG, "图1 整机精度保持性综合评价完整数据流（Image 2生成）")

    add_heading(doc, "2.1 整机评价需要哪些输入", 2)
    add_table(
        doc,
        ["输入组", "具体字段/数据", "数据库或页面来源", "后续用途"],
        [
            ["资产与体系", "资产编号、机床类型、指标分类、指标名称", "设备台账、指标字典", "建立目标层—准则层—指标层关系，并匹配机型推荐权重"],
            ["几何/位置边界", "出厂初始精度δ0、失效精度δth、单位", "设备台账→配置指标", "ARD计算、状态判定"],
            ["加工公差", "USL、LSL、单位", "制品指标配置；设备与加工指标绑定", "计算T=|USL-LSL|和M=(USL+LSL)/2"],
            ["归一化边界", "理想值Ideal、极限值Limit", "设备台账可选；留空使用系统规范默认值", "把不同量纲的评价量统一映射到0~100分"],
            ["层级权重", "指标层权重W_Ci、准则层权重W_Bk", "设备台账→配置指标", "指标层向准则层、目标层汇总"],
            ["跟踪检测数据", "检测时间t_i、指标实测值δ_i；同一时点多次采样先取均值", "数据采集→整机精度检测", "拟合A(t)，计算ARDR、ARD、AFD"],
            ["加工制品数据", "产品、批次、加工数量、同一加工指标的样本X_1…X_m", "设备台账绑定制品→制品数据录入", "计算ACD、AD；当前算法使用最新批次样本"],
        ],
        widths=[1.0, 2.25, 1.65, 1.6],
        font_size=8.7,
    )

    add_heading(doc, "2.2 从输入到最终得分的逐步链路", 2)
    add_table(
        doc,
        ["环节", "输入", "处理/公式", "本环节输出", "输出进入下一环节"],
        [
            ["A. 数据预处理", "同一指标的(t_i,δ_i)或同一时点多次测量", "同一时点多次采样取均值；按时间升序；时间换算为相对天数", "每个指标的有序退化序列", "几何/位置三维特征计算"],
            ["B1. 退化函数", "相对天数t_i、δ_i", "最小二乘直线A(t)=at+b", "斜率a、拟合值A(t_i)", "ARDR直接取|a|；AFD使用拟合残差"],
            ["B2. ARDR", "拟合斜率a", "ARDR=|dA(t)/dt|=|a|", "精度退化速率", "逆向模糊归一化"],
            ["B3. ARD", "δ_i、δ0、δth", "Δδ_mean=(1/n)Σ|δ_i-δ0|；ARD=ρ·exp[-Δδ_mean/(δth-δ0)]", "精度保持度", "正向模糊归一化"],
            ["B4. AFD", "δ_i、A(t_i)", "AFD=√[(1/(n-1))Σ(δ_i-A(t_i))²]", "精度波动度", "逆向模糊归一化"],
            ["C1. ACD", "最新制品批次X_i、USL、LSL", "样本标准差s；T=|USL-LSL|；ACD=T/(6s)", "加工精度一致性", "正向模糊归一化"],
            ["C2. AD", "X_i、USL、LSL", "X̄；M=(USL+LSL)/2；AD=2|X̄-M|/T", "精度偏离度", "逆向模糊归一化"],
            ["D. 百分制归一化", "ARDR/ARD/AFD或ACD/AD、Ideal、Limit", "正向：(Current-Limit)/(Ideal-Limit)×100；逆向：(Limit-Current)/(Limit-Ideal)×100；结果截断到0~100", "每个维度Score_j", "客观权重和组合权重"],
            ["E1. 客观权重", "同一指标下各Score_j", "v_j=Score_j/ΣScore_j", "本次数据驱动的客观权重v_j", "拉格朗日融合"],
            ["E2. 组合权重", "主观权重u_j、客观权重v_j", "W_j=√(u_jv_j)/Σ√(u_jv_j)", "动态组合权重W_j", "单指标综合分"],
            ["E3. 单指标得分", "Score_j、W_j", "S_i=Σ(W_j×Score_j)", "每个具体精度指标得分S_i", "准则层汇总"],
            ["F1. 准则层", "同一精度集内S_i、指标层权重W_Ci", "B_k=Σ(S_i×归一化W_Ci)", "几何/位置/加工分项得分", "目标层汇总"],
            ["F2. 目标层", "B_k、准则层权重W_Bk", "S=Σ(B_k×归一化W_Bk)", "整机综合得分", "页面展示、薄弱环节识别"],
        ],
        widths=[0.8, 1.35, 2.65, 1.05, 1.1],
        font_size=7.8,
    )

    add_heading(doc, "2.3 归一化默认边界", 2)
    add_table(
        doc,
        ["评价量", "方向", "系统默认Ideal", "系统默认Limit/Extreme", "解释"],
        [
            ["ARDR", "逆向，越小越好", "0", "0.1", "退化速度越接近0越好"],
            ["ARD", "正向，越大越好", "1", "0", "完全保持初始精度时接近1"],
            ["AFD", "逆向，越小越好", "0", "1", "拟合残差越小越稳定"],
            ["ACD", "正向，越大越好", "1", "0.5", "公差带相对样本离散程度越大越好"],
            ["AD", "逆向，越小越好", "0", "1", "均值越接近公差中心越好"],
        ],
        widths=[0.9, 1.15, 1.1, 1.35, 2.0],
        font_size=9,
    )
    add_callout(
        doc,
        "重要说明",
        "设备台账中的“归一化理想值/极限值”留空时，系统使用上表规范默认值；填写后则覆盖默认值。这里的Ideal和Limit不是原始精度的δ0、δth，而是ARDR、ARD、AFD、ACD、AD这些评价量的评分边界。",
        fill=ORANGE,
    )

    add_page_break(doc)
    add_heading(doc, "2.4 整机案例：重型立式车床整机样机", 2)
    add_text(doc, "资产：WHOLE-LATHE-001；机型：立式数控车床。当前数据库配置9项有效指标，10期跟踪检测记录，并绑定阀套试件的10个制品批次。算法回归结果完整，缺失项为0。")

    add_heading(doc, "2.4.1 几何精度案例：工作台平面度", 3)
    add_table(
        doc,
        ["输入项", "案例值"],
        [
            ["设备台账配置", "δ0=6 μm；δth=18 μm；指标层原始权重=0.1101；几何精度准则层权重=0.25"],
            ["10期实测序列（μm）", "6.3, 6.7, 7.1, 7.5, 7.9, 8.4, 8.8, 9.2, 9.7, 10.1"],
            ["检测间隔", "2026-01-10至2026-07-09，约每20天一次"],
        ],
        widths=[1.6, 4.9],
        font_size=9.2,
    )
    add_table(
        doc,
        ["步骤", "计算输出", "如何进入下一步"],
        [
            ["最小二乘拟合", "ARDR=0.021242 μm/day；拟合曲线同时给出各时点A(t_i)", "ARDR进入逆向评分；A(t_i)与实测值共同计算AFD"],
            ["保持能力", "ARD=0.834574", "进入正向评分"],
            ["波动程度", "AFD=0.033434 μm", "进入逆向评分"],
            ["模糊归一化", "ARDR 78.76分；ARD 83.46分；AFD 96.66分", "形成客观权重v_j"],
            ["动态组合赋权", "W_ARDR=0.2806；W_ARD=0.4085；W_AFD=0.3109", "与三个维度得分加权"],
            ["单指标综合", "工作台平面度得分=86.24分", "乘几何精度内归一化指标权重0.1616，进入几何精度准则层"],
        ],
        widths=[1.2, 2.45, 2.85],
        font_size=9,
    )
    add_text(doc, "讲解链条：10期实测值 + δ0/δth → 拟合和三维特征 → 三个百分制得分 → 动态组合权重 → 工作台平面度86.24分 → 几何精度分项。", bold=True, color=NAVY)

    add_heading(doc, "2.4.2 加工精度案例：外圆圆度", 3)
    add_table(
        doc,
        ["输入项", "案例值"],
        [
            ["产品与批次", "阀套试件；批次PRD-VC-001-20260709"],
            ["公差配置", "USL=0.03 mm；LSL=0 mm；T=0.03 mm；M=0.015 mm"],
            ["最新批次5个样本", "0.015, 0.016, 0.014, 0.016, 0.015 mm"],
        ],
        widths=[1.6, 4.9],
        font_size=9.2,
    )
    add_table(
        doc,
        ["步骤", "计算输出", "如何进入下一步"],
        [
            ["样本统计", "样本均值X̄=0.0152 mm，样本标准差由5个样本计算", "与T、M共同进入ACD和AD"],
            ["二维评价量", "ACD=5.976143；AD=0.013333", "分别进行正向、逆向归一化"],
            ["百分制评分", "ACD=100分；AD=98.67分", "形成客观权重"],
            ["动态组合赋权", "W_ACD=0.5017；W_AD=0.4983", "计算外圆圆度综合分"],
            ["单指标综合", "外圆圆度=99.34分", "按加工精度内指标权重进入加工精度准则层"],
        ],
        widths=[1.2, 2.45, 2.85],
        font_size=9,
    )

    add_heading(doc, "2.4.3 逐层汇总结果", 3)
    add_table(
        doc,
        ["准则层", "准则层得分B_k", "准则层权重W_Bk", "对整机总分的贡献"],
        [
            ["几何精度", "82.11", "0.25", "20.5275"],
            ["位置精度", "75.69", "0.25", "18.9225"],
            ["加工精度", "96.04", "0.50", "48.0200"],
            ["整机目标层", "87.47", "1.00", "82.11×0.25 + 75.69×0.25 + 96.04×0.50"],
        ],
        widths=[1.25, 1.45, 1.45, 2.35],
        font_size=9.2,
    )
    add_callout(
        doc,
        "案例结论",
        "整机综合得分87.47分，数据覆盖率100%，有效指标9项。加工精度96.04分表现最好；位置精度75.69分相对较低，其中垂直刀架Z1轴定位精度68.91分，是应优先关注的薄弱方向。",
        fill=GREEN,
    )

    add_heading(doc, "3. 功能部件精度保持性评价", 1)
    add_text(doc, "部件评价适用于刀架、主轴、回转台、刀库和铣头等。它不采用整机的三层加权总分流程，而是先执行失效判定，再在“保持时间”和“保持度”之间选择评价方法。")
    add_figure(doc, PART_FIG, "图2 功能部件精度保持性评价分支逻辑（Image 2生成）")

    add_heading(doc, "3.1 部件评价需要哪些输入", 2)
    add_table(
        doc,
        ["输入组", "具体字段/数据", "来源", "用途"],
        [
            ["部件资产", "部件编号、名称、类型（主轴/刀架/回转台/刀库/铣头）", "设备台账", "确定被评对象和应绑定的精度指标"],
            ["指标边界", "每项指标出厂标准值δ0、失效阈值δth、单位", "设备台账→配置指标", "失效判定和ARD计算"],
            ["循环参数", "单循环小时h_cycle；试验计划预设循环次数N", "设备台账；试验方案", "把失效循环换算为保持时间；N控制试验终止边界"],
            ["循环记录", "循环后的检测时间t_i、循环次序i、各指标实测值δ_i", "数据采集", "逐指标失效判定和保持度计算"],
        ],
        widths=[1.1, 2.55, 1.35, 1.5],
        font_size=9,
    )
    add_callout(
        doc,
        "关于预设循环次数N",
        "规范要求试验方案预设固定循环次数。当前软件由“已经录入的循环记录范围”体现试验终止边界，尚未把N作为独立数据库字段参与公式；演示时可将N解释为试验计划参数。单循环小时已作为独立配置并实际用于保持时间换算。",
        fill=ORANGE,
    )

    add_heading(doc, "3.2 部件评价逐步链路", 2)
    add_table(
        doc,
        ["环节", "输入", "判断/公式", "输出", "后续"],
        [
            ["A. 完整性检查", "绑定指标、δ0、δth、至少2期有效记录", "缺失任一关键项则标记缺失，不给出完整评价", "可评价指标集合、缺失清单", "逐项失效判定"],
            ["B. 失效判定", "每项δ_i与δth", "按时间顺序寻找首次δ_i>δth", "是否失效、首次失效循环i_f、失效时间t_f", "决定保持时间或保持度分支"],
            ["C1. 已失效分支", "i_f、h_cycle；若未配置h_cycle则使用t_f-t_0", "T_r=i_f×h_cycle；回退公式T_r=t_f-t_0", "该指标保持时间、失效循环", "部件整体取最早失效指标的保持时间"],
            ["C2. 未失效分支", "全周期δ_i、δ0、δth", "Δδ_mean=(1/n)Σ|δ_i-δ0|；ARD=ρ·exp[-Δδ_mean/(δth-δ0)]", "单指标保持度ARD及百分制ARD×100", "多个有效指标得分求平均"],
            ["D. 结果呈现", "分支结果、最新值、阈值、缺失项", "按分支组织输出", "保持时间或综合得分、指标明细、状态、完整性", "页面展示"],
        ],
        widths=[1.0, 1.65, 2.6, 1.2, 1.05],
        font_size=8.5,
    )

    add_heading(doc, "3.3 部件案例一：精密主轴A样件（未失效）", 2)
    add_table(
        doc,
        ["指标", "δ0", "δth", "最新值", "判定", "保持度", "得分"],
        [
            ["主轴径向跳动", "3.0 μm", "10.0 μm", "5.1 μm", "未失效", "0.864816", "86.48"],
            ["主轴端面跳动", "2.5 μm", "8.0 μm", "4.3 μm", "未失效", "0.846482", "84.65"],
            ["部件综合", "-", "-", "-", "保持度评价", "两项平均", "85.56"],
        ],
        widths=[1.45, 0.8, 0.8, 0.9, 1.0, 0.95, 0.75],
        font_size=9,
    )
    add_text(doc, "讲解链条：各循环实测值均未超过δth → 对每项计算Δδ_mean和ARD → ARD×100形成单项分 → 两项平均得到部件综合得分85.56分。", bold=True, color=NAVY)

    add_heading(doc, "3.4 部件案例二：立式刀架B样件（已失效）", 2)
    add_table(
        doc,
        ["输入/事件", "案例值", "进入的计算"],
        [
            ["失效指标", "刀架Z1轴定位精度", "该指标决定部件切换到保持时间评价"],
            ["边界", "δ0=5.0 μm；δth=14.0 μm", "逐循环比较δ_i>14.0"],
            ["首次失效", "第4个加载循环后超过阈值；最新值15.2 μm", "i_f=4"],
            ["单循环小时", "h_cycle=8小时", "T_r=i_f×h_cycle"],
            ["保持时间", "4×8=32小时", "部件整体精度保持时间=32小时"],
            ["另一未失效指标", "Z1轴重复定位精度保持度得分72.86", "保留在明细中，但整体评价方法仍以最先失效指标为准"],
        ],
        widths=[1.35, 2.45, 2.7],
        font_size=9,
    )
    add_callout(
        doc,
        "为什么不是再给一个平均分？",
        "规范规定，功能部件任一关键精度指标超过设计允许值即可判为精度失效。此时最有意义的结果是“有效精度维持了多久”，所以整体输出32小时，而不是把已失效指标与未失效指标继续平均成一个分数。",
        fill=RED,
    )

    add_page_break(doc)
    add_heading(doc, "4. 软件页面与数据存储的对应关系", 1)
    add_table(
        doc,
        ["业务环节", "用户看到的页面", "主要持久化数据", "评价时读取"],
        [
            ["建设对象", "设备台账", "整机/部件资产、机型、资产编码", "选择评估对象和机型分类"],
            ["配置整机指标", "设备台账→配置指标", "绑定指标、δ0、δth、权重、归一化边界、单位", "整机特征、评分和逐层汇总"],
            ["配置加工制品", "设备台账→绑定制品；参数设置→制品指标", "产品定义、资产产品绑定、加工指标USL/LSL", "加工ACD/AD"],
            ["录入整机检测", "数据采集", "检测时间、指标值或多次样本", "几何/位置退化序列"],
            ["录入制品样本", "制品数据录入", "产品批次、生产数量、各加工指标样本值", "最新批次ACD/AD"],
            ["录入部件循环数据", "数据采集", "循环后的检测时间与各指标值", "失效判定、保持时间/保持度"],
            ["查看结果", "数据分析", "当前版本实时计算，不依赖旧结果快照", "总分/保持时间、分项、明细、完整性"],
        ],
        widths=[1.1, 1.55, 2.15, 1.7],
        font_size=8.7,
    )

    add_heading(doc, "4.1 配置字段的业务含义", 2)
    add_table(
        doc,
        ["字段", "适用对象", "含义", "是否操作者日常修改"],
        [
            ["δ0 出厂/标准值", "几何、位置、部件", "交付或试验开始时的基准精度", "否，由管理员依据验收/出厂资料配置"],
            ["δth 失效阈值", "几何、位置、部件", "超过后判定精度失效的边界", "否，由管理员依据设计允许值配置"],
            ["USL/LSL", "加工精度", "加工产品指标的公差上下限", "否，由管理员依据产品图纸/工艺标准配置"],
            ["Ideal/Extreme", "整机五维评分", "评价量转百分制的理想和极限边界", "通常不改；留空使用规范值"],
            ["指标层权重", "整机", "某具体精度指标在所属准则层中的重要度", "管理员可按机型和任务调整"],
            ["准则层权重", "整机", "几何、位置、加工三类对总分的重要度", "管理员可按项目要求调整"],
            ["单循环小时", "部件", "完成一次加载循环对应的有效试验时间", "管理员按试验方案配置"],
        ],
        widths=[1.25, 1.2, 2.75, 1.3],
        font_size=8.8,
    )

    add_heading(doc, "4.2 数据不足时系统为什么不给最终评价", 2)
    add_table(
        doc,
        ["缺失情况", "受影响步骤", "处理方式"],
        [
            ["指标未绑定", "无法建立指标体系", "在设备台账配置指标"],
            ["几何/位置少于2期", "不能拟合A(t)、计算ARDR和AFD", "继续录入周期检测；规范建议长期跟踪，至少2期是算法最低门槛"],
            ["缺δ0或δth", "ARD和失效判定无边界", "设备台账配置出厂值和失效值"],
            ["加工批次样本少于2个", "样本标准差无意义，ACD无法计算", "制品数据录入至少2个样本，建议5个及以上"],
            ["缺USL/LSL", "不能计算T、M、ACD、AD", "在制品指标配置中填写公差上下限"],
            ["缺指标层权重且未匹配推荐表", "准则层无法完整汇总", "设备台账配置权重"],
            ["部件缺单循环小时", "仍可用时间戳差计算保持时间，但不便于按循环讲解", "按试验方案配置单循环小时"],
        ],
        widths=[1.7, 2.15, 2.65],
        font_size=8.8,
    )

    add_page_break(doc)
    add_heading(doc, "5. 对外讲解稿（可直接照此说明）", 1)
    add_heading(doc, "5.1 整机评估讲解稿", 2)
    for text in [
        "第一步，我们先在设备台账建立整机的评价体系。几何精度和位置精度配置出厂初始值、失效值；加工精度则通过绑定的加工制品配置公差上下限。同时配置具体指标和三类精度集的权重。",
        "第二步，系统读取两类实测数据。几何和位置精度读取按时间连续检测的序列；加工精度读取产品批次中的多个样本值，所以加工精度不是直接拿机床测，而是通过机床加工出的产品来评价。",
        "第三步，几何和位置精度经过最小二乘拟合，得到退化速度ARDR、保持能力ARD和波动程度AFD；加工样本得到一致性ACD和偏离度AD。",
        "第四步，由于五类评价量单位不同，系统利用理想值和极限值统一换算成百分制。保持度和一致性越大越好，其余三项越小越好。",
        "第五步，规范给出的主观权重与本次数据计算出的客观权重进行融合，得到动态组合权重，再计算每一个具体精度指标的综合分。",
        "第六步，具体指标按指标层权重汇总成几何、位置、加工三个分项，再按准则层权重汇总成整机综合得分。页面同时给出分项、明细、雷达图和薄弱环节。",
    ]:
        add_number(doc, text)

    add_heading(doc, "5.2 部件评估讲解稿", 2)
    for text in [
        "部件评估对象包括主轴、刀架、回转台、刀库和铣头。管理员先配置每项精度的出厂标准值、失效阈值和一次循环对应的小时数。",
        "试验过程中，每完成一次空运转或负载循环，就记录各项精度实测值。",
        "系统首先逐项判断是否超过失效阈值。只要任一关键指标首次超限，部件整体就切换为保持时间评价。",
        "保持时间用首次失效循环数乘以单循环小时；如果没有配置单循环小时，系统回退使用失效时间与试验开始时间的时间差。",
        "如果在预设循环范围内所有指标都未失效，则对每项计算精度保持度，转成百分制后求平均，得到部件综合得分。",
        "因此部件模块回答两种不同问题：未失效时回答‘保持得有多好’，已失效时回答‘有效精度保持了多久’。",
    ]:
        add_number(doc, text)

    add_heading(doc, "6. 当前实现与参考文档的对应说明", 1)
    add_table(
        doc,
        ["参考文档要求", "软件实现状态", "说明"],
        [
            ["三层整机评价体系", "已实现", "目标层、几何/位置/加工准则层、具体指标层"],
            ["ARDR、ARD、AFD、ACD、AD", "已实现", "按文档公式计算；ARDR使用拟合斜率绝对值表示退化速度"],
            ["正向/逆向模糊归一化", "已实现", "支持设备级覆盖Ideal/Extreme，留空用默认值"],
            ["AHP主观权重", "以预置结果实现", "系统保存并使用已通过专家/规范确定的u_j，不在每次评价时重新展示判断矩阵和CR计算"],
            ["比重客观权重与拉格朗日组合", "已实现", "v_j和W_j随本次得分动态变化并随指标明细输出"],
            ["指标层、准则层逐层汇总", "已实现", "配置权重会先归一化再参与汇总"],
            ["部件保持时间/保持度分支", "已实现", "任一指标首次失效即整体采用保持时间"],
            ["预设循环次数N", "由试验记录边界体现", "当前未设置独立N字段；不影响已有记录的分支计算，但后续可作为试验计划字段增强"],
            ["评价结果快照", "当前实时计算", "原始数据与配置持久化；评价结果按当前数据库实时重算"],
        ],
        widths=[1.65, 1.25, 3.6],
        font_size=8.7,
    )

    add_page_break(doc)
    add_heading(doc, "7. 结论", 1)
    add_table(
        doc,
        ["对象", "输入", "中间输出", "最终输出"],
        [
            [
                "整机",
                "设备配置、周期检测序列、加工制品样本",
                "ARDR/ARD/AFD/ACD/AD、百分制得分、客观权重、组合权重",
                "单指标得分、三类准则层得分、整机综合得分、薄弱环节",
            ],
            [
                "部件",
                "部件边界参数、循环检测记录",
                "首次失效判定；未失效时计算ARD，已失效时计算失效循环和保持时间",
                "部件综合得分或精度保持时间、指标明细、数据完整性",
            ],
        ],
        widths=[0.75, 1.7, 2.45, 1.6],
        header_fill=NAVY,
        font_size=8.8,
    )

    doc.save(OUT)
    print(OUT)


if __name__ == "__main__":
    build()
