diff --git a/go-services/inventory-go/ingest.go b/go-services/inventory-go/ingest.go index 04ab99e3..213c60cc 100644 --- a/go-services/inventory-go/ingest.go +++ b/go-services/inventory-go/ingest.go @@ -245,6 +245,49 @@ func (s *Server) handleDeleteItem(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{"status": "deleted", "item_id": itemID}) } +// POST /admin/backfill-od — recompute od_rating for all weapon rows from stored +// raw JSON. Internal-only (service is bound to loopback/compose). Idempotent. +func (s *Server) handleBackfillOD(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + rows, err := s.pool.Query(ctx, + `SELECT rd.item_id, rd.original_json FROM item_raw_data rd + JOIN items i ON i.id = rd.item_id + WHERE i.object_class IN (1,9,31)`) + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]any{"error": err.Error()}) + return + } + type upd struct { + id int + od float64 + } + var updates []upd + scanned := 0 + for rows.Next() { + var id int + var oj map[string]any + if err := rows.Scan(&id, &oj); err != nil { + rows.Close() + writeJSON(w, http.StatusInternalServerError, map[string]any{"error": err.Error()}) + return + } + scanned++ + if od, ok := computeOD(oj); ok { + updates = append(updates, upd{id, od}) + } + } + rows.Close() + updated := 0 + for _, u := range updates { + if _, err := s.pool.Exec(ctx, "UPDATE items SET od_rating=$1 WHERE id=$2", u.od, u.id); err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]any{"error": err.Error(), "updated": updated}) + return + } + updated++ + } + writeJSON(w, http.StatusOK, map[string]any{"scanned": scanned, "updated": updated}) +} + func parseNaiveTime(s string) time.Time { if s == "" { return time.Now().UTC() diff --git a/go-services/inventory-go/main.go b/go-services/inventory-go/main.go index 51611dcc..47140c0a 100644 --- a/go-services/inventory-go/main.go +++ b/go-services/inventory-go/main.go @@ -100,6 +100,7 @@ func main() { mux.HandleFunc("POST /process-inventory", srv.handleProcessInventory) mux.HandleFunc("POST /inventory/{character_name}/item", srv.handleUpsertItem) mux.HandleFunc("DELETE /inventory/{character_name}/item/{item_id}", srv.handleDeleteItem) + mux.HandleFunc("POST /admin/backfill-od", srv.handleBackfillOD) // Suitbuilder (port of suitbuilder.py router, mounted at /suitbuilder). mux.HandleFunc("POST /suitbuilder/search", srv.handleSuitSearch) mux.HandleFunc("GET /suitbuilder/characters", srv.handleSuitCharacters)