Spaces:
Running
Running
| """ | |
| Step 0: menu.csv → data/raw/real_menus.json 변환 | |
| 실행: python scripts/00_import_csv.py | |
| 필터 조건: | |
| - IS_VISIBLE_MENU == "true" | |
| - DEPTH1_NAME 존재 | |
| - DEPTH4_NAME에 "팝업" 미포함 | |
| menu_id = SCR_{SCREEN_NUM} | |
| menu_path: DEPTH2 == DEPTH1이면 중복 레벨 제거 | |
| """ | |
| import csv | |
| import json | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| from config import RAW_DIR | |
| CSV_PATH = Path(__file__).parent.parent.parent / "menu.csv" | |
| OUTPUT_PATH = RAW_DIR / "real_menus.json" | |
| def build_menu_path(row: dict) -> str: | |
| """DEPTH 계층에서 menu_path 생성. DEPTH2 == DEPTH1이면 DEPTH2 제거.""" | |
| d1 = (row.get("DEPTH1_NAME") or "").strip() | |
| d2 = (row.get("DEPTH2_NAME") or "").strip() | |
| d3 = (row.get("DEPTH3_NAME") or "").strip() | |
| d4 = (row.get("DEPTH4_NAME") or "").strip() | |
| # DEPTH2가 DEPTH1과 동일하면 중복 레벨 제거 | |
| if d2 == d1: | |
| d2 = "" | |
| parts = [p for p in [d1, d2, d3, d4] if p] | |
| return " > ".join(parts) | |
| def parse_keywords(raw: str) -> list: | |
| """MENU_KEYWORD 콤마 구분 파싱 → List[str]""" | |
| if not raw or not raw.strip(): | |
| return [] | |
| return [kw.strip() for kw in raw.split(",") if kw.strip()] | |
| def parse_search_count(raw: str) -> int: | |
| """SEARCH_COUNT 정수 변환 (없으면 0)""" | |
| try: | |
| return int(raw.strip()) if raw and raw.strip() else 0 | |
| except ValueError: | |
| return 0 | |
| def main(): | |
| if not CSV_PATH.exists(): | |
| print(f"[오류] menu.csv 파일을 찾을 수 없습니다: {CSV_PATH}") | |
| print("menu.csv를 프로젝트 루트(prototype 상위 폴더)에 위치시켜 주세요.") | |
| sys.exit(1) | |
| RAW_DIR.mkdir(parents=True, exist_ok=True) | |
| menus = [] | |
| skipped = {"invisible": 0, "no_depth1": 0, "popup": 0} | |
| screen_num_set = set() | |
| with open(CSV_PATH, encoding="utf-8-sig", newline="") as f: | |
| reader = csv.DictReader(f) | |
| for row in reader: | |
| # 필터 1: IS_VISIBLE_MENU == "true" | |
| if row.get("IS_VISIBLE_MENU", "").strip().lower() != "true": | |
| skipped["invisible"] += 1 | |
| continue | |
| # 필터 2: DEPTH1_NAME 존재 | |
| d1 = (row.get("DEPTH1_NAME") or "").strip() | |
| if not d1: | |
| skipped["no_depth1"] += 1 | |
| continue | |
| # 필터 3: 팝업 제외 | |
| d4 = (row.get("DEPTH4_NAME") or "").strip() | |
| if "팝업" in d4: | |
| skipped["popup"] += 1 | |
| continue | |
| screen_num = (row.get("SCREEN_NUM") or "").strip() | |
| if not screen_num: | |
| continue | |
| menu_id = f"SCR_{screen_num}" | |
| menu_name = (row.get("DEPTH4_NAME") or row.get("DEPTH3_NAME") or d1).strip() | |
| menu_path = build_menu_path(row) | |
| category = d1 | |
| keywords = parse_keywords(row.get("MENU_KEYWORD", "")) | |
| search_count = parse_search_count(row.get("SEARCH_COUNT", "")) | |
| # SCREEN_NUM 중복 체크 (이론상 없어야 하지만 안전 처리) | |
| if screen_num in screen_num_set: | |
| print(f" [경고] SCREEN_NUM 중복: {screen_num} ({menu_name}) 스킵") | |
| continue | |
| screen_num_set.add(screen_num) | |
| menus.append({ | |
| "menu_id": menu_id, | |
| "menu_name": menu_name, | |
| "menu_path": menu_path, | |
| "category": category, | |
| "screen_num": screen_num, | |
| "search_count": search_count, | |
| "keywords": keywords, | |
| }) | |
| OUTPUT_PATH.write_text( | |
| json.dumps(menus, ensure_ascii=False, indent=2), | |
| encoding="utf-8" | |
| ) | |
| print(f"변환 완료: {len(menus)}개 메뉴") | |
| print(f" 스킵 - IS_VISIBLE_MENU 제외: {skipped['invisible']}개") | |
| print(f" 스킵 - DEPTH1 없음: {skipped['no_depth1']}개") | |
| print(f" 스킵 - 팝업 제외: {skipped['popup']}개") | |
| print(f"저장: {OUTPUT_PATH}") | |
| # 카테고리별 집계 | |
| from collections import Counter | |
| cat_counts = Counter(m["category"] for m in menus) | |
| print("\n카테고리별 메뉴 수:") | |
| for cat, cnt in cat_counts.most_common(): | |
| print(f" {cat}: {cnt}개") | |
| # keywords 보유율 | |
| has_kw = sum(1 for m in menus if m["keywords"]) | |
| print(f"\nkeywords 보유: {has_kw}/{len(menus)} ({has_kw/len(menus)*100:.1f}%)") | |
| # search_count 보유율 | |
| has_sc = sum(1 for m in menus if m["search_count"] > 0) | |
| print(f"search_count > 0: {has_sc}/{len(menus)} ({has_sc/len(menus)*100:.1f}%)") | |
| if __name__ == "__main__": | |
| main() | |