Fix migration
This commit is contained in:
406
migration_statistics.py
Normal file
406
migration_statistics.py
Normal 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()
|
||||
Reference in New Issue
Block a user