From c235559914afdf64f5388dfc7dcad1f56427b7ab Mon Sep 17 00:00:00 2001 From: Diya Date: Sun, 29 Mar 2026 14:34:24 -1200 Subject: [PATCH 1/2] BE006 optimise recommendation engine performance --- database/local_backend/scanPipeline.js | 392 ++++++++++++++++++++----- 1 file changed, 311 insertions(+), 81 deletions(-) diff --git a/database/local_backend/scanPipeline.js b/database/local_backend/scanPipeline.js index 9f6747a..777f7cf 100644 --- a/database/local_backend/scanPipeline.js +++ b/database/local_backend/scanPipeline.js @@ -8,7 +8,10 @@ const SEVERITY_WEIGHTS = { high: 3 }; -const PIPELINE_VERSION = "1.1.0"; +const PIPELINE_VERSION = "1.4.0"; + +// Simple in-memory cache for repeated evaluations +const recommendationCache = new Map(); function cleanData(raw) { const normalizeList = (text) => @@ -16,62 +19,120 @@ function cleanData(raw) { ? text .toLowerCase() .split(/[,;().]/) - .map(i => i.trim()) + .map((i) => i.trim()) .filter(Boolean) : []; return { - barcode: raw.barcode?.toString().trim() || "", - name: raw.productName?.trim() || raw.name?.trim() || "Unknown product", - ingredients: normalizeList(raw.ingredientsText || raw.ingredients), - additives: normalizeList(raw.additivesText || raw.additives), - nutrition: raw.nutrition || {} + barcode: raw?.barcode?.toString().trim() || "", + name: raw?.productName?.trim() || raw?.name?.trim() || "Unknown product", + ingredients: normalizeList(raw?.ingredientsText || raw?.ingredients), + additives: normalizeList(raw?.additivesText || raw?.additives), + nutrition: raw?.nutrition || {} }; } -function getWarnings(cleaned, user) { - const warnings = []; - const ingredients = cleaned.ingredients; - const additives = cleaned.additives; - const nutrition = cleaned.nutrition; - - if (user.allergies) { - user.allergies.forEach(allergen => { - if (ingredients.some(i => i.includes(allergen.toLowerCase()))) { - warnings.push({ - type: "allergen", - code: `ALLERGEN_${allergen.toUpperCase()}`, - message: `Contains ${allergen}`, - severity: "high" - }); +function buildProcessedUserProfile(userProfile) { + const safeUserProfile = userProfile || {}; + + return { + id: safeUserProfile.id || null, + allergies: safeUserProfile.allergies || [], + avoidAdditives: safeUserProfile.avoidAdditives || [], + dietPreferences: safeUserProfile.dietPreferences || [], + allergiesSet: new Set( + (safeUserProfile.allergies || []).map((item) => item.toLowerCase()) + ), + avoidAdditivesSet: new Set( + (safeUserProfile.avoidAdditives || []).map((item) => item.toLowerCase()) + ), + dietPreferencesSet: new Set(safeUserProfile.dietPreferences || []) + }; +} + +function buildProductLookupSets(cleaned) { + return { + ingredientSet: new Set(cleaned.ingredients || []), + additiveSet: new Set(cleaned.additives || []) + }; +} + +function createCacheKey(cleaned, processedUserProfile) { + return JSON.stringify({ + barcode: cleaned.barcode, + ingredients: cleaned.ingredients, + additives: cleaned.additives, + nutrition: cleaned.nutrition, + allergies: processedUserProfile.allergies, + avoidAdditives: processedUserProfile.avoidAdditives, + dietPreferences: processedUserProfile.dietPreferences + }); +} + +function containsMatchingValue(setValues, candidates) { + for (const value of setValues) { + for (const candidate of candidates) { + if (value.includes(candidate) || candidate.includes(value)) { + return candidate; } + } + } + return null; +} + +function getWarnings(cleaned, processedUserProfile, productSets) { + const warnings = []; + const nutrition = cleaned.nutrition || {}; + + const nonVeganList = [ + "milk", + "egg", + "honey", + "gelatin", + "cheese", + "butter", + "cream", + "whey", + "casein" + ]; + + const glutenSources = ["wheat", "barley", "rye", "malt"]; + + const matchedAllergen = containsMatchingValue( + productSets.ingredientSet, + [...processedUserProfile.allergiesSet] + ); + + if (matchedAllergen) { + warnings.push({ + type: "allergen", + code: `ALLERGEN_${matchedAllergen.toUpperCase()}`, + message: `Contains ${matchedAllergen}`, + severity: "high" }); } - if (user.avoidAdditives) { - user.avoidAdditives.forEach(additive => { - if (additives.some(a => a.includes(additive.toLowerCase()))) { - warnings.push({ - type: "additive", - code: `ADDITIVE_${additive}`, - message: `Contains additive ${additive}, which you prefer to avoid`, - severity: "medium" - }); - } + const matchedAdditive = containsMatchingValue( + productSets.additiveSet, + [...processedUserProfile.avoidAdditivesSet] + ); + + if (matchedAdditive) { + warnings.push({ + type: "additive", + code: `ADDITIVE_${matchedAdditive}`, + message: `Contains additive ${matchedAdditive}, which you prefer to avoid`, + severity: "medium" }); } - if (user.dietPreferences?.includes("vegan")) { - const nonVeganList = [ - "milk", "egg", "honey", "gelatin", "cheese", - "butter", "cream", "whey", "casein" - ]; + if (processedUserProfile.dietPreferencesSet.has("vegan")) { + const hasNonVeganIngredient = containsMatchingValue( + productSets.ingredientSet, + nonVeganList + ); - if ( - ingredients.some(ing => - nonVeganList.some(blocked => ing.includes(blocked)) - ) - ) { + if (hasNonVeganIngredient) { warnings.push({ type: "diet", code: "DIET_VEGAN_UNSUITABLE", @@ -81,14 +142,13 @@ function getWarnings(cleaned, user) { } } - if (user.dietPreferences?.includes("glutenFree")) { - const glutenSources = ["wheat", "barley", "rye", "malt"]; + if (processedUserProfile.dietPreferencesSet.has("glutenFree")) { + const hasGluten = containsMatchingValue( + productSets.ingredientSet, + glutenSources + ); - if ( - ingredients.some(ing => - glutenSources.some(src => ing.includes(src)) - ) - ) { + if (hasGluten) { warnings.push({ type: "diet", code: "DIET_GLUTEN_UNSUITABLE", @@ -98,8 +158,10 @@ function getWarnings(cleaned, user) { } } - if (typeof nutrition.sugarG === "number" && - nutrition.sugarG > NUTRITION_LIMITS.sugarHighG) { + if ( + typeof nutrition.sugarG === "number" && + nutrition.sugarG > NUTRITION_LIMITS.sugarHighG + ) { warnings.push({ type: "nutrition", code: "HIGH_SUGAR", @@ -112,8 +174,8 @@ function getWarnings(cleaned, user) { } function classifyProduct(warnings) { - const hasHigh = warnings.some(w => w.severity === "high"); - const hasMedium = warnings.some(w => w.severity === "medium"); + const hasHigh = warnings.some((w) => w.severity === "high"); + const hasMedium = warnings.some((w) => w.severity === "medium"); if (hasHigh) return "red"; if (hasMedium) return "grey"; @@ -130,55 +192,223 @@ function calculateRiskScore(warnings) { return Math.min(100, total * 20); } -function getAlternatives(cleaned, classification) { +function calculateRecommendationScore(cleaned, processedUserProfile, warnings, productSets) { + let score = 100; + const nutrition = cleaned.nutrition || {}; + + const nonVeganList = [ + "milk", + "egg", + "honey", + "gelatin", + "cheese", + "butter", + "cream", + "whey", + "casein" + ]; + + const glutenSources = ["wheat", "barley", "rye", "malt"]; + + warnings.forEach((warning) => { + score -= (SEVERITY_WEIGHTS[warning.severity] || 1) * 15; + }); + + if (processedUserProfile.dietPreferencesSet.has("vegan")) { + const hasNonVeganIngredient = containsMatchingValue( + productSets.ingredientSet, + nonVeganList + ); + + if (!hasNonVeganIngredient) { + score += 10; + } + } + + if (processedUserProfile.dietPreferencesSet.has("glutenFree")) { + const hasGluten = containsMatchingValue( + productSets.ingredientSet, + glutenSources + ); + + if (!hasGluten) { + score += 10; + } + } + + if (typeof nutrition.sugarG === "number" && nutrition.sugarG <= 5) { + score += 10; + } + + return Math.max(0, Math.min(100, score)); +} + +function getAlternatives(cleaned, classification, processedUserProfile) { const base = [ - { name: "Dark Chocolate 85%", brand: "Lindt", barcode: "99901", classification: "green" }, - { name: "Organic Vegan Chocolate", brand: "Loving Earth", barcode: "99902", classification: "green" }, - { name: "Cocoa Nibs (Sugar-Free)", brand: "HealthyCo", barcode: "99903", classification: "green" } + { + name: "Dark Chocolate 85%", + brand: "Lindt", + barcode: "99901", + classification: "green", + tags: [] + }, + { + name: "Organic Vegan Chocolate", + brand: "Loving Earth", + barcode: "99902", + classification: "green", + tags: ["vegan"] + }, + { + name: "Cocoa Nibs (Sugar-Free)", + brand: "HealthyCo", + barcode: "99903", + classification: "green", + tags: ["vegan", "glutenFree", "lowSugar"] + } ]; - if (classification === "green") return base.slice(0, 2); - return base; + let filtered = base; + + if (processedUserProfile.dietPreferencesSet.has("vegan")) { + filtered = filtered.filter((item) => item.tags.includes("vegan")); + } + + if (processedUserProfile.dietPreferencesSet.has("glutenFree")) { + filtered = filtered.filter((item) => item.tags.includes("glutenFree")); + } + + if (filtered.length === 0) { + filtered = base; + } + + if (classification === "green") { + return filtered.slice(0, 2); + } + + return filtered; +} + +function generateRecommendationReason(item, processedUserProfile) { + const reasons = []; + const tags = item.tags || []; + + if ( + processedUserProfile.dietPreferencesSet.has("vegan") && + tags.includes("vegan") + ) { + reasons.push("Matches vegan preference"); + } + + if ( + processedUserProfile.dietPreferencesSet.has("glutenFree") && + tags.includes("glutenFree") + ) { + reasons.push("Matches gluten-free preference"); + } + + if (tags.includes("lowSugar")) { + reasons.push("Low sugar alternative"); + } + + return reasons.length ? reasons : ["General healthier alternative"]; } function buildScanResult(rawData, userProfile) { - const cleaned = cleanData(rawData); - const warnings = getWarnings(cleaned, userProfile); + const cleaned = cleanData(rawData || {}); + const processedUserProfile = buildProcessedUserProfile(userProfile); + const cacheKey = createCacheKey(cleaned, processedUserProfile); + + if (recommendationCache.has(cacheKey)) { + const cachedResult = recommendationCache.get(cacheKey); + + return { + ...cachedResult, + metadata: { + ...cachedResult.metadata, + servedFromCache: true + } + }; + } + + const productSets = buildProductLookupSets(cleaned); + const warnings = getWarnings(cleaned, processedUserProfile, productSets); const classification = classifyProduct(warnings); const riskScore = calculateRiskScore(warnings); + const recommendationScore = calculateRecommendationScore( + cleaned, + processedUserProfile, + warnings, + productSets + ); - return { + const alternatives = getAlternatives( + cleaned, + classification, + processedUserProfile + ).map((item) => ({ + ...item, + reason: generateRecommendationReason(item, processedUserProfile) + })); + + const result = { product: cleaned, classification, warnings, suitability: { - isSafe: classification === "green", - reasons: warnings.map(w => w.message), - riskScore + isSafe: classification !== "red", + reasons: warnings.map((w) => w.message), + riskScore, + recommendationScore, + matchedPreferences: processedUserProfile.dietPreferences }, - alternatives: getAlternatives(cleaned, classification), + alternatives, metadata: { processedAt: new Date().toISOString(), pipelineVersion: PIPELINE_VERSION, - userId: userProfile.id || null + userId: processedUserProfile.id, + servedFromCache: false } }; + + recommendationCache.set(cacheKey, result); + return result; } -const testRaw = { - barcode: "12345", - productName: "Milk Chocolate", - ingredientsText: "Milk, Cocoa, Sugar, Wheat flour", - additivesText: "621", - nutrition: { sugarG: 25 } +module.exports = { + cleanData, + buildProcessedUserProfile, + buildProductLookupSets, + createCacheKey, + containsMatchingValue, + getWarnings, + classifyProduct, + calculateRiskScore, + calculateRecommendationScore, + getAlternatives, + generateRecommendationReason, + buildScanResult }; -const testUser = { - id: "user-123", - allergies: ["milk"], - avoidAdditives: ["621"], - dietPreferences: ["vegan", "glutenFree"] -}; +if (require.main === module) { + const testRaw = { + barcode: "12345", + productName: "Milk Chocolate", + ingredientsText: "Milk, Cocoa, Sugar, Wheat flour", + additivesText: "621", + nutrition: { sugarG: 25 } + }; + + const testUser = { + id: "user-123", + allergies: ["milk"], + avoidAdditives: ["621"], + dietPreferences: ["vegan", "glutenFree"] + }; + + console.log("Structured Scan Result:"); + console.log(JSON.stringify(buildScanResult(testRaw, testUser), null, 2)); -console.log("Structured Scan Result:"); -console.log(JSON.stringify(buildScanResult(testRaw, testUser), null, 2)); + console.log("\nRunning same input again to test cache:"); + console.log(JSON.stringify(buildScanResult(testRaw, testUser), null, 2)); +} \ No newline at end of file From fc0f88f0abc3c45584c1553811e5898faea48c6f Mon Sep 17 00:00:00 2001 From: Diya Date: Tue, 7 Apr 2026 01:37:30 -1200 Subject: [PATCH 2/2] BE006 fix caching safeguards and clean optimisation logic --- database/local_backend/scanPipeline.js | 229 +++++++++++-------------- 1 file changed, 97 insertions(+), 132 deletions(-) diff --git a/database/local_backend/scanPipeline.js b/database/local_backend/scanPipeline.js index 777f7cf..1497e8c 100644 --- a/database/local_backend/scanPipeline.js +++ b/database/local_backend/scanPipeline.js @@ -8,9 +8,23 @@ const SEVERITY_WEIGHTS = { high: 3 }; -const PIPELINE_VERSION = "1.4.0"; +const PIPELINE_VERSION = "1.4.1"; +const CACHE_LIMIT = 50; + +const NON_VEGAN_INGREDIENTS = [ + "milk", + "egg", + "honey", + "gelatin", + "cheese", + "butter", + "cream", + "whey", + "casein" +]; + +const GLUTEN_SOURCES = ["wheat", "barley", "rye", "malt"]; -// Simple in-memory cache for repeated evaluations const recommendationCache = new Map(); function cleanData(raw) { @@ -37,15 +51,13 @@ function buildProcessedUserProfile(userProfile) { return { id: safeUserProfile.id || null, - allergies: safeUserProfile.allergies || [], - avoidAdditives: safeUserProfile.avoidAdditives || [], - dietPreferences: safeUserProfile.dietPreferences || [], - allergiesSet: new Set( - (safeUserProfile.allergies || []).map((item) => item.toLowerCase()) + allergies: (safeUserProfile.allergies || []).map((item) => + item.toLowerCase() ), - avoidAdditivesSet: new Set( - (safeUserProfile.avoidAdditives || []).map((item) => item.toLowerCase()) + avoidAdditives: (safeUserProfile.avoidAdditives || []).map((item) => + item.toLowerCase() ), + dietPreferences: safeUserProfile.dietPreferences || [], dietPreferencesSet: new Set(safeUserProfile.dietPreferences || []) }; } @@ -58,81 +70,53 @@ function buildProductLookupSets(cleaned) { } function createCacheKey(cleaned, processedUserProfile) { - return JSON.stringify({ - barcode: cleaned.barcode, - ingredients: cleaned.ingredients, - additives: cleaned.additives, - nutrition: cleaned.nutrition, - allergies: processedUserProfile.allergies, - avoidAdditives: processedUserProfile.avoidAdditives, - dietPreferences: processedUserProfile.dietPreferences - }); + const barcode = cleaned.barcode || "no-barcode"; + const userId = processedUserProfile.id || "anonymous"; + return `${barcode}_${userId}`; } -function containsMatchingValue(setValues, candidates) { - for (const value of setValues) { - for (const candidate of candidates) { - if (value.includes(candidate) || candidate.includes(value)) { - return candidate; - } - } - } - return null; +function hasIngredientMatch(ingredients, targets) { + return ingredients.some((ingredient) => + targets.some((target) => ingredient.includes(target)) + ); } -function getWarnings(cleaned, processedUserProfile, productSets) { - const warnings = []; - const nutrition = cleaned.nutrition || {}; - - const nonVeganList = [ - "milk", - "egg", - "honey", - "gelatin", - "cheese", - "butter", - "cream", - "whey", - "casein" - ]; - - const glutenSources = ["wheat", "barley", "rye", "malt"]; - - const matchedAllergen = containsMatchingValue( - productSets.ingredientSet, - [...processedUserProfile.allergiesSet] +function hasAdditiveMatch(additives, targets) { + return additives.some((additive) => + targets.some((target) => additive.includes(target)) ); +} - if (matchedAllergen) { - warnings.push({ - type: "allergen", - code: `ALLERGEN_${matchedAllergen.toUpperCase()}`, - message: `Contains ${matchedAllergen}`, - severity: "high" - }); - } +function getWarnings(cleaned, processedUserProfile) { + const warnings = []; + const ingredients = cleaned.ingredients || []; + const additives = cleaned.additives || []; + const nutrition = cleaned.nutrition || {}; - const matchedAdditive = containsMatchingValue( - productSets.additiveSet, - [...processedUserProfile.avoidAdditivesSet] - ); + processedUserProfile.allergies.forEach((allergen) => { + if (ingredients.some((ingredient) => ingredient.includes(allergen))) { + warnings.push({ + type: "allergen", + code: `ALLERGEN_${allergen.toUpperCase()}`, + message: `Contains ${allergen}`, + severity: "high" + }); + } + }); - if (matchedAdditive) { - warnings.push({ - type: "additive", - code: `ADDITIVE_${matchedAdditive}`, - message: `Contains additive ${matchedAdditive}, which you prefer to avoid`, - severity: "medium" - }); - } + processedUserProfile.avoidAdditives.forEach((additive) => { + if (additives.some((item) => item.includes(additive))) { + warnings.push({ + type: "additive", + code: `ADDITIVE_${additive}`, + message: `Contains additive ${additive}, which you prefer to avoid`, + severity: "medium" + }); + } + }); if (processedUserProfile.dietPreferencesSet.has("vegan")) { - const hasNonVeganIngredient = containsMatchingValue( - productSets.ingredientSet, - nonVeganList - ); - - if (hasNonVeganIngredient) { + if (hasIngredientMatch(ingredients, NON_VEGAN_INGREDIENTS)) { warnings.push({ type: "diet", code: "DIET_VEGAN_UNSUITABLE", @@ -143,12 +127,7 @@ function getWarnings(cleaned, processedUserProfile, productSets) { } if (processedUserProfile.dietPreferencesSet.has("glutenFree")) { - const hasGluten = containsMatchingValue( - productSets.ingredientSet, - glutenSources - ); - - if (hasGluten) { + if (hasIngredientMatch(ingredients, GLUTEN_SOURCES)) { warnings.push({ type: "diet", code: "DIET_GLUTEN_UNSUITABLE", @@ -174,8 +153,8 @@ function getWarnings(cleaned, processedUserProfile, productSets) { } function classifyProduct(warnings) { - const hasHigh = warnings.some((w) => w.severity === "high"); - const hasMedium = warnings.some((w) => w.severity === "medium"); + const hasHigh = warnings.some((warning) => warning.severity === "high"); + const hasMedium = warnings.some((warning) => warning.severity === "medium"); if (hasHigh) return "red"; if (hasMedium) return "grey"; @@ -185,55 +164,34 @@ function classifyProduct(warnings) { function calculateRiskScore(warnings) { if (!warnings.length) return 0; - const total = warnings.reduce((sum, w) => { - return sum + (SEVERITY_WEIGHTS[w.severity] || 1); + const total = warnings.reduce((sum, warning) => { + return sum + (SEVERITY_WEIGHTS[warning.severity] || 1); }, 0); return Math.min(100, total * 20); } -function calculateRecommendationScore(cleaned, processedUserProfile, warnings, productSets) { +function calculateRecommendationScore(cleaned, processedUserProfile, warnings) { let score = 100; + const ingredients = cleaned.ingredients || []; const nutrition = cleaned.nutrition || {}; - const nonVeganList = [ - "milk", - "egg", - "honey", - "gelatin", - "cheese", - "butter", - "cream", - "whey", - "casein" - ]; - - const glutenSources = ["wheat", "barley", "rye", "malt"]; - warnings.forEach((warning) => { score -= (SEVERITY_WEIGHTS[warning.severity] || 1) * 15; }); - if (processedUserProfile.dietPreferencesSet.has("vegan")) { - const hasNonVeganIngredient = containsMatchingValue( - productSets.ingredientSet, - nonVeganList - ); - - if (!hasNonVeganIngredient) { - score += 10; - } + if ( + processedUserProfile.dietPreferencesSet.has("vegan") && + !hasIngredientMatch(ingredients, NON_VEGAN_INGREDIENTS) + ) { + score += 10; } - if (processedUserProfile.dietPreferencesSet.has("glutenFree")) { - const hasGluten = containsMatchingValue( - productSets.ingredientSet, - glutenSources - ); - - if (!hasGluten) { - score += 10; - } + if ( + processedUserProfile.dietPreferencesSet.has("glutenFree") && + !hasIngredientMatch(ingredients, GLUTEN_SOURCES) + ) { + score += 10; } if (typeof nutrition.sugarG === "number" && nutrition.sugarG <= 5) { @@ -243,7 +201,7 @@ function calculateRecommendationScore(cleaned, processedUserProfile, warnings, p return Math.max(0, Math.min(100, score)); } -function getAlternatives(cleaned, classification, processedUserProfile) { +function getAlternatives(classification, processedUserProfile) { const base = [ { name: "Dark Chocolate 85%", @@ -282,11 +240,7 @@ function getAlternatives(cleaned, classification, processedUserProfile) { filtered = base; } - if (classification === "green") { - return filtered.slice(0, 2); - } - - return filtered; + return classification === "green" ? filtered.slice(0, 2) : filtered; } function generateRecommendationReason(item, processedUserProfile) { @@ -314,6 +268,12 @@ function generateRecommendationReason(item, processedUserProfile) { return reasons.length ? reasons : ["General healthier alternative"]; } +function enforceCacheLimit() { + if (recommendationCache.size >= CACHE_LIMIT) { + recommendationCache.clear(); + } +} + function buildScanResult(rawData, userProfile) { const cleaned = cleanData(rawData || {}); const processedUserProfile = buildProcessedUserProfile(userProfile); @@ -331,19 +291,16 @@ function buildScanResult(rawData, userProfile) { }; } - const productSets = buildProductLookupSets(cleaned); - const warnings = getWarnings(cleaned, processedUserProfile, productSets); + const warnings = getWarnings(cleaned, processedUserProfile); const classification = classifyProduct(warnings); const riskScore = calculateRiskScore(warnings); const recommendationScore = calculateRecommendationScore( cleaned, processedUserProfile, - warnings, - productSets + warnings ); const alternatives = getAlternatives( - cleaned, classification, processedUserProfile ).map((item) => ({ @@ -357,7 +314,7 @@ function buildScanResult(rawData, userProfile) { warnings, suitability: { isSafe: classification !== "red", - reasons: warnings.map((w) => w.message), + reasons: warnings.map((warning) => warning.message), riskScore, recommendationScore, matchedPreferences: processedUserProfile.dietPreferences @@ -371,7 +328,9 @@ function buildScanResult(rawData, userProfile) { } }; + enforceCacheLimit(); recommendationCache.set(cacheKey, result); + return result; } @@ -380,7 +339,6 @@ module.exports = { buildProcessedUserProfile, buildProductLookupSets, createCacheKey, - containsMatchingValue, getWarnings, classifyProduct, calculateRiskScore, @@ -406,9 +364,16 @@ if (require.main === module) { dietPreferences: ["vegan", "glutenFree"] }; + const firstRun = buildScanResult(testRaw, testUser); + const secondRun = buildScanResult(testRaw, testUser); + console.log("Structured Scan Result:"); - console.log(JSON.stringify(buildScanResult(testRaw, testUser), null, 2)); + console.log(JSON.stringify(firstRun, null, 2)); console.log("\nRunning same input again to test cache:"); - console.log(JSON.stringify(buildScanResult(testRaw, testUser), null, 2)); + console.log(JSON.stringify(secondRun, null, 2)); + + console.log("\nBasic cache check:"); + console.log("First run servedFromCache:", firstRun.metadata.servedFromCache); + console.log("Second run servedFromCache:", secondRun.metadata.servedFromCache); } \ No newline at end of file