package parser import ( "fmt" "regexp" "strings" "time" "git.mchus.pro/mchus/logpile/internal/models" ) var manufacturedYearWeekPattern = regexp.MustCompile(`^\d{4}-W\d{2}$`) // NormalizeManufacturedYearWeek converts common FRU manufacturing date formats // into contract-compatible YYYY-Www values. Unknown or ambiguous inputs return "". func NormalizeManufacturedYearWeek(raw string) string { value := strings.TrimSpace(raw) if value == "" { return "" } upper := strings.ToUpper(value) if manufacturedYearWeekPattern.MatchString(upper) { return upper } layouts := []string{ time.RFC3339, "2006-01-02T15:04:05", "2006-01-02 15:04:05", "2006-01-02", "2006/01/02", "01/02/2006 15:04:05", "01/02/2006", "01-02-2006", "Mon Jan 2 15:04:05 2006", "Mon Jan _2 15:04:05 2006", "Jan 2 2006", "Jan _2 2006", } for _, layout := range layouts { if ts, err := time.Parse(layout, value); err == nil { year, week := ts.ISOWeek() return formatYearWeek(year, week) } } return "" } func formatYearWeek(year, week int) string { if year <= 0 || week <= 0 || week > 53 { return "" } return fmt.Sprintf("%04d-W%02d", year, week) } // ApplyManufacturedYearWeekFromFRU attaches normalized manufactured_year_week to // component details by exact serial-number match. Board-level FRU entries are not // expanded to components. func ApplyManufacturedYearWeekFromFRU(frus []models.FRUInfo, hw *models.HardwareConfig) { if hw == nil || len(frus) == 0 { return } bySerial := make(map[string]string, len(frus)) for _, fru := range frus { serial := normalizeFRUSerial(fru.SerialNumber) yearWeek := NormalizeManufacturedYearWeek(fru.MfgDate) if serial == "" || yearWeek == "" { continue } if _, exists := bySerial[serial]; exists { continue } bySerial[serial] = yearWeek } if len(bySerial) == 0 { return } for i := range hw.CPUs { attachYearWeek(&hw.CPUs[i].Details, bySerial[normalizeFRUSerial(hw.CPUs[i].SerialNumber)]) } for i := range hw.Memory { attachYearWeek(&hw.Memory[i].Details, bySerial[normalizeFRUSerial(hw.Memory[i].SerialNumber)]) } for i := range hw.Storage { attachYearWeek(&hw.Storage[i].Details, bySerial[normalizeFRUSerial(hw.Storage[i].SerialNumber)]) } for i := range hw.PCIeDevices { attachYearWeek(&hw.PCIeDevices[i].Details, bySerial[normalizeFRUSerial(hw.PCIeDevices[i].SerialNumber)]) } for i := range hw.GPUs { attachYearWeek(&hw.GPUs[i].Details, bySerial[normalizeFRUSerial(hw.GPUs[i].SerialNumber)]) } for i := range hw.NetworkAdapters { attachYearWeek(&hw.NetworkAdapters[i].Details, bySerial[normalizeFRUSerial(hw.NetworkAdapters[i].SerialNumber)]) } for i := range hw.PowerSupply { attachYearWeek(&hw.PowerSupply[i].Details, bySerial[normalizeFRUSerial(hw.PowerSupply[i].SerialNumber)]) } } func attachYearWeek(details *map[string]any, yearWeek string) { if yearWeek == "" { return } if *details == nil { *details = map[string]any{} } if existing, ok := (*details)["manufactured_year_week"]; ok && strings.TrimSpace(toString(existing)) != "" { return } (*details)["manufactured_year_week"] = yearWeek } func normalizeFRUSerial(v string) string { s := strings.TrimSpace(v) if s == "" { return "" } switch strings.ToUpper(s) { case "N/A", "NA", "NULL", "UNKNOWN", "-", "0": return "" default: return strings.ToUpper(s) } } func toString(v any) string { switch x := v.(type) { case string: return x default: return strings.TrimSpace(fmt.Sprint(v)) } }