refactor: 认定vsOA改为纯Excel公式(UNIQUE+COUNTIFS)-零Python静态值
This commit is contained in:
parent
54e7df87ce
commit
801e14c944
Binary file not shown.
@ -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 静态汇总表(交叉验证)")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user