DB_JOB/_스크립트/_gongju_split_all.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

165 lines
6.0 KiB
Python

# -*- coding: utf-8 -*-
"""335~443행 일반 다단계 탭 분해. _split_plan.json 의 29개 잡을 적용.
- 각 잡: 소스행의 가장 깊은 카테고리열(base)을 찾아, 자식을 base+1, 손자를 base+2에 배치
- 잡은 행번호 내림차순(아래→위)으로 처리해 미처리 잡 인덱스 불변
- 검증된 시프트 로직(값+_style+hyperlink, 병합 +delta 시프트, base열 신규 병합)
- 순번(B)은 마지막에 305~끝 일괄 재번호
--write 로 적용.
"""
import sys, json, shutil
from copy import copy
import openpyxl
from openpyxl.utils.cell import range_boundaries
XLSX = r'D:\01.프로젝트\DB수집\작업파일\광역_사이트맵\충청남도\2.공주시\충청남도_공주시.xlsx'
PLAN = r'D:\01.프로젝트\DB수집\_스크립트\_split_plan.json'
B_START_ROW = 305
B_START_VAL = 311
CATCOLS = [4, 5, 6, 7, 8, 9, 10] # D..J
def resolve_path(ws, row):
"""소스행의 카테고리 경로(병합 반영). {col: value}"""
path = {}
for c in CATCOLS:
v = ws.cell(row, c).value
if v is None:
# 병합 top-left 찾기
for mr in ws.merged_cells.ranges:
if mr.min_col <= c <= mr.max_col and mr.min_row <= row <= mr.max_row:
v = ws.cell(mr.min_row, mr.min_col).value
break
if v is not None:
path[c] = v
return path
def flatten_leaves(job):
"""잡 children 트리 -> 잎 리스트 [(lvl1name, lvl2name|None, url, L, M)]"""
out = []
for ch in job['children']:
if ch.get('children'):
for gc in ch['children']:
out.append((ch['name'], gc['name'], gc['url'], gc.get('L', '페이지'), gc.get('M', 1)))
else:
out.append((ch['name'], None, ch['url'], ch.get('L', '페이지'), ch.get('M', 1)))
return out
def apply_job(ws, source, leaves, tmpl, last_data):
N = len(leaves)
delta = N - 1
base = max(resolve_path(ws, source).keys()) # 가장 깊은 카테고리열
base_val = ws.cell(source, base).value
if base_val is None:
p = resolve_path(ws, source); base_val = p[base]
# 1) 병합 해제(전체) — 시프트 위해
old_merges = [str(mr) for mr in list(ws.merged_cells.ranges)]
for mr in old_merges:
ws.unmerge_cells(mr)
# 2) 시프트 source+1..last_data -> +delta (아래에서 위로)
if delta > 0:
for sr in range(last_data, source, -1):
dr = sr + delta
for c in range(1, 28):
s = ws.cell(sr, c); d = ws.cell(dr, c)
d.value = s.value
if s.has_style:
d._style = copy(s._style)
if s.hyperlink is not None:
d.hyperlink = copy(s.hyperlink); d.hyperlink.ref = d.coordinate
s.hyperlink = None
else:
d.hyperlink = None
# 3) source..source+N-1 클리어 + 템플릿 스타일
for r in range(source, source + N):
for c in range(1, 28):
cell = ws.cell(r, c)
cell.value = None; cell.hyperlink = None
if c in tmpl:
cell._style = copy(tmpl[c])
# 4) 잎 기입
l1col, l2col = base + 1, base + 2
prev_l1 = None
for i, (l1, l2, url, L, M) in enumerate(leaves):
r = source + i
if l1 != prev_l1:
ws.cell(r, l1col).value = l1
prev_l1 = l1
if l2:
ws.cell(r, l2col).value = l2
kc = ws.cell(r, 11); kc.value = url; kc.hyperlink = url
ws.cell(r, 12).value = L
ws.cell(r, 13).value = M
ws.cell(r, 14).value = '어문'
ws.cell(r, 15).value = '미부착'
# base 값 최상단
ws.cell(source, base).value = base_val
# 5) 병합 재생성: 기존(>=source+1행 +delta) + 신규
def shift(rr):
return rr + delta if rr >= source + 1 else rr
for rng in old_merges:
c1, r1, c2, r2 = range_boundaries(rng)
ws.merge_cells(start_row=shift(r1), start_column=c1, end_row=shift(r2), end_column=c2)
# base열 신규 병합(잡 전체)
if N > 1:
ws.merge_cells(start_row=source, start_column=base, end_row=source + N - 1, end_column=base)
# l1col 손자그룹 병합(같은 l1 연속 run)
i = 0
while i < N:
j = i
while j + 1 < N and leaves[j + 1][0] == leaves[i][0]:
j += 1
if j > i: # 2개 이상 -> 병합
ws.merge_cells(start_row=source + i, start_column=l1col, end_row=source + j, end_column=l1col)
i = j + 1
return last_data + delta
def main():
write = '--write' in sys.argv
jobs = json.load(open(PLAN, encoding='utf-8'))
jobs.sort(key=lambda j: j['row'], reverse=True) # 내림차순(아래→위)
wb = openpyxl.load_workbook(XLSX)
ws = wb.active
# 템플릿 스타일(클린 데이터행 305)
tmpl = {c: copy(ws.cell(305, c)._style) for c in range(1, 28) if ws.cell(305, c).has_style}
# 현재 마지막 데이터행
last_data = 3
for r in range(3, ws.max_row + 1):
if ws.cell(r, 11).value:
last_data = r
print('%d개, 시작 last_data=%d' % (len(jobs), last_data))
if not write:
for j in jobs:
print(' r%d %s -> 잎 %d' % (j['row'], j['label'], len(flatten_leaves(j))))
print('(DRY)')
return
bak = XLSX.replace('.xlsx', '_backup_분야별분해전.xlsx')
shutil.copy(XLSX, bak)
for j in jobs:
leaves = flatten_leaves(j)
last_data = apply_job(ws, j['row'], leaves, tmpl, last_data)
print(' 적용 r%d %s (+%d) last_data=%d' % (j['row'], j['label'][:24], len(leaves) - 1, last_data))
# 순번 B 305~last 일괄 재번호
for r in range(B_START_ROW, last_data + 1):
ws.cell(r, 2).value = B_START_VAL + (r - B_START_ROW)
wb.save(XLSX)
print('저장 완료. 백업:', bak, '| 최종 데이터행', last_data)
if __name__ == '__main__':
main()