feat(go-services): inventory-go search — object_class_name (exact, gem context)
Loads the ObjectClass enum map and adds object_class_name via translate_object_class, including the context-aware Gem(11) classification (crystal/mana stone/gem/aetheria by item name, using the original name before the material prefix). The rare aetheria-by-IntValues path is documented as not reproduced (needs original_json). Validated vs Python: 0 mismatches over 600 rows (3 queries incl. a 'crystal' text search that exercises the gem context) for object_class_name, name, material_name, item_set_name, value. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
50360f72c4
commit
1294ec4418
2 changed files with 58 additions and 14 deletions
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -26,6 +27,7 @@ var buildVersion = "dev"
|
||||||
type Server struct {
|
type Server struct {
|
||||||
pool *pgxpool.Pool
|
pool *pgxpool.Pool
|
||||||
attributeSets map[string]string // AttributeSetInfo: set-id -> set name
|
attributeSets map[string]string // AttributeSetInfo: set-id -> set name
|
||||||
|
objectClasses map[int]string // ObjectClass: id -> name
|
||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,13 +45,14 @@ func main() {
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
srv := &Server{log: logger, attributeSets: map[string]string{}}
|
srv := &Server{log: logger, attributeSets: map[string]string{}, objectClasses: map[int]string{}}
|
||||||
|
|
||||||
if sets, err := loadAttributeSets(enumPath); err != nil {
|
if sets, classes, err := loadEnums(enumPath); err != nil {
|
||||||
logger.Warn("could not load enum AttributeSetInfo (set names will be unknown)", "err", err, "path", enumPath)
|
logger.Warn("could not load enum DB (set/class names will be unknown)", "err", err, "path", enumPath)
|
||||||
} else {
|
} else {
|
||||||
srv.attributeSets = sets
|
srv.attributeSets = sets
|
||||||
logger.Info("loaded enum AttributeSetInfo", "sets", len(sets))
|
srv.objectClasses = classes
|
||||||
|
logger.Info("loaded enum DB", "sets", len(sets), "object_classes", len(classes))
|
||||||
}
|
}
|
||||||
|
|
||||||
if dsn == "" {
|
if dsn == "" {
|
||||||
|
|
@ -159,13 +162,13 @@ func (s *Server) dbErr(w http.ResponseWriter, where string, err error) {
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]any{"detail": "Internal server error"})
|
writeJSON(w, http.StatusInternalServerError, map[string]any{"detail": "Internal server error"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadAttributeSets reads the comprehensive enum DB and extracts AttributeSetInfo
|
// loadEnums reads the comprehensive enum DB and extracts AttributeSetInfo
|
||||||
// (set-id -> name), mirroring load_comprehensive_enums (dictionaries first, then
|
// (set-id -> name) and ObjectClass (id -> name), mirroring
|
||||||
// enums). Only the slice needed for /sets/list is decoded.
|
// load_comprehensive_enums (dictionaries first, then enums).
|
||||||
func loadAttributeSets(path string) (map[string]string, error) {
|
func loadEnums(path string) (sets map[string]string, classes map[int]string, err error) {
|
||||||
b, err := os.ReadFile(path)
|
b, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
var db struct {
|
var db struct {
|
||||||
Dictionaries map[string]struct {
|
Dictionaries map[string]struct {
|
||||||
|
|
@ -174,17 +177,26 @@ func loadAttributeSets(path string) (map[string]string, error) {
|
||||||
Enums map[string]struct {
|
Enums map[string]struct {
|
||||||
Values map[string]string `json:"values"`
|
Values map[string]string `json:"values"`
|
||||||
} `json:"enums"`
|
} `json:"enums"`
|
||||||
|
ObjectClasses struct {
|
||||||
|
Values map[string]string `json:"values"`
|
||||||
|
} `json:"object_classes"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(b, &db); err != nil {
|
if err := json.Unmarshal(b, &db); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
sets = map[string]string{}
|
||||||
if d, ok := db.Dictionaries["AttributeSetInfo"]; ok && len(d.Values) > 0 {
|
if d, ok := db.Dictionaries["AttributeSetInfo"]; ok && len(d.Values) > 0 {
|
||||||
return d.Values, nil
|
sets = d.Values
|
||||||
|
} else if e, ok := db.Enums["AttributeSetInfo"]; ok {
|
||||||
|
sets = e.Values
|
||||||
}
|
}
|
||||||
if e, ok := db.Enums["AttributeSetInfo"]; ok {
|
classes = map[int]string{}
|
||||||
return e.Values, nil
|
for k, v := range db.ObjectClasses.Values {
|
||||||
|
if n, err := strconv.Atoi(k); err == nil {
|
||||||
|
classes[n] = v
|
||||||
}
|
}
|
||||||
return map[string]string{}, nil
|
}
|
||||||
|
return sets, classes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func envOr(key, def string) string {
|
func envOr(key, def string) string {
|
||||||
|
|
|
||||||
|
|
@ -358,6 +358,12 @@ func (s *Server) enrichRows(rows []map[string]any) []map[string]any {
|
||||||
row["last_updated"] = pyISO(t)
|
row["last_updated"] = pyISO(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// object_class_name — gem(11) context uses the ORIGINAL item name, so
|
||||||
|
// compute before the material prefix below (translate_object_class).
|
||||||
|
if oc := int(toInt64(row["object_class"])); oc != 0 {
|
||||||
|
row["object_class_name"] = s.translateObjectClass(oc, toStr(row["name"]))
|
||||||
|
}
|
||||||
|
|
||||||
// material_name + material prefix on name (material is already a
|
// material_name + material prefix on name (material is already a
|
||||||
// translated string in the DB; enrich_db_item:2371-2602).
|
// translated string in the DB; enrich_db_item:2371-2602).
|
||||||
if mat := toStr(row["material"]); mat != "" {
|
if mat := toStr(row["material"]); mat != "" {
|
||||||
|
|
@ -383,6 +389,32 @@ func (s *Server) enrichRows(rows []map[string]any) []map[string]any {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// translateObjectClass mirrors translate_object_class: ObjectClass enum lookup,
|
||||||
|
// with the context-aware Gem(11) classification by item name. The aetheria-by-
|
||||||
|
// IntValues path (for gem-class items not named crystal/gem/mana stone) is not
|
||||||
|
// reproduced here (it needs original_json) — a documented rare edge.
|
||||||
|
func (s *Server) translateObjectClass(oc int, name string) string {
|
||||||
|
base, ok := s.objectClasses[oc]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Sprintf("Unknown_ObjectClass_%d", oc)
|
||||||
|
}
|
||||||
|
if base == "Gem" && oc == 11 {
|
||||||
|
n := strings.ToLower(name)
|
||||||
|
switch {
|
||||||
|
case strings.Contains(n, "mana stone"):
|
||||||
|
return "Mana Stone"
|
||||||
|
case strings.Contains(n, "crystal"):
|
||||||
|
return "Crystal"
|
||||||
|
case strings.Contains(n, "gem"):
|
||||||
|
return "Gem"
|
||||||
|
case strings.Contains(n, "aetheria"):
|
||||||
|
return "Aetheria"
|
||||||
|
}
|
||||||
|
return "Gem"
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
// translateSetID mirrors translate_equipment_set_id (AttributeSetInfo lookup,
|
// translateSetID mirrors translate_equipment_set_id (AttributeSetInfo lookup,
|
||||||
// ID-string fallback).
|
// ID-string fallback).
|
||||||
func (s *Server) translateSetID(setID string) string {
|
func (s *Server) translateSetID(setID string) string {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue