Files
GRH/water-api/src/jobs/negativeFlowDetection.ts
2026-02-01 20:54:13 -06:00

171 lines
5.8 KiB
TypeScript

import cron from 'node-cron';
import * as notificationService from '../services/notification.service';
/**
* Cron job that runs daily at 1:00 AM to detect meters with negative flow
* and create notifications for responsible users
*/
export function scheduleNegativeFlowDetection(): void {
// Schedule: Every day at 1:00 AM
// Cron format: minute hour day-of-month month day-of-week
// '0 1 * * *' = At 01:00 (1:00 AM) every day
cron.schedule('0 1 * * *', async () => {
console.log('🔍 [Cron] Starting negative flow detection job...');
const startTime = Date.now();
try {
// Get all meters with negative flow values
const negativeFlowMeters = await notificationService.getMetersWithNegativeFlow();
if (negativeFlowMeters.length === 0) {
console.log('✅ [Cron] No meters with negative flow found');
return;
}
console.log(`⚠️ [Cron] Found ${negativeFlowMeters.length} meter(s) with negative flow`);
let notificationsCreated = 0;
let errors = 0;
// Group meters by project to avoid duplicate notifications
const metersByProject = new Map<string, typeof negativeFlowMeters>();
for (const meter of negativeFlowMeters) {
const projectId = meter.project_id;
if (!metersByProject.has(projectId)) {
metersByProject.set(projectId, []);
}
metersByProject.get(projectId)!.push(meter);
}
// Create notifications for each project's users
for (const [projectId, meters] of metersByProject.entries()) {
try {
// Get users responsible for this project
const userIds = await notificationService.getUsersForProject(projectId);
if (userIds.length === 0) {
console.log(`⚠️ [Cron] No users found for project ${projectId}`);
continue;
}
// Create notification for each meter for each user
for (const meter of meters) {
const title = 'Negative Flow Alert';
const message = `${meter.name} (${meter.serial_number}) has negative flow of ${meter.last_reading_value} units`;
for (const userId of userIds) {
try {
await notificationService.create({
user_id: userId,
meter_id: meter.id,
notification_type: 'NEGATIVE_FLOW',
title,
message,
meter_serial_number: meter.serial_number,
flow_value: meter.last_reading_value,
});
notificationsCreated++;
} catch (error) {
console.error(`❌ [Cron] Error creating notification for user ${userId}, meter ${meter.id}:`, error);
errors++;
}
}
}
} catch (error) {
console.error(`❌ [Cron] Error processing project ${projectId}:`, error);
errors++;
}
}
const duration = Date.now() - startTime;
console.log(
`✅ [Cron] Negative flow detection completed in ${duration}ms:`,
`${notificationsCreated} notification(s) created,`,
`${errors} error(s)`
);
// Clean up old read notifications (optional maintenance task)
try {
const deletedCount = await notificationService.deleteOldReadNotifications();
if (deletedCount > 0) {
console.log(`🗑️ [Cron] Cleaned up ${deletedCount} old read notification(s)`);
}
} catch (error) {
console.error('❌ [Cron] Error cleaning up old notifications:', error);
}
} catch (error) {
console.error('❌ [Cron] Fatal error in negative flow detection job:', error);
}
});
console.log('⏰ [Cron] Negative flow detection job scheduled (daily at 1:00 AM)');
}
/**
* Manual trigger for testing the negative flow detection
* Can be called directly for testing purposes
*/
export async function triggerNegativeFlowDetection(): Promise<void> {
console.log('🔍 [Manual] Starting negative flow detection...');
const startTime = Date.now();
try {
const negativeFlowMeters = await notificationService.getMetersWithNegativeFlow();
if (negativeFlowMeters.length === 0) {
console.log('✅ [Manual] No meters with negative flow found');
return;
}
console.log(`⚠️ [Manual] Found ${negativeFlowMeters.length} meter(s) with negative flow`);
let notificationsCreated = 0;
// Group meters by project
const metersByProject = new Map<string, typeof negativeFlowMeters>();
for (const meter of negativeFlowMeters) {
const projectId = meter.project_id;
if (!metersByProject.has(projectId)) {
metersByProject.set(projectId, []);
}
metersByProject.get(projectId)!.push(meter);
}
// Create notifications
for (const [projectId, meters] of metersByProject.entries()) {
const userIds = await notificationService.getUsersForProject(projectId);
for (const meter of meters) {
const title = 'Negative Flow Alert';
const message = `${meter.name} (${meter.serial_number}) has negative flow of ${meter.last_reading_value} units`;
for (const userId of userIds) {
await notificationService.create({
user_id: userId,
meter_id: meter.id,
notification_type: 'NEGATIVE_FLOW',
title,
message,
meter_serial_number: meter.serial_number,
flow_value: meter.last_reading_value,
});
notificationsCreated++;
}
}
}
const duration = Date.now() - startTime;
console.log(`✅ [Manual] Created ${notificationsCreated} notification(s) in ${duration}ms`);
} catch (error) {
console.error('❌ [Manual] Error in negative flow detection:', error);
throw error;
}
}