Canonical units
Supplies now use a canonical unit model: every supply's stock lives in one of three base units — pieces, litres, or kilograms. Everything else (packs, bottles, boxes, rolls, ml, g) converts to these three.

Why
Historical model let every supply define its own unit string — pcs, pieces, count, ea, each all meant the same thing but never merged. Bottles of shampoo at 300 ml and 500 ml both tracked as "1 bottle" — stock counts drifted per SKU because the base unit was different sizes.
:
- pieces — countable discrete items (capsules, rolls, tabs, packs of packaged items).
- litres — any liquid (converted from ml at parse time).
- kilograms — any bulk solid (converted from g at parse time).
Merge-by-product-type (see 4-level model) now works across SKUs because units align.
AI-assigned per product
When a new supply appears (from a receipt), the LLM picks the base unit based on the product type + typical form:
- Coffee capsules → pieces
- Shampoo → litres (regardless of the SKU's ml size)
- Detergent powder → kilograms
Operator can override in the 4-level model editor.
Checklist consumption units
The cleaner walkthrough's use_supplies action supports:
| Unit type | Example values |
|---|---|
| pieces / kg / litres | Whole-number or fractional counts (e.g. 2 capsules, 0.5 kg) |
| ml / l / g / kg (auto-converted) | Convenient input; converts to base unit on save |
| % of container | Percent of one CONTAINER (bottle / box), not % of stock |
Percent consumption — of a bottle, not of stock
- Earlier, when the operator set a cleaner-walkthrough item to consume
20%of Shampoo, the system consumed 20% of TOTAL stock. If stock was 2.5 litres, it deducted 0.5 litres per cleaning — way more than one bottle's worth. -
percent applies to one container (
containerSizefield on the supply). Shampoo container = 300 ml → 20% consumes 60 ml per cleaning. Operator's intuition matches the deduction.
Keep % as a selectable consumption-unit
Some cleaners naturally say "half a bottle of shampoo" instead of "150 ml". The % unit stays selectable on checklist items — the platform stores the intent as percent + resolves to the base-unit amount at consumption time.
Unit-aware merge
Two rows in the SAME property with the same NAME never merged historically.:
- Same-name + same-property + same-unit + same-container-size → merge on create.
- The receipt-parser dedup guard runs before the row is written.
Migration + re-analysis
- Walked every existing supply.
- Ran the LLM unit picker against each product type.
- Converted stock counts to the base unit.
- Merged same-property same-canonical duplicates.
Every row change was audit-logged. Operator can review under Admin → Audit Log filtered on supplies_audit_log.