@peopl-health/nexus 3.6.1 → 3.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,7 +2,7 @@ const { logger } = require('../utils/logger');
|
|
|
2
2
|
|
|
3
3
|
const { validateAndAdaptBox } = require('../helpers/dashboardHelper');
|
|
4
4
|
|
|
5
|
-
const { getAllBoxes, getStatsById, updateBox } = require('../services/dashboardService');
|
|
5
|
+
const { getAllBoxes, getStatsById, updateBox, getDailyTrend } = require('../services/dashboardService');
|
|
6
6
|
|
|
7
7
|
const getDashboardController = async (req, res) => {
|
|
8
8
|
try {
|
|
@@ -48,8 +48,20 @@ const updateDashboardControllerById = async (req, res) => {
|
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
const getDashboardTrendController = async (req, res) => {
|
|
52
|
+
try {
|
|
53
|
+
const days = parseInt(req.query.days, 10) || 60;
|
|
54
|
+
const result = await getDailyTrend(days);
|
|
55
|
+
res.json({ success: true, ...result });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
logger.error('[DashboardController] Error fetching dashboard trend:', { error: error.message });
|
|
58
|
+
res.status(500).json({ success: false, error: error.message || 'Failed to fetch dashboard trend' });
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
51
62
|
module.exports = {
|
|
52
63
|
getDashboardController,
|
|
53
64
|
getDashboardStatsControllerById,
|
|
54
|
-
updateDashboardControllerById
|
|
65
|
+
updateDashboardControllerById,
|
|
66
|
+
getDashboardTrendController
|
|
55
67
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const EXCLUDED_PHONE_NUMBERS = [
|
|
2
|
+
'whatsapp:+51951538602',
|
|
3
|
+
'whatsapp:+51976302758',
|
|
4
|
+
'whatsapp:+5215511638690',
|
|
5
|
+
'whatsapp:+5215653318508',
|
|
6
|
+
'whatsapp:+51985959446',
|
|
7
|
+
'Whatsapp:+51937258780'
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
function getExcludedPhoneNumbersFilter() {
|
|
11
|
+
return {
|
|
12
|
+
$exists: true,
|
|
13
|
+
$nin: EXCLUDED_PHONE_NUMBERS
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { getExcludedPhoneNumbersFilter };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const moment = require('moment-timezone');
|
|
2
|
+
|
|
3
|
+
const { getExcludedPhoneNumbersFilter } = require('./phoneFilterHelper');
|
|
4
|
+
|
|
5
|
+
const MEXICO_TZ = 'America/Mexico_City';
|
|
6
|
+
|
|
7
|
+
function getMexicoDateRange(days = 60) {
|
|
8
|
+
const today = moment.tz(MEXICO_TZ);
|
|
9
|
+
const startDate = today.clone().subtract(days + 1, 'days').startOf('day').toDate();
|
|
10
|
+
return { startDate, today };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildBaseStages(startDate, matchConditions, extraProjectFields = {}) {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
$match: {
|
|
17
|
+
createdAt: { $gte: startDate, $exists: true },
|
|
18
|
+
...matchConditions
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
$project: {
|
|
23
|
+
mexicoDate: {
|
|
24
|
+
$dateToString: { format: '%Y-%m-%d', date: '$createdAt', timezone: MEXICO_TZ }
|
|
25
|
+
},
|
|
26
|
+
phoneNumber: { $ifNull: ['$numero', '$phoneNumber'] },
|
|
27
|
+
...extraProjectFields
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
$match: { phoneNumber: getExcludedPhoneNumbersFilter() }
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildPatientMessagesPipeline(startDate) {
|
|
37
|
+
return [
|
|
38
|
+
...buildBaseStages(startDate, { from_me: false }),
|
|
39
|
+
{
|
|
40
|
+
$group: {
|
|
41
|
+
_id: '$mexicoDate',
|
|
42
|
+
totalMessages: { $sum: 1 },
|
|
43
|
+
uniquePeople: { $addToSet: '$phoneNumber' }
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
$project: { date: '$_id', totalMessages: 1, uniquePeople: { $size: '$uniquePeople' } }
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildToolDistributionPipeline(startDate) {
|
|
53
|
+
return [
|
|
54
|
+
...buildBaseStages(
|
|
55
|
+
startDate,
|
|
56
|
+
{ from_me: true, tools_executed: { $exists: true, $ne: [] } },
|
|
57
|
+
{ tools_executed: 1 }
|
|
58
|
+
),
|
|
59
|
+
{ $unwind: '$tools_executed' },
|
|
60
|
+
{
|
|
61
|
+
$project: {
|
|
62
|
+
mexicoDate: 1,
|
|
63
|
+
toolName: { $ifNull: ['$tools_executed.tool_name', 'Unknown'] }
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
$group: {
|
|
68
|
+
_id: { date: '$mexicoDate', toolName: '$toolName' },
|
|
69
|
+
count: { $sum: 1 }
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
$group: {
|
|
74
|
+
_id: '$_id.date',
|
|
75
|
+
tools: { $push: { toolName: '$_id.toolName', count: '$count' } },
|
|
76
|
+
activatedFunctions: { $sum: '$count' }
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
$project: { date: '$_id', tools: 1, activatedFunctions: 1 }
|
|
81
|
+
}
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function indexByDate(data) {
|
|
86
|
+
const index = {};
|
|
87
|
+
for (const item of data) index[item.date] = item;
|
|
88
|
+
return index;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function mergeTrendData(patientData, toolData, today, days = 60) {
|
|
92
|
+
const patients = indexByDate(patientData);
|
|
93
|
+
const tools = indexByDate(toolData);
|
|
94
|
+
|
|
95
|
+
const result = [];
|
|
96
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
97
|
+
const dateStr = today.clone().subtract(i, 'days').format('YYYY-MM-DD');
|
|
98
|
+
const dayPatient = patients[dateStr];
|
|
99
|
+
const dayTool = tools[dateStr];
|
|
100
|
+
|
|
101
|
+
result.push({
|
|
102
|
+
date: dateStr,
|
|
103
|
+
totalMessages: dayPatient?.totalMessages || 0,
|
|
104
|
+
uniquePeople: dayPatient?.uniquePeople || 0,
|
|
105
|
+
activatedFunctions: dayTool?.activatedFunctions || 0,
|
|
106
|
+
tools: dayTool?.tools || []
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { getMexicoDateRange, buildPatientMessagesPipeline, buildToolDistributionPipeline, mergeTrendData };
|
package/lib/routes/index.js
CHANGED
|
@@ -83,6 +83,7 @@ const templateRouteDefinitions = {
|
|
|
83
83
|
const dashboardRouteDefinitions = {
|
|
84
84
|
'GET /': 'getDashboardController',
|
|
85
85
|
'GET /stats/:id': 'getDashboardStatsControllerById',
|
|
86
|
+
'GET /trend': 'getDashboardTrendController',
|
|
86
87
|
'POST /:id': 'updateDashboardControllerById'
|
|
87
88
|
};
|
|
88
89
|
|
|
@@ -193,6 +194,7 @@ const builtInControllers = {
|
|
|
193
194
|
// Dashboard controllers
|
|
194
195
|
getDashboardController: dashboardController.getDashboardController,
|
|
195
196
|
getDashboardStatsControllerById: dashboardController.getDashboardStatsControllerById,
|
|
197
|
+
getDashboardTrendController: dashboardController.getDashboardTrendController,
|
|
196
198
|
updateDashboardControllerById: dashboardController.updateDashboardControllerById
|
|
197
199
|
};
|
|
198
200
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const { logger } = require('../utils/logger');
|
|
2
2
|
const MapCache = require('../utils/MapCache');
|
|
3
3
|
|
|
4
|
+
const { Message } = require('../models/messageModel');
|
|
5
|
+
|
|
4
6
|
const { fetchBoxesFromAirtable, fetchDetailsFromAirtable, attachPreview } = require('../helpers/dashboardHelper');
|
|
7
|
+
const { getMexicoDateRange, buildPatientMessagesPipeline, buildToolDistributionPipeline, mergeTrendData } = require('../helpers/trendHelper');
|
|
5
8
|
|
|
6
9
|
const boxCache = new MapCache({ maxSize: 100 });
|
|
7
10
|
const detailCache = new MapCache({ maxSize: 100 });
|
|
@@ -61,8 +64,25 @@ async function updateBox(id, data) {
|
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
async function getDailyTrend(days) {
|
|
68
|
+
const { startDate, today } = getMexicoDateRange(days);
|
|
69
|
+
|
|
70
|
+
const patientPipeline = buildPatientMessagesPipeline(startDate);
|
|
71
|
+
const patientData = await Message.aggregate(patientPipeline);
|
|
72
|
+
|
|
73
|
+
const toolPipeline = buildToolDistributionPipeline(startDate);
|
|
74
|
+
const toolData = await Message.aggregate(toolPipeline);
|
|
75
|
+
|
|
76
|
+
const dailyData = mergeTrendData(patientData, toolData, today, days);
|
|
77
|
+
|
|
78
|
+
logger.info('[DashboardService] Daily trend fetched', { days, totalDays: dailyData.length });
|
|
79
|
+
|
|
80
|
+
return { dailyData, totalDays: dailyData.length };
|
|
81
|
+
}
|
|
82
|
+
|
|
64
83
|
module.exports = {
|
|
65
84
|
getAllBoxes,
|
|
66
85
|
getStatsById,
|
|
67
|
-
updateBox
|
|
86
|
+
updateBox,
|
|
87
|
+
getDailyTrend
|
|
68
88
|
};
|