From e9bb951d979c98b6bc76b87add81deec5ec3a221 Mon Sep 17 00:00:00 2001 From: Froidefond Julien Date: Wed, 18 Mar 2026 21:44:31 +0100 Subject: [PATCH] feat: auto-match metadata when 100% confidence and matching book count When multiple provider results exist but the best has 100% confidence, compare local book count with external total_volumes. If they match, treat it as an auto-match and link+sync series and book metadata automatically instead of requiring manual review. Co-Authored-By: Claude Opus 4.6 --- apps/api/src/metadata_batch.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/apps/api/src/metadata_batch.rs b/apps/api/src/metadata_batch.rs index e1d16bb..8a3b04e 100644 --- a/apps/api/src/metadata_batch.rs +++ b/apps/api/src/metadata_batch.rs @@ -632,7 +632,7 @@ enum SearchOutcome { async fn search_and_evaluate( pool: &PgPool, - _library_id: Uuid, + library_id: Uuid, series_name: &str, provider_name: &str, ) -> SearchOutcome { @@ -660,7 +660,31 @@ async fn search_and_evaluate( // Check if best candidate has perfect confidence let best = candidates.into_iter().next().unwrap(); if (best.confidence - 1.0).abs() < f32::EPSILON { - // Multiple results but best is 100% — still too many results + // Multiple results but best is 100% — check if book count matches to auto-match + if let Some(ext_total) = best.total_volumes { + let local_count: Option = sqlx::query_scalar( + r#" + SELECT COUNT(*) FROM books + WHERE library_id = $1 + AND COALESCE(NULLIF(series, ''), 'unclassified') = $2 + "#, + ) + .bind(library_id) + .bind(series_name) + .fetch_one(pool) + .await + .ok(); + + if let Some(count) = local_count { + if count == ext_total as i64 { + info!( + "[METADATA_BATCH] Auto-match by book count: series='{}' confidence=100% local_books={} external_volumes={}", + series_name, count, ext_total + ); + return SearchOutcome::AutoMatch(best); + } + } + } return SearchOutcome::TooManyResults(1, Some(best)); // count the 100% one }