feat(inventory-go): search spell_names/spells enrichment + shirt/pants filters
Adds the remaining search-result enrichment that the suitbuilder solver (and the
item-detail UI) depend on, validated byte-exact against the Python service on
production data:
- Load the SpellTable (spells.values, 6266 entries) from the enum DB and port
translate_spell (id -> {id,name,description,school,difficulty,duration,mana,
family}, Unknown_Spell_<id> fallback, "" defaults).
- Emit `spells` (full dicts) and `spell_names` from the ordered passive Spells
array (original_json->'Spells', array order + duplicates preserved), exactly
as enrich_db_item/extract_item_properties do — NOT from item_spells. Only set
when the item has spells. A jsonb_typeof guard keeps non-array Spells safe.
- Add the shirt_only / pants_only / underwear_only filters as CTE-body WHERE
injections (coverage-bit logic on key 218103821), mirroring main.py.
Validation (char Plant Enjoyer, all chars): spell_names 0 mismatches (8 spell
items), spells[].name 0 mismatches, shirt_only/pants_only item sets identical
(0 only-py / 0 only-go). Normal-search total_count still matches Python.
Note: for shirt/pants/slot filters Python's total_count is inconsistent with its
own items (separate count CTE lacks the injection); Go uses one CTE so the count
is self-consistent. Deliberately not replicating that Python bug.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c49b81c237
commit
2473b80519
2 changed files with 128 additions and 10 deletions
|
|
@ -11,6 +11,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -26,9 +27,10 @@ var buildVersion = "dev"
|
|||
|
||||
type Server struct {
|
||||
pool *pgxpool.Pool
|
||||
attributeSets map[string]string // AttributeSetInfo: set-id -> set name
|
||||
objectClasses map[int]string // ObjectClass: id -> name
|
||||
materials map[int]string // MaterialType: id -> name
|
||||
attributeSets map[string]string // AttributeSetInfo: set-id -> set name
|
||||
objectClasses map[int]string // ObjectClass: id -> name
|
||||
materials map[int]string // MaterialType: id -> name
|
||||
spells map[int]map[string]any // SpellTable: spell-id -> raw spell value object
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
|
|
@ -46,15 +48,16 @@ func main() {
|
|||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
srv := &Server{log: logger, attributeSets: map[string]string{}, objectClasses: map[int]string{}, materials: map[int]string{}}
|
||||
srv := &Server{log: logger, attributeSets: map[string]string{}, objectClasses: map[int]string{}, materials: map[int]string{}, spells: map[int]map[string]any{}}
|
||||
|
||||
if e, err := loadEnums(enumPath); err != nil {
|
||||
logger.Warn("could not load enum DB (set/class/material names will be unknown)", "err", err, "path", enumPath)
|
||||
logger.Warn("could not load enum DB (set/class/material/spell names will be unknown)", "err", err, "path", enumPath)
|
||||
} else {
|
||||
srv.attributeSets = e.sets
|
||||
srv.objectClasses = e.objectClasses
|
||||
srv.materials = e.materials
|
||||
logger.Info("loaded enum DB", "sets", len(e.sets), "object_classes", len(e.objectClasses), "materials", len(e.materials))
|
||||
srv.spells = e.spells
|
||||
logger.Info("loaded enum DB", "sets", len(e.sets), "object_classes", len(e.objectClasses), "materials", len(e.materials), "spells", len(e.spells))
|
||||
}
|
||||
|
||||
if dsn == "" {
|
||||
|
|
@ -181,6 +184,7 @@ type enumMaps struct {
|
|||
sets map[string]string
|
||||
objectClasses map[int]string
|
||||
materials map[int]string
|
||||
spells map[int]map[string]any
|
||||
}
|
||||
|
||||
// loadEnums reads the comprehensive enum DB and extracts AttributeSetInfo
|
||||
|
|
@ -199,6 +203,9 @@ func loadEnums(path string) (enumMaps, error) {
|
|||
Dictionaries map[string]valmap `json:"dictionaries"`
|
||||
Enums map[string]valmap `json:"enums"`
|
||||
ObjectClasses valmap `json:"object_classes"`
|
||||
Spells struct {
|
||||
Values map[string]map[string]any `json:"values"`
|
||||
} `json:"spells"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &db); err != nil {
|
||||
return em, err
|
||||
|
|
@ -220,9 +227,41 @@ func loadEnums(path string) (enumMaps, error) {
|
|||
}
|
||||
em.objectClasses = intMap(db.ObjectClasses)
|
||||
em.materials = intMap(db.Enums["MaterialType"])
|
||||
// SpellTable: spell-id -> raw value object (translate_spell reads .name etc.).
|
||||
em.spells = map[int]map[string]any{}
|
||||
for k, v := range db.Spells.Values {
|
||||
if n, err := strconv.Atoi(k); err == nil {
|
||||
em.spells[n] = v
|
||||
}
|
||||
}
|
||||
return em, nil
|
||||
}
|
||||
|
||||
// translateSpell mirrors main.py translate_spell: returns the spell dict
|
||||
// (id + name/description/school/difficulty/duration/mana/family), defaulting
|
||||
// missing fields to "" and the name to Unknown_Spell_<id>.
|
||||
func (s *Server) translateSpell(id int) map[string]any {
|
||||
raw := s.spells[id]
|
||||
get := func(k string, def any) any {
|
||||
if raw != nil {
|
||||
if v, ok := raw[k]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
return map[string]any{
|
||||
"id": id,
|
||||
"name": get("name", fmt.Sprintf("Unknown_Spell_%d", id)),
|
||||
"description": get("description", ""),
|
||||
"school": get("school", ""),
|
||||
"difficulty": get("difficulty", ""),
|
||||
"duration": get("duration", ""),
|
||||
"mana": get("mana", ""),
|
||||
"family": get("family", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func envOr(key, def string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue