301 lines
12 KiB
Python
301 lines
12 KiB
Python
#!/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()
|