Spaces:
Sleeping
Sleeping
| #!/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() | |