- 공공기관2/3 작업본 + 오늘 제출 17곳 D~J 카테고리 셀병합 정상화 - 한국지역난방공사 옵션2(고아셀 F98 수정)+전행 높이17 - 제출_프리랜서2_2026-06-21.zip 생성(17개 xlsx, 2,468행) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
216 lines
10 KiB
Python
216 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
import openpyxl, copy, sys, shutil, os, datetime
|
|
from openpyxl.utils import get_column_letter
|
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
|
|
FN = '한국청소년상담복지개발원.xlsx'
|
|
BASE = 'https://www.kyci.or.kr'
|
|
BLUE = 'FFBDD7EE'
|
|
|
|
wb = openpyxl.load_workbook(FN)
|
|
ws = wb.active
|
|
MAXR = ws.max_row
|
|
NCOL = 27 # up to AA
|
|
|
|
# ---- resolve merged effective values ----
|
|
merged = list(ws.merged_cells.ranges)
|
|
def eff(r, c):
|
|
v = ws.cell(r, c).value
|
|
if v is not None:
|
|
return v
|
|
for rng in merged:
|
|
if rng.min_row <= r <= rng.max_row and rng.min_col <= c <= rng.max_col:
|
|
return ws.cell(rng.min_row, rng.min_col).value
|
|
return None
|
|
|
|
# capture style snapshots for original rows
|
|
def snap(r):
|
|
return [copy.copy(ws.cell(r, c)._style) for c in range(1, NCOL+1)]
|
|
|
|
# template data-row style = row 3
|
|
TPL = snap(3)
|
|
|
|
# ---- build flat records for rows 3..MAXR ----
|
|
# record = dict: vals[1..27] (1-indexed col -> value), style list, blue set (cols to paint blue), is_new
|
|
def base_record(r):
|
|
vals = {}
|
|
for c in range(1, NCOL+1):
|
|
if 4 <= c <= 10: # D..J use effective (merge-resolved)
|
|
vals[c] = eff(r, c)
|
|
else:
|
|
vals[c] = ws.cell(r, c).value
|
|
return {'vals': vals, 'style': snap(r), 'blue': set(), 'new': False}
|
|
|
|
def new_record(path_vals, leafcol, label, url, L, M, N, O, ext=False):
|
|
vals = {c: None for c in range(1, NCOL+1)}
|
|
vals[3] = '한국청소년상담복지개발원' # C
|
|
for c in range(4, 11):
|
|
vals[c] = path_vals.get(c) # inherit D..J path
|
|
vals[leafcol] = label
|
|
vals[11] = url # K
|
|
vals[12] = L
|
|
vals[13] = M
|
|
vals[14] = N
|
|
vals[15] = O
|
|
rec = {'vals': vals, 'style': [copy.copy(s) for s in TPL], 'blue': set(), 'new': True}
|
|
rec['blue'] = set(range(2, 16)) # B..O painted blue for new rows
|
|
return rec
|
|
|
|
# ---- expansion definitions (keyed by original sheet row) ----
|
|
def u(p):
|
|
if p.startswith('http'): return p
|
|
return BASE + p
|
|
|
|
# children: (label, url, L, M, N, O)
|
|
EXP = {
|
|
14: ('E', '사업소개', [('사업개요','/userSite/sub02_1_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_1_cont.asp','페이지',1,'어문','미부착')]),
|
|
15: ('E', '사업소개', [('사업개요','/userSite/sub02_14_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_14_cont.asp','페이지',1,'어문','미부착')]),
|
|
16: ('E', '사업소개', [('사업개요','/userSite/sub02_2_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_2_cont.asp','페이지',1,'어문','미부착'),
|
|
('홍보자료','/userSite/sub02_2_gallery.asp','페이지',1,'어문,이미지','미부착')]),
|
|
17: ('E', '사업소개', [('사업개요','/userSite/sub02_3_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_3_cont.asp','페이지',1,'어문','미부착'),
|
|
('홍보자료','/userSite/sub02_3_gallery.asp','페이지',1,'어문,이미지','미부착'),
|
|
('자료실','/userSite/sub02_3_file.asp','페이지',1,'어문','미부착')]),
|
|
18: ('E', '사업소개', [('사업개요','/userSite/sub02_4_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_4_cont.asp','페이지',1,'어문','미부착'),
|
|
('자료실','/userSite/sub02_4_file.asp','페이지',1,'어문','미부착')]),
|
|
19: ('E', '사업소개', [('사업개요','/userSite/sub02_6_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_6_cont.asp','페이지',1,'어문','미부착'),
|
|
('심리검사','/userSite/sub02_6_test.asp','페이지',1,'어문','미부착'),
|
|
('홍보자료','/userSite/sub02_6_gallery.asp','페이지',1,'어문,이미지','미부착')]),
|
|
20: ('E', '사업소개', [('사업개요','/userSite/sub02_7_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_7_cont.asp','페이지',1,'어문','미부착')]),
|
|
21: ('E', '사업소개', [('사업개요','/userSite/sub02_8_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_8_cont.asp','페이지',1,'어문','미부착')]),
|
|
22: ('E', '사업소개', [('사업개요','/userSite/sub02_10_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_10_cont.asp','페이지',1,'어문,이미지','미부착')]),
|
|
23: ('E', '사업소개', [('사업개요','/userSite/sub02_11_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_11_cont.asp','페이지',1,'어문','미부착'),
|
|
('권리교육 관련 자료','https://edu.kyci.or.kr/community/edu_files.asp','사이트',None,None,None)]),
|
|
24: ('E', '사업소개', [('사업개요','/userSite/sub02_13_info.asp','페이지',1,'어문','미부착'),
|
|
('주요내용','/userSite/sub02_13_cont.asp','페이지',1,'어문','미부착'),
|
|
('홍보자료','/userSite/sub02_13_gallery.asp','페이지',1,'어문,이미지','미부착')]),
|
|
45: ('E', '정보공개 제도란?', [
|
|
('제도의 필요성','/userSite/sub06_1_2.asp','페이지',1,'어문','미부착'),
|
|
('정보공개제도 도입배경','/userSite/sub06_1_3.asp','페이지',1,'어문','미부착'),
|
|
('정보공개 청구권자','/userSite/sub06_1_4.asp','페이지',1,'어문','미부착'),
|
|
('공공기관과 청구인의 의무','/userSite/sub06_1_5.asp','페이지',1,'어문','미부착'),
|
|
('정보공개 처리절차','/userSite/sub06_1_6.asp','페이지',1,'어문','미부착'),
|
|
('정보공개 방법','/userSite/sub06_1_7.asp','페이지',1,'어문','미부착'),
|
|
('정보공개 수수료','/userSite/sub06_1_8.asp','페이지',1,'어문','미부착'),
|
|
('불복구제 절차','/userSite/sub06_1_9.asp','페이지',1,'어문','미부착'),
|
|
('정보공개 대상 정보와 비공개 정보','/userSite/sub06_1_10.asp','페이지',1,'어문','미부착'),
|
|
('관련 법령','/boardManagement/Board.asp?bid=bid_35','게시판',3,'어문','미부착'),
|
|
('청구 서식','/boardManagement/Board.asp?bid=bid_36','게시판',6,'어문','미부착')]),
|
|
52: ('E', '입찰정보', [('계약현황','/boardManagement/board.asp?bid=bid_6&menuCategory=6','게시판',580,'어문','미부착')]),
|
|
77: ('F', '상담직', [('행정직','/userSite/sub060304-2.asp','페이지',1,'어문','미부착'),
|
|
('운전직','/userSite/sub060304-3.asp','페이지',1,'어문','미부착')]),
|
|
}
|
|
COLIDX = {'D':4,'E':5,'F':6,'G':7,'H':8,'I':9,'J':10}
|
|
# self-row L/M corrections (입찰정보 is a board, existing was 페이지/1)
|
|
SELF_FIX = {52: {12:'게시판', 13:175}}
|
|
|
|
records = []
|
|
for r in range(3, MAXR+1):
|
|
rec = base_record(r)
|
|
if r in EXP:
|
|
leafletter, self_label, children = EXP[r]
|
|
leafcol = COLIDX[leafletter]
|
|
childcol = leafcol + 1
|
|
# set self label on the existing row at leaf+1
|
|
rec['vals'][childcol] = self_label
|
|
rec['blue'].add(childcol)
|
|
if r in SELF_FIX:
|
|
for c, v in SELF_FIX[r].items():
|
|
rec['vals'][c] = v
|
|
rec['blue'].add(c)
|
|
path = {c: rec['vals'][c] for c in range(4, 11)} # D..J of parent
|
|
path[childcol] = None # children carry parent path up to leafcol only
|
|
records.append(rec)
|
|
for (label, url, L, M, N, O) in children:
|
|
pv = {c: path.get(c) for c in range(4, 11)}
|
|
records.append(new_record(pv, childcol, label, u(url), L, M, N, O))
|
|
else:
|
|
records.append(rec)
|
|
|
|
print('original data rows:', MAXR-2, '-> new total:', len(records))
|
|
|
|
# ---- write to fresh sheet ----
|
|
title = ws.title
|
|
# copy header rows 1..2 first
|
|
hdr = {}
|
|
for r in (1,2):
|
|
for c in range(1, NCOL+1):
|
|
cell = ws.cell(r, c)
|
|
hdr[(r,c)] = (cell.value, copy.copy(cell._style))
|
|
hdr_merges = [str(rng) for rng in merged if rng.min_row <= 2]
|
|
colwidths = {k: v.width for k, v in ws.column_dimensions.items()}
|
|
|
|
del wb[title]
|
|
ns = wb.create_sheet(title)
|
|
wb.move_sheet(title, -(len(wb.sheetnames)-1))
|
|
|
|
# header
|
|
for (r,c),(v,st) in hdr.items():
|
|
cc = ns.cell(r, c); cc.value = v; cc._style = st
|
|
for m in hdr_merges:
|
|
ns.merge_cells(m)
|
|
for col, w in colwidths.items():
|
|
if w: ns.column_dimensions[col].width = w
|
|
|
|
# data
|
|
from openpyxl.styles import PatternFill, Font
|
|
bluefill = PatternFill(fill_type='solid', fgColor=BLUE, bgColor=BLUE)
|
|
linkfont = Font(name='맑은 고딕', size=10, color='0000FF', underline='single')
|
|
for i, rec in enumerate(records):
|
|
R = 3 + i
|
|
for c in range(1, NCOL+1):
|
|
cell = ns.cell(R, c)
|
|
cell._style = rec['style'][c-1]
|
|
cell.value = rec['vals'][c]
|
|
# B renumber continuous
|
|
ns.cell(R, 2).value = i + 1
|
|
# K hyperlink
|
|
kv = rec['vals'][11]
|
|
if kv and isinstance(kv, str) and kv.startswith('http'):
|
|
kc = ns.cell(R, 11)
|
|
kc.hyperlink = kv
|
|
kc.font = linkfont
|
|
# blue paint
|
|
for c in rec['blue']:
|
|
ns.cell(R, c).fill = bluefill
|
|
|
|
# ---- re-merge D..J by boundary-aware equal runs ----
|
|
DATA0 = 3
|
|
DATAN = 3 + len(records) - 1
|
|
def val(R, c):
|
|
return ns.cell(R, c).value
|
|
for c in range(4, 11): # D..J
|
|
R = DATA0
|
|
while R <= DATAN:
|
|
v = val(R, c)
|
|
if v is None or v == '':
|
|
R += 1; continue
|
|
r2 = R
|
|
while r2+1 <= DATAN and val(r2+1, c) == v and all(val(r2+1, pc) == val(R, pc) for pc in range(4, c)):
|
|
r2 += 1
|
|
if r2 > R:
|
|
for rr in range(R+1, r2+1):
|
|
ns.cell(rr, c).value = None
|
|
ns.merge_cells(start_row=R, start_column=c, end_row=r2, end_column=c)
|
|
R = r2 + 1
|
|
|
|
# freeze panes
|
|
ns.freeze_panes = 'A3'
|
|
|
|
# ---- save with backup ----
|
|
bdir = '_backup'
|
|
os.makedirs(bdir, exist_ok=True)
|
|
ts = datetime.datetime.fromtimestamp(os.path.getmtime(FN)).strftime('%Y%m%d_%H%M%S')
|
|
shutil.copy2(FN, os.path.join(bdir, f'_탭전개전_{ts}.xlsx'))
|
|
wb.save(FN)
|
|
print('SAVED', FN, 'rows', DATAN)
|