128 lines
5.4 KiB
Python
128 lines
5.4 KiB
Python
"""
|
||
Аналитика чата и экспорт данных
|
||
"""
|
||
import json
|
||
import logging
|
||
from datetime import datetime, timedelta, timezone
|
||
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)
|
||
self.uly_tz = timezone(timedelta(hours=4))
|
||
|
||
async def get_general_stats(self) -> Dict[str, Any]:
|
||
"""Общая статистика чата (Оригинальная логика из Gitea)"""
|
||
# Всего пользователей
|
||
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_susp = select(func.count(User.user_id)).where(User.spy_score >= 31).where(User.spy_score <= 60)
|
||
suspicious_count = (await self.session.execute(stmt_susp)).scalar() or 0
|
||
|
||
stmt_spies = select(func.count(User.user_id)).where(User.spy_score > 60)
|
||
spies_count = (await self.session.execute(stmt_spies)).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 'нет данных'
|
||
return ', '.join([f'{i}. {r[1] or f"user{r[0]}"}: {r[2]}' for i, r in enumerate(rows, 1)])
|
||
|
||
async def export_to_json(self, full_history: bool = False) -> str:
|
||
"""
|
||
Экспорт в JSON.
|
||
full_history=False -> за неделю (для планировщика)
|
||
full_history=True -> всё (для кнопки в меню)
|
||
"""
|
||
now_local = datetime.now(self.uly_tz)
|
||
prefix = "full_" if full_history else "weekly_"
|
||
filename = f'chat_export_{prefix}{now_local.strftime("%Y%m%d_%H%M%S")}.json'
|
||
filepath = self.export_dir / filename
|
||
|
||
# Пользователи
|
||
users = list((await self.session.execute(select(User).order_by(User.user_id))).scalars().all())
|
||
|
||
# Сообщения
|
||
stmt = select(Message).order_by(Message.timestamp.asc())
|
||
if not full_history:
|
||
week_ago = datetime.utcnow() - timedelta(days=7)
|
||
stmt = stmt.where(Message.timestamp >= week_ago)
|
||
|
||
messages = list((await self.session.execute(stmt)).scalars().all())
|
||
|
||
export_data = {
|
||
'export_date': now_local.isoformat(),
|
||
'is_full_history': full_history,
|
||
'summary': {
|
||
'total_users': len(users),
|
||
'total_messages': len(messages),
|
||
},
|
||
'users': [{
|
||
'user_id': u.user_id, 'username': u.username, 'first_name': u.first_name,
|
||
'apartment': u.apartment, 'verified': u.verified, 'rating': u.rating
|
||
} for u in users],
|
||
'messages': [{
|
||
'id': m.id, 'user_id': m.user_id, 'text': m.text,
|
||
'date': m.timestamp.isoformat() if m.timestamp else None, 'topic': m.topic
|
||
} for m in messages]
|
||
}
|
||
|
||
with open(filepath, 'w', encoding='utf-8') as f:
|
||
json.dump(export_data, f, ensure_ascii=False, indent=2)
|
||
|
||
return str(filepath)
|