Skip to content

Commit 5d9c1f2

Browse files
Merge pull request #3461 from meilisearch/exp-843-new-relevancy-guides
New relevancy section for Guides and 2 guides about ranking rules and ranking score details
2 parents 576c691 + a5eba29 commit 5d9c1f2

5 files changed

Lines changed: 517 additions & 0 deletions

File tree

docs.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,13 @@
434434
"guides/multitenancy_nodejs"
435435
]
436436
},
437+
{
438+
"group": "Relevancy",
439+
"pages": [
440+
"guides/relevancy/ordering_ranking_rules",
441+
"guides/relevancy/interpreting_ranking_scores"
442+
]
443+
},
437444
{
438445
"group": "Deployment",
439446
"pages": [
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
---
2+
title: Interpreting ranking score details
3+
description: Learn how to understand ranking score details to see how Meilisearch evaluates each result and which rules determined their order.
4+
---
5+
6+
# How do I interpret ranking score details?
7+
8+
[In the previous guide](/guides/relevancy/ordering_ranking_rules), we covered how ranking rules determine result order and how changing their sequence affects what your users see first. But when you're actually making those tweaks, how do you know if they're working the way you expect?
9+
10+
That's where ranking score details come in. They give you a behind-the-scenes view of every ranking decision Meilisearch made for each result — with specific numeric scores for each relevancy rule, in the order they were evaluated.
11+
12+
You'll be able to see things like: did Proximity decide this result's position, or was it Typo? Did Sort even get a chance to act, or did an earlier rule already settle things? And since Sort doesn't measure relevance (it shows a `value` rather than a `score`), the details also make it clear exactly where Sort slotted into the evaluation path and whether it actually influenced the final order.
13+
14+
**Firstly, how do I see ranking score details?**
15+
16+
When you search you can pass in an option to view the details of scoring and sorting using `“showRankingScoreDetails”: true` and it will return an indepth look at the ranking rules that you are working with
17+
18+
```markdown
19+
curl \
20+
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
21+
-H 'Content-Type: application/json' \
22+
--data-binary '{
23+
"q": "dragon",
24+
"showRankingScoreDetails": true
25+
}'
26+
```
27+
28+
Ranking Score details example
29+
30+
```markdown
31+
{
32+
"hits": [
33+
{
34+
"id": 31072,
35+
"title": "Dragon",
36+
"overview": "In a desperate attempt to save her kingdom…",
37+
38+
"_rankingScoreDetails": {
39+
"words": {
40+
"order": 0,
41+
"matchingWords": 4,
42+
"maxMatchingWords": 4,
43+
"score": 1.0
44+
},
45+
"typo": {
46+
"order": 2,
47+
"typoCount": 1,
48+
"maxTypoCount": 4,
49+
"score": 0.75
50+
},
51+
"name:asc": {
52+
"order": 1,
53+
"value": "Dragon"
54+
}
55+
}
56+
},
57+
58+
],
59+
60+
}
61+
```
62+
63+
# Ranking rules: same data, different results. How `sort` placement changes outcomes
64+
65+
## The setup
66+
67+
You run a **recipe search app**. You have two recipes in your index:
68+
69+
```json
70+
[
71+
{
72+
"id": 1,
73+
"title": "Easy Chicken Curry",
74+
"description": "A quick and simple chicken curry ready in 20 minutes",
75+
"prep_time_minutes": 20
76+
},
77+
{
78+
"id": 2,
79+
"title": "Chicken Stew with Curry Spices and Vegetables",
80+
"description": "A hearty stew with warming spices",
81+
"prep_time_minutes": 15
82+
}
83+
]
84+
```
85+
86+
A user searches for `"chicken curry"` and sorts by `prep_time_minutes:asc` (quickest first).
87+
88+
Both documents match both search words. But **Doc 1** is clearly the stronger text match as `"chicken"` and `"curry"` appear right next to each other in the title. **Doc 2** has both words in the title too, but they're separated by several other words.
89+
90+
Let's see how moving Sort **one position** in your ranking rules changes which result comes first, and how to read the ranking score details to understand why.
91+
92+
---
93+
94+
## Scenario A: `sort` placed AFTER Group 1 rules (recommended)
95+
96+
We’ve set up our ranking rules to have sort after our Group 1 wide net rules.
97+
98+
```json
99+
["words", "typo", "proximity", "sort", "attribute", "exactness"]
100+
```
101+
102+
With this set up Meilisearch evaluates the text relevance rules first, *then* uses Sort.
103+
104+
### 🥇 Result #1 — Easy Chicken Curry
105+
106+
```json
107+
{
108+
"prep_time_minutes": 20,
109+
"title": "Easy Chicken Curry",
110+
"id": 1,
111+
"description": "A quick and simple chicken curry ready in 20 minutes",
112+
"_rankingScore": 0.9982363315696648,
113+
"_rankingScoreDetails": {
114+
"words": {
115+
"order": 0,
116+
"matchingWords": 2,
117+
"maxMatchingWords": 2,
118+
"score": 1.0
119+
},
120+
"typo": { "order": 1, "typoCount": 0, "maxTypoCount": 2, "score": 1.0 },
121+
"proximity": { "order": 2, "score": 1.0 },
122+
"prep_time_minutes:asc": { "order": 3, "value": 20.0 },
123+
"attribute": {
124+
"order": 4,
125+
"attributeRankingOrderScore": 1.0,
126+
"queryWordDistanceScore": 0.9047619047619048,
127+
"score": 0.9682539682539683
128+
},
129+
"exactness": {
130+
"order": 5,
131+
"matchType": "noExactMatch",
132+
"matchingWords": 2,
133+
"maxMatchingWords": 2,
134+
"score": 0.3333333333333333
135+
}
136+
}
137+
}
138+
```
139+
140+
### 🥈 Result #2 — Chicken Stew with Curry Spices and Vegetables
141+
142+
```json
143+
{
144+
"prep_time_minutes": 15,
145+
"title": "Chicken Stew with Curry Spices and Vegetables",
146+
"id": 2,
147+
"description": "A hearty stew with warming spices",
148+
"_rankingScore": 0.9149029982363316,
149+
"_rankingScoreDetails": {
150+
"words": {
151+
"order": 0,
152+
"matchingWords": 2,
153+
"maxMatchingWords": 2,
154+
"score": 1.0
155+
},
156+
"typo": { "order": 1, "typoCount": 0, "maxTypoCount": 2, "score": 1.0 },
157+
"proximity": { "order": 2, "score": 0.5 },
158+
"prep_time_minutes:asc": { "order": 3, "value": 15.0 },
159+
"attribute": {
160+
"order": 4,
161+
"attributeRankingOrderScore": 1.0,
162+
"queryWordDistanceScore": 0.9047619047619048,
163+
"score": 0.9682539682539683
164+
},
165+
"exactness": {
166+
"order": 5,
167+
"matchType": "noExactMatch",
168+
"matchingWords": 2,
169+
"maxMatchingWords": 2,
170+
"score": 0.3333333333333333
171+
}
172+
}
173+
174+
```
175+
176+
### What decided this? Reading the score details
177+
178+
Walk through the rules in `order` (0, 1, 2…) and look for where the scores diverge:
179+
180+
| Step | Rule | Doc 1 | Doc 2 | Outcome |
181+
| --- | --- | --- | --- | --- |
182+
| 0 | **Words** | 2/2 → `1.0` | 2/2 → `1.0` | 🤝 Tie |
183+
| 1 | **Typo** | 0 typos → `1.0` | 0 typos → `1.0` | 🤝 Tie |
184+
| 2 | **Proximity** | `1.0` | `0.5` | ✅ **Doc 1 wins here** |
185+
186+
Proximity broke the tie. `"chicken"` and `"curry"` sit right next to each other in Doc 1's title (score `1.0`), but are separated by three words in Doc 2's title (score `0.5`).
187+
188+
Sort (order 3) never got a chance to act because Proximity already decided the winner. **Even though Doc 2 has a faster prep time (15 min vs 20 min), it ranks second because text relevance was evaluated first.**
189+
190+
Also notice: Sort shows a `value` instead of a `score`. That's because Sort doesn't measure relevance, it just orders by the field value. This is why Sort doesn't contribute to `_rankingScore`.
191+
192+
---
193+
194+
## Scenario B: `sort` placed BEFORE Group 1 rules
195+
196+
Now let's move `sort` to the top of our ranking rules:
197+
198+
```json
199+
["sort", "words", "typo", "proximity", "attribute", "exactness"]
200+
```
201+
202+
### 🥇 Result #1 — Chicken Stew with Curry Spices and Vegetables
203+
204+
```json
205+
{
206+
"prep_time_minutes": 15,
207+
"title": "Chicken Stew with Curry Spices and Vegetables",
208+
"id": 2,
209+
"description": "A hearty stew with warming spices",
210+
"_rankingScore": 0.9149029982363316,
211+
"_rankingScoreDetails": {
212+
"prep_time_minutes:asc": { "order": 0, "value": 15.0 },
213+
"words": {
214+
"order": 1,
215+
"matchingWords": 2,
216+
"maxMatchingWords": 2,
217+
"score": 1.0
218+
},
219+
"typo": { "order": 2, "typoCount": 0, "maxTypoCount": 2, "score": 1.0 },
220+
"proximity": { "order": 3, "score": 0.5 },
221+
"attribute": {
222+
"order": 4,
223+
"attributeRankingOrderScore": 1.0,
224+
"queryWordDistanceScore": 0.9047619047619048,
225+
"score": 0.9682539682539683
226+
},
227+
"exactness": {
228+
"order": 5,
229+
"matchType": "noExactMatch",
230+
"matchingWords": 2,
231+
"maxMatchingWords": 2,
232+
"score": 0.3333333333333333
233+
}
234+
}
235+
}
236+
```
237+
238+
### 🥈 Result #2 — Easy Chicken Curry
239+
240+
```json
241+
{
242+
"prep_time_minutes": 20,
243+
"title": "Easy Chicken Curry",
244+
"id": 1,
245+
"description": "A quick and simple chicken curry ready in 20 minutes",
246+
"_rankingScore": 0.9982363315696648,
247+
"_rankingScoreDetails": {
248+
"prep_time_minutes:asc": { "order": 0, "value": 20.0 },
249+
"words": {
250+
"order": 1,
251+
"matchingWords": 2,
252+
"maxMatchingWords": 2,
253+
"score": 1.0
254+
},
255+
"typo": { "order": 2, "typoCount": 0, "maxTypoCount": 2, "score": 1.0 },
256+
"proximity": { "order": 3, "score": 1.0 },
257+
"attribute": {
258+
"order": 4,
259+
"attributeRankingOrderScore": 1.0,
260+
"queryWordDistanceScore": 0.9047619047619048,
261+
"score": 0.9682539682539683
262+
},
263+
"exactness": {
264+
"order": 5,
265+
"matchType": "noExactMatch",
266+
"matchingWords": 2,
267+
"maxMatchingWords": 2,
268+
"score": 0.3333333333333333
269+
}
270+
}
271+
}
272+
```
273+
274+
### Reading the score details - what changed?
275+
276+
Look at the `order` values. Sort is now `order: 0` so it runs first.
277+
278+
| Step | Rule | Doc 1 (Easy Chicken Curry) | Doc 2 (Chicken Stew…) | Outcome |
279+
| --- | --- | --- | --- | --- |
280+
| 0 | **Sort** (`prep_time_minutes:asc`) | value: `20` | value: `15` | ✅ **Doc 2 wins here** |
281+
282+
Sort immediately separated the documents: 15 min beats 20 min. `:asc` will sort lowest to highest. Words, Typo, Proximity, and the rest never got a say.
283+
284+
Notice something important: **Doc 1 still has a higher `_rankingScore` (0.998 vs 0.914)** but it ranks second. This is exactly what we described in [Ordering ranking rules](/guides/relevancy/ordering_ranking_rules): ranking score only measures text relevance. Sort affects the final order but doesn't change the ranking score. If you only looked at `_rankingScore`, you'd think Doc 1 should be first. The score details tell you the real story.
285+
286+
---
287+
288+
## Side by side
289+
In both scenarios the user searches for `"chicken curry"` and sorts by `prep_time_minutes:asc` (quickest first). The only change is the ranking rule placement.
290+
291+
| | Scenario A (Sort is placed after Group 1 ranking rules) | Scenario B (Sort is placed first) |
292+
| --- | --- | --- |
293+
| **#1 result** | Easy Chicken Curry (20 min) | Chicken Stew with Curry… (15 min) |
294+
| **Decided by** | Proximity (order 2) | Sort (order 0) |
295+
| **Doc 1 `_rankingScore`** | 0.998 | 0.998 (same — sort doesn't affect it) |
296+
| **Doc 2 `_rankingScore`** | 0.914 | 0.914(same — sort doesn't affect it) |
297+
| **Best for** | Users who want the most relevant recipe | Users who want the quickest recipe regardless of match quality |
298+
299+
---
300+
301+
## The takeaway
302+
303+
Moving Sort **one position** flipped the results. The ranking score details let you see exactly why:
304+
305+
- **Look at the `order` values** to understand the sequence rules were applied
306+
- **Find where scores first diverge** — that's the rule that decided the final order
307+
- **Remember that Sort shows a `value`, not a `score`** It doesn't contribute to `_rankingScore`, which is why a higher-scored document can rank lower when Sort takes priority
308+
309+
Start with Sort after Group 1 rules (Scenario A) and adjust from there based on what your users expect.

0 commit comments

Comments
 (0)