#!/usr/bin/env python3 """ GPS情報(通過データ)移行スクリプト gifurogeのgps_informationテーブルから新しいrogdbシステムに通過データを移行 """ import os import sys import django from datetime import datetime import psycopg2 from django.utils import timezone from django.db import transaction # Django設定 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') django.setup() from rog.models import ( GpsLog, GpsCheckin, CheckinExtended, Entry, NewEvent2, CustomUser, Team, Waypoint, Location2025 ) class GpsInformationMigrator: def __init__(self): # 環境変数から接続情報を取得 self.gifuroge_conn_params = { 'host': os.environ.get('PG_HOST', 'postgres-db'), 'database': 'gifuroge', 'user': os.environ.get('POSTGRES_USER', 'postgres'), 'password': os.environ.get('POSTGRES_PASS', 'password'), 'port': os.environ.get('PG_PORT', 5432), } # 統計情報 self.stats = { 'total_gps_info': 0, 'migrated_gps_logs': 0, 'migrated_checkins': 0, 'skipped_records': 0, 'errors': 0, 'error_details': [] } def connect_to_gifuroge(self): """gifurogeデータベースに接続""" try: conn = psycopg2.connect(**self.gifuroge_conn_params) return conn except Exception as e: print(f"❌ gifurogeデータベース接続エラー: {e}") return None def get_gps_information_data(self): """gifurogeのgps_informationデータを取得""" conn = self.connect_to_gifuroge() if not conn: return [] try: cursor = conn.cursor() # まずテーブル構造を確認 cursor.execute(""" SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'gps_information' AND table_schema = 'public' ORDER BY ordinal_position; """) columns = cursor.fetchall() print("=== gps_information テーブル構造 ===") for col in columns: print(f"- {col[0]}: {col[1]}") # データ数確認 cursor.execute("SELECT COUNT(*) FROM gps_information;") total_count = cursor.fetchone()[0] self.stats['total_gps_info'] = total_count print(f"\n📊 gps_information 総レコード数: {total_count}") if total_count == 0: print("⚠️ gps_informationテーブルにデータがありません") return [] # 全データを取得(テーブル構造に合わせて修正) cursor.execute(""" SELECT serial_number, zekken_number, event_code, cp_number, image_address, goal_time, late_point, create_at, create_user, update_at, update_user, buy_flag, minus_photo_flag, colabo_company_memo FROM gps_information ORDER BY create_at, serial_number; """) data = cursor.fetchall() print(f"✅ {len(data)}件のgps_informationデータを取得しました") return data except Exception as e: print(f"❌ データ取得エラー: {e}") self.stats['errors'] += 1 self.stats['error_details'].append(f"データ取得エラー: {e}") return [] finally: if conn: conn.close() def find_matching_entry(self, zekken_number, event_code): """ゼッケン番号とイベントコードからEntryを検索""" try: # NewEvent2でイベントを検索 events = NewEvent2.objects.filter(event_name__icontains=event_code) if not events.exists(): # イベントコードの部分一致で検索 events = NewEvent2.objects.filter( event_name__icontains=event_code.replace('_', ' ') ) for event in events: # ゼッケン番号でEntryを検索 entries = Entry.objects.filter( event=event, zekken_number=zekken_number ) if entries.exists(): return entries.first() # 見つからない場合はNone return None except Exception as e: print(f"⚠️ Entry検索エラー (ゼッケン: {zekken_number}, イベント: {event_code}): {e}") return None def find_matching_location(self, cp_number): """CP番号からLocationを検索""" try: if not cp_number: return None # Location2025から検索 locations = Location2025.objects.filter(cp_number=cp_number) if locations.exists(): return locations.first() # 部分一致で検索 locations = Location2025.objects.filter(cp_number__icontains=str(cp_number)) if locations.exists(): return locations.first() return None except Exception as e: print(f"⚠️ Location検索エラー (CP: {cp_number}): {e}") return None def migrate_gps_record(self, record): """個別のGPS記録を移行""" try: (serial_number, zekken_number, event_code, cp_number, image_address, goal_time, late_point, create_at, create_user, update_at, update_user, buy_flag, minus_photo_flag, colabo_company_memo) = record # checkin_timeはcreate_atを使用 checkin_time = create_at or timezone.now() # Entryを検索 entry = self.find_matching_entry(zekken_number, event_code) if not entry: print(f"⚠️ Entry未発見: ゼッケン{zekken_number}, イベント{event_code}") self.stats['skipped_records'] += 1 return False # Locationを検索(オプション) location = self.find_matching_location(cp_number) if cp_number else None # 既存のGpsLogをチェック existing_log = GpsLog.objects.filter( zekken_number=str(zekken_number), event_code=event_code, checkin_time=checkin_time ).first() if existing_log: print(f"⚠️ 既存記録をスキップ: ゼッケン{zekken_number}, {checkin_time}") self.stats['skipped_records'] += 1 return False # GpsLogを作成 gps_log = GpsLog.objects.create( serial_number=serial_number or 0, zekken_number=str(zekken_number), event_code=event_code, cp_number=str(cp_number) if cp_number else '', image_address=image_address or '', checkin_time=checkin_time, goal_time=goal_time or '', late_point=late_point or 0, create_at=create_at or timezone.now(), create_user=create_user or '', update_at=update_at or timezone.now(), update_user=update_user or '', buy_flag=buy_flag or False, minus_photo_flag=minus_photo_flag or False, colabo_company_memo=colabo_company_memo or '', is_service_checked=False, # デフォルト値 score=0, # デフォルト値 scoreboard_url='' # デフォルト値 ) self.stats['migrated_gps_logs'] += 1 # CheckinExtendedも作成(通過記録として) if cp_number and location: try: checkin_extended = CheckinExtended.objects.create( entry=entry, location=location, checkin_time=checkin_time, image_url=image_address or '', score_override=0, # デフォルト値 notes=f"移行データ: {colabo_company_memo}", is_verified=False # デフォルト値 ) self.stats['migrated_checkins'] += 1 print(f"✅ チェックイン記録作成: ゼッケン{zekken_number}, CP{cp_number}") except Exception as e: print(f"⚠️ CheckinExtended作成エラー: {e}") print(f"✅ GPS記録移行完了: ゼッケン{zekken_number}, {checkin_time}") return True except Exception as e: print(f"❌ GPS記録移行エラー: {e}") self.stats['errors'] += 1 self.stats['error_details'].append(f"GPS記録移行エラー: {e}") return False def run_migration(self): """メイン移行処理""" print("🚀 GPS情報移行スクリプト開始") print("=" * 50) # gifurogeからデータを取得 gps_data = self.get_gps_information_data() if not gps_data: print("❌ 移行するデータがありません") return print(f"\n📋 {len(gps_data)}件のGPS記録の移行を開始...") # バッチ処理で移行 batch_size = 100 total_batches = (len(gps_data) + batch_size - 1) // batch_size for batch_num in range(total_batches): start_idx = batch_num * batch_size end_idx = min(start_idx + batch_size, len(gps_data)) batch_data = gps_data[start_idx:end_idx] print(f"\n📦 バッチ {batch_num + 1}/{total_batches} ({len(batch_data)}件) 処理中...") with transaction.atomic(): for record in batch_data: self.migrate_gps_record(record) # 統計レポート self.print_migration_report() def print_migration_report(self): """移行結果レポート""" print("\n" + "=" * 50) print("📊 GPS情報移行完了レポート") print("=" * 50) print(f"📋 総GPS記録数: {self.stats['total_gps_info']}") print(f"✅ 移行済みGpsLog: {self.stats['migrated_gps_logs']}") print(f"✅ 移行済みCheckin: {self.stats['migrated_checkins']}") print(f"⚠️ スキップ記録: {self.stats['skipped_records']}") print(f"❌ エラー数: {self.stats['errors']}") if self.stats['error_details']: print("\n❌ エラー詳細:") for error in self.stats['error_details'][:10]: # 最初の10個だけ表示 print(f" - {error}") if len(self.stats['error_details']) > 10: print(f" ... 他 {len(self.stats['error_details']) - 10} 件") success_rate = (self.stats['migrated_gps_logs'] / max(self.stats['total_gps_info'], 1)) * 100 print(f"\n📈 移行成功率: {success_rate:.1f}%") print("=" * 50) def main(): """メイン実行関数""" migrator = GpsInformationMigrator() migrator.run_migration() if __name__ == '__main__': main()