karby/test-suggest.js
cas c2b9074ef9 feat: initiële commit — Karby PWA koolhydratenteller met NEVO database, maaltijden, dagboek, e2e tests
- NEVO dataset (2328 voedingsmiddelen) met zoekfunctie
- Dagboek met eetmomenten, koolhydratenlimiet en progress bar
- Maaltijden opslaan + nesten in dagboek + eenmalig/permanent bewerken
- Portie-systeem met steppers per NEVO-categorie
- Vorteq amber/goud branding met Karby logo
- Voedingscentrum links per product
- 8 Playwright e2e tests (100% groen)
- PWA met offline support

Kanban board: koolhydraat-app
2026-07-24 01:44:19 +02:00

291 lines
10 KiB
JavaScript

// Test script for the new suggestMealName and shortItemName functions
const NEVO_DESC = new Set([
'rauw','rauwe','gekookt','gekookte','onbereid','onbereide','bereid','bereide',
'vers','verse','diepvries','diepgevroren','gedroogd','gedroogde',
'gem','gemixt','gesneden','gebakken','gebraden','gegrild','gestoofd','gefrituurd',
'ongezouten','gezouten','gerookt','gepekeld',
'gekonserveerd','geconserveerd','gezeefd','uitgesmolten',
'gepaneerd','gevuld','voorgekookt','koelvers',
'halfvolle','magere','volle','halfvol',
'scharrel','biologisch','biologische','mais',
'witte','bruine','zwarte','rode','groene','groen','gele','oranje',
'zoete','zure','bittere','zoute',
'schil','vliesje','vel','niet','panklaar',
'kruimig','vastkokend','eigen','mnd','ongekookt','gt','kippen','naturel',
]);
function shortItemName(naam) {
if (!naam) return '';
let name = naam.replace(/^\d+\s+/, '');
const dsi = name.indexOf(' ');
if (dsi > 0) name = name.substring(dsi).trim();
name = name.split('/')[0].trim();
name = name.replace(/\([^)]*\)/g, '').trim();
name = name.split(',')[0].trim();
name = name.replace(/\b\d+[%+]?\b/g, '').trim();
name = name.replace(/>/g, '').trim();
name = name.replace(/\s+/g, ' ').trim();
if (!name) return '';
const words = name.split(/\s+/);
let keep = words.slice(0, 2);
if (keep.length >= 2 && keep[1].endsWith('-') && keep[0].toLowerCase() !== 'ei') {
const mod = keep[1].slice(0, -1);
const base = keep[0];
const comp = mod.toLowerCase() + base.toLowerCase();
keep = [comp.charAt(0).toUpperCase() + comp.slice(1)];
}
keep = keep.map(w => w.replace(/-$/, ''));
const result = [];
const seen = new Set();
for (const w of keep) {
if (w.length <= 1) continue;
const wl = w.toLowerCase();
if (NEVO_DESC.has(wl)) continue;
if (seen.has(wl)) continue;
seen.add(wl);
result.push(w);
}
return result.join(' ');
}
function nevoDinerType(nevoCat) {
const map = {
'Aardappelen en knolgewassen': 'aardappel',
'Groente': 'groente',
'Vlees en gevogelte': 'vlees',
'Vis, schaal- en schelpdieren': 'vlees',
'Vleeswaren': 'vlees',
'Eieren': 'vlees',
'Brood': 'brood',
'Kaas': 'kaas',
'Melk en melkproducten': 'zuivel',
'Fruit': 'fruit',
};
return map[nevoCat] || 'overig';
}
const CATEGORY_MAP = {
'Brood': 'Brood',
'Melk en melkproducten': 'Zuivel',
'Kaas': 'Zuivel',
'Fruit': 'Fruit',
'Groente': 'Groente',
'Aardappelen en knolgewassen': 'Groente',
'Peulvruchten': 'Groente',
'Vlees en gevogelte': 'Vlees/Vis',
'Vis, schaal- en schelpdieren': 'Vlees/Vis',
'Vleeswaren': 'Vlees/Vis',
'Eieren': 'Vlees/Vis',
'Alcoholische dranken': 'Dranken',
'Niet-alcoholische dranken': 'Dranken',
'Hartige snacks en zoutjes': 'Tussendoortjes',
'Noten en zaden': 'Tussendoortjes',
'Gebak en koek': 'Snoep/Koek',
'Suiker, snoep, zoet beleg en zoete sauzen': 'Snoep/Koek',
'Graanproducten en meelsoorten': 'Overig',
'Vleesvervangers en zuivelvervangers': 'Overig',
'Samengestelde gerechten': 'Overig',
'Soepen': 'Overig',
'Hartige sauzen': 'Overig',
'Kruiden en specerijen': 'Overig',
'Vetten en oliën': 'Overig',
'Diversen': 'Overig',
'Flesvoeding en preparaten': 'Overig',
'Hartig broodbeleg': 'Overig'
};
function mapCategory(itemCat) {
return CATEGORY_MAP[itemCat] || 'Overig';
}
function suggestMealName(items) {
if (!items || items.length === 0) return '';
if (items.length === 1) return shortItemName(items[0].item.naam);
const enriched = items.map(e => ({
name: shortItemName(e.item.naam),
cat: mapCategory(e.item.cat || 'Overig'),
nevoCat: e.item.cat || 'Overig',
type: nevoDinerType(e.item.cat || 'Overig'),
}));
const byCat = {};
enriched.forEach(info => {
if (!byCat[info.cat]) byCat[info.cat] = [];
byCat[info.cat].push(info);
});
const cats = Object.keys(byCat);
const hasAardappel = enriched.some(e => e.type === 'aardappel');
const hasGroente = enriched.some(e => e.type === 'groente');
const hasVlees = enriched.some(e => e.type === 'vlees');
if (hasAardappel && (hasGroente || hasVlees)) {
const groenteNamen = enriched
.filter(e => e.type === 'groente')
.map(e => e.name);
const vleesNamen = enriched
.filter(e => e.type === 'vlees')
.map(e => e.name);
const groente = groenteNamen[0] || '';
const vlees = vleesNamen[0] || '';
if (hasGroente && hasVlees && groente && vlees) {
return 'AVG ' + groente + ' en ' + vlees;
}
if (hasGroente && groente) {
return 'Prakkie ' + groente + (vlees ? ' met ' + vlees : '');
}
const other = enriched.find(e => e.type !== 'aardappel');
if (other) return 'Aardappel met ' + other.name;
}
if (hasGroente && hasVlees) {
const gr = enriched.filter(e => e.type === 'groente').map(e => e.name)[0] || '';
const vl = enriched.filter(e => e.type === 'vlees').map(e => e.name)[0] || '';
if (gr && vl) return gr + ' met ' + vl;
if (gr) return 'Groente met ' + vl;
}
if (cats.includes('Brood')) {
const beleg = enriched.filter(e => e.cat !== 'Brood').map(e => e.name);
if (beleg.length === 1) return 'Broodje ' + beleg[0];
if (beleg.length === 2) return 'Broodje ' + beleg[0] + ' en ' + beleg[1];
if (beleg.length <= 4) return 'Brood met ' + beleg.slice(0, -1).join(', ') + ' en ' + beleg[beleg.length - 1];
return 'Brood met beleg';
}
if (cats.length === 1) {
const cat = cats[0];
const catNamen = byCat[cat].map(i => i.name);
if (catNamen.length <= 3) {
if (catNamen.length === 2) return catNamen[0] + ' en ' + catNamen[1];
if (catNamen.length === 3) return catNamen[0] + ', ' + catNamen[1] + ' en ' + catNamen[2];
return catNamen[0];
}
return cat + ' (' + catNamen.length + ' stuks)';
}
if (cats.length === 2) {
const [a, b] = cats;
const na = byCat[a].map(i => i.name);
const nb = byCat[b].map(i => i.name);
if (na.length === 1 && nb.length === 1) return na[0] + ' & ' + nb[0];
const parts = cats.map(c => {
const n = byCat[c].map(i => i.name);
if (n.length <= 2) return n.join(' & ');
return n[0] + ' (+' + (n.length - 1) + ')';
});
return parts.join(' & ');
}
const sorted = cats.sort((a, b) => byCat[b].length - byCat[a].length);
const [top1, top2] = sorted;
const rest = enriched.length - byCat[top1].length - byCat[top2].length;
let r = top1 + ' & ' + top2;
if (rest > 0) r += ' +' + rest + ' meer';
return r;
}
// === TEST SCENARIOS ===
// Bug report: AVG dinner
const bugItems = [
{ item: { naam: '2747 Aardappelen kruimig z schil gekookt gem', cat: 'Aardappelen en knolgewassen', kh: '19' } },
{ item: { naam: '1146 Spinazie gesneden diepvries onbereid', cat: 'Groente', kh: '1' } },
{ item: { naam: '2769 Ei kippen- scharrel rauw Scharrelei/scharrelkippeneieren rauw', cat: 'Eieren', kh: '13' } },
{ item: { naam: '3026 Speklap bereid in eigen vet', cat: 'Vlees en gevogelte', kh: '0' } },
];
console.log('=== BUG REPORT: AVG dinner ===');
console.log('Items:', bugItems.map(i => shortItemName(i.item.naam)).join(', '));
console.log('Suggestion:', suggestMealName(bugItems));
console.log();
// Broodje kaas
const broodjeKaas = [
{ item: { naam: '533 Brood bruin volkoren', cat: 'Brood', kh: '40' } },
{ item: { naam: '511 Kaas Edammer 40+', cat: 'Kaas', kh: '0' } },
];
console.log('=== Broodje kaas ===');
console.log('Items:', broodjeKaas.map(i => i.item.naam).join(', '));
console.log('Suggestion:', suggestMealName(broodjeKaas));
console.log();
// Prakkie: aardappel + groente
const prakkie = [
{ item: { naam: '1 Aardappelen rauw', cat: 'Aardappelen en knolgewassen', kh: '19' } },
{ item: { naam: '8 Andijvie gekookt', cat: 'Groente', kh: '1' } },
];
console.log('=== Prakkie: aardappel + groente ===');
console.log('Items:', prakkie.map(i => shortItemName(i.item.naam)).join(', '));
console.log('Suggestion:', suggestMealName(prakkie));
console.log();
// Prakkie met vlees
const prakkieVlees = [
{ item: { naam: '1 Aardappelen rauw', cat: 'Aardappelen en knolgewassen', kh: '19' } },
{ item: { naam: '8 Andijvie gekookt', cat: 'Groente', kh: '1' } },
{ item: { naam: '1432 Speklap rauw', cat: 'Vlees en gevogelte', kh: '0' } },
];
console.log('=== Prakkie met vlees ===');
console.log('Items:', prakkieVlees.map(i => shortItemName(i.item.naam)).join(', '));
console.log('Suggestion:', suggestMealName(prakkieVlees));
console.log();
// Brood met ham
const broodHam = [
{ item: { naam: '533 Brood bruin volkoren', cat: 'Brood', kh: '40' } },
{ item: { naam: '328 Ham rauwe', cat: 'Vleeswaren', kh: '0' } },
];
console.log('=== Broodje ham ===');
console.log('Suggestion:', suggestMealName(broodHam));
console.log();
// Yoghurt + fruit (ontbijt)
const ontbijt = [
{ item: { naam: '270 Melk rauwe', cat: 'Melk en melkproducten', kh: '5' } },
{ item: { naam: '148 Aardbeien', cat: 'Fruit', kh: '5' } },
{ item: { naam: '209 Ontbijtproduct Cornflakes', cat: 'Graanproducten en meelsoorten', kh: '75' } },
];
console.log('=== Ontbijt (generic) ===');
console.log('Items:', ontbijt.map(i => shortItemName(i.item.naam)).join(', '));
console.log('Suggestion:', suggestMealName(ontbijt));
console.log();
// Groente + vlees dinner
const groenteVlees = [
{ item: { naam: '51 Spinazie rauw', cat: 'Groente', kh: '1' } },
{ item: { naam: '1432 Speklap rauw', cat: 'Vlees en gevogelte', kh: '0' } },
];
console.log('=== Groente + vlees ===');
console.log('Suggestion:', suggestMealName(groenteVlees));
console.log();
// Brood met 2 beleg
const broodDubbel = [
{ item: { naam: '533 Brood bruin volkoren', cat: 'Brood', kh: '40' } },
{ item: { naam: '511 Kaas Edammer 40+', cat: 'Kaas', kh: '0' } },
{ item: { naam: '328 Ham rauwe', cat: 'Vleeswaren', kh: '0' } },
];
console.log('=== Broodje kaas en ham ===');
console.log('Suggestion:', suggestMealName(broodDubbel));
console.log();
// Single category, multiple items (Fruit)
const fruitMand = [
{ item: { naam: '147 Appel z schil gem', cat: 'Fruit', kh: '13' } },
{ item: { naam: '148 Aardbeien', cat: 'Fruit', kh: '5' } },
{ item: { naam: '33 Rozijnen gedroogd', cat: 'Fruit', kh: '67' } },
];
console.log('=== Fruitmand (3 items) ===');
console.log('Suggestion:', suggestMealName(fruitMand));
console.log();
// Kip + groente
const kipGroente = [
{ item: { naam: '51 Spinazie rauw', cat: 'Groente', kh: '1' } },
{ item: { naam: '95 Paardenvlees rauw', cat: 'Vlees en gevogelte', kh: '0' } },
];
console.log('=== Kip + groente (vlees + groente) ===');
console.log('Suggestion:', suggestMealName(kipGroente));