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_rowz): 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_rowez): 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무병합·헤더병합 정상)')