enterprisecargo / src /services /alertEngineService.js
vish85521's picture
Upload 64 files
eeb3436 verified
function createAlertEngineService(deps) {
const {
config,
alertsRepository,
alertRulesRepository,
} = deps;
function defaultsForType(type) {
const base = {
HIGH_TEMPERATURE: {
severity: "WARNING",
thresholdNumeric: config.alerts.highTemperatureThresholdC,
},
GAS_SPIKE: {
severity: "WARNING",
thresholdNumeric: config.alerts.gasSpikeThresholdRaw,
},
SHOCK_DETECTED: {
severity: "CRITICAL",
thresholdNumeric: null,
},
OFFLINE: {
severity: "CRITICAL",
thresholdNumeric: config.alerts.offlineThresholdMs,
},
GPS_LOST: {
severity: "WARNING",
thresholdNumeric: null,
},
};
return base[type];
}
function resolveThresholdRule(ruleMap, alertType) {
const fallback = defaultsForType(alertType);
const dbRule = ruleMap.get(alertType);
if (!dbRule) {
return {
ruleId: null,
severity: fallback.severity,
thresholdNumeric: fallback.thresholdNumeric,
};
}
return {
ruleId: dbRule.id,
severity: dbRule.severity || fallback.severity,
thresholdNumeric:
dbRule.threshold_numeric !== null && dbRule.threshold_numeric !== undefined
? Number(dbRule.threshold_numeric)
: fallback.thresholdNumeric,
};
}
async function applyAlertState(client, payload) {
const {
tenantId,
fleetId,
truckId,
containerId,
tripId,
alertType,
shouldBeOpen,
severity,
title,
message,
latestValueNumeric,
latestValueBoolean,
thresholdValueNumeric,
metadata,
alertRuleId,
actorUserId,
canAutoResolve = true,
} = payload;
const existing = await alertsRepository.findActiveAlertForUpdate(client, {
tenantId,
truckId,
containerId,
alertType,
});
if (shouldBeOpen) {
if (!existing) {
const created = await alertsRepository.createAlert(client, {
tenantId,
fleetId,
truckId,
containerId,
tripId,
alertRuleId,
alertType,
severity,
title,
message,
latestValueNumeric,
latestValueBoolean,
thresholdValueNumeric,
metadata,
});
await alertsRepository.insertAlertEvent(client, {
tenantId,
alertId: created.id,
eventType: "OPENED",
fromStatus: null,
toStatus: "OPEN",
actorUserId,
message,
metadata,
});
return created;
}
const updated = await alertsRepository.updateActiveAlert(client, {
alertId: existing.id,
severity,
title,
message,
latestValueNumeric,
latestValueBoolean,
thresholdValueNumeric,
metadata,
});
return updated;
}
if (!existing || !canAutoResolve) {
return existing;
}
const resolved = await alertsRepository.resolveAlert(client, {
alertId: existing.id,
message,
metadata,
});
await alertsRepository.insertAlertEvent(client, {
tenantId,
alertId: existing.id,
eventType: "RESOLVED",
fromStatus: existing.status,
toStatus: "RESOLVED",
actorUserId,
message,
metadata,
});
return resolved;
}
async function evaluateTelemetryInTransaction(client, context, telemetry) {
const rules = await alertRulesRepository.getAlertRuleMap(
client,
context.tenantId,
context.fleetId
);
const highTempRule = resolveThresholdRule(rules, "HIGH_TEMPERATURE");
const gasRule = resolveThresholdRule(rules, "GAS_SPIKE");
const shockRule = resolveThresholdRule(rules, "SHOCK_DETECTED");
const gpsRule = resolveThresholdRule(rules, "GPS_LOST");
const evaluations = [
{
alertType: "HIGH_TEMPERATURE",
title: "High temperature detected",
shouldBeOpen:
telemetry.temperatureC !== null &&
telemetry.temperatureC > Number(highTempRule.thresholdNumeric),
severity: highTempRule.severity,
latestValueNumeric: telemetry.temperatureC,
latestValueBoolean: null,
thresholdValueNumeric: highTempRule.thresholdNumeric,
message:
telemetry.temperatureC !== null
? `Temperature ${telemetry.temperatureC}C exceeds threshold ${highTempRule.thresholdNumeric}C`
: "Temperature threshold exceeded",
alertRuleId: highTempRule.ruleId,
},
{
alertType: "GAS_SPIKE",
title: "Gas spike detected",
shouldBeOpen:
telemetry.gasRaw !== null && telemetry.gasRaw > Number(gasRule.thresholdNumeric),
severity: gasRule.severity,
latestValueNumeric: telemetry.gasRaw,
latestValueBoolean: null,
thresholdValueNumeric: gasRule.thresholdNumeric,
message:
telemetry.gasRaw !== null
? `Gas sensor value ${telemetry.gasRaw} exceeds threshold ${gasRule.thresholdNumeric}`
: "Gas threshold exceeded",
alertRuleId: gasRule.ruleId,
},
{
alertType: "SHOCK_DETECTED",
title: "Shock detected",
shouldBeOpen: telemetry.shock === true,
severity: shockRule.severity,
latestValueNumeric: null,
latestValueBoolean: telemetry.shock,
thresholdValueNumeric: shockRule.thresholdNumeric,
message: telemetry.shock
? "Shock event reported by sensor"
: "Shock condition cleared",
alertRuleId: shockRule.ruleId,
canAutoResolve: config.alerts.autoResolveShock,
},
{
alertType: "GPS_LOST",
title: "GPS signal lost",
shouldBeOpen: telemetry.gpsFix === false,
severity: gpsRule.severity,
latestValueNumeric: null,
latestValueBoolean: telemetry.gpsFix,
thresholdValueNumeric: gpsRule.thresholdNumeric,
message:
telemetry.gpsFix === false
? "GPS fix is unavailable"
: "GPS fix restored",
alertRuleId: gpsRule.ruleId,
},
{
alertType: "OFFLINE",
title: "Unit offline",
shouldBeOpen: false,
severity: defaultsForType("OFFLINE").severity,
latestValueNumeric: 0,
latestValueBoolean: null,
thresholdValueNumeric: config.alerts.offlineThresholdMs,
message: "Telemetry resumed",
alertRuleId: null,
},
];
for (const evaluation of evaluations) {
await applyAlertState(client, {
tenantId: context.tenantId,
fleetId: context.fleetId,
truckId: context.truckId,
containerId: context.containerId,
tripId: context.tripId,
alertType: evaluation.alertType,
shouldBeOpen: evaluation.shouldBeOpen,
severity: evaluation.severity,
title: evaluation.title,
message: evaluation.message,
latestValueNumeric: evaluation.latestValueNumeric,
latestValueBoolean: evaluation.latestValueBoolean,
thresholdValueNumeric: evaluation.thresholdValueNumeric,
metadata: {
telemetrySourceTs: telemetry.sourceTs,
receivedAt: telemetry.receivedAt,
mqttTopic: telemetry.mqttTopic,
},
alertRuleId: evaluation.alertRuleId,
actorUserId: null,
canAutoResolve:
evaluation.canAutoResolve === undefined
? true
: evaluation.canAutoResolve,
});
}
}
async function evaluateOfflineCandidateInTransaction(client, candidate) {
const rules = await alertRulesRepository.getAlertRuleMap(
client,
candidate.tenant_id,
candidate.fleet_id
);
const offlineRule = resolveThresholdRule(rules, "OFFLINE");
const staleMs = Math.max(
0,
Math.floor(Date.now() - new Date(candidate.received_at).getTime())
);
await applyAlertState(client, {
tenantId: candidate.tenant_id,
fleetId: candidate.fleet_id,
truckId: candidate.truck_id,
containerId: candidate.container_id,
tripId: candidate.trip_id,
alertType: "OFFLINE",
shouldBeOpen: true,
severity: offlineRule.severity,
title: "Unit offline",
message: `No telemetry for ${staleMs} ms`,
latestValueNumeric: staleMs,
latestValueBoolean: null,
thresholdValueNumeric: offlineRule.thresholdNumeric,
metadata: {
lastReceivedAt: candidate.received_at,
mqttTopic: candidate.mqtt_topic,
},
alertRuleId: offlineRule.ruleId,
actorUserId: null,
});
}
return {
evaluateTelemetryInTransaction,
evaluateOfflineCandidateInTransaction,
};
}
module.exports = {
createAlertEngineService,
};