diff --git a/src/gen_workbook.py b/src/gen_workbook.py index 6496767..d64f4a3 100644 --- a/src/gen_workbook.py +++ b/src/gen_workbook.py @@ -114,6 +114,11 @@ CERT_COLS=['所属区域','所属国别','项目名称','方案名称','编制 write_data_sheet(s3,cert_valid.reset_index(drop=True), f'2026年度公司认定危大方案明细(中港科技便〔2026〕6号·{cert_tot}项)',CERT_COLS) +# 动态行数(避免范围空值 → GROUPBY产出0值组) +OA_N = len(valid_2026); OA_END = OA_N + 3 # 有效≥2026 末行(表头row3 + 数据) +CERT_N = len(cert_valid); CERT_END = CERT_N + 3 +TECH_N = len(tech_valid); TECH_END = TECH_N + 3 + # ═══ S4: 公式-认定vsOA ═══ # 项目名从认定数据 C4:C200 提取,省去 UNIQUE(WPS兼容性更好) # 用 COUNTIFS 统计 超规("是") 和 一般("否") @@ -138,13 +143,13 @@ for r in range(4, 24): ar = f'$A{r}' sr = str(r) # B: 认定超规 - s4.cell(r, 2, f'=COUNTIFS(' + "'认定数据'!$C$4:$C$200," + f'{ar},' + "'认定数据'!$H$4:$H$200," + '"是")').font=FORMULA_F; s4.cell(r, 2).border=BORDER + s4.cell(r, 2, f'=COUNTIFS(' + f"'认定数据'!$C$4:$C${CERT_END}," + f'{ar},' + f"'认定数据'!$H$4:$H${CERT_END}," + '"是")').font=FORMULA_F; s4.cell(r, 2).border=BORDER # C: 认定一般 - s4.cell(r, 3, f'=COUNTIFS(' + "'认定数据'!$C$4:$C$200," + f'{ar},' + "'认定数据'!$H$4:$H$200," + '"否")').font=FORMULA_F; s4.cell(r, 3).border=BORDER + s4.cell(r, 3, f'=COUNTIFS(' + f"'认定数据'!$C$4:$C${CERT_END}," + f'{ar},' + f"'认定数据'!$H$4:$H${CERT_END}," + '"否")').font=FORMULA_F; s4.cell(r, 3).border=BORDER # D: OA超规 - s4.cell(r, 4, f'=COUNTIFS(' + "'有效≥2026'!$D$4:$D$200," + f'{ar},' + "'有效≥2026'!$K$4:$K$200," + '"是")').font=FORMULA_F; s4.cell(r, 4).border=BORDER + s4.cell(r, 4, f'=COUNTIFS(' + f"'有效≥2026'!$D$4:$D${OA_END}," + f'{ar},' + f"'有效≥2026'!$K$4:$K${OA_END}," + '"是")').font=FORMULA_F; s4.cell(r, 4).border=BORDER # E: OA一般 - s4.cell(r, 5, f'=COUNTIFS(' + "'有效≥2026'!$D$4:$D$200," + f'{ar},' + "'有效≥2026'!$K$4:$K$200," + '"否")').font=FORMULA_F; s4.cell(r, 5).border=BORDER + s4.cell(r, 5, f'=COUNTIFS(' + f"'有效≥2026'!$D$4:$D${OA_END}," + f'{ar},' + f"'有效≥2026'!$K$4:$K${OA_END}," + '"否")').font=FORMULA_F; s4.cell(r, 5).border=BORDER # F: 差额超规 = D - B s4.cell(r, 6, f'=D{sr}-B{sr}').font=FORMULA_F; s4.cell(r, 6).border=BORDER # G: 差额一般 = E - C @@ -164,24 +169,20 @@ write_data_sheet(s5,tech_valid.reset_index(drop=True), # ── S6: 公式-年度认定 ── s6=wb.create_sheet('公式-年度认定') s6.merge_cells('A1:C1'); s6.cell(1,1,'OA年度认定(≥2026开工·GROUPBY公式)').font=TITLE_F; s6.cell(1,1).border=GOLD_BD -s6.merge_cells('A2:C2'); s6.cell(2,1,'GROUPBY(有效≥2026!K3:K200,有效≥2026!A3:A200,COUNTA,3,0)').font=GRAY_F +s6.merge_cells('A2:C2'); s6.cell(2,1,f'GROUPBY(有效≥2026!K4:K{OA_END},有效≥2026!A4:A{OA_END},COUNTA,3,0)').font=GRAY_F s6.row_dimensions[3].height=6 -s6.cell(4,1,f'=_xlfn.GROUPBY({REF}!K3:K200,{REF}!A3:A200,COUNTA,3,0)').font=FRM_F_B; s6.cell(4,1).border=FRM_LEFT; s6.cell(4,1).fill=FRM_BG -for r in range(5,9): - for c in [1,2]: s6.cell(r,c).fill=SPILL_BG; s6.cell(r,c).border=BORDER +s6.cell(4,1,f'=_xlfn.GROUPBY({REF}!K4:K{OA_END},{REF}!A4:A{OA_END},COUNTA,3,0)').font=FRM_F_B; s6.cell(4,1).border=FRM_LEFT; s6.cell(4,1).fill=FRM_BG for w,c in zip([18,12],'AB'): s6.column_dimensions[c].width=w -s6.merge_cells('A10:C10'); s6.cell(10,1,'💡 _xlfn.前缀=告知WPS动态数组函数·不加@').font=GRAY_F; s6.cell(10,1).fill=INFO_BG +s6.merge_cells('A10:C10'); s6.cell(10,1,'💡 源数据更新后公式自动刷新').font=GRAY_F; s6.cell(10,1).fill=INFO_BG # ── S7: 公式-国别分布 ── s7=wb.create_sheet('公式-国别分布') s7.merge_cells('A1:C1'); s7.cell(1,1,'OA国别分布(自动排序·GROUPBY公式)').font=TITLE_F; s7.cell(1,1).border=GOLD_BD -s7.merge_cells('A2:C2'); s7.cell(2,1,'GROUPBY(有效≥2026!C3:C200,有效≥2026!A3:A200,COUNTA,3,0,-2)').font=GRAY_F +s7.merge_cells('A2:C2'); s7.cell(2,1,f'GROUPBY(有效≥2026!C4:C{OA_END},有效≥2026!A4:A{OA_END},COUNTA,3,0,-2)').font=GRAY_F s7.row_dimensions[3].height=6 -s7.cell(4,1,f'=_xlfn.GROUPBY({REF}!C3:C200,{REF}!A3:A200,COUNTA,3,0,-2)').font=FRM_F_B; s7.cell(4,1).border=FRM_LEFT; s7.cell(4,1).fill=FRM_BG -for r in range(5,12): - for c in [1,2]: s7.cell(r,c).fill=SPILL_BG; s7.cell(r,c).border=BORDER +s7.cell(4,1,f'=_xlfn.GROUPBY({REF}!C4:C{OA_END},{REF}!A4:A{OA_END},COUNTA,3,0,-2)').font=FRM_F_B; s7.cell(4,1).border=FRM_LEFT; s7.cell(4,1).fill=FRM_BG for w,c in zip([30,12],'AB'): s7.column_dimensions[c].width=w -s7.merge_cells('A14:C14'); s7.cell(14,1,'💡 _xlfn.前缀=告知WPS动态数组函数·不加@').font=GRAY_F; s7.cell(14,1).fill=INFO_BG +s7.merge_cells('A14:C14'); s7.cell(14,1,'💡 源数据更新后公式自动刷新').font=GRAY_F; s7.cell(14,1).fill=INFO_BG # ── S8: 公式-审批进度(多指标混合·COUNTIF·美化版) ── s8=wb.create_sheet('公式-审批进度') @@ -189,11 +190,11 @@ s8.merge_cells('A1:C1'); s8.cell(1,1,'OA审批进度 & 预警(COUNTIF公式) s8.row_dimensions[2].height=6 hdr_row(s8,3,['指标','数值','备注']) rows8 = [ - ('方案总数',f'=COUNTA({REF}!A4:A200)','≥2026年开工',BLUE_F,None), - ('已完成审批',f'=COUNTIF({REF}!Z4:Z200,TRUE)','含"已完成"状态',GREEN_F,ROW_DONE), - ('未完成审批',f'=COUNTIF({REF}!Z4:Z200,FALSE)','审批中+未审批',DATA_F,None), - ('🟠 橙色预警',f'=COUNTIF({REF}!AD4:AD200,"orange")','距开工≤30天',ORANGE_F,ROW_WARN), - ('🟡 黄色预警',f'=COUNTIF({REF}!AD4:AD200,"yellow")','距开工≤45天',Font(name='微软雅黑',bold=True,size=10,color='F9A825'),ROW_WARN), + ('方案总数',f'=COUNTA({REF}!A4:A{OA_END})','≥2026年开工',BLUE_F,None), + ('已完成审批',f'=COUNTIF({REF}!Z4:Z{OA_END},TRUE)','含"已完成"状态',GREEN_F,ROW_DONE), + ('未完成审批',f'=COUNTIF({REF}!Z4:Z{OA_END},FALSE)','审批中+未审批',DATA_F,None), + ('🟠 橙色预警',f'=COUNTIF({REF}!AD4:AD{OA_END},"orange")','距开工≤30天',ORANGE_F,ROW_WARN), + ('🟡 黄色预警',f'=COUNTIF({REF}!AD4:AD{OA_END},"yellow")','距开工≤45天',Font(name='微软雅黑',bold=True,size=10,color='F9A825'),ROW_WARN), ('预警合计','=B7+B8','橙色+黄色',RED_F,None), ] for ri,(lab,fm,note,lf,bg) in enumerate(rows8): @@ -208,15 +209,12 @@ for ri,(lab,fm,note,lf,bg) in enumerate(rows8): for ci in range(1,4): s8.cell(r,ci).fill=ROW_ODD for w,c in zip([18,10,35],'ABC'): s8.column_dimensions[c].width=w -# ── S9: 公式-预警明细 ── FILTER 动态筛选(美化版) +# ── S9: 公式-预警明细 ── FILTER 动态筛选 s9=wb.create_sheet('公式-预警明细') s9.cell(1,1,'OA预警明细(FILTER动态筛选)').font=TITLE_F; s9.cell(1,1).border=GOLD_BD -s9.cell(2,1,'_xlfn.FILTER(有效≥2026!A3:AD200,(AD3:AD200<>"none")*(AD3:AD200<>""),"无预警")').font=GRAY_F +s9.cell(2,1,f'_xlfn.FILTER(有效≥2026!A4:AD{OA_END},(AD4:AD{OA_END}<>"none")*(AD4:AD{OA_END}<>""),"无预警")').font=GRAY_F s9.row_dimensions[3].height=6 -s9.cell(4,1,f'=_xlfn.FILTER({REF}!A3:AD200,({REF}!AD3:AD200<>"none")*({REF}!AD3:AD200<>""),"🎉 无预警项")').font=FRM_F_B; s9.cell(4,1).border=FRM_LEFT; s9.cell(4,1).fill=FRM_BG -# 溢出区淡蓝底:A5:AD9(最多5预警+1表头=6行) -for r in range(5,11): - for i in range(1,31): s9.cell(r,i).fill=SPILL_BG; s9.cell(r,i).border=BORDER +s9.cell(4,1,f'=_xlfn.FILTER({REF}!A4:AD{OA_END},({REF}!AD4:AD{OA_END}<>"none")*({REF}!AD4:AD{OA_END}<>""),"🎉 无预警项")').font=FRM_F_B; s9.cell(4,1).border=FRM_LEFT; s9.cell(4,1).fill=FRM_BG for i in range(1,31): s9.column_dimensions[get_column_letter(i)].width=4 s9.column_dimensions['A'].width=6; s9.column_dimensions['C'].width=22 s9.column_dimensions['D'].width=40; s9.column_dimensions['E'].width=35 @@ -225,11 +223,9 @@ s9.column_dimensions['K'].width=12; s9.column_dimensions['AD'].width=10 # ── S10: 公式-认定分类 ── s10=wb.create_sheet('公式-认定分类') s10.merge_cells('A1:C1'); s10.cell(1,1,'认定危大方案分类(GROUPBY公式)').font=TITLE_F; s10.cell(1,1).border=GOLD_BD -s10.merge_cells('A2:C2'); s10.cell(2,1,'GROUPBY(认定数据!H3:H200,认定数据!D3:D200,COUNTA,3,0)').font=GRAY_F +s10.merge_cells('A2:C2'); s10.cell(2,1,f'GROUPBY(认定数据!H4:H{CERT_END},认定数据!D4:D{CERT_END},COUNTA,3,0)').font=GRAY_F s10.row_dimensions[3].height=6 -s10.cell(4,1,f'=_xlfn.GROUPBY({CREF}!H3:H200,{CREF}!D3:D200,COUNTA,3,0)').font=FRM_F_B; s10.cell(4,1).border=FRM_LEFT; s10.cell(4,1).fill=FRM_BG -for r in range(5,9): - for c in [1,2]: s10.cell(r,c).fill=SPILL_BG; s10.cell(r,c).border=BORDER +s10.cell(4,1,f'=_xlfn.GROUPBY({CREF}!H4:H{CERT_END},{CREF}!D4:D{CERT_END},COUNTA,3,0)').font=FRM_F_B; s10.cell(4,1).border=FRM_LEFT; s10.cell(4,1).fill=FRM_BG for w,c in zip([20,12],'AB'): s10.column_dimensions[c].width=w s10.merge_cells('A10:C10'); s10.cell(10,1,'💡 源数据更新后公式自动刷新').font=GRAY_F; s10.cell(10,1).fill=INFO_BG @@ -237,11 +233,9 @@ s10.merge_cells('A10:C10'); s10.cell(10,1,'💡 源数据更新后公式自动 s10b=wb.create_sheet('公式-技术方案分类') TREF="'认定技术方案'" s10b.merge_cells('A1:C1'); s10b.cell(1,1,'认定技术方案等级分布(GROUPBY公式)').font=TITLE_F; s10b.cell(1,1).border=GOLD_BD -s10b.merge_cells('A2:C2'); s10b.cell(2,1,'GROUPBY(认定技术方案!F3:F200,认定技术方案!C3:C200,COUNTA,3,0,-2)').font=GRAY_F +s10b.merge_cells('A2:C2'); s10b.cell(2,1,f'GROUPBY(认定技术方案!F4:F{TECH_END},认定技术方案!C4:C{TECH_END},COUNTA,3,0,-2)').font=GRAY_F s10b.row_dimensions[3].height=6 -s10b.cell(4,1,f'=_xlfn.GROUPBY({TREF}!F3:F200,{TREF}!C3:C200,COUNTA,3,0,-2)').font=FRM_F_B; s10b.cell(4,1).border=FRM_LEFT; s10b.cell(4,1).fill=FRM_BG -for r in range(5,9): - for c in [1,2]: s10b.cell(r,c).fill=SPILL_BG; s10b.cell(r,c).border=BORDER +s10b.cell(4,1,f'=_xlfn.GROUPBY({TREF}!F4:F{TECH_END},{TREF}!C4:C{TECH_END},COUNTA,3,0,-2)').font=FRM_F_B; s10b.cell(4,1).border=FRM_LEFT; s10b.cell(4,1).fill=FRM_BG for w,c in zip([12,12],'AB'): s10b.column_dimensions[c].width=w s10b.merge_cells('A10:C10'); s10b.cell(10,1,'💡 源数据更新后公式自动刷新').font=GRAY_F; s10b.cell(10,1).fill=INFO_BG