Files
rogaining_srv/migrate_gps_information.py
2025-08-29 09:11:20 +09:00

301 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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