import sys, io sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') import openpyxl from openpyxl.utils import get_column_letter ws = openpyxl.load_workbook(r'D:\01.프로젝트\DB수집\작업파일\_제출패키지_2026-06-03\프리\충청남도_부여군.xlsx'.replace('프리','프리랜서1')).active 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 def raw(r,c): 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,'')] dset=set(data) # For each category column, find a standalone value cell followed by >=1 None-unmerged cell # under the SAME parent (cols < c effective equal) -> an UNMERGED group (merge removed). print('=== 병합이 풀린(또는 누락된) 카테고리 그룹 후보 ===') found=[] for c in (4,5,6,7): # D,E,F,G for r in data: if merge_of(r,c): continue # this cell is in a merge -> ok v=raw(r,c) if v in (None,''): continue # need a value to be the group head # scan down for trailing None-unmerged under same parent (cols < c effective same) parent=tuple(eff(r,cc) for cc in range(4,c)) run=[] r2=r+1 while r2 in dset: # parent must match if tuple(eff(r2,cc) for cc in range(4,c))!=parent: break cell_in_merge=merge_of(r2,c) cv=raw(r2,c) if cv in (None,'') and not cell_in_merge: run.append(r2); r2+=1; continue break if run: found.append((c,r,run[-1],v,len(run)+1)) for c,a,z,v,n in found: col=get_column_letter(c) print(f' {col}{a}:{col}{z} 값={v!r} (행 {a}~{z}, {n}행이 병합 안 됨) D={eff(a,4)!r} E={eff(a,5)!r} F={eff(a,6)!r}') if not found: print(' 없음') # Also: list every None+unmerged cell in D/E/F/G that sits under a value (orphan blanks) print('\n=== D/E/F/G 빈칸+비병합 셀(고아 빈칸) 전수 ===') for c in (4,5,6,7): col=get_column_letter(c) orph=[r for r in data if raw(r,c) in (None,'') and not merge_of(r,c)] # filter: those that have a value somewhere above in same column within 50 rows (likely should inherit) print(f' {col}: {len(orph)}개', orph[:30])