#!/usr/bin/env python3 """ CSVからサークルデータのJSONを生成するスクリプト """ import os import sys import csv import json sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from utils.logger import setup_logger from utils.json import get_file_path_from_config, json_dumps # --- ロギングの設定 --- log = setup_logger(__name__) def parse_bool(value: str) -> bool: """文字列をブール値に変換""" return value.lower() in ("true", "1", "yes") def parse_int(value: str) -> int | None: """文字列を整数に変換(空の場合はNone)""" if not value or value.strip() == "": return None try: return int(value) except ValueError: return None def parse_list(value: str, delimiter: str = "|") -> list[str]: """区切り文字で分割してリストに変換""" if not value or value.strip() == "": return [] return [item.strip() for item in value.split(delimiter) if item.strip()] def parse_json(value: str) -> list | dict | None: """JSON文字列をパースする(空の場合はNone、失敗した場合は空リスト)""" if not value or value.strip() == "": return None try: return json.loads(value) except json.JSONDecodeError: log.warning(f"JSONのパースに失敗しました: {value[:50]}...") return [] def convert_annual_schedule(old_schedule: list | None) -> list | None: """ 旧形式の年間予定を新形式に変換する。 旧形式: [{"month": 4, "events": ["新歓", "春季リーグ開幕"]}, ...] 新形式: [{"period": "4月", "event": "新歓"}, {"period": "4月", "event": "春季リーグ開幕"}, ...] """ if not old_schedule: return None new_schedule = [] for item in old_schedule: # 旧形式(month + events)の場合 if "month" in item and "events" in item: month = item["month"] events = item["events"] period = f"{month}月" for event in events: new_schedule.append({"period": period, "event": event}) # 新形式(period + event)の場合はそのまま elif "period" in item and "event" in item: new_schedule.append(item) return new_schedule if new_schedule else None def main(): """CSVからサークルデータを読み込み circles.json を生成する。""" log.info("CSVファイルからサークルデータを読み込みcircles.jsonを生成します。") input_csv_path = get_file_path_from_config( "circles.original_csv", "resources/original_circles.csv" ) output_json_path = get_file_path_from_config( "circles.circles_json", "data/generated/circles.json" ) if not os.path.exists(input_csv_path): log.error(f"入力CSVファイルが見つかりません: {input_csv_path}") sys.exit(1) circles = [] try: with open(input_csv_path, "r", encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: # 画像データのパース images_data = parse_json(row.get("images", "")) images = images_data if images_data else [] # mainImage の決定: images配列の最初の画像(order=0)を使用 main_image = None if images: # order=0 の画像を探す for img in images: if img.get("order", 0) == 0: main_image = img break # order=0 がなければ最初の画像を使用 if main_image is None: main_image = images[0] # SNSデータのパース(handleフィールドを含む) sns_data = parse_json(row.get("sns", "")) sns = [] if sns_data: for sns_item in sns_data: sns.append({ "type": sns_item.get("type", ""), "url": sns_item.get("url", ""), "handle": sns_item.get("handle") }) # 年間予定のパース(新形式に変換) annual_schedule_data = parse_json(row.get("annualSchedule", "")) annual_schedule = convert_annual_schedule(annual_schedule_data) circle = { "circleId": row.get("circleId", ""), "circleName": row.get("circleName", ""), "circleNameKana": row.get("circleNameKana", ""), "projectId": row.get("projectId") or None, "projectName": row.get("projectName") or None, "genre": row.get("genre") or None, "areaCode": row.get("areaCode") or None, "pamphletNumber": parse_int(row.get("pamphletNumber", "")), "shortIntro": row.get("shortIntro") or None, "detailDescription": row.get("detailDescription") or None, "memberCount": parse_int(row.get("memberCount", "")), "memberNote": row.get("memberNote") or None, "activityFrequency": row.get("activityFrequency") or None, "activityFrequencyNote": row.get("activityFrequencyNote") or None, "activityLocation": row.get("activityLocation") or None, "annualFee": parse_int(row.get("annualFee", "")), "otherCosts": row.get("otherCosts") or None, "annualSchedule": annual_schedule, "contactInfo": row.get("contactInfo") or None, "websiteUrl": row.get("websiteUrl") or None, "isArchived": parse_bool(row.get("isArchived", "false")), "images": images, "mainImage": main_image, "sns": sns, } circles.append(circle) except Exception as exc: log.error(f"CSVファイルの読み込みに失敗しました: {exc}") sys.exit(1) os.makedirs(os.path.dirname(output_json_path), exist_ok=True) json_dumps(circles, output_json_path) log.info(f"サークルデータの生成が完了しました: {output_json_path} ({len(circles)}件)") sys.exit(0) if __name__ == "__main__": main()