Files
multy-stub/server/routers/smoke-tracker/stats.js

280 lines
9.0 KiB
JavaScript
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.
const { Router } = require('express')
const mongoose = require('mongoose')
const { getAnswer } = require('../../utils/common')
const { CigaretteModel } = require('./model/cigarette')
const { authMiddleware } = require('./middleware/auth')
const router = Router()
// Все эндпоинты статистики требуют авторизации
router.use(authMiddleware)
// Агрегация по дням: количество сигарет в день для построения графика
router.get('/daily', async (req, res, next) => {
try {
const user = req.user
const { from, to } = req.query
const now = new Date()
const defaultFrom = new Date(now)
defaultFrom.setDate(defaultFrom.getDate() - 30)
const fromDate = from ? new Date(from) : defaultFrom
const toDate = to ? new Date(to) : now
const match = {
userId: new mongoose.Types.ObjectId(user.id),
smokedAt: {
$gte: fromDate,
$lte: toDate,
},
}
// Отладка: проверяем, сколько записей попадает в фильтр
const totalCount = await CigaretteModel.countDocuments(match)
console.log('[STATS] Match filter:', JSON.stringify(match, null, 2))
console.log('[STATS] Total cigarettes in range:', totalCount)
const data = await CigaretteModel.aggregate([
{ $match: match },
{
$group: {
_id: {
$dateToString: { format: '%Y-%m-%d', date: '$smokedAt', timezone: 'UTC' },
},
count: { $sum: 1 },
},
},
{ $sort: { _id: 1 } },
])
console.log('[STATS] Aggregation result:', data)
const result = data.map((item) => ({
date: item._id,
count: item.count,
}))
res.json(getAnswer(null, result))
} catch (err) {
next(err)
}
})
// Сводная статистика: среднее в день, по дням недели, общее по всем пользователям
router.get('/summary', async (req, res, next) => {
try {
const user = req.user
const { from, to } = req.query
const now = new Date()
const defaultFrom = new Date(now)
defaultFrom.setDate(defaultFrom.getDate() - 30)
const fromDate = from ? new Date(from) : defaultFrom
const toDate = to ? new Date(to) : now
// Фильтр для текущего пользователя
const userMatch = {
userId: new mongoose.Types.ObjectId(user.id),
smokedAt: {
$gte: fromDate,
$lte: toDate,
},
}
// Фильтр для всех пользователей (общая статистика)
const globalMatch = {
smokedAt: {
$gte: fromDate,
$lte: toDate,
},
}
// 1. Статистика по дням (для текущего пользователя)
const dailyStats = await CigaretteModel.aggregate([
{ $match: userMatch },
{
$group: {
_id: {
$dateToString: { format: '%Y-%m-%d', date: '$smokedAt', timezone: 'UTC' },
},
count: { $sum: 1 },
},
},
{ $sort: { _id: 1 } },
])
const dailyData = dailyStats.map((item) => ({
date: item._id,
count: item.count,
}))
// 2. Среднее количество в день (для текущего пользователя)
const totalCigarettes = dailyStats.reduce((sum, item) => sum + item.count, 0)
const daysWithData = dailyStats.length
const averagePerDay = daysWithData > 0 ? (totalCigarettes / daysWithData).toFixed(2) : 0
// 3. Статистика по дням недели (для текущего пользователя)
const weekdayStats = await CigaretteModel.aggregate([
{ $match: userMatch },
{
$group: {
_id: { $dayOfWeek: '$smokedAt' }, // 1 = воскресенье, 2 = понедельник, ..., 7 = суббота
count: { $sum: 1 },
},
},
{ $sort: { _id: 1 } },
])
const weekdayNames = ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота']
const weekdayData = weekdayStats.map((item) => {
const dayIndex = item._id - 1 // MongoDB возвращает 1-7, приводим к 0-6
return {
dayOfWeek: item._id,
dayName: weekdayNames[dayIndex],
count: item.count,
}
})
// Вычисляем среднее для каждого дня недели
const weekdayAverages = weekdayData.map((day) => {
// Считаем, сколько раз встречается этот день недели в периоде
const occurrences = Math.floor(
(toDate.getTime() - fromDate.getTime()) / (1000 * 60 * 60 * 24 * 7)
) + 1
return {
...day,
average: occurrences > 0 ? (day.count / occurrences).toFixed(2) : day.count,
}
})
// 4. Определяем активных пользователей (от 2 до 40 сигарет в день в среднем)
const MIN_CIGARETTES_PER_DAY = 2
const MAX_CIGARETTES_PER_DAY = 40
const periodDays = Math.ceil((toDate.getTime() - fromDate.getTime()) / (1000 * 60 * 60 * 24))
// Получаем статистику по каждому пользователю
const userStats = await CigaretteModel.aggregate([
{ $match: globalMatch },
{
$group: {
_id: '$userId',
total: { $sum: 1 },
},
},
])
// Фильтруем активных пользователей (исключаем слишком низкие и слишком высокие значения)
const activeUserIds = userStats
.filter((stat) => {
const avgPerDay = stat.total / periodDays
return avgPerDay > MIN_CIGARETTES_PER_DAY && avgPerDay <= MAX_CIGARETTES_PER_DAY
})
.map((stat) => stat._id)
const filteredLow = userStats.filter((stat) => stat.total / periodDays <= MIN_CIGARETTES_PER_DAY).length
const filteredHigh = userStats.filter((stat) => stat.total / periodDays > MAX_CIGARETTES_PER_DAY).length
console.log('[STATS] Total users:', userStats.length)
console.log('[STATS] Active users (2-40 cigs/day):', activeUserIds.length)
console.log('[STATS] Filtered out (too low):', filteredLow)
console.log('[STATS] Filtered out (too high):', filteredHigh)
// Фильтр только для активных пользователей
const activeGlobalMatch = {
...globalMatch,
userId: { $in: activeUserIds },
}
// Общая статистика по активным пользователям
const globalDailyStats = await CigaretteModel.aggregate([
{ $match: activeGlobalMatch },
{
$group: {
_id: {
$dateToString: { format: '%Y-%m-%d', date: '$smokedAt', timezone: 'UTC' },
},
count: { $sum: 1 },
},
},
{ $sort: { _id: 1 } },
])
const globalDailyData = globalDailyStats.map((item) => ({
date: item._id,
count: item.count,
}))
const globalTotalCigarettes = globalDailyStats.reduce((sum, item) => sum + item.count, 0)
const globalDaysWithData = globalDailyStats.length
const globalAveragePerDay =
globalDaysWithData > 0 ? (globalTotalCigarettes / globalDaysWithData).toFixed(2) : 0
// Общая статистика по дням недели (активные пользователи)
const globalWeekdayStats = await CigaretteModel.aggregate([
{ $match: activeGlobalMatch },
{
$group: {
_id: { $dayOfWeek: '$smokedAt' },
count: { $sum: 1 },
},
},
{ $sort: { _id: 1 } },
])
const globalWeekdayData = globalWeekdayStats.map((item) => {
const dayIndex = item._id - 1
return {
dayOfWeek: item._id,
dayName: weekdayNames[dayIndex],
count: item.count,
}
})
const globalWeekdayAverages = globalWeekdayData.map((day) => {
const occurrences = Math.floor(
(toDate.getTime() - fromDate.getTime()) / (1000 * 60 * 60 * 24 * 7)
) + 1
return {
...day,
average: occurrences > 0 ? (day.count / occurrences).toFixed(2) : day.count,
}
})
// Количество активных пользователей
const activeUsers = activeUserIds
const result = {
user: {
daily: dailyData,
averagePerDay: parseFloat(averagePerDay),
weekday: weekdayAverages,
total: totalCigarettes,
daysWithData,
},
global: {
daily: globalDailyData,
averagePerDay: parseFloat(globalAveragePerDay),
weekday: globalWeekdayAverages,
total: globalTotalCigarettes,
daysWithData: globalDaysWithData,
activeUsers: activeUsers.length,
},
period: {
from: fromDate.toISOString(),
to: toDate.toISOString(),
},
}
res.json(getAnswer(null, result))
} catch (err) {
next(err)
}
})
module.exports = router