diff --git a/MoneyMap/Pages/Receipts.cshtml.cs b/MoneyMap/Pages/Receipts.cshtml.cs index 211f5dd..d0f377f 100644 --- a/MoneyMap/Pages/Receipts.cshtml.cs +++ b/MoneyMap/Pages/Receipts.cshtml.cs @@ -254,32 +254,30 @@ namespace MoneyMap.Pages var candidates = await query .ToListAsync(); - // If receipt has merchant, sort matches by relevance (but don't exclude) + // Sort by merchant/name relevance using word matching if (!string.IsNullOrWhiteSpace(receipt.Merchant)) { - var merchantLower = receipt.Merchant.ToLower(); + var receiptWords = receipt.Merchant.ToLower().Split(new[] { ' ', '-', '_', '.' }, StringSplitOptions.RemoveEmptyEntries); - // 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; + // Exact match + if (merchantName == receipt.Merchant.ToLower() || transactionName == receipt.Merchant.ToLower()) + return 1000; - // Contains match on merchant - if (merchantName.Contains(merchantLower)) - return 2; + // Count matching words + var merchantWords = merchantName.Split(new[] { ' ', '-', '_', '.' }, StringSplitOptions.RemoveEmptyEntries); + var transactionWords = transactionName.Split(new[] { ' ', '-', '_', '.' }, StringSplitOptions.RemoveEmptyEntries); - // Contains match on transaction name - if (transactionName.Contains(merchantLower)) - return 1; + var merchantMatches = receiptWords.Count(rw => merchantWords.Any(mw => mw.Contains(rw) || rw.Contains(mw))); + var transactionMatches = receiptWords.Count(rw => transactionWords.Any(tw => tw.Contains(rw) || rw.Contains(tw))); - // No match - return 0; + // Return the higher match count + return Math.Max(merchantMatches * 10, transactionMatches * 10); }) .ThenByDescending(t => t.Date) .ThenByDescending(t => t.Id) diff --git a/MoneyMap/Services/ReceiptAutoMapper.cs b/MoneyMap/Services/ReceiptAutoMapper.cs index 883ea44..1fbbb82 100644 --- a/MoneyMap/Services/ReceiptAutoMapper.cs +++ b/MoneyMap/Services/ReceiptAutoMapper.cs @@ -118,18 +118,38 @@ namespace MoneyMap.Services return new List(); } - // Filter by merchant if available - if (!string.IsNullOrWhiteSpace(receipt.Merchant)) - { - // Try to find matching merchant name - query = query.Where(t => - (t.Merchant != null && t.Merchant.Name.Contains(receipt.Merchant)) || - t.Name.Contains(receipt.Merchant)); - } - // Get candidates var candidates = await query.ToListAsync(); + // Sort by merchant/name relevance using word matching if merchant available + if (!string.IsNullOrWhiteSpace(receipt.Merchant)) + { + var receiptWords = receipt.Merchant.ToLower().Split(new[] { ' ', '-', '_', '.' }, StringSplitOptions.RemoveEmptyEntries); + + candidates = candidates + .OrderByDescending(t => + { + var merchantName = t.Merchant?.Name?.ToLower() ?? ""; + var transactionName = t.Name?.ToLower() ?? ""; + + // Exact match + if (merchantName == receipt.Merchant.ToLower() || transactionName == receipt.Merchant.ToLower()) + return 1000; + + // Count matching words + var merchantWords = merchantName.Split(new[] { ' ', '-', '_', '.' }, StringSplitOptions.RemoveEmptyEntries); + var transactionWords = transactionName.Split(new[] { ' ', '-', '_', '.' }, StringSplitOptions.RemoveEmptyEntries); + + var merchantMatches = receiptWords.Count(rw => merchantWords.Any(mw => mw.Contains(rw) || rw.Contains(mw))); + var transactionMatches = receiptWords.Count(rw => transactionWords.Any(tw => tw.Contains(rw) || rw.Contains(tw))); + + // Return the higher match count + return Math.Max(merchantMatches * 10, transactionMatches * 10); + }) + .ThenByDescending(t => t.Date) + .ToList(); + } + // If we have a total amount, filter by amount match (±10% tolerance) if (receipt.Total.HasValue) {