Patterns
Turn a phrase of the query, such as "bis 220 cm", into a filter, so shoppers find what fits.
A shopper who types "Sofa bis 220 cm" (sofa up to 220 cm) wants a sofa that fits the wall. A pattern reads such a phrase and turns it into a filter:
curl -s 'http://127.0.0.1:7800/v1/search?query=Sofa%20bis%20220%20cm&debug=1' \
| jq -c '(.trace.steps[] | select(.step == "resolve") | .detections[] | select(.pattern) | {words, as, pattern}),
[.sections[] | select(.type == "product") | .hits[].entity]'{"words":"bis 220 cm","as":{"width":{"lte":220}},"pattern":"width-at-most"}
["product:SOFA-ASKA-2","product:SOFA-LUND-3","product:SOFA-NIKO-SCHLAF"]The home store's pattern width-at-most read "bis 220 cm" as a width up to 220 cm. Three sofas fit; the Vidar corner sofa, 268 cm wide, does not.
Write a pattern
A pattern is an entry of patterns.json, a release file that no panel screen edits. Here is width-at-most, trimmed:
{
"patterns": [
{
"id": "width-at-most",
"phrases": {
"de": ["bis {width} cm", "unter {width} cm", "{width} cm breit"],
"en": ["up to {width} cm", "under {width} cm"]
},
"captures": "at_most"
}
]
}{width}captures a number for the attributewidth, in its unit. A placeholder can also nameprice,delivery_daysorrating, or an option attribute, whose values it reads by code, label or alias.phraseslists the phrases per locale, or in one list for every language. They match whatever the case.capturessays how the number filters.
How the number filters
captures makes the number a value or a range. The home store has a pattern of each kind:
captures | Pattern | Reads | As |
|---|---|---|---|
exact, the default | seats-number | "3-sitzer" (3-seater) | {"seats": 3} |
at_most | width-at-most | "bis 220 cm" | {"width": {"lte": 220}} |
at_least | width-at-least | "ab 200 cm" (from 200 cm) | {"width": {"gte": 200}} |
{"within": 5} | round-diameter | "durchmesser 120" (diameter 120) | {"diameter": {"gte": 115, "lte": 125}} |
Reference: captures
Add fixed values
filter adds what a phrase means without saying it. round-diameter reads a diameter, and its "filter": { "shape": "round" } adds the shape:
curl -s 'http://127.0.0.1:7800/v1/search?query=Tisch%20Durchmesser%20120' \
| jq -c '.resolution.filters, [.sections[] | select(.type == "product") | .hits[].entity]'{"diameter":{"lte":125,"gte":115},"shape":"round"}
["product:TISCH-RUNA"]"Tisch" (table) names the tables, and the phrase adds a diameter of 115 to 125 cm and the shape round: the Runa dining table. A pattern without a placeholder is a filter alone, such as on-sale, which reads "im Angebot" (on sale) as on_sale: true.
Reference: filter
Built-in patterns
Two patterns are built in. quantity reads a number with a unit and filters the one attribute that measures it, and price reads an amount in the channel's currency. Here they read "Leuchte 3000 Kelvin" (lamp, 3000 kelvin) and "Sofa unter 1000 €" (sofa under 1000 €):
for q in 'Leuchte 3000 Kelvin' 'Sofa unter 1000 €'; do
curl -s -G http://127.0.0.1:7800/v1/search --data-urlencode "query=$q" -d debug=1 \
| jq -c '.trace.steps[] | select(.step == "resolve") | .detections[] | select(.pattern) | {words, as, pattern}'
done{"words":"3000 Kelvin","as":{"color_temperature":{"lte":3150,"gte":2850}},"pattern":"built-in:quantity"}
{"words":"unter 1000 €","as":{"price":{"lte":1000}},"pattern":"built-in:price"}A number without a bound means about that much, 5 % either way. Bounds such as "bis" and "under" are read in German and English, whatever the shop's language.
When phrases overlap, the longer one wins, and on the same words the release's own pattern beats a built-in one. Words that are exactly a value's label or alias stay that value: the home store lists "rund 120" as an alias of a size, so the size wins over round-diameter. "built_in": { "quantity": false } switches a built-in pattern off.
Reference: built_in
A length that could mean several things
A shelf has a width, a depth and a height, so the built-in pattern cannot tell what "80 cm" means. It keeps the phrase as text and says why in the resolve step's kept:
curl -s 'http://127.0.0.1:7800/v1/search?query=Regal%2080%20cm&debug=1' \
| jq -rc '(.trace.steps[] | select(.step == "resolve") | .kept[].reason),
[.sections[] | select(.type == "product") | .total]'"80 cm" could be width, depth, height, seat_height or diameter; only a pattern of the merchant's own can tell which.
[0]- resolve“Regal” → category wohnen/regale
- relaxNothing matched, so it searches again without category wohnen/regale
OrbSearch 0.56 msMeilisearch 4.87 ms
"Regal" (shelf) names the shelves, but no shelf holds the words "80 cm", so the search finds nothing, even without the category. Only a pattern of your own can say what a length means. In the home store a length in cm is the width, so start a draft and add a pattern that says so:
draft=$(curl -s -X POST -H "Authorization: Bearer $key" -H 'Content-Type: application/x-ndjson' \
--data-binary @tests/fixtures/home/catalog.jsonl "$api/imports?name=catalog.jsonl" | jq -r .import.id)
version=$(curl -s -H "Authorization: Bearer $key" $api/imports/$draft | jq -r .version)
jq --arg version "$version" '{version: $version, files: {"patterns.json":
(.patterns += [{id: "width-about", phrases: {de: ["{width} cm"]}, captures: {within: 5}}])}}' \
tests/fixtures/home/patterns.json \
| curl -s -X PATCH -H "Authorization: Bearer $key" -H 'Content-Type: application/json' -d @- $api/imports/$draft \
| jq -c '.files | keys'["patterns.json"]Open the search page with the draft's files in place of the live ones:
body=$(curl -s -H "Authorization: Bearer $key" $api/imports/$draft | jq '{url: "/suche?q=Regal+80+cm", files}')
curl -s -H "Authorization: Bearer $key" -H 'Content-Type: application/json' -d "$body" $api/preview/page \
| jq -c '.results.resolution.filters, [.results.sections[] | select(.type == "product") | .hits[].entity]'{"width":{"lte":85,"gte":75}}
["product:REGAL-STEG"]"80 cm" is now a width of 75 to 85 cm, and the search finds the Steg bookshelf.
Reference: PATCH /api/v1/imports/{import} · POST /api/v1/preview/page · kept
How patterns go live
Only the Query API reads patterns.json, so a draft that changes only patterns goes live at once, without a rebuild (Releases). The draft says so before it is published; this one is discarded instead:
curl -s -H "Authorization: Bearer $key" $api/imports/$draft/publication | jq -r .course.sentence
curl -s -X DELETE -H "Authorization: Bearer $key" $api/imports/$draft | jq -r .stateGoes live at once: only patterns.json changed what the Query API reads.
discardedPOST $api/imports/$draft/publish would make it live.
Reference: GET .../publication
Next
- Synonyms: words that mean the same, such as "couch" and "sofa".
- How search understands a query: everything a query can name.
- Releases: draft, preview, publish and roll back.