groceries: relevance-ranked search (plain/basic products first)
All checks were successful
Deploy production / deploy (push) Successful in 1m13s

- search.ts: score on the core name (brand/size stripped): exact or query+base suffix
  (kipdij+filet), head-final compounds, prefix, inside; penalties per extra word and per
  flavour/processing word (search-words.ts, editable); bonuses for store brands, the
  query's dominant category, store count, favourites; trigram tiebreak
- groceries.synonyms table (seeded: kipdij/kippendij, pasta → spaghetti, …) expands queries
- list rows show all pack sizes of a group; group picker uses the same ranking
- scripts/groceries-search-eval.ts prints top results + latency; preflight now 8 tables
This commit is contained in:
2026-10-25 20:20:34 +00:00
parent 5028524537
commit 60370d4572
14 changed files with 31221 additions and 16 deletions

View File

@@ -4,6 +4,8 @@ import {
cheapest,
formatAmount,
formatUnitPrice,
packLabel,
packSizes,
parseEuros,
sortByPrice,
toOffers,
@@ -117,3 +119,21 @@ describe('bestPerStore', () => {
expect(bestPerStore(toOffers([item(1, 1, 100, 100)], []), 'pack').spread).toBeNull();
});
});
describe('packSizes', () => {
it('lists distinct pack sizes smallest first with friendly units', () => {
expect(
packSizes([
{ unitAmount: 0.8, unit: 'kg' },
{ unitAmount: 0.22, unit: 'kg' },
{ unitAmount: 0.45, unit: 'kg' },
{ unitAmount: 0.22, unit: 'kg' },
{ unitAmount: 1.5, unit: 'kg', available: false },
{ unitAmount: null, unit: null }
])
).toEqual(['220 g', '450 g', '800 g']);
expect(packLabel(1.5, 'l')).toBe('1,5 L');
expect(packLabel(0.33, 'l')).toBe('330 ml');
expect(packLabel(6, 'piece')).toBe('6 st');
});
});