Fix size-aware product suggestions in resolver
Previously a different-size variant was boosted to 0.95 confidence
('same product, different size'), so a Small order would be confidently
suggested the Large product. Now suggestions require the same size; a
known different size is suppressed entirely (you create the right one
instead). Also handle 'Size: X for ...' phrasing, medium sizes, and
American 'Color:' spelling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
3c3adcdea9
commit
46d1ca3375
1 changed files with 57 additions and 55 deletions
|
|
@ -260,6 +260,31 @@ const calculateTitleSimilarity = (title1: string, title2: string): number => {
|
|||
return matchingWords / totalUniqueWords;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract a size token (large/med/small) from a title or variant string.
|
||||
* Prefers an explicit "Size: X" so trailing words like "Large for med plants"
|
||||
* don't get misread.
|
||||
*/
|
||||
export const extractSize = (text: string): string => {
|
||||
const lower = text.toLowerCase();
|
||||
const explicit = lower.match(/size:\s*(large|medium|med|small)/);
|
||||
let token = explicit ? explicit[1] : '';
|
||||
if (!token) {
|
||||
if (lower.includes('large') && !lower.includes('small')) token = 'large';
|
||||
else if (lower.includes('small') && !lower.includes('large')) token = 'small';
|
||||
}
|
||||
return token === 'medium' ? 'med' : token;
|
||||
};
|
||||
|
||||
const cleanForCompare = (title: string): string =>
|
||||
title
|
||||
.replace(/Colou?r:\s*[^,\s-]+(?:\s+[^,\s-]+)*/i, '') // strip Color/Colour
|
||||
.replace(/Size:\s*[^,\s-]+(?:\s+(?:for|plants?|succulents?)\b[^,]*)?/i, '') // strip "Size: X for ..."
|
||||
.replace(/3D-Printed/i, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
/**
|
||||
* Find potential matches for a missing product
|
||||
* This helps suggest existing products that might be similar
|
||||
|
|
@ -271,73 +296,50 @@ export const findPotentialMatches = (
|
|||
): Array<{product: any; confidence: number; reason: string}> => {
|
||||
const suggestions: Array<{product: any; confidence: number; reason: string}> = [];
|
||||
|
||||
const itemTitle = missingProductTitle.toLowerCase();
|
||||
let itemSize = '';
|
||||
|
||||
// Extract size from missing product
|
||||
if (itemTitle.includes('large') && !itemTitle.includes('small')) {
|
||||
itemSize = 'large';
|
||||
} else if (itemTitle.includes('small') && !itemTitle.includes('large')) {
|
||||
itemSize = 'small';
|
||||
}
|
||||
|
||||
const cleanItemTitle = missingProductTitle
|
||||
.replace(/Colour:\s*[^,\s-]+(?:\s+[^,\s-]+)*/i, '')
|
||||
.replace(/Size:\s*[^,\s-]+/i, '')
|
||||
.replace(/3D-Printed/i, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const itemSize = extractSize(missingProductTitle);
|
||||
const cleanItemTitle = cleanForCompare(missingProductTitle);
|
||||
|
||||
for (const product of existingProducts) {
|
||||
const productTitle = product.title.toLowerCase();
|
||||
let productSize = '';
|
||||
|
||||
if (productTitle.includes('large') && !productTitle.includes('small')) {
|
||||
productSize = 'large';
|
||||
} else if (productTitle.includes('small') && !productTitle.includes('large')) {
|
||||
productSize = 'small';
|
||||
}
|
||||
|
||||
const cleanProductTitle = product.title
|
||||
.replace(/Colour:\s*[^,\s-]+(?:\s+[^,\s-]+)*/i, '')
|
||||
.replace(/Size:\s*[^,\s-]+/i, '')
|
||||
.replace(/3D-Printed/i, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const productSize = extractSize(`${product.title} ${(product.aliases || []).join(' ')}`);
|
||||
const cleanProductTitle = cleanForCompare(product.title);
|
||||
|
||||
const titleSimilarity = calculateTitleSimilarity(cleanItemTitle, cleanProductTitle);
|
||||
|
||||
let reason = '';
|
||||
let confidence = titleSimilarity;
|
||||
let confidence = 0;
|
||||
|
||||
// Check if it's the same product family but different size
|
||||
if (titleSimilarity > 0.8) {
|
||||
if (itemSize && productSize && itemSize !== productSize) {
|
||||
reason = `Same product, different size (${productSize} vs ${itemSize})`;
|
||||
confidence = 0.95; // Very high confidence - likely meant to be this size variant
|
||||
if (itemSize && productSize) {
|
||||
if (itemSize === productSize) {
|
||||
reason = `Same product, same size (${itemSize})`;
|
||||
confidence = Math.max(titleSimilarity, 0.95);
|
||||
} else {
|
||||
// Both sizes known and they differ — this is NOT the right product.
|
||||
// Suppress it so a wrong-size variant is never suggested.
|
||||
confidence = 0;
|
||||
}
|
||||
} else if (itemSize && !productSize) {
|
||||
reason = `Same product, missing size specification`;
|
||||
confidence = 0.85;
|
||||
reason = `Same product (existing has no size set)`;
|
||||
confidence = 0.75;
|
||||
} else if (!itemSize && productSize) {
|
||||
reason = `Same product, has size: ${productSize}`;
|
||||
confidence = 0.8;
|
||||
confidence = 0.7;
|
||||
} else {
|
||||
reason = `Very similar product`;
|
||||
confidence = titleSimilarity;
|
||||
}
|
||||
} else if (titleSimilarity > 0.6) {
|
||||
reason = `Similar product`;
|
||||
confidence = titleSimilarity;
|
||||
// Don't suggest a different-size variant even on a looser title match
|
||||
if (itemSize && productSize && itemSize !== productSize) {
|
||||
confidence = 0;
|
||||
} else {
|
||||
reason = `Similar product`;
|
||||
confidence = titleSimilarity;
|
||||
}
|
||||
}
|
||||
|
||||
if (confidence > 0.6) {
|
||||
suggestions.push({
|
||||
product,
|
||||
confidence,
|
||||
reason
|
||||
});
|
||||
suggestions.push({ product, confidence, reason });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue