Files
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

139 lines
2.9 KiB
Go

package processing
import (
"fmt"
"strings"
)
// StandardFieldDef is the subset of standard_fields used while filling gaps.
type StandardFieldDef struct {
Key string
DefaultValue string
Unit string
MappingHints []string
}
// FillMissingStandardFields copies values from mapped/raw (via key + mapping_hints)
// or default_value into mapped when an enabled field is empty.
func FillMissingStandardFields(mapped, raw map[string]any, fields []StandardFieldDef) map[string]any {
out := mapped
if out == nil {
out = map[string]any{}
} else {
// Shallow copy so callers can keep the original.
cp := make(map[string]any, len(out)+len(fields))
for k, v := range out {
cp[k] = v
}
out = cp
}
for _, f := range fields {
key := strings.TrimSpace(f.Key)
if key == "" {
continue
}
if valuePresent(out[key]) {
continue
}
candidates := make([]string, 0, 1+len(f.MappingHints))
candidates = append(candidates, key)
for _, h := range f.MappingHints {
h = strings.TrimSpace(h)
if h != "" {
candidates = append(candidates, h)
}
}
if v := lookupAny(mapped, candidates...); valuePresent(v) {
out[key] = normalizeFilled(v, f.Unit)
continue
}
if v := lookupAny(raw, candidates...); valuePresent(v) {
out[key] = normalizeFilled(v, f.Unit)
continue
}
if strings.TrimSpace(f.DefaultValue) != "" {
out[key] = normalizeFilled(f.DefaultValue, f.Unit)
}
}
return out
}
func valuePresent(v any) bool {
if v == nil {
return false
}
switch t := v.(type) {
case string:
return strings.TrimSpace(t) != ""
case []any:
return len(t) > 0
case map[string]any:
return len(t) > 0
default:
s := strings.TrimSpace(fmt.Sprint(t))
return s != "" && s != "<nil>"
}
}
func lookupAny(m map[string]any, keys ...string) any {
if m == nil {
return nil
}
lower := map[string]any{}
for k, v := range m {
lower[strings.ToLower(strings.TrimSpace(k))] = v
}
for _, k := range keys {
lk := strings.ToLower(strings.TrimSpace(k))
if v, ok := lower[lk]; ok && valuePresent(v) {
return v
}
}
return nil
}
func normalizeFilled(v any, unit string) any {
switch t := v.(type) {
case string:
s := SanitizeText(t)
if unit != "" && !strings.Contains(strings.ToLower(s), strings.ToLower(unit)) {
// Keep numeric values plain; unit stays on the field definition.
return s
}
return s
default:
return v
}
}
func parseHints(v any) []string {
switch t := v.(type) {
case []string:
return t
case []any:
out := make([]string, 0, len(t))
for _, item := range t {
if s, ok := item.(string); ok && strings.TrimSpace(s) != "" {
out = append(out, strings.TrimSpace(s))
}
}
return out
case string:
s := strings.TrimSpace(t)
if s == "" {
return nil
}
parts := strings.Split(s, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p != "" {
out = append(out, p)
}
}
return out
default:
return nil
}
}