fix(parser): dedupe HPE AHS DIMM inventory per slot, newest snapshot wins

The AHS blackbox stores a DIMM inventory snapshot per POST cycle and
parseDIMMs flattens all of them. dedupeMemory keyed on serial, so a slot
whose module was swapped between captures survived once per historical
occupant — a 24-slot board reported 26 modules and "same P/N" vs
"mixed P/N" configs looked identical.

dedupeMemory now collapses to one entry per non-empty slot with the last
(most recent, since token order follows chronological blackbox-record
order) occurrence winning. Slotless entries keep the serial/part fallback.

See ADL-060.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-08-27 16:49:12 +03:00
co-authored by Claude Sonnet 5
parent 99d8f098ac
commit 632067fef5
2 changed files with 49 additions and 1 deletions
+20 -1
View File
@@ -1045,11 +1045,30 @@ func dedupeCPUs(items []models.CPU) []models.CPU {
return out
}
// dedupeMemory collapses the DIMM inventory to one entry per physical slot.
//
// The AHS blackbox carries a separate inventory snapshot for every POST cycle,
// and parseDIMMs walks all of them. When a module is swapped between captures the
// same slot shows up with different serials; keying the dedupe on serial alone
// (as an earlier version did) let every historical occupant survive, inflating
// the module count. Tokens are appended in blackbox-record order, which is
// chronological, so the last occurrence for a slot is the most recent snapshot
// and wins. Entries without a slot label fall back to serial-based dedupe.
func dedupeMemory(items []models.MemoryDIMM) []models.MemoryDIMM {
slotIndex := make(map[string]int)
seen := make(map[string]bool)
out := make([]models.MemoryDIMM, 0, len(items))
for _, item := range items {
key := valueOr(item.SerialNumber, item.Slot+"|"+item.PartNumber)
if item.Slot != "" {
if idx, ok := slotIndex[item.Slot]; ok {
out[idx] = item
continue
}
slotIndex[item.Slot] = len(out)
out = append(out, item)
continue
}
key := valueOr(item.SerialNumber, item.PartNumber)
if seen[key] {
continue
}