gen_demo_template.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #!/usr/bin/env python3
  2. """
  3. 生成 Demo 数据 Excel 导入模板
  4. ===============================
  5. 从数据库读取 KpiMaster 指标定义,生成一个带格式的 Excel 模板。
  6. 用户可在 target_value / actual_value 列填入数值,未填的由 gen_demo_data.py 自动生成。
  7. 用法:
  8. python3 scripts/gen_demo_template.py
  9. # 输出: scripts/demo_data_template.xlsx
  10. """
  11. import sys
  12. DB_CONFIG = dict(
  13. host="123.60.180.165",
  14. port=3306,
  15. user="aidopremote",
  16. password="1234567890aiDOP#",
  17. database="aidopdev",
  18. )
  19. DEMO_TENANT_ID = 1300000000888
  20. OUTPUT_PATH = "scripts/demo_data_template.xlsx"
  21. MODULE_NAMES = {
  22. "S1": "S1 产销协同",
  23. "S2": "S2 运作协同",
  24. "S3": "S3 供应协同",
  25. "S4": "S4 采购执行",
  26. "S5": "S5 原材料仓储",
  27. "S6": "S6 制造运作",
  28. "S7": "S7 成品仓储",
  29. "S8": "S8 工厂监控",
  30. "S9": "S9 运营指标",
  31. }
  32. LEVEL_LABELS = {1: "L1", 2: "L2", 3: "L3"}
  33. def connect_db():
  34. try:
  35. import pymysql
  36. return pymysql.connect(**DB_CONFIG)
  37. except ImportError:
  38. pass
  39. try:
  40. import mysql.connector
  41. return mysql.connector.connect(**DB_CONFIG)
  42. except ImportError:
  43. pass
  44. print("ERROR: 需要 pymysql 或 mysql-connector-python", file=sys.stderr)
  45. sys.exit(1)
  46. def load_kpi_master(conn):
  47. cur = conn.cursor()
  48. cur.execute(
  49. "SELECT MetricCode, ModuleCode, MetricLevel, MetricName, Unit, Direction, ParentId, Id "
  50. "FROM ado_smart_ops_kpi_master WHERE TenantId = %s "
  51. "ORDER BY ModuleCode, MetricLevel, SortNo",
  52. (DEMO_TENANT_ID,),
  53. )
  54. rows = cur.fetchall()
  55. cur.close()
  56. return [
  57. dict(
  58. metric_code=r[0],
  59. module_code=r[1],
  60. metric_level=r[2],
  61. metric_name=r[3],
  62. unit=r[4] or "",
  63. direction=r[5] or "higher_is_better",
  64. parent_id=r[6],
  65. id=r[7],
  66. )
  67. for r in rows
  68. ]
  69. def build_template(masters):
  70. from openpyxl import Workbook
  71. from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
  72. wb = Workbook()
  73. ws = wb.active
  74. ws.title = "Demo数据模板"
  75. header_font = Font(name="微软雅黑", bold=True, size=11, color="FFFFFF")
  76. header_fill = PatternFill("solid", fgColor="4472C4")
  77. l1_fill = PatternFill("solid", fgColor="D6E4F0")
  78. l2_fill = PatternFill("solid", fgColor="E8F0FE")
  79. l3_fill = PatternFill("solid", fgColor="F5F5F5")
  80. level_fills = {1: l1_fill, 2: l2_fill, 3: l3_fill}
  81. input_fill = PatternFill("solid", fgColor="FFFFCC")
  82. thin_border = Border(
  83. left=Side(style="thin"),
  84. right=Side(style="thin"),
  85. top=Side(style="thin"),
  86. bottom=Side(style="thin"),
  87. )
  88. headers = [
  89. ("metric_code", "指标编码", 18),
  90. ("module_code", "模块", 8),
  91. ("module_name", "模块名称", 16),
  92. ("metric_level", "层级", 6),
  93. ("metric_name", "指标名称", 30),
  94. ("unit", "单位", 10),
  95. ("direction", "方向", 18),
  96. ("target_value", "目标值 ★", 14),
  97. ("actual_value", "实际值 ★", 14),
  98. ("yellow_threshold", "黄色阈值% ★", 14),
  99. ("red_threshold", "红色阈值% ★", 14),
  100. ]
  101. for col_idx, (_, title, width) in enumerate(headers, 1):
  102. cell = ws.cell(row=1, column=col_idx, value=title)
  103. cell.font = header_font
  104. cell.fill = header_fill
  105. cell.alignment = Alignment(horizontal="center", vertical="center")
  106. cell.border = thin_border
  107. ws.column_dimensions[cell.column_letter].width = width
  108. id_map = {m["id"]: m for m in masters}
  109. row = 2
  110. prev_module = None
  111. for m in masters:
  112. if m["module_code"] != prev_module:
  113. if prev_module is not None:
  114. row += 1
  115. prev_module = m["module_code"]
  116. level = m["metric_level"]
  117. fill = level_fills.get(level, l3_fill)
  118. indent = " " * (level - 1)
  119. default_yellow = 110 if m["direction"] == "lower_is_better" else 95
  120. default_red = 120 if m["direction"] == "lower_is_better" else 80
  121. values = [
  122. m["metric_code"],
  123. m["module_code"],
  124. MODULE_NAMES.get(m["module_code"], m["module_code"]),
  125. LEVEL_LABELS.get(level, f"L{level}"),
  126. indent + m["metric_name"],
  127. m["unit"],
  128. "越高越好" if m["direction"] == "higher_is_better" else "越低越好",
  129. None,
  130. None,
  131. default_yellow,
  132. default_red,
  133. ]
  134. for col_idx, val in enumerate(values, 1):
  135. cell = ws.cell(row=row, column=col_idx, value=val)
  136. cell.border = thin_border
  137. if col_idx <= 7:
  138. cell.fill = fill
  139. elif col_idx <= 9:
  140. cell.fill = input_fill
  141. else:
  142. cell.fill = input_fill
  143. if level == 1:
  144. cell.font = Font(name="微软雅黑", bold=True, size=10)
  145. else:
  146. cell.font = Font(name="微软雅黑", size=10)
  147. row += 1
  148. note_row = row + 1
  149. ws.cell(row=note_row, column=1, value="★ 填写说明:").font = Font(
  150. bold=True, color="C00000", size=10
  151. )
  152. ws.cell(
  153. row=note_row + 1,
  154. column=1,
  155. value="1. 在「目标值」和「实际值」列填入数值,留空则由系统自动生成。",
  156. )
  157. ws.cell(
  158. row=note_row + 2,
  159. column=1,
  160. value="2. 支持任意层级填入:可只填 L1,也可填 L2/L3,系统会自动保证父子层级数值自洽。",
  161. )
  162. ws.cell(
  163. row=note_row + 3,
  164. column=1,
  165. value="3. 若同时填了父/子指标,以实际填入值为准;系统仅对未填的指标自动生成补全。",
  166. )
  167. ws.cell(
  168. row=note_row + 4,
  169. column=1,
  170. value="4.「实际值」为 30 天日均基准,脚本会在此基础上加每日波动。",
  171. )
  172. ws.cell(
  173. row=note_row + 5,
  174. column=1,
  175. value="5.「黄色阈值%」「红色阈值%」控制期量差的颜色判定,已预填默认值(越高越好: 95/80,越低越好: 110/120)。",
  176. )
  177. ws.freeze_panes = "A2"
  178. ws.auto_filter.ref = f"A1:K{row - 1}"
  179. wb.save(OUTPUT_PATH)
  180. print(f"模板已生成: {OUTPUT_PATH} ({row - 2} 条指标)")
  181. def main():
  182. conn = connect_db()
  183. masters = load_kpi_master(conn)
  184. conn.close()
  185. if not masters:
  186. print(f"ERROR: 租户 {DEMO_TENANT_ID} 无 KpiMaster 数据", file=sys.stderr)
  187. sys.exit(1)
  188. build_template(masters)
  189. if __name__ == "__main__":
  190. main()