feat(ingest): extract stated yield descriptions and servings count across recipes

This commit is contained in:
2026-08-17 18:15:50 -05:00
parent 130dc1d501
commit f6510c05ba
+34 -3
View File
@@ -506,16 +506,36 @@ export function parseDoclingBook(doclingJson, targetPages = null) {
textSources.push(...nextPage.texts);
}
const WORD_TO_NUMBER = {
"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10, "dozen": 12, "half": 0.5
};
function parseYieldServings(yieldText) {
if (!yieldText) return null;
const s = yieldText.replace(/^yields?:\s*/i, "").trim().toLowerCase();
const digitMatch = s.match(/^(\d+)/);
if (digitMatch) return parseInt(digitMatch[1], 10);
const wordMatch = s.match(/^(one|two|three|four|five|six|seven|eight|nine|ten|dozen|half)/);
if (wordMatch && WORD_TO_NUMBER[wordMatch[1]]) return WORD_TO_NUMBER[wordMatch[1]];
return null;
}
const steps = [];
let currentSectionPrefix = "";
let inProcedure = false;
let inChefNotes = false;
let yieldDescription = null;
const chefNotesList = [];
const narrativeTexts = [];
for (const textNode of textSources) {
const text = textNode.text.trim();
if (/^yields?:\s*/i.test(text)) {
yieldDescription = text;
continue;
}
// Match procedure section headers like "Dough Packet Procedure:", "Assembly Procedure:", "Procedure:"
if (/procedure:?$/i.test(text)) {
inProcedure = true;
@@ -552,27 +572,38 @@ export function parseDoclingBook(doclingJson, targetPages = null) {
const sumWeight = allItems.reduce((s, i) => s + (i.unit_id === "gram" ? i.quantity : 0), 0);
const yieldQuantity = totalYieldGrams || (sumWeight > 0 ? sumWeight : 1000);
const yieldServings = parseYieldServings(yieldDescription);
const allNotes = [...chefNotesList];
if (yieldDescription) {
allNotes.unshift(yieldDescription);
}
const summary = narrativeTexts.length > 0
? (yieldDescription ? `${yieldDescription}. ${narrativeTexts.join(" ")}` : narrativeTexts.join(" "))
: yieldDescription || null;
recipes.push({
id: slugId,
title,
page_no: pageNo,
chapter: chapterInfo.name,
summary: narrativeTexts.length > 0 ? narrativeTexts.join(" ") : null,
summary,
categories: [chapterInfo.category],
tags: ["pastry_chefs_little_black_book", chapterInfo.category, "classic"],
yield_quantity: Math.round(yieldQuantity * 100) / 100,
yield_unit_id: "gram",
yield_servings: null,
yield_servings: yieldServings,
yield_basis: "theoretical",
yield: {
quantity: Math.round(yieldQuantity * 100) / 100,
unit_id: "gram",
servings: yieldServings,
basis: "theoretical",
},
components: formattedComponents,
steps: steps.length > 0 ? steps : [{ id: "step_1", order: 1, instruction: "Prepare formulation according to standard pastry method.", equipment_ids: [] }],
notes: chefNotesList,
notes: allNotes,
shelf_life: parseShelfLife(chefNotesList),
});
}