279 lines
17 KiB
Python
279 lines
17 KiB
Python
#!/usr/bin/env python3
|
||
"""危大方案看板数据工作簿 v5 — 双数据源 + 纯Excel公式(认定vsOA用COUNTIFS直写)"""
|
||
import sys
|
||
import pandas as pd
|
||
from openpyxl import Workbook
|
||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||
from openpyxl.utils import get_column_letter
|
||
from datetime import datetime
|
||
|
||
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"
|
||
OUT = f"{BASE}/cleaned/危大方案看板数据工作簿.xlsx"
|
||
if len(sys.argv) > 2:
|
||
OUT = sys.argv[2] # override output path (for when WPS locks the file)
|
||
|
||
# ════ 数据源 ════
|
||
df = pd.read_csv(f"{BASE}/cleaned/methods_cleaned.csv")
|
||
SRC_COLS = list(df.columns[:24])
|
||
valid_all = df[df['是否有效登记'] == True].copy()
|
||
valid_2026 = valid_all[valid_all['开工年份'] >= 2026].copy()
|
||
m = valid_2026
|
||
tot = len(m); gen = (m['是否超一定规模']!='是').sum(); sup = tot-gen
|
||
completed = m['是否完成审批'].sum(); unfinished = tot-completed
|
||
projects = m['项目名称'].nunique(); countries = m['所属国别'].value_counts().to_dict()
|
||
warn_df = m[m['预警信号']!='none'].sort_values('分部分项工程计划开工日期')
|
||
warn_total = len(warn_df); orange = (warn_df['预警信号']=='orange').sum(); yellow = warn_total-orange
|
||
|
||
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
|
||
|
||
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()
|
||
tech_tot = len(tech_valid)
|
||
|
||
print(f"OA: {tot}(一般{gen}/超规{sup}) 完成{completed} | 认定: 表1{cert_tot}+表2{tech_tot}={cert_tot+tech_tot}")
|
||
|
||
# ════ 样式 ════
|
||
HDR_F=Font(name='微软雅黑',bold=True,size=10,color='FFFFFF'); HDR_BG=PatternFill('solid',fgColor='1A3A5C')
|
||
TITLE_F=Font(name='微软雅黑',bold=True,size=14,color='1A3A5C'); DATA_F=Font(name='微软雅黑',size=10)
|
||
BOLD_F=Font(name='微软雅黑',bold=True,size=10); GRAY_F=Font(name='微软雅黑',size=9,color='8899AA')
|
||
GREEN_F=Font(name='微软雅黑',bold=True,size=10,color='2E7D32'); RED_F=Font(name='微软雅黑',bold=True,size=10,color='D94E34')
|
||
BLUE_F=Font(name='微软雅黑',bold=True,size=10,color='1A3A5C'); ORANGE_F=Font(name='微软雅黑',bold=True,size=10,color='E65100')
|
||
FORMULA_F=Font(name='Consolas',size=10,color='1A3A5C')
|
||
WARN_BG=PatternFill('solid',fgColor='FFF3E0'); INFO_BG=PatternFill('solid',fgColor='F0F4FA')
|
||
BORDER=Border(left=Side('thin','DBE2EA'),right=Side('thin','DBE2EA'),top=Side('thin','DBE2EA'),bottom=Side('thin','DBE2EA'))
|
||
CENTER=Alignment(horizontal='center',vertical='center',wrap_text=True)
|
||
GOLD_BD=Border(bottom=Side(style='medium',color='C8962E'))
|
||
REF="'有效≥2026'"; CREF="'认定数据'"
|
||
|
||
def hdr_row(ws,r,cols):
|
||
for i,h in enumerate(cols):
|
||
c=ws.cell(r,i+1,h); c.font=HDR_F; c.fill=HDR_BG; c.border=BORDER; c.alignment=CENTER
|
||
|
||
def write_data_sheet(ws,df_out,title,cols):
|
||
ncol=len(cols)
|
||
ws.merge_cells(start_row=1,start_column=1,end_row=1,end_column=ncol)
|
||
ws.cell(1,1,title).font=TITLE_F; ws.cell(1,1).border=GOLD_BD
|
||
hdr_row(ws,3,cols)
|
||
for ri,(_,row) in enumerate(df_out.iterrows()):
|
||
rr=ri+4
|
||
for ci,col in enumerate(cols):
|
||
v=row.get(col,'')
|
||
if pd.isna(v): v=''
|
||
elif isinstance(v,(pd.Timestamp,)): v=str(v)[:10]
|
||
elif isinstance(v,(float,)) and v==int(v): v=int(v)
|
||
ws.cell(rr,ci+1,v).font=DATA_F; ws.cell(rr,ci+1,v).border=BORDER
|
||
ws.auto_filter.ref=f'A3:{get_column_letter(ncol)}{len(df_out)+3}'
|
||
ws.freeze_panes='A4'
|
||
for i,col in enumerate(cols):
|
||
ws.column_dimensions[get_column_letter(i+1)].width=max(10,min(40,len(str(col))*2.2))
|
||
|
||
def write_formula_sheet(ws,title,subtitle,formulas,col_widths):
|
||
ws.merge_cells(start_row=1,start_column=1,end_row=1,end_column=4)
|
||
ws.cell(1,1,title).font=TITLE_F; ws.cell(1,1).border=GOLD_BD
|
||
if subtitle:
|
||
ws.merge_cells(start_row=2,start_column=1,end_row=2,end_column=4)
|
||
ws.cell(2,1,subtitle).font=Font(name='微软雅黑',size=9,color='8899AA')
|
||
for col_letter,row_num,formula,label in formulas:
|
||
cell=ws.cell(row_num,ord(col_letter)-64,formula); cell.font=FORMULA_F; cell.border=BORDER
|
||
if label: ws.cell(row_num,ord(col_letter)-63,label).font=GRAY_F
|
||
for col_letter,w in col_widths: ws.column_dimensions[col_letter].width=w
|
||
ws.merge_cells(start_row=12,start_column=1,end_row=12,end_column=4)
|
||
ws.cell(12,1,'💡 源数据更新后公式自动刷新(WPS新版/Excel 365)').font=GRAY_F; ws.cell(12,1).fill=INFO_BG
|
||
|
||
wb = Workbook()
|
||
|
||
# ═══ S1-S3 数据源 ═══
|
||
s1=wb.active; s1.title='清洗后数据'
|
||
write_data_sheet(s1,valid_all.reset_index(drop=True),
|
||
f'OA登记·清洗后数据(有效登记·全部年份·{len(valid_all)}行)',
|
||
SRC_COLS+['方案状态_clean','是否完成审批','是否有效登记','开工年份','开工月份','预警信号'])
|
||
|
||
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=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 ═══
|
||
# 项目名从认定数据 C4:C200 提取,省去 UNIQUE(WPS兼容性更好)
|
||
# 用 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}·COUNTIFS公式)').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,'A列=项目 | B=认定超规 | C=认定一般 | D=OA超规 | E=OA一般 | F=差额超规 | G=差额一般').font=GRAY_F
|
||
hdr_row(s4,3,COMP_HDR)
|
||
|
||
# 用 Python 预填项目名(因为 UNIQUE 在部分WPS版本不支持)
|
||
# 然后 COUNTIFS 引用项目名列
|
||
proj_list = sorted(cert_valid['项目名称'].unique())
|
||
for ri, proj_name in enumerate(proj_list):
|
||
r = ri + 4
|
||
s4.cell(r, 1, proj_name).font=DATA_F; s4.cell(r, 1).border=BORDER
|
||
|
||
# 公式行:B-H 全部用 =COUNTIFS / =差额(预填20行)
|
||
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
|
||
# 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
|
||
# 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
|
||
# 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
|
||
# F: 差额超规 = D - B
|
||
s4.cell(r, 6, f'=D{sr}-B{sr}').font=FORMULA_F; s4.cell(r, 6).border=BORDER
|
||
# G: 差额一般 = E - C
|
||
s4.cell(r, 7, f'=E{sr}-C{sr}').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: 认定技术方案 ═══
|
||
s5=wb.create_sheet('认定技术方案')
|
||
TECH_COLS=['所属国别','项目名称','方案名称','编制单位','工程类别','方案等级','工程特点/说明','计划开工日期']
|
||
write_data_sheet(s5,tech_valid.reset_index(drop=True),
|
||
f'2026年度公司认定技术方案明细(ⅠⅡⅢ类·{tech_tot}项)',TECH_COLS)
|
||
|
||
# ═══ S6-S10b: 公式(GROUPBY用于分组聚合 + COUNTIF用于多指标混合) ═══
|
||
|
||
# ── 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.cell(4,1,f'=GROUPBY({REF}!K3:K200,{REF}!A3:A200,COUNTA,3,0)').font=FORMULA_F; s6.cell(4,1).border=BORDER
|
||
for w,c in zip([18,12],'AB'): s6.column_dimensions[c].width=w
|
||
# 提示行
|
||
s6.merge_cells('A10:C10'); s6.cell(10,1,'💡 若WPS显示@前缀,选中单元格→删除@即可正常溢出').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.cell(4,1,f'=GROUPBY({REF}!C3:C200,{REF}!A3:A200,COUNTA,3,0,-2)').font=FORMULA_F; s7.cell(4,1).border=BORDER
|
||
for w,c in zip([30,12],'AB'): s7.column_dimensions[c].width=w
|
||
s7.merge_cells('A10:C10'); s7.cell(10,1,'💡 若WPS显示@前缀,选中单元格→删除@即可正常溢出').font=GRAY_F; s7.cell(10,1).fill=INFO_BG
|
||
|
||
# ── S8: 公式-审批进度(多指标混合·COUNTIF) ──
|
||
s8=wb.create_sheet('公式-审批进度')
|
||
s8.merge_cells('A1:C1'); s8.cell(1,1,'OA审批进度 & 预警(COUNTIF公式)').font=TITLE_F; s8.cell(1,1).border=GOLD_BD
|
||
hdr_row(s8,3,['指标','数值','备注'])
|
||
rows8 = [
|
||
('方案总数',f'=COUNTA({REF}!A4:A200)','≥2026年开工'),
|
||
('已完成审批',f'=COUNTIF({REF}!Z4:Z200,TRUE)','含"已完成"状态'),
|
||
('未完成审批',f'=COUNTIF({REF}!Z4:Z200,FALSE)','审批中+未审批'),
|
||
('🟠 橙色预警',f'=COUNTIF({REF}!AD4:AD200,"orange")','距开工≤30天'),
|
||
('🟡 黄色预警',f'=COUNTIF({REF}!AD4:AD200,"yellow")','距开工≤45天'),
|
||
('预警合计','=B7+B8','橙色+黄色'),
|
||
]
|
||
for ri,(lab,fm,note) in enumerate(rows8):
|
||
r=ri+4
|
||
s8.cell(r,1,lab).font=DATA_F; s8.cell(r,1).border=BORDER
|
||
s8.cell(r,2,fm).font=FORMULA_F; s8.cell(r,2).border=BORDER
|
||
s8.cell(r,3,note).font=GRAY_F; s8.cell(r,3).border=BORDER
|
||
for w,c in zip([18,10,35],'ABC'): s8.column_dimensions[c].width=w
|
||
|
||
# ── S9: 公式-预警明细 ── FILTER 动态筛选
|
||
s9=wb.create_sheet('公式-预警明细')
|
||
s9.merge_cells('A1:C1'); s9.cell(1,1,'OA预警明细(FILTER动态筛选)').font=TITLE_F; s9.cell(1,1).border=GOLD_BD
|
||
s9.merge_cells('A2:C2'); s9.cell(2,1,'=FILTER(有效≥2026!A3:AD200,有效≥2026!AD3:AD200<>"none","无预警")').font=GRAY_F
|
||
s9.cell(4,1,f'=FILTER({REF}!A3:AD200,{REF}!AD3:AD200<>"none","🎉 无预警项")').font=FORMULA_F; s9.cell(4,1).border=BORDER
|
||
for w,c in zip([22],'A'): s9.column_dimensions[c].width=w
|
||
s9.merge_cells('A10:C10'); s9.cell(10,1,'💡 若WPS显示@前缀,选中单元格→删除@即可正常溢出').font=GRAY_F; s9.cell(10,1).fill=INFO_BG
|
||
|
||
# ── 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.cell(4,1,f'=GROUPBY({CREF}!H3:H200,{CREF}!D3:D200,COUNTA,3,0)').font=FORMULA_F; s10.cell(4,1).border=BORDER
|
||
for w,c in zip([20,12],'AB'): s10.column_dimensions[c].width=w
|
||
s10.merge_cells('A10:C10'); s10.cell(10,1,'💡 若WPS显示@前缀,选中单元格→删除@即可正常溢出').font=GRAY_F; s10.cell(10,1).fill=INFO_BG
|
||
|
||
# ── S10b: 公式-技术方案分类 ──
|
||
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.cell(4,1,f'=GROUPBY({TREF}!F3:F200,{TREF}!C3:C200,COUNTA,3,0,-2)').font=FORMULA_F; s10b.cell(4,1).border=BORDER
|
||
for w,c in zip([12,12],'AB'): s10b.column_dimensions[c].width=w
|
||
s10b.merge_cells('A10:C10'); s10b.cell(10,1,'💡 若WPS显示@前缀,选中单元格→删除@即可正常溢出').font=GRAY_F; s10b.cell(10,1).fill=INFO_BG
|
||
|
||
# ═══ 静态汇总 ═══
|
||
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=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
|
||
|
||
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(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=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
|
||
|
||
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]),
|
||
('未完成审批',int(unfinished),f'{unfinished/tot*100:.0f}%','审批中+未审批',[DATA_F]*3+[GRAY_F]),
|
||
('🟠 橙色预警',int(orange),f'{orange/tot*100:.0f}%','≤30天',[ORANGE_F]*3+[GRAY_F]),
|
||
('🟡 黄色预警',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=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
|
||
|
||
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
|
||
vals=[{'orange':'🟠','yellow':'🟡','red':'🔴'}.get(row['预警信号'],''),f'{int(days)}天',
|
||
row['项目名称'],row['方案名称'],
|
||
row['方案状态_clean'] if pd.notna(row.get('方案状态_clean')) else row['方案状态'],
|
||
str(row['分部分项工程计划开工日期'])[:10],
|
||
'超规类' if row['是否超一定规模']=='是' else '一般类',row['所属国别']]
|
||
for c,v in enumerate(vals):
|
||
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-S3 数据源: OA{len(valid_all)}/{len(valid_2026)} + 认定{cert_tot}+技术{tech_tot}")
|
||
print(f" S4 公式-认定vsOA: COUNTIFS项目级对比")
|
||
print(f" S5 认定技术方案(表2): {tech_tot}行")
|
||
print(f" S6-S10b 公式: GROUPBY+COUNTIF+FILTER(@问题有提示行)")
|
||
print(f" S11-S14 静态汇总: 交叉验证") |