Skip to content

Commit 8131017

Browse files
clucraftclaude
andcommitted
Fix anchor price selection - prioritize anchor over method
The previous logic checked preferred method first, which could select a wrong price even when anchor price was available. Now: 1. PRIORITY 1: Anchor price - if user confirmed a price, find closest match (within 10% tolerance) across ALL candidates 2. PRIORITY 2: Preferred method - only used if no anchor match found 3. PRIORITY 3: Consensus voting Also added debug logging to trace anchor price saving and retrieval. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 389915a commit 8131017

File tree

3 files changed

+31
-29
lines changed

3 files changed

+31
-29
lines changed

backend/src/routes/products.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ router.post('/', async (req: AuthRequest, res: Response) => {
5959

6060
// Store the anchor price - used on refresh to select the correct variant
6161
await productQueries.updateAnchorPrice(product.id, selectedPrice);
62+
console.log(`[Products] Saved anchor price ${selectedPrice} for product ${product.id} (method: ${selectedMethod})`);
6263

6364
// Record the user-selected price
6465
await priceHistoryQueries.create(

backend/src/services/scheduler.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ async function checkPrices(): Promise<void> {
2929
// Get anchor price for variant products (the price the user confirmed)
3030
const anchorPrice = await productQueries.getAnchorPrice(product.id);
3131

32+
console.log(`[Scheduler] Product ${product.id} - preferredMethod: ${preferredMethod}, anchorPrice: ${anchorPrice}`);
33+
3234
// Use voting scraper with preferred method and anchor price if available
3335
const scrapedData = await scrapeProductWithVoting(
3436
product.url,
@@ -37,6 +39,8 @@ async function checkPrices(): Promise<void> {
3739
anchorPrice || undefined
3840
);
3941

42+
console.log(`[Scheduler] Product ${product.id} - scraped price: ${scrapedData.price?.price}, candidates: ${scrapedData.priceCandidates.map(c => `${c.price}(${c.method})`).join(', ')}`);
43+
4044
// Check for back-in-stock notification
4145
const wasOutOfStock = product.stock_status === 'out_of_stock';
4246
const nowInStock = scrapedData.stockStatus === 'in_stock';

backend/src/services/scraper.ts

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,47 +1429,44 @@ export async function scrapeProductWithVoting(
14291429
// Store all candidates
14301430
result.priceCandidates = allCandidates;
14311431

1432-
// If user has a preferred method, try to use it
1433-
if (preferredMethod && allCandidates.length > 0) {
1434-
const preferredCandidates = allCandidates.filter(c => c.method === preferredMethod);
1435-
if (preferredCandidates.length > 0) {
1436-
let selectedCandidate = preferredCandidates[0];
1437-
1438-
// If we have an anchor price and multiple candidates from preferred method,
1439-
// select the one closest to the anchor price (handles variant products)
1440-
if (anchorPrice && preferredCandidates.length > 1) {
1441-
selectedCandidate = preferredCandidates.reduce((closest, candidate) => {
1442-
const closestDiff = Math.abs(closest.price - anchorPrice);
1443-
const candidateDiff = Math.abs(candidate.price - anchorPrice);
1444-
return candidateDiff < closestDiff ? candidate : closest;
1445-
});
1446-
console.log(`[Voting] Using anchor price ${anchorPrice} to select from ${preferredCandidates.length} candidates: ${selectedCandidate.price}`);
1447-
}
1448-
1449-
console.log(`[Voting] Using preferred method ${preferredMethod}: ${selectedCandidate.price}`);
1450-
result.price = { price: selectedCandidate.price, currency: selectedCandidate.currency };
1451-
result.selectedMethod = preferredMethod;
1452-
return result;
1453-
}
1454-
}
1455-
1456-
// If we have an anchor price but no preferred method, find closest candidate overall
1432+
// PRIORITY 1: If we have an anchor price, it takes precedence (user confirmed this price)
1433+
// This handles variant products where multiple prices exist on the page
14571434
if (anchorPrice && allCandidates.length > 0) {
1435+
console.log(`[Voting] Have anchor price ${anchorPrice}, searching ${allCandidates.length} candidates...`);
1436+
1437+
// Find the candidate closest to the anchor price
14581438
const closestCandidate = allCandidates.reduce((closest, candidate) => {
14591439
const closestDiff = Math.abs(closest.price - anchorPrice);
14601440
const candidateDiff = Math.abs(candidate.price - anchorPrice);
14611441
return candidateDiff < closestDiff ? candidate : closest;
14621442
});
14631443

1464-
// Only use anchor matching if the difference is reasonable (within 20%)
14651444
const priceDiff = Math.abs(closestCandidate.price - anchorPrice) / anchorPrice;
1466-
if (priceDiff < 0.20) {
1467-
console.log(`[Voting] Using anchor price ${anchorPrice} to select: ${closestCandidate.price} (${(priceDiff * 100).toFixed(1)}% diff)`);
1445+
1446+
// Use anchor matching if within 10% (tight tolerance for variants)
1447+
// or if it's an exact match
1448+
if (closestCandidate.price === anchorPrice || priceDiff < 0.10) {
1449+
console.log(`[Voting] Found match for anchor price ${anchorPrice}: ${closestCandidate.price} via ${closestCandidate.method} (${(priceDiff * 100).toFixed(1)}% diff)`);
14681450
result.price = { price: closestCandidate.price, currency: closestCandidate.currency };
14691451
result.selectedMethod = closestCandidate.method;
14701452
return result;
14711453
} else {
1472-
console.log(`[Voting] Anchor price ${anchorPrice} too different from candidates (closest: ${closestCandidate.price}, ${(priceDiff * 100).toFixed(1)}% diff), using consensus`);
1454+
// No close match - price may have legitimately changed
1455+
console.log(`[Voting] No candidate close to anchor price ${anchorPrice} (closest: ${closestCandidate.price}, ${(priceDiff * 100).toFixed(1)}% diff)`);
1456+
// Fall through to preferred method or consensus
1457+
}
1458+
}
1459+
1460+
// PRIORITY 2: If user has a preferred method and no anchor match, try that method
1461+
if (preferredMethod && allCandidates.length > 0) {
1462+
const preferredCandidates = allCandidates.filter(c => c.method === preferredMethod);
1463+
if (preferredCandidates.length > 0) {
1464+
// Use highest confidence candidate from preferred method
1465+
const selectedCandidate = preferredCandidates.sort((a, b) => b.confidence - a.confidence)[0];
1466+
console.log(`[Voting] Using preferred method ${preferredMethod}: ${selectedCandidate.price}`);
1467+
result.price = { price: selectedCandidate.price, currency: selectedCandidate.currency };
1468+
result.selectedMethod = preferredMethod;
1469+
return result;
14731470
}
14741471
}
14751472

0 commit comments

Comments
 (0)