-
Notifications
You must be signed in to change notification settings - Fork 318
Description
Summary
UCP treats quantity as an integer everywhere. That works for discrete items like shirts and phones, but grocery, deli, bulk foods, and fabric all deal in fractional amounts. You can't represent "1.5 kg of apples" or "0.75 lb of deli turkey" today. This proposal adds a dev.ucp.shopping.weighted_items extension to handle measured quantities without touching existing core schemas.
Motivation
I was looking at how UCP would work for grocery and hit a wall. The schema rejects "quantity": 1.5 because it's "type": "integer". That constraint runs through line_item.json, order_line_item.json, fulfillment_event.json, and adjustment.json. The whole lifecycle assumes you're buying discrete, countable things.
There's a unit_price field on variant.json with measure/reference objects, but that's just for regulatory shelf labels (EU "price per 100ml" stuff). It's not wired into checkout or cart. No spec doc even mentions it.
Things you can't represent right now:
- 2.5 lbs chicken at $8.99/lb
- 0.75 kg cheddar at $12.00/kg
- 3.2 meters fabric at $15.00/m
- 1.5 liters olive oil at $10.00/L
Goals
- Allow line items to carry fractional, unit-based quantities (weight, volume, length) through cart, checkout, and order
- Support estimated-to-actual quantity adjustment at fulfillment time (customer orders 2 kg, store picks 2.2 kg, total updates)
- Maintain full backward compatibility so platforms that don't understand the extension still see valid line items
Non-Goals
- Changing the core
quantityfield from integer to number. That cascades through 6+ schemas and breaks existing implementations - Unit conversion logic (kg to lb, etc.). The business handles conversion based on buyer context
- Catch weight, random weight, or price-embedded barcode workflows. Those are follow-ups
- Inventory or stock management for weighted items
Detailed Design
Extension Schema
The extension adds three fields to line items: measured_quantity, price_measurement, and quantity_status.
A customer adding 2 kg of tomatoes at $3.00/kg to their cart:
{
"id": "li_1",
"item": { "id": "tomato-roma", "title": "Roma Tomatoes", "price": 600 },
"quantity": 1,
"measured_quantity": { "value": 2.0, "unit": "kg", "type": "weight" },
"price_measurement": { "unit": "kg", "reference_value": 1 },
"quantity_status": "estimated",
"totals": [{ "type": "subtotal", "amount": 600 }]
}Base quantity stays at 1 so platforms without the extension still see a valid line item with the pre-computed total. The real quantity lives in measured_quantity.
measured_quantity is what you're actually buying. Has a value (number), unit (string like "kg", "lb", "m"), and type (the dimension: weight, volume, length, area).
price_measurement tells you how the item is priced. Per kg, per 100ml, per meter. Has a unit and a reference_value (1 for "per kg", 100 for "per 100ml").
quantity_status is either "estimated" or "actual". Drives the weight adjustment flow described below.
Estimated to Actual Flow
This is the core grocery scenario. Customer orders 2 kg of tomatoes, store picks 2.2 kg, total needs to update.
At checkout, the line item has quantity_status: "estimated" with totals based on the requested weight (2 kg * $3.00/kg = $6.00).
At fulfillment, the store weighs the actual pick. The order line item updates:
{
"id": "li_1",
"item": { "id": "tomato-roma", "title": "Roma Tomatoes", "price": 660 },
"quantity": 1,
"measured_quantity": { "value": 2.2, "unit": "kg", "type": "weight" },
"quantity_status": "actual",
"totals": [{ "type": "subtotal", "amount": 660 }]
}Total goes from $6.00 to $6.60. The $0.60 difference gets recorded through the existing adjustment.json:
{
"id": "adj_1",
"type": "price_adjustment",
"status": "completed",
"occurred_at": "2026-03-09T14:30:00Z",
"line_items": [{ "id": "li_1", "quantity": 1 }],
"amount": 60,
"description": "Weight adjustment: 2.0 kg estimated → 2.2 kg actual"
}Works the other way too. Customer orders 2 kg, store picks 1.8 kg, total drops from $6.00 to $5.40, adjustment is a $0.60 credit back.
Units
Lowercase short codes following Google Merchant Center: kg, g, lb, oz, l, ml, m, cm, ft, yd. Businesses can use other values and platforms just display the string if they don't recognize it.
New Files
source/schemas/shopping/weighted_items.json(extension schema)source/schemas/shopping/types/measured_quantity.jsonsource/schemas/shopping/types/price_measurement.jsondocs/specification/weighted-items.md- Updates to
mkdocs.ymland.cspell/custom-words.txt
Risks and Mitigations
Backward Compatibility: The extension approach avoids this entirely. Base quantity stays integer, platforms without the extension see valid line items. No migration needed. This is the same pattern used by discounts and fees.
Floating Point and Money: Fractional quantities multiplied by prices can produce rounding issues. Mitigation: cap measured_quantity.value at 3 decimal places and document that businesses should round totals to whole minor units before returning them.
Unit Conversion: A product priced in kg but a buyer expecting lb creates confusion. Mitigation: the business handles conversion based on buyer context (locale, preferences). The protocol carries the unit as-is, no conversion logic in the spec.
Complexity: Three new type schemas and one extension schema. Comparable to the fee extension in scope. Businesses that don't sell weighted items never advertise the capability, so there's no burden on them.
Test Plan
Schema Validation:
- Existing integer-quantity line items validate unchanged
measured_quantity.valueaccepts decimals, rejects strings/negativesquantity_statusonly accepts "estimated" or "actual"price_measurement.reference_valuerejects zero (division by zero)
Integration:
- Measured quantity flows through cart, checkout, order without data loss
- Mixed carts with both discrete and measured items work correctly
- Platform without the extension sees
quantity: 1and pre-computed totals
End-to-End:
- Full estimated-to-actual flow: checkout with estimated weight, fulfillment with actual weight, adjustment created with correct difference
- Totals recompute correctly for weight-based line items
- Negative adjustment (actual < estimated) credits the buyer
Graduation Criteria
Working Draft → Candidate:
- Extension schema merged with Working Draft disclaimer
- Type schemas (
measured_quantity.json,price_measurement.json) merged - Schema linting passes
- Spec docs with examples covering weight, volume, and length
- TC majority vote to advance
Candidate → Stable:
- At least one grocery or marketplace implementation running the weighted-item flow end to end
- Estimated-to-actual adjustment flow validated in production
- Backward compatibility confirmed with platforms that don't advertise the capability
- Adoption feedback collected and addressed
- TC majority vote to advance
Implementation History
- 2026-02-27: Initial discussion opened (Support for weight-based and unit-of-measure quantities in line items #221 )
- 2026-03-11: Enhancement proposal submitted