@peopl-health/nexus 2.0.5 → 2.0.6

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.
@@ -0,0 +1,3 @@
1
+ const INTERACTION_QUALITY_VALUES = ['correct', 'correct but not helpful', 'incorrect', 'harmful'];
2
+
3
+ module.exports = { INTERACTION_QUALITY_VALUES };
@@ -0,0 +1,33 @@
1
+ const { Interaction } = require('../models/interactionModel');
2
+ const { INTERACTION_QUALITY_VALUES } = require('../config/interactionConfig');
3
+
4
+ const addInteractionController = async (req, res) => {
5
+ try {
6
+ const { messages, whatsapp_id, quality, description } = req.body;
7
+ if (!messages || !Array.isArray(messages) || messages.length === 0) {
8
+ return res.status(400).json({ success: false, error: 'Messages array is required and must not be empty' });
9
+ }
10
+ if (!whatsapp_id) return res.status(400).json({ success: false, error: 'WhatsApp ID is required' });
11
+ if (!quality || !INTERACTION_QUALITY_VALUES.includes(quality)) {
12
+ return res.status(400).json({ success: false, error: `Quality must be one of: ${INTERACTION_QUALITY_VALUES.join(', ')}` });
13
+ }
14
+ const interaction = await Interaction.create({ messages, whatsapp_id, quality, description });
15
+ res.status(201).json({ success: true, interaction });
16
+ } catch (error) {
17
+ console.error('Error adding interaction:', error);
18
+ res.status(500).json({ success: false, error: error.message });
19
+ }
20
+ };
21
+
22
+ const getInteractionsByWhatsappIdController = async (req, res) => {
23
+ try {
24
+ const { whatsapp_id } = req.params;
25
+ const interactions = await Interaction.find({ whatsapp_id }).populate('messages').sort({ createdAt: -1 });
26
+ res.status(200).json({ success: true, whatsappId: whatsapp_id, count: interactions.length, interactions });
27
+ } catch (error) {
28
+ console.error('Error fetching interactions:', error);
29
+ res.status(500).json({ success: false, error: error.message });
30
+ }
31
+ };
32
+
33
+ module.exports = { addInteractionController, getInteractionsByWhatsappIdController };
@@ -348,10 +348,40 @@ const getLastInteractionController = async (req, res) => {
348
348
  }
349
349
  };
350
350
 
351
+ const getMessageQualityController = async (req, res) => {
352
+ try {
353
+ const { id } = req.params;
354
+ const message = await Message.findById(id);
355
+ if (!message) return res.status(404).json({ success: false, error: 'Message not found' });
356
+ res.status(200).json({ success: true, messageId: id, quality: message.quality });
357
+ } catch (error) {
358
+ console.error('Error fetching message quality:', error);
359
+ res.status(500).json({ success: false, error: error.message });
360
+ }
361
+ };
362
+
363
+ const updateMessageQualityController = async (req, res) => {
364
+ try {
365
+ const { id } = req.params;
366
+ const { quality } = req.body;
367
+ if (!['low', 'medium', 'high'].includes(quality)) {
368
+ return res.status(400).json({ success: false, error: 'Invalid quality value. Must be low, medium, or high' });
369
+ }
370
+ const message = await Message.findByIdAndUpdate(id, { quality }, { new: true });
371
+ if (!message) return res.status(404).json({ success: false, error: 'Message not found' });
372
+ res.status(200).json({ success: true, messageId: id, quality: message.quality });
373
+ } catch (error) {
374
+ console.error('Error updating message quality:', error);
375
+ res.status(500).json({ success: false, error: error.message });
376
+ }
377
+ };
378
+
351
379
  module.exports = {
352
380
  sendMessageController,
353
381
  sendBulkMessageController,
354
382
  sendBulkMessageAirtableController,
355
383
  getLastInteractionController,
384
+ getMessageQualityController,
385
+ updateMessageQualityController,
356
386
  configureMessageController
357
387
  };
@@ -0,0 +1,23 @@
1
+ const { getRecordByFilter } = require('../services/airtableService');
2
+ const { Monitoreo_ID } = require('../config/airtableConfig');
3
+
4
+ const getPatientInfoController = async (req, res) => {
5
+ try {
6
+ const { id } = req.params;
7
+ if (!id) return res.status(400).json({ success: false, error: 'WhatsApp ID is required' });
8
+
9
+ const records = await getRecordByFilter(Monitoreo_ID, 'estado_general', `{whatsapp_id}='${id}'`);
10
+
11
+ if (!records || records.length === 0) {
12
+ return res.status(404).json({ success: false, error: 'Patient not found' });
13
+ }
14
+
15
+ const clinicalContext = records[0]['clinical-context-json'];
16
+ res.status(200).json({ success: true, whatsappId: id, clinicalContext });
17
+ } catch (error) {
18
+ console.error('Error fetching patient info:', error);
19
+ res.status(500).json({ success: false, error: error.message });
20
+ }
21
+ };
22
+
23
+ module.exports = { getPatientInfoController };
@@ -434,7 +434,8 @@ class NexusMessaging {
434
434
  await this.sendMessage({
435
435
  code: from,
436
436
  body: response,
437
- processed: true
437
+ processed: true,
438
+ origin: 'assistant'
438
439
  });
439
440
  }
440
441
  } catch (error) {
@@ -502,7 +503,8 @@ class NexusMessaging {
502
503
  await this.sendMessage({
503
504
  code: from,
504
505
  body: response,
505
- processed: true
506
+ processed: true,
507
+ origin: 'assistant'
506
508
  });
507
509
  }
508
510
  } catch (error) {
@@ -642,7 +644,8 @@ class NexusMessaging {
642
644
  await this.sendMessage({
643
645
  code: chatId,
644
646
  body: botResponse,
645
- processed: true
647
+ processed: true,
648
+ origin: 'assistant'
646
649
  });
647
650
  }
648
651
 
@@ -1,9 +1,11 @@
1
1
  const { Message, getMessageValues, formatTimestamp } = require('./messageModel');
2
2
  const { Thread } = require('./threadModel');
3
+ const { Interaction } = require('./interactionModel');
3
4
 
4
5
  module.exports = {
5
6
  Message,
6
7
  Thread,
8
+ Interaction,
7
9
  getMessageValues,
8
10
  formatTimestamp
9
11
  };
@@ -0,0 +1,17 @@
1
+ const mongoose = require('mongoose');
2
+ const { INTERACTION_QUALITY_VALUES } = require('../config/interactionConfig');
3
+
4
+ const interactionSchema = new mongoose.Schema({
5
+ messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message', required: true }],
6
+ whatsapp_id: { type: String, required: true, index: true },
7
+ quality: {
8
+ type: String,
9
+ enum: INTERACTION_QUALITY_VALUES,
10
+ required: true
11
+ },
12
+ description: { type: String, default: null }
13
+ }, { timestamps: true });
14
+
15
+ const Interaction = mongoose.model('Interaction', interactionSchema);
16
+
17
+ module.exports = { Interaction, interactionSchema };
@@ -1,8 +1,11 @@
1
1
  const mongoose = require('mongoose');
2
2
  const moment = require('moment-timezone');
3
+ const { getRecordByFilter } = require('../services/airtableService');
4
+ const { Monitoreo_ID } = require('../config/airtableConfig');
3
5
 
4
6
 
5
7
  const messageSchema = new mongoose.Schema({
8
+ raw: { type: Object, required: false },
6
9
  nombre_whatsapp: { type: String, required: true },
7
10
  numero: { type: String, required: true },
8
11
  body: { type: String, required: true },
@@ -16,6 +19,7 @@ const messageSchema = new mongoose.Schema({
16
19
  enum: ['button', 'list', 'flow', 'quick_reply'],
17
20
  default: null
18
21
  },
22
+ interactive_data: { type: Object, default: null },
19
23
  group_id: { type: String, default: null },
20
24
  reply_id: { type: String, default: null },
21
25
  processed: { type: Boolean, default: false },
@@ -23,6 +27,10 @@ const messageSchema = new mongoose.Schema({
23
27
  assistant_id: { type: String, default: null },
24
28
  content_sid: { type: String, default: null },
25
29
  from_me: { type: Boolean, default: false },
30
+ origin: {
31
+ type: String,
32
+ enum: ['whatsapp', 'assistant', 'patient'],
33
+ default: 'whatsapp' },
26
34
  media: {
27
35
  contentType: { type: String, default: null },
28
36
  bucketName: { type: String, default: null },
@@ -35,6 +43,12 @@ const messageSchema = new mongoose.Schema({
35
43
  thumbnail: { type: String, default: null },
36
44
  metadata: { type: Object, default: null }
37
45
  },
46
+ clinical_context: { type: Object, default: null },
47
+ quality: {
48
+ type: String,
49
+ enum: ['low', 'medium', 'high'],
50
+ default: 'medium'
51
+ },
38
52
  memoryType: {
39
53
  type: String,
40
54
  enum: ['active', 'archived'],
@@ -58,10 +72,23 @@ messageSchema.pre('save', function (next) {
58
72
 
59
73
  const Message = mongoose.model('Message', messageSchema);
60
74
 
75
+ async function getClinicalContext(whatsappId) {
76
+ try {
77
+ const records = await getRecordByFilter(Monitoreo_ID, 'estado_general', `{whatsapp_id}='${whatsappId}'`);
78
+ if (records && records.length > 0 && records[0]['clinical-context-json']) {
79
+ return records[0]['clinical-context-json'];
80
+ }
81
+ return null;
82
+ } catch (error) {
83
+ console.error('Error fetching clinical context from Airtable:', error);
84
+ return null;
85
+ }
86
+ }
61
87
 
62
88
  async function insertMessage(values) {
63
89
  try {
64
90
  const skipNumbers = ['5215592261426@s.whatsapp.net', '5215547411345@s.whatsapp.net', '51985959446@s.whatsapp.net'];
91
+ const clinical_context = await getClinicalContext(values.numero);
65
92
  const messageData = {
66
93
  nombre_whatsapp: values.nombre_whatsapp,
67
94
  numero: values.numero,
@@ -72,12 +99,15 @@ async function insertMessage(values) {
72
99
  is_media: values.is_media,
73
100
  is_interactive: values.is_interactive || false,
74
101
  interaction_type: values.interaction_type || null,
102
+ interactive_data: values.interactive_data || null,
75
103
  group_id: values.group_id,
76
104
  reply_id: values.reply_id,
77
105
  from_me: values.from_me,
78
106
  processed: values.processed || skipNumbers.includes(values.numero),
79
107
  media: values.media ? values.media : null,
80
- content_sid: values.content_sid || null
108
+ content_sid: values.content_sid || null,
109
+ clinical_context: clinical_context,
110
+ raw: values.raw || null
81
111
  };
82
112
  console.log('Inserting message', messageData);
83
113
 
@@ -32,7 +32,18 @@ const messageRouteDefinitions = {
32
32
  'POST /send': 'sendMessageController',
33
33
  'POST /send-bulk': 'sendBulkMessageController',
34
34
  'POST /send-bulk-airtable': 'sendBulkMessageAirtableController',
35
- 'GET /last': 'getLastInteractionController'
35
+ 'GET /last': 'getLastInteractionController',
36
+ 'GET /:id/quality': 'getMessageQualityController',
37
+ 'PUT /:id/quality': 'updateMessageQualityController'
38
+ };
39
+
40
+ const interactionRouteDefinitions = {
41
+ 'POST /': 'addInteractionController',
42
+ 'GET /:whatsapp_id': 'getInteractionsByWhatsappIdController'
43
+ };
44
+
45
+ const patientRouteDefinitions = {
46
+ 'GET /:id': 'getPatientInfoController'
36
47
  };
37
48
 
38
49
  const templateRouteDefinitions = {
@@ -68,8 +79,10 @@ const createRouter = (routeDefinitions, controllers) => {
68
79
  // Import built-in controllers
69
80
  const assistantController = require('../controllers/assistantController');
70
81
  const conversationController = require('../controllers/conversationController');
82
+ const interactionController = require('../controllers/interactionController');
71
83
  const mediaController = require('../controllers/mediaController');
72
84
  const messageController = require('../controllers/messageController');
85
+ const patientController = require('../controllers/patientController');
73
86
  const templateController = require('../controllers/templateController');
74
87
  const templateFlowController = require('../controllers/templateFlowController');
75
88
  const uploadController = require('../controllers/uploadController');
@@ -96,6 +109,10 @@ const builtInControllers = {
96
109
  sendTemplateToNewNumberController: conversationController.sendTemplateToNewNumberController,
97
110
  markMessagesAsReadController: conversationController.markMessagesAsReadController,
98
111
 
112
+ // Interaction controllers
113
+ addInteractionController: interactionController.addInteractionController,
114
+ getInteractionsByWhatsappIdController: interactionController.getInteractionsByWhatsappIdController,
115
+
99
116
  // Media controllers
100
117
  getMediaController: mediaController.getMediaController,
101
118
  handleFileUpload: uploadController.handleFileUpload,
@@ -105,6 +122,11 @@ const builtInControllers = {
105
122
  sendBulkMessageController: messageController.sendBulkMessageController,
106
123
  sendBulkMessageAirtableController: messageController.sendBulkMessageAirtableController,
107
124
  getLastInteractionController: messageController.getLastInteractionController,
125
+ getMessageQualityController: messageController.getMessageQualityController,
126
+ updateMessageQualityController: messageController.updateMessageQualityController,
127
+
128
+ // Patient controllers
129
+ getPatientInfoController: patientController.getPatientInfoController,
108
130
 
109
131
  // Template controllers
110
132
  createTemplate: templateController.createTemplate,
@@ -123,8 +145,10 @@ const builtInControllers = {
123
145
  const setupDefaultRoutes = (app) => {
124
146
  app.use('/api/assistant', createRouter(assistantRouteDefinitions, builtInControllers));
125
147
  app.use('/api/conversation', createRouter(conversationRouteDefinitions, builtInControllers));
148
+ app.use('/api/interaction', createRouter(interactionRouteDefinitions, builtInControllers));
126
149
  app.use('/api/media', createRouter(mediaRouteDefinitions, builtInControllers));
127
150
  app.use('/api/message', createRouter(messageRouteDefinitions, builtInControllers));
151
+ app.use('/api/patient', createRouter(patientRouteDefinitions, builtInControllers));
128
152
  app.use('/api/template', createRouter(templateRouteDefinitions, builtInControllers));
129
153
  };
130
154
 
@@ -132,8 +156,10 @@ module.exports = {
132
156
  // Route definitions for customers to use with their own controllers
133
157
  assistantRoutes: assistantRouteDefinitions,
134
158
  conversationRoutes: conversationRouteDefinitions,
159
+ interactionRoutes: interactionRouteDefinitions,
135
160
  mediaRoutes: mediaRouteDefinitions,
136
161
  messageRoutes: messageRouteDefinitions,
162
+ patientRoutes: patientRouteDefinitions,
137
163
  templateRoutes: templateRouteDefinitions,
138
164
 
139
165
  // Helper functions
@@ -203,12 +203,14 @@ class MongoStorage {
203
203
  is_media: isMedia,
204
204
  is_interactive: messageData.isInteractive || false,
205
205
  interaction_type: messageData.interactionType || null,
206
+ interactive_data: messageData.interactiveData || null,
206
207
  group_id: isGroup ? normalizedNumero : null,
207
208
  reply_id: messageData.reply_id || messageData.replyId || null,
208
209
  from_me: messageData.fromMe !== undefined ? messageData.fromMe : true,
209
210
  media,
210
211
  content_sid: messageData.contentSid || null,
211
- template_variables: messageData.variables ? JSON.stringify(messageData.variables) : null
212
+ template_variables: messageData.variables ? JSON.stringify(messageData.variables) : null,
213
+ raw: messageData.raw || null
212
214
  };
213
215
  }
214
216
 
@@ -33,7 +33,8 @@ class MessageParser {
33
33
  type: 'interactive',
34
34
  interactive: interactive,
35
35
  isInteractive: true, // Flag to indicate this is an interactive message
36
- interactionType: interactive.type // Store the specific interaction type
36
+ interactionType: interactive.type, // Store the specific interaction type
37
+ interactiveData: interactive
37
38
  };
38
39
  }
39
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",