Files
backend_fiesta/scratch/nutritionlive/main.go
abhishek c49f5372a5 nutrition: do not cache a lookup made under an unresolved brand
Brand case decides whether the catalogue-intelligence service answers at all.
Measured 30 Sep 2026:

    /nutrition/Balaji/balaji_..._135g   -> health_score 65.3, 545 kcal
    /nutrition/balaji/...  (our spelling) -> every field null

The brand list resolves ours to theirs, and /brands has slowed to 0.2-2.3s,
which exceeded the 3s client timeout on a cold start. The fallback then asked
under our own spelling, received a well-formed empty record, and cached it as
"no nutrition" for six hours -- so one slow moment silently removed nutrition
and health scores from every product of every brand, looking exactly like data
the agent team had not supplied.

Two changes:

  - a result reached without a resolved brand is no longer cached, so the next
    request retries rather than inheriting a wrong answer for six hours. A
    genuine miss on a resolved brand is still cached, which is the case that
    matters for traffic.
  - the brand list is warmed in the background at startup, so no shopper is
    ever in the path of that call.

Also logs which state the feature is in at boot, the way mail does. With
NUTRITION_BASE unset the endpoint simply omits `nutrition` and `healthscore`,
which is indistinguishable from an unscored product -- this deploy went out
without the variable set and had to be diagnosed by probing the API from
outside.

scratch/nutritionlive prints the exact response for any product by running this
code against the live product row and the live service.

NUTRITION_BASE=https://mcp.nearle.ai.in/api must be set in the deployment
environment. Unset, nothing changes and no product carries either key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-30 11:48:27 +05:30

77 lines
2.0 KiB
Go

// Shows the exact `getproductbyvariant` response once NUTRITION_BASE is set.
//
// Takes the LIVE Fiesta response for a product, runs the same decoration the
// endpoint runs, and prints the result. Nothing here is hand-assembled: the
// product row is production's, the panel and score are the live catalogue-
// intelligence service's, and the code between them is what is deployed.
//
// go run ./scratch/nutritionlive # product 7101
// go run ./scratch/nutritionlive <tenantid> <productid>
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
"nearle/models"
"nearle/services"
)
const fiesta = "https://fiesta.nearle.app/live/api/v1/mob/products/getproductbyvariant"
type envelope struct {
Code int `json:"code"`
Details []models.Products `json:"details"`
Message string `json:"message"`
Status bool `json:"status"`
}
func main() {
tenant, product := "1147", "7101"
if len(os.Args) == 3 {
tenant, product = os.Args[1], os.Args[2]
}
base := os.Getenv("NUTRITION_BASE")
if base == "" {
base = "https://mcp.nearle.ai.in/api"
}
nutrition := services.NewNutritionService(base)
url := fmt.Sprintf("%s?tenantid=%s&productid=%s&variantid=0", fiesta, tenant, product)
client := &http.Client{Timeout: 30 * time.Second}
response, err := client.Get(url)
if err != nil {
fmt.Println("fetching the live product:", err)
return
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
fmt.Println("reading the live product:", err)
return
}
var out envelope
if err := json.Unmarshal(body, &out); err != nil {
fmt.Println("parsing the live product:", err)
return
}
// The same call the endpoint makes, on the same rows.
for i := range out.Details {
found := nutrition.ForProduct(out.Details[i].Productbrand, out.Details[i].Imageid)
out.Details[i].Nutrition = found.Panel
out.Details[i].Healthscore = found.Health
}
encoded, _ := json.MarshalIndent(out, "", " ")
fmt.Println(string(encoded))
}