Skip to content

Buy one pack less when the waste saving is under a threshold, with an under-buy warning - #41

Merged
guplem merged 2 commits into
mainfrom
29-buy-one-pack-less
Jul 25, 2026
Merged

Buy one pack less when the waste saving is under a threshold, with an under-buy warning#41
guplem merged 2 commits into
mainfrom
29-buy-one-pack-less

Conversation

@guplem

@guplem guplem commented Jul 22, 2026

Copy link
Copy Markdown
Owner

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.

  • Drops one pack only when doing so removes over-buy waste AND every affected recipe stays short by at most the threshold of its own need.
  • Threshold is evaluated per recipe (per cooking event), not on the ingredient total. A small total shortfall that lands entirely on one small recipe is rejected.
  • Threshold is a named constant underBuyMaxRecipeShortfallFraction = 0.20 (20%).
  • A reduced recommendation shows an amber "N unit short" chip with a tooltip: "Buying less than the recipes calculate...", and the on-screen buy count drops by one.
  • Without a valid reduction, behavior is unchanged. Never buys zero.

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 analyze passes
  • flutter test test/ passes (626 tests)
  • A large-surplus ingredient drops one pack and shows the warning
  • A small recipe that would go too short blocks the reduction

🤖 Generated with Claude Code

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>
@guplem guplem added the waiting-for-human-check No human has verified this yet -- direct AI output label Jul 22, 2026
@guplem guplem self-assigned this Jul 22, 2026
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 guplem left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@guplem
guplem merged commit a19febb into main Jul 25, 2026
1 check passed
@guplem
guplem deleted the 29-buy-one-pack-less branch July 25, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-for-human-check No human has verified this yet -- direct AI output

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Buy one pack less when the waste saving is under a threshold, with an under-buy warning

1 participant