Unified search: rank within filtered set instead of pre-thresholding CLIP

When structured filters are present they're the constraint and CLIP only ranks
within the candidate set, so drop the global similarity threshold for that
case. Previously the 0.2 whole-library threshold ran BEFORE intersecting with
the filters, discarding filter-matching photos that scored just under it (e.g.
a 2022 beach photo at 0.18) — producing after_struct_filter=0 even when matches
existed. Plain semantic (no filters) keeps the user's threshold.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cameron Cordes
2026-06-14 02:20:06 -04:00
parent 6c315edacc
commit 6e5898e766
+18 -4
View File
@@ -364,10 +364,24 @@ pub async fn unified_search<TagD: TagDao>(
// ── 4. Rank ── // ── 4. Rank ──
match semantic { match semantic {
Some(ref sem) => { Some(ref sem) => {
// Semantic term present: CLIP-rank, then keep only hits that pass // When structured filters are present they ARE the constraint —
// the structured filters (by content_hash). // CLIP only ranks within the candidate set. So drop the global
let scored = // similarity threshold (it's tuned for whole-library search and
match score_photos(&state, &exif_dao, sem, &library_ids, threshold, None).await { // would pre-discard filter-matching photos that scored just under
// it — e.g. a 2022 beach photo at 0.18 — before the intersection
// ever runs). With no filters, keep the user's threshold for the
// plain semantic case.
let clip_threshold = if has_struct { -1.0 } else { threshold };
let scored = match score_photos(
&state,
&exif_dao,
sem,
&library_ids,
clip_threshold,
None,
)
.await
{
Ok(s) => s, Ok(s) => s,
Err(e) => return score_error_response(e), Err(e) => return score_error_response(e),
}; };