DB_JOB/작업파일/_스크립트/_tab_batch_scan.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

89 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
"""
전체 사이트 탭 재스캔 배치 (읽기전용).
- 현황판(_작업현황.md)에서 기관/상태 파싱
- 각 폴더 대표 xlsx 찾아 _tab_scan.scan_xlsx 실행
- 신규탐지 결과 vs 기존 '탭없음0'/'탭확장' 비교 리포트 생성
출력: _temp/_tab_rescan_report.md, _tab_rescan_report.json
"""
import os, sys, re, json, glob, time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _tab_scan import scan_xlsx
sys.stdout.reconfigure(encoding='utf-8')
ROOT = r'D:\01.프로젝트\DB수집\작업파일\광역_사이트맵'
EXCLUDE = {'서천군'} # 사용자 작업중
# 검수완료(수정금지·읽기만) 목록
INSPECTED = {'계룡시','공주시','금산군','논산시','보령시','당진시','부여군',
'고창군','김제시','남원시','군산시','무주군'}
def pick_xlsx(folder):
cand = [f for f in glob.glob(os.path.join(folder,'*.xlsx'))
if 'backup' not in os.path.basename(f).lower()
and not os.path.basename(f).startswith('_')
and not os.path.basename(f).startswith('~$')]
# 광역_기관.xlsx 우선
cand.sort(key=lambda f: (0 if re.match(r'^[가-힣]+도?_', os.path.basename(f)) else 1, len(f)))
return cand[0] if cand else None
def site_name(folder):
return re.sub(r'^\d+\.', '', os.path.basename(folder))
jobs = []
for prov in sorted(os.listdir(ROOT)):
pdir = os.path.join(ROOT, prov)
if not os.path.isdir(pdir) or prov.startswith('__'): continue
for d in sorted(os.listdir(pdir), key=lambda s:(int(re.match(r'(\d+)',s).group(1)) if re.match(r'(\d+)',s) else 999)):
sdir = os.path.join(pdir, d)
if not os.path.isdir(sdir): continue
nm = site_name(sdir)
if nm in EXCLUDE: continue
xl = pick_xlsx(sdir)
if not xl: continue
jobs.append({'prov': prov, 'name': nm, 'xlsx': xl,
'inspected': nm in INSPECTED})
print(f'{len(jobs)}개 사이트 스캔 시작', flush=True)
results = []
for i, j in enumerate(jobs, 1):
t0 = time.time()
try:
out = scan_xlsx(j['xlsx'], workers=16)
j2 = {**j, 'ok': True, 'groups': out['tab_groups'], 'est_new': out['est_new_rows'],
'rows': out['data_rows'], 'pages': out['scanned_pages'],
'classes': list(out['container_classes'].keys())[:3],
'top': [(g['F'], g['missing']) for g in out['groups'][:5]]}
except Exception as e:
j2 = {**j, 'ok': False, 'err': str(e)[:80]}
j2['sec'] = round(time.time()-t0, 1)
results.append(j2)
tag = '읽기만' if j['inspected'] else '전개대상'
g = j2.get('groups','-'); en = j2.get('est_new','-')
print(f"[{i:>2}/{len(jobs)}] {j['prov'][:2]} {j['name']:6} ({tag}) groups={g} est_new={en} {'ERR '+j2.get('err','') if not j2['ok'] else ''} {j2['sec']}s", flush=True)
# report
TEMP = r'D:\01.프로젝트\DB수집\작업파일\_temp'
os.makedirs(TEMP, exist_ok=True)
json.dump(results, open(os.path.join(TEMP,'_tab_rescan_report.json'),'w',encoding='utf-8'), ensure_ascii=False, indent=1)
lines = ['# 탭 재스캔 리포트 (읽기전용)\n',
'> 새 구조기반 탐지기 결과. est_new=누락 탭(예상 추가행). 검수완료=읽기만(참고), 전개대상=실제 작업 후보.\n',
'| 광역 | 기관 | 분류 | 데이터행 | 탭그룹 | 예상+행 | 비고 |',
'|---|---|---|---|---|---|---|']
for r in sorted(results, key=lambda x:(x['prov'], x['name'])):
if not r['ok']:
lines.append(f"| {r['prov'][:2]} | {r['name']} | {'읽기만' if r['inspected'] else '전개대상'} | - | ERR | - | {r.get('err','')} |")
continue
cls = r['classes'][0] if r.get('classes') else ''
note = f"cls={cls}" if r['groups'] else "탭 0"
flag = ' ⚠️재확인' if (r['groups'] and not r['inspected']) else ''
lines.append(f"| {r['prov'][:2]} | {r['name']} | {'읽기만' if r['inspected'] else '전개대상'} | {r['rows']} | {r['groups']} | {r['est_new']}{flag} | {note} |")
# summary
act = [r for r in results if r['ok'] and not r['inspected']]
tot_new = sum(r['est_new'] for r in act)
lines.append('')
lines.append(f"**전개대상 합계**: {len([r for r in act if r['groups']])}곳에서 탭 발견 / 예상 +{tot_new}")
open(os.path.join(TEMP,'_tab_rescan_report.md'),'w',encoding='utf-8').write('\n'.join(lines))
print('\nDONE. report:', os.path.join(TEMP,'_tab_rescan_report.md'))