diff --git a/tools/data-check/festival_region_check.py b/tools/data-check/festival_region_check.py new file mode 100644 index 0000000..413900d --- /dev/null +++ b/tools/data-check/festival_region_check.py @@ -0,0 +1,103 @@ +"""축제 지역 조회 진단 — 대구 축제·행사가 90일 동안 1건만 잡힌 이유가 데이터 부족인지 조회 조건 때문인지 본다. + +세 가지로 불러 비교한다(searchFestival2, 5~6회 호출). + A. 지역(대구 27) + 시작일 + 종료일 ← 동기화가 쓰는 조건 + B. 지역(대구 27) + 시작일만 ← 종료일 조건의 영향 + C. 전국 + 시작일 + 종료일 ← 지역 코드가 비었거나 다르게 들어간 대구 행사가 있는지(주소로 확인) + +키는 출력하지 않는다(public_data_check 의 call 이 가린다). +실행(저장소 루트에서): python -I -X utf8 tools/data-check/festival_region_check.py backend/.env.development +""" +import collections +import importlib.util +import re +import sys +from datetime import timedelta +from pathlib import Path + + +def _load(name): + spec = importlib.util.spec_from_file_location(name, Path(__file__).with_name(f'{name}.py')) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +c1 = _load('public_data_check') +DAEGU, GYEONGBUK = '27', '47' +LOOKAHEAD_DAYS = 90 + + +def fetch(key, label, params): + out, page = [], 1 + while page <= 10: + d = c1.call(key, label, f'{c1.TOUR}/searchFestival2', + {**c1.COMMON, **params, 'numOfRows': 500, 'pageNo': page, 'arrange': 'A'}) + its = c1.items_of(d) + out += its + if not its or len(out) >= (c1.total_of(d) or 0): + break + page += 1 + return out + + +def line(it): + return (f"{it.get('title')} ({it.get('eventstartdate')}–{it.get('eventenddate')}) " + f"지역코드={it.get('lDongRegnCd') or '(없음)'} 분류={it.get('lclsSystm2') or '-'} 주소={str(it.get('addr1') or '')[:24]}") + + +def main(env_path): + key = c1.load_key(env_path) + today = c1.kst_now() + start = today.strftime('%Y%m%d') + end = (today + timedelta(days=LOOKAHEAD_DAYS)).strftime('%Y%m%d') + print(f'# 축제 지역 조회 진단 — 기간 {start} ~ {end}\n') + + a = fetch(key, '축제 A 대구+종료일', {'eventStartDate': start, 'eventEndDate': end, 'lDongRegnCd': DAEGU}) + b = fetch(key, '축제 B 대구', {'eventStartDate': start, 'lDongRegnCd': DAEGU}) + c = fetch(key, '축제 C 전국+종료일', {'eventStartDate': start, 'eventEndDate': end}) + + print(f'A. 대구 + 종료일 조건: {len(a)}건') + print(f'B. 대구 (종료일 조건 없음): {len(b)}건') + print(f'C. 전국 + 종료일 조건: {len(c)}건') + + by_code = collections.Counter(str(it.get('lDongRegnCd') or '(없음)') for it in c) + print('\nC 의 시·도 코드 분포(상위 10):', by_code.most_common(10)) + no_code = [it for it in c if not it.get('lDongRegnCd')] + # '해운대구' 처럼 '대구'가 글자로만 들어간 주소를 거르려고 시·도 이름으로 시작하는지 본다 + daegu_by_addr = [it for it in c if re.match(r'\s*대구(광역시)?\s', str(it.get('addr1') or ''))] + daegu_by_code = [it for it in c if str(it.get('lDongRegnCd')) == DAEGU] + gb = [it for it in c if str(it.get('lDongRegnCd')) == GYEONGBUK] + print(f'C 중 지역 코드가 빈 행사: {len(no_code)}건') + print(f'C 중 대구 — 코드 기준 {len(daegu_by_code)}건 · 주소 기준 {len(daegu_by_addr)}건') + print(f'C 중 경북(47): {len(gb)}건') + + long_run = [it for it in c if it.get('eventstartdate') and it.get('eventenddate') + and (int(str(it['eventenddate'])[:8]) - int(str(it['eventstartdate'])[:8])) > 200] + print(f'C 중 기간이 매우 긴 행사(대략 2개월 이상): {len(long_run)}건') + + print('\n대구로 보이는 행사(주소 또는 코드):') + seen = set() + for it in daegu_by_addr + daegu_by_code + [i for i in b if i not in daegu_by_code]: + cid = it.get('contentid') + if cid in seen: + continue + seen.add(cid) + print(' -', line(it)) + if no_code: + print('\n지역 코드가 빈 행사 예(5):') + for it in no_code[:5]: + print(' -', line(it)) + print('\n경북 행사 예(8):') + for it in gb[:8]: + print(' -', line(it)) + + print(f'\n호출 수: {c1.calls}') + if c1.errors: + print(f'오류 {len(c1.errors)}건:') + for e in c1.errors[:5]: + print(' -', e) + + +if __name__ == '__main__': + main(sys.argv[1] if len(sys.argv) > 1 else 'backend/.env.development')