DB_JOB/작업파일/_스크립트/_audit_buyeo_merge.py
hehihoho3 df16c98366 백업: DB수집 전체 스냅샷 (공공기관2 정리 전)
공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 18:15:40 +09:00

118 lines
4.3 KiB
Python

import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
import openpyxl
from openpyxl.utils import get_column_letter
PKG = r'D:\01.프로젝트\DB수집\작업파일\_제출패키지_2026-06-03\프리랜서1\충청남도_부여군.xlsx'
ws = openpyxl.load_workbook(PKG).active
# merge lookup
merges = list(ws.merged_cells.ranges)
def merge_of(r,c):
for m in merges:
if m.min_row<=r<=m.max_row and m.min_col<=c<=m.max_col:
return m
return None
def eff(r,c):
m=merge_of(r,c)
if m: return ws.cell(m.min_row,m.min_col).value
return ws.cell(r,c).value
data=[r for r in range(3,ws.max_row+1) if ws.cell(r,2).value not in (None,'')]
issues=[]
# 1) header merges
hdr={str(m) for m in merges}
for h in ('B1:R1','S1:W1','Y1:AA1'):
if h not in hdr: issues.append(f'헤더병합 누락 {h}')
# 2) G+ no merge (col>=7)
gplus=[str(m) for m in merges if m.min_col>=7 and m.min_row>=3]
if gplus:
issues.append(f'[규칙위반] G이상 병합 {len(gplus)}개: {gplus[:20]}')
# helper: maximal runs of equal non-None value over a row list for a column
def runs(rows, col):
out=[];
for r in rows:
v=eff(r,col)
if out and out[-1][2]==v and v not in (None,'') and r==out[-1][1]+1:
out[-1][1]=r
else:
out.append([r,r,v])
return out
def exact_merge(col,a,z):
m=merge_of(a,col)
return m is not None and m.min_col==col==m.max_col and m.min_row==a and m.max_row==z
# 3) D column
druns=runs(data,4)
nD=0
for a,z,v in druns:
if v in (None,''):
issues.append(f'D 빈값 행 r{a}'); continue
nD+=1
if z>a and not exact_merge(4,a,z):
m=merge_of(a,4)
issues.append(f'D런 {v!r} r{a}:{z} 병합불일치(현재 {m})')
if z==a:
m=merge_of(a,4)
if m and (m.max_row>m.min_row):
issues.append(f'D 단일행 r{a} {v!r} 인데 병합 {m}')
# split check: same D value appearing in non-adjacent runs
from collections import Counter
dval_runs=Counter(v for a,z,v in druns if v not in (None,''))
for v,cnt in dval_runs.items():
if cnt>1:
issues.append(f'[주의] D값 {v!r}{cnt}개 비연속 런으로 분리됨')
# 4) E within each D-run
for a,z,dv in druns:
drows=[r for r in data if a<=r<=z]
for ea,ez,ev in runs(drows,5):
if ev in (None,''): continue
# E merge must be within D-run
if ez>ea and not exact_merge(5,ea,ez):
m=merge_of(ea,5)
issues.append(f'E런 {ev!r} r{ea}:{ez}(D={dv!r}) 병합불일치(현재 {m})')
m=merge_of(ea,5)
if m and (m.min_row<a or m.max_row>z):
issues.append(f'[규칙위반] E병합 {m}이 D그룹 r{a}:{z}({dv!r}) 경계 넘음')
if ez==ea and m and m.max_row>m.min_row:
issues.append(f'E 단일행 r{ea} {ev!r} 인데 병합 {m}')
# 5) F within each D+E sub-run
for a,z,dv in druns:
drows=[r for r in data if a<=r<=z]
for ea,ez,ev in runs(drows,5):
erows=[r for r in drows if ea<=r<=ez] if ev not in (None,'') else [r for r in drows if eff(r,5) in (None,'')]
# group F within this E sub-run
sub=[r for r in drows if ea<=r<=ez]
for fa,fz,fv in runs(sub,6):
if fv in (None,''): continue
if fz>fa and not exact_merge(6,fa,fz):
m=merge_of(fa,6)
issues.append(f'F런 {fv!r} r{fa}:{fz}(D={dv!r},E={ev!r}) 병합불일치(현재 {m})')
m=merge_of(fa,6)
if m and (m.min_row<ea or m.max_row>ez):
issues.append(f'[규칙위반] F병합 {m}이 D+E그룹 r{ea}:{ez} 경계 넘음')
# 6) merge overlaps
seen={}; overlap=0
for m in merges:
for r in range(m.min_row,m.max_row+1):
for c in range(m.min_col,m.max_col+1):
if (r,c) in seen: overlap+=1
seen[(r,c)]=1
if overlap: issues.append(f'병합 겹침 셀 {overlap}')
print(f'데이터행 {len(data)} 대분류(D런) {nD}개 총병합 {len(merges)}')
print(f'D 병합수={sum(1 for m in merges if m.min_col==4)} E={sum(1 for m in merges if m.min_col==5)} F={sum(1 for m in merges if m.min_col==6)} G+={len(gplus)}')
print('대분류 목록:', [v for a,z,v in druns])
print()
if issues:
print(f'⚠ 병합 이슈 {len(issues)}건:')
for x in issues: print('', x)
else:
print('✅ 병합 규칙 위반 없음 (D/E/F 경계·연속병합·G무병합·헤더병합 정상)')