netanalyzer/integrations/storeHealth.js
Joseph McQueen 459301cc4d chore: Phase 0 initial hygiene
- Initialize git repository
- Add comprehensive .gitignore (protects .env and secrets)
- Fix package.json (correct main entry, add metadata)
- Expand .env.example with all required variables and comments
- Add README.md with architecture, setup, and commands
- Clean up empty scaffolding directories (logs removed, agents/models marked)
- Backup previous .env file locally

This establishes a safe foundation before further development.
2026-06-24 13:32:37 -04:00

219 lines
No EOL
9.4 KiB
JavaScript

const { getStoreLocation } = require('../services/siw');
const { findMerakiNetwork, getMerakiDeviceAvailabilities, getMerakiClients } = require('../services/meraki');
const { getMDMDevices } = require('../services/mdm');
async function getStoreHealth(storeNumber) {
console.log(`🔍 Running health analysis for store ${storeNumber}`);
let locationData, merakiNetwork, merakiClients = [], mdmDevices = [], paymentTerminals = [], printers = [];
try {
[locationData, merakiNetwork] = await Promise.all([
getStoreLocation(storeNumber),
findMerakiNetwork(storeNumber)
]);
if (merakiNetwork) {
[merakiClients, mdmDevices, paymentTerminals, printers] = await Promise.all([
getMerakiClients(merakiNetwork.id),
getMDMDevices(storeNumber),
require('../services/siw').getStorePaymentTerminals(storeNumber),
require('../services/siw').getStorePrinters(storeNumber)
]);
}
} catch (err) {
console.error('❌ Health analysis error:', err.message);
}
let summary = `**📊 Store ${storeNumber} Health Summary - ${locationData?.name || 'Unknown Store'}**\n\n`;
let overallScore = 100;
const issues = [];
// === Network Infrastructure ===
summary += `**🌐 Network Infrastructure**\n`;
if (merakiNetwork) {
summary += `- Meraki Network: ✅ [${merakiNetwork.name}](${merakiNetwork.url})\n`;
try {
const avail = await getMerakiDeviceAvailabilities(merakiNetwork.id);
const switches = avail.filter(d => d.productType === 'switch' && d.status !== 'dormant');
const aps = avail.filter(d => d.productType === 'wireless' && d.status !== 'dormant');
const swOnline = switches.filter(d => d.status === 'online').length;
const apOnline = aps.filter(d => d.status === 'online').length;
const apAlerting = aps.filter(d => d.status === 'alerting').length;
summary += `- Switching: ${swOnline}/${switches.length} online\n`;
summary += `- Access Points: ${apOnline}/${aps.length} online`;
if (apAlerting > 0) {
summary += ` (${apAlerting} alerting)`;
}
summary += `\n\n`;
// Scoring & Issues
if (swOnline < switches.length) {
const offlineSw = switches.length - swOnline;
overallScore -= offlineSw * 15;
issues.push(`${offlineSw} switch(es) offline`);
}
if (apOnline < aps.length || apAlerting > 0) {
const totalApIssues = aps.length - apOnline;
if (apAlerting > 0) {
overallScore -= totalApIssues * 3;
issues.push(`${apAlerting} access point(s) alerting (check cabling/power)`);
} else {
overallScore -= totalApIssues * 5;
issues.push(`${totalApIssues} access point(s) offline`);
}
}
} catch (e) {
console.error('Meraki availability error:', e.message);
}
} else {
summary += `- Meraki Network: ❌ Not Found\n\n`;
overallScore -= 50;
issues.push("No Meraki network found");
}
// === Core Systems ===
summary += `**🖥️ POS Systems**\n`;
if (mdmDevices && mdmDevices.length > 0) {
const servers = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('SRV'));
const mobileRegs = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('MR'));
const custDisplays = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('CD'));
const iphones = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('IPH'));
// Server (heavy penalty)
let serverStatus = '❓ Unknown';
if (servers.length > 0) {
const match = merakiClients.find(c =>
c?.description && servers.some(s =>
(s.UserName || s.DeviceFriendlyName || '').toLowerCase().includes((c.description || '').toLowerCase())
)
);
serverStatus = match && match.status === 'Online' ? '✅ Online' : '❌ Offline';
if (serverStatus === '❌ Offline') {
overallScore -= 35;
issues.push(`Store Server ${servers[0]?.UserName} is offline`);
}
}
summary += `- Store Server: ${serverStatus}\n`;
// Mobile Registers
let mobileOnline = 0;
mobileRegs.forEach(reg => {
const name = (reg.UserName || reg.DeviceFriendlyName || '').trim();
const match = merakiClients.find(c =>
c?.description && c.description.toLowerCase().includes(name.toLowerCase()) ||
(c.user && c.user.toLowerCase().includes(name.toLowerCase()))
);
const status = match && match.status === 'Online' ? '✅ Online' : '❌ Offline';
if (status === '✅ Online') mobileOnline++;
if (status === '❌ Offline') issues.push(`Mobile Register ${name} is offline`);
});
summary += `- Mobile Registers: ${mobileOnline}/${mobileRegs.length} online\n`;
// Customer Displays & iPhones (lighter penalty)
let cdOnline = 0;
custDisplays.forEach(cd => {
const name = (cd.UserName || cd.DeviceFriendlyName || '').trim();
const match = merakiClients.find(c =>
c?.description && c.description.toLowerCase().includes(name.toLowerCase()) ||
(c.user && c.user.toLowerCase().includes(name.toLowerCase()))
);
if (match && match.status === 'Online') cdOnline++;
if (match && match.status !== 'Online') issues.push(`Customer Display ${name} is offline`);
});
summary += `- Customer Displays: ${cdOnline}/${custDisplays.length} online\n`;
let iphoneOnline = 0;
iphones.forEach(phone => {
const name = (phone.UserName || phone.DeviceFriendlyName || '').trim();
const match = merakiClients.find(c =>
c?.description && c.description.toLowerCase().includes(name.toLowerCase()) ||
(c.user && c.user.toLowerCase().includes(name.toLowerCase()))
);
if (match && match.status === 'Online') iphoneOnline++;
if (match && match.status !== 'Online') issues.push(`iPhone ${name} is offline`);
});
summary += `- Store iPhones: ${iphoneOnline}/${iphones.length} online\n\n`;
} else {
summary += `- No MDM data available\n\n`;
overallScore -= 20;
}
// === POS & Peripherals (with per-device penalty) ===
summary += `**💳 POS Peripherals**\n`;
// Payment Terminals
if (paymentTerminals && paymentTerminals.length > 0) {
let onlinePayments = 0;
paymentTerminals.forEach(term => {
const name = (term.device_name || term.adyen_device_name || '').trim().toLowerCase();
const cleanIp = (term.ip_address || '').split('.')[0].toLowerCase();
const match = merakiClients.find(c =>
c?.description && (
c.description.toLowerCase().includes(name) ||
c.description.toLowerCase().includes(cleanIp)
)
);
const status = match && match.status === 'Online' ? '✅ Online' : '❌ Offline';
if (status === '✅ Online') onlinePayments++;
if (status === '❌ Offline') {
overallScore -= 5; // penalty per offline terminal
issues.push(`Payment Terminal ${name} is offline`);
}
});
summary += `- Payment Terminals: ${onlinePayments}/${paymentTerminals.length} online\n`;
}
// Printers
const filteredPrinters = (printers || []).filter(p =>
(p.connection_type_name || '').toLowerCase() !== 'usb'
);
if (filteredPrinters.length > 0) {
let onlinePrinters = 0;
filteredPrinters.forEach(printer => {
const name = (printer.printer_name || '').trim().toLowerCase();
const match = merakiClients.find(c =>
c?.description && c.description.toLowerCase().includes(name)
);
const status = match && match.status === 'Online' ? '✅ Online' : '❌ Offline';
if (status === '✅ Online') onlinePrinters++;
if (status === '❌ Offline') {
overallScore -= 4; // penalty per offline printer
issues.push(`Printer ${name} is offline`);
}
});
summary += `- Printers: ${onlinePrinters}/${filteredPrinters.length} online\n`;
}
// === Overall Status ===
overallScore = Math.max(0, Math.round(overallScore));
let statusEmoji = overallScore >= 90 ? '🟢' : overallScore >= 70 ? '🟡' : '🔴';
let statusText = overallScore >= 90 ? 'Good' : overallScore >= 70 ? 'Fair' : 'Needs Attention';
summary += `\n**Overall Status**: ${statusEmoji} ${statusText} (${overallScore}% healthy)\n\n`;
if (issues.length > 0) {
summary += `**⚠️ Issues Detected**\n`;
issues.forEach(issue => summary += `- ${issue}\n`);
} else {
summary += `**✅ No major issues detected**\n`;
}
return { summary };
}
module.exports = { getStoreHealth };