feat(ingest): extract stated yield descriptions and servings count across recipes
This commit is contained in:
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user