picker order by2

This commit is contained in:
Priec
2026-07-16 14:49:26 +02:00
parent f13db931a4
commit c0b9cfda9f

View File

@@ -25,6 +25,7 @@ const DEFAULT_RESULT_LIMIT: usize = 60;
const HARD_RESULT_LIMIT: usize = 200;
const DEFAULT_LIST_LIMIT: usize = 5;
const SEARCH_SCORE_RELATIVE_FLOOR: f32 = 0.25;
const SEARCH_SCORE_GROUP_WIDTH: f32 = 0.25;
pub struct SearcherService {
pub pool: PgPool,
@@ -624,6 +625,33 @@ fn order_clause(column: &ResolvedOrderColumn, direction: SearchOrderDirection) -
}
}
fn ranked_order_clause(
column: &ResolvedOrderColumn,
direction: SearchOrderDirection,
) -> String {
let direction = order_direction_sql(direction);
match column {
ResolvedOrderColumn::Position => {
format!("candidate.candidate_group DESC, positioned.picker_position {}", direction)
}
ResolvedOrderColumn::Column(column) => {
let quoted_column = format!("\"{}\"", column.replace('"', "\"\""));
format!(
"candidate.candidate_group DESC, positioned.{} {} NULLS LAST, positioned.id ASC",
quoted_column, direction
)
}
}
}
fn relevance_group(score: f32, best_score: f32) -> i32 {
if best_score <= 0.0 || !best_score.is_finite() || !score.is_finite() {
return 0;
}
((score.max(0.0) / best_score / SEARCH_SCORE_GROUP_WIDTH).floor() as i32).clamp(0, 3)
}
async fn fetch_ordered_rows(
pool: &PgPool,
profile_name: &str,
@@ -847,10 +875,15 @@ async fn fetch_ordered_candidate_rows(
SELECT t.*, ROW_NUMBER() OVER (ORDER BY id ASC) AS picker_position \
FROM {} t WHERE deleted = FALSE\
) \
SELECT id, to_jsonb(positioned) - 'picker_position' AS data, picker_position \
FROM positioned WHERE id = ANY($1) ORDER BY {} LIMIT $2 OFFSET $3",
SELECT positioned.id, to_jsonb(positioned) - 'picker_position' AS data, \
picker_position, candidate_score \
FROM positioned \
JOIN UNNEST($1::BIGINT[], $2::REAL[], $3::INTEGER[]) \
AS candidate(candidate_id, candidate_score, candidate_group) \
ON candidate_id = positioned.id \
ORDER BY {} LIMIT $4 OFFSET $5",
qualify_profile_table(profile_name, table_name),
order_clause(&resolved_order, order.direction),
ranked_order_clause(&resolved_order, order.direction),
);
let ids = candidates
.iter()
@@ -858,10 +891,21 @@ async fn fetch_ordered_candidate_rows(
.collect::<Vec<_>>();
let scores = candidates
.iter()
.map(|(score, id, _)| (*id, *score))
.collect::<HashMap<_, _>>();
.map(|(score, _, _)| *score)
.collect::<Vec<_>>();
let best_score = scores
.iter()
.copied()
.max_by(f32::total_cmp)
.unwrap_or_default();
let groups = scores
.iter()
.map(|score| relevance_group(*score, best_score))
.collect::<Vec<_>>();
let rows = sqlx::query(AssertSqlSafe(sql))
.bind(&ids)
.bind(&scores)
.bind(&groups)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(pool)
@@ -874,11 +918,12 @@ async fn fetch_ordered_candidate_rows(
let id: i64 = row.try_get("id").unwrap_or_default();
let json_data: serde_json::Value = row.try_get("data").unwrap_or_default();
let position: i64 = row.try_get("picker_position").unwrap_or_default();
let score: f32 = row.try_get("candidate_score").unwrap_or_default();
let json_data = remap_json_to_display_names(json_data, &physical_to_display);
let display_value = row_display_value(&json_data, &display_column);
Hit {
id,
score: scores.get(&id).copied().unwrap_or_default(),
score,
content_json: json_data.to_string(),
table_name: table_name.to_string(),
row_display_value: display_value,
@@ -955,4 +1000,26 @@ mod tests {
"\"1\" ASC NULLS LAST, id ASC"
);
}
#[test]
fn ranked_order_preserves_relevance_before_column_order() {
assert_eq!(
ranked_order_clause(
&ResolvedOrderColumn::Column("created_at".to_string()),
SearchOrderDirection::Desc,
),
"candidate.candidate_group DESC, positioned.\"created_at\" DESC NULLS LAST, positioned.id ASC"
);
}
#[test]
fn relevance_groups_nearby_high_scores_and_separates_low_scores() {
let best_score = 8.7;
assert_eq!(relevance_group(8.7, best_score), 3);
assert_eq!(relevance_group(8.6, best_score), 3);
assert_eq!(relevance_group(8.4, best_score), 3);
assert_eq!(relevance_group(7.0, best_score), 3);
assert_eq!(relevance_group(3.0, best_score), 1);
}
}