# -*- coding: utf-8 -*- """재검수 audit diff를 엑셀에 반영(정정). 신뢰분류 필터 적용. 적용 규칙(보수적): - L: 페이지→게시판 & newM>=3 → L=게시판, M=newM (실글수 확인된 실게시판) - L: 게시판→페이지 & oldM<=1 → L=페이지, M=1 (빈게시판/오분류) - N: 모든 newN 반영(현행 이미지규칙·노이즈제외 적용된 값) - 억제(미적용·로그만): 페이지→게시판 M<3(검색창/총계미파싱)·게시판→페이지 oldM>1(일시오류 의심) 백업 *_backup_재검수정정전.xlsx. 행수·구조 무변경(셀값만). 사용: python -X utf8 _재검수_apply.py <기관...> [--dry] """ import sys, os, io, json, shutil, importlib.util, re import openpyxl # 게시판 승격은 URL이 게시판 엔드포인트일 때만(내러티브 '총 N개' 오파싱·예산금액 차단) BOARD_URL = re.compile( r'selectBbsNttList|/bbs/|BBSMSTR|board/list|list\.buan|selectBoardList' r'|selectCntrct|selectPric|/list\.do|mode=list|nttList', re.I) M_CAP = 200000 # 글수가 이보다 크면 금액/통계 오파싱 의심 → 미적용 HERE = os.path.dirname(os.path.abspath(__file__)) TEMP = os.path.join(HERE, '..', '_temp') MODULES = ['_chungnam_phase234_all.py', '_chungbuk_phase234_all.py', '_jeonbuk_phase234_all.py'] def to_int(v): try: return int(v) except: return 0 def load_sites(): sites = {} for f in MODULES: spec = importlib.util.spec_from_file_location(f[:-3], os.path.join(HERE, f)) m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) sites.update(m.SITES) return sites def apply_inst(name, cfg, dry): jp = os.path.join(TEMP, f'_audit_{name}.json') if not os.path.exists(jp): return f'{name}: audit json 없음' d = json.load(open(jp, encoding='utf-8')) xlsx = cfg['xlsx'] wb = openpyxl.load_workbook(xlsx); ws = wb.active L_applied, L_skip, N_applied, mreview = [], [], [], [] for x in d.get('L_diffs', []): r, o, n, nm, om = x['r'], x['oldL'], x['newL'], to_int(x['newM']), to_int(x['oldM']) url = x.get('url', '') if o == '페이지' and n == '게시판' and 3 <= nm <= M_CAP and BOARD_URL.search(url): if not dry: ws.cell(r, 12).value = '게시판'; ws.cell(r, 13).value = nm L_applied.append((r, x['label'], f'페이지→게시판 M{om}→{nm}')) elif o == '게시판' and n == '페이지' and om <= 1: if not dry: ws.cell(r, 12).value = '페이지'; ws.cell(r, 13).value = 1 L_applied.append((r, x['label'], f'게시판→페이지 M{om}→1')) else: L_skip.append((r, x['label'], f'{o}→{n} M{om}→{nm}')) if o == '페이지' and n == '게시판': # 실게시판 가능성(M미파싱/URL불명/금액의심) mreview.append((r, x['label'], url, nm)) for x in d.get('N_diffs', []): r, n = x['r'], x['newN'] if not dry: ws.cell(r, 14).value = n N_applied.append((r, x['label'], f"{x['oldN']}→{n}")) if not dry and (L_applied or N_applied): bak = xlsx.replace('.xlsx', '_backup_재검수정정전.xlsx') if not os.path.exists(bak): shutil.copy(xlsx, bak) wb.save(xlsx) return {'name': name, 'L적용': len(L_applied), 'L억제': len(L_skip), 'N적용': len(N_applied), 'M검토필요': len(mreview), 'L_applied': L_applied, 'mreview': mreview} def main(): args = sys.argv[1:] dry = '--dry' in args names = [a for a in args if not a.startswith('--')] sites = load_sites() for name in names: if name not in sites: print(f'{name}: SITES에 없음'); continue res = apply_inst(name, sites[name], dry) if isinstance(res, str): print(res); continue print(f"{res['name']:6} {'[DRY]' if dry else '[적용]'} L적용{res['L적용']} L억제{res['L억제']} N적용{res['N적용']} M검토{res['M검토필요']}") for r, lab, msg in res['L_applied']: print(f' +L r{r} {(lab or "")[:24]} {msg}') if __name__ == '__main__': main()