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 ──
match semantic {
Some(ref sem) => {
// Semantic term present: CLIP-rank, then keep only hits that pass
// the structured filters (by content_hash).
let scored =
match score_photos(&state, &exif_dao, sem, &library_ids, threshold, None).await {
// When structured filters are present they ARE the constraint —
// CLIP only ranks within the candidate set. So drop the global
// similarity threshold (it's tuned for whole-library search and
// 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,
Err(e) => return score_error_response(e),
};