Functional Weave
Code in Rust

hospitality.recipe-scale@1.0.1

README.md

1,760 bytes · view raw

# hospitality.recipe-scale

Scales a recipe from the number of portions it makes to the number wanted, and
writes each quantity the way a kitchen would weigh it.

## The rules

The scaling itself is exact: the quantity is decimal text, multiplied by
`toPortions / fromPortions` as a fraction. Then each quantity is rounded
**once**, half-up:

| unit | rounded to | written in |
|---|---|---|
| mg, g, kg | whole grams from 10 g, 0.1 g from 1 to 10 g, 0.01 g below 1 g | g, or kg from 1000 g |
| mL, L | the same steps in millilitres | mL, or L from 1000 mL |
| each | always **up** to a whole number: half an egg short is short | each |
| anything else (oz, lb, cup_us, tbsp, sprig...) | 2 decimal places | unchanged |

So 500 g for 4 is 1.25 kg for 10, 1.2 kg for 4 is 600 g for 2, and 666.4 g
scaled by 1.5 is 999.6 g, which rounds to 1000 g and is written `1` kg.
Metric amounts are converted with `units.convert`; the kilogram and litre
forms keep up to 3 decimal places, which is whole grams and millilitres.

Imperial and cup measures stay in their unit rather than being converted to
metric, because a cook reading a cup recipe expects cups back. Units the
registry does not know (tbsp, sprig, pinch) are scaled as written.

## Limits and errors

- Portions are whole numbers from 1 to 10000.
- A quantity is non-negative decimal text (`"0.5"`, not `"1/2"`), at most 15
  significant digits and 15 decimal places. Trailing zeros are ignored.
- The unit must not be empty.

## Not covered

Scaling is linear. Real kitchens don't always scale linearly: seasoning,
leavening, and cooking times and pan sizes often need adjusting by hand when a
recipe is multiplied many times over.

1.0.1 fixes Python accepting a trailing newline in quantity; adds tests.