Feature/unified nl search #106

Open
cameron wants to merge 26 commits from feature/unified-nl-search into master
Showing only changes of commit e56235acc5 - Show all commits
+33
View File
@@ -231,6 +231,22 @@ pub async fn unified_search<TagD: TagDao>(
let has_struct = let has_struct =
has_exif_filter || gps.is_some() || !sq.tag_ids.is_empty() || sq.media_type.is_some(); has_exif_filter || gps.is_some() || !sq.tag_ids.is_empty() || sq.media_type.is_some();
// Stage trace: what the model extracted + whether a structured filter is
// active. The chips show this to the user too, but logging it makes the
// "why no results" path debuggable from the server side.
log::info!(
"unified_search: q={nl:?} semantic={:?} tag_ids={:?} exclude={:?} place={:?} gps={:?} date=({:?},{:?}) media={:?} unmatched={:?} has_struct={has_struct}",
sq.semantic,
sq.tag_ids,
sq.exclude_tag_ids,
resolved_place.as_ref().map(|p| p.display_name.as_str()),
gps,
sq.date_from,
sq.date_to,
sq.media_type,
sq.unmatched_tags,
);
// ── 3. Build the structured candidate set (EXIF rows passing every // ── 3. Build the structured candidate set (EXIF rows passing every
// filter). Skipped entirely for a pure-semantic query. ── // filter). Skipped entirely for a pure-semantic query. ──
let mut candidate: Vec<crate::database::models::ImageExif> = Vec::new(); let mut candidate: Vec<crate::database::models::ImageExif> = Vec::new();
@@ -255,6 +271,11 @@ pub async fn unified_search<TagD: TagDao>(
} }
} }
}; };
log::info!(
"unified_search: tag_ids={:?} -> tag_set_files={:?}",
sq.tag_ids,
tag_set.as_ref().map(|s| s.len())
);
// EXIF query handles camera/lens/gps-box/date. With no EXIF filters // EXIF query handles camera/lens/gps-box/date. With no EXIF filters
// it returns the whole table, which we then narrow by the predicates // it returns the whole table, which we then narrow by the predicates
@@ -322,6 +343,11 @@ pub async fn unified_search<TagD: TagDao>(
.iter() .iter()
.filter_map(|r| r.content_hash.clone()) .filter_map(|r| r.content_hash.clone())
.collect(); .collect();
log::info!(
"unified_search: candidate_rows={} allowed_hashes={}",
candidate.len(),
allowed_hashes.len()
);
} }
// ── 4. Rank ── // ── 4. Rank ──
@@ -334,6 +360,8 @@ pub async fn unified_search<TagD: TagDao>(
Ok(s) => s, Ok(s) => s,
Err(e) => return score_error_response(e), Err(e) => return score_error_response(e),
}; };
let considered = scored.considered;
let clip_hits = scored.hits.len();
let hits: Vec<(f32, String)> = if has_struct { let hits: Vec<(f32, String)> = if has_struct {
scored scored
.hits .hits
@@ -343,6 +371,10 @@ pub async fn unified_search<TagD: TagDao>(
} else { } else {
scored.hits scored.hits
}; };
log::info!(
"unified_search: clip considered={considered} hits={clip_hits} after_struct_filter={}",
hits.len()
);
let total_matching = hits.len(); let total_matching = hits.len();
let page = paginate(&hits, offset, limit); let page = paginate(&hits, offset, limit);
let results = resolve_hits(&exif_dao, &page); let results = resolve_hits(&exif_dao, &page);
@@ -364,6 +396,7 @@ pub async fn unified_search<TagD: TagDao>(
} }
candidate.sort_by(|a, b| b.date_taken.cmp(&a.date_taken)); candidate.sort_by(|a, b| b.date_taken.cmp(&a.date_taken));
let total_matching = candidate.len(); let total_matching = candidate.len();
log::info!("unified_search: filters-only matches={total_matching}");
let end = (offset + limit).min(total_matching); let end = (offset + limit).min(total_matching);
let results: Vec<SearchHit> = if offset >= total_matching { let results: Vec<SearchHit> = if offset >= total_matching {
Vec::new() Vec::new()