Buy one pack less when the waste saving is under a threshold, with an under-buy warning - #41
Merged
Conversation
The waste optimizer always rounded pack counts up so the shopper bought enough. This could leave a mostly-empty extra pack as over-buy surplus (for example 3 packs of 400g for a 900g recipe: the 3rd pack is 75% wasted). Now, when buying one pack less removes the over-buy surplus AND leaves every affected cooking event short by no more than 20% of that recipe's own need, the optimizer recommends one pack less. The threshold is checked PER RECIPE (per cooking event), not on the ingredient total: the shortfall is allocated to the latest cooking events first, so a small total shortfall that lands entirely on one small recipe does not trigger the reduction. Without a reduction, behavior is unchanged. ProductRecommendation gains an `underBuy` flag and the `shortfall` amount. The shopping product row shows an amber "buying less than the recipes calculate" warning chip and drops the on-screen single-total buy line by one. The threshold is a named constant (underBuyMaxRecipeShortfallFraction, 20%). ADR 0010 records the exception to the round-up rule. Closes #29 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR #41 review found three problems with the "buy one pack less" feature. 1. The reduction and the "N short" chip were driven by the DESIRED-need analysis (rankProducts) but applied to the REMAINING-need buy count (after owned stock). When the user owned part of an ingredient the two diverged, so the "-1" and the chip were wrong. The row now applies the reduction and shows the chip only when the actual buy count matches the analysis (packsToBuy == recommendation.packsNeeded + 1). 2. The chip showed in the multi-trip split even though the per-trip lines render the full round-up, contradicting the "buying less" warning. The chip is now suppressed when the trip-split layout is active. 3. The reduced recommendation set overBuyWaste to 0, so its totalWaste became 0. Since ranking and the "best option" marker sort by totalWaste, an under-buyer could outrank a product that fully covers the need with small waste. The under-buy recommendation now keeps its full-pack-buy waste; only packsNeeded and shortfall reflect the reduction. Red-green TDD: added a ranking test (fully-covering beats under-buyer), a widget test for owned>0 divergence, and a widget test for the trip-split chip suppression. Updated ADR 0010 to document all three. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
guplem
commented
Jul 22, 2026
guplem
left a comment
Owner
Author
There was a problem hiding this comment.
Reviewed by an automated pass (deep review + a fix round). One correctness bug and two consistency issues were found and fixed on the branch; the change is now correct and ready for a human check.
What is done well
- The per-recipe threshold is genuinely per recipe, not on the total: the one-pack shortfall is allocated latest-event-first and rejected if any single recipe goes short by more than 20% of its own need (verified the day0=1000g / day5=40g case is correctly rejected at 3.8% of the total). Gates are all correct: skips when expiry waste exists, when there is no surplus, and never buys zero. Threshold is a named constant with a doc comment. Strong test coverage on real values.
Found and fixed on this branch
- (was Required) The reduction and the warning were computed on the desired need but applied to the owned-subtracted buy count, so when the user owned part of the ingredient the -1 pack and the N-short chip were wrong. Fixed: the reduction and chip now apply only when the actual buy count matches the desired-based analysis (
packsToBuy == packsNeeded + 1); otherwise the full count shows and no chip. A new owned>0 test covers it. - The under-buy chip appeared in multi-trip mode while the per-trip lines still summed to the full amount. Fixed: the chip is suppressed when the per-trip split layout is active.
- The reduced recommendation reported zero waste, which could rank an under-buyer as best option over a product that fully covers the need. Fixed: ranking and the best-option marker keep each product's full-buy waste; only the buy count and shortfall reflect the reduction. New test proves the fully-covering product stays best. Full suite green (629 tests). ADR 0010 updated.
Verdict: approved pending human review. CI analyze-and-test gate must be green before merge.
Note for the reviewer: this PR shares waste_optimizer.dart with #37, #42, so expect merge conflicts when landing more than one.
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #29
The optimizer always rounded up to fully cover the recipes, sometimes leaving a large surplus. Now it can drop one pack when that removes waste and the shortfall stays small, and it warns you clearly.
underBuyMaxRecipeShortfallFraction = 0.20(20%).Scope note
The reduction and warning apply to the recommendation and the on-screen single-total buy line. The clipboard/trip-split copy path is unchanged and still shows the full round-up count. Making the copy reflect the under-buy is a possible follow-up.
Test plan
flutter analyzepassesflutter test test/passes (626 tests)🤖 Generated with Claude Code