···2020func main() {
2121 var searchTerm string
2222 var location string
2323+ var verbose bool
2324 flag.StringVar(&searchTerm, "ingredient", "", "Search term for ingredient lookup")
2425 flag.StringVar(&searchTerm, "i", "", "Search term for ingredient lookup")
2526 flag.StringVar(&location, "location", "", "Location for recipe sourcing (e.g., 70100023)")
2627 flag.StringVar(&location, "l", "", "Location for recipe sourcing (short form)")
2828+ flag.BoolVar(&verbose, "verbose", false, "dump all ingredients and grades")
2729 flag.Parse()
2830 ctx := context.Background()
3131+3232+ if searchTerm != "" {
3333+ verbose = true
3434+ }
29353036 cfg, err := config.Load()
3137 if err != nil {
···4854 }
49555056 catMap := make(map[string]int)
5151- if cfg.IngredientGrading.Enable {
5252- log.Printf("Grading %d ingredients", len(ings))
5353- cacheStore, err := cache.MakeCache()
5454- if err != nil {
5555- log.Fatalf("failed to create cache for ingredient grading: %s", err)
5757+5858+ log.Printf("Grading %d ingredients", len(ings))
5959+ cacheStore, err := cache.MakeCache()
6060+ if err != nil {
6161+ log.Fatalf("failed to create cache for ingredient grading: %s", err)
6262+ }
6363+ grader := ingredientgrading.NewManager(cfg, cacheStore)
6464+ graded, err := grader.GradeIngredients(ctx, ings)
6565+ if err != nil {
6666+ log.Fatalf("failed to grade ingredients: %s", err)
6767+ }
6868+ slices.SortFunc(graded, func(a, b ai.InputIngredient) int {
6969+ if a.Grade.Score != b.Grade.Score {
7070+ return b.Grade.Score - a.Grade.Score
5671 }
5757- grader := ingredientgrading.NewManager(cfg, cacheStore)
5858- graded, err := grader.GradeIngredients(ctx, ings)
5959- if err != nil {
6060- log.Fatalf("failed to grade ingredients: %s", err)
7272+ return strings.Compare(strings.ToLower(a.Description), strings.ToLower(b.Description))
7373+ })
7474+ for _, result := range graded {
7575+ for _, cat := range result.Categories {
7676+ catMap[cat] += 1
6177 }
6262- slices.SortFunc(graded, func(a, b ai.InputIngredient) int {
6363- if a.Grade.Score != b.Grade.Score {
6464- return b.Grade.Score - a.Grade.Score
6565- }
6666- return strings.Compare(strings.ToLower(a.Description), strings.ToLower(b.Description))
6767- })
6868- for _, result := range graded {
6969- for _, cat := range result.Categories {
7070- catMap[cat] += 1
7171- }
7272-7878+ if verbose {
7379 fmt.Printf("%2d/10: %s - %s: size: %s: %s\n", result.Grade.Score, result.Brand, result.Description, result.Size, result.Grade.Reason)
7480 }
7575- for cat, count := range catMap {
7676- fmt.Printf("Category: %s, Count: %d\n", cat, count)
7777- }
7878-7979- counts := lo.Reduce(graded, func(counts map[int]int, ingredient ai.InputIngredient, _ int) map[int]int {
8080- counts[ingredient.Grade.Score] += 1
8181- return counts
8282- }, make(map[int]int))
8383- fmt.Println("Grade distribution:")
8484- for score := 0; score <= 10; score++ {
8585- fmt.Printf("Score %2d: %d ingredients\n", score, counts[score])
8686- }
8787- return
8888- }
8989-9090- for _, i := range ings {
9191- for _, cat := range categories(i) {
9292- catMap[cat] += 1
9393- }
9494- fmt.Printf("%s: %s - %s:($%s) size: %s categories: %v\n", i.ProductID, i.Brand, i.Description, toFloat(i.PriceRegular), i.Size, i.Categories)
9581 }
9682 for cat, count := range catMap {
9783 fmt.Printf("Category: %s, Count: %d\n", cat, count)
9884 }
9999-}
10085101101-func toFloat(f *float32) string {
102102- if f == nil {
103103- return ""
8686+ counts := lo.Reduce(graded, func(counts map[int]int, ingredient ai.InputIngredient, _ int) map[int]int {
8787+ counts[ingredient.Grade.Score] += 1
8888+ return counts
8989+ }, make(map[int]int))
9090+ fmt.Println("Grade distribution:")
9191+ for score := range 10 {
9292+ fmt.Printf("Score %2d: %d ingredients\n", score, counts[score])
10493 }
105105- return fmt.Sprintf("%.2f", *f)
106106-}
107107-108108-func categories(i ai.InputIngredient) []string {
109109- if i.Categories == nil {
110110- return nil
111111- }
112112- return i.Categories
9494+ sumGrades := lo.SumBy(graded, func(ing ai.InputIngredient) int { return ing.Grade.Score })
9595+ fmt.Printf("Total count %d and score %d\n", len(graded), sumGrades)
11396}
+1-5
internal/kroger/products/client.gen.go
···340340 Retstrictions *ProductsRestrictionsModel `json:"retstrictions,omitempty"`
341341342342 // SnapEligible Indicates if the product is eligible for SNAP benefits.
343343- SnapEligible *bool `json:"snapEligible,omitempty"`
344344- SweeteningMethods *struct {
345345- Code *string `json:"code,omitempty"`
346346- Name *string `json:"name,omitempty"`
347347- } `json:"sweeteningMethods,omitempty"`
343343+ SnapEligible *bool `json:"snapEligible,omitempty"`
348344349345 // Temperature Information about the item's temperature requirements.
350346 Temperature *ProductsProductTemperatureModel `json:"temperature,omitempty"`
+3
internal/kroger/products/overlay.yaml
···66 - target: $.components.schemas["products.productModel"].properties.nutritionInformation
77 description: Drop field because Kroger returns both object and array shapes and staples does not use it.
88 remove: true
99+ - target: $.components.schemas["products.productModel"].properties.sweeteningMethods
1010+ description: Drop field because Kroger returns both object and array shapes and staples does not use it.
1111+ remove: true
912 - target: $.components.schemas["products.productItemPriceModel"].properties.expirationDate
1013 description: Drop field because Kroger returns date-time strings while the spec declares date-only values and staples does not use it.
1114 remove: true