From 3f97b34c547a6611dfc16b041d8b37644ae8e26f Mon Sep 17 00:00:00 2001 From: AJ Date: Sun, 12 Oct 2025 14:11:40 -0400 Subject: [PATCH] Fix merchant filtering in receipt mapping to be non-exclusive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed merchant filtering from exclusionary to relevance-based sorting. This fixes the issue where transactions don't appear if the merchant name doesn't exactly match. Changes: - Date range (±3 days) remains the primary hard filter - Merchant name now sorts results by relevance instead of excluding - Retrieves 100 candidates, sorts by match quality, returns top 50 - Case-insensitive matching using ToLower() Relevance Scoring: - Score 3: Exact match on merchant or transaction name - Score 2: Partial match on merchant name (Contains) - Score 1: Partial match on transaction name (Contains) - Score 0: No match (still included if within date range) Results sorted by: Match score → Date → ID Benefits: - McDonald's receipt will show all transactions in date range - Best matches appear at top - No transactions excluded due to name variations - More forgiving for typos, abbreviations, etc. - User can still see and select any transaction in date window Example: Receipt says "McDonald's" but transaction says "MCDONALD'S #1234" - Before: Excluded (no exact Contains match) - After: Included, sorted to top (partial match score 2) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- MoneyMap/Pages/Receipts.cshtml.cs | 50 ++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/MoneyMap/Pages/Receipts.cshtml.cs b/MoneyMap/Pages/Receipts.cshtml.cs index 1d57308..89f0ee3 100644 --- a/MoneyMap/Pages/Receipts.cshtml.cs +++ b/MoneyMap/Pages/Receipts.cshtml.cs @@ -232,7 +232,7 @@ namespace MoneyMap.Pages .Where(t => !transactionsWithReceipts.Contains(t.Id)) .AsQueryable(); - // If receipt has a date, filter by +/- 3 days + // If receipt has a date, filter by +/- 3 days (this is the primary filter) if (receipt.ReceiptDate.HasValue) { var minDate = receipt.ReceiptDate.Value.AddDays(-3); @@ -240,20 +240,50 @@ namespace MoneyMap.Pages query = query.Where(t => t.Date >= minDate && t.Date <= maxDate); } - // If receipt has merchant, filter by merchant name - if (!string.IsNullOrWhiteSpace(receipt.Merchant)) - { - query = query.Where(t => - (t.Merchant != null && t.Merchant.Name.Contains(receipt.Merchant)) || - t.Name.Contains(receipt.Merchant)); - } - + // Get all candidates within date range (don't filter by merchant in query) var candidates = await query .OrderByDescending(t => t.Date) .ThenByDescending(t => t.Id) - .Take(50) // Limit to 50 matches + .Take(100) // Get more candidates for sorting .ToListAsync(); + // If receipt has merchant, sort matches by relevance (but don't exclude) + if (!string.IsNullOrWhiteSpace(receipt.Merchant)) + { + var merchantLower = receipt.Merchant.ToLower(); + + // Sort: exact matches first, then partial matches, then others + candidates = candidates + .OrderByDescending(t => + { + var merchantName = t.Merchant?.Name?.ToLower() ?? ""; + var transactionName = t.Name?.ToLower() ?? ""; + + // Exact match on merchant or transaction name + if (merchantName == merchantLower || transactionName == merchantLower) + return 3; + + // Contains match on merchant + if (merchantName.Contains(merchantLower)) + return 2; + + // Contains match on transaction name + if (transactionName.Contains(merchantLower)) + return 1; + + // No match + return 0; + }) + .ThenByDescending(t => t.Date) + .Take(50) + .ToList(); + } + else + { + // No merchant filter, just take top 50 by date + candidates = candidates.Take(50).ToList(); + } + // Calculate match scores and mark close amount matches var options = candidates.Select(t => {