Fix migration

This commit is contained in:
2025-08-25 18:49:33 +09:00
parent 6886ba15c8
commit 8e3f7024a2
12 changed files with 2254 additions and 20 deletions

406
migration_statistics.py Normal file
View File

@ -0,0 +1,406 @@
#!/usr/bin/env python3
"""
移行結果統計情報表示スクリプト
Docker Compose環境で実行可能
"""
import os
import sys
import psycopg2
from datetime import datetime, timedelta
import pytz
from collections import defaultdict
import json
def connect_database():
"""データベースに接続"""
try:
conn = psycopg2.connect(
host=os.environ.get('DB_HOST', 'postgres-db'),
port=os.environ.get('DB_PORT', '5432'),
database=os.environ.get('POSTGRES_DBNAME', 'rogdb'),
user=os.environ.get('POSTGRES_USER', 'admin'),
password=os.environ.get('POSTGRES_PASS', 'admin123456')
)
return conn
except Exception as e:
print(f"❌ データベース接続エラー: {e}")
return None
def get_basic_statistics(cursor):
"""基本統計情報を取得"""
print("\n" + "="*80)
print("📊 移行データ基本統計情報")
print("="*80)
# テーブル別レコード数
tables = [
('rog_newevent2', 'イベント'),
('rog_team', 'チーム'),
('rog_member', 'メンバー'),
('rog_entry', 'エントリー'),
('rog_gpscheckin', 'GPSチェックイン'),
('rog_checkpoint', 'チェックポイント'),
('rog_location2025', 'ロケーション2025'),
('rog_customuser', 'ユーザー')
]
print("\n📋 テーブル別レコード数:")
print("テーブル名 日本語名 レコード数")
print("-" * 65)
total_records = 0
for table_name, japanese_name in tables:
try:
cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
count = cursor.fetchone()[0]
total_records += count
print(f"{table_name:<25} {japanese_name:<15} {count:>10,}")
except Exception as e:
print(f"{table_name:<25} {japanese_name:<15} {'エラー':>10}")
print("-" * 65)
print(f"{'合計':<41} {total_records:>10,}")
def get_event_statistics(cursor):
"""イベント別統計情報"""
print("\n" + "="*80)
print("🎯 イベント別統計情報")
print("="*80)
# イベント一覧と基本情報
cursor.execute("""
SELECT id, event_name, event_date, created_at
FROM rog_newevent2
ORDER BY event_date DESC
""")
events = cursor.fetchall()
if not events:
print("イベントデータがありません")
return
print(f"\n📅 登録イベント数: {len(events)}")
print("\nイベント詳細:")
print("ID イベント名 開催日 登録日時")
print("-" * 60)
for event in events:
event_id, event_name, event_date, created_at = event
event_date_str = event_date.strftime("%Y-%m-%d") if event_date else "未設定"
created_str = created_at.strftime("%Y-%m-%d %H:%M") if created_at else "未設定"
print(f"{event_id:>2} {event_name:<15} {event_date_str} {created_str}")
# イベント別参加統計
cursor.execute("""
SELECT
e.event_name,
COUNT(DISTINCT t.id) as team_count,
COUNT(DISTINCT m.id) as member_count,
COUNT(DISTINCT en.id) as entry_count
FROM rog_newevent2 e
LEFT JOIN rog_team t ON e.id = t.event_id
LEFT JOIN rog_member m ON t.id = m.team_id
LEFT JOIN rog_entry en ON t.id = en.team_id
GROUP BY e.id, e.event_name
ORDER BY team_count DESC
""")
participation_stats = cursor.fetchall()
print("\n👥 イベント別参加統計:")
print("イベント名 チーム数 メンバー数 エントリー数")
print("-" * 55)
total_teams = 0
total_members = 0
total_entries = 0
for stat in participation_stats:
event_name, team_count, member_count, entry_count = stat
total_teams += team_count
total_members += member_count
total_entries += entry_count
print(f"{event_name:<15} {team_count:>6}{member_count:>8}{entry_count:>9}")
print("-" * 55)
print(f"{'合計':<15} {total_teams:>6}{total_members:>8}{total_entries:>9}")
def get_gps_checkin_statistics(cursor):
"""GPSチェックイン統計"""
print("\n" + "="*80)
print("📍 GPSチェックイン統計情報")
print("="*80)
# 基本統計
cursor.execute("""
SELECT
COUNT(*) as total_checkins,
COUNT(DISTINCT zekken) as unique_teams,
COUNT(DISTINCT cp_number) as unique_checkpoints,
MIN(checkin_time) as earliest_checkin,
MAX(checkin_time) as latest_checkin
FROM rog_gpscheckin
""")
basic_stats = cursor.fetchone()
if not basic_stats or basic_stats[0] == 0:
print("GPSチェックインデータがありません")
return
total_checkins, unique_teams, unique_checkpoints, earliest, latest = basic_stats
print(f"\n📊 基本統計:")
print(f"総チェックイン数: {total_checkins:,}")
print(f"参加チーム数: {unique_teams:,}チーム")
print(f"利用CP数: {unique_checkpoints:,}箇所")
if earliest and latest:
print(f"最早チェックイン: {earliest.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"最終チェックイン: {latest.strftime('%Y-%m-%d %H:%M:%S')}")
# 時間帯別分析
cursor.execute("""
SELECT
EXTRACT(hour FROM checkin_time) as hour,
COUNT(*) as count
FROM rog_gpscheckin
GROUP BY EXTRACT(hour FROM checkin_time)
ORDER BY hour
""")
hourly_stats = cursor.fetchall()
print(f"\n⏰ 時間帯別チェックイン分布:")
print("時間 チェックイン数 グラフ")
print("-" * 40)
max_count = max([count for _, count in hourly_stats]) if hourly_stats else 1
for hour, count in hourly_stats:
bar_length = int((count / max_count) * 20)
bar = "" * bar_length
print(f"{int(hour):>2}{count:>8}{bar}")
# CP別利用統計上位10位
cursor.execute("""
SELECT
cp_number,
COUNT(*) as checkin_count,
COUNT(DISTINCT zekken) as team_count
FROM rog_gpscheckin
GROUP BY cp_number
ORDER BY checkin_count DESC
LIMIT 10
""")
cp_stats = cursor.fetchall()
print(f"\n🏅 CP利用ランキング上位10位:")
print("順位 CP番号 チェックイン数 利用チーム数")
print("-" * 40)
for i, (cp_number, checkin_count, team_count) in enumerate(cp_stats, 1):
print(f"{i:>2}位 CP{cp_number:>3} {checkin_count:>8}{team_count:>7}チーム")
def get_team_statistics(cursor):
"""チーム統計"""
print("\n" + "="*80)
print("👥 チーム統計情報")
print("="*80)
# チーム基本統計
cursor.execute("""
SELECT
COUNT(*) as total_teams,
COUNT(DISTINCT class_name) as unique_classes,
AVG(CASE WHEN member_count > 0 THEN member_count END) as avg_members
FROM (
SELECT
t.id,
t.class_name,
COUNT(m.id) as member_count
FROM rog_team t
LEFT JOIN rog_member m ON t.id = m.team_id
GROUP BY t.id, t.class_name
) team_stats
""")
team_basic = cursor.fetchone()
total_teams, unique_classes, avg_members = team_basic
print(f"\n📊 基本統計:")
print(f"総チーム数: {total_teams:,}チーム")
print(f"クラス数: {unique_classes or 0}種類")
print(f"平均メンバー数: {avg_members:.1f}人/チーム" if avg_members else "平均メンバー数: データなし")
# クラス別統計
cursor.execute("""
SELECT
COALESCE(class_name, '未分類') as class_name,
COUNT(*) as team_count,
COUNT(CASE WHEN member_count > 0 THEN 1 END) as active_teams
FROM (
SELECT
t.class_name,
COUNT(m.id) as member_count
FROM rog_team t
LEFT JOIN rog_member m ON t.id = m.team_id
GROUP BY t.id, t.class_name
) team_stats
GROUP BY class_name
ORDER BY team_count DESC
""")
class_stats = cursor.fetchall()
if class_stats:
print(f"\n🏆 クラス別チーム数:")
print("クラス名 チーム数 アクティブ")
print("-" * 35)
for class_name, team_count, active_teams in class_stats:
print(f"{class_name:<15} {team_count:>6}{active_teams:>7}")
def get_data_quality_check(cursor):
"""データ品質チェック"""
print("\n" + "="*80)
print("🔍 データ品質チェック")
print("="*80)
checks = []
# 1. 重複チェック
cursor.execute("""
SELECT COUNT(*) FROM (
SELECT zekken, cp_number, checkin_time, COUNT(*)
FROM rog_gpscheckin
GROUP BY zekken, cp_number, checkin_time
HAVING COUNT(*) > 1
) duplicates
""")
duplicate_count = cursor.fetchone()[0]
checks.append(("重複チェックイン", duplicate_count, ""))
# 2. 異常時刻チェック0時台
cursor.execute("""
SELECT COUNT(*) FROM rog_gpscheckin
WHERE EXTRACT(hour FROM checkin_time) = 0
""")
zero_hour_count = cursor.fetchone()[0]
checks.append(("0時台チェックイン", zero_hour_count, ""))
# 3. 未来日時チェック
cursor.execute("""
SELECT COUNT(*) FROM rog_gpscheckin
WHERE checkin_time > NOW()
""")
future_count = cursor.fetchone()[0]
checks.append(("未来日時チェックイン", future_count, ""))
# 4. メンバー不在チーム
cursor.execute("""
SELECT COUNT(*) FROM rog_team t
LEFT JOIN rog_member m ON t.id = m.team_id
WHERE m.id IS NULL
""")
no_member_teams = cursor.fetchone()[0]
checks.append(("メンバー不在チーム", no_member_teams, "チーム"))
# 5. エントリー不在チーム
cursor.execute("""
SELECT COUNT(*) FROM rog_team t
LEFT JOIN rog_entry e ON t.id = e.team_id
WHERE e.id IS NULL
""")
no_entry_teams = cursor.fetchone()[0]
checks.append(("エントリー不在チーム", no_entry_teams, "チーム"))
print("\n🧪 品質チェック結果:")
print("チェック項目 件数 状態")
print("-" * 40)
for check_name, count, unit in checks:
status = "✅ 正常" if count == 0 else "⚠️ 要確認"
print(f"{check_name:<15} {count:>6}{unit} {status}")
def export_statistics_json(cursor):
"""統計情報をJSONで出力"""
print("\n" + "="*80)
print("📄 統計情報JSON出力")
print("="*80)
statistics = {}
# 基本統計
cursor.execute("SELECT COUNT(*) FROM rog_gpscheckin")
statistics['total_checkins'] = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(DISTINCT zekken) FROM rog_gpscheckin")
statistics['unique_teams'] = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(*) FROM rog_newevent2")
statistics['total_events'] = cursor.fetchone()[0]
# イベント別統計
cursor.execute("""
SELECT event_name, COUNT(*) as checkin_count
FROM rog_newevent2 e
LEFT JOIN rog_team t ON e.id = t.event_id
LEFT JOIN rog_gpscheckin g ON t.zekken = g.zekken
GROUP BY e.id, event_name
ORDER BY checkin_count DESC
""")
event_stats = {}
for event_name, count in cursor.fetchall():
event_stats[event_name] = count
statistics['event_checkins'] = event_stats
statistics['generated_at'] = datetime.now().isoformat()
# ファイル出力
output_file = f"/app/migration_statistics_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
try:
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(statistics, f, ensure_ascii=False, indent=2)
print(f"✅ 統計情報をJSONで出力しました: {output_file}")
except Exception as e:
print(f"❌ JSON出力エラー: {e}")
def main():
"""メイン処理"""
print("🚀 移行結果統計情報表示スクリプト開始")
print(f"実行時刻: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
# データベース接続
conn = connect_database()
if not conn:
sys.exit(1)
try:
cursor = conn.cursor()
# 各統計情報を表示
get_basic_statistics(cursor)
get_event_statistics(cursor)
get_gps_checkin_statistics(cursor)
get_team_statistics(cursor)
get_data_quality_check(cursor)
export_statistics_json(cursor)
print("\n" + "="*80)
print("✅ 統計情報表示完了")
print("="*80)
except Exception as e:
print(f"❌ 統計処理中にエラーが発生しました: {e}")
import traceback
traceback.print_exc()
finally:
cursor.close()
conn.close()
if __name__ == "__main__":
main()