Add summary statistics endpoint to smoke-tracker API; update documentation to include new route

This commit is contained in:
Primakov Alexandr Alexandrovich
2025-11-17 14:14:15 +03:00
parent dd75c54b32
commit f856d94596
3 changed files with 414 additions and 0 deletions

View File

@@ -62,6 +62,180 @@ router.get('/daily', async (req, res, next) => {
}
})
// Сводная статистика: среднее в день, по дням недели, общее по всем пользователям
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. Общая статистика по всем пользователям
const globalDailyStats = await CigaretteModel.aggregate([
{ $match: globalMatch },
{
$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: globalMatch },
{
$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 = await CigaretteModel.distinct('userId', globalMatch)
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