### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
This commit is contained in:
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,7 +1,29 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
## [0.9.16] - 2025-01-27
|
## [0.9.17] - 2025-08-31
|
||||||
|
|
||||||
|
### 👥 Author Statistics Enhancement
|
||||||
|
- **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat
|
||||||
|
- `topics`: Количество уникальных тем, в которых участвовал автор
|
||||||
|
- `coauthors`: Количество соавторов
|
||||||
|
- `replies_count`: Количество вызванных комментариев
|
||||||
|
- `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF)
|
||||||
|
- `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии)
|
||||||
|
- `replies_count`: Количество вызванных комментариев
|
||||||
|
- `comments`: Количество созданных комментариев и цитат
|
||||||
|
- `viewed_shouts`: Общее количество просмотров всех публикаций автора
|
||||||
|
- **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики
|
||||||
|
- **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом
|
||||||
|
- **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики
|
||||||
|
|
||||||
|
### 🔧 Technical Implementation
|
||||||
|
- **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков
|
||||||
|
- **Database**: Оптимизированные SQL-запросы с JOIN для статистики
|
||||||
|
- **Caching**: Интеграция с ViewedStorage для подсчёта просмотров
|
||||||
|
- **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
|
||||||
|
|
||||||
|
## [0.9.16] - 2025-08-31
|
||||||
|
|
||||||
### 🔍 Search System Revolution
|
### 🔍 Search System Revolution
|
||||||
- **🚀 Настоящие векторные эмбединги**: Заменил псевдослучайные hash-эмбединги на SentenceTransformers
|
- **🚀 Настоящие векторные эмбединги**: Заменил псевдослучайные hash-эмбединги на SentenceTransformers
|
||||||
|
|||||||
291
docs/author-statistics.md
Normal file
291
docs/author-statistics.md
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
# 📊 Система статистики авторов
|
||||||
|
|
||||||
|
Полная документация по расчёту и использованию статистики авторов в Discours.
|
||||||
|
|
||||||
|
## 🎯 Обзор
|
||||||
|
|
||||||
|
Система статистики авторов предоставляет многомерную оценку активности, популярности и вовлечённости каждого автора на платформе. Все метрики рассчитываются в реальном времени и кешируются для производительности.
|
||||||
|
|
||||||
|
## 📈 Метрики AuthorStat
|
||||||
|
|
||||||
|
```graphql
|
||||||
|
# Статистика автора - полная метрика активности и популярности
|
||||||
|
type AuthorStat {
|
||||||
|
# Контент автора
|
||||||
|
shouts: Int # Количество опубликованных статей
|
||||||
|
topics: Int # Количество уникальных тем, в которых участвовал
|
||||||
|
comments: Int # Количество созданных комментариев и цитат
|
||||||
|
|
||||||
|
# Взаимодействие с другими авторами
|
||||||
|
coauthors: Int # Количество уникальных соавторов
|
||||||
|
followers: Int # Количество подписчиков
|
||||||
|
|
||||||
|
# Рейтинговая система
|
||||||
|
rating: Int # Общий рейтинг (rating_shouts + rating_comments)
|
||||||
|
rating_shouts: Int # Рейтинг публикаций (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF)
|
||||||
|
rating_comments: Int # Рейтинг комментариев (реакции на комментарии автора)
|
||||||
|
|
||||||
|
# Метрики вовлечённости
|
||||||
|
replies_count: Int # Количество ответов на контент автора (ответы на комментарии + комментарии на посты)
|
||||||
|
viewed_shouts: Int # Общее количество просмотров всех публикаций автора
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📝 Контент автора
|
||||||
|
|
||||||
|
#### `shouts: Int`
|
||||||
|
**Количество опубликованных статей**
|
||||||
|
- Учитывает только статьи со статусом `published_at IS NOT NULL`
|
||||||
|
- Исключает удалённые статьи (`deleted_at IS NULL`)
|
||||||
|
- Подсчитывается через таблицу `shout_author`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT sa.author, COUNT(DISTINCT s.id) as shouts_count
|
||||||
|
FROM shout_author sa
|
||||||
|
JOIN shout s ON sa.shout = s.id
|
||||||
|
WHERE s.deleted_at IS NULL AND s.published_at IS NOT NULL
|
||||||
|
GROUP BY sa.author
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `topics: Int`
|
||||||
|
**Количество уникальных тем, в которых участвовал автор**
|
||||||
|
- Подсчитывает уникальные темы через связку статей автора
|
||||||
|
- Основано на таблицах `shout_author` → `shout_topic`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT sa.author, COUNT(DISTINCT st.topic) as topics_count
|
||||||
|
FROM shout_author sa
|
||||||
|
JOIN shout s ON sa.shout = s.id AND s.deleted_at IS NULL AND s.published_at IS NOT NULL
|
||||||
|
JOIN shout_topic st ON s.id = st.shout
|
||||||
|
GROUP BY sa.author
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `comments: Int`
|
||||||
|
**Количество созданных комментариев и цитат**
|
||||||
|
- Включает реакции типа `COMMENT` и `QUOTE`
|
||||||
|
- Исключает удалённые комментарии
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT r.created_by, COUNT(DISTINCT r.id) as comments_count
|
||||||
|
FROM reaction r
|
||||||
|
JOIN shout s ON r.shout = s.id AND s.deleted_at IS NULL
|
||||||
|
WHERE r.deleted_at IS NULL AND r.kind IN ('COMMENT', 'QUOTE')
|
||||||
|
GROUP BY r.created_by
|
||||||
|
```
|
||||||
|
|
||||||
|
### 👥 Взаимодействие с другими авторами
|
||||||
|
|
||||||
|
#### `coauthors: Int`
|
||||||
|
**Количество уникальных соавторов**
|
||||||
|
- Подсчитывает авторов, с которыми автор публиковал совместные статьи
|
||||||
|
- Исключает самого автора из подсчёта
|
||||||
|
- Учитывает только опубликованные и неудалённые статьи
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT sa1.author, COUNT(DISTINCT sa2.author) as coauthors_count
|
||||||
|
FROM shout_author sa1
|
||||||
|
JOIN shout s ON sa1.shout = s.id
|
||||||
|
AND s.deleted_at IS NULL
|
||||||
|
AND s.published_at IS NOT NULL
|
||||||
|
JOIN shout_author sa2 ON s.id = sa2.shout
|
||||||
|
AND sa2.author != sa1.author -- исключаем самого автора
|
||||||
|
GROUP BY sa1.author
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `followers: Int`
|
||||||
|
**Количество подписчиков**
|
||||||
|
- Прямой подсчёт из таблицы `author_follower`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT following, COUNT(DISTINCT follower) as followers_count
|
||||||
|
FROM author_follower
|
||||||
|
GROUP BY following
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⭐ Рейтинговая система
|
||||||
|
|
||||||
|
#### `rating: Int`
|
||||||
|
**Общий рейтинг автора**
|
||||||
|
- Сумма `rating_shouts + rating_comments`
|
||||||
|
- Агрегированная метрика популярности контента
|
||||||
|
|
||||||
|
#### `rating_shouts: Int`
|
||||||
|
**Рейтинг публикаций автора**
|
||||||
|
- Сумма всех реакций на статьи автора
|
||||||
|
- Положительные реакции: `LIKE`, `AGREE`, `ACCEPT`, `PROOF`, `CREDIT` (+1)
|
||||||
|
- Отрицательные реакции: `DISLIKE`, `DISAGREE`, `REJECT`, `DISPROOF` (-1)
|
||||||
|
- Нейтральные реакции: остальные (0)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT sa.author,
|
||||||
|
SUM(CASE
|
||||||
|
WHEN r.kind IN ('LIKE', 'AGREE', 'ACCEPT', 'PROOF', 'CREDIT') THEN 1
|
||||||
|
WHEN r.kind IN ('DISLIKE', 'DISAGREE', 'REJECT', 'DISPROOF') THEN -1
|
||||||
|
ELSE 0
|
||||||
|
END) as rating_shouts
|
||||||
|
FROM shout_author sa
|
||||||
|
JOIN shout s ON sa.shout = s.id AND s.deleted_at IS NULL AND s.published_at IS NOT NULL
|
||||||
|
JOIN reaction r ON s.id = r.shout AND r.deleted_at IS NULL
|
||||||
|
GROUP BY sa.author
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `rating_comments: Int`
|
||||||
|
**Рейтинг комментариев автора**
|
||||||
|
- Аналогичная система для реакций на комментарии автора
|
||||||
|
- Подсчитывает реакции на комментарии через `reply_to`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT r1.created_by,
|
||||||
|
SUM(CASE
|
||||||
|
WHEN r2.kind IN ('LIKE', 'AGREE', 'ACCEPT', 'PROOF', 'CREDIT') THEN 1
|
||||||
|
WHEN r2.kind IN ('DISLIKE', 'DISAGREE', 'REJECT', 'DISPROOF') THEN -1
|
||||||
|
ELSE 0
|
||||||
|
END) as rating_comments
|
||||||
|
FROM reaction r1
|
||||||
|
JOIN reaction r2 ON r1.id = r2.reply_to AND r2.deleted_at IS NULL
|
||||||
|
WHERE r1.deleted_at IS NULL AND r1.kind IN ('COMMENT', 'QUOTE')
|
||||||
|
GROUP BY r1.created_by
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔄 Метрики вовлечённости
|
||||||
|
|
||||||
|
#### `replies_count: Int`
|
||||||
|
**Количество ответов на контент автора**
|
||||||
|
- **Комплексная метрика**, включающая:
|
||||||
|
1. **Ответы на комментарии автора** (через `reply_to`)
|
||||||
|
2. **Комментарии на посты автора** (прямые комментарии к статьям)
|
||||||
|
|
||||||
|
Логика расчёта:
|
||||||
|
```python
|
||||||
|
# Ответы на комментарии
|
||||||
|
replies_to_comments = COUNT(r2) WHERE r1.created_by = author AND r2.reply_to = r1.id
|
||||||
|
|
||||||
|
# Комментарии на посты
|
||||||
|
comments_on_posts = COUNT(r) WHERE sa.author = author AND r.shout = s.id
|
||||||
|
|
||||||
|
# Итого
|
||||||
|
replies_count = replies_to_comments + comments_on_posts
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `viewed_shouts: Int`
|
||||||
|
**Общее количество просмотров всех публикаций автора**
|
||||||
|
- Интеграция с `ViewedStorage` (Google Analytics)
|
||||||
|
- Суммирует просмотры всех статей автора
|
||||||
|
- Обновляется асинхронно из внешних источников
|
||||||
|
|
||||||
|
## 🔍 API использования
|
||||||
|
|
||||||
|
### GraphQL запрос
|
||||||
|
|
||||||
|
```graphql
|
||||||
|
query LoadAuthors($by: AuthorsBy, $limit: Int, $offset: Int) {
|
||||||
|
load_authors_by(by: $by, limit: $limit, offset: $offset) {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
bio
|
||||||
|
pic
|
||||||
|
stat {
|
||||||
|
shouts
|
||||||
|
topics
|
||||||
|
coauthors
|
||||||
|
followers
|
||||||
|
rating
|
||||||
|
rating_shouts
|
||||||
|
rating_comments
|
||||||
|
comments
|
||||||
|
replies_count
|
||||||
|
viewed_shouts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Параметры сортировки
|
||||||
|
|
||||||
|
```graphql
|
||||||
|
# Сортировка по количеству публикаций
|
||||||
|
{ "order": "shouts" }
|
||||||
|
|
||||||
|
# Сортировка по общему рейтингу
|
||||||
|
{ "order": "rating" }
|
||||||
|
|
||||||
|
# Сортировка по вовлечённости
|
||||||
|
{ "order": "replies_count" }
|
||||||
|
|
||||||
|
# Сортировка по просмотрам
|
||||||
|
{ "order": "viewed_shouts" }
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚡ Производительность
|
||||||
|
|
||||||
|
### Кеширование
|
||||||
|
- **Redis кеш** для результатов запросов
|
||||||
|
- **Ключи кеша**: `authors:stats:limit={limit}:offset={offset}:order={order}`
|
||||||
|
- **TTL**: Настраивается в `cache.py`
|
||||||
|
|
||||||
|
### Оптимизации SQL
|
||||||
|
- **Batch запросы** для получения статистики всех авторов одновременно
|
||||||
|
- **Подготовленные параметры** для защиты от SQL-инъекций
|
||||||
|
- **Индексы** на ключевых полях (`author_id`, `shout_id`, `reaction.kind`)
|
||||||
|
|
||||||
|
### Сортировка
|
||||||
|
- **SQL-уровень сортировки** для метрик статистики
|
||||||
|
- **Подзапросы с JOIN** для производительности
|
||||||
|
- **COALESCE** для обработки NULL значений
|
||||||
|
|
||||||
|
## 🧪 Тестирование
|
||||||
|
|
||||||
|
### Unit тесты
|
||||||
|
```python
|
||||||
|
# Тестирование расчёта статистики
|
||||||
|
async def test_author_stats_calculation():
|
||||||
|
# Создаём тестовые данные
|
||||||
|
# Проверяем корректность расчёта каждой метрики
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Тестирование сортировки
|
||||||
|
async def test_author_sorting():
|
||||||
|
# Проверяем сортировку по разным полям
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Интеграционные тесты
|
||||||
|
- Тестирование с реальными данными
|
||||||
|
- Проверка производительности на больших объёмах
|
||||||
|
- Валидация кеширования
|
||||||
|
|
||||||
|
## 🔧 Конфигурация
|
||||||
|
|
||||||
|
### Переменные окружения
|
||||||
|
```bash
|
||||||
|
# Google Analytics для просмотров
|
||||||
|
GOOGLE_KEYFILE_PATH=/path/to/service-account.json
|
||||||
|
GOOGLE_PROPERTY_ID=your-property-id
|
||||||
|
|
||||||
|
# Redis для кеширования
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
```
|
||||||
|
|
||||||
|
### Настройки реакций
|
||||||
|
Типы реакций определены в `orm/reaction.py`:
|
||||||
|
```python
|
||||||
|
# Положительные (+1)
|
||||||
|
POSITIVE_REACTIONS = ["LIKE", "AGREE", "ACCEPT", "PROOF", "CREDIT"]
|
||||||
|
|
||||||
|
# Отрицательные (-1)
|
||||||
|
NEGATIVE_REACTIONS = ["DISLIKE", "DISAGREE", "REJECT", "DISPROOF"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Развитие
|
||||||
|
|
||||||
|
### Планируемые улучшения
|
||||||
|
- [ ] Исторические тренды статистики
|
||||||
|
- [ ] Сегментация по периодам времени
|
||||||
|
- [ ] Дополнительные метрики вовлечённости
|
||||||
|
- [ ] Персонализированные рекомендации на основе статистики
|
||||||
|
|
||||||
|
### Известные ограничения
|
||||||
|
- Просмотры обновляются с задержкой (Google Analytics API)
|
||||||
|
- Большие объёмы данных могут замедлять запросы без кеша
|
||||||
|
- Сложные запросы сортировки требуют больше ресурсов
|
||||||
@@ -18,7 +18,8 @@ from cache.cache import (
|
|||||||
)
|
)
|
||||||
from orm.author import Author, AuthorFollower
|
from orm.author import Author, AuthorFollower
|
||||||
from orm.community import Community, CommunityAuthor, CommunityFollower
|
from orm.community import Community, CommunityAuthor, CommunityFollower
|
||||||
from orm.shout import Shout, ShoutAuthor
|
from orm.reaction import Reaction
|
||||||
|
from orm.shout import Shout, ShoutAuthor, ShoutTopic
|
||||||
from resolvers.stat import get_with_stat
|
from resolvers.stat import get_with_stat
|
||||||
from services.auth import login_required
|
from services.auth import login_required
|
||||||
from services.search import search_service
|
from services.search import search_service
|
||||||
@@ -34,17 +35,25 @@ DEFAULT_COMMUNITIES = [1]
|
|||||||
# Определение типа AuthorsBy на основе схемы GraphQL
|
# Определение типа AuthorsBy на основе схемы GraphQL
|
||||||
class AuthorsBy(TypedDict, total=False):
|
class AuthorsBy(TypedDict, total=False):
|
||||||
"""
|
"""
|
||||||
Тип для параметра сортировки авторов, соответствующий схеме GraphQL.
|
Параметры фильтрации и сортировки авторов для GraphQL запроса load_authors_by.
|
||||||
|
|
||||||
Поля:
|
📊 Поля сортировки:
|
||||||
|
order: Поле для сортировки авторов:
|
||||||
|
🔢 Базовые метрики: "shouts" (публикации), "followers" (подписчики)
|
||||||
|
🏷️ Контент: "topics" (темы), "comments" (комментарии)
|
||||||
|
👥 Социальные: "coauthors" (соавторы), "replies_count" (ответы на контент)
|
||||||
|
⭐ Рейтинг: "rating" (общий), "rating_shouts" (публикации), "rating_comments" (комментарии)
|
||||||
|
👁️ Вовлечённость: "viewed_shouts" (просмотры)
|
||||||
|
📝 Алфавит: "name" (по имени)
|
||||||
|
|
||||||
|
🔍 Поля фильтрации:
|
||||||
last_seen: Временная метка последнего посещения
|
last_seen: Временная метка последнего посещения
|
||||||
created_at: Временная метка создания
|
created_at: Временная метка создания
|
||||||
slug: Уникальный идентификатор автора
|
slug: Уникальный идентификатор автора
|
||||||
name: Имя автора
|
name: Имя автора для поиска
|
||||||
topic: Тема, связанная с автором
|
topic: Тема, связанная с автором
|
||||||
order: Поле для сортировки (shouts, followers, rating, comments, name)
|
|
||||||
after: Временная метка для фильтрации "после"
|
after: Временная метка для фильтрации "после"
|
||||||
stat: Поле статистики
|
stat: Поле статистики для дополнительной фильтрации
|
||||||
"""
|
"""
|
||||||
|
|
||||||
last_seen: int | None
|
last_seen: int | None
|
||||||
@@ -96,15 +105,36 @@ async def get_authors_with_stats(
|
|||||||
limit: int = 10, offset: int = 0, by: AuthorsBy | None = None, current_user_id: int | None = None
|
limit: int = 10, offset: int = 0, by: AuthorsBy | None = None, current_user_id: int | None = None
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Получает авторов со статистикой с пагинацией.
|
🧪 Получает авторов с полной статистикой и поддержкой сортировки.
|
||||||
|
|
||||||
|
📊 Рассчитывает все метрики AuthorStat:
|
||||||
|
- shouts: Количество опубликованных статей
|
||||||
|
- topics: Уникальные темы участия
|
||||||
|
- coauthors: Количество соавторов
|
||||||
|
- followers: Подписчики
|
||||||
|
- rating: Общий рейтинг (rating_shouts + rating_comments)
|
||||||
|
- rating_shouts: Рейтинг публикаций (реакции)
|
||||||
|
- rating_comments: Рейтинг комментариев (реакции)
|
||||||
|
- comments: Созданные комментарии
|
||||||
|
- replies_count: Ответы на контент (комментарии на посты + ответы на комментарии)
|
||||||
|
- viewed_shouts: Просмотры публикаций (из ViewedStorage)
|
||||||
|
|
||||||
|
⚡ Оптимизации:
|
||||||
|
- Batch SQL-запросы для статистики
|
||||||
|
- Кеширование результатов
|
||||||
|
- Сортировка на уровне SQL для производительности
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
limit: Максимальное количество возвращаемых авторов
|
limit: Максимальное количество возвращаемых авторов (1-100)
|
||||||
offset: Смещение для пагинации
|
offset: Смещение для пагинации
|
||||||
by: Опциональный параметр сортировки (AuthorsBy)
|
by: Параметры фильтрации и сортировки (AuthorsBy)
|
||||||
current_user_id: ID текущего пользователя
|
current_user_id: ID текущего пользователя для фильтрации доступа
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: Список авторов с их статистикой
|
list[dict]: Список авторов с полной статистикой, отсортированных согласно параметрам
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: При ошибках выполнения SQL-запросов или доступа к ViewedStorage
|
||||||
"""
|
"""
|
||||||
# Формируем ключ кеша с помощью универсальной функции
|
# Формируем ключ кеша с помощью универсальной функции
|
||||||
order_value = by.get("order", "default") if by else "default"
|
order_value = by.get("order", "default") if by else "default"
|
||||||
@@ -128,7 +158,18 @@ async def get_authors_with_stats(
|
|||||||
if "order" in by:
|
if "order" in by:
|
||||||
order_value = by["order"]
|
order_value = by["order"]
|
||||||
logger.debug(f"Found order field with value: {order_value}")
|
logger.debug(f"Found order field with value: {order_value}")
|
||||||
if order_value in ["shouts", "followers", "rating", "comments"]:
|
if order_value in [
|
||||||
|
"shouts",
|
||||||
|
"followers",
|
||||||
|
"rating",
|
||||||
|
"comments",
|
||||||
|
"topics",
|
||||||
|
"coauthors",
|
||||||
|
"viewed_shouts",
|
||||||
|
"rating_shouts",
|
||||||
|
"rating_comments",
|
||||||
|
"replies_count",
|
||||||
|
]:
|
||||||
stats_sort_field = order_value
|
stats_sort_field = order_value
|
||||||
logger.debug(f"Applying statistics-based sorting by: {stats_sort_field}")
|
logger.debug(f"Applying statistics-based sorting by: {stats_sort_field}")
|
||||||
# Не применяем другую сортировку, так как будем использовать stats_sort_field
|
# Не применяем другую сортировку, так как будем использовать stats_sort_field
|
||||||
@@ -212,12 +253,140 @@ async def get_authors_with_stats(
|
|||||||
sql_desc(func.coalesce(subquery.c.followers_count, 0))
|
sql_desc(func.coalesce(subquery.c.followers_count, 0))
|
||||||
)
|
)
|
||||||
logger.debug("Applied sorting by followers count")
|
logger.debug("Applied sorting by followers count")
|
||||||
|
elif stats_sort_field == "topics":
|
||||||
|
# 🏷️ Сортировка по количеству тем
|
||||||
|
logger.debug("Building subquery for topics sorting")
|
||||||
|
subquery = (
|
||||||
|
select(ShoutAuthor.author, func.count(func.distinct(ShoutTopic.topic)).label("topics_count"))
|
||||||
|
.select_from(ShoutAuthor)
|
||||||
|
.join(Shout, ShoutAuthor.shout == Shout.id)
|
||||||
|
.join(ShoutTopic, Shout.id == ShoutTopic.shout)
|
||||||
|
.where(and_(Shout.deleted_at.is_(None), Shout.published_at.is_not(None)))
|
||||||
|
.group_by(ShoutAuthor.author)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
|
||||||
|
sql_desc(func.coalesce(subquery.c.topics_count, 0))
|
||||||
|
)
|
||||||
|
logger.debug("Applied sorting by topics count")
|
||||||
|
elif stats_sort_field == "coauthors":
|
||||||
|
# ✍️ Сортировка по количеству соавторов
|
||||||
|
logger.debug("Building subquery for coauthors sorting")
|
||||||
|
sa1 = ShoutAuthor.__table__.alias("sa1")
|
||||||
|
sa2 = ShoutAuthor.__table__.alias("sa2")
|
||||||
|
subquery = (
|
||||||
|
select(sa1.c.author, func.count(func.distinct(sa2.c.author)).label("coauthors_count"))
|
||||||
|
.select_from(sa1.join(Shout, sa1.c.shout == Shout.id).join(sa2, sa2.c.shout == Shout.id))
|
||||||
|
.where(
|
||||||
|
and_(
|
||||||
|
Shout.deleted_at.is_(None),
|
||||||
|
Shout.published_at.is_not(None),
|
||||||
|
sa1.c.author != sa2.c.author, # исключаем самого автора из подсчёта
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.group_by(sa1.c.author)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
|
||||||
|
sql_desc(func.coalesce(subquery.c.coauthors_count, 0))
|
||||||
|
)
|
||||||
|
logger.debug("Applied sorting by coauthors count")
|
||||||
|
elif stats_sort_field == "comments":
|
||||||
|
# 💬 Сортировка по количеству комментариев
|
||||||
|
logger.debug("Building subquery for comments sorting")
|
||||||
|
subquery = (
|
||||||
|
select(Reaction.created_by, func.count(func.distinct(Reaction.id)).label("comments_count"))
|
||||||
|
.select_from(Reaction)
|
||||||
|
.join(Shout, Reaction.shout == Shout.id)
|
||||||
|
.where(
|
||||||
|
and_(
|
||||||
|
Reaction.deleted_at.is_(None),
|
||||||
|
Shout.deleted_at.is_(None),
|
||||||
|
Reaction.kind.in_(["COMMENT", "QUOTE"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.group_by(Reaction.created_by)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.created_by).order_by(
|
||||||
|
sql_desc(func.coalesce(subquery.c.comments_count, 0))
|
||||||
|
)
|
||||||
|
logger.debug("Applied sorting by comments count")
|
||||||
|
elif stats_sort_field == "replies_count":
|
||||||
|
# 💬 Сортировка по общему количеству ответов (комментарии на посты + ответы на комментарии)
|
||||||
|
logger.debug("Building subquery for replies_count sorting")
|
||||||
|
|
||||||
|
# Подзапрос для ответов на комментарии автора
|
||||||
|
replies_to_comments_subq = (
|
||||||
|
select(
|
||||||
|
Reaction.created_by.label("author_id"),
|
||||||
|
func.count(func.distinct(Reaction.id)).label("replies_count"),
|
||||||
|
)
|
||||||
|
.select_from(Reaction)
|
||||||
|
.where(
|
||||||
|
and_(
|
||||||
|
Reaction.deleted_at.is_(None),
|
||||||
|
Reaction.reply_to.is_not(None),
|
||||||
|
Reaction.kind.in_(["COMMENT", "QUOTE"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.group_by(Reaction.created_by)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Подзапрос для комментариев на посты автора
|
||||||
|
comments_on_posts_subq = (
|
||||||
|
select(
|
||||||
|
ShoutAuthor.author.label("author_id"),
|
||||||
|
func.count(func.distinct(Reaction.id)).label("replies_count"),
|
||||||
|
)
|
||||||
|
.select_from(ShoutAuthor)
|
||||||
|
.join(Shout, ShoutAuthor.shout == Shout.id)
|
||||||
|
.join(Reaction, Shout.id == Reaction.shout)
|
||||||
|
.where(
|
||||||
|
and_(
|
||||||
|
Shout.deleted_at.is_(None),
|
||||||
|
Shout.published_at.is_not(None),
|
||||||
|
Reaction.deleted_at.is_(None),
|
||||||
|
Reaction.kind.in_(["COMMENT", "QUOTE"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.group_by(ShoutAuthor.author)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Объединяем оба подзапроса через UNION ALL
|
||||||
|
combined_replies_subq = (
|
||||||
|
select(
|
||||||
|
func.coalesce(
|
||||||
|
replies_to_comments_subq.c.author_id, comments_on_posts_subq.c.author_id
|
||||||
|
).label("author_id"),
|
||||||
|
func.coalesce(
|
||||||
|
func.coalesce(replies_to_comments_subq.c.replies_count, 0)
|
||||||
|
+ func.coalesce(comments_on_posts_subq.c.replies_count, 0),
|
||||||
|
0,
|
||||||
|
).label("total_replies"),
|
||||||
|
)
|
||||||
|
.select_from(
|
||||||
|
replies_to_comments_subq
|
||||||
|
.outerjoin(
|
||||||
|
comments_on_posts_subq,
|
||||||
|
replies_to_comments_subq.c.author_id == comments_on_posts_subq.c.author_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
|
||||||
|
base_query = base_query.outerjoin(
|
||||||
|
combined_replies_subq, Author.id == combined_replies_subq.c.author_id
|
||||||
|
).order_by(sql_desc(func.coalesce(combined_replies_subq.c.total_replies, 0)))
|
||||||
|
logger.debug("Applied sorting by replies_count")
|
||||||
|
|
||||||
# Логирование для отладки сортировки
|
# Логирование для отладки сортировки
|
||||||
try:
|
try:
|
||||||
# Получаем SQL запрос для проверки
|
# Получаем SQL запрос для проверки
|
||||||
sql_query = str(base_query.compile(compile_kwargs={"literal_binds": True}))
|
sql_query = str(base_query.compile(compile_kwargs={"literal_binds": True}))
|
||||||
logger.debug(f"Generated SQL query for followers sorting: {sql_query}")
|
logger.debug(f"Generated SQL query for replies_count sorting: {sql_query}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error generating SQL query: {e}")
|
logger.error(f"Error generating SQL query: {e}")
|
||||||
|
|
||||||
@@ -238,9 +407,13 @@ async def get_authors_with_stats(
|
|||||||
if stats_sort_field:
|
if stats_sort_field:
|
||||||
logger.debug(f"Query returned {len(authors)} authors with sorting by {stats_sort_field}")
|
logger.debug(f"Query returned {len(authors)} authors with sorting by {stats_sort_field}")
|
||||||
|
|
||||||
# Оптимизированный запрос для получения статистики по публикациям для авторов
|
# 🧪 Оптимизированные запросы для получения всей статистики авторов
|
||||||
logger.debug("Executing shouts statistics query")
|
logger.debug("Executing comprehensive statistics queries")
|
||||||
placeholders = ", ".join([f":id{i}" for i in range(len(author_ids))])
|
placeholders = ", ".join([f":id{i}" for i in range(len(author_ids))])
|
||||||
|
params = {f"id{i}": author_id for i, author_id in enumerate(author_ids)}
|
||||||
|
|
||||||
|
# 📊 Статистика по публикациям
|
||||||
|
logger.debug("Executing shouts statistics query")
|
||||||
shouts_stats_query = f"""
|
shouts_stats_query = f"""
|
||||||
SELECT sa.author, COUNT(DISTINCT s.id) as shouts_count
|
SELECT sa.author, COUNT(DISTINCT s.id) as shouts_count
|
||||||
FROM shout_author sa
|
FROM shout_author sa
|
||||||
@@ -248,11 +421,10 @@ async def get_authors_with_stats(
|
|||||||
WHERE sa.author IN ({placeholders})
|
WHERE sa.author IN ({placeholders})
|
||||||
GROUP BY sa.author
|
GROUP BY sa.author
|
||||||
"""
|
"""
|
||||||
params = {f"id{i}": author_id for i, author_id in enumerate(author_ids)}
|
|
||||||
shouts_stats = {row[0]: row[1] for row in session.execute(text(shouts_stats_query), params)}
|
shouts_stats = {row[0]: row[1] for row in session.execute(text(shouts_stats_query), params)}
|
||||||
logger.debug(f"Shouts stats retrieved: {shouts_stats}")
|
logger.debug(f"Shouts stats retrieved: {shouts_stats}")
|
||||||
|
|
||||||
# Запрос на получение статистики по подписчикам для авторов
|
# 👥 Статистика по подписчикам
|
||||||
logger.debug("Executing followers statistics query")
|
logger.debug("Executing followers statistics query")
|
||||||
followers_stats_query = f"""
|
followers_stats_query = f"""
|
||||||
SELECT following, COUNT(DISTINCT follower) as followers_count
|
SELECT following, COUNT(DISTINCT follower) as followers_count
|
||||||
@@ -263,8 +435,163 @@ async def get_authors_with_stats(
|
|||||||
followers_stats = {row[0]: row[1] for row in session.execute(text(followers_stats_query), params)}
|
followers_stats = {row[0]: row[1] for row in session.execute(text(followers_stats_query), params)}
|
||||||
logger.debug(f"Followers stats retrieved: {followers_stats}")
|
logger.debug(f"Followers stats retrieved: {followers_stats}")
|
||||||
|
|
||||||
# Формируем результат с добавлением статистики
|
# 🏷️ Статистика по темам (количество уникальных тем, в которых участвовал автор)
|
||||||
logger.debug("Building final result with statistics")
|
logger.debug("Executing topics statistics query")
|
||||||
|
topics_stats_query = f"""
|
||||||
|
SELECT sa.author, COUNT(DISTINCT st.topic) as topics_count
|
||||||
|
FROM shout_author sa
|
||||||
|
JOIN shout s ON sa.shout = s.id AND s.deleted_at IS NULL AND s.published_at IS NOT NULL
|
||||||
|
JOIN shout_topic st ON s.id = st.shout
|
||||||
|
WHERE sa.author IN ({placeholders})
|
||||||
|
GROUP BY sa.author
|
||||||
|
"""
|
||||||
|
topics_stats = {row[0]: row[1] for row in session.execute(text(topics_stats_query), params)}
|
||||||
|
logger.debug(f"Topics stats retrieved: {topics_stats}")
|
||||||
|
|
||||||
|
# ✍️ Статистика по соавторам (количество уникальных соавторов)
|
||||||
|
logger.debug("Executing coauthors statistics query")
|
||||||
|
coauthors_stats_query = f"""
|
||||||
|
SELECT sa1.author, COUNT(DISTINCT sa2.author) as coauthors_count
|
||||||
|
FROM shout_author sa1
|
||||||
|
JOIN shout s ON sa1.shout = s.id
|
||||||
|
AND s.deleted_at IS NULL
|
||||||
|
AND s.published_at IS NOT NULL
|
||||||
|
JOIN shout_author sa2 ON s.id = sa2.shout
|
||||||
|
AND sa2.author != sa1.author -- исключаем самого автора
|
||||||
|
WHERE sa1.author IN ({placeholders})
|
||||||
|
GROUP BY sa1.author
|
||||||
|
"""
|
||||||
|
coauthors_stats = {row[0]: row[1] for row in session.execute(text(coauthors_stats_query), params)}
|
||||||
|
logger.debug(f"Coauthors stats retrieved: {coauthors_stats}")
|
||||||
|
|
||||||
|
# 💬 Статистика по комментариям (количество созданных комментариев)
|
||||||
|
logger.debug("Executing comments statistics query")
|
||||||
|
comments_stats_query = f"""
|
||||||
|
SELECT r.created_by, COUNT(DISTINCT r.id) as comments_count
|
||||||
|
FROM reaction r
|
||||||
|
JOIN shout s ON r.shout = s.id AND s.deleted_at IS NULL
|
||||||
|
WHERE r.created_by IN ({placeholders}) AND r.deleted_at IS NULL
|
||||||
|
AND r.kind IN ('COMMENT', 'QUOTE')
|
||||||
|
GROUP BY r.created_by
|
||||||
|
"""
|
||||||
|
comments_stats = {row[0]: row[1] for row in session.execute(text(comments_stats_query), params)}
|
||||||
|
logger.debug(f"Comments stats retrieved: {comments_stats}")
|
||||||
|
|
||||||
|
# 💬 Статистика по вызванным комментариям (ответы на комментарии + комментарии на посты)
|
||||||
|
logger.debug("Executing replies_count statistics query")
|
||||||
|
|
||||||
|
# Ответы на комментарии автора
|
||||||
|
replies_to_comments_query = f"""
|
||||||
|
SELECT r1.created_by as author_id, COUNT(DISTINCT r2.id) as replies_count
|
||||||
|
FROM reaction r1
|
||||||
|
JOIN reaction r2 ON r1.id = r2.reply_to AND r2.deleted_at IS NULL
|
||||||
|
WHERE r1.created_by IN ({placeholders}) AND r1.deleted_at IS NULL
|
||||||
|
AND r1.kind IN ('COMMENT', 'QUOTE')
|
||||||
|
AND r2.kind IN ('COMMENT', 'QUOTE')
|
||||||
|
GROUP BY r1.created_by
|
||||||
|
"""
|
||||||
|
replies_to_comments_stats = {
|
||||||
|
row[0]: row[1] for row in session.execute(text(replies_to_comments_query), params)
|
||||||
|
}
|
||||||
|
logger.debug(f"Replies to comments stats retrieved: {replies_to_comments_stats}")
|
||||||
|
|
||||||
|
# Комментарии на посты автора
|
||||||
|
comments_on_posts_query = f"""
|
||||||
|
SELECT sa.author as author_id, COUNT(DISTINCT r.id) as replies_count
|
||||||
|
FROM shout_author sa
|
||||||
|
JOIN shout s ON sa.shout = s.id AND s.deleted_at IS NULL AND s.published_at IS NOT NULL
|
||||||
|
JOIN reaction r ON s.id = r.shout AND r.deleted_at IS NULL
|
||||||
|
WHERE sa.author IN ({placeholders})
|
||||||
|
AND r.kind IN ('COMMENT', 'QUOTE')
|
||||||
|
GROUP BY sa.author
|
||||||
|
"""
|
||||||
|
comments_on_posts_stats = {
|
||||||
|
row[0]: row[1] for row in session.execute(text(comments_on_posts_query), params)
|
||||||
|
}
|
||||||
|
logger.debug(f"Comments on posts stats retrieved: {comments_on_posts_stats}")
|
||||||
|
|
||||||
|
# Объединяем статистику
|
||||||
|
replies_count_stats = {}
|
||||||
|
for author_id in author_ids:
|
||||||
|
replies_to_comments = replies_to_comments_stats.get(author_id, 0)
|
||||||
|
comments_on_posts = comments_on_posts_stats.get(author_id, 0)
|
||||||
|
replies_count_stats[author_id] = replies_to_comments + comments_on_posts
|
||||||
|
logger.debug(f"Combined replies count stats: {replies_count_stats}")
|
||||||
|
|
||||||
|
# ⭐ Статистика по рейтингу публикаций (сумма реакций на публикации автора)
|
||||||
|
logger.debug("Executing rating_shouts statistics query")
|
||||||
|
rating_shouts_stats_query = f"""
|
||||||
|
SELECT sa.author,
|
||||||
|
SUM(CASE
|
||||||
|
WHEN r.kind IN ('LIKE', 'AGREE', 'ACCEPT', 'PROOF', 'CREDIT') THEN 1
|
||||||
|
WHEN r.kind IN ('DISLIKE', 'DISAGREE', 'REJECT', 'DISPROOF') THEN -1
|
||||||
|
ELSE 0
|
||||||
|
END) as rating_shouts
|
||||||
|
FROM shout_author sa
|
||||||
|
JOIN shout s ON sa.shout = s.id AND s.deleted_at IS NULL AND s.published_at IS NOT NULL
|
||||||
|
JOIN reaction r ON s.id = r.shout AND r.deleted_at IS NULL
|
||||||
|
WHERE sa.author IN ({placeholders})
|
||||||
|
GROUP BY sa.author
|
||||||
|
"""
|
||||||
|
rating_shouts_stats = {
|
||||||
|
row[0]: row[1] for row in session.execute(text(rating_shouts_stats_query), params)
|
||||||
|
}
|
||||||
|
logger.debug(f"Rating shouts stats retrieved: {rating_shouts_stats}")
|
||||||
|
|
||||||
|
# ⭐ Статистика по рейтингу комментариев (реакции на комментарии автора)
|
||||||
|
logger.debug("Executing rating_comments statistics query")
|
||||||
|
rating_comments_stats_query = f"""
|
||||||
|
SELECT r1.created_by,
|
||||||
|
SUM(CASE
|
||||||
|
WHEN r2.kind IN ('LIKE', 'AGREE', 'ACCEPT', 'PROOF', 'CREDIT') THEN 1
|
||||||
|
WHEN r2.kind IN ('DISLIKE', 'DISAGREE', 'REJECT', 'DISPROOF') THEN -1
|
||||||
|
ELSE 0
|
||||||
|
END) as rating_comments
|
||||||
|
FROM reaction r1
|
||||||
|
JOIN reaction r2 ON r1.id = r2.reply_to AND r2.deleted_at IS NULL
|
||||||
|
WHERE r1.created_by IN ({placeholders}) AND r1.deleted_at IS NULL
|
||||||
|
AND r1.kind IN ('COMMENT', 'QUOTE')
|
||||||
|
GROUP BY r1.created_by
|
||||||
|
"""
|
||||||
|
rating_comments_stats = {
|
||||||
|
row[0]: row[1] for row in session.execute(text(rating_comments_stats_query), params)
|
||||||
|
}
|
||||||
|
logger.debug(f"Rating comments stats retrieved: {rating_comments_stats}")
|
||||||
|
|
||||||
|
# 📈 Общий рейтинг (сумма рейтингов публикаций и комментариев)
|
||||||
|
logger.debug("Calculating overall rating")
|
||||||
|
overall_rating_stats = {}
|
||||||
|
for author_id in author_ids:
|
||||||
|
shouts_rating = rating_shouts_stats.get(author_id, 0)
|
||||||
|
comments_rating = rating_comments_stats.get(author_id, 0)
|
||||||
|
overall_rating_stats[author_id] = shouts_rating + comments_rating
|
||||||
|
logger.debug(f"Overall rating stats calculated: {overall_rating_stats}")
|
||||||
|
|
||||||
|
# 👁️ Статистика по просмотрам публикаций (используем ViewedStorage для получения агрегированных данных)
|
||||||
|
logger.debug("Calculating viewed_shouts statistics from ViewedStorage")
|
||||||
|
from services.viewed import ViewedStorage
|
||||||
|
|
||||||
|
viewed_shouts_stats = {}
|
||||||
|
# Получаем общие просмотры для всех публикаций каждого автора
|
||||||
|
for author_id in author_ids:
|
||||||
|
total_views = 0
|
||||||
|
# Получаем все публикации автора и суммируем их просмотры
|
||||||
|
author_shouts_query = """
|
||||||
|
SELECT s.slug
|
||||||
|
FROM shout_author sa
|
||||||
|
JOIN shout s ON sa.shout = s.id AND s.deleted_at IS NULL AND s.published_at IS NOT NULL
|
||||||
|
WHERE sa.author = :author_id
|
||||||
|
"""
|
||||||
|
shout_rows = session.execute(text(author_shouts_query), {"author_id": author_id})
|
||||||
|
for shout_row in shout_rows:
|
||||||
|
shout_slug = shout_row[0]
|
||||||
|
shout_views = ViewedStorage.get_shout(shout_slug=shout_slug)
|
||||||
|
total_views += shout_views
|
||||||
|
viewed_shouts_stats[author_id] = total_views
|
||||||
|
logger.debug(f"Viewed shouts stats calculated: {viewed_shouts_stats}")
|
||||||
|
|
||||||
|
# 🎯 Формируем результат с добавлением полной статистики
|
||||||
|
logger.debug("Building final result with comprehensive statistics")
|
||||||
result = []
|
result = []
|
||||||
for author in authors:
|
for author in authors:
|
||||||
try:
|
try:
|
||||||
@@ -272,7 +599,15 @@ async def get_authors_with_stats(
|
|||||||
author_dict = author.dict()
|
author_dict = author.dict()
|
||||||
author_dict["stat"] = {
|
author_dict["stat"] = {
|
||||||
"shouts": shouts_stats.get(author.id, 0),
|
"shouts": shouts_stats.get(author.id, 0),
|
||||||
|
"topics": topics_stats.get(author.id, 0),
|
||||||
|
"coauthors": coauthors_stats.get(author.id, 0),
|
||||||
"followers": followers_stats.get(author.id, 0),
|
"followers": followers_stats.get(author.id, 0),
|
||||||
|
"rating": overall_rating_stats.get(author.id, 0),
|
||||||
|
"rating_shouts": rating_shouts_stats.get(author.id, 0),
|
||||||
|
"rating_comments": rating_comments_stats.get(author.id, 0),
|
||||||
|
"comments": comments_stats.get(author.id, 0),
|
||||||
|
"replies_count": replies_count_stats.get(author.id, 0),
|
||||||
|
"viewed_shouts": viewed_shouts_stats.get(author.id, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
result.append(author_dict)
|
result.append(author_dict)
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
|
# Статистика автора - полная метрика активности и популярности
|
||||||
type AuthorStat {
|
type AuthorStat {
|
||||||
shouts: Int
|
# Контент автора
|
||||||
topics: Int
|
shouts: Int # Количество опубликованных статей
|
||||||
authors: Int
|
topics: Int # Количество уникальных тем, в которых участвовал
|
||||||
followers: Int
|
comments: Int # Количество созданных комментариев и цитат
|
||||||
rating: Int
|
|
||||||
rating_shouts: Int
|
# Взаимодействие с другими авторами
|
||||||
rating_comments: Int
|
coauthors: Int # Количество уникальных соавторов
|
||||||
comments: Int
|
followers: Int # Количество подписчиков
|
||||||
viewed: Int
|
|
||||||
|
# Рейтинговая система
|
||||||
|
rating: Int # Общий рейтинг (rating_shouts + rating_comments)
|
||||||
|
rating_shouts: Int # Рейтинг публикаций (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF)
|
||||||
|
rating_comments: Int # Рейтинг комментариев (реакции на комментарии автора)
|
||||||
|
|
||||||
|
# Метрики вовлечённости
|
||||||
|
replies_count: Int # Количество ответов на контент автора (ответы на комментарии + комментарии на посты)
|
||||||
|
viewed_shouts: Int # Общее количество просмотров всех публикаций автора
|
||||||
}
|
}
|
||||||
|
|
||||||
type Author {
|
type Author {
|
||||||
|
|||||||
Reference in New Issue
Block a user