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>
107 lines
3.3 KiB
JavaScript
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 };
|
|
}
|