FloorVisualizer/internal/handler/recommend_handler.go
dindang 94895cbc22 Initial commit: FloorVisualizer backend
- Go API server with PostgreSQL + Redis
- AI floor replacement (OpenRouter Gemini)
- Product database (10 brands, 3539 products)
- Recommendation engine, calculator, articles
- Redis async queue + worker pool
- Hot product caching, brand view tracking
- JWT auth, favorites, projects
- Docker deployment ready

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-16 09:47:11 +08:00

80 lines
2.2 KiB
Go

package handler
import (
"database/sql"
"fmt"
"log"
"net/http"
"floorvisualizer/internal/model"
"floorvisualizer/internal/repository"
"floorvisualizer/internal/service"
)
type RecommendResponse struct {
Source model.Product `json:"source"`
Products []model.Product `json:"products"`
Scores []string `json:"scores"`
Total int `json:"total"`
}
var productService *service.ProductService
func initProductService(db *sql.DB) {
if productService != nil { return }
products, err := repository.QueryAllProducts(db)
if err != nil {
log.Printf("Failed to load products: %v", err)
return
}
log.Printf("Loaded %d products for recommendation engine", len(products))
productService = service.NewProductService(products, model.DefaultEngineConfig())
}
func handleRecommend(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
sku := r.URL.Query().Get("sku")
if sku == "" {
writeJSON(w, 400, map[string]string{"error": "sku is required"})
return
}
initProductService(db)
if productService == nil {
writeJSON(w, 500, map[string]string{"error": "Engine not initialized"})
return
}
source, err := repository.GetProductBySKU(db, sku)
if err != nil {
writeJSON(w, 404, map[string]string{"error": "product not found"})
return
}
// Load variants & build specs for price-aware matching
if variants, _ := repository.GetVariantsByStyle(db, sku); len(variants) > 0 {
source.Variants = variants
allVariants := append([]model.Product{*source}, variants...)
for _, v := range allVariants {
source.Specs = append(source.Specs, model.ProductSpec{
SKU: v.SKU, SizeLabel: v.SizeLabel,
WidthIn: v.WidthIn, LengthIn: v.LengthIn, Finish: v.Finish,
PricePerSqft: v.PricePerSqft, PriceTier: v.PriceTier,
CoverageSqftPerBox: v.CoverageSqftPerBox, MainImageURL: v.MainImageURL,
})
}
}
results := productService.FindMatches(*source)
prods := make([]model.Product, len(results))
scores := make([]string, len(results))
for i, r := range results {
prods[i] = r.Product
scores[i] = fmt.Sprintf("%.0f%%", r.Score)
}
writeJSON(w, 200, RecommendResponse{
Source: *source, Products: prods, Scores: scores, Total: len(prods),
})
}
}