···2121 defaultIngredientGradeModel = openai.ChatModelGPT5Mini
2222)
23232424+// should we have category spefic grading prompts?
2425const ingredientGradeSystemInstruction = `
2526You review grocery catalog items before they are shown to a home recipe generator.
2627···3738- formats intended mainly for snacking or immediate eating rather than cooking
3839- pre-cut fruit unless it is still broadly useful for cooking or baking
39404141+Additional rules for pasta, grains, rice, legumes, and noodles:
4242+4343+- Prefer flexible base carbohydrates:
4444+ rice, dry pasta, oats, quinoa, farro, freekah
4545+4646+- Use simple score anchors:
4747+ standard dry pasta → 6–7
4848+ premium dry pasta → 8–9
4949+ alternative pasta (chickpea, lentil, gluten-free) → 5–6
5050+ bread → 5–6
5151+ prepared sauces → max 6
5252+ instant or flavored mixes → 3–5
5353+5454+- Reward real cooking-performance signals:
5555+ bronze-cut, slow-dried, high-protein durum, whole grain, hulled, pearled
5656+5757+- Reward known higher-quality brands (e.g., Felicetti, De Cecco, Rummo, Rustichella)
5858+5959+- Do not infer quality from generic terms:
6060+ "quality", "non-GMO", "organic", "traditional"
6161+6262+- Penalize items that are less flexible or more processed
40634164Scoring anchors:
4265- 9-10: excellent raw/fresh flexible cooking ingredient, e.g. whole vegetables, greens, roots, raw meats, fresh fruit useful in baking/cooking
+10-5
internal/albertsons/query/client.go
···1212 "time"
1313)
14141515+// this is a strange set. Actual sub categories don't work but thes aisle-vs ones do.
1616+// for en example broke sub category here is beef https://www.safeway.com/shop/aisles/meat-seafood/beef.html?sort=&page=1&loc=1142
1517const (
1616- Category_Vegatables = "GR-C-categ-8c62c848"
1717- Category_Fruit = "GR-C-categ-a8eea474"
1818- Category_Seafood = "GR-C-Categ-6090cd27"
1919- Category_Meat = "GR-MeatF-fffc8662"
2020- Category_Wine = "GR-S-Searc-db592d50"
1818+ Category_Vegatables = "GR-C-categ-8c62c848"
1919+ Category_Fruit = "GR-C-categ-a8eea474"
2020+ Category_Seafood = "GR-C-Categ-6090cd27" // https://www.safeway.com/aisle-vs/meat-seafood/seafood-favorites.html
2121+ Category_Meat = "GR-MeatF-fffc8662" // https://www.safeway.com/aisle-vs/meat-seafood/meat-favorites.html
2222+ Category_Wine = "GR-S-Searc-db592d50"
2323+ Category_Pasta_Grains = "GR-C-Categ-77b9d5dd" // https://www.safeway.com/aisle-vs/grains-pasta-sides/best-sellers.html
2424+ Category_Dairy = "GR-C-Categ-f210e5cd" // new and trending seems dubious https://www.safeway.com/aisle-vs/dairy-eggs-cheese/new-trending.html
2125)
22262327func StapleCategories() []string {
···2630 Category_Fruit,
2731 Category_Seafood,
2832 Category_Meat,
3333+ Category_Pasta_Grains,
2934 }
3035}
3136
+9-1
internal/kroger/staples.go
···143143 //Taxonomy: product.,
144144 // CountryOrigin: product.CountryOrigin,
145145 // Favorite: item.Favorite,
146146- // InventoryStockLevel: item.InventoryStockLevel),
147146 }
148147 }
149148···185184 Term: "lamb",
186185 Brands: []string{"Simple Truth"},
187186 },
187187+ {
188188+ Term: "grains",
189189+ Brands: []string{"*"},
190190+ },
191191+ {
192192+ Term: "pasta",
193193+ Brands: []string{"*"}, // Should we just put our thumb on the scale
194194+ },
195195+ // TODO dairy, international
188196 }...)
189197}
190198
+6-2
internal/recipes/generator.go
···104104 slog.InfoContext(ctx, "Regenerating recipes for location", "location", p.String(), "response_id", p.ResponseID)
105105 instructions := regenerateInstructions(p)
106106107107+ // TODO give them some sort of status.
107108 shoppingList, err := g.aiClient.Regenerate(ctx, instructions, p.ResponseID)
108109 if err != nil {
109110 return nil, fmt.Errorf("failed to regenerate recipes with AI: %w", err)
···129130 if err != nil {
130131 return nil, fmt.Errorf("failed to get staples: %w", err)
131132 }
132132- g.writeStatus(ctx, hash, fmt.Sprintf("Looking through %d ingredients", len(ingredients)))
133133+ ogCount := len(ingredients)
133134 ingredients = lo.Filter(ingredients, func(ing ai.InputIngredient, _ int) bool {
134135 // TODO make configurable?
135135- return ing.Grade == nil || ing.Grade.Score > 5
136136+ return ing.Grade == nil || ing.Grade.Score > 6
136137 })
138138+ // having category would be interesing here.
139139+ g.writeStatus(ctx, hash, fmt.Sprintf("Considering %d out of %d ingredients", len(ingredients), ogCount))
140140+137141 mutable.Shuffle(ingredients)
138142139143 instructions := []string{p.Directive, p.Instructions}
+2-2
internal/recipes/generator_hash_test.go
···2323 }
24242525 // make sure we're intentional about breaking hash
2626- if h1 != "JjKXkKjKKpE" {
2626+ if h1 != "wrxx3dmHzBA" {
2727 t.Fatalf("expected hash to be stable and equal to JjKXkKjKKpE, got %s", h1)
2828 }
2929···3131 if !ok {
3232 t.Fatal("expected current hash passhed to legacy")
3333 }
3434- if legacyHash != "cmVjaXBlJjKXkKjKKpE=" {
3434+ if legacyHash != "cmVjaXBlwrxx3dmHzBA=" {
3535 t.Fatalf("expected legacy hash to be base64 of recipe hash with prefix, got %s", legacyHash)
3636 }
3737