mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-10-10 13:46:28 +00:00
Merge pull request #5933 from meilisearch/fix-ranking-score-with-sort
Fix ranking score bug when sort is present
This commit is contained in:
@@ -2128,3 +2128,102 @@ async fn simple_search_changing_unrelated_settings() {
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn ranking_score_bug_with_sort() {
|
||||||
|
let server = Server::new_shared();
|
||||||
|
let index = server.unique_index();
|
||||||
|
|
||||||
|
// Create documents with a "created" field for sorting
|
||||||
|
let documents = json!([
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"title": "Coffee Mug",
|
||||||
|
"created": "2023-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"title": "Water Bottle",
|
||||||
|
"created": "2023-01-02T00:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"title": "Tumbler Cup",
|
||||||
|
"created": "2023-01-03T00:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"title": "Stainless Steel Tumbler",
|
||||||
|
"created": "2023-01-04T00:00:00Z"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Add documents
|
||||||
|
let (task, code) = index.add_documents(documents, None).await;
|
||||||
|
assert_eq!(code, 202, "{task}");
|
||||||
|
server.wait_task(task.uid()).await.succeeded();
|
||||||
|
|
||||||
|
// Configure sortable attributes
|
||||||
|
let (task, code) = index
|
||||||
|
.update_settings(json!({
|
||||||
|
"sortableAttributes": ["created"]
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
assert_eq!(code, 202, "{task}");
|
||||||
|
server.wait_task(task.uid()).await.succeeded();
|
||||||
|
|
||||||
|
// Test 1: Search without sort - should have proper ranking scores
|
||||||
|
index
|
||||||
|
.search(
|
||||||
|
json!({
|
||||||
|
"q": "tumbler",
|
||||||
|
"showRankingScore": true,
|
||||||
|
"rankingScoreThreshold": 0.0,
|
||||||
|
"attributesToRetrieve": ["title"]
|
||||||
|
}),
|
||||||
|
|response, code| {
|
||||||
|
assert_eq!(code, 200, "{response}");
|
||||||
|
snapshot!(json_string!(response["hits"]), @r###"
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "Tumbler Cup",
|
||||||
|
"_rankingScore": 0.9848484848484848
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Stainless Steel Tumbler",
|
||||||
|
"_rankingScore": 0.8787878787878788
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"###);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Test 2: Search with sort - this is where the bug occurs
|
||||||
|
index
|
||||||
|
.search(
|
||||||
|
json!({
|
||||||
|
"q": "tumbler",
|
||||||
|
"showRankingScore": true,
|
||||||
|
"rankingScoreThreshold": 0.0,
|
||||||
|
"sort": ["created:desc"],
|
||||||
|
"attributesToRetrieve": ["title"]
|
||||||
|
}),
|
||||||
|
|response, code| {
|
||||||
|
assert_eq!(code, 200, "{response}");
|
||||||
|
snapshot!(json_string!(response["hits"]), @r###"
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "Tumbler Cup",
|
||||||
|
"_rankingScore": 0.9848484848484848
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Stainless Steel Tumbler",
|
||||||
|
"_rankingScore": 0.8787878787878788
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"###);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
@@ -66,15 +66,29 @@ impl ScoreDetails {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate the global score of the details.
|
||||||
|
///
|
||||||
|
/// It is computed from the ranks of the ranking rules, excluding the sort/geo sort rules.
|
||||||
|
/// If the details contain a semantic score (ScoreDetails::Vector), it is used instead of the ranking score.
|
||||||
|
///
|
||||||
|
/// note: this function expects a maximum of one semantic score, otherwise only the last one will be used.
|
||||||
pub fn global_score<'a>(details: impl Iterator<Item = &'a Self> + 'a) -> f64 {
|
pub fn global_score<'a>(details: impl Iterator<Item = &'a Self> + 'a) -> f64 {
|
||||||
Self::score_values(details)
|
// Filter out only the ranking scores (Rank values) and exclude sort/geo sort
|
||||||
.find_map(|x| {
|
let mut semantic_score = None;
|
||||||
let ScoreValue::Score(score) = x else {
|
let ranking_ranks = details.filter_map(|detail| match detail.rank_or_value() {
|
||||||
return None;
|
RankOrValue::Rank(rank) => Some(rank),
|
||||||
};
|
RankOrValue::Score(score) => {
|
||||||
Some(score)
|
semantic_score = Some(score);
|
||||||
})
|
None
|
||||||
.unwrap_or(1.0f64)
|
}
|
||||||
|
RankOrValue::Sort(_) => None,
|
||||||
|
RankOrValue::GeoSort(_) => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let ranking_score = Rank::global_score(ranking_ranks);
|
||||||
|
|
||||||
|
// If we have semantic score, use it, otherwise use ranking score
|
||||||
|
semantic_score.unwrap_or(ranking_score)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn score_values<'a>(
|
pub fn score_values<'a>(
|
||||||
|
Reference in New Issue
Block a user