From 801e14c9442c4617adf31ab3cf55020ad4aadbcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E5=B8=88?= Date: Tue, 9 Jun 2026 04:09:00 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E8=AE=A4=E5=AE=9AvsOA=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E7=BA=AFExcel=E5=85=AC=E5=BC=8F(UNIQUE+COUNTIFS)-?= =?UTF-8?q?=E9=9B=B6Python=E9=9D=99=E6=80=81=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cleaned/危大方案看板数据工作簿.xlsx | Bin 69233 -> 69602 bytes src/gen_workbook.py | 164 +++++++++--------- 2 files changed, 79 insertions(+), 85 deletions(-) diff --git a/data/2026-06-08/cleaned/危大方案看板数据工作簿.xlsx b/data/2026-06-08/cleaned/危大方案看板数据工作簿.xlsx index 2f1a8f2b2e3856bd80bcbcc1b7f659d11d9b71a7..13b3b7a6a728e5b441e70327de669199462fe380 100644 GIT binary patch delta 9914 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 delta 9505 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 diff --git a/src/gen_workbook.py b/src/gen_workbook.py index f0a9460..99d0700 100644 --- a/src/gen_workbook.py +++ b/src/gen_workbook.py @@ -7,19 +7,18 @@ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.utils import get_column_letter from datetime import datetime -# 日期:命令行参数 or 默认今天 if len(sys.argv) > 1: REPORT_DATE = sys.argv[1] else: REPORT_DATE = datetime.now().strftime('%Y-%m-%d') BASE = f"/mnt/y/Openclaw_Hub/03.资源/实施项目 wiki/dashboard/data/{REPORT_DATE}" -CERT_DIR = "/mnt/y/Openclaw_Hub/03.资源/实施项目 wiki/dashboard/data/认定数据/2026" # 年度固定目录 +CERT_DIR = "/mnt/y/Openclaw_Hub/03.资源/实施项目 wiki/dashboard/data/认定数据/2026" OUT = f"{BASE}/cleaned/危大方案看板数据工作簿.xlsx" -REF = "'有效≥2026'" # OA公式引用 -CREF = "'认定数据'" # 认定公式引用 +REF = "'有效≥2026'" +CREF = "'认定数据'" -# ════ 数据源1: OA登记(methods_cleaned.csv) ════ +# ════ 数据源1: OA登记 ════ df = pd.read_csv(f"{BASE}/cleaned/methods_cleaned.csv") SRC_COLS = list(df.columns[:24]) valid_all = df[df['是否有效登记'] == True].copy() @@ -32,14 +31,13 @@ warn_df = m[m['预警信号']!='none'].sort_values('分部分项工程计划开 warn_total = len(warn_df); orange = (warn_df['预警信号']=='orange').sum(); yellow = warn_total-orange print(f"OA登记: {tot}项(一般{gen}/超规{sup}) 完成{completed} 预警{orange}橙{yellow}黄") -# ════ 数据源2: 公司认定(certified_schemes_detail.csv) ════ +# ════ 数据源2: 公司认定 ════ cert_raw = pd.read_csv(f"{CERT_DIR}/certified_schemes_detail.csv") cert_raw['计划开工日期_p'] = pd.to_datetime(cert_raw['计划开工日期'].astype(str).str.replace('.','-'), errors='coerce') cert_valid = cert_raw[cert_raw['计划开工日期_p'].dt.year >= 2026].copy() cert_tot = len(cert_valid) cert_sup = (cert_valid['是否超一定规模']=='是').sum(); cert_gen = cert_tot-cert_sup cert_comp = pd.read_csv(f"{CERT_DIR}/certified_schemes.csv") -# 表2: I II Ⅲ类技术方案 tech_raw = pd.read_csv(f"{CERT_DIR}/certified_tech_schemes_detail.csv") tech_raw['计划开工日期_p'] = pd.to_datetime(tech_raw['计划开工日期'].astype(str).str.replace('.','-'), errors='coerce') tech_valid = tech_raw[tech_raw['计划开工日期_p'].dt.year >= 2026].copy() @@ -97,76 +95,72 @@ def write_formula_sheet(ws,title,subtitle,formulas,col_widths): wb = Workbook() -# ════════ S1: OA清洗后数据 ════════ +# ═══ S1: OA清洗后数据 ═══ s1=wb.active; s1.title='清洗后数据' write_data_sheet(s1,valid_all.reset_index(drop=True), f'OA登记·清洗后数据(有效登记·全部年份·{len(valid_all)}行)', SRC_COLS+['方案状态_clean','是否完成审批','是否有效登记','开工年份','开工月份','预警信号']) -# ════════ S2: OA有效≥2026 ════════ +# ═══ S2: OA有效≥2026 ═══ s2=wb.create_sheet('有效≥2026') write_data_sheet(s2,valid_2026.reset_index(drop=True), f'OA登记·有效≥2026年开工({len(valid_2026)}行)', SRC_COLS+['方案状态_clean','是否完成审批','是否有效登记','开工年份','开工月份','预警信号']) -# ════════ S3: 认定数据 ════════ +# ═══ S3: 认定数据 ═══ s3=wb.create_sheet('认定数据') CERT_COLS=['所属区域','所属国别','项目名称','方案名称','编制单位','工程类别','分部工程类别','是否超一定规模','计划开工日期'] write_data_sheet(s3,cert_valid.reset_index(drop=True), f'2026年度公司认定危大方案明细(中港科技便〔2026〕6号·{cert_tot}项)',CERT_COLS) -# ════════ S4: 认定vsOA ════════ -s4=wb.create_sheet('认定vsOA') -COMP_COLS=['项目名称','认定_危大方案总数','认定_超规数','认定_一般数', - '平台_方案总数','平台_超规数','平台_一般数', - '差额_超规','差额_一般','差额_合计','匹配状态'] -ncol_c=len(COMP_COLS) +# ═══ S4: 公式-认定vsOA(纯Excel公式·COUNTIFS动态对比) ═══ +s4=wb.create_sheet('公式-认定vsOA') +COMP_HDR=['项目名称','认定超规','认定一般','OA超规','OA一般','差额超规','差额一般'] +ncol_c=len(COMP_HDR) s4.merge_cells(start_row=1,start_column=1,end_row=1,end_column=ncol_c) -s4.cell(1,1,f'认定 vs OA登记 项目级对比({REPORT_DATE})').font=TITLE_F; s4.cell(1,1).border=GOLD_BD -hdr_row(s4,3,COMP_COLS) -for ri,(_,row) in enumerate(cert_comp.iterrows()): - for ci,col in enumerate(COMP_COLS): - v=row.get(col,'') - if pd.isna(v): v='' - cell=s4.cell(ri+4,ci+1,v); cell.font=DATA_F; cell.border=BORDER - # 差额高亮 - if col.startswith('差额_') and isinstance(v,(int,float)) and v!=0: - cell.font=RED_F if v<0 else GREEN_F - if col=='匹配状态': cell.font=GREEN_F if '✅' in str(v) else DATA_F -s4.auto_filter.ref=f'A3:{get_column_letter(ncol_c)}{len(cert_comp)+3}' -for i,col in enumerate(COMP_COLS): - s4.column_dimensions[get_column_letter(i+1)].width=max(14,min(45,len(str(col))*2.5)) +s4.cell(1,1,f'认定 vs OA登记 项目级对比({REPORT_DATE}·Excel公式自动计算)').font=TITLE_F +s4.cell(1,1).border=GOLD_BD +s4.merge_cells(start_row=2,start_column=1,end_row=2,end_column=ncol_c) +s4.cell(2,1,'COUNTIFS(认定数据!C:C,项目,H:H,"是"/"否") vs COUNTIFS(有效≥2026!D:D,项目,K:K,"是"/"否")').font=GRAY_F +hdr_row(s4,3,COMP_HDR) -# ════════ S4b: 认定技术方案(表2) ════════ -s4b=wb.create_sheet('认定技术方案') +# A4: UNIQUE动态获取项目列表 +s4.cell(4,1,"=UNIQUE('认定数据'!C4:C200)").font=FORMULA_F; s4.cell(4,1).border=BORDER + +# B4-G23: COUNTIFS + 差额公式(预填20行,超UNIQUE范围自动留空) +for r in range(4, 24): + ar = f'$A{r}' if r > 4 else 'A4' + pref = f'IF({ar}="","",' + s4.cell(r,2,f'={pref}COUNTIFS(' + "'认定数据'!$C$4:$C$200," + f'{ar},' + "'认定数据'!$H$4:$H$200," + '"是"))').font=FORMULA_F; s4.cell(r,2).border=BORDER + s4.cell(r,3,f'={pref}COUNTIFS(' + "'认定数据'!$C$4:$C$200," + f'{ar},' + "'认定数据'!$H$4:$H$200," + '"否"))').font=FORMULA_F; s4.cell(r,3).border=BORDER + s4.cell(r,4,f'={pref}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,5,f'={pref}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,6,f'={pref}D{r}-B{r})').font=FORMULA_F; s4.cell(r,6).border=BORDER + s4.cell(r,7,f'={pref}E{r}-C{r})').font=FORMULA_F; s4.cell(r,7).border=BORDER + +s4.auto_filter.ref='A3:G23' +for w,col in zip([40,12,12,12,12,12,12],'ABCDEFG'): s4.column_dimensions[col].width=w + +# ═══ S5: 认定技术方案(表2) ═══ +s5=wb.create_sheet('认定技术方案') TECH_COLS=['所属国别','项目名称','方案名称','编制单位','工程类别','方案等级','工程特点/说明','计划开工日期'] -write_data_sheet(s4b,tech_valid.reset_index(drop=True), +write_data_sheet(s5,tech_valid.reset_index(drop=True), f'2026年度公司认定技术方案明细(ⅠⅡⅢ类·中港科技便〔2026〕6号·{tech_tot}项)',TECH_COLS) -# ── 公式-技术方案分类 ── -s4bf=wb.create_sheet('公式-技术方案分类') -TREF = "'认定技术方案'" -write_formula_sheet(s4bf,f'认定技术方案等级分布(GROUPBY·{tech_tot}项)', - f'GROUPBY({TREF}!F3:F200,{TREF}!C3:C200,COUNTA,3,0)', - [('A',4,f'=GROUPBY({TREF}!F3:F200,{TREF}!C3:C200,COUNTA,3,0)','')],[('A',20),('B',12)]) +# ═══ S6-S10: 动态公式 ═══ -# ════════ S5-S8: 动态公式 ════════ - -# ── S5: 公式-年度认定 ── -s5=wb.create_sheet('公式-年度认定') -write_formula_sheet(s5,f'OA年度认定(≥2026开工)', +s6=wb.create_sheet('公式-年度认定') +write_formula_sheet(s6,f'OA年度认定(≥2026开工)', f'GROUPBY({REF}!K3:K200,{REF}!A3:A200,COUNTA,3,0)', [('A',4,f'=GROUPBY({REF}!K3:K200,{REF}!A3:A200,COUNTA,3,0)','')],[('A',18),('B',12)]) -# ── S6: 公式-国别分布 ── -s6=wb.create_sheet('公式-国别分布') -write_formula_sheet(s6,f'OA国别×分类(自动排序)', +s7=wb.create_sheet('公式-国别分布') +write_formula_sheet(s7,f'OA国别×分类(自动排序)', f'GROUPBY({REF}!C3:C200,{REF}!A3:A200,COUNTA,3,0,-2)', [('A',4,f'=GROUPBY({REF}!C3:C200,{REF}!A3:A200,COUNTA,3,0,-2)','')],[('A',30),('B',12)]) -# ── S7: 公式-审批进度 ── -s7=wb.create_sheet('公式-审批进度') -write_formula_sheet(s7,f'OA审批进度 & 预警', +s8=wb.create_sheet('公式-审批进度') +write_formula_sheet(s8,f'OA审批进度 & 预警', f'引用 {REF}!Z:Z + AD:AD', [('A',4,'方案总数',''),('B',4,f'=COUNTA({REF}!A4:A200)',''), ('A',5,'已完成审批',''),('B',5,f'=COUNTIF({REF}!Z4:Z200,TRUE)',''), @@ -175,48 +169,49 @@ write_formula_sheet(s7,f'OA审批进度 & 预警', ('A',8,'黄色预警',''),('B',8,f'=COUNTIF({REF}!AD4:AD200,"yellow")',''), ('A',9,'预警合计',''),('B',9,'=B7+B8','')],[('A',20),('B',14)]) -# ── S8: 公式-预警明细 ── -s8=wb.create_sheet('公式-预警明细') -write_formula_sheet(s8,f'OA预警明细(FILTER)', +s9=wb.create_sheet('公式-预警明细') +write_formula_sheet(s9,f'OA预警明细(FILTER)', f'FILTER({REF}!A3:AD200,{REF}!AD3:AD200<>"none","无预警")', [('A',4,f'=FILTER({REF}!A3:AD200,{REF}!AD3:AD200<>"none","🎉 无预警项")','')],[('A',22)]) -# ── S9: 公式-认定分类 ── -s9=wb.create_sheet('公式-认定分类') -write_formula_sheet(s9,f'公司认定方案分类(GROUPBY·{cert_tot}项)', +s10=wb.create_sheet('公式-认定分类') +write_formula_sheet(s10,f'公司认定方案分类(GROUPBY·{cert_tot}项)', f'GROUPBY({CREF}!H3:H200,{CREF}!D3:D200,COUNTA,3,0)', [('A',4,f'=GROUPBY({CREF}!H3:H200,{CREF}!D3:D200,COUNTA,3,0)','')],[('A',20),('B',12)]) -# ════════ S10-S13: 静态汇总表(可交叉验证) ════════ +s10b=wb.create_sheet('公式-技术方案分类') +TREF = "'认定技术方案'" +write_formula_sheet(s10b,f'认定技术方案等级分布(GROUPBY·{tech_tot}项)', + f'GROUPBY({TREF}!F3:F200,{TREF}!C3:C200,COUNTA,3,0)', + [('A',4,f'=GROUPBY({TREF}!F3:F200,{TREF}!C3:C200,COUNTA,3,0)','')],[('A',20),('B',12)]) -# ── S10: 年度认定汇总 ── -s10=wb.create_sheet('年度认定汇总') -s10.merge_cells('A1:E1'); s10.cell(1,1,f'OA年度认定(≥2026·静态)').font=TITLE_F; s10.cell(1,1).border=GOLD_BD -hdr_row(s10,3,['分类','方案数','项目数','占比','备注']) +# ═══ S11-S14: 静态汇总(交叉验证) ═══ + +s11=wb.create_sheet('年度认定汇总') +s11.merge_cells('A1:E1'); s11.cell(1,1,f'OA年度认定(≥2026·静态)').font=TITLE_F; s11.cell(1,1).border=GOLD_BD +hdr_row(s11,3,['分类','方案数','项目数','占比','备注']) for r,(lab,val,proj,pct,note) in enumerate([ ('一般类',gen,valid_2026[valid_2026['是否超一定规模']!='是']['项目名称'].nunique(),f'{gen/tot*100:.0f}%','非超一定规模'), ('超规类',sup,valid_2026[valid_2026['是否超一定规模']=='是']['项目名称'].nunique(),f'{sup/tot*100:.0f}%','超一定规模'), ('合计',tot,projects,'100%',f'涵盖{len(countries)}国')]): fmts=[BOLD_F if '合计' in lab else DATA_F]*5 for c,(v,f) in enumerate(zip([lab,val,proj,pct,note],fmts)): - cell=s10.cell(r+4,c+1,v); cell.font=f; cell.border=BORDER; cell.alignment=CENTER -for w,col in zip([12,10,10,10,20],'ABCDE'): s10.column_dimensions[col].width=w + cell=s11.cell(r+4,c+1,v); cell.font=f; cell.border=BORDER; cell.alignment=CENTER +for w,col in zip([12,10,10,10,20],'ABCDE'): s11.column_dimensions[col].width=w -# ── S11: 国别×分类 ── -s11=wb.create_sheet('国别×分类') -s11.merge_cells('A1:D1'); s11.cell(1,1,'OA国别×分类·静态').font=TITLE_F; s11.cell(1,1).border=GOLD_BD +s12=wb.create_sheet('国别×分类') +s12.merge_cells('A1:D1'); s12.cell(1,1,'OA国别×分类·静态').font=TITLE_F; s12.cell(1,1).border=GOLD_BD ct=m.groupby(['所属国别','是否超一定规模']).size().unstack(fill_value=0) ct.columns=['一般类' if c=='否' else '超规类' for c in ct.columns]; ct['合计']=ct.sum(1); ct.loc['合计']=ct.sum() -hdr_row(s11,3,['国别']+list(ct.columns)) +hdr_row(s12,3,['国别']+list(ct.columns)) for r,(idx,row) in enumerate(ct.iterrows()): for c,v in enumerate([idx]+[int(x) for x in row]): - cell=s11.cell(r+4,c+1,v); cell.font=BOLD_F if '合计' in str(idx) else DATA_F; cell.border=BORDER; cell.alignment=CENTER -s11.column_dimensions['A'].width=25 + cell=s12.cell(r+4,c+1,v); cell.font=BOLD_F if '合计' in str(idx) else DATA_F; cell.border=BORDER; cell.alignment=CENTER +s12.column_dimensions['A'].width=25 -# ── S12: 审批进度 ── -s12=wb.create_sheet('审批进度') -s12.merge_cells('A1:D1'); s12.cell(1,1,f'OA审批进度·静态({REPORT_DATE})').font=TITLE_F; s12.cell(1,1).border=GOLD_BD -hdr_row(s12,3,['指标','数值','占比','备注']) +s13=wb.create_sheet('审批进度') +s13.merge_cells('A1:D1'); s13.cell(1,1,f'OA审批进度·静态({REPORT_DATE})').font=TITLE_F; s13.cell(1,1).border=GOLD_BD +hdr_row(s13,3,['指标','数值','占比','备注']) for r,(lab,val,pct,note,fmts) in enumerate([ ('方案总数',tot,'100%','≥2026年开工',[BLUE_F]*3+[GRAY_F]), ('已完成审批',int(completed),f'{completed/tot*100:.0f}%','含"已完成"',[GREEN_F]*3+[GRAY_F]), @@ -225,13 +220,12 @@ for r,(lab,val,pct,note,fmts) in enumerate([ ('🟡 黄色预警',int(yellow),f'{yellow/tot*100:.0f}%','≤45天',[Font(name='微软雅黑',bold=True,size=10,color='F9A825')]*3+[GRAY_F]), ('预警合计',int(warn_total),f'{warn_total/tot*100:.0f}%',f'🟠{orange}+🟡{yellow}',[RED_F]*3+[GRAY_F])]): for c,(v,f) in enumerate(zip([lab,val,pct,note],fmts)): - cell=s12.cell(r+4,c+1,v); cell.font=f; cell.border=BORDER; cell.alignment=CENTER -for w,col in zip([18,10,10,35],'ABCD'): s12.column_dimensions[col].width=w + cell=s13.cell(r+4,c+1,v); cell.font=f; cell.border=BORDER; cell.alignment=CENTER +for w,col in zip([18,10,10,35],'ABCD'): s13.column_dimensions[col].width=w -# ── S13: 预警明细 ── -s13=wb.create_sheet('预警明细') -s13.merge_cells('A1:H1'); s13.cell(1,1,f'OA预警明细·静态(共{warn_total}项)').font=TITLE_F; s13.cell(1,1).border=GOLD_BD -hdr_row(s13,3,['信号','距开工','项目名称','方案名称','方案状态','计划开工','超规/一般','国别']) +s14=wb.create_sheet('预警明细') +s14.merge_cells('A1:H1'); s14.cell(1,1,f'OA预警明细·静态(共{warn_total}项)').font=TITLE_F; s14.cell(1,1).border=GOLD_BD +hdr_row(s14,3,['信号','距开工','项目名称','方案名称','方案状态','计划开工','超规/一般','国别']) today=pd.Timestamp(REPORT_DATE) for r,(_,row) in enumerate(warn_df.iterrows()): days=(pd.to_datetime(row['分部分项工程计划开工日期'])-today).days @@ -241,15 +235,15 @@ for r,(_,row) in enumerate(warn_df.iterrows()): str(row['分部分项工程计划开工日期'])[:10], '超规类' if row['是否超一定规模']=='是' else '一般类',row['所属国别']] for c,v in enumerate(vals): - cell=s13.cell(r+4,c+1,v); cell.font=RED_F if c==1 and days<=3 else DATA_F; cell.border=BORDER; cell.fill=WARN_BG -s13.auto_filter.ref=f'A3:H{warn_total+3}' -for w,col in zip([6,8,40,35,18,12,10,20],'ABCDEFGH'): s13.column_dimensions[col].width=w + cell=s14.cell(r+4,c+1,v); cell.font=RED_F if c==1 and days<=3 else DATA_F; cell.border=BORDER; cell.fill=WARN_BG +s14.auto_filter.ref=f'A3:H{warn_total+3}' +for w,col in zip([6,8,40,35,18,12,10,20],'ABCDEFGH'): s14.column_dimensions[col].width=w wb.save(OUT) print(f"\n✅ {OUT}") print(f" S1-S2 OA登记: {len(valid_all)}全量 + {len(valid_2026)}≥2026") print(f" S3 认定危大方案(表1): {cert_tot}行({cert_gen}一般+{cert_sup}超规)") -print(f" S4 认定vsOA: {len(cert_comp)}项目") -print(f" S4b 认定技术方案(表2): {tech_tot}行") -print(f" S5-S10 GROUPBY/FILTER 动态公式(含表1+表2)") +print(f" S4 公式-认定vsOA: COUNTIFS动态对比") +print(f" S5 认定技术方案(表2): {tech_tot}行") +print(f" S6-S10 GROUPBY/FILTER 动态公式(含表1+表2)") print(f" S11-S14 静态汇总表(交叉验证)")