# -*- 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'))