fill_weekly_plan_from_person.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. # -*- coding: utf-8 -*-
  2. """
  3. 将「每周详细计划_人员」同步到「每周详细计划_时间」(保持时间页版式),
  4. 并更新日报「20260413」中「二、项目周进度」为 W2(2026-04-13 所在周),
  5. 补充「三、项目日进度」与合并区风险/预案文案。
  6. """
  7. from __future__ import annotations
  8. import re
  9. import shutil
  10. from datetime import datetime
  11. from pathlib import Path
  12. import openpyxl
  13. from openpyxl.utils import range_boundaries
  14. SRC = Path(r"d:\Projects\Ai-DOP\AI-DOP项目总计划_20260413.xlsx")
  15. BACKUP = SRC.with_name(SRC.stem + "_before_fill_" + datetime.now().strftime("%Y%m%d_%H%M%S") + ".xlsx")
  16. def cell_tl(ws, row: int, col: int) -> tuple[int, int]:
  17. for m in ws.merged_cells.ranges:
  18. min_col, min_row, max_col, max_row = range_boundaries(str(m))
  19. if min_row <= row <= max_row and min_col <= col <= max_col:
  20. return min_row, min_col
  21. return row, col
  22. def get_cell(ws, row: int, col: int):
  23. rr, cc = cell_tl(ws, row, col)
  24. return ws.cell(rr, cc).value
  25. def set_cell(ws, row: int, col: int, value):
  26. rr, cc = cell_tl(ws, row, col)
  27. ws.cell(rr, cc).value = value
  28. def norm_week(val) -> str | None:
  29. if val is None:
  30. return None
  31. s = str(val).strip()
  32. m = re.match(r"^(W\d+)\b", s, re.I)
  33. return m.group(1).upper() if m else None
  34. def merge_cell_text(a, b, tag: str) -> str:
  35. ta = str(a).strip() if a is not None else ""
  36. tb = str(b).strip() if b is not None else ""
  37. if not tb or tb == ta:
  38. return ta
  39. if not ta:
  40. return f"【{tag}】{tb}"
  41. return f"{ta}\n【{tag}】{tb}"
  42. def set_merged_top_left(ws, row: int, col: int, value):
  43. for m in ws.merged_cells.ranges:
  44. min_col, min_row, max_col, max_row = range_boundaries(str(m))
  45. if min_row <= row <= max_row and min_col <= col <= max_col:
  46. ws.cell(min_row, min_col).value = value
  47. return
  48. ws.cell(row, col).value = value
  49. def fill_time_sheet(time_ws, person_ws):
  50. for c in range(1, 9):
  51. set_cell(time_ws, 1, c, get_cell(person_ws, 1, c))
  52. for r in range(2, 9):
  53. for c in range(1, 9):
  54. set_cell(time_ws, r, c, get_cell(person_ws, r, c))
  55. # W1:合并彭熙玉轨道(人员第 14 行)
  56. tag = "彭熙玉"
  57. for c in range(3, 9):
  58. set_cell(
  59. time_ws,
  60. 5,
  61. c,
  62. merge_cell_text(get_cell(time_ws, 5, c), get_cell(person_ws, 14, c), tag),
  63. )
  64. # W2:合并彭熙玉轨道(人员第 15 行)
  65. for c in range(3, 9):
  66. set_cell(
  67. time_ws,
  68. 6,
  69. c,
  70. merge_cell_text(get_cell(time_ws, 6, c), get_cell(person_ws, 15, c), tag),
  71. )
  72. # W8–W14:人员表第 16–22 行 → 时间页行号 = 4 + 周序号(与原有 W5→第9行一致)
  73. for pr in range(16, 23):
  74. wk = norm_week(get_cell(person_ws, pr, 1))
  75. if not wk:
  76. continue
  77. try:
  78. n = int(wk[1:])
  79. except ValueError:
  80. continue
  81. tr = 4 + n
  82. if tr < 9:
  83. continue
  84. for c in range(1, 9):
  85. set_cell(time_ws, tr, c, get_cell(person_ws, pr, c))
  86. def build_w2_daily_block(person_ws) -> dict[str, object]:
  87. """日报「周进度」区:日期/状态以刘文博 W2(第 6 行)为准,目标与事项合并彭熙玉(第 15 行)。"""
  88. tag = "彭熙玉"
  89. out: dict[str, object] = {
  90. "G": norm_week(get_cell(person_ws, 6, 1)) or "W2",
  91. "H": get_cell(person_ws, 6, 8) or "进行中",
  92. "I": get_cell(person_ws, 6, 2) or "2026/04/13 - 2026/04/18",
  93. "J": merge_cell_text(get_cell(person_ws, 6, 3), get_cell(person_ws, 15, 3), tag),
  94. "K": merge_cell_text(get_cell(person_ws, 6, 4), get_cell(person_ws, 15, 4), tag),
  95. "L": merge_cell_text(get_cell(person_ws, 6, 5), get_cell(person_ws, 15, 5), tag),
  96. "M": merge_cell_text(get_cell(person_ws, 6, 6), get_cell(person_ws, 15, 6), tag),
  97. "N": merge_cell_text(get_cell(person_ws, 6, 7), get_cell(person_ws, 15, 7), tag),
  98. }
  99. return out
  100. def fill_daily_20260413(ws, g_block: dict[str, object]):
  101. # 周进度 G–N 只写首行;多人一行由「时间」页拆分后运行 split_weekly_time_by_person 同步
  102. for r in (4,):
  103. for key, ci in zip("GHIJKLMN", range(7, 15)):
  104. val = g_block.get(key)
  105. if val is not None:
  106. ws.cell(r, ci).value = val
  107. ws.cell(16, 2).value = (
  108. "1.按W2计划推进S0对外接口与多变量推演联调;\n"
  109. "2.对照周验收物推进S8研发节点(与S0联动相关接口自检)。"
  110. )
  111. set_merged_top_left(
  112. ws,
  113. 16,
  114. 3,
  115. "1.继续S0与S8联动联调收尾;\n2.整理当日联调记录与待确认问题清单。",
  116. )
  117. ws.cell(16, 5).value = "与周计划一致:关注模块间契约变更需拉通确认。"
  118. risk = str(g_block.get("N") or "").strip() or "模块间契约、接口口径与联调窗口需当日对齐。"
  119. set_merged_top_left(ws, 16, 7, risk)
  120. set_merged_top_left(
  121. ws,
  122. 16,
  123. 12,
  124. "1.接口/口径变更走会议纪要确认;\n"
  125. "2.关键路径延误则当日升级并调整次日优先级(对齐W2验收物)。",
  126. )
  127. ws.cell(17, 2).value = (
  128. "1.按W2「S1-S4开发推进」拆解:梳理产销/计划/供应/采购相关表结构与页面清单;\n"
  129. "2.对齐与S8/S9数据依赖字段,标记待确认项。"
  130. )
  131. set_merged_top_left(
  132. ws,
  133. 17,
  134. 3,
  135. "1.继续完善模块功能清单;\n2.准备与刘文博侧S8联动所需的数据字段对齐材料。",
  136. )
  137. ws.cell(18, 2).value = (
  138. "1.组织W2联调节奏:S0对外接口、S8节点对照检查;\n"
  139. "2.同步S9指标建模与S4试点相关风险;\n"
  140. "3.团队工具/框架答疑与Demo路线澄清。"
  141. )
  142. set_merged_top_left(
  143. ws,
  144. 18,
  145. 3,
  146. "1.继续推进S9指标建模与S4试点演示准备;\n"
  147. "2.跟进S0/S8联调问题闭环与跨模块口径确认。",
  148. )
  149. def main():
  150. if not SRC.exists():
  151. raise SystemExit(f"missing: {SRC}")
  152. shutil.copy2(SRC, BACKUP)
  153. wb = openpyxl.load_workbook(SRC)
  154. person = wb["每周详细计划_人员"]
  155. time_ws = wb["每周详细计划_时间"]
  156. day = wb["20260413"]
  157. fill_time_sheet(time_ws, person)
  158. w2_daily = build_w2_daily_block(person)
  159. fill_daily_20260413(day, w2_daily)
  160. wb.save(SRC)
  161. print("saved:", SRC)
  162. print("backup:", BACKUP)
  163. if __name__ == "__main__":
  164. main()