@4kk11/cooklang-sankey 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +37 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -146
- package/dist/index.d.ts +38 -146
- package/dist/index.js +36 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -141,20 +141,45 @@ var calculateNodeValues = (nodes, sortedNodeIds) => {
|
|
|
141
141
|
}
|
|
142
142
|
return valueMap;
|
|
143
143
|
};
|
|
144
|
+
var formatValue = (value) => {
|
|
145
|
+
if (!value) return "";
|
|
146
|
+
if (value.type === "number") {
|
|
147
|
+
const num = cooklang.getNumericValue(value);
|
|
148
|
+
return num !== null ? num.toString() : "";
|
|
149
|
+
} else if (value.type === "range") {
|
|
150
|
+
const rangeValue = value.value;
|
|
151
|
+
const startNum = cooklang.getNumericValue({ type: "number", value: rangeValue.start });
|
|
152
|
+
const endNum = cooklang.getNumericValue({ type: "number", value: rangeValue.end });
|
|
153
|
+
if (startNum !== null && endNum !== null) {
|
|
154
|
+
return `${startNum}-${endNum}`;
|
|
155
|
+
}
|
|
156
|
+
return "";
|
|
157
|
+
} else if (value.type === "text") {
|
|
158
|
+
return value.value;
|
|
159
|
+
}
|
|
160
|
+
return "";
|
|
161
|
+
};
|
|
162
|
+
var formatQuantityAmount = (quantity) => {
|
|
163
|
+
if (!quantity) {
|
|
164
|
+
return { quantity: "", unit: "" };
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
quantity: formatValue(quantity.value),
|
|
168
|
+
unit: quantity.unit || ""
|
|
169
|
+
};
|
|
170
|
+
};
|
|
144
171
|
var generateStepText = (step, recipe) => {
|
|
145
172
|
return step.items.map((item) => {
|
|
146
173
|
if (item.type === "text") {
|
|
147
174
|
return item.value;
|
|
148
175
|
} else if (item.type === "ingredient") {
|
|
149
|
-
const
|
|
150
|
-
if (!
|
|
151
|
-
const
|
|
152
|
-
const quantityText =
|
|
153
|
-
return `${name}${quantityText}`;
|
|
176
|
+
const originalIngredient = recipe.ingredients[item.index];
|
|
177
|
+
if (!originalIngredient) return "";
|
|
178
|
+
const formatted = formatQuantityAmount(originalIngredient.quantity);
|
|
179
|
+
const quantityText = formatted.quantity && formatted.unit ? `(${formatted.quantity}${formatted.unit})` : formatted.quantity ? `(${formatted.quantity})` : "";
|
|
180
|
+
return `${originalIngredient.name}${quantityText}`;
|
|
154
181
|
} else if (item.type === "cookware") {
|
|
155
|
-
|
|
156
|
-
if (!cookware) return "";
|
|
157
|
-
return cooklang.cookware_display_name(cookware);
|
|
182
|
+
return recipe.cookware[item.index]?.name || "";
|
|
158
183
|
} else if (item.type === "timer") {
|
|
159
184
|
const timer = recipe.timers[item.index];
|
|
160
185
|
if (!timer) return "";
|
|
@@ -440,40 +465,6 @@ var optimizeSankeyData = (data) => {
|
|
|
440
465
|
links: normalizedLinks
|
|
441
466
|
};
|
|
442
467
|
};
|
|
443
|
-
function parseRecipeForDisplay(recipe) {
|
|
444
|
-
const flatIngredients = cooklang.getFlatIngredients(recipe);
|
|
445
|
-
const ingredients = flatIngredients.map((ing) => ({
|
|
446
|
-
name: ing.name,
|
|
447
|
-
quantity: ing.quantity !== null ? String(ing.quantity) : "",
|
|
448
|
-
unit: ing.unit || ""
|
|
449
|
-
}));
|
|
450
|
-
const flatCookware = cooklang.getFlatCookware(recipe);
|
|
451
|
-
const cookware = flatCookware.map((cw) => ({
|
|
452
|
-
name: cw.name,
|
|
453
|
-
note: cw.note || ""
|
|
454
|
-
}));
|
|
455
|
-
const flatTimers = cooklang.getFlatTimers(recipe);
|
|
456
|
-
const timers = flatTimers.map((timer) => ({
|
|
457
|
-
name: timer.name || "",
|
|
458
|
-
quantity: timer.quantity !== null ? String(timer.quantity) : "",
|
|
459
|
-
unit: timer.unit || "minutes"
|
|
460
|
-
}));
|
|
461
|
-
const steps = [];
|
|
462
|
-
for (const section of recipe.sections) {
|
|
463
|
-
for (const content of section.content) {
|
|
464
|
-
if (content.type === "step") {
|
|
465
|
-
const stepText = generateStepText(content.value, recipe);
|
|
466
|
-
steps.push({ text: stepText });
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
return {
|
|
471
|
-
ingredients,
|
|
472
|
-
cookware,
|
|
473
|
-
timers,
|
|
474
|
-
steps
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
468
|
|
|
478
469
|
Object.defineProperty(exports, "CooklangParser", {
|
|
479
470
|
enumerable: true,
|
|
@@ -483,34 +474,19 @@ Object.defineProperty(exports, "CooklangRecipe", {
|
|
|
483
474
|
enumerable: true,
|
|
484
475
|
get: function () { return cooklang.CooklangRecipe; }
|
|
485
476
|
});
|
|
486
|
-
Object.defineProperty(exports, "
|
|
487
|
-
enumerable: true,
|
|
488
|
-
get: function () { return cooklang.cookware_display_name; }
|
|
489
|
-
});
|
|
490
|
-
Object.defineProperty(exports, "getFlatCookware", {
|
|
491
|
-
enumerable: true,
|
|
492
|
-
get: function () { return cooklang.getFlatCookware; }
|
|
493
|
-
});
|
|
494
|
-
Object.defineProperty(exports, "getFlatIngredients", {
|
|
495
|
-
enumerable: true,
|
|
496
|
-
get: function () { return cooklang.getFlatIngredients; }
|
|
497
|
-
});
|
|
498
|
-
Object.defineProperty(exports, "getFlatTimers", {
|
|
499
|
-
enumerable: true,
|
|
500
|
-
get: function () { return cooklang.getFlatTimers; }
|
|
501
|
-
});
|
|
502
|
-
Object.defineProperty(exports, "ingredient_display_name", {
|
|
477
|
+
Object.defineProperty(exports, "getNumericValue", {
|
|
503
478
|
enumerable: true,
|
|
504
|
-
get: function () { return cooklang.
|
|
479
|
+
get: function () { return cooklang.getNumericValue; }
|
|
505
480
|
});
|
|
506
481
|
Object.defineProperty(exports, "quantity_display", {
|
|
507
482
|
enumerable: true,
|
|
508
483
|
get: function () { return cooklang.quantity_display; }
|
|
509
484
|
});
|
|
510
485
|
exports.extractMetadata = extractMetadata;
|
|
486
|
+
exports.formatQuantityAmount = formatQuantityAmount;
|
|
487
|
+
exports.formatValue = formatValue;
|
|
511
488
|
exports.generateSankeyData = generateSankeyData;
|
|
512
489
|
exports.generateStepText = generateStepText;
|
|
513
490
|
exports.optimizeSankeyData = optimizeSankeyData;
|
|
514
|
-
exports.parseRecipeForDisplay = parseRecipeForDisplay;
|
|
515
491
|
//# sourceMappingURL=index.cjs.map
|
|
516
492
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/parser.ts","../src/constants.ts","../src/sankey/normalizer.ts","../src/sankey/dag.ts","../src/formatter.ts","../src/sankey/node-builders.ts","../src/sankey/edge-builders.ts","../src/sankey/generator.ts","../src/display.ts"],"names":["toposort","ingredient_display_name","quantity_display","cookware_display_name","grouped_quantity_is_empty","grouped_quantity_display","getNumericValue","getFlatIngredients","getFlatCookware","getFlatTimers"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Cooklang Parser wrapper using official @cooklang/cooklang package.\n *\n * @remarks\n * This module provides a thin wrapper around the official Cooklang parser,\n * re-exporting types and adding metadata extraction utilities.\n *\n * @packageDocumentation\n */\n\nimport {\n CooklangParser,\n CooklangRecipe,\n type Ingredient,\n type Quantity,\n type Section,\n type Step,\n type Content,\n type Item,\n type Value,\n} from \"@cooklang/cooklang\";\n\n// Re-export types for convenience\nexport type {\n CooklangParser,\n CooklangRecipe,\n Ingredient,\n Quantity,\n Section,\n Step,\n Content,\n Item,\n Value,\n};\n\n/**\n * Metadata extracted from a parsed Cooklang recipe.\n *\n * @remarks\n * Contains standard recipe metadata fields plus any custom metadata\n * defined in the recipe using the `>> key: value` syntax.\n */\nexport interface RecipeMetadata {\n /** Recipe title from `>> title:` */\n title?: string;\n /** Recipe description from `>> description:` */\n description?: string;\n /** Array of tags from `>> tags:` */\n tags?: string[];\n /** Formatted cooking time string (e.g., \"prep: 10min, cook: 30min\") */\n cookingTime?: string;\n /** Servings range or value (e.g., \"4\" or \"4-6\") */\n servings?: string;\n /** Additional custom metadata fields */\n [key: string]: unknown;\n}\n\n/**\n * Extracts metadata from a parsed Cooklang recipe.\n *\n * @param recipe - The parsed CooklangRecipe object\n * @returns An object containing extracted metadata fields\n *\n * @example\n * ```ts\n * import { CooklangParser } from \"@cooklang/cooklang\";\n * import { extractMetadata } from \"./parser\";\n *\n * const parser = new CooklangParser();\n * const [recipe] = parser.parse(`\n * >> title: Pasta Carbonara\n * >> servings: 4\n * @pasta{400g} を茹でる\n * `);\n *\n * const metadata = extractMetadata(recipe);\n * // { title: \"Pasta Carbonara\", servings: \"4\" }\n * ```\n */\nexport function extractMetadata(recipe: CooklangRecipe): RecipeMetadata {\n const result: RecipeMetadata = {};\n\n if (recipe.title) {\n result.title = recipe.title;\n }\n\n if (recipe.description) {\n result.description = recipe.description;\n }\n\n if (recipe.tags && recipe.tags.size > 0) {\n result.tags = Array.from(recipe.tags);\n }\n\n if (recipe.time) {\n // Format time object to string\n const timeValue = recipe.time;\n if (typeof timeValue === \"object\" && timeValue !== null) {\n const parts: string[] = [];\n if (\"prep_time\" in timeValue && timeValue.prep_time) {\n parts.push(`prep: ${timeValue.prep_time}`);\n }\n if (\"cook_time\" in timeValue && timeValue.cook_time) {\n parts.push(`cook: ${timeValue.cook_time}`);\n }\n if (\"total_time\" in timeValue && timeValue.total_time) {\n parts.push(`total: ${timeValue.total_time}`);\n }\n if (parts.length > 0) {\n result.cookingTime = parts.join(\", \");\n }\n }\n }\n\n if (recipe.servings) {\n const servings = recipe.servings;\n if (Array.isArray(servings)) {\n result.servings = servings.join(\"-\");\n } else if (typeof servings === \"object\" && servings !== null) {\n result.servings = String(servings);\n }\n }\n\n // Add custom metadata\n if (recipe.custom_metadata && recipe.custom_metadata.size > 0) {\n for (const [key, value] of recipe.custom_metadata) {\n if (!(key in result)) {\n result[key] = value;\n }\n }\n }\n\n return result;\n}\n","/**\n * Constants for Sankey diagram generation\n */\n\n/**\n * Normalization constants for value scaling\n */\nexport const NORMALIZATION = {\n /** Minimum normalized value for logarithmic scale */\n LOG_MIN: 0.1,\n /** Maximum normalized value for logarithmic scale */\n LOG_MAX: 0.3,\n /** Minimum normalized value for linear scale */\n LINEAR_MIN: 0.1,\n /** Range for linear normalization */\n LINEAR_RANGE: 0.2,\n /** Default value when normalization range is zero */\n DEFAULT_VALUE: 1,\n} as const;\n\n/**\n * Default options for Sankey generation\n */\nexport const DEFAULT_FINAL_NODE_NAME = \"完成品\";\nexport const DEFAULT_MIN_LINK_VALUE = 0.1;\nexport const DEFAULT_NORMALIZATION = \"logarithmic\" as const;\n\n/**\n * Node identifiers\n */\nexport const FINAL_NODE_ID = \"final_dish\";\nexport const DEFAULT_SECTION_NAME = \"main\";\n","/**\n * Value normalization utilities for Sankey diagram nodes.\n *\n * @remarks\n * Provides normalization functions to scale ingredient values for\n * balanced Sankey diagram visualization. Supports logarithmic, linear,\n * and no normalization modes.\n *\n * @packageDocumentation\n */\n\nimport { NORMALIZATION } from \"../constants\";\nimport type { SankeyNode } from \"../types/sankey\";\n\n/**\n * Normalizes ingredient node values using the specified method.\n *\n * @remarks\n * Returns a Map for O(1) lookup of normalized values by node ID.\n * - **logarithmic**: Compresses large value ranges using log10 scale\n * - **linear**: Scales values linearly within a fixed range\n * - **none**: Returns original values unchanged\n *\n * @param ingredientNodes - Array of ingredient nodes to normalize\n * @param normalization - The normalization method to apply\n * @returns A Map of node ID to normalized value\n *\n * @example\n * ```ts\n * const nodes: SankeyNode[] = [\n * { id: \"0\", name: \"flour\", value: 500, ... },\n * { id: \"1\", name: \"salt\", value: 5, ... }\n * ];\n *\n * const normalized = normalizeIngredientValues(nodes, \"logarithmic\");\n * // Map { \"0\" => 0.3, \"1\" => 0.1 }\n * ```\n */\nexport const normalizeIngredientValues = (\n ingredientNodes: SankeyNode[],\n normalization: \"logarithmic\" | \"linear\" | \"none\",\n): Map<string, number> => {\n const result = new Map<string, number>();\n\n if (normalization === \"none\") {\n ingredientNodes.forEach((node) => {\n result.set(node.id, node.value || NORMALIZATION.DEFAULT_VALUE);\n });\n return result;\n }\n\n const nodeValues = ingredientNodes.map((n) => n.value || NORMALIZATION.DEFAULT_VALUE);\n const minValue = Math.min(...nodeValues);\n const maxValue = Math.max(...nodeValues);\n const valueRange = maxValue - minValue;\n\n const normalizeValue = (value: number): number => {\n if (valueRange === 0) return NORMALIZATION.DEFAULT_VALUE;\n\n if (normalization === \"logarithmic\") {\n const normalizedLinear = (value - minValue) / valueRange;\n const logScale = Math.log10(1 + normalizedLinear * 9);\n return Math.min(Math.max(NORMALIZATION.LOG_MIN, logScale), NORMALIZATION.LOG_MAX);\n } else {\n return (\n NORMALIZATION.LINEAR_MIN + ((value - minValue) / valueRange) * NORMALIZATION.LINEAR_RANGE\n );\n }\n };\n\n ingredientNodes.forEach((node) => {\n result.set(node.id, node.value ? normalizeValue(node.value) : NORMALIZATION.DEFAULT_VALUE);\n });\n\n return result;\n};\n","/**\n * DAG (Directed Acyclic Graph) operations for Sankey diagram generation.\n *\n * @remarks\n * Provides topological sorting and value propagation algorithms\n * for computing flow values through the recipe graph.\n *\n * @packageDocumentation\n */\n\nimport toposort from \"toposort\";\nimport type { DAGNode, DAGEdge } from \"../types/sankey\";\n\n/**\n * Builds a DAG from nodes and edges, returning topologically sorted node IDs.\n *\n * @remarks\n * Uses Kahn's algorithm via the `toposort` library. If a cycle is detected,\n * falls back to returning node IDs in their original order.\n *\n * @param nodes - Array of DAG nodes\n * @param edges - Array of DAG edges defining dependencies\n * @returns Array of node IDs in topological order (dependencies before dependents)\n *\n * @example\n * ```ts\n * const nodes = [ingredientNode, stepNode, finalNode];\n * const edges = [\n * { from: ingredientNode.id, to: stepNode.id },\n * { from: stepNode.id, to: finalNode.id }\n * ];\n *\n * const sorted = buildDAGAndSort(nodes, edges);\n * // [ingredientNode.id, stepNode.id, finalNode.id]\n * ```\n */\nexport const buildDAGAndSort = (nodes: DAGNode[], edges: DAGEdge[]): string[] => {\n const edgePairs: Array<[string, string]> = edges.map((edge) => [edge.from, edge.to]);\n\n try {\n return toposort(edgePairs);\n } catch (error) {\n console.error(\"Cycle detected in DAG:\", error);\n return nodes.map((node) => node.id);\n }\n};\n\n/**\n * Calculates node values by propagating flow through the DAG.\n *\n * @remarks\n * Ingredient nodes retain their original values. Process and final nodes\n * accumulate values from their inputs. When a node has multiple outputs,\n * its value is split equally among them.\n *\n * @param nodes - Array of DAG nodes with input/output connections\n * @param sortedNodeIds - Node IDs in topological order\n * @returns A Map of node ID to calculated flow value\n *\n * @example\n * ```ts\n * // Given: flour(500) -> step1 -> final\n * // salt(5) -> step1\n * const values = calculateNodeValues(nodes, sortedIds);\n * // Map { \"flour\" => 500, \"salt\" => 5, \"step1\" => 505, \"final\" => 505 }\n * ```\n */\nexport const calculateNodeValues = (\n nodes: DAGNode[],\n sortedNodeIds: string[],\n): Map<string, number> => {\n const nodeMap = new Map<string, DAGNode>();\n const valueMap = new Map<string, number>();\n\n nodes.forEach((node) => {\n nodeMap.set(node.id, node);\n if (node.category === \"ingredient\") {\n valueMap.set(node.id, node.value);\n } else {\n valueMap.set(node.id, 0);\n }\n });\n\n for (const nodeId of sortedNodeIds) {\n const node = nodeMap.get(nodeId);\n if (!node) continue;\n\n if (node.category === \"ingredient\") {\n continue;\n }\n\n let totalInputValue = 0;\n for (const inputId of node.inputs) {\n let inputValue = valueMap.get(inputId) || 0;\n const inputNode = nodeMap.get(inputId);\n if (inputNode && inputNode.outputs.length > 1) {\n inputValue /= inputNode.outputs.length;\n }\n totalInputValue += inputValue;\n }\n\n valueMap.set(nodeId, totalInputValue);\n }\n\n return valueMap;\n};\n","/**\n * Step text generation utilities using official @cooklang/cooklang package.\n *\n * @remarks\n * Provides utilities for converting Cooklang step items into human-readable\n * string representations using official display functions.\n *\n * @packageDocumentation\n */\n\nimport {\n ingredient_display_name,\n cookware_display_name,\n quantity_display,\n type CooklangRecipe,\n type Step,\n} from \"@cooklang/cooklang\";\n\n/**\n * Generates complete text from step items by resolving references.\n *\n * @remarks\n * Converts a Step's items array into a readable string by:\n * - Keeping text items as-is\n * - Resolving ingredient references to \"name(quantity)\" format using official display functions\n * - Resolving cookware references to their display names\n * - Resolving timer references to their display values\n *\n * @param step - The Step object containing items to format\n * @param recipe - The parent CooklangRecipe for resolving references\n * @returns A concatenated string of all step items\n *\n * @example\n * ```ts\n * // For a step with text \"Cook \" + ingredient(pasta, 400g) + \" until done\"\n * generateStepText(step, recipe);\n * // \"Cook pasta(400 g) until done\"\n * ```\n */\nexport const generateStepText = (step: Step, recipe: CooklangRecipe): string => {\n return step.items\n .map((item) => {\n if (item.type === \"text\") {\n return item.value;\n } else if (item.type === \"ingredient\") {\n const ingredient = recipe.ingredients[item.index];\n if (!ingredient) return \"\";\n\n const name = ingredient_display_name(ingredient);\n const quantityText = ingredient.quantity\n ? `(${quantity_display(ingredient.quantity)})`\n : \"\";\n\n return `${name}${quantityText}`;\n } else if (item.type === \"cookware\") {\n const cookware = recipe.cookware[item.index];\n if (!cookware) return \"\";\n return cookware_display_name(cookware);\n } else if (item.type === \"timer\") {\n const timer = recipe.timers[item.index];\n if (!timer) return \"\";\n return timer.name || (timer.quantity ? quantity_display(timer.quantity) : \"\");\n }\n return \"\";\n })\n .join(\"\");\n};\n\n// Re-export useful functions from official package\nexport { ingredient_display_name, cookware_display_name, quantity_display };\n","/**\n * Node builders for Sankey diagram generation.\n *\n * @remarks\n * Factory functions for creating DAG nodes from Cooklang recipe elements.\n * Handles ingredients, process steps, and the final dish node.\n *\n * @packageDocumentation\n */\n\nimport {\n type CooklangRecipe,\n getNumericValue,\n grouped_quantity_display,\n grouped_quantity_is_empty,\n type Quantity,\n} from \"@cooklang/cooklang\";\nimport { generateStepText } from \"../formatter\";\nimport { DEFAULT_SECTION_NAME, FINAL_NODE_ID } from \"../constants\";\nimport type { DAGNode, NodeCategory, ExtractedValue } from \"../types/sankey\";\n\n/**\n * Extracts a numeric or text value from a Cooklang GroupedQuantity.\n *\n * @remarks\n * Attempts to extract a numeric value from fixed, scalable, or unknown\n * quantity types. Falls back to text representation if no numeric value\n * is available.\n *\n * @param groupedQuantity - The GroupedQuantity object from Cooklang parser\n * @returns An ExtractedValue with either numeric value or text label\n *\n * @example\n * ```ts\n * // For \"200g\" quantity\n * calculateIngredientQuantity(gq);\n * // { type: \"number\", value: 200, label: \"200g\" }\n *\n * // For \"some\" quantity\n * calculateIngredientQuantity(gq);\n * // { type: \"text\", label: \"some\" }\n * ```\n */\nexport const calculateIngredientQuantity = (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n groupedQuantity: any,\n): ExtractedValue => {\n if (grouped_quantity_is_empty(groupedQuantity)) {\n return { type: \"text\", label: \"unknown\" };\n }\n\n const displayText = grouped_quantity_display(groupedQuantity);\n\n const gq = groupedQuantity as {\n fixed?: Quantity | null;\n scalable?: Quantity | null;\n fixed_unknown?: Quantity | null;\n scalable_unknown?: Quantity | null;\n };\n\n const quantityToUse = gq.fixed || gq.scalable || gq.fixed_unknown || gq.scalable_unknown;\n\n if (quantityToUse) {\n const numericValue = getNumericValue(quantityToUse.value);\n if (numericValue !== null) {\n return { type: \"number\", value: numericValue, label: displayText || `${numericValue}` };\n }\n }\n\n return { type: \"text\", label: displayText || \"unknown\" };\n};\n\n/**\n * Creates DAG nodes for all ingredients in a recipe.\n *\n * @remarks\n * Processes the recipe's grouped ingredients to create nodes with:\n * - Unique IDs based on ingredient index\n * - Extracted quantity values for flow calculation\n * - Display labels with quantity information\n *\n * @param recipe - The parsed CooklangRecipe\n * @returns An object containing:\n * - `nodes`: Array of ingredient DAGNodes\n * - `indexMap`: Map from original ingredient index to node ID\n *\n * @example\n * ```ts\n * const { nodes, indexMap } = createIngredientNodes(recipe);\n * // nodes: [{ id: \"0\", name: \"flour\", value: 500, ... }, ...]\n * // indexMap: Map { 0 => \"0\", 1 => \"1\", ... }\n * ```\n */\nexport const createIngredientNodes = (\n recipe: CooklangRecipe,\n): { nodes: DAGNode[]; indexMap: Map<number, string> } => {\n const nodes: DAGNode[] = [];\n const indexMap = new Map<number, string>();\n\n recipe.groupedIngredients.forEach(([ingredient, groupedQuantity], index) => {\n const extracted = calculateIngredientQuantity(groupedQuantity);\n const value = extracted.type === \"number\" ? extracted.value : 1;\n const label = extracted.label;\n const ingredientId = index.toString();\n\n indexMap.set(index, ingredientId);\n\n nodes.push({\n id: ingredientId,\n name: ingredient.name,\n category: \"ingredient\" as NodeCategory,\n value,\n originalValue: value,\n label,\n inputs: [],\n outputs: [],\n metadata: {\n originalIndex: index,\n },\n });\n });\n\n return { nodes, indexMap };\n};\n\n/**\n * Creates DAG nodes for all cooking steps in a recipe.\n *\n * @remarks\n * Iterates through all sections and steps, creating process nodes with:\n * - Unique IDs based on section name and step number\n * - Generated text from step items (ingredients, cookware, timers)\n * - Initial value of 0 (calculated later via flow propagation)\n *\n * @param recipe - The parsed CooklangRecipe\n * @returns Array of process DAGNodes representing cooking steps\n *\n * @example\n * ```ts\n * const stepNodes = createStepNodes(recipe);\n * // [{ id: \"step_main_1\", name: \"Boil pasta(400g)\", category: \"process\", ... }]\n * ```\n */\nexport const createStepNodes = (recipe: CooklangRecipe): DAGNode[] => {\n const nodes: DAGNode[] = [];\n\n for (const section of recipe.sections) {\n for (const content of section.content) {\n if (content.type === \"step\") {\n const step = content.value;\n const stepText = generateStepText(step, recipe);\n const stepId = `step_${section.name || DEFAULT_SECTION_NAME}_${step.number}`;\n\n nodes.push({\n id: stepId,\n name: stepText || `手順 ${step.number}`,\n category: \"process\" as NodeCategory,\n value: 0,\n label: `${step.number}`,\n inputs: [],\n outputs: [],\n metadata: {\n stepNumber: step.number,\n sectionName: section.name || undefined,\n },\n });\n }\n }\n }\n\n return nodes;\n};\n\n/**\n * Creates the final node representing the completed dish.\n *\n * @remarks\n * The final node is the sink of the Sankey diagram where all\n * process flows converge. Its value is calculated by summing\n * all incoming flows.\n *\n * @param finalNodeName - Display name for the final dish node\n * @returns A DAGNode with category \"final\"\n *\n * @example\n * ```ts\n * const finalNode = createFinalNode(\"Carbonara\");\n * // { id: \"final_dish\", name: \"Carbonara\", category: \"final\", ... }\n * ```\n */\nexport const createFinalNode = (finalNodeName: string): DAGNode => ({\n id: FINAL_NODE_ID,\n name: finalNodeName,\n category: \"final\" as NodeCategory,\n value: 0,\n label: \"\",\n inputs: [],\n outputs: [],\n});\n","/**\n * Edge builders for Sankey diagram generation.\n *\n * @remarks\n * Factory functions for creating DAG edges that connect nodes.\n * Handles ingredient-to-step and step-to-final connections,\n * including step reference resolution.\n *\n * @packageDocumentation\n */\n\nimport { type CooklangRecipe, type Step } from \"@cooklang/cooklang\";\nimport { DEFAULT_SECTION_NAME } from \"../constants\";\nimport type { DAGNode, DAGEdge, TransformationType } from \"../types/sankey\";\n\n/**\n * Generates a unique step ID from section name and step number.\n *\n * @param sectionName - The recipe section name (null uses default)\n * @param stepNumber - The 1-indexed step number\n * @returns A unique step ID string (e.g., \"step_main_1\")\n *\n * @example\n * ```ts\n * getStepId(\"main\", 1); // \"step_main_1\"\n * getStepId(null, 2); // \"step_main_2\"\n * getStepId(\"sauce\", 1); // \"step_sauce_1\"\n * ```\n */\nexport const getStepId = (sectionName: string | null | undefined, stepNumber: number): string => {\n return `step_${sectionName || DEFAULT_SECTION_NAME}_${stepNumber}`;\n};\n\n/**\n * Extracts ingredient usage information from a recipe step.\n *\n * @param step - The Step object to analyze\n * @returns Array of objects containing ingredient index and step number\n *\n * @example\n * ```ts\n * // For a step using @pasta and @salt\n * extractIngredientUsageFromStep(step);\n * // [{ ingredientIndex: 0, stepNumber: 1 }, { ingredientIndex: 1, stepNumber: 1 }]\n * ```\n */\nexport const extractIngredientUsageFromStep = (\n step: Step,\n): Array<{ ingredientIndex: number; stepNumber: number }> => {\n const usages: Array<{ ingredientIndex: number; stepNumber: number }> = [];\n\n for (const item of step.items) {\n if (item.type === \"ingredient\") {\n usages.push({\n ingredientIndex: item.index,\n stepNumber: step.number,\n });\n }\n }\n\n return usages;\n};\n\n/**\n * Builds edges connecting ingredients to their consuming steps.\n *\n * @remarks\n * Analyzes the recipe to create edges from:\n * - Ingredient nodes to step nodes (for direct ingredient usage)\n * - Step nodes to other step nodes (for step references like \"the pasta from step 1\")\n *\n * Also updates the input/output arrays on the nodes for DAG traversal.\n *\n * @param recipe - The parsed CooklangRecipe\n * @param ingredientIndexMap - Map from ingredient index to node ID\n * @param nodes - Array of all DAG nodes (modified in place)\n * @returns Array of DAGEdges connecting ingredients/steps to steps\n *\n * @example\n * ```ts\n * const edges = buildIngredientToStepEdges(recipe, indexMap, nodes);\n * // [{ from: \"0\", to: \"step_main_1\", metadata: { ... } }, ...]\n * ```\n */\nexport const buildIngredientToStepEdges = (\n recipe: CooklangRecipe,\n ingredientIndexMap: Map<number, string>,\n nodes: DAGNode[],\n): DAGEdge[] => {\n const edges: DAGEdge[] = [];\n const nodeMap = new Map<string, DAGNode>();\n\n nodes.forEach((node) => nodeMap.set(node.id, node));\n\n for (const section of recipe.sections) {\n for (const content of section.content) {\n if (content.type === \"step\") {\n const step = content.value;\n const currentStepId = getStepId(section.name, step.number);\n const usedIngredients = extractIngredientUsageFromStep(step);\n\n for (const usage of usedIngredients) {\n const ingredient = recipe.ingredients[usage.ingredientIndex];\n\n // Check for step reference\n const relation = ingredient.relation;\n if (\n relation &&\n relation.relation?.type === \"reference\" &&\n relation.reference_target === \"step\"\n ) {\n const referenceTo = relation.relation.references_to;\n const sourceStepId = getStepId(section.name, referenceTo + 1);\n edges.push({\n from: sourceStepId,\n to: currentStepId,\n metadata: {\n stepNumber: step.number,\n transformationType: \"cooking\" as TransformationType,\n },\n });\n\n const sourceNode = nodeMap.get(sourceStepId);\n const targetNode = nodeMap.get(currentStepId);\n if (sourceNode && targetNode) {\n sourceNode.outputs.push(currentStepId);\n targetNode.inputs.push(sourceStepId);\n }\n continue;\n }\n\n const ingredientId = ingredientIndexMap.get(usage.ingredientIndex);\n if (ingredientId) {\n edges.push({\n from: ingredientId,\n to: currentStepId,\n metadata: {\n stepNumber: step.number,\n transformationType: \"preparation\" as TransformationType,\n },\n });\n\n const ingredientNode = nodeMap.get(ingredientId);\n const stepNode = nodeMap.get(currentStepId);\n if (ingredientNode && stepNode) {\n ingredientNode.outputs.push(currentStepId);\n stepNode.inputs.push(ingredientId);\n }\n }\n }\n }\n }\n }\n\n return edges;\n};\n\n/**\n * Builds an edge from the last step to the final dish node.\n *\n * @remarks\n * Connects only the last step to the final node, representing\n * the completion of the recipe. Also updates the input/output\n * arrays on the affected nodes.\n *\n * @param stepNodes - Array of step nodes in order\n * @param finalNode - The final dish node (modified in place)\n * @returns Array containing a single edge to the final node, or empty if no steps\n *\n * @example\n * ```ts\n * const edges = buildStepToFinalEdges(stepNodes, finalNode);\n * // [{ from: \"step_main_3\", to: \"final_dish\", metadata: { transformationType: \"completion\" } }]\n * ```\n */\nexport const buildStepToFinalEdges = (stepNodes: DAGNode[], finalNode: DAGNode): DAGEdge[] => {\n if (stepNodes.length === 0) return [];\n\n const lastStepNode = stepNodes[stepNodes.length - 1];\n\n lastStepNode.outputs.push(finalNode.id);\n finalNode.inputs.push(lastStepNode.id);\n\n return [\n {\n from: lastStepNode.id,\n to: finalNode.id,\n metadata: {\n transformationType: \"completion\" as TransformationType,\n },\n },\n ];\n};\n","/**\n * Sankey diagram data generator from Cooklang recipes.\n *\n * @remarks\n * Main module for transforming parsed Cooklang recipes into\n * Sankey diagram data structures. Orchestrates the DAG building,\n * value calculation, and normalization pipeline.\n *\n * @packageDocumentation\n */\n\nimport type { CooklangRecipe } from \"@cooklang/cooklang\";\nimport {\n DEFAULT_FINAL_NODE_NAME,\n DEFAULT_MIN_LINK_VALUE,\n DEFAULT_NORMALIZATION,\n} from \"../constants\";\nimport type { SankeyNode, SankeyLink, SankeyData, DAGNode, DAGEdge } from \"../types/sankey\";\nimport { normalizeIngredientValues } from \"./normalizer\";\nimport { buildDAGAndSort, calculateNodeValues } from \"./dag\";\nimport { createIngredientNodes, createStepNodes, createFinalNode } from \"./node-builders\";\nimport { buildIngredientToStepEdges, buildStepToFinalEdges } from \"./edge-builders\";\n\n/**\n * Configuration options for Sankey diagram generation.\n *\n * @example\n * ```ts\n * const options: SankeyGeneratorOptions = {\n * finalNodeName: \"Carbonara\",\n * normalization: \"logarithmic\",\n * minLinkValue: 0.1\n * };\n * ```\n */\nexport interface SankeyGeneratorOptions {\n /**\n * Display name for the final dish node.\n * @defaultValue \"完成品\"\n */\n finalNodeName?: string;\n /**\n * Method for normalizing ingredient values.\n * - `logarithmic`: Compress large ranges (recommended)\n * - `linear`: Linear scaling\n * - `none`: Use raw values\n * @defaultValue \"logarithmic\"\n */\n normalization?: \"logarithmic\" | \"linear\" | \"none\";\n /**\n * Minimum value for links to ensure visibility.\n * @defaultValue 0.1\n */\n minLinkValue?: number;\n}\n\nconst DEFAULT_OPTIONS: Required<SankeyGeneratorOptions> = {\n finalNodeName: DEFAULT_FINAL_NODE_NAME,\n normalization: DEFAULT_NORMALIZATION,\n minLinkValue: DEFAULT_MIN_LINK_VALUE,\n};\n\n/**\n * Transforms DAG nodes and edges into final Sankey diagram data.\n *\n * @remarks\n * Internal function that:\n * 1. Normalizes ingredient values using the specified method\n * 2. Calculates flow values for all nodes via DAG propagation\n * 3. Converts DAG structures to Sankey-compatible format\n * 4. Applies minimum value constraints to links\n *\n * @param dagNodes - Array of DAG nodes with dependency info\n * @param dagEdges - Array of DAG edges\n * @param sortedNodeIds - Node IDs in topological order\n * @param options - Generation options with all defaults applied\n * @returns Complete SankeyData structure ready for visualization\n *\n * @internal\n */\nconst buildSankeyData = (\n dagNodes: DAGNode[],\n dagEdges: DAGEdge[],\n sortedNodeIds: string[],\n options: Required<SankeyGeneratorOptions>,\n): SankeyData => {\n const ingredientNodes = dagNodes\n .filter((node) => node.category === \"ingredient\")\n .map((node) => ({\n id: node.id,\n name: node.name,\n category: node.category,\n value: node.value,\n label: node.label,\n originalValue: node.originalValue,\n metadata: node.metadata,\n }));\n\n const normalizedValueMap = normalizeIngredientValues(ingredientNodes, options.normalization);\n\n const normalizedDAGNodes = dagNodes.map((node) => {\n if (node.category === \"ingredient\") {\n return {\n ...node,\n value: normalizedValueMap.get(node.id) ?? node.value,\n };\n }\n return node;\n });\n\n const finalCalculatedValues = calculateNodeValues(normalizedDAGNodes, sortedNodeIds);\n\n const sankeyNodes: SankeyNode[] = dagNodes.map((dagNode) => {\n const finalValue = finalCalculatedValues.get(dagNode.id) || 0;\n return {\n id: dagNode.id,\n name: dagNode.name,\n category: dagNode.category,\n value:\n dagNode.category === \"ingredient\"\n ? (normalizedValueMap.get(dagNode.id) ?? dagNode.value)\n : finalValue,\n label: dagNode.label,\n originalValue: dagNode.originalValue,\n metadata: dagNode.metadata,\n };\n });\n\n const sankeyLinks: SankeyLink[] = dagEdges.map((edge) => {\n const sourceValue = finalCalculatedValues.get(edge.from) || 0;\n const sourceNode = dagNodes.find((n) => n.id === edge.from);\n\n let linkValue = sourceValue;\n if (sourceNode && sourceNode.outputs.length > 1) {\n linkValue = sourceValue / sourceNode.outputs.length;\n }\n\n return {\n source: edge.from,\n target: edge.to,\n value: Math.max(options.minLinkValue, linkValue),\n originalValue: sourceValue,\n metadata: edge.metadata,\n };\n });\n\n return {\n nodes: sankeyNodes,\n links: sankeyLinks,\n };\n};\n\n/**\n * Generates Sankey diagram data from a parsed Cooklang recipe.\n *\n * @remarks\n * This is the main entry point for the library. It transforms a parsed\n * Cooklang recipe into a Sankey diagram data structure by:\n * 1. Creating nodes for ingredients, steps, and the final dish\n * 2. Building edges representing ingredient flow\n * 3. Performing topological sort for correct value propagation\n * 4. Normalizing values for balanced visualization\n *\n * @param recipe - A parsed CooklangRecipe object\n * @param options - Optional configuration for generation\n * @returns SankeyData structure, or null if generation fails\n *\n * @example\n * ```ts\n * import { CooklangParser, generateSankeyData } from 'cooklang-sankey';\n *\n * const parser = new CooklangParser();\n * const [recipe] = parser.parse(`\n * @pasta{400g} を茹でる。\n * @卵{3個}と@チーズ{100g}を混ぜる。\n * `);\n *\n * const data = generateSankeyData(recipe, {\n * finalNodeName: \"Carbonara\",\n * normalization: \"logarithmic\"\n * });\n * ```\n */\nexport const generateSankeyData = (\n recipe: CooklangRecipe,\n options?: SankeyGeneratorOptions,\n): SankeyData | null => {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n try {\n const { nodes: ingredientNodes, indexMap: ingredientIndexMap } = createIngredientNodes(recipe);\n\n const stepNodes = createStepNodes(recipe);\n const finalNode = createFinalNode(opts.finalNodeName);\n\n const allNodes = [...ingredientNodes, ...stepNodes, finalNode];\n\n const ingredientToStepEdges = buildIngredientToStepEdges(recipe, ingredientIndexMap, allNodes);\n const stepToFinalEdges = buildStepToFinalEdges(stepNodes, finalNode);\n const allEdges = [...ingredientToStepEdges, ...stepToFinalEdges];\n\n const sortedNodeIds = buildDAGAndSort(allNodes, allEdges);\n\n return buildSankeyData(allNodes, allEdges, sortedNodeIds, opts);\n } catch (error) {\n console.error(\"Error generating sankey data from cooklang:\", error);\n return null;\n }\n};\n\n/**\n * Validates and optimizes Sankey data for visualization.\n *\n * @remarks\n * Performs three optimization steps:\n * 1. **Remove invalid links**: Filters out links referencing non-existent nodes\n * 2. **Remove orphaned nodes**: Keeps only nodes connected to valid links\n * (final nodes are always preserved)\n * 3. **Normalize link values**: Scales values relative to minimum for\n * stable visualization (ensures all values >= 1)\n *\n * @param data - The SankeyData to optimize\n * @returns A new SankeyData with optimizations applied\n *\n * @example\n * ```ts\n * const rawData = generateSankeyData(recipe);\n * if (rawData) {\n * const optimized = optimizeSankeyData(rawData);\n * // Use optimized data for visualization\n * }\n * ```\n */\nexport const optimizeSankeyData = (data: SankeyData): SankeyData => {\n // Create a Set of existing node IDs for O(1) lookup\n const existingNodeIds = new Set(data.nodes.map((node) => node.id));\n\n // Step 1: Remove invalid links\n // Filter out links where source or target node doesn't exist\n const validLinks = data.links.filter((link) => {\n const sourceExists = existingNodeIds.has(link.source);\n const targetExists = existingNodeIds.has(link.target);\n\n if (!sourceExists) {\n console.error(`Missing source node: ${link.source}`);\n }\n if (!targetExists) {\n console.error(`Missing target node: ${link.target}`);\n }\n\n return sourceExists && targetExists;\n });\n\n // Step 2: Remove orphaned nodes\n // Collect node IDs that are connected to valid links\n const connectedNodeIds = new Set<string>();\n validLinks.forEach((link) => {\n connectedNodeIds.add(link.source);\n connectedNodeIds.add(link.target);\n });\n\n // Keep only nodes connected to links, or final nodes (always preserved)\n const filteredNodes = data.nodes.filter(\n (node) => connectedNodeIds.has(node.id) || node.category === \"final\",\n );\n\n // Step 3: Normalize link values\n // Scale values relative to minimum for stable visualization\n let normalizedLinks = validLinks;\n if (validLinks.length > 0) {\n const minValue = Math.min(...validLinks.map((link) => link.value));\n if (minValue > 0) {\n // Divide by min value and clamp to ensure value >= 1\n normalizedLinks = validLinks.map((link) => ({\n ...link,\n value: Math.max(1, link.value / minValue),\n }));\n }\n }\n\n return {\n nodes: filteredNodes,\n links: normalizedLinks,\n };\n};\n","/**\n * Recipe display utilities for UI rendering.\n *\n * @remarks\n * Provides utilities for converting parsed CooklangRecipe objects into\n * display-ready data structures suitable for UI rendering.\n *\n * Uses the official @cooklang/cooklang package's convenience functions\n * for accurate quantity aggregation and formatting.\n *\n * @packageDocumentation\n */\n\nimport {\n type CooklangRecipe,\n getFlatIngredients,\n getFlatCookware,\n getFlatTimers,\n} from \"@cooklang/cooklang\";\nimport { generateStepText } from \"./formatter\";\nimport type {\n DisplayRecipe,\n DisplayIngredient,\n DisplayCookware,\n DisplayTimer,\n DisplayStep,\n} from \"./types/display\";\n\n/**\n * Parses a CooklangRecipe into a display-ready format.\n *\n * @remarks\n * Uses the official @cooklang/cooklang package's convenience functions\n * (`getFlatIngredients`, `getFlatCookware`, `getFlatTimers`) for proper\n * handling of grouped quantities.\n *\n * @param recipe - The parsed CooklangRecipe object\n * @returns DisplayRecipe with formatted data for UI rendering\n *\n * @example\n * ```ts\n * import { CooklangParser, parseRecipeForDisplay } from '@4kk11/cooklang-sankey';\n *\n * const parser = new CooklangParser();\n * const [recipe] = parser.parse(recipeText);\n * const displayData = parseRecipeForDisplay(recipe);\n *\n * // Render ingredients\n * displayData.ingredients.forEach(ing => {\n * console.log(`${ing.name}: ${ing.quantity}${ing.unit}`);\n * });\n *\n * // Render steps\n * displayData.steps.forEach((step, i) => {\n * console.log(`${i + 1}. ${step.text}`);\n * });\n * ```\n */\nexport function parseRecipeForDisplay(recipe: CooklangRecipe): DisplayRecipe {\n // Use getFlatIngredients which properly handles grouped quantities\n const flatIngredients = getFlatIngredients(recipe);\n const ingredients: DisplayIngredient[] = flatIngredients.map((ing) => ({\n name: ing.name,\n quantity: ing.quantity !== null ? String(ing.quantity) : \"\",\n unit: ing.unit || \"\",\n }));\n\n // Use getFlatCookware for cookware\n const flatCookware = getFlatCookware(recipe);\n const cookware: DisplayCookware[] = flatCookware.map((cw) => ({\n name: cw.name,\n note: cw.note || \"\",\n }));\n\n // Use getFlatTimers for timers\n const flatTimers = getFlatTimers(recipe);\n const timers: DisplayTimer[] = flatTimers.map((timer) => ({\n name: timer.name || \"\",\n quantity: timer.quantity !== null ? String(timer.quantity) : \"\",\n unit: timer.unit || \"minutes\",\n }));\n\n // Generate step texts\n const steps: DisplayStep[] = [];\n for (const section of recipe.sections) {\n for (const content of section.content) {\n if (content.type === \"step\") {\n const stepText = generateStepText(content.value, recipe);\n steps.push({ text: stepText });\n }\n }\n }\n\n return {\n ingredients,\n cookware,\n timers,\n steps,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/parser.ts","../src/constants.ts","../src/sankey/normalizer.ts","../src/sankey/dag.ts","../src/formatter.ts","../src/sankey/node-builders.ts","../src/sankey/edge-builders.ts","../src/sankey/generator.ts"],"names":["toposort","getNumericValue","quantity_display","grouped_quantity_is_empty","grouped_quantity_display"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Cooklang Parser wrapper using official @cooklang/cooklang package.\n *\n * @remarks\n * This module provides a thin wrapper around the official Cooklang parser,\n * re-exporting types and adding metadata extraction utilities.\n *\n * @packageDocumentation\n */\n\nimport {\n CooklangParser,\n CooklangRecipe,\n type Ingredient,\n type Quantity,\n type Section,\n type Step,\n type Content,\n type Item,\n type Value,\n} from \"@cooklang/cooklang\";\n\n// Re-export types for convenience\nexport type {\n CooklangParser,\n CooklangRecipe,\n Ingredient,\n Quantity,\n Section,\n Step,\n Content,\n Item,\n Value,\n};\n\n/**\n * Metadata extracted from a parsed Cooklang recipe.\n *\n * @remarks\n * Contains standard recipe metadata fields plus any custom metadata\n * defined in the recipe using the `>> key: value` syntax.\n */\nexport interface RecipeMetadata {\n /** Recipe title from `>> title:` */\n title?: string;\n /** Recipe description from `>> description:` */\n description?: string;\n /** Array of tags from `>> tags:` */\n tags?: string[];\n /** Formatted cooking time string (e.g., \"prep: 10min, cook: 30min\") */\n cookingTime?: string;\n /** Servings range or value (e.g., \"4\" or \"4-6\") */\n servings?: string;\n /** Additional custom metadata fields */\n [key: string]: unknown;\n}\n\n/**\n * Extracts metadata from a parsed Cooklang recipe.\n *\n * @param recipe - The parsed CooklangRecipe object\n * @returns An object containing extracted metadata fields\n *\n * @example\n * ```ts\n * import { CooklangParser } from \"@cooklang/cooklang\";\n * import { extractMetadata } from \"./parser\";\n *\n * const parser = new CooklangParser();\n * const [recipe] = parser.parse(`\n * >> title: Pasta Carbonara\n * >> servings: 4\n * @pasta{400g} を茹でる\n * `);\n *\n * const metadata = extractMetadata(recipe);\n * // { title: \"Pasta Carbonara\", servings: \"4\" }\n * ```\n */\nexport function extractMetadata(recipe: CooklangRecipe): RecipeMetadata {\n const result: RecipeMetadata = {};\n\n if (recipe.title) {\n result.title = recipe.title;\n }\n\n if (recipe.description) {\n result.description = recipe.description;\n }\n\n if (recipe.tags && recipe.tags.size > 0) {\n result.tags = Array.from(recipe.tags);\n }\n\n if (recipe.time) {\n // Format time object to string\n const timeValue = recipe.time;\n if (typeof timeValue === \"object\" && timeValue !== null) {\n const parts: string[] = [];\n if (\"prep_time\" in timeValue && timeValue.prep_time) {\n parts.push(`prep: ${timeValue.prep_time}`);\n }\n if (\"cook_time\" in timeValue && timeValue.cook_time) {\n parts.push(`cook: ${timeValue.cook_time}`);\n }\n if (\"total_time\" in timeValue && timeValue.total_time) {\n parts.push(`total: ${timeValue.total_time}`);\n }\n if (parts.length > 0) {\n result.cookingTime = parts.join(\", \");\n }\n }\n }\n\n if (recipe.servings) {\n const servings = recipe.servings;\n if (Array.isArray(servings)) {\n result.servings = servings.join(\"-\");\n } else if (typeof servings === \"object\" && servings !== null) {\n result.servings = String(servings);\n }\n }\n\n // Add custom metadata\n if (recipe.custom_metadata && recipe.custom_metadata.size > 0) {\n for (const [key, value] of recipe.custom_metadata) {\n if (!(key in result)) {\n result[key] = value;\n }\n }\n }\n\n return result;\n}\n","/**\n * Constants for Sankey diagram generation\n */\n\n/**\n * Normalization constants for value scaling\n */\nexport const NORMALIZATION = {\n /** Minimum normalized value for logarithmic scale */\n LOG_MIN: 0.1,\n /** Maximum normalized value for logarithmic scale */\n LOG_MAX: 0.3,\n /** Minimum normalized value for linear scale */\n LINEAR_MIN: 0.1,\n /** Range for linear normalization */\n LINEAR_RANGE: 0.2,\n /** Default value when normalization range is zero */\n DEFAULT_VALUE: 1,\n} as const;\n\n/**\n * Default options for Sankey generation\n */\nexport const DEFAULT_FINAL_NODE_NAME = \"完成品\";\nexport const DEFAULT_MIN_LINK_VALUE = 0.1;\nexport const DEFAULT_NORMALIZATION = \"logarithmic\" as const;\n\n/**\n * Node identifiers\n */\nexport const FINAL_NODE_ID = \"final_dish\";\nexport const DEFAULT_SECTION_NAME = \"main\";\n","/**\n * Value normalization utilities for Sankey diagram nodes.\n *\n * @remarks\n * Provides normalization functions to scale ingredient values for\n * balanced Sankey diagram visualization. Supports logarithmic, linear,\n * and no normalization modes.\n *\n * @packageDocumentation\n */\n\nimport { NORMALIZATION } from \"../constants\";\nimport type { SankeyNode } from \"../types/sankey\";\n\n/**\n * Normalizes ingredient node values using the specified method.\n *\n * @remarks\n * Returns a Map for O(1) lookup of normalized values by node ID.\n * - **logarithmic**: Compresses large value ranges using log10 scale\n * - **linear**: Scales values linearly within a fixed range\n * - **none**: Returns original values unchanged\n *\n * @param ingredientNodes - Array of ingredient nodes to normalize\n * @param normalization - The normalization method to apply\n * @returns A Map of node ID to normalized value\n *\n * @example\n * ```ts\n * const nodes: SankeyNode[] = [\n * { id: \"0\", name: \"flour\", value: 500, ... },\n * { id: \"1\", name: \"salt\", value: 5, ... }\n * ];\n *\n * const normalized = normalizeIngredientValues(nodes, \"logarithmic\");\n * // Map { \"0\" => 0.3, \"1\" => 0.1 }\n * ```\n */\nexport const normalizeIngredientValues = (\n ingredientNodes: SankeyNode[],\n normalization: \"logarithmic\" | \"linear\" | \"none\",\n): Map<string, number> => {\n const result = new Map<string, number>();\n\n if (normalization === \"none\") {\n ingredientNodes.forEach((node) => {\n result.set(node.id, node.value || NORMALIZATION.DEFAULT_VALUE);\n });\n return result;\n }\n\n const nodeValues = ingredientNodes.map((n) => n.value || NORMALIZATION.DEFAULT_VALUE);\n const minValue = Math.min(...nodeValues);\n const maxValue = Math.max(...nodeValues);\n const valueRange = maxValue - minValue;\n\n const normalizeValue = (value: number): number => {\n if (valueRange === 0) return NORMALIZATION.DEFAULT_VALUE;\n\n if (normalization === \"logarithmic\") {\n const normalizedLinear = (value - minValue) / valueRange;\n const logScale = Math.log10(1 + normalizedLinear * 9);\n return Math.min(Math.max(NORMALIZATION.LOG_MIN, logScale), NORMALIZATION.LOG_MAX);\n } else {\n return (\n NORMALIZATION.LINEAR_MIN + ((value - minValue) / valueRange) * NORMALIZATION.LINEAR_RANGE\n );\n }\n };\n\n ingredientNodes.forEach((node) => {\n result.set(node.id, node.value ? normalizeValue(node.value) : NORMALIZATION.DEFAULT_VALUE);\n });\n\n return result;\n};\n","/**\n * DAG (Directed Acyclic Graph) operations for Sankey diagram generation.\n *\n * @remarks\n * Provides topological sorting and value propagation algorithms\n * for computing flow values through the recipe graph.\n *\n * @packageDocumentation\n */\n\nimport toposort from \"toposort\";\nimport type { DAGNode, DAGEdge } from \"../types/sankey\";\n\n/**\n * Builds a DAG from nodes and edges, returning topologically sorted node IDs.\n *\n * @remarks\n * Uses Kahn's algorithm via the `toposort` library. If a cycle is detected,\n * falls back to returning node IDs in their original order.\n *\n * @param nodes - Array of DAG nodes\n * @param edges - Array of DAG edges defining dependencies\n * @returns Array of node IDs in topological order (dependencies before dependents)\n *\n * @example\n * ```ts\n * const nodes = [ingredientNode, stepNode, finalNode];\n * const edges = [\n * { from: ingredientNode.id, to: stepNode.id },\n * { from: stepNode.id, to: finalNode.id }\n * ];\n *\n * const sorted = buildDAGAndSort(nodes, edges);\n * // [ingredientNode.id, stepNode.id, finalNode.id]\n * ```\n */\nexport const buildDAGAndSort = (nodes: DAGNode[], edges: DAGEdge[]): string[] => {\n const edgePairs: Array<[string, string]> = edges.map((edge) => [edge.from, edge.to]);\n\n try {\n return toposort(edgePairs);\n } catch (error) {\n console.error(\"Cycle detected in DAG:\", error);\n return nodes.map((node) => node.id);\n }\n};\n\n/**\n * Calculates node values by propagating flow through the DAG.\n *\n * @remarks\n * Ingredient nodes retain their original values. Process and final nodes\n * accumulate values from their inputs. When a node has multiple outputs,\n * its value is split equally among them.\n *\n * @param nodes - Array of DAG nodes with input/output connections\n * @param sortedNodeIds - Node IDs in topological order\n * @returns A Map of node ID to calculated flow value\n *\n * @example\n * ```ts\n * // Given: flour(500) -> step1 -> final\n * // salt(5) -> step1\n * const values = calculateNodeValues(nodes, sortedIds);\n * // Map { \"flour\" => 500, \"salt\" => 5, \"step1\" => 505, \"final\" => 505 }\n * ```\n */\nexport const calculateNodeValues = (\n nodes: DAGNode[],\n sortedNodeIds: string[],\n): Map<string, number> => {\n const nodeMap = new Map<string, DAGNode>();\n const valueMap = new Map<string, number>();\n\n nodes.forEach((node) => {\n nodeMap.set(node.id, node);\n if (node.category === \"ingredient\") {\n valueMap.set(node.id, node.value);\n } else {\n valueMap.set(node.id, 0);\n }\n });\n\n for (const nodeId of sortedNodeIds) {\n const node = nodeMap.get(nodeId);\n if (!node) continue;\n\n if (node.category === \"ingredient\") {\n continue;\n }\n\n let totalInputValue = 0;\n for (const inputId of node.inputs) {\n let inputValue = valueMap.get(inputId) || 0;\n const inputNode = nodeMap.get(inputId);\n if (inputNode && inputNode.outputs.length > 1) {\n inputValue /= inputNode.outputs.length;\n }\n totalInputValue += inputValue;\n }\n\n valueMap.set(nodeId, totalInputValue);\n }\n\n return valueMap;\n};\n","/**\n * Value formatting utilities using official @cooklang/cooklang package.\n *\n * @remarks\n * Provides utilities for converting Cooklang quantity and value types\n * into human-readable string representations.\n *\n * @packageDocumentation\n */\n\nimport {\n getNumericValue,\n quantity_display,\n type CooklangRecipe,\n type Value,\n type Quantity,\n type Step,\n} from \"@cooklang/cooklang\";\n\n/**\n * Formats a Cooklang Value to a string representation.\n *\n * @param value - The Value object to format (number, range, or text)\n * @returns A string representation of the value, or empty string if null/undefined\n *\n * @example\n * ```ts\n * formatValue({ type: \"number\", value: 200 }); // \"200\"\n * formatValue({ type: \"range\", value: { start: 2, end: 3 } }); // \"2-3\"\n * formatValue({ type: \"text\", value: \"some\" }); // \"some\"\n * formatValue(null); // \"\"\n * ```\n */\nexport const formatValue = (value: Value | null | undefined): string => {\n if (!value) return \"\";\n\n if (value.type === \"number\") {\n const num = getNumericValue(value);\n return num !== null ? num.toString() : \"\";\n } else if (value.type === \"range\") {\n // Range structure: { start, end }\n const rangeValue = value.value as { start: unknown; end: unknown };\n const startNum = getNumericValue({ type: \"number\", value: rangeValue.start } as Value);\n const endNum = getNumericValue({ type: \"number\", value: rangeValue.end } as Value);\n if (startNum !== null && endNum !== null) {\n return `${startNum}-${endNum}`;\n }\n return \"\";\n } else if (value.type === \"text\") {\n return value.value as string;\n }\n return \"\";\n};\n\n/**\n * Formats a Cooklang Quantity into separate value and unit strings.\n *\n * @param quantity - The Quantity object to format\n * @returns An object with `quantity` (numeric string) and `unit` (unit string)\n *\n * @example\n * ```ts\n * formatQuantityAmount({ value: { type: \"number\", value: 200 }, unit: \"g\" });\n * // { quantity: \"200\", unit: \"g\" }\n *\n * formatQuantityAmount(null);\n * // { quantity: \"\", unit: \"\" }\n * ```\n */\nexport const formatQuantityAmount = (\n quantity: Quantity | null | undefined,\n): { quantity: string; unit: string } => {\n if (!quantity) {\n return { quantity: \"\", unit: \"\" };\n }\n return {\n quantity: formatValue(quantity.value),\n unit: quantity.unit || \"\",\n };\n};\n\n/**\n * Generates complete text from step items by resolving references.\n *\n * @remarks\n * Converts a Step's items array into a readable string by:\n * - Keeping text items as-is\n * - Resolving ingredient references to \"name(quantity)\" format\n * - Resolving cookware references to their names\n * - Resolving timer references to their display values\n *\n * @param step - The Step object containing items to format\n * @param recipe - The parent CooklangRecipe for resolving references\n * @returns A concatenated string of all step items\n *\n * @example\n * ```ts\n * // For a step with text \"Cook \" + ingredient(pasta, 400g) + \" until done\"\n * generateStepText(step, recipe);\n * // \"Cook pasta(400g) until done\"\n * ```\n */\nexport const generateStepText = (step: Step, recipe: CooklangRecipe): string => {\n return step.items\n .map((item) => {\n if (item.type === \"text\") {\n return item.value;\n } else if (item.type === \"ingredient\") {\n const originalIngredient = recipe.ingredients[item.index];\n if (!originalIngredient) return \"\";\n\n const formatted = formatQuantityAmount(originalIngredient.quantity);\n const quantityText =\n formatted.quantity && formatted.unit\n ? `(${formatted.quantity}${formatted.unit})`\n : formatted.quantity\n ? `(${formatted.quantity})`\n : \"\";\n\n return `${originalIngredient.name}${quantityText}`;\n } else if (item.type === \"cookware\") {\n return recipe.cookware[item.index]?.name || \"\";\n } else if (item.type === \"timer\") {\n const timer = recipe.timers[item.index];\n if (!timer) return \"\";\n return timer.name || (timer.quantity ? quantity_display(timer.quantity) : \"\");\n }\n return \"\";\n })\n .join(\"\");\n};\n\n// Re-export useful functions from official package\nexport { getNumericValue, quantity_display };\n","/**\n * Node builders for Sankey diagram generation.\n *\n * @remarks\n * Factory functions for creating DAG nodes from Cooklang recipe elements.\n * Handles ingredients, process steps, and the final dish node.\n *\n * @packageDocumentation\n */\n\nimport {\n type CooklangRecipe,\n getNumericValue,\n grouped_quantity_display,\n grouped_quantity_is_empty,\n type Quantity,\n} from \"@cooklang/cooklang\";\nimport { generateStepText } from \"../formatter\";\nimport { DEFAULT_SECTION_NAME, FINAL_NODE_ID } from \"../constants\";\nimport type { DAGNode, NodeCategory, ExtractedValue } from \"../types/sankey\";\n\n/**\n * Extracts a numeric or text value from a Cooklang GroupedQuantity.\n *\n * @remarks\n * Attempts to extract a numeric value from fixed, scalable, or unknown\n * quantity types. Falls back to text representation if no numeric value\n * is available.\n *\n * @param groupedQuantity - The GroupedQuantity object from Cooklang parser\n * @returns An ExtractedValue with either numeric value or text label\n *\n * @example\n * ```ts\n * // For \"200g\" quantity\n * calculateIngredientQuantity(gq);\n * // { type: \"number\", value: 200, label: \"200g\" }\n *\n * // For \"some\" quantity\n * calculateIngredientQuantity(gq);\n * // { type: \"text\", label: \"some\" }\n * ```\n */\nexport const calculateIngredientQuantity = (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n groupedQuantity: any,\n): ExtractedValue => {\n if (grouped_quantity_is_empty(groupedQuantity)) {\n return { type: \"text\", label: \"unknown\" };\n }\n\n const displayText = grouped_quantity_display(groupedQuantity);\n\n const gq = groupedQuantity as {\n fixed?: Quantity | null;\n scalable?: Quantity | null;\n fixed_unknown?: Quantity | null;\n scalable_unknown?: Quantity | null;\n };\n\n const quantityToUse = gq.fixed || gq.scalable || gq.fixed_unknown || gq.scalable_unknown;\n\n if (quantityToUse) {\n const numericValue = getNumericValue(quantityToUse.value);\n if (numericValue !== null) {\n return { type: \"number\", value: numericValue, label: displayText || `${numericValue}` };\n }\n }\n\n return { type: \"text\", label: displayText || \"unknown\" };\n};\n\n/**\n * Creates DAG nodes for all ingredients in a recipe.\n *\n * @remarks\n * Processes the recipe's grouped ingredients to create nodes with:\n * - Unique IDs based on ingredient index\n * - Extracted quantity values for flow calculation\n * - Display labels with quantity information\n *\n * @param recipe - The parsed CooklangRecipe\n * @returns An object containing:\n * - `nodes`: Array of ingredient DAGNodes\n * - `indexMap`: Map from original ingredient index to node ID\n *\n * @example\n * ```ts\n * const { nodes, indexMap } = createIngredientNodes(recipe);\n * // nodes: [{ id: \"0\", name: \"flour\", value: 500, ... }, ...]\n * // indexMap: Map { 0 => \"0\", 1 => \"1\", ... }\n * ```\n */\nexport const createIngredientNodes = (\n recipe: CooklangRecipe,\n): { nodes: DAGNode[]; indexMap: Map<number, string> } => {\n const nodes: DAGNode[] = [];\n const indexMap = new Map<number, string>();\n\n recipe.groupedIngredients.forEach(([ingredient, groupedQuantity], index) => {\n const extracted = calculateIngredientQuantity(groupedQuantity);\n const value = extracted.type === \"number\" ? extracted.value : 1;\n const label = extracted.label;\n const ingredientId = index.toString();\n\n indexMap.set(index, ingredientId);\n\n nodes.push({\n id: ingredientId,\n name: ingredient.name,\n category: \"ingredient\" as NodeCategory,\n value,\n originalValue: value,\n label,\n inputs: [],\n outputs: [],\n metadata: {\n originalIndex: index,\n },\n });\n });\n\n return { nodes, indexMap };\n};\n\n/**\n * Creates DAG nodes for all cooking steps in a recipe.\n *\n * @remarks\n * Iterates through all sections and steps, creating process nodes with:\n * - Unique IDs based on section name and step number\n * - Generated text from step items (ingredients, cookware, timers)\n * - Initial value of 0 (calculated later via flow propagation)\n *\n * @param recipe - The parsed CooklangRecipe\n * @returns Array of process DAGNodes representing cooking steps\n *\n * @example\n * ```ts\n * const stepNodes = createStepNodes(recipe);\n * // [{ id: \"step_main_1\", name: \"Boil pasta(400g)\", category: \"process\", ... }]\n * ```\n */\nexport const createStepNodes = (recipe: CooklangRecipe): DAGNode[] => {\n const nodes: DAGNode[] = [];\n\n for (const section of recipe.sections) {\n for (const content of section.content) {\n if (content.type === \"step\") {\n const step = content.value;\n const stepText = generateStepText(step, recipe);\n const stepId = `step_${section.name || DEFAULT_SECTION_NAME}_${step.number}`;\n\n nodes.push({\n id: stepId,\n name: stepText || `手順 ${step.number}`,\n category: \"process\" as NodeCategory,\n value: 0,\n label: `${step.number}`,\n inputs: [],\n outputs: [],\n metadata: {\n stepNumber: step.number,\n sectionName: section.name || undefined,\n },\n });\n }\n }\n }\n\n return nodes;\n};\n\n/**\n * Creates the final node representing the completed dish.\n *\n * @remarks\n * The final node is the sink of the Sankey diagram where all\n * process flows converge. Its value is calculated by summing\n * all incoming flows.\n *\n * @param finalNodeName - Display name for the final dish node\n * @returns A DAGNode with category \"final\"\n *\n * @example\n * ```ts\n * const finalNode = createFinalNode(\"Carbonara\");\n * // { id: \"final_dish\", name: \"Carbonara\", category: \"final\", ... }\n * ```\n */\nexport const createFinalNode = (finalNodeName: string): DAGNode => ({\n id: FINAL_NODE_ID,\n name: finalNodeName,\n category: \"final\" as NodeCategory,\n value: 0,\n label: \"\",\n inputs: [],\n outputs: [],\n});\n","/**\n * Edge builders for Sankey diagram generation.\n *\n * @remarks\n * Factory functions for creating DAG edges that connect nodes.\n * Handles ingredient-to-step and step-to-final connections,\n * including step reference resolution.\n *\n * @packageDocumentation\n */\n\nimport { type CooklangRecipe, type Step } from \"@cooklang/cooklang\";\nimport { DEFAULT_SECTION_NAME } from \"../constants\";\nimport type { DAGNode, DAGEdge, TransformationType } from \"../types/sankey\";\n\n/**\n * Generates a unique step ID from section name and step number.\n *\n * @param sectionName - The recipe section name (null uses default)\n * @param stepNumber - The 1-indexed step number\n * @returns A unique step ID string (e.g., \"step_main_1\")\n *\n * @example\n * ```ts\n * getStepId(\"main\", 1); // \"step_main_1\"\n * getStepId(null, 2); // \"step_main_2\"\n * getStepId(\"sauce\", 1); // \"step_sauce_1\"\n * ```\n */\nexport const getStepId = (sectionName: string | null | undefined, stepNumber: number): string => {\n return `step_${sectionName || DEFAULT_SECTION_NAME}_${stepNumber}`;\n};\n\n/**\n * Extracts ingredient usage information from a recipe step.\n *\n * @param step - The Step object to analyze\n * @returns Array of objects containing ingredient index and step number\n *\n * @example\n * ```ts\n * // For a step using @pasta and @salt\n * extractIngredientUsageFromStep(step);\n * // [{ ingredientIndex: 0, stepNumber: 1 }, { ingredientIndex: 1, stepNumber: 1 }]\n * ```\n */\nexport const extractIngredientUsageFromStep = (\n step: Step,\n): Array<{ ingredientIndex: number; stepNumber: number }> => {\n const usages: Array<{ ingredientIndex: number; stepNumber: number }> = [];\n\n for (const item of step.items) {\n if (item.type === \"ingredient\") {\n usages.push({\n ingredientIndex: item.index,\n stepNumber: step.number,\n });\n }\n }\n\n return usages;\n};\n\n/**\n * Builds edges connecting ingredients to their consuming steps.\n *\n * @remarks\n * Analyzes the recipe to create edges from:\n * - Ingredient nodes to step nodes (for direct ingredient usage)\n * - Step nodes to other step nodes (for step references like \"the pasta from step 1\")\n *\n * Also updates the input/output arrays on the nodes for DAG traversal.\n *\n * @param recipe - The parsed CooklangRecipe\n * @param ingredientIndexMap - Map from ingredient index to node ID\n * @param nodes - Array of all DAG nodes (modified in place)\n * @returns Array of DAGEdges connecting ingredients/steps to steps\n *\n * @example\n * ```ts\n * const edges = buildIngredientToStepEdges(recipe, indexMap, nodes);\n * // [{ from: \"0\", to: \"step_main_1\", metadata: { ... } }, ...]\n * ```\n */\nexport const buildIngredientToStepEdges = (\n recipe: CooklangRecipe,\n ingredientIndexMap: Map<number, string>,\n nodes: DAGNode[],\n): DAGEdge[] => {\n const edges: DAGEdge[] = [];\n const nodeMap = new Map<string, DAGNode>();\n\n nodes.forEach((node) => nodeMap.set(node.id, node));\n\n for (const section of recipe.sections) {\n for (const content of section.content) {\n if (content.type === \"step\") {\n const step = content.value;\n const currentStepId = getStepId(section.name, step.number);\n const usedIngredients = extractIngredientUsageFromStep(step);\n\n for (const usage of usedIngredients) {\n const ingredient = recipe.ingredients[usage.ingredientIndex];\n\n // Check for step reference\n const relation = ingredient.relation;\n if (\n relation &&\n relation.relation?.type === \"reference\" &&\n relation.reference_target === \"step\"\n ) {\n const referenceTo = relation.relation.references_to;\n const sourceStepId = getStepId(section.name, referenceTo + 1);\n edges.push({\n from: sourceStepId,\n to: currentStepId,\n metadata: {\n stepNumber: step.number,\n transformationType: \"cooking\" as TransformationType,\n },\n });\n\n const sourceNode = nodeMap.get(sourceStepId);\n const targetNode = nodeMap.get(currentStepId);\n if (sourceNode && targetNode) {\n sourceNode.outputs.push(currentStepId);\n targetNode.inputs.push(sourceStepId);\n }\n continue;\n }\n\n const ingredientId = ingredientIndexMap.get(usage.ingredientIndex);\n if (ingredientId) {\n edges.push({\n from: ingredientId,\n to: currentStepId,\n metadata: {\n stepNumber: step.number,\n transformationType: \"preparation\" as TransformationType,\n },\n });\n\n const ingredientNode = nodeMap.get(ingredientId);\n const stepNode = nodeMap.get(currentStepId);\n if (ingredientNode && stepNode) {\n ingredientNode.outputs.push(currentStepId);\n stepNode.inputs.push(ingredientId);\n }\n }\n }\n }\n }\n }\n\n return edges;\n};\n\n/**\n * Builds an edge from the last step to the final dish node.\n *\n * @remarks\n * Connects only the last step to the final node, representing\n * the completion of the recipe. Also updates the input/output\n * arrays on the affected nodes.\n *\n * @param stepNodes - Array of step nodes in order\n * @param finalNode - The final dish node (modified in place)\n * @returns Array containing a single edge to the final node, or empty if no steps\n *\n * @example\n * ```ts\n * const edges = buildStepToFinalEdges(stepNodes, finalNode);\n * // [{ from: \"step_main_3\", to: \"final_dish\", metadata: { transformationType: \"completion\" } }]\n * ```\n */\nexport const buildStepToFinalEdges = (stepNodes: DAGNode[], finalNode: DAGNode): DAGEdge[] => {\n if (stepNodes.length === 0) return [];\n\n const lastStepNode = stepNodes[stepNodes.length - 1];\n\n lastStepNode.outputs.push(finalNode.id);\n finalNode.inputs.push(lastStepNode.id);\n\n return [\n {\n from: lastStepNode.id,\n to: finalNode.id,\n metadata: {\n transformationType: \"completion\" as TransformationType,\n },\n },\n ];\n};\n","/**\n * Sankey diagram data generator from Cooklang recipes.\n *\n * @remarks\n * Main module for transforming parsed Cooklang recipes into\n * Sankey diagram data structures. Orchestrates the DAG building,\n * value calculation, and normalization pipeline.\n *\n * @packageDocumentation\n */\n\nimport type { CooklangRecipe } from \"@cooklang/cooklang\";\nimport {\n DEFAULT_FINAL_NODE_NAME,\n DEFAULT_MIN_LINK_VALUE,\n DEFAULT_NORMALIZATION,\n} from \"../constants\";\nimport type { SankeyNode, SankeyLink, SankeyData, DAGNode, DAGEdge } from \"../types/sankey\";\nimport { normalizeIngredientValues } from \"./normalizer\";\nimport { buildDAGAndSort, calculateNodeValues } from \"./dag\";\nimport { createIngredientNodes, createStepNodes, createFinalNode } from \"./node-builders\";\nimport { buildIngredientToStepEdges, buildStepToFinalEdges } from \"./edge-builders\";\n\n/**\n * Configuration options for Sankey diagram generation.\n *\n * @example\n * ```ts\n * const options: SankeyGeneratorOptions = {\n * finalNodeName: \"Carbonara\",\n * normalization: \"logarithmic\",\n * minLinkValue: 0.1\n * };\n * ```\n */\nexport interface SankeyGeneratorOptions {\n /**\n * Display name for the final dish node.\n * @defaultValue \"完成品\"\n */\n finalNodeName?: string;\n /**\n * Method for normalizing ingredient values.\n * - `logarithmic`: Compress large ranges (recommended)\n * - `linear`: Linear scaling\n * - `none`: Use raw values\n * @defaultValue \"logarithmic\"\n */\n normalization?: \"logarithmic\" | \"linear\" | \"none\";\n /**\n * Minimum value for links to ensure visibility.\n * @defaultValue 0.1\n */\n minLinkValue?: number;\n}\n\nconst DEFAULT_OPTIONS: Required<SankeyGeneratorOptions> = {\n finalNodeName: DEFAULT_FINAL_NODE_NAME,\n normalization: DEFAULT_NORMALIZATION,\n minLinkValue: DEFAULT_MIN_LINK_VALUE,\n};\n\n/**\n * Transforms DAG nodes and edges into final Sankey diagram data.\n *\n * @remarks\n * Internal function that:\n * 1. Normalizes ingredient values using the specified method\n * 2. Calculates flow values for all nodes via DAG propagation\n * 3. Converts DAG structures to Sankey-compatible format\n * 4. Applies minimum value constraints to links\n *\n * @param dagNodes - Array of DAG nodes with dependency info\n * @param dagEdges - Array of DAG edges\n * @param sortedNodeIds - Node IDs in topological order\n * @param options - Generation options with all defaults applied\n * @returns Complete SankeyData structure ready for visualization\n *\n * @internal\n */\nconst buildSankeyData = (\n dagNodes: DAGNode[],\n dagEdges: DAGEdge[],\n sortedNodeIds: string[],\n options: Required<SankeyGeneratorOptions>,\n): SankeyData => {\n const ingredientNodes = dagNodes\n .filter((node) => node.category === \"ingredient\")\n .map((node) => ({\n id: node.id,\n name: node.name,\n category: node.category,\n value: node.value,\n label: node.label,\n originalValue: node.originalValue,\n metadata: node.metadata,\n }));\n\n const normalizedValueMap = normalizeIngredientValues(ingredientNodes, options.normalization);\n\n const normalizedDAGNodes = dagNodes.map((node) => {\n if (node.category === \"ingredient\") {\n return {\n ...node,\n value: normalizedValueMap.get(node.id) ?? node.value,\n };\n }\n return node;\n });\n\n const finalCalculatedValues = calculateNodeValues(normalizedDAGNodes, sortedNodeIds);\n\n const sankeyNodes: SankeyNode[] = dagNodes.map((dagNode) => {\n const finalValue = finalCalculatedValues.get(dagNode.id) || 0;\n return {\n id: dagNode.id,\n name: dagNode.name,\n category: dagNode.category,\n value:\n dagNode.category === \"ingredient\"\n ? (normalizedValueMap.get(dagNode.id) ?? dagNode.value)\n : finalValue,\n label: dagNode.label,\n originalValue: dagNode.originalValue,\n metadata: dagNode.metadata,\n };\n });\n\n const sankeyLinks: SankeyLink[] = dagEdges.map((edge) => {\n const sourceValue = finalCalculatedValues.get(edge.from) || 0;\n const sourceNode = dagNodes.find((n) => n.id === edge.from);\n\n let linkValue = sourceValue;\n if (sourceNode && sourceNode.outputs.length > 1) {\n linkValue = sourceValue / sourceNode.outputs.length;\n }\n\n return {\n source: edge.from,\n target: edge.to,\n value: Math.max(options.minLinkValue, linkValue),\n originalValue: sourceValue,\n metadata: edge.metadata,\n };\n });\n\n return {\n nodes: sankeyNodes,\n links: sankeyLinks,\n };\n};\n\n/**\n * Generates Sankey diagram data from a parsed Cooklang recipe.\n *\n * @remarks\n * This is the main entry point for the library. It transforms a parsed\n * Cooklang recipe into a Sankey diagram data structure by:\n * 1. Creating nodes for ingredients, steps, and the final dish\n * 2. Building edges representing ingredient flow\n * 3. Performing topological sort for correct value propagation\n * 4. Normalizing values for balanced visualization\n *\n * @param recipe - A parsed CooklangRecipe object\n * @param options - Optional configuration for generation\n * @returns SankeyData structure, or null if generation fails\n *\n * @example\n * ```ts\n * import { CooklangParser, generateSankeyData } from 'cooklang-sankey';\n *\n * const parser = new CooklangParser();\n * const [recipe] = parser.parse(`\n * @pasta{400g} を茹でる。\n * @卵{3個}と@チーズ{100g}を混ぜる。\n * `);\n *\n * const data = generateSankeyData(recipe, {\n * finalNodeName: \"Carbonara\",\n * normalization: \"logarithmic\"\n * });\n * ```\n */\nexport const generateSankeyData = (\n recipe: CooklangRecipe,\n options?: SankeyGeneratorOptions,\n): SankeyData | null => {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n try {\n const { nodes: ingredientNodes, indexMap: ingredientIndexMap } = createIngredientNodes(recipe);\n\n const stepNodes = createStepNodes(recipe);\n const finalNode = createFinalNode(opts.finalNodeName);\n\n const allNodes = [...ingredientNodes, ...stepNodes, finalNode];\n\n const ingredientToStepEdges = buildIngredientToStepEdges(recipe, ingredientIndexMap, allNodes);\n const stepToFinalEdges = buildStepToFinalEdges(stepNodes, finalNode);\n const allEdges = [...ingredientToStepEdges, ...stepToFinalEdges];\n\n const sortedNodeIds = buildDAGAndSort(allNodes, allEdges);\n\n return buildSankeyData(allNodes, allEdges, sortedNodeIds, opts);\n } catch (error) {\n console.error(\"Error generating sankey data from cooklang:\", error);\n return null;\n }\n};\n\n/**\n * Validates and optimizes Sankey data for visualization.\n *\n * @remarks\n * Performs three optimization steps:\n * 1. **Remove invalid links**: Filters out links referencing non-existent nodes\n * 2. **Remove orphaned nodes**: Keeps only nodes connected to valid links\n * (final nodes are always preserved)\n * 3. **Normalize link values**: Scales values relative to minimum for\n * stable visualization (ensures all values >= 1)\n *\n * @param data - The SankeyData to optimize\n * @returns A new SankeyData with optimizations applied\n *\n * @example\n * ```ts\n * const rawData = generateSankeyData(recipe);\n * if (rawData) {\n * const optimized = optimizeSankeyData(rawData);\n * // Use optimized data for visualization\n * }\n * ```\n */\nexport const optimizeSankeyData = (data: SankeyData): SankeyData => {\n // Create a Set of existing node IDs for O(1) lookup\n const existingNodeIds = new Set(data.nodes.map((node) => node.id));\n\n // Step 1: Remove invalid links\n // Filter out links where source or target node doesn't exist\n const validLinks = data.links.filter((link) => {\n const sourceExists = existingNodeIds.has(link.source);\n const targetExists = existingNodeIds.has(link.target);\n\n if (!sourceExists) {\n console.error(`Missing source node: ${link.source}`);\n }\n if (!targetExists) {\n console.error(`Missing target node: ${link.target}`);\n }\n\n return sourceExists && targetExists;\n });\n\n // Step 2: Remove orphaned nodes\n // Collect node IDs that are connected to valid links\n const connectedNodeIds = new Set<string>();\n validLinks.forEach((link) => {\n connectedNodeIds.add(link.source);\n connectedNodeIds.add(link.target);\n });\n\n // Keep only nodes connected to links, or final nodes (always preserved)\n const filteredNodes = data.nodes.filter(\n (node) => connectedNodeIds.has(node.id) || node.category === \"final\",\n );\n\n // Step 3: Normalize link values\n // Scale values relative to minimum for stable visualization\n let normalizedLinks = validLinks;\n if (validLinks.length > 0) {\n const minValue = Math.min(...validLinks.map((link) => link.value));\n if (minValue > 0) {\n // Divide by min value and clamp to ensure value >= 1\n normalizedLinks = validLinks.map((link) => ({\n ...link,\n value: Math.max(1, link.value / minValue),\n }));\n }\n }\n\n return {\n nodes: filteredNodes,\n links: normalizedLinks,\n };\n};\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CooklangRecipe, Step } from '@cooklang/cooklang';
|
|
2
|
-
export { Content, CooklangParser, CooklangRecipe,
|
|
1
|
+
import { CooklangRecipe, Value, Quantity, Step } from '@cooklang/cooklang';
|
|
2
|
+
export { Content, CooklangParser, CooklangRecipe, Ingredient, Item, Quantity, Section, Step, Value, getNumericValue, quantity_display } from '@cooklang/cooklang';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Cooklang Parser wrapper using official @cooklang/cooklang package.
|
|
@@ -339,178 +339,70 @@ declare const generateSankeyData: (recipe: CooklangRecipe, options?: SankeyGener
|
|
|
339
339
|
declare const optimizeSankeyData: (data: SankeyData) => SankeyData;
|
|
340
340
|
|
|
341
341
|
/**
|
|
342
|
-
*
|
|
342
|
+
* Value formatting utilities using official @cooklang/cooklang package.
|
|
343
343
|
*
|
|
344
344
|
* @remarks
|
|
345
|
-
* Provides utilities for converting Cooklang
|
|
346
|
-
* string representations
|
|
345
|
+
* Provides utilities for converting Cooklang quantity and value types
|
|
346
|
+
* into human-readable string representations.
|
|
347
347
|
*
|
|
348
348
|
* @packageDocumentation
|
|
349
349
|
*/
|
|
350
350
|
|
|
351
351
|
/**
|
|
352
|
-
*
|
|
352
|
+
* Formats a Cooklang Value to a string representation.
|
|
353
353
|
*
|
|
354
|
-
* @
|
|
355
|
-
*
|
|
356
|
-
* - Keeping text items as-is
|
|
357
|
-
* - Resolving ingredient references to "name(quantity)" format using official display functions
|
|
358
|
-
* - Resolving cookware references to their display names
|
|
359
|
-
* - Resolving timer references to their display values
|
|
360
|
-
*
|
|
361
|
-
* @param step - The Step object containing items to format
|
|
362
|
-
* @param recipe - The parent CooklangRecipe for resolving references
|
|
363
|
-
* @returns A concatenated string of all step items
|
|
354
|
+
* @param value - The Value object to format (number, range, or text)
|
|
355
|
+
* @returns A string representation of the value, or empty string if null/undefined
|
|
364
356
|
*
|
|
365
357
|
* @example
|
|
366
358
|
* ```ts
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
359
|
+
* formatValue({ type: "number", value: 200 }); // "200"
|
|
360
|
+
* formatValue({ type: "range", value: { start: 2, end: 3 } }); // "2-3"
|
|
361
|
+
* formatValue({ type: "text", value: "some" }); // "some"
|
|
362
|
+
* formatValue(null); // ""
|
|
370
363
|
* ```
|
|
371
364
|
*/
|
|
372
|
-
declare const
|
|
373
|
-
|
|
365
|
+
declare const formatValue: (value: Value | null | undefined) => string;
|
|
374
366
|
/**
|
|
375
|
-
*
|
|
367
|
+
* Formats a Cooklang Quantity into separate value and unit strings.
|
|
376
368
|
*
|
|
377
|
-
* @
|
|
378
|
-
*
|
|
379
|
-
* They use simple string values for quantities and units, making them easy
|
|
380
|
-
* to render without additional formatting logic.
|
|
381
|
-
*
|
|
382
|
-
* @packageDocumentation
|
|
383
|
-
*/
|
|
384
|
-
/**
|
|
385
|
-
* Ingredient formatted for display.
|
|
369
|
+
* @param quantity - The Quantity object to format
|
|
370
|
+
* @returns An object with `quantity` (numeric string) and `unit` (unit string)
|
|
386
371
|
*
|
|
387
372
|
* @example
|
|
388
373
|
* ```ts
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
* quantity: "200",
|
|
392
|
-
* unit: "g"
|
|
393
|
-
* };
|
|
394
|
-
* ```
|
|
395
|
-
*/
|
|
396
|
-
interface DisplayIngredient {
|
|
397
|
-
readonly name: string;
|
|
398
|
-
readonly quantity: string;
|
|
399
|
-
readonly unit: string;
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Cookware formatted for display.
|
|
403
|
-
*
|
|
404
|
-
* @example
|
|
405
|
-
* ```ts
|
|
406
|
-
* const cookware: DisplayCookware = {
|
|
407
|
-
* name: "フライパン",
|
|
408
|
-
* note: "26cm"
|
|
409
|
-
* };
|
|
410
|
-
* ```
|
|
411
|
-
*/
|
|
412
|
-
interface DisplayCookware {
|
|
413
|
-
readonly name: string;
|
|
414
|
-
readonly note: string;
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Timer formatted for display.
|
|
374
|
+
* formatQuantityAmount({ value: { type: "number", value: 200 }, unit: "g" });
|
|
375
|
+
* // { quantity: "200", unit: "g" }
|
|
418
376
|
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
* const timer: DisplayTimer = {
|
|
422
|
-
* name: "煮込み",
|
|
423
|
-
* quantity: "30",
|
|
424
|
-
* unit: "分"
|
|
425
|
-
* };
|
|
377
|
+
* formatQuantityAmount(null);
|
|
378
|
+
* // { quantity: "", unit: "" }
|
|
426
379
|
* ```
|
|
427
380
|
*/
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Step formatted for display.
|
|
435
|
-
*
|
|
436
|
-
* @example
|
|
437
|
-
* ```ts
|
|
438
|
-
* const step: DisplayStep = {
|
|
439
|
-
* text: "小麦粉(200g)と卵(2個)を混ぜる"
|
|
440
|
-
* };
|
|
441
|
-
* ```
|
|
442
|
-
*/
|
|
443
|
-
interface DisplayStep {
|
|
444
|
-
readonly text: string;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Complete recipe data formatted for UI display.
|
|
448
|
-
*
|
|
449
|
-
* @remarks
|
|
450
|
-
* This interface represents a fully parsed and formatted recipe
|
|
451
|
-
* ready for rendering in a UI component. All values are strings
|
|
452
|
-
* for easy display without additional formatting.
|
|
453
|
-
*
|
|
454
|
-
* @example
|
|
455
|
-
* ```ts
|
|
456
|
-
* import { parseRecipeForDisplay } from '@4kk11/cooklang-sankey';
|
|
457
|
-
*
|
|
458
|
-
* const displayData = parseRecipeForDisplay(recipe);
|
|
459
|
-
* displayData.ingredients.forEach(ing => {
|
|
460
|
-
* console.log(`${ing.name}: ${ing.quantity}${ing.unit}`);
|
|
461
|
-
* });
|
|
462
|
-
* ```
|
|
463
|
-
*/
|
|
464
|
-
interface DisplayRecipe {
|
|
465
|
-
readonly ingredients: readonly DisplayIngredient[];
|
|
466
|
-
readonly cookware: readonly DisplayCookware[];
|
|
467
|
-
readonly timers: readonly DisplayTimer[];
|
|
468
|
-
readonly steps: readonly DisplayStep[];
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Recipe display utilities for UI rendering.
|
|
473
|
-
*
|
|
474
|
-
* @remarks
|
|
475
|
-
* Provides utilities for converting parsed CooklangRecipe objects into
|
|
476
|
-
* display-ready data structures suitable for UI rendering.
|
|
477
|
-
*
|
|
478
|
-
* Uses the official @cooklang/cooklang package's convenience functions
|
|
479
|
-
* for accurate quantity aggregation and formatting.
|
|
480
|
-
*
|
|
481
|
-
* @packageDocumentation
|
|
482
|
-
*/
|
|
483
|
-
|
|
381
|
+
declare const formatQuantityAmount: (quantity: Quantity | null | undefined) => {
|
|
382
|
+
quantity: string;
|
|
383
|
+
unit: string;
|
|
384
|
+
};
|
|
484
385
|
/**
|
|
485
|
-
*
|
|
386
|
+
* Generates complete text from step items by resolving references.
|
|
486
387
|
*
|
|
487
388
|
* @remarks
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
389
|
+
* Converts a Step's items array into a readable string by:
|
|
390
|
+
* - Keeping text items as-is
|
|
391
|
+
* - Resolving ingredient references to "name(quantity)" format
|
|
392
|
+
* - Resolving cookware references to their names
|
|
393
|
+
* - Resolving timer references to their display values
|
|
491
394
|
*
|
|
492
|
-
* @param
|
|
493
|
-
* @
|
|
395
|
+
* @param step - The Step object containing items to format
|
|
396
|
+
* @param recipe - The parent CooklangRecipe for resolving references
|
|
397
|
+
* @returns A concatenated string of all step items
|
|
494
398
|
*
|
|
495
399
|
* @example
|
|
496
400
|
* ```ts
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
* const [recipe] = parser.parse(recipeText);
|
|
501
|
-
* const displayData = parseRecipeForDisplay(recipe);
|
|
502
|
-
*
|
|
503
|
-
* // Render ingredients
|
|
504
|
-
* displayData.ingredients.forEach(ing => {
|
|
505
|
-
* console.log(`${ing.name}: ${ing.quantity}${ing.unit}`);
|
|
506
|
-
* });
|
|
507
|
-
*
|
|
508
|
-
* // Render steps
|
|
509
|
-
* displayData.steps.forEach((step, i) => {
|
|
510
|
-
* console.log(`${i + 1}. ${step.text}`);
|
|
511
|
-
* });
|
|
401
|
+
* // For a step with text "Cook " + ingredient(pasta, 400g) + " until done"
|
|
402
|
+
* generateStepText(step, recipe);
|
|
403
|
+
* // "Cook pasta(400g) until done"
|
|
512
404
|
* ```
|
|
513
405
|
*/
|
|
514
|
-
declare
|
|
406
|
+
declare const generateStepText: (step: Step, recipe: CooklangRecipe) => string;
|
|
515
407
|
|
|
516
|
-
export { type BaseLinkMetadata, type BaseNodeMetadata, type BaseSankeyLink, type BaseSankeyNode, type DAGEdge, type DAGNode, type
|
|
408
|
+
export { type BaseLinkMetadata, type BaseNodeMetadata, type BaseSankeyLink, type BaseSankeyNode, type DAGEdge, type DAGNode, type ExtractedValue, type NodeCategory, type RecipeMetadata, type SankeyData, type SankeyGeneratorOptions, type SankeyLink, type SankeyNode, type TransformationType, extractMetadata, formatQuantityAmount, formatValue, generateSankeyData, generateStepText, optimizeSankeyData };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CooklangRecipe, Step } from '@cooklang/cooklang';
|
|
2
|
-
export { Content, CooklangParser, CooklangRecipe,
|
|
1
|
+
import { CooklangRecipe, Value, Quantity, Step } from '@cooklang/cooklang';
|
|
2
|
+
export { Content, CooklangParser, CooklangRecipe, Ingredient, Item, Quantity, Section, Step, Value, getNumericValue, quantity_display } from '@cooklang/cooklang';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Cooklang Parser wrapper using official @cooklang/cooklang package.
|
|
@@ -339,178 +339,70 @@ declare const generateSankeyData: (recipe: CooklangRecipe, options?: SankeyGener
|
|
|
339
339
|
declare const optimizeSankeyData: (data: SankeyData) => SankeyData;
|
|
340
340
|
|
|
341
341
|
/**
|
|
342
|
-
*
|
|
342
|
+
* Value formatting utilities using official @cooklang/cooklang package.
|
|
343
343
|
*
|
|
344
344
|
* @remarks
|
|
345
|
-
* Provides utilities for converting Cooklang
|
|
346
|
-
* string representations
|
|
345
|
+
* Provides utilities for converting Cooklang quantity and value types
|
|
346
|
+
* into human-readable string representations.
|
|
347
347
|
*
|
|
348
348
|
* @packageDocumentation
|
|
349
349
|
*/
|
|
350
350
|
|
|
351
351
|
/**
|
|
352
|
-
*
|
|
352
|
+
* Formats a Cooklang Value to a string representation.
|
|
353
353
|
*
|
|
354
|
-
* @
|
|
355
|
-
*
|
|
356
|
-
* - Keeping text items as-is
|
|
357
|
-
* - Resolving ingredient references to "name(quantity)" format using official display functions
|
|
358
|
-
* - Resolving cookware references to their display names
|
|
359
|
-
* - Resolving timer references to their display values
|
|
360
|
-
*
|
|
361
|
-
* @param step - The Step object containing items to format
|
|
362
|
-
* @param recipe - The parent CooklangRecipe for resolving references
|
|
363
|
-
* @returns A concatenated string of all step items
|
|
354
|
+
* @param value - The Value object to format (number, range, or text)
|
|
355
|
+
* @returns A string representation of the value, or empty string if null/undefined
|
|
364
356
|
*
|
|
365
357
|
* @example
|
|
366
358
|
* ```ts
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
359
|
+
* formatValue({ type: "number", value: 200 }); // "200"
|
|
360
|
+
* formatValue({ type: "range", value: { start: 2, end: 3 } }); // "2-3"
|
|
361
|
+
* formatValue({ type: "text", value: "some" }); // "some"
|
|
362
|
+
* formatValue(null); // ""
|
|
370
363
|
* ```
|
|
371
364
|
*/
|
|
372
|
-
declare const
|
|
373
|
-
|
|
365
|
+
declare const formatValue: (value: Value | null | undefined) => string;
|
|
374
366
|
/**
|
|
375
|
-
*
|
|
367
|
+
* Formats a Cooklang Quantity into separate value and unit strings.
|
|
376
368
|
*
|
|
377
|
-
* @
|
|
378
|
-
*
|
|
379
|
-
* They use simple string values for quantities and units, making them easy
|
|
380
|
-
* to render without additional formatting logic.
|
|
381
|
-
*
|
|
382
|
-
* @packageDocumentation
|
|
383
|
-
*/
|
|
384
|
-
/**
|
|
385
|
-
* Ingredient formatted for display.
|
|
369
|
+
* @param quantity - The Quantity object to format
|
|
370
|
+
* @returns An object with `quantity` (numeric string) and `unit` (unit string)
|
|
386
371
|
*
|
|
387
372
|
* @example
|
|
388
373
|
* ```ts
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
* quantity: "200",
|
|
392
|
-
* unit: "g"
|
|
393
|
-
* };
|
|
394
|
-
* ```
|
|
395
|
-
*/
|
|
396
|
-
interface DisplayIngredient {
|
|
397
|
-
readonly name: string;
|
|
398
|
-
readonly quantity: string;
|
|
399
|
-
readonly unit: string;
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Cookware formatted for display.
|
|
403
|
-
*
|
|
404
|
-
* @example
|
|
405
|
-
* ```ts
|
|
406
|
-
* const cookware: DisplayCookware = {
|
|
407
|
-
* name: "フライパン",
|
|
408
|
-
* note: "26cm"
|
|
409
|
-
* };
|
|
410
|
-
* ```
|
|
411
|
-
*/
|
|
412
|
-
interface DisplayCookware {
|
|
413
|
-
readonly name: string;
|
|
414
|
-
readonly note: string;
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Timer formatted for display.
|
|
374
|
+
* formatQuantityAmount({ value: { type: "number", value: 200 }, unit: "g" });
|
|
375
|
+
* // { quantity: "200", unit: "g" }
|
|
418
376
|
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
* const timer: DisplayTimer = {
|
|
422
|
-
* name: "煮込み",
|
|
423
|
-
* quantity: "30",
|
|
424
|
-
* unit: "分"
|
|
425
|
-
* };
|
|
377
|
+
* formatQuantityAmount(null);
|
|
378
|
+
* // { quantity: "", unit: "" }
|
|
426
379
|
* ```
|
|
427
380
|
*/
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Step formatted for display.
|
|
435
|
-
*
|
|
436
|
-
* @example
|
|
437
|
-
* ```ts
|
|
438
|
-
* const step: DisplayStep = {
|
|
439
|
-
* text: "小麦粉(200g)と卵(2個)を混ぜる"
|
|
440
|
-
* };
|
|
441
|
-
* ```
|
|
442
|
-
*/
|
|
443
|
-
interface DisplayStep {
|
|
444
|
-
readonly text: string;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Complete recipe data formatted for UI display.
|
|
448
|
-
*
|
|
449
|
-
* @remarks
|
|
450
|
-
* This interface represents a fully parsed and formatted recipe
|
|
451
|
-
* ready for rendering in a UI component. All values are strings
|
|
452
|
-
* for easy display without additional formatting.
|
|
453
|
-
*
|
|
454
|
-
* @example
|
|
455
|
-
* ```ts
|
|
456
|
-
* import { parseRecipeForDisplay } from '@4kk11/cooklang-sankey';
|
|
457
|
-
*
|
|
458
|
-
* const displayData = parseRecipeForDisplay(recipe);
|
|
459
|
-
* displayData.ingredients.forEach(ing => {
|
|
460
|
-
* console.log(`${ing.name}: ${ing.quantity}${ing.unit}`);
|
|
461
|
-
* });
|
|
462
|
-
* ```
|
|
463
|
-
*/
|
|
464
|
-
interface DisplayRecipe {
|
|
465
|
-
readonly ingredients: readonly DisplayIngredient[];
|
|
466
|
-
readonly cookware: readonly DisplayCookware[];
|
|
467
|
-
readonly timers: readonly DisplayTimer[];
|
|
468
|
-
readonly steps: readonly DisplayStep[];
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Recipe display utilities for UI rendering.
|
|
473
|
-
*
|
|
474
|
-
* @remarks
|
|
475
|
-
* Provides utilities for converting parsed CooklangRecipe objects into
|
|
476
|
-
* display-ready data structures suitable for UI rendering.
|
|
477
|
-
*
|
|
478
|
-
* Uses the official @cooklang/cooklang package's convenience functions
|
|
479
|
-
* for accurate quantity aggregation and formatting.
|
|
480
|
-
*
|
|
481
|
-
* @packageDocumentation
|
|
482
|
-
*/
|
|
483
|
-
|
|
381
|
+
declare const formatQuantityAmount: (quantity: Quantity | null | undefined) => {
|
|
382
|
+
quantity: string;
|
|
383
|
+
unit: string;
|
|
384
|
+
};
|
|
484
385
|
/**
|
|
485
|
-
*
|
|
386
|
+
* Generates complete text from step items by resolving references.
|
|
486
387
|
*
|
|
487
388
|
* @remarks
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
389
|
+
* Converts a Step's items array into a readable string by:
|
|
390
|
+
* - Keeping text items as-is
|
|
391
|
+
* - Resolving ingredient references to "name(quantity)" format
|
|
392
|
+
* - Resolving cookware references to their names
|
|
393
|
+
* - Resolving timer references to their display values
|
|
491
394
|
*
|
|
492
|
-
* @param
|
|
493
|
-
* @
|
|
395
|
+
* @param step - The Step object containing items to format
|
|
396
|
+
* @param recipe - The parent CooklangRecipe for resolving references
|
|
397
|
+
* @returns A concatenated string of all step items
|
|
494
398
|
*
|
|
495
399
|
* @example
|
|
496
400
|
* ```ts
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
* const [recipe] = parser.parse(recipeText);
|
|
501
|
-
* const displayData = parseRecipeForDisplay(recipe);
|
|
502
|
-
*
|
|
503
|
-
* // Render ingredients
|
|
504
|
-
* displayData.ingredients.forEach(ing => {
|
|
505
|
-
* console.log(`${ing.name}: ${ing.quantity}${ing.unit}`);
|
|
506
|
-
* });
|
|
507
|
-
*
|
|
508
|
-
* // Render steps
|
|
509
|
-
* displayData.steps.forEach((step, i) => {
|
|
510
|
-
* console.log(`${i + 1}. ${step.text}`);
|
|
511
|
-
* });
|
|
401
|
+
* // For a step with text "Cook " + ingredient(pasta, 400g) + " until done"
|
|
402
|
+
* generateStepText(step, recipe);
|
|
403
|
+
* // "Cook pasta(400g) until done"
|
|
512
404
|
* ```
|
|
513
405
|
*/
|
|
514
|
-
declare
|
|
406
|
+
declare const generateStepText: (step: Step, recipe: CooklangRecipe) => string;
|
|
515
407
|
|
|
516
|
-
export { type BaseLinkMetadata, type BaseNodeMetadata, type BaseSankeyLink, type BaseSankeyNode, type DAGEdge, type DAGNode, type
|
|
408
|
+
export { type BaseLinkMetadata, type BaseNodeMetadata, type BaseSankeyLink, type BaseSankeyNode, type DAGEdge, type DAGNode, type ExtractedValue, type NodeCategory, type RecipeMetadata, type SankeyData, type SankeyGeneratorOptions, type SankeyLink, type SankeyNode, type TransformationType, extractMetadata, formatQuantityAmount, formatValue, generateSankeyData, generateStepText, optimizeSankeyData };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { CooklangParser, CooklangRecipe,
|
|
1
|
+
import { getNumericValue, quantity_display, grouped_quantity_is_empty, grouped_quantity_display } from '@cooklang/cooklang';
|
|
2
|
+
export { CooklangParser, CooklangRecipe, getNumericValue, quantity_display } from '@cooklang/cooklang';
|
|
3
3
|
import toposort from 'toposort';
|
|
4
4
|
|
|
5
5
|
// src/parser.ts
|
|
@@ -136,20 +136,45 @@ var calculateNodeValues = (nodes, sortedNodeIds) => {
|
|
|
136
136
|
}
|
|
137
137
|
return valueMap;
|
|
138
138
|
};
|
|
139
|
+
var formatValue = (value) => {
|
|
140
|
+
if (!value) return "";
|
|
141
|
+
if (value.type === "number") {
|
|
142
|
+
const num = getNumericValue(value);
|
|
143
|
+
return num !== null ? num.toString() : "";
|
|
144
|
+
} else if (value.type === "range") {
|
|
145
|
+
const rangeValue = value.value;
|
|
146
|
+
const startNum = getNumericValue({ type: "number", value: rangeValue.start });
|
|
147
|
+
const endNum = getNumericValue({ type: "number", value: rangeValue.end });
|
|
148
|
+
if (startNum !== null && endNum !== null) {
|
|
149
|
+
return `${startNum}-${endNum}`;
|
|
150
|
+
}
|
|
151
|
+
return "";
|
|
152
|
+
} else if (value.type === "text") {
|
|
153
|
+
return value.value;
|
|
154
|
+
}
|
|
155
|
+
return "";
|
|
156
|
+
};
|
|
157
|
+
var formatQuantityAmount = (quantity) => {
|
|
158
|
+
if (!quantity) {
|
|
159
|
+
return { quantity: "", unit: "" };
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
quantity: formatValue(quantity.value),
|
|
163
|
+
unit: quantity.unit || ""
|
|
164
|
+
};
|
|
165
|
+
};
|
|
139
166
|
var generateStepText = (step, recipe) => {
|
|
140
167
|
return step.items.map((item) => {
|
|
141
168
|
if (item.type === "text") {
|
|
142
169
|
return item.value;
|
|
143
170
|
} else if (item.type === "ingredient") {
|
|
144
|
-
const
|
|
145
|
-
if (!
|
|
146
|
-
const
|
|
147
|
-
const quantityText =
|
|
148
|
-
return `${name}${quantityText}`;
|
|
171
|
+
const originalIngredient = recipe.ingredients[item.index];
|
|
172
|
+
if (!originalIngredient) return "";
|
|
173
|
+
const formatted = formatQuantityAmount(originalIngredient.quantity);
|
|
174
|
+
const quantityText = formatted.quantity && formatted.unit ? `(${formatted.quantity}${formatted.unit})` : formatted.quantity ? `(${formatted.quantity})` : "";
|
|
175
|
+
return `${originalIngredient.name}${quantityText}`;
|
|
149
176
|
} else if (item.type === "cookware") {
|
|
150
|
-
|
|
151
|
-
if (!cookware) return "";
|
|
152
|
-
return cookware_display_name(cookware);
|
|
177
|
+
return recipe.cookware[item.index]?.name || "";
|
|
153
178
|
} else if (item.type === "timer") {
|
|
154
179
|
const timer = recipe.timers[item.index];
|
|
155
180
|
if (!timer) return "";
|
|
@@ -435,41 +460,7 @@ var optimizeSankeyData = (data) => {
|
|
|
435
460
|
links: normalizedLinks
|
|
436
461
|
};
|
|
437
462
|
};
|
|
438
|
-
function parseRecipeForDisplay(recipe) {
|
|
439
|
-
const flatIngredients = getFlatIngredients(recipe);
|
|
440
|
-
const ingredients = flatIngredients.map((ing) => ({
|
|
441
|
-
name: ing.name,
|
|
442
|
-
quantity: ing.quantity !== null ? String(ing.quantity) : "",
|
|
443
|
-
unit: ing.unit || ""
|
|
444
|
-
}));
|
|
445
|
-
const flatCookware = getFlatCookware(recipe);
|
|
446
|
-
const cookware = flatCookware.map((cw) => ({
|
|
447
|
-
name: cw.name,
|
|
448
|
-
note: cw.note || ""
|
|
449
|
-
}));
|
|
450
|
-
const flatTimers = getFlatTimers(recipe);
|
|
451
|
-
const timers = flatTimers.map((timer) => ({
|
|
452
|
-
name: timer.name || "",
|
|
453
|
-
quantity: timer.quantity !== null ? String(timer.quantity) : "",
|
|
454
|
-
unit: timer.unit || "minutes"
|
|
455
|
-
}));
|
|
456
|
-
const steps = [];
|
|
457
|
-
for (const section of recipe.sections) {
|
|
458
|
-
for (const content of section.content) {
|
|
459
|
-
if (content.type === "step") {
|
|
460
|
-
const stepText = generateStepText(content.value, recipe);
|
|
461
|
-
steps.push({ text: stepText });
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
return {
|
|
466
|
-
ingredients,
|
|
467
|
-
cookware,
|
|
468
|
-
timers,
|
|
469
|
-
steps
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
463
|
|
|
473
|
-
export { extractMetadata, generateSankeyData, generateStepText, optimizeSankeyData
|
|
464
|
+
export { extractMetadata, formatQuantityAmount, formatValue, generateSankeyData, generateStepText, optimizeSankeyData };
|
|
474
465
|
//# sourceMappingURL=index.js.map
|
|
475
466
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/parser.ts","../src/constants.ts","../src/sankey/normalizer.ts","../src/sankey/dag.ts","../src/formatter.ts","../src/sankey/node-builders.ts","../src/sankey/edge-builders.ts","../src/sankey/generator.ts","../src/display.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Cooklang Parser wrapper using official @cooklang/cooklang package.\n *\n * @remarks\n * This module provides a thin wrapper around the official Cooklang parser,\n * re-exporting types and adding metadata extraction utilities.\n *\n * @packageDocumentation\n */\n\nimport {\n CooklangParser,\n CooklangRecipe,\n type Ingredient,\n type Quantity,\n type Section,\n type Step,\n type Content,\n type Item,\n type Value,\n} from \"@cooklang/cooklang\";\n\n// Re-export types for convenience\nexport type {\n CooklangParser,\n CooklangRecipe,\n Ingredient,\n Quantity,\n Section,\n Step,\n Content,\n Item,\n Value,\n};\n\n/**\n * Metadata extracted from a parsed Cooklang recipe.\n *\n * @remarks\n * Contains standard recipe metadata fields plus any custom metadata\n * defined in the recipe using the `>> key: value` syntax.\n */\nexport interface RecipeMetadata {\n /** Recipe title from `>> title:` */\n title?: string;\n /** Recipe description from `>> description:` */\n description?: string;\n /** Array of tags from `>> tags:` */\n tags?: string[];\n /** Formatted cooking time string (e.g., \"prep: 10min, cook: 30min\") */\n cookingTime?: string;\n /** Servings range or value (e.g., \"4\" or \"4-6\") */\n servings?: string;\n /** Additional custom metadata fields */\n [key: string]: unknown;\n}\n\n/**\n * Extracts metadata from a parsed Cooklang recipe.\n *\n * @param recipe - The parsed CooklangRecipe object\n * @returns An object containing extracted metadata fields\n *\n * @example\n * ```ts\n * import { CooklangParser } from \"@cooklang/cooklang\";\n * import { extractMetadata } from \"./parser\";\n *\n * const parser = new CooklangParser();\n * const [recipe] = parser.parse(`\n * >> title: Pasta Carbonara\n * >> servings: 4\n * @pasta{400g} を茹でる\n * `);\n *\n * const metadata = extractMetadata(recipe);\n * // { title: \"Pasta Carbonara\", servings: \"4\" }\n * ```\n */\nexport function extractMetadata(recipe: CooklangRecipe): RecipeMetadata {\n const result: RecipeMetadata = {};\n\n if (recipe.title) {\n result.title = recipe.title;\n }\n\n if (recipe.description) {\n result.description = recipe.description;\n }\n\n if (recipe.tags && recipe.tags.size > 0) {\n result.tags = Array.from(recipe.tags);\n }\n\n if (recipe.time) {\n // Format time object to string\n const timeValue = recipe.time;\n if (typeof timeValue === \"object\" && timeValue !== null) {\n const parts: string[] = [];\n if (\"prep_time\" in timeValue && timeValue.prep_time) {\n parts.push(`prep: ${timeValue.prep_time}`);\n }\n if (\"cook_time\" in timeValue && timeValue.cook_time) {\n parts.push(`cook: ${timeValue.cook_time}`);\n }\n if (\"total_time\" in timeValue && timeValue.total_time) {\n parts.push(`total: ${timeValue.total_time}`);\n }\n if (parts.length > 0) {\n result.cookingTime = parts.join(\", \");\n }\n }\n }\n\n if (recipe.servings) {\n const servings = recipe.servings;\n if (Array.isArray(servings)) {\n result.servings = servings.join(\"-\");\n } else if (typeof servings === \"object\" && servings !== null) {\n result.servings = String(servings);\n }\n }\n\n // Add custom metadata\n if (recipe.custom_metadata && recipe.custom_metadata.size > 0) {\n for (const [key, value] of recipe.custom_metadata) {\n if (!(key in result)) {\n result[key] = value;\n }\n }\n }\n\n return result;\n}\n","/**\n * Constants for Sankey diagram generation\n */\n\n/**\n * Normalization constants for value scaling\n */\nexport const NORMALIZATION = {\n /** Minimum normalized value for logarithmic scale */\n LOG_MIN: 0.1,\n /** Maximum normalized value for logarithmic scale */\n LOG_MAX: 0.3,\n /** Minimum normalized value for linear scale */\n LINEAR_MIN: 0.1,\n /** Range for linear normalization */\n LINEAR_RANGE: 0.2,\n /** Default value when normalization range is zero */\n DEFAULT_VALUE: 1,\n} as const;\n\n/**\n * Default options for Sankey generation\n */\nexport const DEFAULT_FINAL_NODE_NAME = \"完成品\";\nexport const DEFAULT_MIN_LINK_VALUE = 0.1;\nexport const DEFAULT_NORMALIZATION = \"logarithmic\" as const;\n\n/**\n * Node identifiers\n */\nexport const FINAL_NODE_ID = \"final_dish\";\nexport const DEFAULT_SECTION_NAME = \"main\";\n","/**\n * Value normalization utilities for Sankey diagram nodes.\n *\n * @remarks\n * Provides normalization functions to scale ingredient values for\n * balanced Sankey diagram visualization. Supports logarithmic, linear,\n * and no normalization modes.\n *\n * @packageDocumentation\n */\n\nimport { NORMALIZATION } from \"../constants\";\nimport type { SankeyNode } from \"../types/sankey\";\n\n/**\n * Normalizes ingredient node values using the specified method.\n *\n * @remarks\n * Returns a Map for O(1) lookup of normalized values by node ID.\n * - **logarithmic**: Compresses large value ranges using log10 scale\n * - **linear**: Scales values linearly within a fixed range\n * - **none**: Returns original values unchanged\n *\n * @param ingredientNodes - Array of ingredient nodes to normalize\n * @param normalization - The normalization method to apply\n * @returns A Map of node ID to normalized value\n *\n * @example\n * ```ts\n * const nodes: SankeyNode[] = [\n * { id: \"0\", name: \"flour\", value: 500, ... },\n * { id: \"1\", name: \"salt\", value: 5, ... }\n * ];\n *\n * const normalized = normalizeIngredientValues(nodes, \"logarithmic\");\n * // Map { \"0\" => 0.3, \"1\" => 0.1 }\n * ```\n */\nexport const normalizeIngredientValues = (\n ingredientNodes: SankeyNode[],\n normalization: \"logarithmic\" | \"linear\" | \"none\",\n): Map<string, number> => {\n const result = new Map<string, number>();\n\n if (normalization === \"none\") {\n ingredientNodes.forEach((node) => {\n result.set(node.id, node.value || NORMALIZATION.DEFAULT_VALUE);\n });\n return result;\n }\n\n const nodeValues = ingredientNodes.map((n) => n.value || NORMALIZATION.DEFAULT_VALUE);\n const minValue = Math.min(...nodeValues);\n const maxValue = Math.max(...nodeValues);\n const valueRange = maxValue - minValue;\n\n const normalizeValue = (value: number): number => {\n if (valueRange === 0) return NORMALIZATION.DEFAULT_VALUE;\n\n if (normalization === \"logarithmic\") {\n const normalizedLinear = (value - minValue) / valueRange;\n const logScale = Math.log10(1 + normalizedLinear * 9);\n return Math.min(Math.max(NORMALIZATION.LOG_MIN, logScale), NORMALIZATION.LOG_MAX);\n } else {\n return (\n NORMALIZATION.LINEAR_MIN + ((value - minValue) / valueRange) * NORMALIZATION.LINEAR_RANGE\n );\n }\n };\n\n ingredientNodes.forEach((node) => {\n result.set(node.id, node.value ? normalizeValue(node.value) : NORMALIZATION.DEFAULT_VALUE);\n });\n\n return result;\n};\n","/**\n * DAG (Directed Acyclic Graph) operations for Sankey diagram generation.\n *\n * @remarks\n * Provides topological sorting and value propagation algorithms\n * for computing flow values through the recipe graph.\n *\n * @packageDocumentation\n */\n\nimport toposort from \"toposort\";\nimport type { DAGNode, DAGEdge } from \"../types/sankey\";\n\n/**\n * Builds a DAG from nodes and edges, returning topologically sorted node IDs.\n *\n * @remarks\n * Uses Kahn's algorithm via the `toposort` library. If a cycle is detected,\n * falls back to returning node IDs in their original order.\n *\n * @param nodes - Array of DAG nodes\n * @param edges - Array of DAG edges defining dependencies\n * @returns Array of node IDs in topological order (dependencies before dependents)\n *\n * @example\n * ```ts\n * const nodes = [ingredientNode, stepNode, finalNode];\n * const edges = [\n * { from: ingredientNode.id, to: stepNode.id },\n * { from: stepNode.id, to: finalNode.id }\n * ];\n *\n * const sorted = buildDAGAndSort(nodes, edges);\n * // [ingredientNode.id, stepNode.id, finalNode.id]\n * ```\n */\nexport const buildDAGAndSort = (nodes: DAGNode[], edges: DAGEdge[]): string[] => {\n const edgePairs: Array<[string, string]> = edges.map((edge) => [edge.from, edge.to]);\n\n try {\n return toposort(edgePairs);\n } catch (error) {\n console.error(\"Cycle detected in DAG:\", error);\n return nodes.map((node) => node.id);\n }\n};\n\n/**\n * Calculates node values by propagating flow through the DAG.\n *\n * @remarks\n * Ingredient nodes retain their original values. Process and final nodes\n * accumulate values from their inputs. When a node has multiple outputs,\n * its value is split equally among them.\n *\n * @param nodes - Array of DAG nodes with input/output connections\n * @param sortedNodeIds - Node IDs in topological order\n * @returns A Map of node ID to calculated flow value\n *\n * @example\n * ```ts\n * // Given: flour(500) -> step1 -> final\n * // salt(5) -> step1\n * const values = calculateNodeValues(nodes, sortedIds);\n * // Map { \"flour\" => 500, \"salt\" => 5, \"step1\" => 505, \"final\" => 505 }\n * ```\n */\nexport const calculateNodeValues = (\n nodes: DAGNode[],\n sortedNodeIds: string[],\n): Map<string, number> => {\n const nodeMap = new Map<string, DAGNode>();\n const valueMap = new Map<string, number>();\n\n nodes.forEach((node) => {\n nodeMap.set(node.id, node);\n if (node.category === \"ingredient\") {\n valueMap.set(node.id, node.value);\n } else {\n valueMap.set(node.id, 0);\n }\n });\n\n for (const nodeId of sortedNodeIds) {\n const node = nodeMap.get(nodeId);\n if (!node) continue;\n\n if (node.category === \"ingredient\") {\n continue;\n }\n\n let totalInputValue = 0;\n for (const inputId of node.inputs) {\n let inputValue = valueMap.get(inputId) || 0;\n const inputNode = nodeMap.get(inputId);\n if (inputNode && inputNode.outputs.length > 1) {\n inputValue /= inputNode.outputs.length;\n }\n totalInputValue += inputValue;\n }\n\n valueMap.set(nodeId, totalInputValue);\n }\n\n return valueMap;\n};\n","/**\n * Step text generation utilities using official @cooklang/cooklang package.\n *\n * @remarks\n * Provides utilities for converting Cooklang step items into human-readable\n * string representations using official display functions.\n *\n * @packageDocumentation\n */\n\nimport {\n ingredient_display_name,\n cookware_display_name,\n quantity_display,\n type CooklangRecipe,\n type Step,\n} from \"@cooklang/cooklang\";\n\n/**\n * Generates complete text from step items by resolving references.\n *\n * @remarks\n * Converts a Step's items array into a readable string by:\n * - Keeping text items as-is\n * - Resolving ingredient references to \"name(quantity)\" format using official display functions\n * - Resolving cookware references to their display names\n * - Resolving timer references to their display values\n *\n * @param step - The Step object containing items to format\n * @param recipe - The parent CooklangRecipe for resolving references\n * @returns A concatenated string of all step items\n *\n * @example\n * ```ts\n * // For a step with text \"Cook \" + ingredient(pasta, 400g) + \" until done\"\n * generateStepText(step, recipe);\n * // \"Cook pasta(400 g) until done\"\n * ```\n */\nexport const generateStepText = (step: Step, recipe: CooklangRecipe): string => {\n return step.items\n .map((item) => {\n if (item.type === \"text\") {\n return item.value;\n } else if (item.type === \"ingredient\") {\n const ingredient = recipe.ingredients[item.index];\n if (!ingredient) return \"\";\n\n const name = ingredient_display_name(ingredient);\n const quantityText = ingredient.quantity\n ? `(${quantity_display(ingredient.quantity)})`\n : \"\";\n\n return `${name}${quantityText}`;\n } else if (item.type === \"cookware\") {\n const cookware = recipe.cookware[item.index];\n if (!cookware) return \"\";\n return cookware_display_name(cookware);\n } else if (item.type === \"timer\") {\n const timer = recipe.timers[item.index];\n if (!timer) return \"\";\n return timer.name || (timer.quantity ? quantity_display(timer.quantity) : \"\");\n }\n return \"\";\n })\n .join(\"\");\n};\n\n// Re-export useful functions from official package\nexport { ingredient_display_name, cookware_display_name, quantity_display };\n","/**\n * Node builders for Sankey diagram generation.\n *\n * @remarks\n * Factory functions for creating DAG nodes from Cooklang recipe elements.\n * Handles ingredients, process steps, and the final dish node.\n *\n * @packageDocumentation\n */\n\nimport {\n type CooklangRecipe,\n getNumericValue,\n grouped_quantity_display,\n grouped_quantity_is_empty,\n type Quantity,\n} from \"@cooklang/cooklang\";\nimport { generateStepText } from \"../formatter\";\nimport { DEFAULT_SECTION_NAME, FINAL_NODE_ID } from \"../constants\";\nimport type { DAGNode, NodeCategory, ExtractedValue } from \"../types/sankey\";\n\n/**\n * Extracts a numeric or text value from a Cooklang GroupedQuantity.\n *\n * @remarks\n * Attempts to extract a numeric value from fixed, scalable, or unknown\n * quantity types. Falls back to text representation if no numeric value\n * is available.\n *\n * @param groupedQuantity - The GroupedQuantity object from Cooklang parser\n * @returns An ExtractedValue with either numeric value or text label\n *\n * @example\n * ```ts\n * // For \"200g\" quantity\n * calculateIngredientQuantity(gq);\n * // { type: \"number\", value: 200, label: \"200g\" }\n *\n * // For \"some\" quantity\n * calculateIngredientQuantity(gq);\n * // { type: \"text\", label: \"some\" }\n * ```\n */\nexport const calculateIngredientQuantity = (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n groupedQuantity: any,\n): ExtractedValue => {\n if (grouped_quantity_is_empty(groupedQuantity)) {\n return { type: \"text\", label: \"unknown\" };\n }\n\n const displayText = grouped_quantity_display(groupedQuantity);\n\n const gq = groupedQuantity as {\n fixed?: Quantity | null;\n scalable?: Quantity | null;\n fixed_unknown?: Quantity | null;\n scalable_unknown?: Quantity | null;\n };\n\n const quantityToUse = gq.fixed || gq.scalable || gq.fixed_unknown || gq.scalable_unknown;\n\n if (quantityToUse) {\n const numericValue = getNumericValue(quantityToUse.value);\n if (numericValue !== null) {\n return { type: \"number\", value: numericValue, label: displayText || `${numericValue}` };\n }\n }\n\n return { type: \"text\", label: displayText || \"unknown\" };\n};\n\n/**\n * Creates DAG nodes for all ingredients in a recipe.\n *\n * @remarks\n * Processes the recipe's grouped ingredients to create nodes with:\n * - Unique IDs based on ingredient index\n * - Extracted quantity values for flow calculation\n * - Display labels with quantity information\n *\n * @param recipe - The parsed CooklangRecipe\n * @returns An object containing:\n * - `nodes`: Array of ingredient DAGNodes\n * - `indexMap`: Map from original ingredient index to node ID\n *\n * @example\n * ```ts\n * const { nodes, indexMap } = createIngredientNodes(recipe);\n * // nodes: [{ id: \"0\", name: \"flour\", value: 500, ... }, ...]\n * // indexMap: Map { 0 => \"0\", 1 => \"1\", ... }\n * ```\n */\nexport const createIngredientNodes = (\n recipe: CooklangRecipe,\n): { nodes: DAGNode[]; indexMap: Map<number, string> } => {\n const nodes: DAGNode[] = [];\n const indexMap = new Map<number, string>();\n\n recipe.groupedIngredients.forEach(([ingredient, groupedQuantity], index) => {\n const extracted = calculateIngredientQuantity(groupedQuantity);\n const value = extracted.type === \"number\" ? extracted.value : 1;\n const label = extracted.label;\n const ingredientId = index.toString();\n\n indexMap.set(index, ingredientId);\n\n nodes.push({\n id: ingredientId,\n name: ingredient.name,\n category: \"ingredient\" as NodeCategory,\n value,\n originalValue: value,\n label,\n inputs: [],\n outputs: [],\n metadata: {\n originalIndex: index,\n },\n });\n });\n\n return { nodes, indexMap };\n};\n\n/**\n * Creates DAG nodes for all cooking steps in a recipe.\n *\n * @remarks\n * Iterates through all sections and steps, creating process nodes with:\n * - Unique IDs based on section name and step number\n * - Generated text from step items (ingredients, cookware, timers)\n * - Initial value of 0 (calculated later via flow propagation)\n *\n * @param recipe - The parsed CooklangRecipe\n * @returns Array of process DAGNodes representing cooking steps\n *\n * @example\n * ```ts\n * const stepNodes = createStepNodes(recipe);\n * // [{ id: \"step_main_1\", name: \"Boil pasta(400g)\", category: \"process\", ... }]\n * ```\n */\nexport const createStepNodes = (recipe: CooklangRecipe): DAGNode[] => {\n const nodes: DAGNode[] = [];\n\n for (const section of recipe.sections) {\n for (const content of section.content) {\n if (content.type === \"step\") {\n const step = content.value;\n const stepText = generateStepText(step, recipe);\n const stepId = `step_${section.name || DEFAULT_SECTION_NAME}_${step.number}`;\n\n nodes.push({\n id: stepId,\n name: stepText || `手順 ${step.number}`,\n category: \"process\" as NodeCategory,\n value: 0,\n label: `${step.number}`,\n inputs: [],\n outputs: [],\n metadata: {\n stepNumber: step.number,\n sectionName: section.name || undefined,\n },\n });\n }\n }\n }\n\n return nodes;\n};\n\n/**\n * Creates the final node representing the completed dish.\n *\n * @remarks\n * The final node is the sink of the Sankey diagram where all\n * process flows converge. Its value is calculated by summing\n * all incoming flows.\n *\n * @param finalNodeName - Display name for the final dish node\n * @returns A DAGNode with category \"final\"\n *\n * @example\n * ```ts\n * const finalNode = createFinalNode(\"Carbonara\");\n * // { id: \"final_dish\", name: \"Carbonara\", category: \"final\", ... }\n * ```\n */\nexport const createFinalNode = (finalNodeName: string): DAGNode => ({\n id: FINAL_NODE_ID,\n name: finalNodeName,\n category: \"final\" as NodeCategory,\n value: 0,\n label: \"\",\n inputs: [],\n outputs: [],\n});\n","/**\n * Edge builders for Sankey diagram generation.\n *\n * @remarks\n * Factory functions for creating DAG edges that connect nodes.\n * Handles ingredient-to-step and step-to-final connections,\n * including step reference resolution.\n *\n * @packageDocumentation\n */\n\nimport { type CooklangRecipe, type Step } from \"@cooklang/cooklang\";\nimport { DEFAULT_SECTION_NAME } from \"../constants\";\nimport type { DAGNode, DAGEdge, TransformationType } from \"../types/sankey\";\n\n/**\n * Generates a unique step ID from section name and step number.\n *\n * @param sectionName - The recipe section name (null uses default)\n * @param stepNumber - The 1-indexed step number\n * @returns A unique step ID string (e.g., \"step_main_1\")\n *\n * @example\n * ```ts\n * getStepId(\"main\", 1); // \"step_main_1\"\n * getStepId(null, 2); // \"step_main_2\"\n * getStepId(\"sauce\", 1); // \"step_sauce_1\"\n * ```\n */\nexport const getStepId = (sectionName: string | null | undefined, stepNumber: number): string => {\n return `step_${sectionName || DEFAULT_SECTION_NAME}_${stepNumber}`;\n};\n\n/**\n * Extracts ingredient usage information from a recipe step.\n *\n * @param step - The Step object to analyze\n * @returns Array of objects containing ingredient index and step number\n *\n * @example\n * ```ts\n * // For a step using @pasta and @salt\n * extractIngredientUsageFromStep(step);\n * // [{ ingredientIndex: 0, stepNumber: 1 }, { ingredientIndex: 1, stepNumber: 1 }]\n * ```\n */\nexport const extractIngredientUsageFromStep = (\n step: Step,\n): Array<{ ingredientIndex: number; stepNumber: number }> => {\n const usages: Array<{ ingredientIndex: number; stepNumber: number }> = [];\n\n for (const item of step.items) {\n if (item.type === \"ingredient\") {\n usages.push({\n ingredientIndex: item.index,\n stepNumber: step.number,\n });\n }\n }\n\n return usages;\n};\n\n/**\n * Builds edges connecting ingredients to their consuming steps.\n *\n * @remarks\n * Analyzes the recipe to create edges from:\n * - Ingredient nodes to step nodes (for direct ingredient usage)\n * - Step nodes to other step nodes (for step references like \"the pasta from step 1\")\n *\n * Also updates the input/output arrays on the nodes for DAG traversal.\n *\n * @param recipe - The parsed CooklangRecipe\n * @param ingredientIndexMap - Map from ingredient index to node ID\n * @param nodes - Array of all DAG nodes (modified in place)\n * @returns Array of DAGEdges connecting ingredients/steps to steps\n *\n * @example\n * ```ts\n * const edges = buildIngredientToStepEdges(recipe, indexMap, nodes);\n * // [{ from: \"0\", to: \"step_main_1\", metadata: { ... } }, ...]\n * ```\n */\nexport const buildIngredientToStepEdges = (\n recipe: CooklangRecipe,\n ingredientIndexMap: Map<number, string>,\n nodes: DAGNode[],\n): DAGEdge[] => {\n const edges: DAGEdge[] = [];\n const nodeMap = new Map<string, DAGNode>();\n\n nodes.forEach((node) => nodeMap.set(node.id, node));\n\n for (const section of recipe.sections) {\n for (const content of section.content) {\n if (content.type === \"step\") {\n const step = content.value;\n const currentStepId = getStepId(section.name, step.number);\n const usedIngredients = extractIngredientUsageFromStep(step);\n\n for (const usage of usedIngredients) {\n const ingredient = recipe.ingredients[usage.ingredientIndex];\n\n // Check for step reference\n const relation = ingredient.relation;\n if (\n relation &&\n relation.relation?.type === \"reference\" &&\n relation.reference_target === \"step\"\n ) {\n const referenceTo = relation.relation.references_to;\n const sourceStepId = getStepId(section.name, referenceTo + 1);\n edges.push({\n from: sourceStepId,\n to: currentStepId,\n metadata: {\n stepNumber: step.number,\n transformationType: \"cooking\" as TransformationType,\n },\n });\n\n const sourceNode = nodeMap.get(sourceStepId);\n const targetNode = nodeMap.get(currentStepId);\n if (sourceNode && targetNode) {\n sourceNode.outputs.push(currentStepId);\n targetNode.inputs.push(sourceStepId);\n }\n continue;\n }\n\n const ingredientId = ingredientIndexMap.get(usage.ingredientIndex);\n if (ingredientId) {\n edges.push({\n from: ingredientId,\n to: currentStepId,\n metadata: {\n stepNumber: step.number,\n transformationType: \"preparation\" as TransformationType,\n },\n });\n\n const ingredientNode = nodeMap.get(ingredientId);\n const stepNode = nodeMap.get(currentStepId);\n if (ingredientNode && stepNode) {\n ingredientNode.outputs.push(currentStepId);\n stepNode.inputs.push(ingredientId);\n }\n }\n }\n }\n }\n }\n\n return edges;\n};\n\n/**\n * Builds an edge from the last step to the final dish node.\n *\n * @remarks\n * Connects only the last step to the final node, representing\n * the completion of the recipe. Also updates the input/output\n * arrays on the affected nodes.\n *\n * @param stepNodes - Array of step nodes in order\n * @param finalNode - The final dish node (modified in place)\n * @returns Array containing a single edge to the final node, or empty if no steps\n *\n * @example\n * ```ts\n * const edges = buildStepToFinalEdges(stepNodes, finalNode);\n * // [{ from: \"step_main_3\", to: \"final_dish\", metadata: { transformationType: \"completion\" } }]\n * ```\n */\nexport const buildStepToFinalEdges = (stepNodes: DAGNode[], finalNode: DAGNode): DAGEdge[] => {\n if (stepNodes.length === 0) return [];\n\n const lastStepNode = stepNodes[stepNodes.length - 1];\n\n lastStepNode.outputs.push(finalNode.id);\n finalNode.inputs.push(lastStepNode.id);\n\n return [\n {\n from: lastStepNode.id,\n to: finalNode.id,\n metadata: {\n transformationType: \"completion\" as TransformationType,\n },\n },\n ];\n};\n","/**\n * Sankey diagram data generator from Cooklang recipes.\n *\n * @remarks\n * Main module for transforming parsed Cooklang recipes into\n * Sankey diagram data structures. Orchestrates the DAG building,\n * value calculation, and normalization pipeline.\n *\n * @packageDocumentation\n */\n\nimport type { CooklangRecipe } from \"@cooklang/cooklang\";\nimport {\n DEFAULT_FINAL_NODE_NAME,\n DEFAULT_MIN_LINK_VALUE,\n DEFAULT_NORMALIZATION,\n} from \"../constants\";\nimport type { SankeyNode, SankeyLink, SankeyData, DAGNode, DAGEdge } from \"../types/sankey\";\nimport { normalizeIngredientValues } from \"./normalizer\";\nimport { buildDAGAndSort, calculateNodeValues } from \"./dag\";\nimport { createIngredientNodes, createStepNodes, createFinalNode } from \"./node-builders\";\nimport { buildIngredientToStepEdges, buildStepToFinalEdges } from \"./edge-builders\";\n\n/**\n * Configuration options for Sankey diagram generation.\n *\n * @example\n * ```ts\n * const options: SankeyGeneratorOptions = {\n * finalNodeName: \"Carbonara\",\n * normalization: \"logarithmic\",\n * minLinkValue: 0.1\n * };\n * ```\n */\nexport interface SankeyGeneratorOptions {\n /**\n * Display name for the final dish node.\n * @defaultValue \"完成品\"\n */\n finalNodeName?: string;\n /**\n * Method for normalizing ingredient values.\n * - `logarithmic`: Compress large ranges (recommended)\n * - `linear`: Linear scaling\n * - `none`: Use raw values\n * @defaultValue \"logarithmic\"\n */\n normalization?: \"logarithmic\" | \"linear\" | \"none\";\n /**\n * Minimum value for links to ensure visibility.\n * @defaultValue 0.1\n */\n minLinkValue?: number;\n}\n\nconst DEFAULT_OPTIONS: Required<SankeyGeneratorOptions> = {\n finalNodeName: DEFAULT_FINAL_NODE_NAME,\n normalization: DEFAULT_NORMALIZATION,\n minLinkValue: DEFAULT_MIN_LINK_VALUE,\n};\n\n/**\n * Transforms DAG nodes and edges into final Sankey diagram data.\n *\n * @remarks\n * Internal function that:\n * 1. Normalizes ingredient values using the specified method\n * 2. Calculates flow values for all nodes via DAG propagation\n * 3. Converts DAG structures to Sankey-compatible format\n * 4. Applies minimum value constraints to links\n *\n * @param dagNodes - Array of DAG nodes with dependency info\n * @param dagEdges - Array of DAG edges\n * @param sortedNodeIds - Node IDs in topological order\n * @param options - Generation options with all defaults applied\n * @returns Complete SankeyData structure ready for visualization\n *\n * @internal\n */\nconst buildSankeyData = (\n dagNodes: DAGNode[],\n dagEdges: DAGEdge[],\n sortedNodeIds: string[],\n options: Required<SankeyGeneratorOptions>,\n): SankeyData => {\n const ingredientNodes = dagNodes\n .filter((node) => node.category === \"ingredient\")\n .map((node) => ({\n id: node.id,\n name: node.name,\n category: node.category,\n value: node.value,\n label: node.label,\n originalValue: node.originalValue,\n metadata: node.metadata,\n }));\n\n const normalizedValueMap = normalizeIngredientValues(ingredientNodes, options.normalization);\n\n const normalizedDAGNodes = dagNodes.map((node) => {\n if (node.category === \"ingredient\") {\n return {\n ...node,\n value: normalizedValueMap.get(node.id) ?? node.value,\n };\n }\n return node;\n });\n\n const finalCalculatedValues = calculateNodeValues(normalizedDAGNodes, sortedNodeIds);\n\n const sankeyNodes: SankeyNode[] = dagNodes.map((dagNode) => {\n const finalValue = finalCalculatedValues.get(dagNode.id) || 0;\n return {\n id: dagNode.id,\n name: dagNode.name,\n category: dagNode.category,\n value:\n dagNode.category === \"ingredient\"\n ? (normalizedValueMap.get(dagNode.id) ?? dagNode.value)\n : finalValue,\n label: dagNode.label,\n originalValue: dagNode.originalValue,\n metadata: dagNode.metadata,\n };\n });\n\n const sankeyLinks: SankeyLink[] = dagEdges.map((edge) => {\n const sourceValue = finalCalculatedValues.get(edge.from) || 0;\n const sourceNode = dagNodes.find((n) => n.id === edge.from);\n\n let linkValue = sourceValue;\n if (sourceNode && sourceNode.outputs.length > 1) {\n linkValue = sourceValue / sourceNode.outputs.length;\n }\n\n return {\n source: edge.from,\n target: edge.to,\n value: Math.max(options.minLinkValue, linkValue),\n originalValue: sourceValue,\n metadata: edge.metadata,\n };\n });\n\n return {\n nodes: sankeyNodes,\n links: sankeyLinks,\n };\n};\n\n/**\n * Generates Sankey diagram data from a parsed Cooklang recipe.\n *\n * @remarks\n * This is the main entry point for the library. It transforms a parsed\n * Cooklang recipe into a Sankey diagram data structure by:\n * 1. Creating nodes for ingredients, steps, and the final dish\n * 2. Building edges representing ingredient flow\n * 3. Performing topological sort for correct value propagation\n * 4. Normalizing values for balanced visualization\n *\n * @param recipe - A parsed CooklangRecipe object\n * @param options - Optional configuration for generation\n * @returns SankeyData structure, or null if generation fails\n *\n * @example\n * ```ts\n * import { CooklangParser, generateSankeyData } from 'cooklang-sankey';\n *\n * const parser = new CooklangParser();\n * const [recipe] = parser.parse(`\n * @pasta{400g} を茹でる。\n * @卵{3個}と@チーズ{100g}を混ぜる。\n * `);\n *\n * const data = generateSankeyData(recipe, {\n * finalNodeName: \"Carbonara\",\n * normalization: \"logarithmic\"\n * });\n * ```\n */\nexport const generateSankeyData = (\n recipe: CooklangRecipe,\n options?: SankeyGeneratorOptions,\n): SankeyData | null => {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n try {\n const { nodes: ingredientNodes, indexMap: ingredientIndexMap } = createIngredientNodes(recipe);\n\n const stepNodes = createStepNodes(recipe);\n const finalNode = createFinalNode(opts.finalNodeName);\n\n const allNodes = [...ingredientNodes, ...stepNodes, finalNode];\n\n const ingredientToStepEdges = buildIngredientToStepEdges(recipe, ingredientIndexMap, allNodes);\n const stepToFinalEdges = buildStepToFinalEdges(stepNodes, finalNode);\n const allEdges = [...ingredientToStepEdges, ...stepToFinalEdges];\n\n const sortedNodeIds = buildDAGAndSort(allNodes, allEdges);\n\n return buildSankeyData(allNodes, allEdges, sortedNodeIds, opts);\n } catch (error) {\n console.error(\"Error generating sankey data from cooklang:\", error);\n return null;\n }\n};\n\n/**\n * Validates and optimizes Sankey data for visualization.\n *\n * @remarks\n * Performs three optimization steps:\n * 1. **Remove invalid links**: Filters out links referencing non-existent nodes\n * 2. **Remove orphaned nodes**: Keeps only nodes connected to valid links\n * (final nodes are always preserved)\n * 3. **Normalize link values**: Scales values relative to minimum for\n * stable visualization (ensures all values >= 1)\n *\n * @param data - The SankeyData to optimize\n * @returns A new SankeyData with optimizations applied\n *\n * @example\n * ```ts\n * const rawData = generateSankeyData(recipe);\n * if (rawData) {\n * const optimized = optimizeSankeyData(rawData);\n * // Use optimized data for visualization\n * }\n * ```\n */\nexport const optimizeSankeyData = (data: SankeyData): SankeyData => {\n // Create a Set of existing node IDs for O(1) lookup\n const existingNodeIds = new Set(data.nodes.map((node) => node.id));\n\n // Step 1: Remove invalid links\n // Filter out links where source or target node doesn't exist\n const validLinks = data.links.filter((link) => {\n const sourceExists = existingNodeIds.has(link.source);\n const targetExists = existingNodeIds.has(link.target);\n\n if (!sourceExists) {\n console.error(`Missing source node: ${link.source}`);\n }\n if (!targetExists) {\n console.error(`Missing target node: ${link.target}`);\n }\n\n return sourceExists && targetExists;\n });\n\n // Step 2: Remove orphaned nodes\n // Collect node IDs that are connected to valid links\n const connectedNodeIds = new Set<string>();\n validLinks.forEach((link) => {\n connectedNodeIds.add(link.source);\n connectedNodeIds.add(link.target);\n });\n\n // Keep only nodes connected to links, or final nodes (always preserved)\n const filteredNodes = data.nodes.filter(\n (node) => connectedNodeIds.has(node.id) || node.category === \"final\",\n );\n\n // Step 3: Normalize link values\n // Scale values relative to minimum for stable visualization\n let normalizedLinks = validLinks;\n if (validLinks.length > 0) {\n const minValue = Math.min(...validLinks.map((link) => link.value));\n if (minValue > 0) {\n // Divide by min value and clamp to ensure value >= 1\n normalizedLinks = validLinks.map((link) => ({\n ...link,\n value: Math.max(1, link.value / minValue),\n }));\n }\n }\n\n return {\n nodes: filteredNodes,\n links: normalizedLinks,\n };\n};\n","/**\n * Recipe display utilities for UI rendering.\n *\n * @remarks\n * Provides utilities for converting parsed CooklangRecipe objects into\n * display-ready data structures suitable for UI rendering.\n *\n * Uses the official @cooklang/cooklang package's convenience functions\n * for accurate quantity aggregation and formatting.\n *\n * @packageDocumentation\n */\n\nimport {\n type CooklangRecipe,\n getFlatIngredients,\n getFlatCookware,\n getFlatTimers,\n} from \"@cooklang/cooklang\";\nimport { generateStepText } from \"./formatter\";\nimport type {\n DisplayRecipe,\n DisplayIngredient,\n DisplayCookware,\n DisplayTimer,\n DisplayStep,\n} from \"./types/display\";\n\n/**\n * Parses a CooklangRecipe into a display-ready format.\n *\n * @remarks\n * Uses the official @cooklang/cooklang package's convenience functions\n * (`getFlatIngredients`, `getFlatCookware`, `getFlatTimers`) for proper\n * handling of grouped quantities.\n *\n * @param recipe - The parsed CooklangRecipe object\n * @returns DisplayRecipe with formatted data for UI rendering\n *\n * @example\n * ```ts\n * import { CooklangParser, parseRecipeForDisplay } from '@4kk11/cooklang-sankey';\n *\n * const parser = new CooklangParser();\n * const [recipe] = parser.parse(recipeText);\n * const displayData = parseRecipeForDisplay(recipe);\n *\n * // Render ingredients\n * displayData.ingredients.forEach(ing => {\n * console.log(`${ing.name}: ${ing.quantity}${ing.unit}`);\n * });\n *\n * // Render steps\n * displayData.steps.forEach((step, i) => {\n * console.log(`${i + 1}. ${step.text}`);\n * });\n * ```\n */\nexport function parseRecipeForDisplay(recipe: CooklangRecipe): DisplayRecipe {\n // Use getFlatIngredients which properly handles grouped quantities\n const flatIngredients = getFlatIngredients(recipe);\n const ingredients: DisplayIngredient[] = flatIngredients.map((ing) => ({\n name: ing.name,\n quantity: ing.quantity !== null ? String(ing.quantity) : \"\",\n unit: ing.unit || \"\",\n }));\n\n // Use getFlatCookware for cookware\n const flatCookware = getFlatCookware(recipe);\n const cookware: DisplayCookware[] = flatCookware.map((cw) => ({\n name: cw.name,\n note: cw.note || \"\",\n }));\n\n // Use getFlatTimers for timers\n const flatTimers = getFlatTimers(recipe);\n const timers: DisplayTimer[] = flatTimers.map((timer) => ({\n name: timer.name || \"\",\n quantity: timer.quantity !== null ? String(timer.quantity) : \"\",\n unit: timer.unit || \"minutes\",\n }));\n\n // Generate step texts\n const steps: DisplayStep[] = [];\n for (const section of recipe.sections) {\n for (const content of section.content) {\n if (content.type === \"step\") {\n const stepText = generateStepText(content.value, recipe);\n steps.push({ text: stepText });\n }\n }\n }\n\n return {\n ingredients,\n cookware,\n timers,\n steps,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/parser.ts","../src/constants.ts","../src/sankey/normalizer.ts","../src/sankey/dag.ts","../src/formatter.ts","../src/sankey/node-builders.ts","../src/sankey/edge-builders.ts","../src/sankey/generator.ts"],"names":["getNumericValue"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Cooklang Parser wrapper using official @cooklang/cooklang package.\n *\n * @remarks\n * This module provides a thin wrapper around the official Cooklang parser,\n * re-exporting types and adding metadata extraction utilities.\n *\n * @packageDocumentation\n */\n\nimport {\n CooklangParser,\n CooklangRecipe,\n type Ingredient,\n type Quantity,\n type Section,\n type Step,\n type Content,\n type Item,\n type Value,\n} from \"@cooklang/cooklang\";\n\n// Re-export types for convenience\nexport type {\n CooklangParser,\n CooklangRecipe,\n Ingredient,\n Quantity,\n Section,\n Step,\n Content,\n Item,\n Value,\n};\n\n/**\n * Metadata extracted from a parsed Cooklang recipe.\n *\n * @remarks\n * Contains standard recipe metadata fields plus any custom metadata\n * defined in the recipe using the `>> key: value` syntax.\n */\nexport interface RecipeMetadata {\n /** Recipe title from `>> title:` */\n title?: string;\n /** Recipe description from `>> description:` */\n description?: string;\n /** Array of tags from `>> tags:` */\n tags?: string[];\n /** Formatted cooking time string (e.g., \"prep: 10min, cook: 30min\") */\n cookingTime?: string;\n /** Servings range or value (e.g., \"4\" or \"4-6\") */\n servings?: string;\n /** Additional custom metadata fields */\n [key: string]: unknown;\n}\n\n/**\n * Extracts metadata from a parsed Cooklang recipe.\n *\n * @param recipe - The parsed CooklangRecipe object\n * @returns An object containing extracted metadata fields\n *\n * @example\n * ```ts\n * import { CooklangParser } from \"@cooklang/cooklang\";\n * import { extractMetadata } from \"./parser\";\n *\n * const parser = new CooklangParser();\n * const [recipe] = parser.parse(`\n * >> title: Pasta Carbonara\n * >> servings: 4\n * @pasta{400g} を茹でる\n * `);\n *\n * const metadata = extractMetadata(recipe);\n * // { title: \"Pasta Carbonara\", servings: \"4\" }\n * ```\n */\nexport function extractMetadata(recipe: CooklangRecipe): RecipeMetadata {\n const result: RecipeMetadata = {};\n\n if (recipe.title) {\n result.title = recipe.title;\n }\n\n if (recipe.description) {\n result.description = recipe.description;\n }\n\n if (recipe.tags && recipe.tags.size > 0) {\n result.tags = Array.from(recipe.tags);\n }\n\n if (recipe.time) {\n // Format time object to string\n const timeValue = recipe.time;\n if (typeof timeValue === \"object\" && timeValue !== null) {\n const parts: string[] = [];\n if (\"prep_time\" in timeValue && timeValue.prep_time) {\n parts.push(`prep: ${timeValue.prep_time}`);\n }\n if (\"cook_time\" in timeValue && timeValue.cook_time) {\n parts.push(`cook: ${timeValue.cook_time}`);\n }\n if (\"total_time\" in timeValue && timeValue.total_time) {\n parts.push(`total: ${timeValue.total_time}`);\n }\n if (parts.length > 0) {\n result.cookingTime = parts.join(\", \");\n }\n }\n }\n\n if (recipe.servings) {\n const servings = recipe.servings;\n if (Array.isArray(servings)) {\n result.servings = servings.join(\"-\");\n } else if (typeof servings === \"object\" && servings !== null) {\n result.servings = String(servings);\n }\n }\n\n // Add custom metadata\n if (recipe.custom_metadata && recipe.custom_metadata.size > 0) {\n for (const [key, value] of recipe.custom_metadata) {\n if (!(key in result)) {\n result[key] = value;\n }\n }\n }\n\n return result;\n}\n","/**\n * Constants for Sankey diagram generation\n */\n\n/**\n * Normalization constants for value scaling\n */\nexport const NORMALIZATION = {\n /** Minimum normalized value for logarithmic scale */\n LOG_MIN: 0.1,\n /** Maximum normalized value for logarithmic scale */\n LOG_MAX: 0.3,\n /** Minimum normalized value for linear scale */\n LINEAR_MIN: 0.1,\n /** Range for linear normalization */\n LINEAR_RANGE: 0.2,\n /** Default value when normalization range is zero */\n DEFAULT_VALUE: 1,\n} as const;\n\n/**\n * Default options for Sankey generation\n */\nexport const DEFAULT_FINAL_NODE_NAME = \"完成品\";\nexport const DEFAULT_MIN_LINK_VALUE = 0.1;\nexport const DEFAULT_NORMALIZATION = \"logarithmic\" as const;\n\n/**\n * Node identifiers\n */\nexport const FINAL_NODE_ID = \"final_dish\";\nexport const DEFAULT_SECTION_NAME = \"main\";\n","/**\n * Value normalization utilities for Sankey diagram nodes.\n *\n * @remarks\n * Provides normalization functions to scale ingredient values for\n * balanced Sankey diagram visualization. Supports logarithmic, linear,\n * and no normalization modes.\n *\n * @packageDocumentation\n */\n\nimport { NORMALIZATION } from \"../constants\";\nimport type { SankeyNode } from \"../types/sankey\";\n\n/**\n * Normalizes ingredient node values using the specified method.\n *\n * @remarks\n * Returns a Map for O(1) lookup of normalized values by node ID.\n * - **logarithmic**: Compresses large value ranges using log10 scale\n * - **linear**: Scales values linearly within a fixed range\n * - **none**: Returns original values unchanged\n *\n * @param ingredientNodes - Array of ingredient nodes to normalize\n * @param normalization - The normalization method to apply\n * @returns A Map of node ID to normalized value\n *\n * @example\n * ```ts\n * const nodes: SankeyNode[] = [\n * { id: \"0\", name: \"flour\", value: 500, ... },\n * { id: \"1\", name: \"salt\", value: 5, ... }\n * ];\n *\n * const normalized = normalizeIngredientValues(nodes, \"logarithmic\");\n * // Map { \"0\" => 0.3, \"1\" => 0.1 }\n * ```\n */\nexport const normalizeIngredientValues = (\n ingredientNodes: SankeyNode[],\n normalization: \"logarithmic\" | \"linear\" | \"none\",\n): Map<string, number> => {\n const result = new Map<string, number>();\n\n if (normalization === \"none\") {\n ingredientNodes.forEach((node) => {\n result.set(node.id, node.value || NORMALIZATION.DEFAULT_VALUE);\n });\n return result;\n }\n\n const nodeValues = ingredientNodes.map((n) => n.value || NORMALIZATION.DEFAULT_VALUE);\n const minValue = Math.min(...nodeValues);\n const maxValue = Math.max(...nodeValues);\n const valueRange = maxValue - minValue;\n\n const normalizeValue = (value: number): number => {\n if (valueRange === 0) return NORMALIZATION.DEFAULT_VALUE;\n\n if (normalization === \"logarithmic\") {\n const normalizedLinear = (value - minValue) / valueRange;\n const logScale = Math.log10(1 + normalizedLinear * 9);\n return Math.min(Math.max(NORMALIZATION.LOG_MIN, logScale), NORMALIZATION.LOG_MAX);\n } else {\n return (\n NORMALIZATION.LINEAR_MIN + ((value - minValue) / valueRange) * NORMALIZATION.LINEAR_RANGE\n );\n }\n };\n\n ingredientNodes.forEach((node) => {\n result.set(node.id, node.value ? normalizeValue(node.value) : NORMALIZATION.DEFAULT_VALUE);\n });\n\n return result;\n};\n","/**\n * DAG (Directed Acyclic Graph) operations for Sankey diagram generation.\n *\n * @remarks\n * Provides topological sorting and value propagation algorithms\n * for computing flow values through the recipe graph.\n *\n * @packageDocumentation\n */\n\nimport toposort from \"toposort\";\nimport type { DAGNode, DAGEdge } from \"../types/sankey\";\n\n/**\n * Builds a DAG from nodes and edges, returning topologically sorted node IDs.\n *\n * @remarks\n * Uses Kahn's algorithm via the `toposort` library. If a cycle is detected,\n * falls back to returning node IDs in their original order.\n *\n * @param nodes - Array of DAG nodes\n * @param edges - Array of DAG edges defining dependencies\n * @returns Array of node IDs in topological order (dependencies before dependents)\n *\n * @example\n * ```ts\n * const nodes = [ingredientNode, stepNode, finalNode];\n * const edges = [\n * { from: ingredientNode.id, to: stepNode.id },\n * { from: stepNode.id, to: finalNode.id }\n * ];\n *\n * const sorted = buildDAGAndSort(nodes, edges);\n * // [ingredientNode.id, stepNode.id, finalNode.id]\n * ```\n */\nexport const buildDAGAndSort = (nodes: DAGNode[], edges: DAGEdge[]): string[] => {\n const edgePairs: Array<[string, string]> = edges.map((edge) => [edge.from, edge.to]);\n\n try {\n return toposort(edgePairs);\n } catch (error) {\n console.error(\"Cycle detected in DAG:\", error);\n return nodes.map((node) => node.id);\n }\n};\n\n/**\n * Calculates node values by propagating flow through the DAG.\n *\n * @remarks\n * Ingredient nodes retain their original values. Process and final nodes\n * accumulate values from their inputs. When a node has multiple outputs,\n * its value is split equally among them.\n *\n * @param nodes - Array of DAG nodes with input/output connections\n * @param sortedNodeIds - Node IDs in topological order\n * @returns A Map of node ID to calculated flow value\n *\n * @example\n * ```ts\n * // Given: flour(500) -> step1 -> final\n * // salt(5) -> step1\n * const values = calculateNodeValues(nodes, sortedIds);\n * // Map { \"flour\" => 500, \"salt\" => 5, \"step1\" => 505, \"final\" => 505 }\n * ```\n */\nexport const calculateNodeValues = (\n nodes: DAGNode[],\n sortedNodeIds: string[],\n): Map<string, number> => {\n const nodeMap = new Map<string, DAGNode>();\n const valueMap = new Map<string, number>();\n\n nodes.forEach((node) => {\n nodeMap.set(node.id, node);\n if (node.category === \"ingredient\") {\n valueMap.set(node.id, node.value);\n } else {\n valueMap.set(node.id, 0);\n }\n });\n\n for (const nodeId of sortedNodeIds) {\n const node = nodeMap.get(nodeId);\n if (!node) continue;\n\n if (node.category === \"ingredient\") {\n continue;\n }\n\n let totalInputValue = 0;\n for (const inputId of node.inputs) {\n let inputValue = valueMap.get(inputId) || 0;\n const inputNode = nodeMap.get(inputId);\n if (inputNode && inputNode.outputs.length > 1) {\n inputValue /= inputNode.outputs.length;\n }\n totalInputValue += inputValue;\n }\n\n valueMap.set(nodeId, totalInputValue);\n }\n\n return valueMap;\n};\n","/**\n * Value formatting utilities using official @cooklang/cooklang package.\n *\n * @remarks\n * Provides utilities for converting Cooklang quantity and value types\n * into human-readable string representations.\n *\n * @packageDocumentation\n */\n\nimport {\n getNumericValue,\n quantity_display,\n type CooklangRecipe,\n type Value,\n type Quantity,\n type Step,\n} from \"@cooklang/cooklang\";\n\n/**\n * Formats a Cooklang Value to a string representation.\n *\n * @param value - The Value object to format (number, range, or text)\n * @returns A string representation of the value, or empty string if null/undefined\n *\n * @example\n * ```ts\n * formatValue({ type: \"number\", value: 200 }); // \"200\"\n * formatValue({ type: \"range\", value: { start: 2, end: 3 } }); // \"2-3\"\n * formatValue({ type: \"text\", value: \"some\" }); // \"some\"\n * formatValue(null); // \"\"\n * ```\n */\nexport const formatValue = (value: Value | null | undefined): string => {\n if (!value) return \"\";\n\n if (value.type === \"number\") {\n const num = getNumericValue(value);\n return num !== null ? num.toString() : \"\";\n } else if (value.type === \"range\") {\n // Range structure: { start, end }\n const rangeValue = value.value as { start: unknown; end: unknown };\n const startNum = getNumericValue({ type: \"number\", value: rangeValue.start } as Value);\n const endNum = getNumericValue({ type: \"number\", value: rangeValue.end } as Value);\n if (startNum !== null && endNum !== null) {\n return `${startNum}-${endNum}`;\n }\n return \"\";\n } else if (value.type === \"text\") {\n return value.value as string;\n }\n return \"\";\n};\n\n/**\n * Formats a Cooklang Quantity into separate value and unit strings.\n *\n * @param quantity - The Quantity object to format\n * @returns An object with `quantity` (numeric string) and `unit` (unit string)\n *\n * @example\n * ```ts\n * formatQuantityAmount({ value: { type: \"number\", value: 200 }, unit: \"g\" });\n * // { quantity: \"200\", unit: \"g\" }\n *\n * formatQuantityAmount(null);\n * // { quantity: \"\", unit: \"\" }\n * ```\n */\nexport const formatQuantityAmount = (\n quantity: Quantity | null | undefined,\n): { quantity: string; unit: string } => {\n if (!quantity) {\n return { quantity: \"\", unit: \"\" };\n }\n return {\n quantity: formatValue(quantity.value),\n unit: quantity.unit || \"\",\n };\n};\n\n/**\n * Generates complete text from step items by resolving references.\n *\n * @remarks\n * Converts a Step's items array into a readable string by:\n * - Keeping text items as-is\n * - Resolving ingredient references to \"name(quantity)\" format\n * - Resolving cookware references to their names\n * - Resolving timer references to their display values\n *\n * @param step - The Step object containing items to format\n * @param recipe - The parent CooklangRecipe for resolving references\n * @returns A concatenated string of all step items\n *\n * @example\n * ```ts\n * // For a step with text \"Cook \" + ingredient(pasta, 400g) + \" until done\"\n * generateStepText(step, recipe);\n * // \"Cook pasta(400g) until done\"\n * ```\n */\nexport const generateStepText = (step: Step, recipe: CooklangRecipe): string => {\n return step.items\n .map((item) => {\n if (item.type === \"text\") {\n return item.value;\n } else if (item.type === \"ingredient\") {\n const originalIngredient = recipe.ingredients[item.index];\n if (!originalIngredient) return \"\";\n\n const formatted = formatQuantityAmount(originalIngredient.quantity);\n const quantityText =\n formatted.quantity && formatted.unit\n ? `(${formatted.quantity}${formatted.unit})`\n : formatted.quantity\n ? `(${formatted.quantity})`\n : \"\";\n\n return `${originalIngredient.name}${quantityText}`;\n } else if (item.type === \"cookware\") {\n return recipe.cookware[item.index]?.name || \"\";\n } else if (item.type === \"timer\") {\n const timer = recipe.timers[item.index];\n if (!timer) return \"\";\n return timer.name || (timer.quantity ? quantity_display(timer.quantity) : \"\");\n }\n return \"\";\n })\n .join(\"\");\n};\n\n// Re-export useful functions from official package\nexport { getNumericValue, quantity_display };\n","/**\n * Node builders for Sankey diagram generation.\n *\n * @remarks\n * Factory functions for creating DAG nodes from Cooklang recipe elements.\n * Handles ingredients, process steps, and the final dish node.\n *\n * @packageDocumentation\n */\n\nimport {\n type CooklangRecipe,\n getNumericValue,\n grouped_quantity_display,\n grouped_quantity_is_empty,\n type Quantity,\n} from \"@cooklang/cooklang\";\nimport { generateStepText } from \"../formatter\";\nimport { DEFAULT_SECTION_NAME, FINAL_NODE_ID } from \"../constants\";\nimport type { DAGNode, NodeCategory, ExtractedValue } from \"../types/sankey\";\n\n/**\n * Extracts a numeric or text value from a Cooklang GroupedQuantity.\n *\n * @remarks\n * Attempts to extract a numeric value from fixed, scalable, or unknown\n * quantity types. Falls back to text representation if no numeric value\n * is available.\n *\n * @param groupedQuantity - The GroupedQuantity object from Cooklang parser\n * @returns An ExtractedValue with either numeric value or text label\n *\n * @example\n * ```ts\n * // For \"200g\" quantity\n * calculateIngredientQuantity(gq);\n * // { type: \"number\", value: 200, label: \"200g\" }\n *\n * // For \"some\" quantity\n * calculateIngredientQuantity(gq);\n * // { type: \"text\", label: \"some\" }\n * ```\n */\nexport const calculateIngredientQuantity = (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n groupedQuantity: any,\n): ExtractedValue => {\n if (grouped_quantity_is_empty(groupedQuantity)) {\n return { type: \"text\", label: \"unknown\" };\n }\n\n const displayText = grouped_quantity_display(groupedQuantity);\n\n const gq = groupedQuantity as {\n fixed?: Quantity | null;\n scalable?: Quantity | null;\n fixed_unknown?: Quantity | null;\n scalable_unknown?: Quantity | null;\n };\n\n const quantityToUse = gq.fixed || gq.scalable || gq.fixed_unknown || gq.scalable_unknown;\n\n if (quantityToUse) {\n const numericValue = getNumericValue(quantityToUse.value);\n if (numericValue !== null) {\n return { type: \"number\", value: numericValue, label: displayText || `${numericValue}` };\n }\n }\n\n return { type: \"text\", label: displayText || \"unknown\" };\n};\n\n/**\n * Creates DAG nodes for all ingredients in a recipe.\n *\n * @remarks\n * Processes the recipe's grouped ingredients to create nodes with:\n * - Unique IDs based on ingredient index\n * - Extracted quantity values for flow calculation\n * - Display labels with quantity information\n *\n * @param recipe - The parsed CooklangRecipe\n * @returns An object containing:\n * - `nodes`: Array of ingredient DAGNodes\n * - `indexMap`: Map from original ingredient index to node ID\n *\n * @example\n * ```ts\n * const { nodes, indexMap } = createIngredientNodes(recipe);\n * // nodes: [{ id: \"0\", name: \"flour\", value: 500, ... }, ...]\n * // indexMap: Map { 0 => \"0\", 1 => \"1\", ... }\n * ```\n */\nexport const createIngredientNodes = (\n recipe: CooklangRecipe,\n): { nodes: DAGNode[]; indexMap: Map<number, string> } => {\n const nodes: DAGNode[] = [];\n const indexMap = new Map<number, string>();\n\n recipe.groupedIngredients.forEach(([ingredient, groupedQuantity], index) => {\n const extracted = calculateIngredientQuantity(groupedQuantity);\n const value = extracted.type === \"number\" ? extracted.value : 1;\n const label = extracted.label;\n const ingredientId = index.toString();\n\n indexMap.set(index, ingredientId);\n\n nodes.push({\n id: ingredientId,\n name: ingredient.name,\n category: \"ingredient\" as NodeCategory,\n value,\n originalValue: value,\n label,\n inputs: [],\n outputs: [],\n metadata: {\n originalIndex: index,\n },\n });\n });\n\n return { nodes, indexMap };\n};\n\n/**\n * Creates DAG nodes for all cooking steps in a recipe.\n *\n * @remarks\n * Iterates through all sections and steps, creating process nodes with:\n * - Unique IDs based on section name and step number\n * - Generated text from step items (ingredients, cookware, timers)\n * - Initial value of 0 (calculated later via flow propagation)\n *\n * @param recipe - The parsed CooklangRecipe\n * @returns Array of process DAGNodes representing cooking steps\n *\n * @example\n * ```ts\n * const stepNodes = createStepNodes(recipe);\n * // [{ id: \"step_main_1\", name: \"Boil pasta(400g)\", category: \"process\", ... }]\n * ```\n */\nexport const createStepNodes = (recipe: CooklangRecipe): DAGNode[] => {\n const nodes: DAGNode[] = [];\n\n for (const section of recipe.sections) {\n for (const content of section.content) {\n if (content.type === \"step\") {\n const step = content.value;\n const stepText = generateStepText(step, recipe);\n const stepId = `step_${section.name || DEFAULT_SECTION_NAME}_${step.number}`;\n\n nodes.push({\n id: stepId,\n name: stepText || `手順 ${step.number}`,\n category: \"process\" as NodeCategory,\n value: 0,\n label: `${step.number}`,\n inputs: [],\n outputs: [],\n metadata: {\n stepNumber: step.number,\n sectionName: section.name || undefined,\n },\n });\n }\n }\n }\n\n return nodes;\n};\n\n/**\n * Creates the final node representing the completed dish.\n *\n * @remarks\n * The final node is the sink of the Sankey diagram where all\n * process flows converge. Its value is calculated by summing\n * all incoming flows.\n *\n * @param finalNodeName - Display name for the final dish node\n * @returns A DAGNode with category \"final\"\n *\n * @example\n * ```ts\n * const finalNode = createFinalNode(\"Carbonara\");\n * // { id: \"final_dish\", name: \"Carbonara\", category: \"final\", ... }\n * ```\n */\nexport const createFinalNode = (finalNodeName: string): DAGNode => ({\n id: FINAL_NODE_ID,\n name: finalNodeName,\n category: \"final\" as NodeCategory,\n value: 0,\n label: \"\",\n inputs: [],\n outputs: [],\n});\n","/**\n * Edge builders for Sankey diagram generation.\n *\n * @remarks\n * Factory functions for creating DAG edges that connect nodes.\n * Handles ingredient-to-step and step-to-final connections,\n * including step reference resolution.\n *\n * @packageDocumentation\n */\n\nimport { type CooklangRecipe, type Step } from \"@cooklang/cooklang\";\nimport { DEFAULT_SECTION_NAME } from \"../constants\";\nimport type { DAGNode, DAGEdge, TransformationType } from \"../types/sankey\";\n\n/**\n * Generates a unique step ID from section name and step number.\n *\n * @param sectionName - The recipe section name (null uses default)\n * @param stepNumber - The 1-indexed step number\n * @returns A unique step ID string (e.g., \"step_main_1\")\n *\n * @example\n * ```ts\n * getStepId(\"main\", 1); // \"step_main_1\"\n * getStepId(null, 2); // \"step_main_2\"\n * getStepId(\"sauce\", 1); // \"step_sauce_1\"\n * ```\n */\nexport const getStepId = (sectionName: string | null | undefined, stepNumber: number): string => {\n return `step_${sectionName || DEFAULT_SECTION_NAME}_${stepNumber}`;\n};\n\n/**\n * Extracts ingredient usage information from a recipe step.\n *\n * @param step - The Step object to analyze\n * @returns Array of objects containing ingredient index and step number\n *\n * @example\n * ```ts\n * // For a step using @pasta and @salt\n * extractIngredientUsageFromStep(step);\n * // [{ ingredientIndex: 0, stepNumber: 1 }, { ingredientIndex: 1, stepNumber: 1 }]\n * ```\n */\nexport const extractIngredientUsageFromStep = (\n step: Step,\n): Array<{ ingredientIndex: number; stepNumber: number }> => {\n const usages: Array<{ ingredientIndex: number; stepNumber: number }> = [];\n\n for (const item of step.items) {\n if (item.type === \"ingredient\") {\n usages.push({\n ingredientIndex: item.index,\n stepNumber: step.number,\n });\n }\n }\n\n return usages;\n};\n\n/**\n * Builds edges connecting ingredients to their consuming steps.\n *\n * @remarks\n * Analyzes the recipe to create edges from:\n * - Ingredient nodes to step nodes (for direct ingredient usage)\n * - Step nodes to other step nodes (for step references like \"the pasta from step 1\")\n *\n * Also updates the input/output arrays on the nodes for DAG traversal.\n *\n * @param recipe - The parsed CooklangRecipe\n * @param ingredientIndexMap - Map from ingredient index to node ID\n * @param nodes - Array of all DAG nodes (modified in place)\n * @returns Array of DAGEdges connecting ingredients/steps to steps\n *\n * @example\n * ```ts\n * const edges = buildIngredientToStepEdges(recipe, indexMap, nodes);\n * // [{ from: \"0\", to: \"step_main_1\", metadata: { ... } }, ...]\n * ```\n */\nexport const buildIngredientToStepEdges = (\n recipe: CooklangRecipe,\n ingredientIndexMap: Map<number, string>,\n nodes: DAGNode[],\n): DAGEdge[] => {\n const edges: DAGEdge[] = [];\n const nodeMap = new Map<string, DAGNode>();\n\n nodes.forEach((node) => nodeMap.set(node.id, node));\n\n for (const section of recipe.sections) {\n for (const content of section.content) {\n if (content.type === \"step\") {\n const step = content.value;\n const currentStepId = getStepId(section.name, step.number);\n const usedIngredients = extractIngredientUsageFromStep(step);\n\n for (const usage of usedIngredients) {\n const ingredient = recipe.ingredients[usage.ingredientIndex];\n\n // Check for step reference\n const relation = ingredient.relation;\n if (\n relation &&\n relation.relation?.type === \"reference\" &&\n relation.reference_target === \"step\"\n ) {\n const referenceTo = relation.relation.references_to;\n const sourceStepId = getStepId(section.name, referenceTo + 1);\n edges.push({\n from: sourceStepId,\n to: currentStepId,\n metadata: {\n stepNumber: step.number,\n transformationType: \"cooking\" as TransformationType,\n },\n });\n\n const sourceNode = nodeMap.get(sourceStepId);\n const targetNode = nodeMap.get(currentStepId);\n if (sourceNode && targetNode) {\n sourceNode.outputs.push(currentStepId);\n targetNode.inputs.push(sourceStepId);\n }\n continue;\n }\n\n const ingredientId = ingredientIndexMap.get(usage.ingredientIndex);\n if (ingredientId) {\n edges.push({\n from: ingredientId,\n to: currentStepId,\n metadata: {\n stepNumber: step.number,\n transformationType: \"preparation\" as TransformationType,\n },\n });\n\n const ingredientNode = nodeMap.get(ingredientId);\n const stepNode = nodeMap.get(currentStepId);\n if (ingredientNode && stepNode) {\n ingredientNode.outputs.push(currentStepId);\n stepNode.inputs.push(ingredientId);\n }\n }\n }\n }\n }\n }\n\n return edges;\n};\n\n/**\n * Builds an edge from the last step to the final dish node.\n *\n * @remarks\n * Connects only the last step to the final node, representing\n * the completion of the recipe. Also updates the input/output\n * arrays on the affected nodes.\n *\n * @param stepNodes - Array of step nodes in order\n * @param finalNode - The final dish node (modified in place)\n * @returns Array containing a single edge to the final node, or empty if no steps\n *\n * @example\n * ```ts\n * const edges = buildStepToFinalEdges(stepNodes, finalNode);\n * // [{ from: \"step_main_3\", to: \"final_dish\", metadata: { transformationType: \"completion\" } }]\n * ```\n */\nexport const buildStepToFinalEdges = (stepNodes: DAGNode[], finalNode: DAGNode): DAGEdge[] => {\n if (stepNodes.length === 0) return [];\n\n const lastStepNode = stepNodes[stepNodes.length - 1];\n\n lastStepNode.outputs.push(finalNode.id);\n finalNode.inputs.push(lastStepNode.id);\n\n return [\n {\n from: lastStepNode.id,\n to: finalNode.id,\n metadata: {\n transformationType: \"completion\" as TransformationType,\n },\n },\n ];\n};\n","/**\n * Sankey diagram data generator from Cooklang recipes.\n *\n * @remarks\n * Main module for transforming parsed Cooklang recipes into\n * Sankey diagram data structures. Orchestrates the DAG building,\n * value calculation, and normalization pipeline.\n *\n * @packageDocumentation\n */\n\nimport type { CooklangRecipe } from \"@cooklang/cooklang\";\nimport {\n DEFAULT_FINAL_NODE_NAME,\n DEFAULT_MIN_LINK_VALUE,\n DEFAULT_NORMALIZATION,\n} from \"../constants\";\nimport type { SankeyNode, SankeyLink, SankeyData, DAGNode, DAGEdge } from \"../types/sankey\";\nimport { normalizeIngredientValues } from \"./normalizer\";\nimport { buildDAGAndSort, calculateNodeValues } from \"./dag\";\nimport { createIngredientNodes, createStepNodes, createFinalNode } from \"./node-builders\";\nimport { buildIngredientToStepEdges, buildStepToFinalEdges } from \"./edge-builders\";\n\n/**\n * Configuration options for Sankey diagram generation.\n *\n * @example\n * ```ts\n * const options: SankeyGeneratorOptions = {\n * finalNodeName: \"Carbonara\",\n * normalization: \"logarithmic\",\n * minLinkValue: 0.1\n * };\n * ```\n */\nexport interface SankeyGeneratorOptions {\n /**\n * Display name for the final dish node.\n * @defaultValue \"完成品\"\n */\n finalNodeName?: string;\n /**\n * Method for normalizing ingredient values.\n * - `logarithmic`: Compress large ranges (recommended)\n * - `linear`: Linear scaling\n * - `none`: Use raw values\n * @defaultValue \"logarithmic\"\n */\n normalization?: \"logarithmic\" | \"linear\" | \"none\";\n /**\n * Minimum value for links to ensure visibility.\n * @defaultValue 0.1\n */\n minLinkValue?: number;\n}\n\nconst DEFAULT_OPTIONS: Required<SankeyGeneratorOptions> = {\n finalNodeName: DEFAULT_FINAL_NODE_NAME,\n normalization: DEFAULT_NORMALIZATION,\n minLinkValue: DEFAULT_MIN_LINK_VALUE,\n};\n\n/**\n * Transforms DAG nodes and edges into final Sankey diagram data.\n *\n * @remarks\n * Internal function that:\n * 1. Normalizes ingredient values using the specified method\n * 2. Calculates flow values for all nodes via DAG propagation\n * 3. Converts DAG structures to Sankey-compatible format\n * 4. Applies minimum value constraints to links\n *\n * @param dagNodes - Array of DAG nodes with dependency info\n * @param dagEdges - Array of DAG edges\n * @param sortedNodeIds - Node IDs in topological order\n * @param options - Generation options with all defaults applied\n * @returns Complete SankeyData structure ready for visualization\n *\n * @internal\n */\nconst buildSankeyData = (\n dagNodes: DAGNode[],\n dagEdges: DAGEdge[],\n sortedNodeIds: string[],\n options: Required<SankeyGeneratorOptions>,\n): SankeyData => {\n const ingredientNodes = dagNodes\n .filter((node) => node.category === \"ingredient\")\n .map((node) => ({\n id: node.id,\n name: node.name,\n category: node.category,\n value: node.value,\n label: node.label,\n originalValue: node.originalValue,\n metadata: node.metadata,\n }));\n\n const normalizedValueMap = normalizeIngredientValues(ingredientNodes, options.normalization);\n\n const normalizedDAGNodes = dagNodes.map((node) => {\n if (node.category === \"ingredient\") {\n return {\n ...node,\n value: normalizedValueMap.get(node.id) ?? node.value,\n };\n }\n return node;\n });\n\n const finalCalculatedValues = calculateNodeValues(normalizedDAGNodes, sortedNodeIds);\n\n const sankeyNodes: SankeyNode[] = dagNodes.map((dagNode) => {\n const finalValue = finalCalculatedValues.get(dagNode.id) || 0;\n return {\n id: dagNode.id,\n name: dagNode.name,\n category: dagNode.category,\n value:\n dagNode.category === \"ingredient\"\n ? (normalizedValueMap.get(dagNode.id) ?? dagNode.value)\n : finalValue,\n label: dagNode.label,\n originalValue: dagNode.originalValue,\n metadata: dagNode.metadata,\n };\n });\n\n const sankeyLinks: SankeyLink[] = dagEdges.map((edge) => {\n const sourceValue = finalCalculatedValues.get(edge.from) || 0;\n const sourceNode = dagNodes.find((n) => n.id === edge.from);\n\n let linkValue = sourceValue;\n if (sourceNode && sourceNode.outputs.length > 1) {\n linkValue = sourceValue / sourceNode.outputs.length;\n }\n\n return {\n source: edge.from,\n target: edge.to,\n value: Math.max(options.minLinkValue, linkValue),\n originalValue: sourceValue,\n metadata: edge.metadata,\n };\n });\n\n return {\n nodes: sankeyNodes,\n links: sankeyLinks,\n };\n};\n\n/**\n * Generates Sankey diagram data from a parsed Cooklang recipe.\n *\n * @remarks\n * This is the main entry point for the library. It transforms a parsed\n * Cooklang recipe into a Sankey diagram data structure by:\n * 1. Creating nodes for ingredients, steps, and the final dish\n * 2. Building edges representing ingredient flow\n * 3. Performing topological sort for correct value propagation\n * 4. Normalizing values for balanced visualization\n *\n * @param recipe - A parsed CooklangRecipe object\n * @param options - Optional configuration for generation\n * @returns SankeyData structure, or null if generation fails\n *\n * @example\n * ```ts\n * import { CooklangParser, generateSankeyData } from 'cooklang-sankey';\n *\n * const parser = new CooklangParser();\n * const [recipe] = parser.parse(`\n * @pasta{400g} を茹でる。\n * @卵{3個}と@チーズ{100g}を混ぜる。\n * `);\n *\n * const data = generateSankeyData(recipe, {\n * finalNodeName: \"Carbonara\",\n * normalization: \"logarithmic\"\n * });\n * ```\n */\nexport const generateSankeyData = (\n recipe: CooklangRecipe,\n options?: SankeyGeneratorOptions,\n): SankeyData | null => {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n try {\n const { nodes: ingredientNodes, indexMap: ingredientIndexMap } = createIngredientNodes(recipe);\n\n const stepNodes = createStepNodes(recipe);\n const finalNode = createFinalNode(opts.finalNodeName);\n\n const allNodes = [...ingredientNodes, ...stepNodes, finalNode];\n\n const ingredientToStepEdges = buildIngredientToStepEdges(recipe, ingredientIndexMap, allNodes);\n const stepToFinalEdges = buildStepToFinalEdges(stepNodes, finalNode);\n const allEdges = [...ingredientToStepEdges, ...stepToFinalEdges];\n\n const sortedNodeIds = buildDAGAndSort(allNodes, allEdges);\n\n return buildSankeyData(allNodes, allEdges, sortedNodeIds, opts);\n } catch (error) {\n console.error(\"Error generating sankey data from cooklang:\", error);\n return null;\n }\n};\n\n/**\n * Validates and optimizes Sankey data for visualization.\n *\n * @remarks\n * Performs three optimization steps:\n * 1. **Remove invalid links**: Filters out links referencing non-existent nodes\n * 2. **Remove orphaned nodes**: Keeps only nodes connected to valid links\n * (final nodes are always preserved)\n * 3. **Normalize link values**: Scales values relative to minimum for\n * stable visualization (ensures all values >= 1)\n *\n * @param data - The SankeyData to optimize\n * @returns A new SankeyData with optimizations applied\n *\n * @example\n * ```ts\n * const rawData = generateSankeyData(recipe);\n * if (rawData) {\n * const optimized = optimizeSankeyData(rawData);\n * // Use optimized data for visualization\n * }\n * ```\n */\nexport const optimizeSankeyData = (data: SankeyData): SankeyData => {\n // Create a Set of existing node IDs for O(1) lookup\n const existingNodeIds = new Set(data.nodes.map((node) => node.id));\n\n // Step 1: Remove invalid links\n // Filter out links where source or target node doesn't exist\n const validLinks = data.links.filter((link) => {\n const sourceExists = existingNodeIds.has(link.source);\n const targetExists = existingNodeIds.has(link.target);\n\n if (!sourceExists) {\n console.error(`Missing source node: ${link.source}`);\n }\n if (!targetExists) {\n console.error(`Missing target node: ${link.target}`);\n }\n\n return sourceExists && targetExists;\n });\n\n // Step 2: Remove orphaned nodes\n // Collect node IDs that are connected to valid links\n const connectedNodeIds = new Set<string>();\n validLinks.forEach((link) => {\n connectedNodeIds.add(link.source);\n connectedNodeIds.add(link.target);\n });\n\n // Keep only nodes connected to links, or final nodes (always preserved)\n const filteredNodes = data.nodes.filter(\n (node) => connectedNodeIds.has(node.id) || node.category === \"final\",\n );\n\n // Step 3: Normalize link values\n // Scale values relative to minimum for stable visualization\n let normalizedLinks = validLinks;\n if (validLinks.length > 0) {\n const minValue = Math.min(...validLinks.map((link) => link.value));\n if (minValue > 0) {\n // Divide by min value and clamp to ensure value >= 1\n normalizedLinks = validLinks.map((link) => ({\n ...link,\n value: Math.max(1, link.value / minValue),\n }));\n }\n }\n\n return {\n nodes: filteredNodes,\n links: normalizedLinks,\n };\n};\n"]}
|