domovoy_bot/services/analytics_reference.py

217 lines
8 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.

"""
Аналитика чата и экспорт данных
"""
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,
}