+
+
+
+
+ {#if nutritionMappings.length > 0}
+
+
+
- {nutritionResult.mappings.filter((m) => m.matchMethod !== 'none').length}/{nutritionResult.count} Zutaten zugeordnet
+ {nutritionMappings.filter((m) => m.matchMethod !== 'none').length}/{nutritionMappings.length} Zutaten zugeordnet
- {/if}
-
-
- {#if !showTranslationWorkflow}
-
+ {:else}
+
+
Nährwerte
+
+
- {/if}
+
+
+ {/if}
+
+ {#if !translationData && !showTranslationWorkflow}
+
+
Übersetzung
+
+
+
{/if}
-{#if showTranslationWorkflow}
+{#if translationData || showTranslationWorkflow}
{/if}
+
+
+
diff --git a/src/routes/api/[recipeLang=recipeLang]/nutrition/generate/[name]/+server.ts b/src/routes/api/[recipeLang=recipeLang]/nutrition/generate/[name]/+server.ts
index dd26d03..92b600e 100644
--- a/src/routes/api/[recipeLang=recipeLang]/nutrition/generate/[name]/+server.ts
+++ b/src/routes/api/[recipeLang=recipeLang]/nutrition/generate/[name]/+server.ts
@@ -4,7 +4,7 @@ import { dbConnect } from '$utils/db';
import { isEnglish } from '$lib/server/recipeHelpers';
import { generateNutritionMappings } from '$lib/server/nutritionMatcher';
-export const POST: RequestHandler = async ({ params, locals }) => {
+export const POST: RequestHandler = async ({ params, locals, url }) => {
await locals.auth();
await dbConnect();
@@ -19,6 +19,14 @@ export const POST: RequestHandler = async ({ params, locals }) => {
const ingredients = recipe.ingredients || [];
const translatedIngredients = recipe.translations?.en?.ingredients;
+ const newMappings = await generateNutritionMappings(ingredients, translatedIngredients);
+ const preview = url.searchParams.get('preview') === 'true';
+
+ // In preview mode, return pure auto-matches without saving (client merges manual edits)
+ if (preview) {
+ return json({ mappings: newMappings, count: newMappings.length });
+ }
+
// Preserve manually edited mappings
const existingMappings = recipe.nutritionMappings || [];
const manualMappings = new Map(
@@ -27,8 +35,6 @@ export const POST: RequestHandler = async ({ params, locals }) => {
.map((m: any) => [`${m.sectionIndex}-${m.ingredientIndex}`, m])
);
- const newMappings = await generateNutritionMappings(ingredients, translatedIngredients);
-
// Merge: keep manual edits, use new auto-matches for the rest
const finalMappings = newMappings.map(m => {
const key = `${m.sectionIndex}-${m.ingredientIndex}`;
diff --git a/src/routes/api/nutrition/search/+server.ts b/src/routes/api/nutrition/search/+server.ts
index 74ab505..99d0e37 100644
--- a/src/routes/api/nutrition/search/+server.ts
+++ b/src/routes/api/nutrition/search/+server.ts
@@ -1,41 +1,48 @@
import { json, type RequestHandler } from '@sveltejs/kit';
import { NUTRITION_DB } from '$lib/data/nutritionDb';
import { BLS_DB } from '$lib/data/blsDb';
+import { fuzzyScore } from '$lib/js/fuzzy';
-/** GET: Search BLS + USDA nutrition databases by name substring */
+/** GET: Search BLS + USDA nutrition databases by fuzzy name match */
export const GET: RequestHandler = async ({ url }) => {
const q = (url.searchParams.get('q') || '').toLowerCase().trim();
if (q.length < 2) return json([]);
- const results: { source: 'bls' | 'usda'; id: string; name: string; category: string; calories: number }[] = [];
+ const scored: { source: 'bls' | 'usda'; id: string; name: string; category: string; calories: number; score: number }[] = [];
- // Search BLS first (primary)
+ // Search BLS (primary)
for (const entry of BLS_DB) {
- if (results.length >= 30) break;
- if (entry.nameDe.toLowerCase().includes(q) || entry.nameEn.toLowerCase().includes(q)) {
- results.push({
+ const scoreDe = fuzzyScore(q, entry.nameDe.toLowerCase());
+ const scoreEn = entry.nameEn ? fuzzyScore(q, entry.nameEn.toLowerCase()) : 0;
+ const best = Math.max(scoreDe, scoreEn);
+ if (best > 0) {
+ scored.push({
source: 'bls',
id: entry.blsCode,
name: `${entry.nameDe}${entry.nameEn ? ` (${entry.nameEn})` : ''}`,
category: entry.category,
calories: entry.per100g.calories,
+ score: best,
});
}
}
- // Then USDA
+ // Search USDA
for (const entry of NUTRITION_DB) {
- if (results.length >= 40) break;
- if (entry.name.toLowerCase().includes(q)) {
- results.push({
+ const s = fuzzyScore(q, entry.name.toLowerCase());
+ if (s > 0) {
+ scored.push({
source: 'usda',
id: String(entry.fdcId),
name: entry.name,
category: entry.category,
calories: entry.per100g.calories,
+ score: s,
});
}
}
- return json(results.slice(0, 30));
+ // Sort by score descending, return top 30 (without score field)
+ scored.sort((a, b) => b.score - a.score);
+ return json(scored.slice(0, 30).map(({ score, ...rest }) => rest));
};