공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
128 lines
5.8 KiB
Python
128 lines
5.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
import openpyxl, glob, os, re, sys, json, shutil
|
|
from copy import copy
|
|
from openpyxl.styles import PatternFill
|
|
BASE=os.path.dirname(os.path.abspath(__file__))
|
|
NO=int(sys.argv[1]); APPLY='--apply' in sys.argv
|
|
BLUE=PatternFill(fill_type='solid', fgColor='FFBDD7EE')
|
|
MAXC=27
|
|
trees=json.load(open(os.path.join(BASE,'_gnb_trees.json'),encoding='utf-8'))
|
|
g=trees[str(NO)]; base=g['base']
|
|
def findfile(no):
|
|
for d in glob.glob(os.path.join(BASE,'*')):
|
|
b=os.path.basename(d)
|
|
if os.path.isdir(d) and b.startswith(str(no)+'.'):
|
|
xs=[p for p in glob.glob(os.path.join(d,'*.xlsx')) if not os.path.basename(p).startswith(('_','~')) and 'conflict' not in os.path.basename(p)]
|
|
if xs: return xs[0]
|
|
fn=findfile(NO)
|
|
def norm(u):
|
|
if not u: return None
|
|
u=str(u).split('#')[0]
|
|
pg=re.search(r'pgmid=([A-Za-z0-9]+)', u, re.I) # 심평원 등: pgmid가 경로보다 식별자
|
|
if pg: return 'pgmid:'+pg.group(1).lower()
|
|
m=re.search(r'https?://[^/]+(/[^?]*)', u)
|
|
path=(m.group(1) if m else u).rstrip('/').lower()
|
|
qs=u.split('?',1)[1] if '?' in u else ''
|
|
params=[]
|
|
for kv in qs.split('&'):
|
|
if not kv or '=' not in kv: continue
|
|
k=kv.split('=',1)[0].lower()
|
|
if k.startswith('wt.') or k.startswith('utm') or k in ('returnurl','redirect'): continue
|
|
params.append(kv.lower())
|
|
return path+('?'+'&'.join(sorted(params)) if params else '')
|
|
# build GNB ordered leaf list: walk tree, track D/E/F path; a node is a 'row' if it has href(leaf-ish)
|
|
tree=g['tree']
|
|
rows_gnb=[] # (D,E,F,G,normpath)
|
|
path=[None]*8 # idx by lvl 1..7
|
|
for x in tree:
|
|
lvl=x['lvl']; t=x['t']; href=x['href']
|
|
if lvl>=len(path): continue
|
|
path[lvl]=t
|
|
for k in range(lvl+1,len(path)): path[k]=None
|
|
if href and href not in ('#','#a') and 'javascript' not in href:
|
|
full=href if href.startswith('http') else base+href
|
|
if base.split('//')[1].split('/')[0] in full: # internal only
|
|
rows_gnb.append((path[1],path[2],path[3],path[4],norm(full)))
|
|
gmap={}
|
|
for D,E,F,G,np in rows_gnb:
|
|
if np and np not in gmap: gmap[np]=(D,E,F,G)
|
|
print('GNB 내부리프 %d개 (유니크 %d)'%(len(rows_gnb),len(gmap)))
|
|
|
|
wb=openpyxl.load_workbook(fn); ws=wb.active
|
|
def eff(r,c):
|
|
v=ws.cell(r,c).value
|
|
if v is not None: return v
|
|
for mr in ws.merged_cells.ranges:
|
|
if mr.min_col==c and mr.min_row<=r<=mr.max_row: return ws.cell(mr.min_row,c).value
|
|
return None
|
|
data=[r for r in range(3,ws.max_row+1) if any(ws.cell(r,c).value not in (None,'') for c in (2,4,5,6,7,11))]
|
|
matched=0
|
|
for r in data:
|
|
if norm(ws.cell(r,11).value) in gmap: matched+=1
|
|
print('시트 %d행 중 GNB매칭 %d행 (%.0f%%)'%(len(data),matched,100*matched/len(data)))
|
|
gnb_order=list(dict.fromkeys(D for D,_,_,_,_ in rows_gnb))
|
|
print('--- GNB 대분류 순서 ---', gnb_order)
|
|
print('--- 현재 시트 대분류 ---', list(dict.fromkeys(eff(r,4) for r in data if eff(r,4)))[:15])
|
|
|
|
if not APPLY:
|
|
print('\nDRY. --apply 로 in-place relabel 실행.'); sys.exit(0)
|
|
|
|
mtime_before=os.path.getmtime(fn)
|
|
# capture records (keep sheet order), relabel matched D/E/F
|
|
stylesnap={(r,c):copy(ws.cell(r,c)._style) for r in data for c in range(1,MAXC+1)}
|
|
recs=[]
|
|
for r in data:
|
|
rec={'src':r,'mark':set()}
|
|
np=norm(ws.cell(r,11).value)
|
|
if np in gmap:
|
|
D,E,F,G=gmap[np]
|
|
rec['D'],rec['E'],rec['F']=D,E,F or None
|
|
rec[7]=G or None # GNB leaf G(4단)
|
|
if (eff(r,4),eff(r,5),ws.cell(r,6).value,ws.cell(r,7).value)!=(D,E,F,G): rec['mark'].update([4,5,6,7])
|
|
else:
|
|
rec['D']=eff(r,4); rec['E']=eff(r,5); rec['F']=ws.cell(r,6).value; rec[7]=ws.cell(r,7).value
|
|
for c in range(8,MAXC+1): rec[c]=ws.cell(r,c).value
|
|
rec['url']=ws.cell(r,11).value
|
|
hl=ws.cell(r,11).hyperlink; rec['hlink']=hl.target if hl else rec['url']
|
|
recs.append(rec)
|
|
# reorder: by GNB 대분류 order; within keep sheet order; unmatched D keep at end in sheet order
|
|
order_idx={d:i for i,d in enumerate(gnb_order)}
|
|
for i,rc in enumerate(recs): rc['oi']=i
|
|
recs.sort(key=lambda rc:(order_idx.get(rc['D'],999), rc['oi']))
|
|
# write
|
|
for mr in list(ws.merged_cells.ranges):
|
|
if mr.min_row>=3: ws.unmerge_cells(str(mr))
|
|
n=len(recs)
|
|
for i,rc in enumerate(recs):
|
|
r=3+i; src=rc['src']
|
|
for c in range(1,MAXC+1): ws.cell(r,c)._style=copy(stylesnap[(src,c)])
|
|
ws.cell(r,2).value=i+1; ws.cell(r,3).value=ws.cell(src,3).value
|
|
ws.cell(r,4).value=rc['D']; ws.cell(r,5).value=rc['E']; ws.cell(r,6).value=rc['F']
|
|
for c in range(7,MAXC+1): ws.cell(r,c).value=rc.get(c)
|
|
kc=ws.cell(r,11); kc.value=rc['url']; kc.hyperlink=rc['hlink'] if rc['url'] else None
|
|
for c in rc['mark']: ws.cell(r,c).fill=BLUE
|
|
ws.row_dimensions[r].height=15
|
|
for r in range(3+n, ws.max_row+1):
|
|
for c in range(1,MAXC+1):
|
|
cell=ws.cell(r,c); cell.value=None; cell.hyperlink=None; cell.fill=PatternFill(fill_type=None)
|
|
def remerge(col,scope):
|
|
r=3
|
|
while r<3+n:
|
|
v=ws.cell(r,col).value
|
|
if v is None or v=='': r+=1; continue
|
|
r2=r
|
|
while r2+1<3+n and ws.cell(r2+1,col).value==v and all(ws.cell(r2+1,s).value==ws.cell(r,s).value for s in scope): r2+=1
|
|
if r2>r:
|
|
for rr in range(r+1,r2+1): ws.cell(rr,col).value=None
|
|
ws.merge_cells(start_row=r,start_column=col,end_row=r2,end_column=col)
|
|
r=r2+1
|
|
remerge(7,[4,5,6]); remerge(6,[4,5]); remerge(5,[4]); remerge(4,[])
|
|
if os.path.getmtime(fn)!=mtime_before:
|
|
print('ABORT: file changed'); sys.exit(1)
|
|
bdir=os.path.join(os.path.dirname(fn),'_backup'); os.makedirs(bdir,exist_ok=True)
|
|
shutil.copy(fn, os.path.join(bdir, os.path.basename(fn).replace('.xlsx','_backup_GNB재라벨전.xlsx')))
|
|
wb.save(fn)
|
|
# verify
|
|
bs=[ws.cell(r,2).value for r in range(3,3+n)]
|
|
print('saved %d행 B연속%s'%(n,bs==list(range(1,n+1))))
|