from docx import Document from docx.enum.table import WD_TABLE_ALIGNMENT, WD_CELL_VERTICAL_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 OUT = "大屏数据来源说明-客户版.docx" def shade(cell, fill): tc_pr = cell._tc.get_or_add_tcPr() shd = tc_pr.find(qn("w:shd")) if shd is None: shd = OxmlElement("w:shd") tc_pr.append(shd) shd.set(qn("w:fill"), fill) def set_east_asia_font(run, font_name="Microsoft YaHei"): run.font.name = "Calibri" r_pr = run._element.get_or_add_rPr() r_fonts = r_pr.rFonts if r_fonts is None: r_fonts = OxmlElement("w:rFonts") r_pr.append(r_fonts) r_fonts.set(qn("w:eastAsia"), font_name) def set_table_geometry(table, widths): table.autofit = False table.alignment = WD_TABLE_ALIGNMENT.CENTER tbl_pr = table._tbl.tblPr 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(int(w.inches * 1440) for w in widths))) tbl_w.set(qn("w:type"), "dxa") margins = tbl_pr.find(qn("w:tblCellMar")) if margins is None: margins = OxmlElement("w:tblCellMar") tbl_pr.append(margins) for name, value in (("top", 80), ("bottom", 80), ("start", 120), ("end", 120)): node = margins.find(qn(f"w:{name}")) if node is None: node = OxmlElement(f"w:{name}") margins.append(node) node.set(qn("w:w"), str(value)) node.set(qn("w:type"), "dxa") for row in table.rows: for i, width in enumerate(widths): cell = row.cells[i] cell.width = width cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER tc_pr = cell._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(int(width.inches * 1440))) tc_w.set(qn("w:type"), "dxa") def repeat_header(row): tr_pr = row._tr.get_or_add_trPr() node = OxmlElement("w:tblHeader") node.set(qn("w:val"), "true") tr_pr.append(node) def setup_doc(doc): sec = doc.sections[0] sec.top_margin = Inches(1) sec.bottom_margin = Inches(1) sec.left_margin = Inches(1) sec.right_margin = Inches(1) sec.header_distance = Inches(0.492) sec.footer_distance = Inches(0.492) normal = doc.styles["Normal"] normal.font.name = "Calibri" normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Microsoft YaHei") normal.font.size = Pt(11) normal.paragraph_format.space_after = Pt(6) normal.paragraph_format.line_spacing = 1.10 for name, size, color, before, after in [ ("Heading 1", 16, "2E74B5", 16, 8), ("Heading 2", 13, "2E74B5", 12, 6), ]: style = doc.styles[name] style.font.name = "Calibri" style._element.rPr.rFonts.set(qn("w:eastAsia"), "Microsoft YaHei") style.font.size = Pt(size) style.font.bold = True style.font.color.rgb = RGBColor.from_string(color) style.paragraph_format.space_before = Pt(before) style.paragraph_format.space_after = Pt(after) def add_title(doc): p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER r = p.add_run("MES 大屏数据来源说明") r.bold = True r.font.size = Pt(22) r.font.color.rgb = RGBColor.from_string("0B2545") set_east_asia_font(r) sub = doc.add_paragraph() sub.alignment = WD_ALIGN_PARAGRAPH.CENTER r2 = sub.add_run("客户版:按大屏页面说明每项数据来自哪个业务模块") r2.font.size = Pt(10) r2.font.color.rgb = RGBColor.from_string("5B677A") set_east_asia_font(r2) def add_note(doc, text): table = doc.add_table(rows=1, cols=1) table.style = "Table Grid" cell = table.rows[0].cells[0] cell.text = text shade(cell, "F4F6F9") set_table_geometry(table, [Inches(6.5)]) doc.add_paragraph() def add_table(doc, rows): table = doc.add_table(rows=1, cols=3) table.style = "Table Grid" headers = ["大屏展示内容", "数据来源", "统计说明"] for i, header in enumerate(headers): cell = table.rows[0].cells[i] cell.text = header shade(cell, "F2F4F7") for p in cell.paragraphs: for run in p.runs: run.bold = True for item, source, rule in rows: cells = table.add_row().cells cells[0].text = item cells[1].text = source cells[2].text = rule repeat_header(table.rows[0]) set_table_geometry(table, [Inches(1.75), Inches(2.15), Inches(2.60)]) doc.add_paragraph() def bullet(doc, text): p = doc.add_paragraph(style="List Bullet") p.paragraph_format.left_indent = Inches(0.5) p.paragraph_format.first_line_indent = Inches(-0.25) p.paragraph_format.space_after = Pt(8) p.paragraph_format.line_spacing = 1.167 p.add_run(text) def main(): doc = Document() setup_doc(doc) add_title(doc) add_note(doc, "说明:本文档只说明大屏上各类数据从系统哪个业务模块取得,以及目前页面采用的大致统计口径,不涉及代码、接口和数据库字段。") doc.add_heading("一、MES 综合数据大屏", level=1) add_table(doc, [ ("人员数量", "用户管理 / 员工账号信息", "统计系统中已维护的员工用户数量。"), ("设备数量", "设备管理", "统计系统中已维护的设备总数。"), ("计划产量", "系统配置的大屏计划值", "读取系统配置中的计划产量;部分工位计划会优先读取大屏计划管理中的配置。"), ("实际产量", "产品生产记录", "统计系统中已生成的生产产品数量。页面部分位置展示累计产量,部分趋势图按日期统计。"), ("今日 / 近七日产量", "产品生产记录", "按生产记录的生成时间,分别统计当天、近七天或每小时产量。"), ("小时产量", "产品生产记录", "按小时统计当天产量;综合图表中主要展示 8 点到 18 点的小时产量。"), ("返修数量", "返修管理", "按当天返修记录统计,部分地方按产线和工件编号去重后计算。"), ("报废数量", "报废管理", "统计系统中的报废记录数量。"), ("产线不良率报警", "生产记录 + 返修记录", "按产线比较当天生产数量和返修数量,超过设定比例后在大屏显示报警。"), ("工具剩余寿命", "工具寿命管理", "读取正在使用的工具及其当前剩余寿命。"), ("出库数量", "发运 / 出库记录", "按出库记录中的数量汇总,展示今日、本月或近七日出库情况。"), ("当前工位工件", "工位在制信息", "展示当前各工位正在加工或流转的工件编号。"), ("不良问题统计", "返修问题清单", "按返修问题类型汇总数量,用于显示问题类型分布。"), ("设备报警排行", "设备报警记录", "按报警类型汇总数量,展示报警较多的类型。"), ("设备故障率趋势", "设备管理 + 设备报警记录", "按天统计发生过报警的设备数量,再与设备总数比较得到故障率。"), ("NG 超时提醒", "生产过站 / 检测记录", "查找长时间未处理的 NG 记录,在大屏上做提醒。"), ("工位产能列表", "工艺工位 + 生产过站记录 + 大屏计划", "按工位统计今日完成数量、近 12 小时完成数量,并展示对应计划产能。"), ]) doc.add_heading("二、小时产量看板", level=1) add_table(doc, [ ("HEVXT 上线数量", "HEVXT 产线生产过站记录", "统计 HEVXT 产线当天每小时上线工位的记录数量。"), ("HEVXT 下线数量", "HEVXT 产线生产过站记录", "统计 HEVXT 产线当天每小时下线工位的记录数量。"), ("B13XT 上线数量", "B13XT 产线生产过站记录", "统计 B13XT 产线当天每小时上线工位的记录数量。"), ("B13XT 下线数量", "B13XT 产线生产过站记录", "统计 B13XT 产线当天每小时下线工位的记录数量。"), ]) doc.add_heading("三、质量管理大屏", level=1) add_table(doc, [ ("今日检查数量", "产品检验记录", "统计当天产生的产品检验记录数量。"), ("今日首检数量", "首件检验记录", "统计当天产生的首件检验记录数量。"), ("NG 数量", "NG 记录", "统计当天或近七天的 NG 记录数量。"), ("返修数量", "返修管理", "统计当天或近七天的返修记录数量。"), ("报废数量", "报废管理", "统计当天或近七天的报废记录数量。"), ("待返修数量", "返修管理", "统计当前仍处于待处理状态的返修记录。"), ("质量趋势", "检验、NG、返修、报废记录", "按最近七天分别汇总检查、NG、返修和报废数量。"), ("检查状态分布", "产品检验记录", "按检验状态汇总数量。"), ("检查类型分布", "产品检验记录", "按检验类型汇总数量。"), ("首检状态分布", "首件检验记录", "按首件检验状态汇总数量。"), ("NG 类型分布", "NG 记录", "按 NG 类型汇总数量。"), ("返修状态 / 结果分布", "返修管理", "按返修状态和返修结果汇总数量。"), ("报废原因排行", "报废管理", "按报废原因汇总,展示数量较多的原因。"), ("责任工位排行", "检验、NG、返修记录", "按相关工位汇总质量问题数量。"), ("最新质量问题", "检验、NG、返修、报废记录", "展示最近产生的质量异常或处理记录。"), ]) doc.add_heading("四、物料管理看板", level=1) add_table(doc, [ ("物料总数", "物料基础资料", "统计系统中已维护的物料数量。"), ("库存批次数", "库存台账", "统计当前仍有库存数量的批次。"), ("库存总量", "库存台账", "汇总当前正库存数量。"), ("今日入库数量", "入库记录", "汇总当天入库数量。"), ("今日出库数量", "出库记录", "汇总当天出库数量。"), ("今日报检数量", "物料报检记录", "统计当天产生的报检记录数量。"), ("待处理报检", "物料报检记录", "统计当前仍未完成处理的报检记录。"), ("今日异常数量", "不良品、退货、报废记录", "汇总当天物料异常相关记录数量。"), ("效期预警", "库存台账", "统计未来 30 天内即将到期的库存批次。"), ("物料趋势", "入库、出库、报检和异常记录", "按最近七天汇总业务发生数量。"), ("报检 / 检查 / 不良状态", "物料报检、检查、不良品记录", "按状态汇总数量,用于饼图展示。"), ("入库类型分布", "入库单据", "按入库类型汇总数量。"), ("领料状态分布", "领料单据", "按领料状态汇总数量。"), ("库位库存排行", "库存台账", "按库位汇总库存数量,展示库存较多的库位。"), ("物料库存排行", "库存台账", "按物料汇总库存数量,展示库存较多的物料。"), ("库存明细 / 到期清单", "库存台账", "展示当前库存和即将到期的重点批次。"), ("最新业务动态", "报检、检查、入库、出库及异常记录", "展示最近发生的物料业务记录。"), ]) doc.add_heading("五、设备 OEE 大屏", level=1) add_table(doc, [ ("设备总数", "设备管理", "统计系统中已维护的设备数量。"), ("运行 / 故障 / 维修 / 报警 / 离线数量", "设备实时状态记录 + 设备管理", "读取每台设备最新状态后按状态分类汇总。"), ("设备开动率", "设备状态记录", "按当天设备处于运行状态的时长计算,运行时长占当天已过时间的比例。"), ("平均开动率", "设备状态记录", "把所有设备当天运行时长汇总后,计算整体平均开动率。"), ("设备卡片", "设备管理 + 设备状态记录", "展示设备名称、编号、所属产线、当前状态、最近上报时间和开动率。"), ("开动率排行", "设备状态记录", "按设备当天开动率排序,展示开动率较高的设备。"), ]) add_note(doc, "特别说明:当前设备 OEE 大屏展示的是“设备开动率”,不是完整 OEE。完整 OEE 通常还需要计划运行时间、理想节拍、总产量、良品数量等数据。") doc.add_heading("六、客户确认建议", level=1) for text in [ "确认综合大屏顶部统计希望展示“今日数据”“本月数据”还是“历史累计数据”。", "确认生产日期口径使用自然日 00:00-24:00,还是生产班次日 08:00-次日 08:00。", "确认质量大屏中良品、不良是否必须全部来自真实检验数据。", "确认设备 OEE 是否只展示开动率,还是后续需要扩展为完整 OEE。", "确认报警、排行类数据是否需要限定为今日、近七天或本月,避免历史累计影响展示判断。", ]: bullet(doc, text) footer = doc.sections[0].footer.paragraphs[0] footer.alignment = WD_ALIGN_PARAGRAPH.RIGHT footer.add_run("MES 大屏数据来源说明") doc.save(OUT) if __name__ == "__main__": main()