Feature/unified nl search #106
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user