groceries: grouping ignores 'durum tarwe' and possessive store lines (Jumbo's), so house pasta lines up across stores
All checks were successful
Deploy production / deploy (push) Successful in 2m49s

This commit is contained in:
2026-10-24 15:03:38 +00:00
parent 8c9a08afa1
commit 807f8af167
2 changed files with 9 additions and 2 deletions

View File

@@ -95,6 +95,9 @@ describe('names and brands', () => {
'melk'
]);
expect(nameTokens('Calvé Pindakaas 350g', 'Calvé')).toEqual(['pindakaa']);
expect(nameTokens("Jumbo's Spaghetti 500 g", "Jumbo's")).toEqual(['spaghetti']);
expect(nameTokens('Jumbo Penne Durum Tarwe 500 g', 'Jumbo')).toEqual(['penne']);
expect(nameTokens('AH Penne', 'AH')).toEqual(['penne']);
});
it('makes display names', () => {
expect(displayName('AH Halfvolle melk', 'AH')).toBe('Halfvolle melk');

View File

@@ -293,14 +293,18 @@ const stem = (t: string) =>
/** Meaningful name tokens: no brand, store name, size, packaging or stop words. */
export function nameTokens(title: string, brand?: string | null): string[] {
let s = fold(title).replace(/&/g, ' en ').replace(SIZE_IN_NAME, ' ');
let s = fold(title)
.replace(/&/g, ' en ')
.replace(SIZE_IN_NAME, ' ')
.replace(/\bdurum\s*-?\s*(?:tarwe)?\b/g, ' '); // "Penne durum tarwe" is plain penne
const drop = new Set<string>();
for (const w of [brand ?? '', ...STORE_WORDS])
for (const t of fold(w).split(/[^a-z0-9]+/)) if (t) drop.add(t);
for (const t of fold(w).replace(/['’]/g, '').split(/[^a-z0-9]+/)) if (t) drop.add(t);
s = s.replace(/['’]/g, '').replace(/[^a-z0-9%+]+/g, ' ');
const out: string[] = [];
for (const raw of s.split(' ')) {
if (!raw || drop.has(raw) || STOP.has(raw)) continue;
if (/^(?:ah|jumbo|plus)s$/.test(raw)) continue; // possessive store lines: Jumbo's
if (/^\d+$/.test(raw) && raw.length > 2) continue; // stray numbers (article codes)
const t = stem(raw);
if (!out.includes(t)) out.push(t);