Skip to content

fix(pantry): convert compatible units before subtracting from the shopping list - #104

Open
gillesdami wants to merge 1 commit into
cooklang:mainfrom
gillesdami:fix/pantry-unit-conversion
Open

fix(pantry): convert compatible units before subtracting from the shopping list#104
gillesdami wants to merge 1 commit into
cooklang:mainfrom
gillesdami:fix/pantry-unit-conversion

Conversation

@gillesdami

Copy link
Copy Markdown

The bug

An ingredient in the pantry is skipped entirely when its recipe quantity is
reported in a different unit from the one the pantry is written in, even when the
two units are the same kind of measure. The full amount stays on the shopping
list and only a tracing::warn! on stderr says why — the HTTP API and the web UI
show a list that is quietly too long.

This is not an edge case the author can avoid, because the unit in the file is
not the unit that reaches the pantry
: a quantity is fitted to its own magnitude
first.

recipe                 parsed as     pantry           result on the list
@semolina{1500%g}      1.5 kg        semolina 2000%g  1.5 kg   (nothing subtracted)
@semolina{0.25%kg}     250 g         semolina 2%kg    250 g    (nothing subtracted)

Writing the pantry in the larger unit does not fix it, because the fitting works
in both directions. So an ingredient used both above and below 1000 g/ml — a
couscous needing 1.5 kg of semolina and a tabbouleh needing 250 g of the same
semolina — has no pantry unit that works for both, and cannot be tracked at
all.

Reproduce

$ cat config/pantry.conf
[epicerie]
semoule = "2000%g"
lait    = "1000%ml"

$ cat Big.cook
Verser @semoule{1500%g} et @lait{1500%ml} dans un #saladier{}.
$ cat Small.cook
Verser @semoule{600%g} et @lait{200%ml}.

$ cook shopping-list Big.cook Small.cook
 WARN Unit mismatch for 'semoule': recipe needs 'kg', pantry has 'g'
 WARN Unit mismatch for 'lait': recipe needs 'l', pantry has 'ml'
[epicerie]
semoule 2.1 kg
lait    1.7 l

2.1 kg of semolina to buy with 2 kg already in the cupboard. Aggregation across
recipes is perfect (1500 g + 600 g = 2.1 kg); it is only the pantry that is
dropped. POST /api/shopping_list behaves the same way, without even the warning.

The fix

IngredientList::subtract_pantry compared req_unit == pantry_unit as strings.
It now converts the pantry quantity into the unit the recipe was parsed with,
using the Converter that is already in scope on the next line, and falls
through to the existing mismatch branch only when the conversion fails.

$ cook shopping-list Big.cook Small.cook   # patched
[epicerie]
semoule 0.1 kg
lait    0.7 l

Units that measure different things are still a mismatch. 500 g of oil
against oil = "1%l" in the pantry keeps the full 500 g and still warns, because
there is no density to convert with — the existing
test_subtract_pantry_unit_mismatch covers exactly that and passes unchanged.

Tests

Two added next to the existing ones in ingredient_list::pantry_tests:

  • test_subtract_pantry_converts_compatible_units — 1500 g needed against
    1%kg on the shelf leaves 0.5 kg, and 200 ml against 1%l leaves nothing.
  • test_subtract_pantry_when_the_pantry_holds_the_smaller_unit — the direction a
    household actually writes: grams on the shelf, a recipe big enough to be
    reported in kilograms.

cargo test --all-features passes except tests/serde.rs, which fails
identically on an unmodified checkout of main (Modifiers(REF) vs
Modifiers(0x0)) — pre-existing and unrelated. cargo fmt --check and
cargo clippy are clean for the touched file; the diffs they report in
bindings/ are pre-existing on main too.

Also verified against a real 33-recipe collection: the patched and unpatched
shopping lists are byte-identical there, since nothing in it crossed a unit
boundary. Only the broken cases change.

Note

src/pantry.rs:160 compares current_unit == threshold_unit the same way, so a
pantry with quantity = "2%kg" and low = "500%g" will not report as depleted.
Left alone to keep this change to one thing; happy to follow up.

Disclosure

This patch was written with AI assistance (Claude Code). It is not a speculative
cleanup: I hit this bug in my own kitchen collection through
CookCLI, on a couscous recipe needing
1.5 kg of semolina while 2 kg sat in the pantry. Every claim above is measured
against the shipped ghcr.io/cooklang/cookcli image and against a local build of
this branch, and I have read and understood every line of the change.

…pping list

A recipe quantity is fitted to its own magnitude before it reaches
`subtract_pantry`, so `@semolina{1500%g}` arrives as 1.5 kg while the pantry
still says `2000%g`. The two were compared as strings, so the pantry was skipped
and the full amount stayed on the shopping list.

Writing the pantry in the larger unit does not help, because the fitting works
both ways: `0.25%kg` arrives as 250 g and misses a pantry kept in kg. An
ingredient used both above and below 1000 g/ml could therefore not be tracked at
all — a couscous needing 1.5 kg of semolina and a tabbouleh needing 250 g have no
pantry unit in common.

Convert the pantry quantity into the unit the recipe was parsed with, using the
converter already in scope, and keep the warning for units that measure
different things: 500 g of oil against a litre on the shelf is still a mismatch,
since there is no density to convert with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown

commit: 459ccf5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant