Fix merchant filtering in receipt mapping to be non-exclusive

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 <noreply@anthropic.com>
This commit is contained in:
AJ
2025-10-12 14:11:40 -04:00
parent 123367c7bf
commit 3f97b34c54

View File

@@ -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 =>
{