공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
89 lines
4.4 KiB
Python
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'))
|