From 3310f7fa225b566abc7948f4e03f7990c3cd7a88 Mon Sep 17 00:00:00 2001 From: Jordan Ramos Date: Wed, 27 May 2026 19:40:21 -0600 Subject: [PATCH] Improve CARD enrichment to extract fields from card_flags The owner endpoint doesn't return ncim_discovery (so EQUIP_INST_ID is unavailable from that endpoint). Update extractGraniteFields to pull hostname from CARD_HOSTNAME, ASN from CARD_ASN, CLLI from CARD_CLLI, serial from CARD_DEVICE_ID, and vendor/model from CARD_VENDOR_MODEL in the card_flags array. Assets without EQUIP_INST_ID are not in Granite and should use the Add operation in the loader sheet instead of Change. --- backend/routes/cardApi.js | 54 ++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/backend/routes/cardApi.js b/backend/routes/cardApi.js index 62092e7..08104f2 100644 --- a/backend/routes/cardApi.js +++ b/backend/routes/cardApi.js @@ -590,17 +590,28 @@ function createCardApiRouter() { } /** - * Extract Granite-relevant fields from a CARD asset record. + * Extract Granite-relevant fields from a CARD asset/owner record. + * The owner endpoint returns: owner (confirmed/unconfirmed/etc), card_flags, tmp. + * ncim_discovery is only available from the team assets endpoint. */ function extractGraniteFields(asset, ip) { + // card_flags can be an array or nested in the response differently + let flags = {}; + if (asset.card_flags) { + flags = Array.isArray(asset.card_flags) ? (asset.card_flags[0] || {}) : asset.card_flags; + } + // Also check if flags are at the top level (owner endpoint format) + if (!flags.CARD_HOSTNAME && asset.CARD_HOSTNAME) { + flags = asset; + } + const ncim = asset.ncim_discovery || []; const granite = asset.netops_granite_allips || []; const iseGranite = asset.ise_granite_equipment || []; - const flags = (asset.card_flags && asset.card_flags[0]) || {}; const qualys = asset.qualys_hosts || []; const ivanti = asset.ivanti_assets || []; - // EQUIP_INST_ID — primary from ncim_discovery, fallbacks + // EQUIP_INST_ID — from ncim_discovery or granite (only available from team assets endpoint) let equip_inst_id = null; let site_name = null; let responsible_team = null; @@ -623,25 +634,37 @@ function extractGraniteFields(asset, ip) { equip_inst_id = iseGranite[0].EQUIP_INST_ID || null; } - // Hostname fallbacks - if (!hostname) { - hostname = (flags.CARD_HOSTNAME && flags.CARD_HOSTNAME[0]) - || (qualys.length > 0 && qualys[0].HOSTNAME) - || (ivanti.length > 0 && ivanti[0].hostName) - || null; + // Hostname from card_flags (primary source from owner endpoint) + if (!hostname && flags.CARD_HOSTNAME) { + const hostnames = Array.isArray(flags.CARD_HOSTNAME) ? flags.CARD_HOSTNAME : [flags.CARD_HOSTNAME]; + hostname = hostnames[0] || null; } + if (!hostname && qualys.length > 0) hostname = qualys[0].HOSTNAME || null; + if (!hostname && ivanti.length > 0) hostname = ivanti[0].hostName || null; - // ASN - const mgmt_ip_asn = (flags.CARD_ASN) || null; + // ASN from card_flags + const mgmt_ip_asn = flags.CARD_ASN || null; + + // CLLI → can be used as site hint + if (!site_name && flags.CARD_CLLI) { + site_name = flags.CARD_CLLI; // CLLI code, not full site name — user may need to map + } // Equipment class — always S (Shelf) from CARD context const equipment_class = 'S'; - // Equip status from flags - const equip_status = (flags.status) || null; + // Device ID → maps to SERIALNUMBER in Granite + const serial_number = flags.CARD_DEVICE_ID || null; - // Equip template — not typically in CARD data, leave null - const equip_template = null; + // Equip status from flags + const equip_status = flags.status || null; + + // Vendor/model from card_flags + let equip_template = null; + if (flags.CARD_VENDOR_MODEL && Array.isArray(flags.CARD_VENDOR_MODEL) && flags.CARD_VENDOR_MODEL.length > 0) { + const vm = flags.CARD_VENDOR_MODEL[0]; + equip_template = typeof vm === 'string' ? vm : (vm.vendor_model || null); + } // Confirmed team from owner record const confirmed_team = asset.owner && asset.owner.confirmed @@ -656,6 +679,7 @@ function extractGraniteFields(asset, ip) { equipment_class, equip_template, equip_status, + serial_number, }; }