feat: Introduce Location Search + Lookup capabilities #545
Conversation
| "description": "Geographic coordinates and geofence for the location.", | ||
| "ucp_request": "omit" | ||
| }, | ||
| "hours": { |
There was a problem hiding this comment.
Two related flags where the same input can be read two ways by different implementations. First, daily_hour/exception_hour say "when is_closed/is_24_hours is true, intervals MUST be omitted," but nothing enforces it, so a contradictory record (is_closed: true + is_24_hours: true + intervals: [...]) validates and two businesses can disagree on whether the store is open. Second, the "overnight = two entries across two days" guidance is prose-only: time_interval leaves close < open undefined (22:00-06:00 validates with no defined meaning), and 24:00 is rejected by the HH:MM pattern, so a shift ending at midnight has to be written 00:00, which is itself ambiguous.
Rather than reach for if/then schema enforcement, should we make every record deterministic with one small schema change plus two normative rules?
- Allow
24:00intime_intervalso an end-of-day/midnight close is expressible:- "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$" + "pattern": "^(([01][0-9]|2[0-3]):[0-5][0-9]|24:00)$" - Add a precedence rule (in the Location hours section): for a given day or date, if
is_closedis true the location is closed andis_24_hours/intervalsMUST be ignored; otherwise ifis_24_hoursis true it is open all day andintervalsMUST be ignored; otherwise it is open duringintervals(absent or empty means closed). - Add a cross-midnight rule: within one interval
closeMUST be later thanopen; a span that crosses midnight MUST be split into twodaily_hourentries (the first ending24:00, the next day starting00:00).
With 24:00 allowed, the midnight close becomes unambiguous, close > open always holds, and 22:00-06:00 is invalid (must be split) rather than undefined. Would that work?
There was a problem hiding this comment.
The proposed rules makes sense and do help to disambiguate & enforce stricter semantics on the schema based on the prose. The only nit is for rule 1, I only adjusted the pattern to time_interval.close to allow for the value 24:00 - given close > open, I don't see any reasons to allow a time interval to start with 24:00.
Added the recommendations accordingly.
| "properties": { | ||
| "center": { | ||
| "$ref": "geo.json", | ||
| "description": "The center coordinates. If omitted, the user's course location hints in the request context MUST be used to derive the center." |
There was a problem hiding this comment.
The description says that when center is omitted, "the user's coarse location hints in the request context MUST be used to derive the center." But center is a geo (required latitude/longitude), while context composes locality.json, which only carries address_country, address_region, and postal_code, with no coordinates anywhere. As written, the one normative geo input a search can have is not expressible from the request alone, so the MUST cannot be met without an unstated geocoding step. The first REST example sidesteps this by sending explicit center coordinates, so the fallback path is never exercised.
Could we either (a) relax this to SHOULD and state that the business geocodes the coarse locality hint into a centroid, or (b) let center also accept a locality so the postal/region centroid is a first-class, schema-expressible input? Whichever we choose, could the spec name the exact field the center is derived from, so two implementations geocode the same thing?
There was a problem hiding this comment.
I want to pressure test this comment a bit more - I think there is value to enforce a MUST semantics here, because when the values is omitted, then there needs to be another data source to which the distance can be measured against - so loosening it up to a SHOULD semantics will introduce an undefined behaviour for implementors.
I also feel it's not ideal to introduce locality into center since for distance measurement, in my mind, it's always going to be based off a set point. So curious if there is an option c) we can explore here: we mention specifically that the expectation, when using the user address hints in context as fallback, is that business must convert it using forward geocoding strategies?
| "type": "boolean", | ||
| "description": "Only return locations that are currently open." | ||
| }, | ||
| "open_at": { |
There was a problem hiding this comment.
open_at describes two incompatible time meanings; could we pick one?
open_at is format: date-time with the description "the specified date and time (RFC 3339) in the location's local time." Those two halves disagree. An RFC 3339 date-time carries an offset and denotes one absolute instant on the global timeline; "in the location's local time" describes a wall-clock value (2 PM at the store), which is a different quantity and is not even a valid RFC 3339 date-time (no offset). So an implementer cannot tell which is meant.
It changes results for any multi-timezone search. If a platform sends 2026-06-29T14:00:00-04:00, read as an absolute instant the business converts it to each location's local time and tests New York at 14:00 but Los Angeles at 11:00; read as "2 PM local everywhere," a single instant cannot express that at all (each timezone is a different instant). Same request, different locations returned.
Could we commit to one reading in the description? The cleaner option, matching format: date-time, is absolute-instant: "An RFC 3339 instant. The business converts it to the location's local time using location.timezone and evaluates it against hours/exception_hours (the HH:MM intervals)." If the intent is actually a local wall-clock time, then could we drop format: date-time and model it as a date plus HH:MM evaluated in each location's own timezone? Either works; the ask is just to state which, since open_now (a plain boolean evaluated "now" per location) is unambiguous but open_at currently is not.
There was a problem hiding this comment.
Good catch on this disagreement, I favour the option where platform still pass in a RFC 3339 instant and let the business converts it into the local timezone of the location and do the comparison as it will also support the use case where platform may not know about the location's timezone ahead of the request.
Will update this description here accordingly.
| "max_distance": { | ||
| "type": "number", | ||
| "minimum": 0, | ||
| "description": "Maximum distance in RFC 7035 distance unit (meters)." |
There was a problem hiding this comment.
Is this the right RFC to link?
There was a problem hiding this comment.
I would like to propose yes, https://datatracker.ietf.org/doc/html/rfc7035 is the RFC titled Relative Location Representation and this field is semantically equivalent to the concept of relative offset.
| "type": "object", | ||
| "required": ["id"], | ||
| "properties": { | ||
| "id": { |
There was a problem hiding this comment.
Undeclared cross-capability coupling.
inventory_filter.id is "product or variant ID in shopping, or dish ID in food ordering," and type well-known values are product/dish. That ties this common capability to Catalog/menu ID namespaces. There is no $ref from common/ into shopping/, so this creates a documentation gap. Could we add a short Dependencies note (IDs originate from Catalog/menu; type routes backend resolution) so integrators know how to obtain valid IDs?
There was a problem hiding this comment.
That's a good call out. Related to this comment, I'm almost leaning towards removing type as a field in this filter here as a start due to:
- Like what's being called out in the comment, as soon as we start introducing
well-knownvalues intotype, it introduces a new connection betweencommoncapability and vertical service names. This might be a decision made too pre-maturely since it opens up the precedence for the dependency to go beyond schema constructs. - This field originally was introduced with the intention to support businesses that operate in multiple verticals. However, I now feel like even in those cases, the products/items/offerings they have should be unique enough for them to easily understand the lookup via
id(the additional backend explicit routing mechanism may not be needed, and even if we do end up introducing it later, it would be a straightforward non-breaking change).
I'm open to hearing more thoughts and opinions from folks, but as a start I will be removing type from inventory_filter and only leverage the unique id while also adding a dependency note in the schema description.
…ing/ to generally represent any physical location (to be referenced by Location capability).
|
Closing this PR as #589 will supersede it moving forward. |
Description
Defines standard interfaces for discovering, searching, and retrieving physical locations (e.g., retail stores, restaurants, warehouses, lodging properties).
It introduces two new capabilities under the
dev.ucp.commonnamespace (consistent with Shopping's Catalog capability design):dev.ucp.common.location.search): Discovery-focused endpoint for natural language query, geographic, and offerings-based filters.dev.ucp.common.location.lookup): Resolution-focused endpoints supporting single & batch lookups.Some key commerce flows it will be able to unlock:
nearby that support customer pickup and checking their operating hours & inventory availability
before selection.
or local service provider) has delivery coverage for a buyer's address.
Category (Required)
ucp-schematool (resolver, linter, validator). (Requires Maintainer approval)Related Issues
This is related to RFC #375's section 10.
Checklist
!for breaking changes).Screenshots / Logs (if applicable)