diff --git a/data/2026-06-08/cleaned/危大方案看板数据工作簿.xlsx b/data/2026-06-08/cleaned/危大方案看板数据工作簿.xlsx index 2f1a8f2..13b3b7a 100644 Binary files a/data/2026-06-08/cleaned/危大方案看板数据工作簿.xlsx and b/data/2026-06-08/cleaned/危大方案看板数据工作簿.xlsx differ 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 静态汇总表(交叉验证)")