fix(ingest): resolve Docling stacked fraction OCR artifacts into clean measurements

This commit is contained in:
2026-08-17 18:10:27 -05:00
parent 63492445a9
commit 130dc1d501
+20 -10
View File
@@ -222,13 +222,23 @@ function parseMetricAmount(str) {
return null;
}
export function cleanFractionText(str) {
if (!str) return "";
return str
.replace(/[\r\n]+/g, " ")
.replace(/(\d+)\s*\/\s*\1\s*\/\s*(\d+)/g, (m, a, b) => `${a}/${b}`)
.replace(/(\d+)\s*\/\s*(\d+)/g, (m, a, b) => `${a}/${b}`)
.replace(/\s+/g, " ")
.trim();
}
function parseUsFallback(text, name) {
if (!text) {
if (/zest/i.test(name)) return { quantity: 1, unit_id: "each", notes: "Zest of 1" };
if (/vanilla bean/i.test(name)) return { quantity: 1, unit_id: "each", notes: "1 bean" };
return { quantity: 1, unit_id: "gram", notes: "To taste / as needed" };
}
const s = text.replace(/[\r\n\s]+/g, " ").trim().toLowerCase();
const s = cleanFractionText(text).toLowerCase();
if (s.includes("1/8") || s.includes("⅛")) return { quantity: 0.6, unit_id: "gram", notes: "⅛ tsp" };
if (s.includes("1/4") || s.includes("¼")) return { quantity: 1.25, unit_id: "gram", notes: "¼ tsp" };
if (s.includes("1/2") || s.includes("½")) return { quantity: 2.5, unit_id: "gram", notes: "½ tsp" };
@@ -244,23 +254,23 @@ function parseUsFallback(text, name) {
const ozMatch = s.match(/^([\d.]+)\s*oz$/);
if (ozMatch && parseFloat(ozMatch[1]) > 0) {
return { quantity: Math.round(parseFloat(ozMatch[1]) * 28.3495 * 100) / 100, unit_id: "gram", notes: text };
return { quantity: Math.round(parseFloat(ozMatch[1]) * 28.3495 * 100) / 100, unit_id: "gram", notes: cleanFractionText(text) };
}
return { quantity: 1, unit_id: "gram", notes: text };
return { quantity: 1, unit_id: "gram", notes: cleanFractionText(text) };
}
function cleanIngredientName(raw) {
let name = raw.trim().replace(/^[\s•\-\*]+/, "");
let cleaned = cleanFractionText(raw).replace(/^[\s•\-\*]+/, "");
let notes = null;
const parenMatch = name.match(/^([^(]+)\s*\(([^)]+)\)$/);
const parenMatch = cleaned.match(/^([^(]+)\s*\(([^)]+)\)$/);
if (parenMatch) {
name = parenMatch[1].trim();
cleaned = parenMatch[1].trim();
notes = parenMatch[2].trim();
}
return { name, notes };
return { name: cleaned, notes };
}
function inferIngredientId(name) {
@@ -397,9 +407,9 @@ export function parseDoclingBook(doclingJson, targetPages = null) {
const row = grid.get(r);
if (!row) continue;
const ingText = row.get(0) || "";
const metricText = row.get(1) || "";
const usText = row.get(2) || "";
const ingText = cleanFractionText(row.get(0) || "");
const metricText = cleanFractionText(row.get(1) || "");
const usText = cleanFractionText(row.get(2) || "");
if (/total weight/i.test(ingText)) {
const parsedTotal = parseMetricAmount(metricText) || parseMetricAmount(usText);