136 lines
3.4 KiB
Go
136 lines
3.4 KiB
Go
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))
|
|
}
|
|
}
|