import { parseQuantity } from "./parse-quantity"; const KNOWN_UNITS = new Set([ "cup", "cups", "c", "tablespoon", "tablespoons", "tbsp", "tbsps", "tbs", "teaspoon", "teaspoons", "tsp", "tsps", "gram", "grams", "g", "kilogram", "kilograms", "kg", "ounce", "ounces", "oz", "pound", "pounds", "lb", "lbs", "milliliter", "milliliters", "millilitre", "millilitres", "ml", "liter", "liters", "litre", "litres", "l", "pinch", "pinches", "dash", "dashes", "clove", "cloves", "slice", "slices", "piece", "pieces", "can", "cans", "jar", "jars", "package", "packages", "pkg", "bunch", "bunches", "sprig", "sprigs", "stick", "sticks", "quart", "quarts", "qt", "pint", "pints", "pt", "fl", // matches the first token of "fl oz" — handled below ]); // Leading numeric token: plain int/decimal, unicode fraction, mixed number ("1 1/2"), or // simple fraction ("1/2") — mirrors what parseQuantity already knows how to parse. const LEADING_NUMBER = /^\s*(\d+\s+\d+\s*\/\s*\d+|\d+\s*\/\s*\d+|\d+(?:\.\d+)?|[¼½¾⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞])\s*/; /** * Some AI-generated (or pasted/imported) ingredients arrive with the quantity baked into * the name itself (e.g. rawName: "2 cups flour", quantity/unit left empty) instead of the * expected separate fields. Detects a leading quantity (+ optional recognized unit) in * rawName and pulls it out, so the ingredient name shown to the user is just "flour", not * "2 cups flour". Never runs when an explicit quantity was already provided — an entry * with real quantity/unit fields is trusted as-is, this is only a fallback for the case * where the model (or a copy-pasted ingredient line) merged everything into the name. */ export function extractIngredientQuantity( rawName: string, quantity: string | undefined, unit: string | undefined ): { rawName: string; quantity: string | undefined; unit: string | undefined } { if (quantity !== undefined && quantity !== "") return { rawName, quantity, unit }; const match = rawName.match(LEADING_NUMBER); if (!match) return { rawName, quantity, unit }; const numberToken = match[1]!.trim(); const parsedQuantity = parseQuantity(numberToken); if (parsedQuantity === undefined) return { rawName, quantity, unit }; let rest = rawName.slice(match[0].length).trim(); if (!rest) return { rawName, quantity, unit }; // nothing left — not actually a name+quantity string let extractedUnit = unit; const unitMatch = rest.match(/^([a-zA-Z.]+)\s+(.*)$/); if (unitMatch && !extractedUnit) { const candidate = unitMatch[1]!.replace(/\.$/, "").toLowerCase(); if (candidate === "fl" && unitMatch[2]!.toLowerCase().startsWith("oz")) { extractedUnit = "fl oz"; rest = unitMatch[2]!.replace(/^oz\.?\s*/i, "").trim(); } else if (KNOWN_UNITS.has(candidate)) { extractedUnit = candidate; rest = unitMatch[2]!.trim(); } } if (!rest) return { rawName, quantity, unit }; // stripping the unit ate the whole string — bail out return { rawName: rest, quantity: parsedQuantity, unit: extractedUnit }; }