From 807f8af167d3a108ca3420500ec747661010318d Mon Sep 17 00:00:00 2001 From: Stan Runge Date: Sat, 24 Oct 2026 15:03:38 +0000 Subject: [PATCH] groceries: grouping ignores 'durum tarwe' and possessive store lines (Jumbo's), so house pasta lines up across stores --- src/lib/server/groceries/normalize.test.ts | 3 +++ src/lib/server/groceries/normalize.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lib/server/groceries/normalize.test.ts b/src/lib/server/groceries/normalize.test.ts index 8e70677..37022e4 100644 --- a/src/lib/server/groceries/normalize.test.ts +++ b/src/lib/server/groceries/normalize.test.ts @@ -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'); diff --git a/src/lib/server/groceries/normalize.ts b/src/lib/server/groceries/normalize.ts index e683047..36117ce 100644 --- a/src/lib/server/groceries/normalize.ts +++ b/src/lib/server/groceries/normalize.ts @@ -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(); 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);