Add summary statistics endpoint to smoke-tracker API; update documentation to include new route
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user