collabSupport/integrations/atlas/resolveStoreDevice.js
jmcqueen 5e54ea6f57 Add /atlasdiag live AZM diagnostic with source alerts and monitor auto-off.
Ports Atlas /check as /atlasdiag: Hub resolve, UI tunnel, WebSocket report
for sources/zones/accessories, low source dB warnings, and auto-disable of
the monitor speaker when left on after troubleshooting.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 17:59:17 -04:00

107 lines
3.3 KiB
JavaScript

// integrations/atlas/resolveStoreDevice.js
// Strict AMP store resolution for /atlasdiag (separate from avstatus includes() matching).
export function deviceStatus(d) {
return String(
d?.status || d?.effective_status || d?.state?.status || d?.state?.effective_status || '',
).toLowerCase().trim();
}
export function isOffline(d) {
const st = deviceStatus(d);
return st === 'offline' || st === 'disconnected' || st === 'never_seen';
}
/**
* Resolve by UUID, exact name, or unique partial name match.
* @throws {{ code: 'AMBIGUOUS' }} when multiple partial matches
*/
export function resolveDevice(devices, idOrName) {
if (!idOrName) return null;
const q = String(idOrName).trim();
const byId = devices.find(d => d.id === q);
if (byId) return byId;
const lower = q.toLowerCase();
const exact = devices.find(d => String(d.name || '').toLowerCase() === lower);
if (exact) return exact;
const partial = devices.filter(d => String(d.name || '').toLowerCase().includes(lower));
if (partial.length === 1) return partial[0];
if (partial.length > 1) {
const err = new Error(
`Ambiguous device "${q}": ${partial.slice(0, 5).map(d => d.name).join(', ')}`,
);
err.code = 'AMBIGUOUS';
err.matches = partial;
throw err;
}
return null;
}
/**
* Pad a store number to 6 digits (2547 → 002547).
*/
export function padStoreNumber(storeNumber) {
const digits = String(storeNumber).replace(/\D/g, '');
if (!digits) return null;
return digits.padStart(6, '0').slice(-6);
}
/**
* Match device names like US002547AMP / CA000969AMP for a store number.
*
* @returns {{ device: object|null, storePadded: string|null, matches: object[], error?: string, code?: string }}
*/
export function resolveStoreDevice(devices, storeOrName) {
const q = String(storeOrName || '').trim();
if (!q) {
return { device: null, storePadded: null, matches: [], error: 'Store number required', code: 'MISSING' };
}
if (!/^\d{1,6}$/.test(q)) {
try {
const device = resolveDevice(devices, q);
if (!device) {
return { device: null, storePadded: null, matches: [], error: `No device found for "${q}"`, code: 'NOT_FOUND' };
}
const m = String(device.name || '').match(/^[A-Z]{2}(\d{6})AMP$/i);
return { device, storePadded: m ? m[1] : null, matches: [device] };
} catch (err) {
if (err.code === 'AMBIGUOUS') {
return {
device: null,
storePadded: null,
matches: err.matches || [],
error: err.message,
code: 'AMBIGUOUS',
};
}
throw err;
}
}
const padded = padStoreNumber(q);
const re = new RegExp(`^[A-Z]{2}${padded}AMP$`, 'i');
const matches = (devices || []).filter(d => re.test(String(d.name || '')));
if (matches.length === 0) {
return {
device: null,
storePadded: padded,
matches: [],
error: `No AZM found for store ${String(Number(padded))} (${padded})`,
code: 'NOT_FOUND',
};
}
if (matches.length > 1) {
return {
device: null,
storePadded: padded,
matches,
error: `Multiple AZMs for store ${String(Number(padded))}: ${matches.map(d => d.name).join(', ')}. Retry with a device name.`,
code: 'AMBIGUOUS',
};
}
return { device: matches[0], storePadded: padded, matches };
}