- 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
291 lines
10 KiB
JavaScript
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));
|