217 lines
8 KiB
Python
217 lines
8 KiB
Python
"""
|
||
Аналитика чата и экспорт данных
|
||
"""
|
||
import json
|
||
import logging
|
||
from datetime import datetime, timedelta
|
||
from typing import Dict, List, Any
|
||
from pathlib import Path
|
||
from sqlalchemy import select, func, distinct
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from database.models import User, Message
|
||
from config import EXPORT_DIR
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class ChatAnalytics:
|
||
"""Аналитика чата"""
|
||
|
||
def __init__(self, session: AsyncSession):
|
||
self.session = session
|
||
self.export_dir = Path(EXPORT_DIR)
|
||
self.export_dir.mkdir(parents=True, exist_ok=True)
|
||
|
||
async def get_general_stats(self) -> Dict[str, Any]:
|
||
"""Общая статистика чата"""
|
||
# Всего пользователей
|
||
stmt = select(func.count(User.user_id))
|
||
result = await self.session.execute(stmt)
|
||
total_users = result.scalar() or 0
|
||
|
||
# Верифицированы
|
||
stmt = select(func.count(User.user_id)).where(User.verified == True)
|
||
result = await self.session.execute(stmt)
|
||
verified_users = result.scalar() or 0
|
||
|
||
# Сообщения всего
|
||
stmt = select(func.count(Message.id))
|
||
result = await self.session.execute(stmt)
|
||
total_messages = result.scalar() or 0
|
||
|
||
# Сообщения за неделю
|
||
week_ago = datetime.utcnow() - timedelta(days=7)
|
||
stmt = (
|
||
select(func.count(Message.id))
|
||
.where(Message.timestamp >= week_ago)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
messages_week = result.scalar() or 0
|
||
|
||
# Сообщения за день
|
||
day_ago = datetime.utcnow() - timedelta(days=1)
|
||
stmt = (
|
||
select(func.count(Message.id))
|
||
.where(Message.timestamp >= day_ago)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
messages_day = result.scalar() or 0
|
||
|
||
# Подозрительные
|
||
stmt = (
|
||
select(func.count(User.user_id))
|
||
.where(User.spy_score >= 31)
|
||
.where(User.spy_score <= 60)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
suspicious_count = result.scalar() or 0
|
||
|
||
# Шпионы
|
||
stmt = (
|
||
select(func.count(User.user_id))
|
||
.where(User.spy_score > 60)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
spies_count = result.scalar() or 0
|
||
|
||
# Топ пользователей за неделю
|
||
top_users = await self._get_top_users(week_ago, limit=5)
|
||
|
||
return {
|
||
'total_users': total_users,
|
||
'verified_users': verified_users,
|
||
'unverified_users': total_users - verified_users,
|
||
'total_messages': total_messages,
|
||
'messages_week': messages_week,
|
||
'messages_day': messages_day,
|
||
'suspicious_count': suspicious_count,
|
||
'spies_count': spies_count,
|
||
'top_users': top_users,
|
||
}
|
||
|
||
async def _get_top_users(self, since: datetime, limit: int = 5) -> str:
|
||
"""Топ пользователей по активности"""
|
||
stmt = (
|
||
select(User.user_id, User.first_name, func.count(Message.id).label('count'))
|
||
.join(Message, User.user_id == Message.user_id)
|
||
.where(Message.timestamp >= since)
|
||
.group_by(User.user_id, User.first_name)
|
||
.order_by(func.count(Message.id).desc())
|
||
.limit(limit)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
rows = result.all()
|
||
|
||
if not rows:
|
||
return 'нет данных'
|
||
|
||
lines = []
|
||
for i, (user_id, name, count) in enumerate(rows, 1):
|
||
display_name = name or f'user{user_id}'
|
||
lines.append(f'{i}. {display_name}: {count}')
|
||
|
||
return ', '.join(lines)
|
||
|
||
async def export_to_json(self) -> str:
|
||
"""
|
||
Экспорт данных чата в JSON
|
||
Возвращает путь к файлу
|
||
"""
|
||
timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
|
||
filename = f'chat_export_{timestamp}.json'
|
||
filepath = self.export_dir / filename
|
||
|
||
# Получаем всех пользователей
|
||
stmt = select(User).order_by(User.join_date.desc())
|
||
result = await self.session.execute(stmt)
|
||
users = list(result.scalars().all())
|
||
|
||
# Получаем сообщения за последнюю неделю
|
||
week_ago = datetime.utcnow() - timedelta(days=7)
|
||
stmt = (
|
||
select(Message)
|
||
.where(Message.timestamp >= week_ago)
|
||
.order_by(Message.timestamp.desc())
|
||
.limit(10000) # Ограничение на количество
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
messages = list(result.scalars().all())
|
||
|
||
# Формируем экспорт
|
||
export_data = {
|
||
'export_date': datetime.utcnow().isoformat(),
|
||
'chat_id': None, # Заполнится при использовании
|
||
'period': {
|
||
'from': week_ago.isoformat(),
|
||
'to': datetime.utcnow().isoformat(),
|
||
},
|
||
'summary': {
|
||
'total_users': len(users),
|
||
'verified_users': sum(1 for u in users if u.verified),
|
||
'total_messages': len(messages),
|
||
},
|
||
'users': [
|
||
{
|
||
'user_id': u.user_id,
|
||
'username': u.username,
|
||
'first_name': u.first_name,
|
||
'last_name': u.last_name,
|
||
'apartment': u.apartment,
|
||
'verified': u.verified,
|
||
'join_date': u.join_date.isoformat() if u.join_date else None,
|
||
'message_count': u.message_count,
|
||
'spy_score': u.spy_score,
|
||
'spy_flags': json.loads(u.spy_flags) if u.spy_flags else [],
|
||
}
|
||
for u in users
|
||
],
|
||
'messages': [
|
||
{
|
||
'message_id': m.message_id,
|
||
'user_id': m.user_id,
|
||
'text': m.text[:500] if m.text else None, # Обрезаем длинные
|
||
'timestamp': m.timestamp.isoformat() if m.timestamp else None,
|
||
'reply_to': m.reply_to_message_id,
|
||
}
|
||
for m in messages
|
||
]
|
||
}
|
||
|
||
# Сохраняем
|
||
with open(filepath, 'w', encoding='utf-8') as f:
|
||
json.dump(export_data, f, ensure_ascii=False, indent=2)
|
||
|
||
logger.info(f'Экспорт сохранён: {filepath}')
|
||
return str(filepath)
|
||
|
||
async def get_activity_report(self, days: int = 7) -> Dict[str, Any]:
|
||
"""Отчёт по активности за период"""
|
||
since = datetime.utcnow() - timedelta(days=days)
|
||
|
||
# Активные пользователи
|
||
stmt = (
|
||
select(distinct(Message.user_id))
|
||
.where(Message.timestamp >= since)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
active_users = len(list(result.scalars().all()))
|
||
|
||
# Самые активные часы
|
||
stmt = (
|
||
select(
|
||
func.strftime('%H', Message.timestamp).label('hour'),
|
||
func.count().label('count')
|
||
)
|
||
.where(Message.timestamp >= since)
|
||
.group_by(func.strftime('%H', Message.timestamp))
|
||
.order_by(func.count().desc())
|
||
.limit(5)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
peak_hours = [(row.hour, row.count) for row in result.all()]
|
||
|
||
return {
|
||
'active_users': active_users,
|
||
'peak_hours': peak_hours,
|
||
'period_days': days,
|
||
}
|